Port Arduino sketch to particle

HI
I have the following code running on an Arduino Uno but would really like it running on a Photon as I need to get the data output in to a database on a raspberry pi. I think the main issues are how the interupts work and the fact that the sketch is referencing some registers that will be specific to the Arduino.
Thanks.


#include <Wire.h>


//Interface Definitions
int     RxPin      = 8;     //The number of signal from the Rx

// Variables for Manchester Receiver Logic:
word    sDelay     = 280;    //Small Delay about 1/4 of bit duration  try like 250 to 500
word    lDelay     = 560;    //Long Delay about 1/2 of bit duration  try like 500 to 1000, 1/4 + 1/2 = 3/4
byte    polarity   = 1;      //0 for lo->hi==1 or 1 for hi->lo==1 for Polarity, sets tempBit at start
byte    tempBit    = 1;      //Reflects the required transition polarity
byte    discards   = 0;      //how many leading "bits" need to be dumped, usually just a zero if anything eg discards=1
byte    discNos    = 0;      //Counter for the Discards
boolean firstZero  = false;  //has it processed the first zero yet?  This a "sync" bit.
boolean noErrors   = true;   //flags if signal does not follow Manchester conventions
//variables for Header detection
byte    headerBits = 15;     //The number of ones expected to make a valid header
byte    headerHits = 0;      //Counts the number of "1"s to determine a header
//Variables for Byte storage
byte    dataByte   = 0;      //Accumulates the bit information
byte    nosBits    = 0;      //Counts to 8 bits within a dataByte
byte    maxBytes   = 9;      //Set the bytes collected after each header. NB if set too high, any end noise will cause an error
byte    nosBytes   = 0;      //Counter stays within 0 -> maxBytes
//Bank array for packet (at least one will be needed)
byte    manchester[12];      //Stores manchester pattern decoded on the fly
//Oregon bit pattern, causes nibble rotation to the right, ABCDabcd becomes DCBAdcba
byte    oregon[]   = {
  16, 32, 64, 128, 1, 2, 4, 8
};
byte    csIndex       = 0;   //counter for nibbles needed for checksum
//Weather Variables
byte    quadrant      = 0;   //used to look up 16 positions around the compass rose
double  avWindspeed   = 0.0;
double  gustWindspeed = 0.0; //now used for general anemometer readings rather than avWindspeed
float   rainTotal     = 0.0;
float   rainRate      = 0.0;
double  temperature   = 0.0;
int     humidity      = 0;
double  intTemp       = 0;
double  intHumi       = 0;
double  intPres       = 0;
byte    intSolar      = 0;   //eg Solar power
byte    intLightning  = 0;   //eg Lightning Strikes
byte    intUV         = 0;   //eg UV Light Levels
const char windDir[16][4] = {
  "N  ", "NNE", "NE ", "ENE",  "E  ", "ESE", "SE ", "SSE",  "S  ", "SSW", "SW ", "WSW",  "W  ", "WNW", "NW ", "NNW"
};

byte    scan      = 0; // &7!=0 means that all three sensors has been detected, so it reports all three with meaningful figures first up (not the latest addition though)
byte  seconds     = 0; // Counter to trigger the 60 seconds worth of data.
byte  batStat     = 0; // bit1= temp, bit2=wind bit3=rain, bit4=exp if true then that sensor has not been logged for 20 minutes, its battery probably getting flat
byte  logTemp     = 0; // Counter for number of minutes a sensor reading is missed
byte  logWind     = 0; // Counter for number of minutes a sensor reading is missed
byte  logRain     = 0; // Counter for number of minutes a sensor reading is missed
byte  logUV       = 0; // Counter for number of minutes a sensor reading is missed
byte  logExp      = 0; // Counter for number of minutes a sensor reading is missed
int   aday        = 0; // Counts the number of minutes in a day and clears battery status every 24hrs

