ATTiny84 + JeeLib + RFM69CW + ATTinyCore: rf12_sendWait hangs

I have a number of ATTiny84 boards from back with Nathan Chantrell, Jean-Claude Wippler, the folks at Open Energy Monitor, and others were using this MCU and RFM12B radios (and later the RFM69CW in compatible mode) to send sensor data to a Raspberry Pi gateway.

Back then the Arduino IDE core I was using was different than the modern one that Spence Konde (@DrAzzy) created.

The original Nathan Chantrell TinyTx sketches suggested in the comments:

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

I still worked with ATTiny84 a bit here and there, but found it more and more difficult to maintain a working version of that older core.

Today I tried out the Spence Konde ATTinyCore ( 1.5.2) for the first time. It is awesome.

I got out the old TinyTx sketch (below) and found that it compiled on the first try.

I uploaded it to a breadboard version of the early RFM2Pi from Open Energy Monitor.

The RFM69CW sends one packet successfully, then hangs.

I added some code to turn on an LED during setup, then turn it off after the code reaches a certain point. I found that the MCU hangs at "sendWait(2)" in the rfwrite function.

If I replace this with "delay(5)" the sketch works as expected, transmitting a counter every 10 seconds.

This could be hardware. Breadboards are notorious, especially with these radios. I tested wiring it a few different ways. I tested more than one RFM69CW radio. Still, could be hardware.

My question for this forum is: could it be something incompatible in the JeeLib sendWait method and the ATTinyCore?

//----------------------------------------------------------------------------------------------------------------------
// 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)

}





It is possible that there could be something incompatible between the JeeLib sendWait method and the ATTinyCore. Since the code hangs at the sendWait function in the rfwrite function, it could be worth checking if there are any known issues with the ATTinyCore's implementation of the sendWait function.

Another possibility is that there could be a timing issue, as the original code used a delay of 5ms instead of the sendWait function. It might be worth experimenting with different values for the sendWait parameter to see if this resolves the issue.

It could also be a hardware issue, as breadboards can be problematic, especially with RF radios. It might be worth testing the code on a different hardware setup to see if the problem persists.

Please link to the version of each library you used? JeeLib and RFM69CW libraries have been forked many times

Also....
sendWait? WTF is sendWait? Never heard of that one before...

The JeeLib fork I use is the Open Energy Monitor one. GitHub - openenergymonitor/jeelib: JeeLib for Arduino IDE: Ports, RF12, and RF69 drivers from JeeLabs · GitHub

The pins they use for the SPI connection to the radio are as follows:

#elif defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny44__)

#define RFM_IRQ     2
#define SS_DDR      DDRB
#define SS_PORT     PORTB
#define SS_BIT      1

#define SPI_SS      1     // PB1, pin 3
#define SPI_MISO    4     // PA6, pin 7
#define SPI_MOSI    5     // PA5, pin 8
#define SPI_SCK     6     // PA4, pin 9

This corresponds to the "Alternative Pinout" numbering scheme in ATTinyCore, so I'm loading the sketch with the "Counterclockwise" pin mapping option.

This is part of JeeLib in the RF12.cpp library code.

/// @details
/// Wait for completion of the preceding rf12_sendStart() call, using the
/// specified low-power mode.
/// @note rf12_sendWait() should only be called right after rf12_sendStart().
/// @param mode Power-down mode during wait: 0 = NORMAL, 1 = IDLE, 2 = STANDBY,
///             3 = PWR_DOWN. Values 2 and 3 can cause the millisecond time to
///             lose a few interrupts. Value 3 can only be used if the ATmega
///             fuses have been set for fast startup, i.e. 258 CK - the default
///             Arduino fuse settings are not suitable for full power down.
void rf12_sendWait (uint8_t mode) {
    // wait for packet to actually finish sending
    // go into low power mode, as interrupts are going to come in very soon
    while (rxstate != TXIDLE)
        if (mode) {
            // power down mode is only possible if the fuses are set to start
            // up in 258 clock cycles, i.e. approx 4 us - else must use standby!
            // modes 2 and higher may lose a few clock timer ticks
            set_sleep_mode(mode == 3 ? SLEEP_MODE_PWR_DOWN :
#ifdef SLEEP_MODE_STANDBY
                           mode == 2 ? SLEEP_MODE_STANDBY :
#endif
                                       SLEEP_MODE_IDLE);
            sleep_mode();
        }
}

