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).
RFM12 - RSSI
  • 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

prototype

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_PORT   PORTB
#define SPI_CS        2
#define SPI_SCK_PORT  PORTB
#define SPI_SCK       5
#define SPI_MISO_PORT PORTB
#define SPI_MISO      4
#define SPI_MOSI_PORT PORTB
#define SPI_MOSI      3

//Peripherals of the "demo" board, two LEDs and a pushbutton
#define LED_GREEN      1
#define LED_GREEN_PORT PORTB
#define LED_RED        0
#define LED_RED_PORT   PORTB
#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(0x80E7);//EL,EF,868band,12.0pF
    RFXX_WRT_CMD(0x8219);//!er,!ebb,!ET,ES,EX,!eb,!ew,DC
    RFXX_WRT_CMD(0xA67C);//868MHz
    RFXX_WRT_CMD(0xC647);//4.8kbps
    RFXX_WRT_CMD(0x94A0);//VDI,FAST,134kHz,0dBm,-103dBm
    RFXX_WRT_CMD(0xC2AC);//AL,!ml,DIG,DQD4
    RFXX_WRT_CMD(0xCA81);//FIFO8,SYNC,!ff,DR
    RFXX_WRT_CMD(0xCED4);//SYNC=2DD4;
    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
    RFXX_WRT_CMD(0xC040);//1.66MHz,2.2V
}

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(0xAA);
    rf12_txbyte(0xAA);
    rf12_txbyte(0xAA);
    rf12_txbyte(0x2D);                        //then the predefined sync words
    rf12_txbyte(0xD4);
    rf12_txbyte(0xC0);                        //and a secret 0xC0DE
    rf12_txbyte(0xDE);
    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
    rf12_txbyte(0x00);
    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
        _delay_us(150);
        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 
        break;                                
    }
    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
    OUTPUT(LED_GREEN); CLEAR(LED_GREEN);

    radio_config();                            //setup the module
    
    uint8_t buff[200];                        //provide some buffer for data
    while(1)
    {
        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: mega_rf.zip








Advertising
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.