Environment: Win7, Arduino 1.8.1, Teensyduino 1.35, Radiohead v1.62 or v1.67 or Teensy RH version
I’ve been trying to get the example sketch on Adafruit’s RFM9x page: https://learn.adafruit.com/radio-featherwing/using-the-rfm-9x-radio
to run with my Teensy 3.2 feather board. The example Tx sketch is basically the rf95_client example code included with the Radiohead library.
The sketch would simply loop twice and on the second “rf95.waitPacketSent()” would hang indefinitely. I tried substituting RH v1.67 for the v1.62 library linked to the Adafruit page and saw exactly the same behavior. The current (v1.69) RH library is available here: http://www.airspayce.com/mikem/arduino/RadioHead/
The Adafruit Tx sketch:
I hung a scope probe on pin 4 of the Teensy (IRQ from RF9x module) to verify interrupts were indeed firing. I then instrumented the RH RF95 interrupt handler code to print the current _mode and IRQ flags upon entry.
Interestingly, the first send IRQ resulted in 0x308 = _mode = Tx, flags = TxCmplt and the second send IRQ resulted in 0x208 = _mode = idle, flags = TxCmplt after which the sketch would always hang in the following call to “rf95.waitPacketSent()”.
I started instrumenting the sketch with various delays and debug prints and found a combo which consistently yielded IRQ outputs of 0x308 and the sketch no longer hung after the second send.
This led me to believe that the RH library might have a timing window where a race condition could cause it to misbehave.
I read the RH code for “RH_RF95::send” looking for where Tx might be initiated without a corresponding _mode set and zeroed in on “RH_RF95::setModeTx()”.
As you can see the code sets _mode after sending the Tx command via spi to the module, leaving a timing window where a fast interrupt could get to the handler without _mode being set correctly.
I simply moved the _mode set before the spiWrite’s and the sketch ran continuously with IRQ outputs of 0x308 (correct _mode for IRQ) with or without debug delays and prints.
I also moved the _mode set in “RH_RF95::setModeRx()” and “RH_RF95::isChannelActive()” as these also cause interrupts to fire and exhibited the same potential timing windows.
As the Radiohead lib supports numerous radio modules with different specific low level code there are sure to be other potential windows for race conditions. I only verified the fix for my specific case.
I noticed that there is a Radiohead library in the Teensyduino distro that appears to be quite a bit older. I did try it with my setup and it hung as well. Looking very briefly at that code I didn’t see any specific Teensy related changes such as spi transactions. It may be time for Paul to consider updating the library as newer versions have added various params and features.
Looking at v1.69 of the RH library, the code for “RH_RF95::setModeTx()”, “RH_RF95::setModeRx()” and “RH_RF95::isChannelActive()” is the same as v1.67 (& v1.62), so I’d expect the race condition to be in v1.69 as well.
Hopefully the above may help others who encounter similar issues using the Radiohead library.
David
I’ve been trying to get the example sketch on Adafruit’s RFM9x page: https://learn.adafruit.com/radio-featherwing/using-the-rfm-9x-radio
to run with my Teensy 3.2 feather board. The example Tx sketch is basically the rf95_client example code included with the Radiohead library.
The sketch would simply loop twice and on the second “rf95.waitPacketSent()” would hang indefinitely. I tried substituting RH v1.67 for the v1.62 library linked to the Adafruit page and saw exactly the same behavior. The current (v1.69) RH library is available here: http://www.airspayce.com/mikem/arduino/RadioHead/
The Adafruit Tx sketch:
Code:
// Feather9x_TX
// -*- mode: C++ -*-
// Example sketch showing how to create a simple messaging client (transmitter)
// with the RH_RF95 class. RH_RF95 class does not provide for addressing or
// reliability, so you should only use RH_RF95 if you do not need the higher
// level messaging abilities.
// It is designed to work with the other example Feather9x_RX
#include <SPI.h>
#include <RH_RF95.h>
/* for feather32u4
#define RFM95_CS 8
#define RFM95_RST 4
#define RFM95_INT 7
*/
/* for feather m0
#define RFM95_CS 8
#define RFM95_RST 4
#define RFM95_INT 3
*/
/* for shield
#define RFM95_CS 10
#define RFM95_RST 9
#define RFM95_INT 7
*/
/* for ESP w/featherwing
#define RFM95_CS 2 // "E"
#define RFM95_RST 16 // "D"
#define RFM95_INT 15 // "B"
*/
/* Feather 32u4 w/wing
#define RFM95_RST 11 // "A"
#define RFM95_CS 10 // "B"
#define RFM95_INT 2 // "SDA" (only SDA/SCL/RX/TX have IRQ!)
*/
/* Feather m0 w/wing
#define RFM95_RST 11 // "A"
#define RFM95_CS 10 // "B"
#define RFM95_INT 6 // "D"
*/
/* Teensy 3.x w/wing */
#define RFM95_RST 9 // "A"
#define RFM95_CS 10 // "B"
#define RFM95_INT 4 // "C"
// Change to 434.0 or other frequency, must match RX's freq!
#define RF95_FREQ 433.0
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
void setup()
{
pinMode(RFM95_RST, OUTPUT);
digitalWrite(RFM95_RST, HIGH);
while (!Serial);
Serial.begin(9600);
delay(100);
Serial.println("Feather LoRa TX Test!");
// manual reset
digitalWrite(RFM95_RST, LOW);
delay(10);
digitalWrite(RFM95_RST, HIGH);
delay(10);
while (!rf95.init()) {
Serial.println("LoRa radio init failed");
while (1);
}
Serial.println("LoRa radio init OK!");
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
if (!rf95.setFrequency(RF95_FREQ)) {
Serial.println("setFrequency failed");
while (1);
}
Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
// The default transmitter power is 13dBm, using PA_BOOST.
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
// you can set transmitter powers from 5 to 23 dBm:
rf95.setTxPower(23, false);
}
int16_t packetnum = 0; // packet counter, we increment per xmission
void loop()
{
Serial.println("Sending to rf95_server");
// Send a message to rf95_server
char radiopacket[20] = "Hello World # ";
itoa(packetnum++, radiopacket+13, 10);
Serial.print("Sending "); Serial.println(radiopacket);
radiopacket[19] = 0;
Serial.println("Sending..."); delay(10);
rf95.send((uint8_t *)radiopacket, 20);
Serial.println("Waiting for packet to complete..."); delay(10);
rf95.waitPacketSent();
// Now wait for a reply
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
Serial.println("Waiting for reply..."); delay(10);
if (rf95.waitAvailableTimeout(1000))
{
// Should be a reply message for us now
if (rf95.recv(buf, &len))
{
Serial.print("Got reply: ");
Serial.println((char*)buf);
Serial.print("RSSI: ");
Serial.println(rf95.lastRssi(), DEC);
}
else
{
Serial.println("Receive failed");
}
}
else
{
Serial.println("No reply, is there a listener around?");
}
delay(1000);
}
I hung a scope probe on pin 4 of the Teensy (IRQ from RF9x module) to verify interrupts were indeed firing. I then instrumented the RH RF95 interrupt handler code to print the current _mode and IRQ flags upon entry.
Interestingly, the first send IRQ resulted in 0x308 = _mode = Tx, flags = TxCmplt and the second send IRQ resulted in 0x208 = _mode = idle, flags = TxCmplt after which the sketch would always hang in the following call to “rf95.waitPacketSent()”.
I started instrumenting the sketch with various delays and debug prints and found a combo which consistently yielded IRQ outputs of 0x308 and the sketch no longer hung after the second send.
This led me to believe that the RH library might have a timing window where a race condition could cause it to misbehave.
I read the RH code for “RH_RF95::send” looking for where Tx might be initiated without a corresponding _mode set and zeroed in on “RH_RF95::setModeTx()”.
Code:
void RH_RF95::setModeTx()
{
if (_mode != RHModeTx)
{
spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_TX);
spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x40); // Interrupt on TxDone
_mode = RHModeTx;
}
}
As you can see the code sets _mode after sending the Tx command via spi to the module, leaving a timing window where a fast interrupt could get to the handler without _mode being set correctly.
I simply moved the _mode set before the spiWrite’s and the sketch ran continuously with IRQ outputs of 0x308 (correct _mode for IRQ) with or without debug delays and prints.
Code:
void RH_RF95::setModeTx()
{
if (_mode != RHModeTx)
{
_mode = RHModeTx; // set mode prior to sending Tx cmd to avoid fast TxCmplt race condition
spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_TX);
spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x40); // Interrupt on TxDone
// _mode = RHModeTx;
}
}
I also moved the _mode set in “RH_RF95::setModeRx()” and “RH_RF95::isChannelActive()” as these also cause interrupts to fire and exhibited the same potential timing windows.
As the Radiohead lib supports numerous radio modules with different specific low level code there are sure to be other potential windows for race conditions. I only verified the fix for my specific case.
I noticed that there is a Radiohead library in the Teensyduino distro that appears to be quite a bit older. I did try it with my setup and it hung as well. Looking very briefly at that code I didn’t see any specific Teensy related changes such as spi transactions. It may be time for Paul to consider updating the library as newer versions have added various params and features.
Looking at v1.69 of the RH library, the code for “RH_RF95::setModeTx()”, “RH_RF95::setModeRx()” and “RH_RF95::isChannelActive()” is the same as v1.67 (& v1.62), so I’d expect the race condition to be in v1.69 as well.
Hopefully the above may help others who encounter similar issues using the Radiohead library.
David