Skip to main content

You are here

Please help testing improved RFM12 driver

47 posts / 0 new
Last post
Please help testing improved RFM12 driver

Hello everyone

As you may already know, I started to change some internal stuff in jcw's RFM12 driver.

New features

  • Faster FIFO reading while receiving data
  • Working pin change interrupt
  • signal strength estimation while receiving (rf12_getRSSI())
  • Use the RFM module for ultra low-power wakuep (rf12_setWatchdog(), rf12_watchdogFired())
  • Handle interrupts according to RFM status feedback
  • Do a full RFM module reset in rf12_initialize()
  • Separate function to set bitrate (rf12_setBitrate()), do not set it directly or rf12_getRSSI() will break

Discussion of this stuff is distributed over some other forum topics. Sorry for hijacking them… This thread should finally bring all things related to this driver changes to one place.

Current state:

  • JohnO is testing this code on a JeeBlock (pin change interrupt) and JeeNodes
  • I've installed this driver on a JeeLink and some JeeNode (v5 clones) using roomnode sketch
  • As far as I know there are no known bugs

Please us it in your projects and report all problems here. The current version is on a separate branch on jcw's github page:

How to use signal strength estimation

After receiving a packet use rf12_getRSSI() to get a signal strength estimation. If packet is too short there are only two possible values (1, 5) which are returned. If there are some data bytes (number of bytes dependents on data rate) there is a better estimation (0, 2, 4 or 6).

