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