void setup() {
  Serial.begin(9600);
  pinMode(RxPin, INPUT);

  /*
  //  Enable these if attempting to debug the program or the circuit
  Serial.println("Debug Manchester Version 18");
  Serial.print("Using a delay of 1/4 bitWaveform ");
  Serial.print(sDelay,DEC);
  Serial.print(" uSecs 1/2 bitWaveform ");
  Serial.print(lDelay,DEC);
  Serial.println(" uSecs ");
  if (polarity){
    Serial.println("Negative Polarity hi->lo=1");
  }
  else{
    Serial.println("Positive Polarity lo->hi=1");
  }
  Serial.print(headerBits,DEC);
  Serial.println(" bits expected for a valid header");
  if (discards){
    Serial.print(discards,DEC);
    Serial.println(" leading bits discarded from Packet");
  }
  else{
    Serial.println("All bits inside the Packet");
  }
  Serial.println("D 00 00001111 01 22223333 02 44445555 03 66667777 04 88889999 05 AAAABBBB 06 CCCCDDDD 07 EEEEFFFF 08 00001111 09 22223333 0A 44445555");
  */

  //Tutorial on using the BMP05 Press/Temp transducer https://www.sparkfun.com/tutorials/253


  // Initialize Timer1 for a 1 second interrupt
  // Thanks to http://www.engblaze.com/ for this section, see their Interrupt Tutorial
  cli();          // disable global interrupts
  TCCR1A = 0;     // set entire TCCR1A register to 0
  TCCR1B = 0;     // same for TCCR1B
  // set compare match register to desired timer count:
  OCR1A = 15624;
  // turn on CTC mode:
  TCCR1B |= (1 << WGM12);
  // Set CS10 and CS12 bits for 1024 prescaler:
  TCCR1B |= (1 << CS10);
  TCCR1B |= (1 << CS12);
  // enable timer compare interrupt:
  TIMSK1 |= (1 << OCIE1A);
  // enable global interrupts:
  sei();
}

//Routine Driven by Interrupt, trap 1 second interrupts, and output every minute
ISR(TIMER1_COMPA_vect) {
  seconds++;
  if (seconds == 60) { //make 60 for each output
    seconds = 0;
    //Serial.print("One second ...");
    //usbData();//comment this out to temporarily disable data every minute for debug
  }
} //end of interrupt routine

// Main routines, find header, then sync in with it, get a packet, and decode data in it, plus report any errors.
void loop() {
  tempBit = polarity ^ 1;
  noErrors = true;
  firstZero = false;
  headerHits = 0;
  nosBits = 0;
  maxBytes = 15; //too big for any known OS signal
  nosBytes = 0;
  discNos = discards;
  manchester[0] = 0;
  while (noErrors && (nosBytes < maxBytes)) {
    while (digitalRead(RxPin) != tempBit) {
    }
    delayMicroseconds(sDelay);
    if (digitalRead(RxPin) != tempBit) {
      noErrors = false;
    }
    else {
      byte bitState = tempBit ^ polarity;
      delayMicroseconds(lDelay);
      if (digitalRead(RxPin) == tempBit) {
        tempBit = tempBit ^ 1;
      }
      if (bitState == 1) {
        if (!firstZero) {
          headerHits++;
          if (headerHits == headerBits) {
          }
        }
        else {
          add(bitState);
        }
      }
      else {
        if (headerHits < headerBits) {
          noErrors = false;
        }
        else {
          if ((!firstZero) && (headerHits >= headerBits)) {
            firstZero = true;


          }
          add(bitState);
        }
      }
    }
  }

}

void add(byte bitData) {
  if (discNos > 0) {
    discNos--;//discard bits before real data
  }
  else {
    //the incoming bitstream has bytes placed in reversed nibble order on the fly, then the CS is done.
    if (bitData) {
      //if it is a '1' OR it in, others leave at a '0'
      manchester[nosBytes] |= oregon[nosBits];//places the reversed low nibble, with hi nibble, on the fly!!!
    }
    //Oregon Scientific sensors have specific packet lengths
    //Maximum bytes for each sensor must set once the sensor has been detected.

    if (manchester[0] == 0xA1) {
      maxBytes = 10; //wind
      csIndex = 18;
    }
    if (manchester[0] == 0xAF) {
      maxBytes = 9; //temp
      csIndex = 16;
    }
   
    nosBits++;
    //Pack the bits into 8bit bytes
    if (nosBits == 8) {
      nosBits = 0;
      nosBytes++;
      manchester[nosBytes] = 0; //next byte to 0 to accumulate data
    }
    //Check the bytes for a valid packet once maxBytes received
    if (nosBytes == maxBytes) {
      //hexBinDump();

      //Check Checksum first
      if (ValidCS(csIndex)) {
        //Process the byte array into Human readable numbers
        analyseData();
      }
      noErrors = false; //make it begin again from the start
    }
  }
}

