Code for radio communication using RFM12 on AVR

March 31st, 2009

I've just spent a weekend forcing RFM12 modules to do what I need. RFM12 are small digital radio transceivers built using RF12 chips. Their obvious advantages are price, quite nice range (100m with a 433Mhz version working with low bitrate) and no external components at all (Ok: except for one, tiny, negligible resistor). In the mode I used them they are just a cheap SPI->Antenna solution. However this text would end here if not for their most important disadvantages: inaccurate documentation with insane code examples, lack of any higher level layer and half-duplex operation.

Further explanation and code follow.

First thing I tried to fix was a way a RFM configuration was defined in code examples. They just used a simple set of magic numbers - understandable (or edited) only with a help of documentation. That's why I started with a creation of a pretty big header file which helps programmer to change most of the options without looking each time into the docs. Although someone might not like this extensive macro usage... it helps me. Second thing to write was a low-level interface configuring pins, performing device initialization and implementing interface functions - this far, completely interrupt-less.

If you want to send any data via RFM you just enable transmitter, then fed it with synchronization bytes (0xAA), two bytes which enable "fifo fill" on the receiver side (2D D4) and then your data. After the last byte is send our transmitter might be turned off. I call this sequence of bytes a "frame". There's of course no guarantee that the receiver will see our data at all (that the 2D D4 will arrive and trigger the fifo fill) and no guarantee that even if the fifo starts - the data received will be correct. Also after the fifo fill is enabled the user has to shutdown it himself, unless he wants to read a garbage arriving after the data.

These are problems I tried to fix with the Comm module. It automates creation of synchronization bytes, and wraps data into so called "packet". Packet contains a 1-byte field describing packet length, control byte, data and CRC16. After the frame data is prepared the rest of work is done with interrupts. Each time RFM is done sending a byte, interrupt is requested which feeds him another byte until the whole frame is sent.

Similar work has to be done on the receiver side. After the RFM FIFO is triggered we get an interrupt for each received byte. Then we check RFM state, read FIFO, calculate CRC and check the length field to see if the packet is ready. If it is and the CRC is correct receiver is turned off and application can read the packet and reinitialize receiver. If CRC happens to be incorrect we reset the FIFO and wait for our next chance.

Receiver, transmitter, CRC, control byte and stats calculation might be freely turned on or off with #defines at the beginning of file. This clobbers file a little with macros but is a nice feature to have. Source code is written in C++ (haha, surprise?) but from ++ features it uses only namespaces so can be very easily converted to C99. If anyone bothers with doing this I'll be happy to post it here. Code is published on GPLv3, but ask me for other licenses if troubled.

Let's have a look at targzballs RFM12-1.2.tar.gz or older: RFM12-1.0beta.tar.gz. There are no header files for Comm/RF because I compile my AVR programs as a one unit. Those files simply get included in main project file. This shouldn't be a problem - if rewriting just remember to remove "static inline" from function declarations.

Guidelines: Beware of SPI. If you get high error count try decreasing it's speed. Use short SPI lines. Even shorter than you thought. Shooorter. Use shielding for wires if only possible. The trick is that the faster your SPI goes and slower is the RFM bitrate the less the main Comm interrupt will bother your program. Sending one byte with RFM requires us to transmit 4 bytes over the SPI - With /16 prescaler this will take more than 64 cycles. Calculating CRC during receiving ought to take under roughly 20 cycles.

Say that RFM works with 95kbit/s bitrate, AVR has 20Mhz clock and SPI has /16 prescaler. Transmitting a single byte would take (1/(95*10^3)) * 8 * 10^6 = 84µs whereas transmitting 4 bytes into RFM will take 4 * (1/(20*10^6 / 16) * 8) * 10^6 = 25µs + say 60 cycles for branching (3µs) = 28µs, so application is left with 56µs between interrupts = 66% time - if this calculations are correct. 115.2kbit/s = 60%. Having SPI prescaler /4 in first example application time raises to 88%, in second to 86%.