uint8_t rssi = rf12_getRSSI(); if (B1000 & rssi) { Serial.print(B111 & rssi); // 0 (very poor reception), 2, 4, 6 (very good reception) } else { // packet was too short Serial.print(rssi); // 1 (bad: 0 or 2), 5 (good: 4 or 6) }

How to use RFM watchdog timer

Use rf12_setWatchdog(unsigned long ms) to request a wakeup interrupt. This function does not suspend the current sketch it just configures the RFM module. Wakeup times from 1ms up to about 49 days are possible. There is only one timer in the RFM module. Setting a new one will overwrite an old timer. Set it to 0 to abort a running timer.

To check if the timer fired you have to call rf12_watchdogFired() which will respond 1 exactly once if the timer fired. So to use it for sleeping your code should look like this:

void sleep() { rf12_setWatchdog(80000); // sleep about 80 s rf12_sleep(0); // power off RFM module (watchdog still works) byte adcsraSave = ADCSRA; ADCSRA &= ~ bit(ADEN); // disable the ADC set_sleep_mode(SLEEP_MODE_PWR_DOWN); ATOMIC_BLOCK(ATOMIC_FORCEON) { sleep_enable(); #ifdef BODSE MCUCR = MCUCR | bit(BODSE) | bit(BODS); // timed sequence MCUCR = (MCUCR & ~ bit(BODSE)) | bit(BODS); #endif } // sleep until watchdog fired while (! rf12_watchdogFired()) sleep_cpu(); sleep_disable(); // re-enable what we disabled ADCSRA = adcsraSave; rf12_sleep(-1); }  

There is some low-power stuff included here. It's not user-friendly but it works.


All looks fine on the JeeNode Micro T84.


works for me too on JN6.
But, somehow, my consumption during sleep is way too high.
Could you attach one of your complete sketches, please?

BTW, what about correcting millis() ?? Like from here:
extern volatile unsigned long timer0_millis; timer0_millis = timer0_millis + SLEEP_INTERVAL_MSEC ;


@PavelE: Thanks for testing. It's important to note that rf12_setWatchdog(xxx ms) goes not to sleep itself. It just requests an interrupt. What you do in this time is your own decision. I think I have to add a convenience function which uses the watchdog and sleeps, all in one simple function call. Correcting the millis value would be a nice addition in this convenience function.

Until there is a function like this, try this simple sketch here:

Don't forget to comment out the SPEED definition at the beginning.


Hi Pavel,

The routine names changed recently you will also need to tweak the following two lines:

rf12_setWatchdog(80000); // after 80 s while (! rf12_watchdogFired())

Hi @tht, I think I have used the rf12_setWatchdog(8000); mechanism in a manner that has exposed something I wasn't expecting. My sketch loops waiting for pin change interrupts and counting stuff when they occur. If there aren't any pin changes it uses the RF12 to send an I'm still alive message. Once this message has been sent my loop starts again with setWatchdog but the expected interrupt from watchdogFired no longer appears to fire and I slumber on. Normal service is resumed if I cause a pin change interrupt. Everything looks good until the next I'm alive message; which is sent but then the next watchdogFired doesn't bring me out of sleep again.

GasCounter_RFSleep_Harvington_1.pde9.94 KB

I've had a thought - I use the prescaler to drop the speed below the RFM12 operating capability. I assumed this only applied for transmission - I wonder if it applies to all communication with the RFM12B.


No, that isn't it.


The problem I'm having is related to (PRR, PRUSI);


Not surprisingly we can't talk to the RFM12B with the USI powered down. The code is still good for me.


@JohnO: I'm not sure if I do understand you correctly. You did power down the SPI (USI) interface which broke the sketch. But it's working as expected with USI enabled? Does disabling USI really reduce power consumption if the µC is in poweroff mode?

@PavelE: Does the sketch work for you? How is power consumption?


@tht, I have adapted (badly) a sketch that originally used WDT. I was powering down the USI not fully realising what it was - thanks for the tip (SPI). Now I understand the USI topic things are fine. I am now looking at the prescaler which I think I need to keep to at least 4Mhz while accessing the RFM12B & SPI I guess.


The RFM12B appears to notice if the USI is powered off/on - I'm not sure which or both. It causes a problem even if one ensures it is always on before trying to communicate with the RFM.


@tht, You made a couple of suggestions to improve the accuracy of the watchdog timer. I can't find them now and wondered if you had put them to into the git library.


Did you change rf12_interrupt() to enable USI? It's not enough if you only do it in the sketch itself.

The RFM watchdog timer is not that accurate, it's specified as about 10%. I tested two different RFM12B modules and both fired too early. This helped me to get a quite accurate timing:

unsigned long ms = 80000; // time to sleep ms += (ms >> 4); // + 1/16 ms += (ms >> 6); // + 1/64 rf12_setwatchdog(ms);

Did you change rf12_interrupt() to enable USI?

Good spot, I have already removed my messing around with USI. Things are looking very good.

I am surprised that we are only offered 10% since it does have a crystal. I see only now that you have put your correction into the sketch. I thought the tweak would be applied inside rf12.cpp.

I'll give your tweak values a spin.


Maybe it's an idea to make a more user-friendly calibration function, e.g. provide a calibration sketch in examples that turns on a LED, sleeps for 60 seconds using the RF12, and turns off the LED. This will allow users to time the actual clock offset; and then maybe it could be #defined somewhere like #define MS_IN_MINUTE 58000; then use that in the setwatchdog routine.

If the value should be accurate for the ATmega clock speed, it's even easier: send the watchdog command and record the time; don't go to sleep but wait for the RF12 interrupt; then see how long that took and report over serial/save to EEPROM.


The watchdog does not need the RFM crystal running. I don't know exactly how it works but this is the reason for the bad accuracy.


@tht, the RFM12B integrated WDT is based on an RC oscillator and draws very low power (<3.5uA). Due to process spreads and temperature depedencies, the accuracy is limited (~10%). Note that the WDT is recalibrated at each enable, provided the crystal oscillator is running and stable at the time.


Thanks martynj for these insights. On power-up the crystal should be enabled, there is a delay (about 50ms) while the RFM resets itself but I think the crystal should be able to stabilize in this time. So there is quiet some time for the crystal to stabilize before the watchdog is enabled.


Interesting, I understand that @tht's code can do a full initialise of the RFM12B - might this also undertake the WDT calibration?


Interesting---if the RFM12's WDT is calibrated automatically, why does it seem to be off consistently to one side?


I guess it can only be as good as the crystal - doesn't explain 10% though. I think the crystal too can be calibrated.


According to (german) there is a factor (1.03) which has to be added. So it's more like 5% off.


@pavinder - too far from nominal that the adjustment is max'd out? Not getting a complete calibration cycle? I doubt it is the crystal - actual measurements have confirmed +/- 10ppm


Unexplained power consumption: My usual sketch - posted elsewhere in this thread has unusual power characteristics. When the sketch starts up it sends out its 'I'm alive' complete with acknowledgement request. It then goes to sleep pending an interrupt - RFM or otherwise.

void loop () { while (ms < TimeOut) // Note ms is not an accurate millsecond since using Prescaler Max & Min values. 220=min 13320=hour { rf12_setWatchdog(RestTime); // after ~10 s setPrescaler(8); // div 256, i.e. 1 or 8 MHz div 256 (8) = 32kHz // Sleep at slow speed set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); // Expecting to sleep for 10 seconds or any other interrupt! sleep_mode();

At this point the power consumption is 249.4 micro amps. High but hold that thought for a moment. A pin change interrupt occurs and a non acknowledged RF packet is transmitted. We drop back into loop as above and now the power consumption is 4.5 micro amps. It stays at this level until it is time for the 'I'm alive' acknowledged packet at which point the power consumption goes back up to 249 micro amps and stays there until the next pin change.

Is there something wrong with my acknowledged packet transmission (below) or is it something else?

for (byte i = 0; i < RETRY_LIMIT; ++i) { rf12_sleep(RF12_WAKEUP); while (!rf12_canSend()) rf12_recvDone(); rf12_sendStart(RF12_HDR_ACK, &payload, sizeof payload, RADIO_SYNC_MODE); byte acked = waitForAck(); rf12_sleep(RF12_SLEEP); if (acked) { return; } }

the ack routine is I think standard @jcw boiler plate - below.

// wait a few milliseconds for proper ACK to me, return true if indeed received static byte waitForAck() { MilliTimer ackTimer; while (!ackTimer.poll(ACK_TIME)) { if (rf12_recvDone() && rf12_crc == 0 && // see rf12_hdr == (RF12_HDR_DST | RF12_HDR_CTL | myNodeID)) return 1; set_sleep_mode(SLEEP_MODE_IDLE); // Wait a while for the reply? sleep_mode(); } return 0; }

I'm not sure if I got it… Is your problem that the power consumption is too high before you send your first packet?

After initialization the RFM module is in receiving state but it looks like you handle this already. There has to be something you do after sending a packet instead of at the beginning of the loop. Try to rewrite your code like this:

setup() { init everthing but do not enter powersafe mode } loop() { enter powersafe mode sleep() power up everything needed send packet }  

Are you using pin change interrupts on RFM12B? The initialization on pin change interrupts is not as efficient as level interrupts.

Why do you have this 'delay(1000)' at the beginning of 'setup()'. This burns quite a bit of power.

How do you measure power consumption? Are you sure you don't measure during this 'delay(1000)'?

I'm still struggling trying to measure power consumption. Best I could get until now is this (JeeNode Micro clone; no voltage regulator; 3.3V; 1mV/µA)


Quite some open questions here. Why is base consumption almost 50µA? That's clearly too much. What is this 'noise' at the end? Why don't I see the individual bytes being sent? I'm using this simple sketch here:

#include <JeeLib.h> #include <avr/sleep.h> #define BODS 7 //BOD Sleep bit in MCUCR #define BODSE 2 //BOD Sleep enable bit in MCUCR #define RADIO_SYNC_MODE 3 int data = 0xcccc; void setup() { // init RF12 and go to sleep rf12_initialize(20, RF12_868MHZ, 212); rf12_setBitrate(0x40); // using low data rate rf12_sleep(RF12_SLEEP); ADCSRA &= ~(1<<ADEN); // turn off ADC ACSR |= _BV(ACD); // disable the analog comparator MCUCR |= _BV(BODS) | _BV(BODSE); // turn off the brown-out detector } void loop () { // going low-power rf12_sleep(RF12_SLEEP); // sleeping here rf12_setWatchdog(1000); set_sleep_mode(SLEEP_MODE_PWR_DOWN); // set the type of sleep mode to use sleep_enable(); // enable sleep mode while (! rf12_watchdogFired()) { sleep_cpu(); // nighty-night } sleep_disable(); // just woke up, disable sleep mode for safety // powering up stuff rf12_sleep(RF12_WAKEUP); // send a packet while (!rf12_canSend()) rf12_recvDone(); rf12_sendStart(0, &data, sizeof data, RADIO_SYNC_MODE); ++data; }

I have to work out this stuff on this simple sketch before going to more complex sketches like yours. I'll rewrite this sketch so it's able to work with jcw's rfm12 driver to check if there is any difference.


I'm not sure if I got it… Is your problem that the power consumption is too high before you send your first packet? The power consumption is high after the first packet (it happens to request acknowledgement). Power consumption then drops to 4.5 microamps.

Are you using pin change interrupts on RFM12B? The initialization on pin change interrupts is not as efficient as level interrupts. I am not using pin change interrupts for the RFM12 operation but I do use them to trigger my counters. I am using your latest RFM12 code.

Why do you have this 'delay(1000)' at the beginning of 'setup()'. This burns quite a bit of power. I have included this to prevent interactions between my ISP programmer and the RFM initialisation at sketch start-up. I don't measure until everything has settled down and the first packet has been sent out.

How do you measure power consumption? I am using a multimeter, I start off on the milliamp range and after the start-up phase and the first radio packet is transmitted, the meter settles at 0.25millamp. I cause a pin change interrupt which transmits an unacknowledged packet and the meter then drops to 0.00 milliamps. I switch the meter to the microamp range and see 4.5 micro amps - I have a 3 microamp approximate load attached to the JeeNode as a switch de-bouncer.

I'm still struggling trying to measure power consumption. I am using a JeeNode Micro with regulated (external to Jeenode) 3v3. Although I also have a Rigol I have not tried to measure current with it.

I am happy to try a basic sketch to establish a baseline between us.

I would like you to send a packet requiring acknowledgement and measure the current consumption afterwards. Once you have this stable cause a non acknowledged packet to transmit and then notice if your power consumption has dropped dramatically.

For the morrow I think :-) Many thanks for your considered reply.


I feel that the RFM12B isn't put to sleep properly after the transmission of an acknowledged packet. This 'condition' is cleared when a normal transmission is done.

UPDATE: I will check with standard Jee code this evening.


Have you got another Multimeter nearby? I had some problems measuring in the µA range. The voltage loss over the Multimeter (burden voltage) was so high the ATtiny was running way out of specs.

So if you have another Multimeter measure the voltage after the first Multimeter or attach the scope there and configure a trigger if voltage goes below 3V or so.

Maybe there's not everything switched off on the RFM12 module. rf12_sleep(0) does only disable the crystal. Maybe it helps disabling some more components there. Is it possible to output some debug data using a serial line? If yes, please report the values of 'rfmstate' at some places. Most likely you'll have to add a 'extern volatile uint16_t rfmstate;' at the beginning of your Sketch to access rfmstate.


I just uploaded some changes which could improve power consumption on your side.

The high base usage (almost 50µA, as reported earlier) is just wrong. My Multimeter shows 2.0µA which is most likely correct. Let me quote jcw here:

1.5 µA for the RFM12B w/ wake-up timer enabled and 0.1 µA for the ATtiny (at 25°C). You could turn off the RFM12B's low-battery detector for a marginal improvement, but that will require changing a few #define's in the RF12 driver.

So there is still about 0.4µA lost somewhere (if my measurement is exact.

Next step will be implementing acks in this simple test sketch.


Premium Drupal Themes by Adaptivethemes