//Useful to invoke to debug the byte Array
void hexBinDump() {
  //Serial.println("T A3 10100011 07 00000111 02 00000010 AA 10101010 F0 11110000 06 00000110 FF 11111111 07 00000111 33 00110011 60 01100000");
  Serial.print("D ");
  for ( int i = 0; i < maxBytes; i++) {
    byte mask = B10000000;
    if (manchester[i] < 16) {
      Serial.print("0");
    }
    Serial.print(manchester[i], HEX);
    Serial.print(" ");
    for (int k = 0; k < 8; k++) {
      if (manchester[i] & mask) {
        Serial.print("1");
      }
      else {
        Serial.print("0");
      }
      mask = mask >> 1;
    }
    Serial.print(" ");
  }
  Serial.println();
}

//Support Routines for Nybbles and CheckSum

// http://www.lostbyte.com/Arduino-OSV3/ (9) brian@lostbyte.com
// Directly lifted, then modified from Brian's work. Now nybble's bits are pre-processed into standard order, ie MSNybble + LSNybble
// CS = the sum of nybbles, 1 to (CSpos-1), then compared to CSpos nybble (LSNybble) and CSpos+1 nybble (MSNybble);
// This sums the nybbles in the packet and creates a 1 byte number, and this is compared to the two nybbles beginning at CSpos
// Note that Temp 9 bytes and anemometer 10 bytes, but rainfall uses 11 bytes per packet. (NB Rainfall CS spans a byte boundary)
bool ValidCS(int CSPos) {
  boolean ok = false;
  byte cs = 0;
  for (int x = 1; x < CSPos; x++) {
    byte test = nyb(x);
    cs += test;
  }
  //do it by nybbles as some CS's cross the byte boundaries eg rainfall
  byte check1 = nyb(CSPos);
  byte check2 = nyb(CSPos + 1);
  byte check = (check2 << 4) + check1;
  /*
  if (manchester[0]==0xA2){
  Serial.print(check1,HEX);  //dump out the LSNybble Checksum
   Serial.print("(LSB), ");
   Serial.print(check2,HEX);  //dump out the MSNybble Checksum
   Serial.print("(MSB), ");
   Serial.print(check,HEX);   //dump out the Rx'ed predicted byte Checksum
   Serial.print("(combined), calculated = ");
   Serial.println(cs,HEX);    //dump out the calculated byte Checksum
   //Serial.print("   ");     //Space it out for the next printout
  }
  */
  if (cs == check) {
    ok = true;
  }
  return ok;
}
// Get a nybble from manchester bytes, short name so equations elsewhere are neater :-)
// Enables the byte array to be indexed as an array of nybbles
byte nyb(int nybble) {
  int bite = nybble / 2;       //DIV 2, find the byte
  int nybb  = nybble % 2;      //MOD 2  0=MSB 1=LSB
  byte b = manchester[bite];
  if (nybb == 0) {
    b = (byte)((byte)(b) >> 4);
  }
  else {
    b = (byte)((byte)(b) & (byte)(0xf));
  }
  return b;
}

//enable debug dumps if formatted data required
void analyseData() {
  if (manchester[0] == 0xaf) { //detected the Thermometer and Hygrometer (every 53seconds)
    scan = scan | 1;
    logTemp = 0; //reset missing reads to zero
    thermom();
    dumpThermom();


  }
  if (manchester[0] == 0xa1) { //detected the Anemometer and Wind Direction (every 14seconds)
    scan = scan | 2;
    logWind = 0;//reset missing reads to zero
    anemom();
    dumpAnemom();
    
  }

  //Serial.println(scan,DEC);
  eraseManchester();
}