Here are my basic notes and ideas about how Comm should work. They are not fully realised of course... maybe in future... comm.txt.

Version 1.2 was tested with remote controlled robot. The controller (master) was reading control data from user and sending a frame containing commands for the robot. After each frame it switched to the RX mode and waited some time for answer with statistics. Robot (slave) waited in RX mode (doing lots of other tasks like balancing) for the frame to come, and replied with current angle, speed and number of received frames.

Update: Created a GIT repository with code and history here: It has small changes over 1.2 tarball, mostly in documentation. Last commit was taken from working project (slave side).

TODO: Add compile-in option for RGUR/FFOV checking/Status reading.

(First which will change in next version)
- Fixed bug in swapping TX/RX Comm modes.

v1.2 (26.05.2009):
- Tested in interleaved transmission
- Function naming fixed a bit (BuffGet -> GetBuff)
- Added helper functions to check current mode (TXReady/RXReady)
- Splitted stats compilation (separate RX and TX for debugging)
- One testcase added for interleaved transmission

Comment by Farin

submitted on May 26th, 2009 at 03:27

Your ISR is too big. What about fifo (ring buffer) on receive, normal buffer on send. Frameing, packeting on the fly char by char in main.

Comment by bla

submitted on May 26th, 2009 at 07:52

The thing I most dislike about this ISR is the need to read a status byte via SPI. I guess in most situations this could be somehow ignored. Then there's a huge RGUR handling code part which executes rarely, usually is just an 'if condition' check.

TX part is mostly a normal buffer, everything is prepared prior to sending. RX part is more complicated and some things might be moved as you are proposing to function called from main - With every byte received it calculates CRC and takes much more time at the end of header - to validate it (which could be more/less removed) and at the end of frame to change mode.

This IRQ-based approach suited my program which in main loop just went to sleep while all work was carried on by interrupts.

Soon I'll publish better debugged version, which worked in two-ways and I can try reducing error checking a bit. Feel free to modify it to your needs.

Also with a larger buffer one might want to first gather bytes and then accept more than one packet from main - but this would require changes in the ways packet is transceived;

Comment by bla

submitted on May 27th, 2009 at 10:22

This SPI usage in ISR might be helped with nFFS and FFIT pins, which aren't currently used. Separate ISR can then handle RGUR/FFOV and separate incoming data. Also FIFO can be directly accessed instead of an access with a status read command. I believe that changing this code in this way could improve performance a lot.

Comment by Jean-Claude Wippler

submitted on May 31st, 2009 at 03:43

Hi - I just came across your page for the RFM12. Great to see that there is more going on with the RFM12 modules. FYI, here's a page with an interrupt-driven RF12 driver library in C:

I decided not to do SPI with interrupts, instead I set the clock as high as possible within RFM12 specs, and use a busy loop.

Feel free to use/adapt as you like, it's open source.


Comment by morty-mohajjel

submitted on June 8th, 2009 at 17:13

can you explain how your code switch between trans and receive mode or vice versa.
I have a code that works in fix direction but when it switch dos not work.

Comment by bla

submitted on June 8th, 2009 at 21:04

Take a look at testcases which were written to test RX/TX switch behaviour. You might find a good example there.

Also: is main file from robot controller which generally does the thing, but code is a bit verbose. It behaves as master:

1) Initializes RFM
2) (line295) starts transmission
3) (351) checks if it's finished and switches to RX mode
4) Waits some time until either something is received
or timeout occurs (one could check also of something STARTS to come) maybe some callback...
5) 361 grabs RX buffer and preinitializes next TX
6) 376 Update local data with data received from robot

Robot (slave) does thing in other direction; start with RX
and if anything cames it switches to TX and replies with packet.

Comment by mcKoY

submitted on June 28th, 2009 at 05:18

what do you know about radio communications coding?

