VoIP Server
by Ben on Dec.22, 2009, under Projects, VoIP Server
This project was featured on the Make Blog in October 2009.
I’ve always wanted to embed an Ethernet port onto something. This, I claim, is an Electrical Engineer’s trait. I also just like networks.
This project is, without a doubt, the most ambitious I have attempted to date. The finished product (or almost-finished product, as the case may be) has several significant subsections. Aside from the hopefully-obvious TCP/IP component and core processing circuit, there’s a file retrieval subsystem, an audio subsystem, and a user interface of sorts. We’ll explore these in (roughly) order of increasing complexity.
Core Circuit
The board is centered around a PIC18F26K20 (datasheet), currently my favorite microcontroller. It’s fast (64MHz), cheap (about $4), and has lots of memory (4k RAM, 64K flash, 1k EEPROM) and peripherals (too many to list). Pair this with the ENC28J60 (datasheet) and you’ve got a small-footprint Ethernet solution just looking for a problem. With these two chips, a 3.3V source, and an Ethernet jack, you’ve got a very tiny and reasonably powerful and flexible networking device. The ENC28J60 is connected to the PIC18F26K20′s SPI port, as well as some general-purpose I/O on PortB for such things as the /CS and /RESET pins.
User Interface
From a coding perspective, the user interface is just a giant state machine. A variable inside the code keeps track of which state the UI is currently in, and button presses move the state machine to new states. The LCD is updated as appropriate for each state change. A few events force the UI state to change without user intervention. Examples are an alarm going off, a VoIP call coming in, or a watchdog timer reset.
Each time the program passes through the main loop, it checks to see which UI state it is currently using. The code then jumps to the appropriate location and runs the corresponding code. A button press in most any state will transition the UI to another state, which is then executed on the next pass through the loop.
It’s a simple idea, really. The fact that there are some 35 UI states makes it such that the UI code takes up about 1,700 lines of code.
Audio
The audio subsystem on this board is actually pretty simple, once you get past the math. The PIC has two PWM outputs, each of which drives a low-pass Butterworth filter with a cutoff frequency of approximately 4kHz. This, according to our friend Nyquist, corresponds nicely to the audio sampling rate of 8kHz. Each filter is a simple RC circuit buffered with an op-amp and followed up by a second-order filter of the Sallen-Key architecture. This gives an overall third-order transfer function. Without increasing the number of op-amps, it would have been nearly trivial to increase the filter order to four, but then the possibility of “nice” component values would have vanished.
To determine the cutoff frequency, let
R=R1=R2=R3
C=C1=0.5C2=2C3
Then
fC=1/(2?RC)
Verification of this fact is left as an exercise to the reader (I’ve always wanted to say that!). The values of R4 and R5 should be equal and about 10k; these two resistors serve as a voltage divider to generate an “artificial ground” rail halfway between VCC and GND. C4 is a decoupling capacitor and should be about 0.1uF. When I built these boards, I used
R1=R2=R3=390 ohms,
C1=0.1uF,
C2=0.22uF, and
C3=0.047uF.
These are standard values but differ slightly from the computed values above. This will effect a small change in the frequency response of the circuit, but the overall response is still close to that of a third-order low-pass Butterworth filter.To actually play back audio, the process is quite simple. Eight thousand times per second, the code simply writes a new value to the PWM duty cycle register. That’s it. The value corresponds to the instantaneous value of the sound waveform at that instant. Because the PWM output is connected to a low-pass filter, the average value of the PWM waveform over any one period is changed to a relatively constant voltage. In this way, a 250kHz square wave is modulated to sound like audio of any arbitrary frequency up to 4kHz.
File System
The file system used in this project is loosely modeled after Microchip’s MPFS. The firmware handles filenames as a string exactly as you’d expect to see the filename written. For example, if you call the fopen function with a pointer to “index.htm\0″, the firmware will attempt to open a file with the name “index.htm.” Not bad for a homebrew assembly-language solution. Let’s start by looking at the three major areas designated in the 4MB EEPROM chip (diagram not to scale):
| Reserved (64kB) | File index (variable; multiple of 512 bytes) | File data (variable size) |
The entire memory is both physically and logically divided into 512-byte blocks. Not only does this simplify address generation and storage, but it also corresponds nicely with the 512-byte block size of TFTP transfers.
The first 64kB of memory is reserved for storage of incoming messages from the Telnet and HTTP servers. Each record is allotted 512 bytes, and is stored as follows:
| Day | 1 byte | 0=Sunday 1=Monday ::: 6=Saturday |
| Time | 3 bytes | Byte 0 = hours (0-23) Byte 1 = minutes (0-59) Byte 2 = seconds (0-59) Encoded as packed BCD |
| Remote IP address | 4 bytes | Byte 0 = first octet ::: Byte 3 = fourth octet |
| Message | 256 bytes | Message is zero-padded at end. |
| Unused | 248 bytes | Should be set to zero |
The next major section is the file index. The firmware uses this to determine whether a given file name exists in memory, and if so, where it starts and how long it is. Each file record entry is 16 bytes and is formatted as follows:
| File name | 8 bytes | Unused positions padded with zero |
| File extension | 3 bytes | Unused positions padded with zero |
| Starting block number | 2 bytes | This field contains the starting block number of the file. This is the most significant 16 bits of its address. |
| Length | 3 bytes | The length of the file in bytes |
Since each entry is 16 bytes in length, 32 of them fit into a 512-byte block of EEPROM. Any unused entries in any given block are filled with zero. The table must be an integer multiple of 512 bytes in length. If the unpadded table is a multiple of 512 bytes in length, a 512-byte block of all zeroes must follow the table. This is so the firmware can tell where the table ends.
The third major section is where all the files are actually stored. Each file must start on a page boundary, that is, its starting address must be a multiple of 512. Each file is padded with enough NULL bytes at the end to make it an even number of 512 bytes in length.
This specification makes it sound much harder than it actually is. The firmware is pretty simple, weighing in at less than 100 lines to process the file name, find it, and open it.
This system is not without limitations, however. Filenames are limited to “8.3″ format — just as in the days of DOS — and the maximum file size is 16MB. This is not too big a concern when you consider that the EEPROM size is only 4MB. There is also no provision for time- or date-stamping files. Finally, there is no handler in place for duplicate file names. The first file of a given name the firmware finds is the one it will open.
TCP/IP
I’ll write this description some day. Let’s just say that writing an IP stack in 8-bit assembler isn’t trivial. My code supports ARP, ICMP, IP, UDP, TCP, TFTP, NTP, HTTP, and Telnet. Like I said, no small task.
Files for download
By downloading these files, you agree that you will in no form take credit for their creation, make money off of them, turn them in as your own work for a school project, or otherwise gain from them (except, of course, to build a cool project).
A zipped archive of all necessary documents to recreate the project is located here.
Update! A reader by the name of Ramon built this board and got it to work, but did not like my file system generation utility. I don’t blame him; my version is pretty ugly. As he is a .NET developer by profession, he created this program. The tool allows the use of the Windows GUI to select files, then builds the required database for the PIC to decode. The really neat part is that his version includes a built-in TFTP tool to upload files to the board. Very slick.










March 25th, 2010 on 9:19 AM
Hi,
Man it is awesome, I was read whole article, but only problem I am not a technical man like you, It is hard to understand formulas like C1=0.1uF,
I study at telecommunication faculty (university) in my country, and it is hard work to learn.
C2=0.22uF, and C3=0.047uF, but maybe for physics it will be great for project “How to make voip server”. Only thing what i cant understand , what will be next ? How to use it? For what? It will be like Google Voice servers ?
Ben you are great, keep doing good work, it is good resource for info (what you wrote).
Thanks!
August 3rd, 2011 on 4:13 PM
Just one thing,I’d like some light on the time frame for this scale,and more specifically the core networking crunch code.Theres also that factor of debugging,but one question at a time.