The RFM12 / RFM12B radio communication modules

Few tips for using RFM12 modules:

  • There are three or more sources of information concerning registers and commands of this module.
    1. Documentation provided by the module manufacturer - Hope RF
    2. Documentation by the manufacturer of "silicon" used in mudules - TRC101
    3. Si4021 device documentation by Silicon Labs earlier Integration Associates
    I find the second one, the most useful and accurate. Unfortunately every of the mentioned papers provides some details, that are not covered in other documents.
  • The B series (RFM12B) should be supplied with voltage not exceeding 3.6V, while the version without B index works with voltages reaching 5.4V. It is clearly not recommended by the manufacturer, but the B-series modules seem to work correctly even powered with 5V.
  • The module manufacturer did not provided pin for sensing analog RSSI (Received Signal Strength Indicator) signal coming from the chip. However, this signal is easily reachable at one of the capacitor pads - that is located nearest the nSEL pin (in SMD version).
  • The SMD version of the module is very vulnerable to be damaged by a high temperature, prolongated soldering can easily cause a permanent damage to it. The contacts of the board are very delicate, and often not survive the attempt of desoldering the module.
unsoldered RFM12
  • Part of the documentation suggests that the SPI interface is able to work with 20 MHz (inverse of sum "clock high time" and "clock low time"), but further you may find, that the SPI clock cannot be faster than 2.5 MHz - when the receive FIFO is used.
  • Receiver baseband bandwidth should be at least two times wider than transmitter deviation.
  • Before initializing module, you may want to reset it. The reset command is 0xFE00.
  • The documentation says, that after power-on reset, the module will not accept commands for about 150 ms. The time is specified for "typical values" - so may be longer.

Exemplary source code


The described circuit offers pretty simple user interface: two LEDs and one momentary switch. While in idle state, the one of the diodes blinks and the other is turned off. After pushing the button the later diode of the transmitter is turned on and a short radio transmission is performed. When the receiver detects a valid data packet it also turns it's LED on.

The program was tested with the internal RC oscillator running at the frequency of 8 MHz, but it should work even at very different frequencies. The RFM12B module is connected to the hardware SPI controller, with the nSEL line connected to the SS pin of the microcontroller.

The demo code may be adapted to the other board or project by simply changing the definitions given below. The macros for operating on pins defined in such manner are enclosed in the file definicje.h. The program were developed using an ATMEGA88 microcontroller, but without any changes it should compile for ATMEGA48 and ATMEGA168 processors. Compilation for the ATMEGA8 on similar microcontrollers will require few cosmetic changes to the code, in the functions related to timing (namely, the TIFR2 register should be renamed to TIFR, and the TCCR2B to TCCR2).

#include <avr/io.h>
#include <util/delay.h>
#include <inttypes.h>
#include "definicje.h"

//Definitions of SPI pins in the microcontroller
#define SPI_CS        2
#define SPI_SCK       5
#define SPI_MISO      4
#define SPI_MOSI      3

//Peripherals of the "demo" board, two LEDs and a pushbutton
#define LED_GREEN      1
#define LED_RED        0
#define BTN            0
#define BTN_PORT       PORTC

The following procedures provide means for managing a simple timeout mechanism, using timer two. They are used for terminating the transmit and receive operations, so when there is no transmission or there are some troubles with the module, the program can continue its execution - in this particular case: blinking the LED. The first procedure configures the timer, clears it and then enables. The second one returns returns "true" (non zero) when the timer has overflowed, which will occur after 1024 * 256 clock cycles (about 32 ms @ 8 MHz).

//Setup a simple timeout
inline void timeout_init(void)
    TCCR2B = 0;                                //disable the timer
    TCNT2 = 0;                                //start counting from 0
    TCCR2B = 7;                                //turn the timer on (prescaler 1024)
    TIFR2 = (1 << TOV2);                    //clear the overflow flag

//Test if the timeout expired
inline uint8_t timeout(void)
    return (TIFR2 & (1 << TOV2));            //return non-zero if the timer overflowed

