nRF24L01 and Weather Station Interrupts

Hi, I have an Acurite 5-in-1 weather station and the humidity sensors has died. I called Acurite and they said buy another outside unit. Hmmm, $103, yep, I'll get right on that.

I found some code by Jens Jensen, (c)2015 (AWESOME JENS, THANK YOU) and it decodes the signal from the 5-in-1. I'd like put an Arduino outside to beam this data inside to a Nano + nRF24L01 inside but noticed that the code for the 5-in1 uses Interrupts.

Am I likely to run into conflicts with the RF24/RF24Network Libraries by TMRH20) and the 5-in-1 code below? If so, how do I get around that? (Code examples please)

Can't recall where I got Jen's code, but it was a year or more back AND I had to strip out all the Debug and comments as this site only allows 9000 character posts! DUH It might be smart to NOT include Code in that count, eh Mods??

/* accurite 5n1 weather station decoder
  Jens Jensen, (c)2015
*/
#import <avr/eeprom.h>
#define SYNC_HI 675
#define SYNC_LO 575
#define LONG_HI 450
#define LONG_LO 375
#define SHORT_HI 250
#define SHORT_LO 175
#define RESETTIME 10000
#define PIN 2 // data pin from 433 RX module
#define MAXBITS 65 // max framesize
#define RESET 0 // no sync yet
#define INSYNC 1 // sync pulses detected
#define SYNCDONE 2 // complete sync header received
volatile unsigned int pulsecnt = 0;
volatile unsigned long risets = 0; // track rising edge time
volatile unsigned int syncpulses = 0; // track sync pulses
volatile byte state = RESET;
volatile byte buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // msg frame buffer
volatile bool reading = false; // have valid reading
unsigned int raincounter = 0;
unsigned int EEMEM raincounter_persist; // persist raincounter in eeprom
#define MARKER 0x5AA5
unsigned int EEMEM eeprom_marker = MARKER; // indicate if we have written to eeprom or not before
const float winddirections[] = { 315.0, 247.5, 292.5, 270.0,
                                 337.5, 225.0, 0.0, 202.5,
                                 67.5, 135.0, 90.0, 112.5,
                                 45.0, 157.5, 22.5, 180.0
                               };
