ATTiny84 + JeeLib + ATTinyCore: rf12_sendWait(2) hangs

Summary: I’m looking for help with RFM69CW radios waking up an ATTiny84 after JeeLib puts the MCU in standby mode with rf12_sendWait(2). It works with a very old core, but I need help getting it to work with ATTinyCore.

Background: I have been looking at all my sensors and examining how to make a move to the LowPowerLabs format and/or live in a hybrid mode with older JeeLib-based sensors. I have sensors that use the ATTiny84 DIP as the MCU, based on Nathan Chantrell’s TinyTx, and with firmware based on his TinyTx sketches, which use JeeLib. Some people might be interested in this topic because they have an original RFM2Pi that used the ATTiny84.

I am also interested in using Spence Konde’s ATTinyCore for any new firmware I apply to these sensors. I have a very hard time maintaining the previous Arduino core that Nathan and others used with the ATTiny84. (Mentioned in his original sketch)

//Requires Arduino IDE with arduino-tiny core: http://code.google.com/p/arduino-tiny/

In order to use that core I’ve kept around a very old PC that happens to run it. I haven’t been able to get it to coexist with my current modern PC and IDE environment, and I would much rather spend my time getting Spence Konde’s ATTinyCore working for me in any case. I think he’s does an amazing job.

When I read that Spence Konde’s core for DxCore was being used for the new emonTx v4, this gave me more hope that using the ATTinyCore with my OEM sensors would work. My plan is to see if I can program the TinyTx sensors with the original JeeLib-based firmware using ATTinyCore. Then move on and look at whether the LowPowerLabs RF library could replace JeeLib in this ecosystem.

Problem: That’s the background. The problem I run into is that everything goes great until the sketch calls rf12_sendWait(2). As far as I can tell, this tells JeeLib to put the MCU into Standby and waits for the transceiver to wake it up with the results of transmitting a message. It appears that the MCU never wakes up.

I tried a bunch of things. And I originally posted the question on the Arduino forum. Spence Konde responded there and had some suggestions, but ultimately I think that knowing a lot about JeeLib might be what is needed. So I am posting the question here. Thanks!

The Sketch

/----------------------------------------------------------------------------------------------------------------------
// TinyTX Send test, sends an incrementing counter and battery voltage every 10 seconds
// By Nathan Chantrell. For hardware design see http://nathan.chantrell.net/tinytx
//
// Licenced under the Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) licence:
// http://creativecommons.org/licenses/by-sa/3.0/
//----------------------------------------------------------------------------------------------------------------------

#define RF69_COMPAT 1
#include <JeeLib.h> // https://github.com/jcw/jeelib

ISR(WDT_vect) { Sleepy::watchdogEvent(); } // interrupt handler for JeeLabs Sleepy power saving

#define myNodeID 28     // RF12 node ID in the range 1-30
#define network 210      // RF12 Network group.
#define freq RF12_433MHZ // Frequency of RFM12B module

//#define USE_ACK           // Enable ACKs, comment out to disable
#define RETRY_PERIOD 5    // How soon to retry (in seconds) if ACK didn't come in
#define RETRY_LIMIT 5     // Maximum number of times to retry
#define ACK_TIME 10       // Number of milliseconds to wait for an ack

int counter;

//########################################################################################################################
//Data Structure to be sent
//########################################################################################################################

 typedef struct {
      int counter;  // counter
      int supplyV;  // Supply voltage
 } Payload;

 Payload tinytx;

// Wait a few milliseconds for proper ACK
 #ifdef USE_ACK
  static byte waitForAck() {
   MilliTimer ackTimer;
   while (!ackTimer.poll(ACK_TIME)) {
     if (rf12_recvDone() && rf12_crc == 0 &&
        rf12_hdr == (RF12_HDR_DST | RF12_HDR_CTL | myNodeID))
        return 1;
     }
   return 0;
  }
 #endif