The following functions tests if the RFM12B is ready for starting transmission or reading the next byte from the queue. The chosen method is reading the SO line, performed after the activation of the module.

//Test if the module is ready for sending / receiving next byte
uint8_t rf12_is_ready(void)
    CLEAR(SPI_CS);                            //activate the module
    _delay_us(1);                            //let it respond
    uint8_t r = READ(SPI_MISO);                //read the SO line (first bit of status word)
    SET(SPI_CS);                            //deactivate the module
    return r;                                //return the value of the first bit

The same piece of code is used for both writing the data to the module and for retrieving it back - the rf12_trans function. Its task is exchanging a two-byte word with the RFM12B. The functions for reading the module queue (rf12_rxbyte) and writing to the transmit register (rf12_txbyte) are heavily based on it.

//Exchange a word (two bytes, big-endian) with the module
uint16_t rf12_trans(uint16_t to_send)
    uint16_t received = 0;                    //buffer for data we are going to read
    CLEAR(SPI_CS);                            //activate the module
    SPDR = (to_send >> 8) & 0xFF;            //send the upper byte
    while (!(SPSR & (1 << SPIF)));            //wait until the transmission is complete
    received = SPDR;                        //store received byte
    received <<= 8;                            //move it on its proper position
    SPDR = (0xFF & to_send);                //send the lower byte
    while (!(SPSR & (1 << SPIF)));            //wait until the transmission is complete
    received |= SPDR;                        //store received byte
    SET(SPI_CS);                            //deactivate the module
    return received;                        //return the data from the module

//send one byte through the radio
void rf12_txbyte(uint8_t b)
    while (!rf12_is_ready())                //wait while the module is not ready...
        if (timeout())                        //...if it is too long...
            return;                            //...abort the operation
    rf12_trans(0xB800 | b);                    //send the desired byte

//receive one byte through the radio
uint8_t rf12_rxbyte(void)
    while (!rf12_is_ready())                //wait while the module is not ready...
        if (timeout())                        //...if it is too long...
            return 0;                        //...abort the operation
    return rf12_trans(0xB000);                //read the byte from the receive FIFO

The process of configuring the module requires only resetting the circuit and writing about a dozen registers. The particular values, that has to be written, were taken from the exemplary code provided by HopeRF. Unfortunately, there is no way of verifying if the write operations were successful.

//adaptation to use the statements from rf12b_code.pdf
#define RFXX_WRT_CMD(x) rf12_trans(x)

//prepare the radio module
void radio_config(void)
    OUTPUT(SPI_CS); OUTPUT(SPI_MOSI);        //setup the directions...
    OUTPUT(SPI_SCK); INPUT(SPI_MISO);        //...of the SPI pins
    SET(SPI_CS);                            //initially deactivate the module
    SPCR = (1 << SPE) | (1 << MSTR);        //turn the SPI on
    SPSR = 0;                                //with the single speed
    _delay_ms(10);                            //wait a moment
    rf12_trans(0xFE00);                        //send the reset command
    _delay_ms(150);                            //wait for reset to complete
    //Example setup
    RFXX_WRT_CMD(0xC483);//@PWR,NO RSTRIC,!st,!fi,OE,EN
    RFXX_WRT_CMD(0x9850);//!mp,90kHz,MAX OUT
    RFXX_WRT_CMD(0xE000);//NOT USE
    RFXX_WRT_CMD(0xC800);//NOT USE

Sending the data with the RFM12B module is relatively simple, it requires only putting it into the transmit mode, sending the preamble, synchronization bytes and the actual data. According the vendors suggestion, after the transmission the two dummy bytes should be sent. Neglecting to do so, may cause distortion to the last byte received by the listening modules. After transmission is finished the module should be brought back to the idle state.

//Send data packet through the radio
void radio_send(uint8_t * buffer, uint8_t len)
    timeout_init();                            //setup the timeout timer
    rf12_trans(0x8238);                        //start transmitter
    rf12_txbyte(0xAA);                        //send the preamble, four times 0xAA
    rf12_txbyte(0x2D);                        //then the predefined sync words
    rf12_txbyte(0xC0);                        //and a secret 0xC0DE
    rf12_txbyte(len);                        //next the length of the data
    while (len--)
        rf12_txbyte(*buffer++);            //and then the data itself
    rf12_txbyte(0x00);                        //finish the transmission with two dummy bytes
    while (!rf12_is_ready() && !timeout());    //wait for the completion of the send operation
    rf12_trans(0x8208);                        //go to idle, disable the transmitter