My sketch (really Nathan Chantrell's sketch) in the post above calls rf12_sendWait around line 70. The message sends (I can see it arrive on the receiving radio) but whatever conditions rf12_sendWait is waiting for must not be satisfied, because the code never moves on. This part of the code I have not dug into enough yet to say what might be happening. Is there an interrupt that isn't getting triggered? Is there a buffer that never gets emptied? I don't know.

The reason I asked about ATTinyCore is that this sketch works when compiled and loaded with the older core. I double-checked the pin mapping in case that was the difference, but it appears to be compatible with the "Alternative Pinout".

Note: I updated the title of the post, changing "sendWait" to the more precise command: "rf12_sendWait".

Updates:

I tested using mode 1 (SLEEP_MODE_IDLE) and 0 (NORMAL) instead of 2 (SLEEP_MODE_STANDBY). The sketch works with both mode 1 and mode 0.

I tested using the current JeeLabs library for JeeLib instead of the OEM fork. No luck.

I tested using the current OEM fork JeeLib library instead of the one I had downloaded sometime in the past. No luck.

I tested sleep.h with my ATTiny84 breadboard setup and an LED, just to do a sanity check that INT0 was capable of waking up my board using the ATTinyCore. That worked just fine.

I tested my breadboard hardware with the older Arduino-tiny core (I think I got it on Google Code, but it has been a long time) that has worked for me in the past. It worked. I soldered up a TinyTx board (Nathan Chantrell's design for this MCU and radio) and tested it with the older core. It worked. So the hardware is not the issue.

I also realized: should I move this question to the Open Energy Monitor forum? They've been working to get RFM69CW radios working with DxCore, so have some experience with the Spence Konde cores. And they have a long history with JeeLib prior to moving to the LowPowerLab alternative.

Just noticed this bit.... what mode you trying to use?

// power down mode is only possible if the fuses are set to start
 // up in 258 clock cycles, i.e. approx 4 us - else must use standby!

Are you by any chance using an oscillator or resonator?

If so get out your datasheet, and turn to page 35, table 6-1)

We see that of the 8 options on that table, only the cases where CKSEL0 (ceramic resonator, not crystal) and SUT set to either 00 or 01 to get the 258 clock cycle start. The other two ceramic resonator options with the high SUT bit set have 1K clock cycle start delay, as does one of the 4 crystal options, and the remaining crystal options all specify 16K clock cycle start delay.

So therefor, unless you're using a ceramic resonator and you do not have problems running it with SUT = 00 or SUT = 01, you cannot use POWER_DOWN mode for that, only STANDBY.

Note that ATTinyCore:

  1. Does not support ceramic resonators.
  2. Always sets the SUT bits to the most conservative setting (that is SUT=11, so with a crystal we will always wait 16k clock cycles, plus - after a reset - an extra 64ms to ensure that the power supply has stabilized.

These were done because:

  1. Nobody chooses to use a ceramic resonator unless they're making huge quantities and want something inbetween a crystal and the internal osc in accuracy/cost
  2. Very very very few people care if startup is in 4us, 1ms, or 64ms - but they very much do care if it tries to start but fails and malfunctions at startup. Hobbyists in particular often have poor layout/wiring to their crystals (the classic AVRs are real troopers with regard to that and are very good at getting the crystal to oscillate even in terribly adverse wiring conditions; one of the few ways in which post-2016 AVR revolution processors are worse, they are much less aggressive at driving the crystal, and I routinely empirically determine the required loading cap values....

Thanks for looking at this!

Standby.

I'm not. It's a really simple SPI connection to the RFM, a couple caps and the MCU. Here's the schematic, essentially.

tinytxBreadBoard.pdf (13.1 KB)

On the image you see there's also an LED and a pull-up on SEL/NSS. The oddly traversing orange hookup wire to the right is where I replaced an LDO that was getting me a 3V3 rail. In the configuration shown, I'm just powering the whole thing with 3V3 using the IC test clip for ISP.

This is good information about the caps, resonators, crystals, and the ATTinyCore, though. Thank you!

I also recently tried the 2.0 version of the core, to see if that helped. No difference.

Right now 2.0.0 is more likely to hinder than help - I definitely want people pounding on it and finding problems, but there is a reason it hasn't been released yet

I have new information if you could take a look at this when you get a chance @DrAzzy.

I made a simple sketch to test the interaction of avr/sleep.h and the INT0 interrupt pins. I don't think INT0 is able to wake the ATTiny84 using ATTinyCore. At least I am not able to get it to work.

JeeLib is using attachInterrupt(0, RF69::interrupt_compat, RISING)
in RF69_compat.cpp. This is the attached interrupt that it is supposed to wake the MCU from whatever sleep mode is chosen with rf12_sendWait(). STANDBY is the mode that the sketch uses.

So I am testing the following combination:

  1. Attached interrupt on INT0 with RISING
  2. ATTiny84 + ATTinyCore (as well as Arduino Uno with its avr core as a sanity check)
  3. sleepmode = SLEEP_MODE_STANDBY
  4. Arduino IDE 1.8.14, in case that matters

My test is simply to see if I can wake up the MCU from STANDYBY using INTO.

I can't. Not with ATTinyCore and an ATTiny84. I can with the same sketch and an Arduino Uno.

The circuit in question is shown below. Basically PB2 on the ATTiny84 (D2 on the UNO) is pulled down with a resistor. A RISING interrupt is attached to the pin. The MCU goes into STANDBY. The ISR wakes up the MCU and blinks the LED. Repeat.

/*
 * Tests avr/sleep.h and interrupts using Arduino UNO. This sketch is working.
 */

#include <avr/sleep.h>

const byte ledPin = LED_BUILTIN;
const byte interruptPin = 2;


void setup() {
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, RISING);
}

void loop() {
  set_sleep_mode(SLEEP_MODE_STANDBY);
  sleep_mode();
}

void blink() {
  sleep_disable();
  detachInterrupt(0);
  digitalWrite(ledPin, HIGH);
  delay(500);
  digitalWrite(ledPin, LOW);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, RISING);
}

And here is the same sketch, modifying the pin setup for ATTiny84 and ATTinyCore. This sketch does not work with STANDBY, but does work with IDLE. Just like I'm seeing with JeeLib.

/*
 * Tests avr/sleep.h using ATTiny84 and Spence Konde's ATTinyCore
 */

#include <avr/sleep.h>

#if !(defined(PINMAPPING_CCW) || defined(PINMAPPING_CW))
#error "Sketch was written for Spence Konde ATTinyCore and uses PIN_Pxn syntax."
#endif

const byte ledPin = PIN_PA2;
const byte interruptPin = PIN_PB2;


void setup() {
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, RISING);
}