//Calculation Routines
/*The following bit has been identified by Xander Zimmerman as a 'battery low' indicator
  He used a variable voltage power supply to test signals from rainfall, temp/hum and anemometer eg
  - no more messages: < 2.1 V
  - switch to low < 2.6 V
  - only 1 Bit used: Byte 4; Bit 6: high => Bat low (Nyb8, Bit 2)
  This aligns with the nybble I suspected contained the Battery level indicator
  However it does not tally with my experiments.  However I used resistors to lower the voltage to the sensor,
  rather than a precise and steady voltage.  I am inclined to accept Xander's opinion here but reluctant
  to change my present strategy of notification if the sensor is not detected at all for 20 minutes.
  This strategy allows for instant collapse of the sensor whether it be low batteries, or the cold, or phyiscal damage
  or the transmitter suddenly being shielded or a lightning strike or any other quick demise.  The Oregon LCD base console
  does not have icons that signal a low battery, or the absence of a signal.  So if the "low battery" transmissions are
  missed then the sensor can drop out before action is taken.
  In the case of some batteries, I have them go "low" overnight when it has been very cold only to act normal again the next
  day as the air temperature rose.  Some sort of flag system in the server code would need to capture and reain that status.
  However I am sure if you see value in using the "battery low" bit, then it will be easy enough to incorporate.
  Xander is also a fan of http://www.openhab.org/ for organising his home systems and also https://github.com/wfrog/wfrog
  for rendering the Oregon information into graphs etc.
*/






// WGR800 Wind speed sensor
// Sample Data:
// 0        1        2        3        4        5        6        7        8        9
// A1       98       40       8E       00       0C       70       04       00       34
// 0   1    2   3    4   5    6   7    8   9    A   B    C   D    E   F    0   1    2   3
// 10100001 10011000 01000000 10001110 00000000 00001100 01110000 00000100 00000000 00110100
// -------- -------- bbbb---- NRRRRRRR xxxx9999 xxxxxxxx CCCCDDDD xxxxFFFF 0000---- CCCCcccc
// Av Speed 0.4000000000m/s Gusts 0.7000000000m/s  Direction: N

// byte(0)_byte(1) = Sensor ID?????
// bbbb = Battery indicator??? (7)  My investigations would disagree here.  After exhaustive low battery tests these bits did not change
// NRRRRRRR = Rolling Code Byte, the N bit is set to 1 for 64 cycles to indicate it is reset or new to the Rx box
// 9999 = Direction
// DDDD.CCCC = Gust Speed (m per sec)
// 0000.FFFF = Avg Speed(m per sec)
// multiply by 3600/1000 for km/hr
// ccccCCCC = 1 byte checksum cf. sum of nybbles
// packet length is 20 nybbles

void anemom() {
  //D A1 98 40 8E 08 0C 60 04 00 A4
  avWindspeed = ((nyb(16) * 10) + nyb(15)) * 2.23694/ 10;
  double gust = ((nyb(13) * 10) + nyb(12)) * 2.23694/ 10;
  // after every minute, reset gustWindspeed to avWindspeed and then take the highest gust after that (approx4 readings a minute)
  if (gust > gustWindspeed) {
    gustWindspeed = gust;
  }
  quadrant = nyb(9) & 0xF;
}
void dumpAnemom() {
  Serial.print("Av Speed ");
  Serial.print(avWindspeed);
  Serial.print(" mph, Gusts ");
  Serial.print(gustWindspeed);
  Serial.print(" mph, Direction: ");
  Serial.print(quadrant);
  Serial.print(" -> ");
  Serial.println(windDir[quadrant]);
}

