
Part 15 Section 15.247 and Section 15.249 of the FCC rules limits the maximum power of 900MHz band radio devices to approximately 1mW depending on the exact antenna used. This limit is increased to 1 Watt if the device uses frequency hopping techniques meaning the device changes frequencies rapidly instead of using a fixed frequency. The idea is that if interference occurs, the interference will only last for a brief period.
I had used frequency hopping techniques in a separate project, but the transmission in that project was unidirectional. I wanted to see if I could expand my algorithms to bidirectional communication. I decided to build an RF modem to link two personal computers together.
The hardware for the modem is built out of very generic parts. It uses a very basic RF module that does not have the MAC layer built-in like the very popular MaxStream modules. I wanted to implement this myself. The modem interface is USB and the modem implements a dual CDC + WinUSB interface. The CDC interface is the serial port emulation that carries the data and WinUSB is used for configuring the modem through a custom Win32 application. The modem does not implement error detection and correction, but neither do telephone modems. A very basic flow control mechanism is implemented. I have used zmodem for transferring files between PCs and have used PPP to build a Internet connection with the modem. The modem is capable of approximately 70 kbps when used with PPP.


The Semtec DP1205 RF module is the core of the modem. This module is a Semtec XE1205 transceiver plus an antenna switch and other required discrete components that are not built on the XE1205 chip. The XE1205 is a very generic 900MHz FM module without any built-in protocol logic. It is very similar to the modules produced by Linx Technologies. The module has an SPI interface for changing between transmitter and receiver modes and for setting the RF frequency and the FM frequency deviation. The data connection directly drives the FSM circuity. For this project, the module uses a 100KHz deviation with a transmission power of 15dBm.
The Microchip PIC 18F2550 processor with built-in USB controls the Semtec module and handles all the protocol details. The processor runs at 5.0v and the Semtec module runs at 3.3v. Discrete logic buffers are used to translate the voltage levels. The processor is clocked at 16MHz. The SPI connection to the RF module is bit banged. The data connection of the module is connected to the UART of the PIC which uses NRZ encoding clocked at 100kbps. The processor drives three status LED that are on the outside of the enclosure. One of the LEDs is used to indicated that the modem is active as opposed to sleeping. A second LED is flashed whenever valid data is transmitted or received. A third LED is reserved for future expansion.
The custom PCB has a few extra components: USB jack, 3.3v voltage regulator, and a RP-SMA antenna connector. The enclosure is a cheap generic plastic box from Radio Shack.
The first problem encountered when building a bidirectional link is that almost all modern RF equipment including the XE1205 is half duplex. The module can only be transmitting or receiving but not both at the same time. The solution is what I call the Ping-Pong algorithm. Designate one modem as the master and the other modem as the slave. The master sends a PING packet to the slave which contains 0 to 64 bytes. The slave responds to the master with a PONG packet that also contains 0 to 64 bytes of data. If the master doesn’t receive a PONG to the PING, another PING packet is sent after a timeout. The slave never sends any data unless it receives the PING. This algorithm of data bouncing back and forth allows bandwidth to be allocated between the two directions based on the amount of data that is ready to be sent.
The modem does not implement error detection and correction. This is done by the file transfer protocol such as zmodem or by the TCP/UDP layers of TCP/IP when PPP is used for an Internet link. The modem does implement basic flow control otherwise huge amounts of data would be lost. This is implemented by adding a boolean flag at the end of the PING and PONG packets. If the master or slave is capable of receiving more data the boolean is marked true meaning clear to send. The slave only sends when a PING is received so it always gets the clear to send flag. This isn’t true for the master since the PING or PONG could be lost. If the PING times out on the master, then it is assumed that the slave is currently incapable of receiving more data.
| Offset | Size | Function |
| None | 1 | Preamble - single 0xAA |
| 0 | 4 | Packet Identifier |
| 4 | 1 | Data payload size not including headers or trailers |
| 5 | 1 | 2's completement of payload size |
| 6 | N=0 to 64 | Payload data |
| N+6 | 1 | Clear to send boolean(true = clear to send) |
The packet starts with a preamble of alternating 1s and 0s. This is necessary since most FM RF modules can not lock a DC signal and the preamble adds some high frequency components to the signal before the actual data. The preamble is followed by a magic sequence of 4 bytes to make it possible to filter out random noise when the other end is the link is not actively transmitting. Both the payload size and the 2s complement of the size are sent to add error checking to the header. The packet is only valid if the entire header makes it through a set of consistency checks.
Now I’m going to describe the frequency hopping. The modems contain a fixed table of 53 frequencies that hardcodes the order that frequencies will be used. Time is divided into 200ms frames which the master and slave both use the same frequency. Both the master and slave maintain a clock that is used controls actions and when to change to the next frequency in the table. During the frame, PINGs and PONGs are sent back and forth until one of several markers is hit. The MasterStopPingTime marker marks the time into the frame which the master will no longer send a PING. The SlaveStopDataSendTime designates the cutoff time when the slave will respond with PING packets but not include any data in the packet. The FrameStopTime marks the start of a quite period for the frame. The NextFrameTime starts the next frame. Channels are changed and the transmitter of the master is powered up to send the first PING of the frame. If the master fails to receive a PONG, the entire frame and no more PINGs will be sent until the start of the next frame.
| Frame Offset | Marker | Function |
| 183ms | MasterStopPingTime | Master will stop sending PINGs |
| 185ms | SlaveStopDataSendTime | Slave will stop sending data in the PONGs |
| 195ms | FrameStopTime | Start of quiet period |
| 200ms | NextFrameTime | Start of next frame(Frequencies are changed) |
To start this process the master and slave need a mechanism to synchronize clocks and the current index into the frequency table. The master always follows the table. It never waits for the slave. The slave starts by picking a frequency and waiting for the first PING packet of the frame, called the SYNC frame. The SYNC packet always has the most significant bit of the payload size set to 1. Upon receiving the sync frame the slave synchronizes the clock to the master’s clock. The frequency table contains no duplicates so the frequency at which the SYNC packet is received tells the slave the master’s current position in the table. Clocks are always synchronized on SYNC packets even if the link is active to prevent clock drift. While synchronizing, if the slave does not receive a SYNC packet for 60 frames, it is assumed this channel has too much interference and the next entry in the table is selected. If the link is synchronized and no packets are received for 60 frames, the link is considered unsynchronized and the slave parks at the current channel until the link is resynchronized or another 60 frames pass.
| Transmitter power | 15dBm |
| Byte encoding | NRZ |
| UART bitrate | 100KHz |
| Number of frequencies | 53 |
| Frame length | 200ms |
| Max time for slave to synchronize | 200ms*53 = 10.6s |
| Max payload per packet | 64 bytes |
The software for the RF modem is composed of several components. These are the RF modem firmware, INF files for CDC the custom interfaces, a C++ library that wraps WinUSB, a graphical Win32 application(pingpong-control) the sets the mode of the modem and identifier, and a command line utility for flashing(usbdevprogramer). I am giving out the source code as a learning tool for other people. I don’t care what you do with it but I am not responsible for any damage that might happen from using it. NOTE: Improper use of hardware or software may cause serious injury or death.:)
I use Visual Studio 2005 Professional but I have heard that the project will build with Visual Studio Express. To build the project it is necessary to download the latest version of the WDK to get the headers and library files for WinUSB. The default directories in Visual studio need to be modified to point to the directories of these resources in the WDK. If you use Visual Studio Express it's necessary to recreate all the project files and change the default calling convention to _stdcall.
| Directory | Function |
| inc | General Include Directory |
| miscdevices/pingpong-cdc | Modem firmware(Build first) |
| usbdvcpdriver | INF and WinUSB resources |
| usbdrvclilib | C++ WinUSB wrapper(Build second) |
| deviceutils/pingpong-control | Modem configuration utility(Build third) |
| deviceutils/usbdevprogramer | Programmer command line utility(Build last) |
The modem firmware is based on a modified version of the Microchip USB framework. The framework has been collapsed into a single source file with callbacks between the framework and the main code. The framework( in my case it’s more of a library ) has been modified to use interrupts instead of the traditional polling method. Instead of calling USBDriverService in a loop, this function is called whenever the USB interrupt fires. The library is response for handling control requests on endpoint 0. The body code is responsible for handling I/O on all other endpoints.
The RF Modem is a composite device of a CDC serial device and a custom WinUSB interface. With CDC all of the sample data is transferred through a separate endpoint then control requests. CDC requests such as setting the baud rate or line coding are simply stubs but are through endpoint 0(the control endpoint.) WinUSB uses it’s own endpoint. Since each function of the card has a separate endpoint, each of the functions can be implemented at a different priority level without preemption problem. The P18F2550 has two interrupt priorities plus the main handler. This gives a total of three priorities. Handling the RF protocol is the most critical function this it was given the highest priority. Next comes control endpoint requests which include the standard requests plus CDC requests. Since these functions have relatively low timeouts, these were assigned low priority. WinUSB functions are in the main body code because actions such as configuring the modem, querying the firmware version, settings the serial number, or flashing the firmware are not time critical at all.
| Endpoint | Priority | Function |
| EP 2 | Interrupt(High) | Sending data through the communication link |
| EP 0 | Interrupt(Low) | Control Endpoint function and CDC requests |
| EP 1 | Main(Background) | Non standard functions(configuring the modem) |
The core of the firmware is the file rf.c which implements the code for the wire protocol. The source is organized as a big finite state machine. Here packets are built, received and transferred to and from the CDC data endpoint. IMPORTANT NOTE: The size of the CDC data endpoint is exactly 64 bytes which is as the maximum packet payload. No intermediate buffering is done in the processor. Data on the endpoint is mapped directly into a packet payload. I tried adding buffering but a 16MHz clocked PIC was not fast enough for the extra workload and only gained a very minimal performance increase with increased errors. The names of the frame marker variables are the same as the names given in the wire protocol section and are calculated by the ComputeFrameTimes function. Timer 2 is the millisecond clock used in the wire protocol and timer 0 is the transmitter powerup timer.
| State | Function |
| PINGPONG_STATE_POWERINGTRANSMITTER | The transmitter is being powered |
| PINGPONG_STATE_TRANSMITTING | The modem is actively transmitting |
| PINGPONG_STATE_RECEIVING | The modem is actively receiving |
| PINGPONG_STATE_QUIETTIME | This is the quiet part of the frame |

PINGPONG-CDC needs to be configured before use to set the link identifier (first 4 bytes of packet) and the mode of the modem (master/slave). My solution was to write a small GUI Win32 application for this called PINGPONG-CONTROL. In addition to configuring the modem, the application displays link statistics which are useful for debugging problems and generic link monitoring. PINGPONG-CONTROL uses WinUSB through my WinUSB C++ wrapper for this custom communication.
Eagle Schematic and Layout for PINGPONG-CDC