Comment by NOOR

submitted on June 29th, 2009 at 11:06

Is RFM12 can transmit to it self receiver, i have try but i am failed to make this work. I do this because want to make self debug program for this module.

Comment by bla

submitted on June 29th, 2009 at 11:27

@NOOR: In my understanding RFM can be either in TX mode or RX mode never in both (which would be necessary for such debug). So you have to use two separate RFMs (possibly connected to one AVR, but this wouldn't make the life easier).

Biggest problem then is that when something doesn't work you can never be sure if it's transmitter fault or receiver.

Comment by dragon

submitted on October 4th, 2009 at 12:20

I'm feeling rather frustrated to get these modules working.
1. the master send a byte as follow:

send(0x5A); // data

2. what the salve received is 0x20 (not 0x5A)

Accroding the appearances, I can refer that:
The master send successfully and the slave received successfully too. Because the slave fill the FIFO after received 0x2DD4.
The mistake may happened when I read the FIFO. But I can't find any mistake here.

uint WrCmd(uint cmd)
uint tmp;

Comment by dragon

submitted on October 4th, 2009 at 12:28

Why the code can't be submitted rightly?

uint WrCmd(uint cmd)
uint tmp;

Comment by bla

submitted on October 4th, 2009 at 12:44

Wordpress weirdness;
SPDR=cmd; this thing I did with & 0x00ff, but shouldn't matter with unsigned type.

Decrease SPI speed of both devices just to be sure, increase number of 0xAA sent -- just to be sure. The problem can be ANYWHERE. So don't hesitate to recheck anything, including configuration bytes. You can try my without using it has more/less the previous thingies + configuration without high-level api.

Check when doing send() if the sending buffer is empty! Otherwise you might overwrite something. Same for receiving - receive if 8bits of fifo are filled. If you have some debug (serial, LCD) read status often and print it to yourself.

Comment by dragon

submitted on October 4th, 2009 at 13:21

You said "increase number of 0xAA sent", I have tried it. And I have checked configuration bytes several times. I don't think there is any problem. Because if I configurated mistakenly, the slave can't receive the synchronous bytes(0x2DD4) rightly. And of course, it won't fill the FIFO, and I won't read any data. But actually I can get data from it. And when I changed the data 0x5A to any other number, What the slave received changed too.

I find if I replace "while(!(WriteCmd(0x0000)&0x8000));" with "while (RF_IRQ_PIN & RF_IRQ_MASK);", the salve received nothing. So I think what I read from the SPI is wrong.

Comment by dragon

submitted on October 4th, 2009 at 13:24


I find if I replace “while (RF_IRQ_PIN & RF_IRQ_MASK);” with “while(!(WriteCmd(0×0000)&0×8000));”, the salve received nothing. So I think what I read from the SPI is wrong.

Comment by bla

submitted on November 16th, 2009 at 13:01

(We solved Dragon's problem partially; it was in part caused by two long SPI wires, which complicated any readings.)

Comment by Aurash

submitted on August 23rd, 2011 at 14:56

Nice work, if someone needs hardware can have a look at we have built a USB radio stick based on RFM12B module.
Cheers and keep up the good working

Comment by Foka

submitted on August 26th, 2011 at 18:29


very nice project but it seems the tarball is slightly outdated. The
link says its v1.2 but the file underneath is 'RFM12-1.0beta.tar.gz'.

On the other hand, the 'source' link accepts only authorized users...

I'm interested in the bugfix for RX/TX communication mode changes.

- Foka

Comment by Foka

submitted on August 27th, 2011 at 23:49

Wow, that was fast!
Thanks alot :))

- Foka

Add a comment [+] Hide the comment form [-]

I'm a bot, please ignore this comment.

I'm not a bot. Tick this box.

This is a mail of my good friend who loves rubbish email. Send him some if you want to land on my not-so-welcome lists: John Sparrow john(at)