#define MT_WS_WD_RF 49 // wind speed, wind direction, rainfall
#define MT_WS_T_RH 56 // wind speed, temp, RH
void setup() {
  Serial.begin(9600);
  Serial.println(F("Starting Acurite5n1 433 WX Decoder v0.2 ..."));
  pinMode(PIN, INPUT);
  raincounter = getRaincounterEEPROM();
  attachInterrupt(0, My_ISR, CHANGE);
}
void loop() {
  if (reading)
  { noInterrupts();
    if (acurite_crc(buf, sizeof(buf))) {
      // passes crc, good message
      float windspeedkph = getWindSpeed(buf[3], buf[4]);
      Serial.print("windspeed: ");
        Serial.print(convKphMph(windspeedkph), 1);
        Serial.print(" mph, ");
      int msgtype = (buf[2] & 0x3F);
      if (msgtype == MT_WS_WD_RF) {
        float rainfall = 0.00;
        unsigned int curraincounter = getRainfallCounter(buf[5], buf[6]);
        updateRaincounterEEPROM(curraincounter);
        if (raincounter > 0) {
          // track rainfall difference after first run
          rainfall = (curraincounter - raincounter) * 0.01;
        } else {
          // capture starting counter
          raincounter = curraincounter;
        }
        float winddir = getWindDirection(buf[4]);
        Serial.print("wind direction: ");
        Serial.print(winddir, 1);
        Serial.print(", rain gauge: ");
          Serial.print(rainfall, 2);
          Serial.print(" inches");
      } else if (msgtype == MT_WS_T_RH) {
        // wind speed, temp, RH
        float tempf = getTempF(buf[4], buf[5]);
        int humidity = getHumidity(buf[6]);
        bool batteryok = ((buf[2] & 0x40) >> 6);
        Serial.print("temp: ");
          Serial.print(tempf, 1);
          Serial.print(" F, ");
        Serial.print("humidity: ");
        Serial.print(humidity);
        Serial.print(" %RH, battery: ");
        if (batteryok) {
          Serial.print("OK");
        } else {
          Serial.print("LOW");
        }
      } else {
        Serial.print("unknown msgtype: ");
        for (int i = 0; i < 8; i++) {
          Serial.print(buf[i], HEX);
          Serial.print(" ");
        }
      }
      // time
      unsigned int timesincestart = millis() / 60 / 1000;
      Serial.print(", mins since start: ");
      Serial.print(timesincestart);
      Serial.println();
    } else {
      // failed CRC
    }
    reading = false;
    interrupts();
  }
  delay(100);
}
bool acurite_crc(volatile byte row[], int cols) {
  cols -= 1; // last byte is CRC
  int sum = 0;
  for (int i = 0; i < cols; i++) {
    sum += row[i];
  }
  if (sum != 0 && sum % 256 == row[cols]) {
    return true;
  } else {
    return false;
  }
}
float getTempF(byte hibyte, byte lobyte) {
  int highbits = (hibyte & 0x0F) << 7;
  int lowbits = lobyte & 0x7F;
  int rawtemp = highbits | lowbits;
  float temp = (rawtemp - 400) / 10.0;
  return temp;
}
float getWindSpeed(byte hibyte, byte lobyte) {
  int highbits = (hibyte & 0x7F) << 3;
  int lowbits = (lobyte & 0x7F) >> 4;
  float speed = highbits | lowbits;
  // speed in m/s formula according to empirical data
  if (speed > 0) {
    speed = speed * 0.23 + 0.28;
  }
  float kph = speed * 60 * 60 / 1000;
  return kph;
}
float getWindDirection(byte b) {
  int direction = b & 0x0F;
  return winddirections[direction];
}
int getHumidity(byte b) {
  int humidity = b & 0x7F;
  return humidity;
}
int getRainfallCounter(byte hibyte, byte lobyte) {
  int raincounter = ((hibyte & 0x7f) << 7) | (lobyte & 0x7F);
  return raincounter;
}
float convKphMph(float kph) {
  return kph * 0.62137;
}
float convFC(float f) {
  return (f - 32) / 1.8;
}
float convInMm(float in) {
  return in * 25.4;
}
unsigned int getRaincounterEEPROM() {
  unsigned int oldraincounter = 0;
  unsigned int marker = eeprom_read_word(&eeprom_marker);
  if (marker == MARKER) {
    // we have written before, use old value
    oldraincounter = eeprom_read_word(&raincounter_persist);
  }
  return oldraincounter;
}
void updateRaincounterEEPROM(unsigned int raincounter) {
  eeprom_update_word(&raincounter_persist, raincounter);
  eeprom_update_word(&eeprom_marker, MARKER); // indicate first write
}
void My_ISR()
{
  unsigned long timestamp = micros();
  if (digitalRead(PIN) == HIGH) {
    // going high, start timing
    if (timestamp - risets > RESETTIME) {
      // detect reset condition
      state = RESET;
      syncpulses = 0;
      pulsecnt = 0;
    }
    risets = timestamp;
    return;
  }
  unsigned long duration = timestamp - risets;
  if (state == RESET || state == INSYNC) {
    if ((SYNC_LO) < duration && duration < (SYNC_HI)) {
      state = INSYNC;
      syncpulses++;
      if (syncpulses > 3) {
        // found complete sync header
        state = SYNCDONE;
        syncpulses = 0;
        pulsecnt = 0;
      }
      return;
    } else {
      // not interested, reset
      syncpulses = 0;
      pulsecnt = 0;
      state = RESET;
      return;
    }
  } else {
    if ( pulsecnt > MAXBITS ) {
      state = RESET;
      pulsecnt = 0;
      reading = true;
      return;
    }
    byte bytepos = pulsecnt / 8;
    byte bitpos = 7 - (pulsecnt % 8); // reverse bitorder
    if ( LONG_LO < duration && duration < LONG_HI) {
      bitSet(buf[bytepos], bitpos);
      pulsecnt++;
    }
    else if ( SHORT_LO < duration && duration < SHORT_HI) {
      bitClear(buf[bytepos], bitpos);
      pulsecnt++;
    }
  }
}

I don't immediately see any reason why it would interfere with an nRF24.

However almost the whole of the code in loop() is between noInterrupts() and interrupts() and that is lunacy in an interrupt driven system.