// THGN800 Temperature and Humidity Sensor
// 0        1        2        3        4        5        6        7        8        9          Bytes
// 0   1    2   3    4   5    6   7    8   9    A   B    C   D    E   F    0   1    2   3      nybbles
// 01011111 00010100 01000001 01000000 10001100 10000000 00001100 10100000 10110100 01111001   Bits
// -------- -------- bbbbcccc RRRRRRRR 88889999 AAAABBBB SSSSDDDD EEEE---- CCCCcccc --------   Explanation
// byte(0)_byte(1) = Sensor ID?????
// bbbb = Battery indicator??? (7), My investigations on the anemometer would disagree here.  After exhaustive low battery tests these bits did not change
// RRRRRRRR = Rolling code byte
// nybble(5) is channel selector c (Switch on the sensor to allocate it a number)
// BBBBAAAA.99998888 Temperature in BCD
// SSSS sign for negative (- is !=0)
// EEEEDDDD Humidity in BCD
// ccccCCCC 1 byte checksum cf. sum of nybbles
// Packet length is 18 nybbles and indeterminate after that
// H 00 01 02 03 04 05 06 07 08 09    Byte Sequence
// D AF 82 41 CB 89 42 00 48 85 55    Real example
// Temperature 24.9799995422 degC Humidity 40.0000000000 % rel
void thermom() {
  temperature = (double)((nyb(11) * 100) + (nyb(10) * 10) + nyb(9)) / 10; //accuracy to 0.1 degree seems unlikely
  //The following line has been corrected by Darko in Italy, thank you, Grazie ragazzi!!!
  if (nyb(12) == 8) { //  Trigger a negative temperature
    temperature = -1.0 * temperature;
  }
  humidity = (nyb(14) * 10) + nyb(13);
}
void dumpThermom() {
  Serial.print("Temperature ");
  Serial.print(temperature);
  Serial.print(" degC, Humidity ");
  Serial.print(humidity);
  Serial.println("% Rel");
}

// Formating routine for interface to host computer, output once a minute once all three sesnors have been detected
void usbData() {
  // Stn Id, Packet Type, Wind Quadrant, Wind Speed, Rain Tips, Ext temp, Int Temp, Int Pressure, Int Humidity
               //scan==15 means all 4 readings now have a valid value, ready for output on Serial
    //Battery/Signal status, OR in the the four status values for the signal connections.
    logTemp++;
    if (logTemp>40){
      batStat = batStat | 1;
      }
    logWind++;
    if (logWind>40){
      batStat = batStat | 2;
      }
    
    //reset the batStat to 0 every 24hours
    aday++;
    if (aday>1440){
      batStat=0;
      aday=0;
      }
    // Order: Battery Status, Quadrant, Wind Gust, Rainfall, Temperature, InternalTemp, Internal Pressure, Int Humidity, Solar Power, Lightning, UV Radiation
    Serial.print(batStat,DEC);       //Send out the number to indicate if a sensor is not transmitting for maore than 20 mins. Low Battery or other damage.
    Serial.print(",");
    Serial.print(quadrant);          //0-15 in 22.5 degrees steps clockwise
    Serial.print(",");
    Serial.print(gustWindspeed, 1);  //Gust windspeed km/hr, not average windspeed (graphing over 10 samples gives Average)
    Serial.print(",");
    gustWindspeed = avWindspeed;     //reset gust to average, then take the larger next reading
    Serial.print(",");
    Serial.print(temperature, 2);    // OS Temperature Centigrade
    Serial.println();
  
}

void eraseManchester(){
  for( int i=0; i < 15; i++){
    manchester[i]=0;
  }
}

@bayesp, the hardware timer used in the code can be replaced by a Software Timer with the ā€œinterruptā€ code remaining mostly the same. Software Timers are not as accurate as hardware timers but for your use case, a few milliseconds is tolerable IMO.

So, this chunk of code:

  // Initialize Timer1 for a 1 second interrupt
  // Thanks to http://www.engblaze.com/ for this section, see their Interrupt Tutorial
  cli();          // disable global interrupts
  TCCR1A = 0;     // set entire TCCR1A register to 0
  TCCR1B = 0;     // same for TCCR1B
  // set compare match register to desired timer count:
  OCR1A = 15624;
  // turn on CTC mode:
  TCCR1B |= (1 << WGM12);
  // Set CS10 and CS12 bits for 1024 prescaler:
  TCCR1B |= (1 << CS10);
  TCCR1B |= (1 << CS12);
  // enable timer compare interrupt:
  TIMSK1 |= (1 << OCIE1A);
  // enable global interrupts:
  sei();

would be replaced with a Timer declaration outside of setup() which could look like this:
Timer oneSecTimer(1000, oneSecCallback); //Set interval to 1000ms or 1s