The procedure for data receiving is a bit more complicated. Firstly it configures the timer and switches the module to the receive mode. Next the program waits for a timeout occurrence or data arrival - whatever will happen first. If the byte were received, it is read from the queue and compared with its expected value. If any of those first bytes will not math, the receive procedure will start again looking for the preamble and sync bytes. If all the bytes have their expected value, the receive process continues and reads the following bytes.

//receive data packet through the radio
int16_t radio_rcv(uint8_t * buffer, uint8_t max_len)
    uint8_t len, i, timeout_counter;
    timeout_init();                            //setup the timeout timer
    timeout_counter = 3;                    //after some timeouts the procedure will give-up
    while (1)                                
        rf12_trans(0x8208);                    //send the module to the idle
        rf12_trans(0x82C8);                    //and restart as a receiver
        rf12_trans(0xCA81);                    //disable the FIFO, and...
        rf12_trans(0xCA83);                    //...enable again, just to clear it 
        while(1)                            //wait for the transmission to start
            if (timeout())                    //if the timeout occurred...
                if (!(timeout_counter--))    //count it, and if no more trials remain
                    rf12_trans(0x8208);        //put the module to the idle state
                    return -1;                //and return an error code
                timeout_init();                //setup the timer for the next measurement
            if(rf12_is_ready()) break;        //proceed if the module captured some data
        timeout_init();                        //restart the timeout timer
        i = rf12_trans(0xB000);                //retrieve the received byte
        if(i != 0xC0) continue;                //test if its correct
        i = rf12_rxbyte();                    //try to receive the next byte
        if(i != 0xDE) continue;                //test if its correct
        len = rf12_rxbyte();                //try to receive the 'length' byte
        if (len > max_len) continue;        //test if the passed buffer is large enough
        //if all the bytes received so far are correct, we may assume that the
        //transmission is not a "false positive", so the program will continue reception 
    i = len;                                //we re going to read 'len' bytes
    while (i--)                                //loop while there is anything more to read
        *buffer++ = rf12_rxbyte();            //receive next byte, and advance write pointer
        if (timeout())                        //if a timeout occured
            rf12_trans(0x8208);                //stop receiving
            return -2;                        //and return error code
    rf12_trans(0x8208);                        //put the module to the idle state
    return len;                                //return packet length

The last piece of code is just a simple procedure demonstrating the use of functions defined above.

int main(void)
    INPUT(BTN); SET(BTN);                    //setup button...
    OUTPUT(LED_RED); CLEAR(LED_RED);        //...and the LEDs

    radio_config();                            //setup the module
    uint8_t buff[200];                        //provide some buffer for data
        if(!READ(BTN))                        //if the button is pressed
            SET(LED_GREEN);                    //turn on the LED
            radio_config();                    //setup the radio again (not really needed)
            buff[0] = 'a';                    //put data in the buffer
            buff[1] = 'b';
            buff[2] = 'c';
            radio_send(buff, 3);            //send the data
            _delay_ms(200);                    //keep the LED on for some time
            CLEAR(LED_GREEN);                //and turn it OFF
        } else {                            //if the button is not pressed
            if(radio_rcv(buff, 200) > 0)    //try to receive some data
                if(buff[0] == 'a' && buff[1] == 'b' && buff[2] == 'c')
                    SET(LED_GREEN);            //indicate that the packet is received
                    _delay_ms(180);            //wait for some (shorter) time
                    CLEAR(LED_GREEN);        //and turn the LED off
        TOGGLE(LED_RED);                    //toggle the second LED

Full source code of this design is available here:

Aleksander Mielczarek 2013 - all rights reserved
Available languages: Polski, English

This website utilizes the cookie files to identify which parts of it are of the most interest.