All that needs to be done with interrupts off is to read the volatile variables into other working variables. The period with interrupts suspended should be absolutely as short as possible.

Having interrupts suspended will also impact on other Arduino activities such as millis() and Serial.print().

...R

Thanks Robin, I should point out that I am not all the good with C++ so no idea how to change the code to just use the Interrupts when needed.

As a trial I included that code into sketch with a reliably working RF24network node and it is giving occasional errors. As you point out it is also messing with Serial.print. Not all that often, maybe once every two minutes or so when called with a 5000-millis-type delay and not using any delay(5000).

So, how do I go about getting these two to play nice together?

Can you point at where the Interrupts should be on and off?

I set "radio.stopListening();" before "if (reading)..." and reset it at the end but not a great deal of difference.

I think the problem might be that the Nano has no idea when the weather station is going to transmit so it needs to keep listening as does the RF24 and neither is happy.

Post the program that has everything in it and I will have a look at it.

...R

Thanks Robin. I cleaned out the Serial.print statements fr the original 5-in-1 code as I don't need them. This is mounted in a small box attached to the under-side of the patio roof. The RF24 radio works fine if I comment out the "checkWeather();"

Similarly, if I comment out the "network.update();" etc the incoming weather data is fine. They just don't play reliably together. When the systems are combined, about 60% OK, 40% not so.

If I reduce the Tx-interval to five seconds the errors come faster. The RF24 seems to send OK but the 5-in-1 stuff is getting mashed about 40% of the times.

// OUTSIDE WEATHER STATION
#define SYNC_HI 675
#define SYNC_LO 575
#define LONG_HI 450
#define LONG_LO 375
#define SHORT_HI 250
#define SHORT_LO 175
#define RESETTIME 10000
#define PIN 2 // data pin from 433 RX module
#define MAXBITS 65 // max framesize
#define RESET 0 // no sync yet
#define INSYNC 1 // sync pulses detected
#define SYNCDONE 2 // complete sync header received
volatile unsigned int pulsecnt = 0;
volatile unsigned long risets = 0; // track rising edge time
volatile unsigned int syncpulses = 0; // track sync pulses
volatile byte state = RESET;
volatile byte buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // msg frame buffer
volatile bool reading = false; // have valid reading
const float winddirections[] = { 315.0, 247.5, 292.5, 270.0,
                                 337.5, 225.0, 0.0, 202.5,
                                 67.5, 135.0, 90.0, 112.5,
                                 45.0, 157.5, 22.5, 180.0
                               };
#define MT_WS_WD_RF 49 // wind speed, wind direction, rainfall
#define MT_WS_T_RH 56  // wind speed, temp, RH
bool weatherBattery = 0;
float weatherWindSpeed = 0;
float weatherWindDir = 0;
float weatherRainfall = 0;
float weatherTemp = 0;
//***********************************************************************************************
#include <RF24Network.h>
#include <RF24.h>
RF24 radio(9,10);
RF24Network network(radio);
uint32_t sendDuration = 30000;  // Send every thirty Seconds
uint32_t lastSend = 0;
uint32_t sendIdx = 0;

struct data_t 
{ uint8_t NodeTo;
  uint8_t Bool1; 
  float Float1;  
  float Float2;  
  float Float3;  
  float Float4;  
  uint32_t Idx;
};

data_t dataIn;
data_t dataOut;

#define FAIL_OUT "FAIL_OUT "
#define FAIL_IN  "FAIL_IN "

void getTemp()
{ checkWeather();
  dataOut.Bool1 = weatherBattery;
  dataOut.Float1 = weatherWindSpeed;
  dataOut.Float2 = weatherWindDir;
  dataOut.Float3 = weatherRainfall;
  dataOut.Float4 = weatherTemp;
}

void networkSend()
{ int16_t tries = 5; // number of transmission retries
  bool failed = true;
  network.update();
  dataOut.NodeTo = 0;
  dataOut.Idx = ++sendIdx;
  RF24NetworkHeader header(dataOut.NodeTo,'A');
  while (failed && tries > 0) 
  { if (network.write(header,&dataOut,sizeof(dataOut)))
    { failed = false;
    } else
    { tries--;
    }
  }
  if (tries == 0)
  { Serial.print(F(FAIL_OUT));
  }
}