Then, in setup() you would start the time with:
oneSecTimer.start();

And finally, the ā€œinterruptā€ code, which is now the Software Timer callback code, becomes:

void oneSecCallback() {
  seconds++;
  if (seconds == 60) {   //make 60 for each output
    seconds = 0;
  }
}
1 Like

Wow, thanks for the speedy response. Iā€™ll give it a go tomorrow.

1 Like

Hi Sorry to be a pain. I tried to flash the modified code and the device goes offline almost instantly and I have to flash tinker over USB to get it back. I tried removing the timer as I am not using the function that relies on it but with the same result. It compiles fine, but flashing this firmware seems to kill it.
I can flash the ā€˜blink and LEDā€™ sketch OTA, so it definitely is an issue with my code.

@bayesp, can you share your code?

https://go.particle.io/shared_apps/5ee8f7608533f6000715cf90

@bayesp, you are running your code without SYSTEM_THREAD(ENABLED) and are not calling Particle.process() to keep give time to the DeviceOS to run. In loop(), you have while() statements which will block until bits are received, not allowing loop() to end and the DeviceOS getting time to run.

I suggest you run with SYSTEM_THREAD(ENABLED), allowing the Software Timer thread to run better and preventing the application thread from blocking the DeviceOS. Keep in mind, though, that with threading enabled, the application thread will start immediately and not wait for the Particle Cloud connection so before you do any Cloud operations, you will want to make sure the connection is there. Typically this is done by checking Paricle.connected().

1 Like

Thanks again. I will try and implement your suggestions when I get a chance.

Thanks again, seems to be working now, at least I think it is but unusually there is no wind.

1 Like

OK, mostly working but I am getting a panic, hard_fault error frequently. My latest attempt is here:https://go.particle.io/shared_apps/5ee9195c8533f6001615d307

So it worked all night, albeit erring and resetting regularly as mentioned above. I added a publish for the thermometer and that also worked, still with the recurring errors. I had to unplug it and plug it back in again and since then I only get temperature and humidity (although humidity is always 0, and no anemometer readings, but I am also not getting the errors?? so the rrors must be related to the anenom function? Its really tricky to debug as I didnā€™t write the original code and it was written for Arduino so some of it is maybe not quite right for a Photon. I dont really understand the point of a bitwise and with 0xF on a nibble as surely going to give you the same value back? Any ideas gratefully received.
Latest version:
https://go.particle.io/shared_apps/5ee9f3e56c2eea000c44526b

@bayesp, can you provide a link to the original library? Which hardware exactly is being used in this project (I assume it involves a 433MHz receiver)?

Sure.


Yes, I am using a 433mhz receiver, like this.

I only have the anenometer and temperature gauge.

Thanks

@bayesp, I get the impression that this library, which uses an interrupt-based Oregon data decoder is better suited to your application. The code includes a web server which you wonā€™t need. The code would easily be adapted for the Photon.

1 Like

Thanks. I had looked at this one but it is based on the OS protocol v2, my sensors use v3. I will see how hard it is to change the decoding from v2 to v3

@bayesp, if you look carefully, the V3 protocol is also supported.

Ah, sorry. Will take another look.

The saga continues.
I took the code you suggested, removed most of what I donā€™t want, fixed the error that came up when first compiling and got a successful compile.
https://go.particle.io/shared_apps/5eeb6615f6738c00074cbae5
I am now getting an SOS led signal followed by 14 flashes (heap error). Any ideas what I have done wrong this time?
Thanks

@bayesp, calling Particle.publish() in an interrupt service routine is an absolute no-no! Thatā€™s where your heap error is coming from. At best, blink a LED an an indicator. You canā€™t even put a `Serial.print()!

void ext_int_1(void) {
    Particle.publish("interupt", "found", PRIVATE);  <---- CAN'T DO THIS IN ISR!!!
  static word last;
  // determine the pulse length in microseconds, for either polarity
  pulse = micros() - last;
  last += pulse;
}
1 Like

Oops. I just wanted to see if it even was seeing a pulse as I was getting no output at all. Will try again.