void loop() {
  set_sleep_mode(SLEEP_MODE_STANDBY);  //Not working: ATtiny84 will not wake up upon interrupt
  //set_sleep_mode(SLEEP_MODE_IDLE);       //Works: ATtiny84 will wake up and call ISR, but uses more power than STANDBY
  sleep_mode();
}

void blink() {
  sleep_disable();
  detachInterrupt(0);
  digitalWrite(ledPin, HIGH);
  delay(500);
  digitalWrite(ledPin, LOW);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, RISING);
}

INTn type pin interrupts on classic AVR (with apparently a few poorly documented exceptions) can only wake the device on "change" or "low level" not "rising" or "falling"

Only low level or change on INTn or a PCINT can.

Well, that is progress of sorts.

I find that on my old PC that runs the old arduino-tiny core I can get the sketch to work with the "RISING" mode.

However, it will not compile with "digitalPinToInterrupt" in the first parameter of attachInterrupt.
So this is what works:
attachInterrupt(0, blink, RISING);

Also, the compiler complains the SLEEP_MODE_STANDBY is not defined. So I used set_sleep_mode(2); But I haven't yet had time to verify if the core has the same mapping of 2 = STANDBY that is found in the JeeLib rf12 library.

With the interrupt defined that way, and using the old arduino-tiny core, the sketch works. Either it is not sleeping (because "2" isn't meaningful to the set_sleep_mode function) or it is sleeping and waking up despite the request to watch for a RISING edge.

Regardless, JeeLib works with this older core.

It seems that the creators of the arduino-tiny core did something forgiving with the request for a "RISING" interrupt and translated it into some other mode that works for JeeLib when it requests an interrupt in that mode.

Maybe that is in WInterrupts.c? Although that looks pretty similar to WInterrupts.c in ATTinyCore.

Well I don't see how that could be. It is stated explicity that:

The INT0 interrupt can be triggered by a falling or rising edge or a low level. (snip) Note
that recognition of falling or rising edge interrupts on INT0 requires the presence of an I/O clock,
as described in “Clock Sources” on page 31

So the hardware in theory should not be capable of what you are asking of it. No matter which core you use.

SLEEP_MODE_STANDBY is supplied by the io headers that should be getting included automatically and which are (in arduino land) considered part of the toolchain. It absolutely should not be undefined.
Find in files results with 1.8.19 (no difference with latest ATpack either - those sleep modes are defined in all cases, so anything that #includes <Arduino.h> (the main sketch does automatically) ought to be able to see it (and not also that the three tiny84-like parts have the same numeric definitions too.

C:\arduino-1.8.19\hardware\tools\avr\avr\include\avr\iotn84.h:
   90  #define SLEEP_MODE_ADC (0x01<<3)
   91  #define SLEEP_MODE_PWR_DOWN (0x02<<3)
   92: #define SLEEP_MODE_STANDBY (0x03<<3)
   93  
   94  #endif /* _AVR_IOTN84_H_ */

C:\arduino-1.8.19\hardware\tools\avr\avr\include\avr\iotn841.h:
  712  #define SLEEP_MODE_ADC (0x01<<3)
  713  #define SLEEP_MODE_PWR_DOWN (0x02<<3)
  714: #define SLEEP_MODE_STANDBY (0x03<<3)
  715  
  716  /* Interrupt vectors */

C:\arduino-1.8.19\hardware\tools\avr\avr\include\avr\iotn84a.h:
  839  #define SLEEP_MODE_ADC (0x01<<3)
  840  #define SLEEP_MODE_PWR_DOWN (0x02<<3)
  841: #define SLEEP_MODE_STANDBY (0x03<<3)

AAAAHAHAHAA!!!!

I've got it!

The reason it works when you set the sleep mode to 2 is that the sleep modes (as you can see) are all leftshifted. so if you set sleep mode to 2 - what are they going to do with it? Probably that byte will be andi'ed with the group mask (var & 0b00011000, the current value of MCUCR will be loaded, and that value andi'ed with the bitwise inverse of the group mask (ie 0b11100111, or'ed with the value that was passed and written back to MCUCR.

So sleep mode is reset to mode zero! What mode is that?
#define SLEEP_MODE_IDLE (0x00<<3)

Idle sleep mode. the one that keeps everything running and barely saves any power.

From idle sleep mode, you can wake from a rising INTn interrupt, because it has barely turned anything off.

I looked in the c:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\avr\iotn84a.h on this old PC running arduino-tiny and IDE 1.6.5. SLEEP_MODE_STANDBY does not get defined.

So then we have JeeLib defining using attachinterrupt, and when doing so for the RFM69CW it tries to use RISING as the trigger.

uint8_t rf69_initialize (uint8_t id, uint8_t band, uint8_t group, uint16_t off) {
    uint8_t freq = 0;
    switch (band) {
        case RF12_433MHZ: freq = 43; break;
        case RF12_868MHZ: freq = 86; break;
        case RF12_915MHZ: freq = 90; break;
    }
    RF69::setFrequency(freq * 10000000L + band * 2500L * off);
    RF69::group = group;
    RF69::node = id & RF12_HDR_MASK;
    delay(20); // needed to make RFM69 work properly on power-up
    if (RF69::node != 0)
        attachInterrupt(0, RF69::interrupt_compat, RISING);
    else
        detachInterrupt(0);
    RF69::configure_compat();
    return nodeid = id;
}

Using RISING as the interrupt trigger won't work on an ATTiny84, but might appear to work if the MCU never actually goes to sleep.

Now the sketch calls rf12_sendWait(2); Because we are in RF69_COMPAT mode, this gets directed over to the function rf69_sendWait:

void rf69_sendWait (uint8_t mode) {
    while (RF69::sending())
        if (mode) {
            set_sleep_mode(mode == 3 ? SLEEP_MODE_PWR_DOWN :
#ifdef SLEEP_MODE_STANDBY
                           mode == 2 ? SLEEP_MODE_STANDBY :
#endif
                                       SLEEP_MODE_IDLE);
            sleep_mode();
        }
}

I am not sure at this point why the compiler doesn't choke and say that these SLEEP_MODEs are undefined. I don't know what is happening next. It seems like the sketch might actually be falling to put the MCU to sleep because of the lack of definition? In which case it would appear to be waking up as desired?

AAAHAAA!!!
In sleep.h:

#elif defined(__AVR_AT90S4434__) \
|| defined(__AVR_ATA5505__) \
|| defined(__AVR_ATA5272__) \
|| defined(__AVR_AT76C711__) \
|| defined(__AVR_AT90S8535__) \
|| defined(__AVR_ATmega103__) \
|| defined(__AVR_ATmega161__) \
|| defined(__AVR_ATmega163__) \
|| defined(__AVR_ATmega16HVB__) \
|| defined(__AVR_ATmega16HVBREVB__) \
|| defined(__AVR_ATmega32HVB__) \
|| defined(__AVR_ATmega32HVBREVB__) \
|| defined(__AVR_ATtiny13__) \
|| defined(__AVR_ATtiny13A__) \
|| defined(__AVR_ATtiny15__) \
|| defined(__AVR_ATtiny24__) \
|| defined(__AVR_ATtiny24A__) \
|| defined(__AVR_ATtiny44__) \
|| defined(__AVR_ATtiny44A__) \
|| defined(__AVR_ATtiny84__) \
|| defined(__AVR_ATtiny84A__) \
|| defined(__AVR_ATtiny25__) \
|| defined(__AVR_ATtiny45__) \
|| defined(__AVR_ATtiny48__) \
|| defined(__AVR_ATtiny85__) \
|| defined(__AVR_ATtiny88__)

    #define SLEEP_MODE_IDLE         0
    #define SLEEP_MODE_ADC          _BV(SM0)
    #define SLEEP_MODE_PWR_DOWN     _BV(SM1)
    #define SLEEP_MODE_PWR_SAVE     (_BV(SM0) | _BV(SM1))

    #define set_sleep_mode(mode) \
    do { \
        _SLEEP_CONTROL_REG = ((_SLEEP_CONTROL_REG & ~(_BV(SM0) | _BV(SM1))) | (mode)); \
    } while(0)

AVRLibc is compensating for the failings of the official IO headers. Which is, unfortunately, typical.
Except they're doing it wrong here, which is less common.

Well i can't say I'm surprised that the headers on 1.6.5 are bad. That's a 4.8.x-derived toolchain with atpacks from --- before whenever that version of the IDE was released. We're now using 7.3.0-derived toolchain, and my modern AVR cores (but not ATTinyCore - yet because I keep having to do other stupid shit instead of get 2.0.0 of ATTC out the door) use my own build of it with the latest atpacks

But you want to at least be using a 6.x version of avr-gcc to get LTO (apparently they added 5.x for LTO, but it turned out to have some problem and a couple versions later went to 6.x to pick up fixes, then the 7.3.0 was needed for the modern AVRs. As I understand it, 4.8.x does a better job of the compiling part - but it still loses in flash size to 7.3 most of the time because of LTO (Link Time Optimization - saves you 10-20% flash and sometimes a bit of ram)

That matches exactly what I have on that old PC in sleep.h. Of note: SLEEP_MODE_STANDBY is not defined for ATtiny84 in this old code.

So JeeLib's rf69_sendWait is checking, hey, is SLEEP_MODE_STANDBY defined? Because it expects that it will not be in the case of some of the parts. It isn't defined for ATTiny84, so it keeps going and ends up with SLEEP_MODE_IDLE.

Fast forward to modern cores such as ATTinyCore, and SLEEP_MODE_STANDBY is defined for the ATTiny84x parts. Which is correct -- the part supports this mode, but by correctly offering a definition of STANDBY for these parts, JeeLib is tripped up, because it is expecting it won't be.

So -- what would be the better condition to check? JeeLib should be checking to see if (a) the defined part offers SLEEP_MODE_STANDBY and (b) the part offers the ability to wake from that mode using the chosen interrupt.

AND/OR it should include a check during the attachinterrupt to see if RISING is a supported trigger for the part. If not it should use something else appropriate for the RFM69CW external interrupt (HIGH? So far I can't tell from the data sheet.)

Does ATTinyCore make any part-specific definitions that could be checked for these conditions?

Making some notes here to answer my own question.

The RFM69CW interrupts are active HIGH. Page 45 of the data sheet.

PacketSent: PacketSent interrupt source goes high when the SR's last bit has been sent

(This is the opposite of the RFM12B, where the interrupts are active LOW. An unfortunate twist because the idea of the RFM69CW was to be compatible with the RFM12B.)

Of the available INT0 interrupt modes to wake an ATTiny84, HIGH is not one. As mentioned above.

Rising and Falling Interrupts on INTO requires an I/O clock on ATTiny84. And none of the classic JeeLib devices that use ATTiny84 use a crystal, resonator, or external clock. Not the JeeNode Micro from JeeLabs, not the RFM2Pi v1 from OpenEnergyMonitor, and not the TinyTx v3 from Nathan Chantrell.

Given the above, my thought is that it would be best to do exactly what JeeLib did back in the day, and use IDLE for ATTiny84 in r69_sendWait. Leave the RISING interrupt in place for the processors that can handle it (by far the most common being ATMega328 for JeeLib devices). The ATTiny84 can't do that, but won't need to if it is in IDLE mode.

We just need to update the way the system checks if ATTiny84 is the µC in use.

A quick-and-dirty way would be to change

#ifdef SLEEP_MODE_STANDBY

to

#if !defined(__AVR_ATtinyX4__)

Later this could be refined, or simply added to, if other µCs needed to be checked for.

This works! It is nice to be using a modern PC, Arduino IDE, and ATTinyCore to program JeeLib-based RFM69CW transceivers. Very excited to be getting rid of the old PC that I kept around for only that purpose! Thank you, @DrAzzy!