void checkNetwork()
{ while (network.available()) 
  { RF24NetworkHeader header;
    if (network.read(header,&dataIn,sizeof(dataIn)))
    { // Nothing comes this way
    } else
    { Serial.println(F(FAIL_IN));
    }
  }
}

void checkSerial()
{ if (Serial.available())
  { while (Serial.available() > 0)
    { String tStr = Serial.readString();
    }
  }
}

void setup(void)
{ SPI.begin();
  Serial.begin(115200);
  while(!Serial);
  radio.begin();
  radio.setDataRate(RF24_250KBPS);  // RF24_250KBPS, RF24_1MBPS, RF24_2MBPS
  radio.setPALevel(RF24_PA_HIGH);    // RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX
  radio.setChannel(90);
  radio.setAutoAck(1);
  radio.enableAckPayload();
  radio.enableDynamicPayloads();
  radio.setRetries(10,15);          // Max is 15. 0 means 250us, 15 means 4000us.
  network.begin(2);
  Serial.println(F("Starting Acurite5n1 433 WX Decoder v0.2 ..."));
  pinMode(PIN, INPUT);
  attachInterrupt(0, My_ISR, CHANGE);
  lastSend = millis() + sendDuration;  // Force a Send at start up 
}//////////////////////////////////////////////////////////////////
void loop() 
{ network.update();
  if (millis() - lastSend > sendDuration)
  { lastSend = millis();
    getTemp();
    networkSend();
  }
}///////////////////////////////////////////////////////////////////

void checkWeather()
{ if (reading)
  { radio.stopListening();
    noInterrupts();
    if (acurite_crc(buf, sizeof(buf))) 
    { float windspeedkph = getWindSpeed(buf[3], buf[4]);
      weatherWindSpeed = convKphMph(windspeedkph);  // Wind Speed
      int msgtype = (buf[2] & 0x3F);
      if (msgtype == MT_WS_WD_RF) 
      { float rainfall = 0.00;
        unsigned int curraincounter = getRainfallCounter(buf[5], buf[6]);
        rainfall = curraincounter * 0.01;
        float winddir = getWindDirection(buf[4]);
        weatherWindDir = winddir;   // Wind direction
        weatherRainfall = rainfall;  // Rainfall total
      } else 
      if (msgtype == MT_WS_T_RH) 
      { float tempf = getTempF(buf[4], buf[5]);
        int humidity = getHumidity(buf[6]);
        bool batteryok = ((buf[2] & 0x40) >> 6);
        weatherBattery = batteryok;  // Battery state
        weatherTemp = tempf;     // Another Temp
      } else 
      { Serial.print("unknown msgtype: ");
        for (int i = 0; i < 8; i++) 
        { Serial.print(buf[i], HEX);
          Serial.print(" ");
        }
      }
    } else 
    {
      // failed CRC
    }
    reading = false;
    interrupts();
    radio.startListening();
  }
}

bool acurite_crc(volatile byte row[], int cols) 
{ cols -= 1; // last byte is CRC
  int sum = 0;
  for (int i = 0; i < cols; i++) 
  { sum += row[i];
  }
  if (sum != 0 && sum % 256 == row[cols]) 
  { return true;
  } else 
  { return false;
  }
}
float getTempF(byte hibyte, byte lobyte) 
{ int highbits = (hibyte & 0x0F) << 7;
  int lowbits = lobyte & 0x7F;
  int rawtemp = highbits | lowbits;
  float temp = (rawtemp - 400) / 10.0;
  return temp;
}

float getWindSpeed(byte hibyte, byte lobyte) 
{ int highbits = (hibyte & 0x7F) << 3;
  int lowbits = (lobyte & 0x7F) >> 4;
  float speed = highbits | lowbits;
  // speed in m/s formula according to empirical data
  if (speed > 0) 
  { speed = speed * 0.23 + 0.28;
  }
  float kph = speed * 60 * 60 / 1000;
  return kph;
}

float getWindDirection(byte b) 
{ int direction = b & 0x0F;
  return winddirections[direction];
}
int getHumidity(byte b) {
  int humidity = b & 0x7F;
  return humidity;
}

int getRainfallCounter(byte hibyte, byte lobyte) 
{ int raincounter = ((hibyte & 0x7f) << 7) | (lobyte & 0x7F);
  return raincounter;
}