//--------------------------------------------------------------------------------------------------
// Send payload data via RF
//-------------------------------------------------------------------------------------------------
 static void rfwrite(){
  #ifdef USE_ACK
   for (byte i = 0; i <= RETRY_LIMIT; ++i) {  // tx and wait for ack up to RETRY_LIMIT times
     rf12_sleep(-1);              // Wake up RF module
      while (!rf12_canSend())
      rf12_recvDone();
      rf12_sendStart(RF12_HDR_ACK, &tinytx, sizeof tinytx); 
      rf12_sendWait(2);           // Wait for RF to finish sending while in standby mode
      byte acked = waitForAck();  // Wait for ACK
      rf12_sleep(0);              // Put RF module to sleep
      if (acked) { return; }      // Return if ACK received
  
   Sleepy::loseSomeTime(RETRY_PERIOD * 1000);     // If no ack received wait and try again
   }
  #else
     rf12_sleep(-1);              // Wake up RF module
     rf12_sendNow(0, &tinytx, sizeof tinytx); 

     rf12_sendWait(2);  // Wait for RF to finish sending while in standby mode.  BB: The sketch currently hangs here.
     
     //delay(5); //BB: Delay(5) will work in place of rf12_sendWait(2). The sketch will then dutifully send messages each time interval.
     
     digitalWrite(8, LOW);  //BB: The sketch doesn't currently get past rf12_sendWait(2) and the LED stays on. Sketch stops.

     
     rf12_sleep(0);              // Put RF module to sleep
     return;
  #endif
 }



//--------------------------------------------------------------------------------------------------
// Read current supply voltage
//--------------------------------------------------------------------------------------------------
 long readVcc() {
   bitClear(PRR, PRADC); ADCSRA |= bit(ADEN); // Enable the ADC
   long result;
   // Read 1.1V reference against Vcc
   #if defined(__AVR_ATtiny84__) 
    ADMUX = _BV(MUX5) | _BV(MUX0); // For ATtiny84
   #else
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);  // For ATmega328
   #endif 
   delay(2); // Wait for Vref to settle
   ADCSRA |= _BV(ADSC); // Convert
   while (bit_is_set(ADCSRA,ADSC));
   result = ADCL;
   result |= ADCH<<8;
   result = 1126400L / result; // Back-calculate Vcc in mV
   ADCSRA &= ~ bit(ADEN); bitSet(PRR, PRADC); // Disable the ADC to save power
   return result;
} 

//########################################################################################################################

void setup() {
  pinMode(8, OUTPUT);
  digitalWrite(8, HIGH);   // turn the LED off by making the voltage LOW 


  rf12_initialize(myNodeID,freq,network); // Initialize RFM12 with settings defined above 
  rf12_sleep(0);                          // Put the RFM12 to sleep

  PRR = bit(PRTIM1); // only keep timer 0 going
  
  ADCSRA &= ~ bit(ADEN); bitSet(PRR, PRADC); // Disable the ADC to save power
  
}

void loop() {
  
  counter++;

  tinytx.counter = (counter); // Get temperature reading and convert to integer, reversed at receiving end
  tinytx.supplyV = readVcc(); // Get supply voltage

  rfwrite(); // Send data via RF 

  Sleepy::loseSomeTime(10000); //JeeLabs power save function: enter low power mode for 10 seconds (valid range 16-65000 ms)

}

The Board
image
image

This was solved on the Arduino Forum with the help of Spence Konde.

See the detail over there, but here is the summary.

ATTiny84 is not able to wake from SLEEP_MODE_STANDBY using a interrupt that waits for a RISING edge.

JCW had originally accounted for this in JeeLib by checking “#ifdef SLEEP_MODE_STANDBY” – this mode was not defined for ATTiny84 in older core io files (Arduino 1.5.x and before).

SLEEP_MODE_STANDBY is defined for ATTiny84 in newer io definition files such as ATTinyCore.

So, some solutions are possible for the sketch above.
(1) use an RFM12B radio, which triggers an interrupt with active LOW, or
(2) use rf12_sendWait(1), which invokes SLEEP_MODE_IDLE, or
(3) in JeeLib > RF12.cpp > rf12sendWait, change
#ifdef SLEEP_MODE_STANDBY
to something that correctly detects ATTiny84, such as
#if !defined(__AVR_ATtinyX4__

Things are working now. I’m moving on to the next thing – seeing if I can get the same unit to run the new OEM fork of the LPL RF69 radio library.