float convKphMph(float kph) 
{ return kph * 0.62137;
}

float convFC(float f) 
{ return (f - 32) / 1.8;
}

float convInMm(float in) 
{ return in * 25.4;
}

void My_ISR()
{ unsigned long timestamp = micros();
  if (digitalRead(PIN) == HIGH) {
    // going high, start timing
    if (timestamp - risets > RESETTIME) 
    { // detect reset condition
      state = RESET;
      syncpulses = 0;
      pulsecnt = 0;
    }
    risets = timestamp;
    return;
  }
  unsigned long duration = timestamp - risets;
  if (state == RESET || state == INSYNC) 
  { if ((SYNC_LO) < duration && duration < (SYNC_HI)) 
    { state = INSYNC;
      syncpulses++;
      if (syncpulses > 3) 
      { state = SYNCDONE;
        syncpulses = 0;
        pulsecnt = 0;
      }
      return;
    } else 
    { syncpulses = 0;
      pulsecnt = 0;
      state = RESET;
      return;
    }
  } else 
  { if ( pulsecnt > MAXBITS ) 
    { state = RESET;
      pulsecnt = 0;
      reading = true;
      return;
    }
    byte bytepos = pulsecnt / 8;
    byte bitpos = 7 - (pulsecnt % 8); // reverse bitorder
    if ( LONG_LO < duration && duration < LONG_HI) 
    { bitSet(buf[bytepos], bitpos);
      pulsecnt++;
    }
    else if ( SHORT_LO < duration && duration < SHORT_HI) 
    { bitClear(buf[bytepos], bitpos);
      pulsecnt++;
    }
  }
}

I should say at the outset that I have never used the RF24Network library and I have no idea how that works.

Your ISR is very long. When I looked briefly at your Original Post I thought it terminated at the first return

What is the ISR doing?

I suspect the problem is with the use of noInterrupts() in checkWeather(). You need to gather all the data from the volatile variables as quickly as possible. Something like

noInterrupts();
  workingWindspeed = windspeed;
  // other volatile variables
interrupts();

But your system is a bit more complex because there seem to be some calculations in the ISR - perhaps you can explain them. I would try to make an ISR as short as

void myISR() {
   pulseCount++;
}

unless I had absoutely no option but to make it more complex.

And, for convenience, can you reorgainze your program so all the important functions are near each other. I find myself continually scrolling from line 133 to 230 and back to try to see the relationships.

...R

Robin, thanks for looking at the code -- but it is not mine. The first block down to ...

float weatherTemp = 0; and all of the function "checkWeather()" is the code created by Jens Jenson I acknowledged in the OP, soi I cannot really answer what or why about it. It s mostly well outside my comprehension of C++ programming.

I would try to make an ISR as short as

Thanks for that, I will endeavor to move stuff around, albeit by trial and error (only basic C++ skills) to minimize the length of time spent in the ISR. Not sure if ISR is the same as an ISR (Install, Stay Resident) from my old DOS programming days, yup, I am that old, but with your comments I will try and lean that section down.

I. too, remember MSDos.

ISR = Interrupt Service Routine

...R

Ah, MSDOS. Back when all in one Apple's with 9" screens used to crash with a little bomb picture and not give you any chance to recover at all.

CrossRoads:
all in one Apple's with 9" screens used to crash

Sorry, would not know. The only apple I have ever had, had a skin and a core and usually tasted good. Too smart with the pennies to own one that came with a power cord and plug. :slight_smile:

Back to topic. Trimoing out everything except critical assignments made no difference. I added a kludge by including a check variable outside the weather-call that I set it with a Random number before the call and read it after. If it is still the same then the data (so far) seems valid. Not pretty, but saves me a lot of hammering around in the dark with C++. :slight_smile:

What do I have to do to get past the 5-minute one post nonsense?
I am still being blocked after 35 posts.

AFAIK the 5 minutes does not disappear until you have done 100 posts. It is a real PITA but it apparently has a had a huge benefit in reducing SPAM.

While the 5 minute limit applies it may make you more productive to compose your Replies in a text editor and then copy and paste them into the Forum when you are satisfied with your text.

Post the latest version of your code so we can keep up with you (not every 5 minutes, however :slight_smile: )

...R