This chapter is not really for every one. Maybe a little too technical for the average auto electrician. This is an early version of an Arduino sketch for a network node reading temperature values from digital temperature sensors ($1.80 each) and sending the data to a gateway with Ethernet port to be forwarded to the central processor for processing and display. This is not a programming lesson, but the few who might be interested to see what an Arduino sketch looks like may have something to ponder over. For an IT person this is probably peanuts, since I used ancient C/C++ here and the level of complexity is minimal.

It is just simple straight forward data collection. After a little bit of learning about the hardware of the Arduinos I find it really easy to utilise them for the project. I have already done some error recovery in the programming, so I can remove power on any of the nodes or unplug the bus and put it back and all components will nicely restart and synchronise. I plan to have all the same code in my Arduinos and any configuration which might be needed will be downloaded from the central system. I do not plan to have individual configurations done over the serial port or a HTML page or such. I don’t like this because it is much harder to commission a system with nodes which require individual configuration. I am not sure at this stage if I can manage that since the memory of an Arduino is limited and I am not sure at this stage if I can fit everything in for all the signals I am going to use. I am optimistic though that I get it done.

If anyone wants the latest source code, just email me.

25.3.2015 I have updated the code with a non interrupt version and storing the serial number in EEPROM for quicker restart. This is the basis for further versions. See comment in the code about the interrupt. All future config info will go into the EEPROM. No command yet to clear the ROM and force a rewrite. This needs to be done in case the id chip will change or some other parameters. Will create a command for that. Note: the soft reset with call of address 0 works well. I am going to use this when the Arduino did not receive anything form any node for a certain time span. Then it will self reset and start identification and configuration again. This even cleared the frozen interrupt, but I am not going to use that.

 

#include <mcp_can.h>
#include <mcp_can_dfs.h>
#include <mcp_can.h>                    // CAN bus library
#include <SPI.h>                        // serial library
#include <OneWire.h>                    // one wire library
#include <DallasTemperature.h>          // dallas library for ds18b20
#include <Timer.h>                      // Timer Library from Jack Christensen
#include <EEPROM.h>

/* ----------------------------------------------------------------------------------------------------------
 *    Address definitions SRAM
 * ----------------------------------------------------------------------------------------------------------*/
DeviceAddress serialNumber = {0,0,0,0,0,0,0,0};      // once filled store it in EEPROM  
boolean idInitialised=false;

byte ourClientId=0;
byte gatewayId=0xF0;
int sendTempData=0;  
long requestId=0;
Timer watchDogTimer;
Timer temperatureTimer;
Timer t;
int heartBeat = 0;
int ledEvent;

/* ----------------------------------------------------------------------------------------------------------
 *    Address definitions EEPROM
 *    Configuration information is stored in the EEPROM of the processor to retain it after reset
 * ----------------------------------------------------------------------------------------------------------*/
#define EE_dataAvailable  0
#define EE_idInitialised  1
#define EE_SerialNumber   2    // 8 bytes
#define EE_ourClientId    10
/* ----------------------------------------------------------------------------------------------------------
 *    Temperature sensor definitions
 * ----------------------------------------------------------------------------------------------------------*/
#define ONE_WIRE_BUS 3                  // Data wire is plugged into pin 3 on the Arduino
OneWire oneWire(ONE_WIRE_BUS);          // Setup a oneWire instance to communicate with any OneWire devices
DallasTemperature sensors(&oneWire);    // Pass our oneWire reference to Dallas Temperature.
int numTempSensors = 0;                 // Auto configured
DeviceAddress deviceList[10] = {  { 0,0,0,0,0,0,0,0 }, { 0,0,0,0,0,0,0,0 }, { 0,0,0,0,0,0,0,0 },
                                  { 0,0,0,0,0,0,0,0 }, { 0,0,0,0,0,0,0,0 }, { 0,0,0,0,0,0,0,0 },
                                  { 0,0,0,0,0,0,0,0 }, { 0,0,0,0,0,0,0,0 }, { 0,0,0,0,0,0,0,0 },
                                  { 0,0,0,0,0,0,0,0 } }; 
                                  // did I say that I hate C - the effort of making this dynamic !
float tempList[10];               // list to store the temperature values
int tempSensorStatusList[10]= { 0,0,0,0,0,0,0,0,0,0};    // status is 1 when sensor has an id 
                                                          // 2 = sensor active 3= sensor inactive
#define TSENS_UNKNOWN 0   
#define TSENS_PRESENT 1
#define TSENS_ACTIVE  2
#define TSENS_FAULT   3

                                                         
/* ----------------------------------------------------------------------------------------------------------
 *    CAN bus definitions
 * ----------------------------------------------------------------------------------------------------------*/
MCP_CAN CAN(9);                                           // Set CS to pin 9 for CAN shield
// input buffer for CAN read
unsigned char readLength = 0;
unsigned char readBuffer[8];
// output buffer for CAN write 
unsigned char stmp[8]     = {0, 0, 0, 0, 0, 0, 0, 0};
unsigned char initStmp[8] = {0, 0, 0, 0, 0, 0, 0, 0};
#define MESSAGE_LENGTH 8
/* ----------------------------------------------------------------------------------------------------------
 *    Initialise at startup
 * ----------------------------------------------------------------------------------------------------------*/
void setup(){
    Serial.begin(9600);
    sensors.begin();
    
    while(CAN_OK != CAN.begin(CAN_500KBPS)) {                 // init can bus : baudrate = 500k
        Serial.println("CAN BUS Shield init fail");
        delay(500);
    }
    Serial.println("CAN BUS Shield init ok!");
    discoverOneWireDevices();
    delay(50);
    
    // pinMode(2, INPUT);                                        // Setting pin 2 for /INT input
    // attachInterrupt(0, handleCanRequest, FALLING);            // start interrupt
    
    // first we check if we were initialised before and have a serial and data in the eeprom
    // unsigned char aByte[4] = { 0 ,0,0,0};
    char aByte1 = EEPROM.read(EE_dataAvailable);
    char aByte2 = EEPROM.read(EE_idInitialised);
    if (aByte1 == 0xFF) {
        Serial.println("I am a virgin");
    }else {
        Serial.print("Read EEPROM ");    
        if (aByte2 == 0x10) {
           retrieveSerial();
           if ( ourClientId == 0xFF)                             // something is wrong - identify again 
              idInitialised = false;
        }
    }
    
    delay(50);
    watchDogTimer.every(10000, checkBusHeartbeat);
    temperatureTimer.every(500, readTemperatures);
    if (!idInitialised)
        sendRestartMessage();                                 // tell the gateway we are a new kid on the block
    
    //pinMode(13, OUTPUT);                                    // not using interrupt
    //ledEvent = t.oscillate(13, 50, HIGH,5);                 // the seed studio shield freezes the bus after a while
    // when the interrupt cannot be cleared through any means other than reset of the processor. 
    // this happened every 20 minutes or so .. without interrupt it is running now for 36 hours without 
    // any hickup. I get other shields - even cheaper - will see if this still happens. 
    
}
void(* resetFunc) (void) = 0;                                 //declare reset function @ address 0
/* ----------------------------------------------------------------------------------------------------------
 *    Identification routines
 * ----------------------------------------------------------------------------------------------------------
 * For all Arduinos which have temperature sensors connected we use one sensor, connected
 * to the break out shield as an id chip as well as enclosure internal temperature sensor.
 * Firstly, we search for all one wire devices and we assume they are all tenperature sensors.
 * The id sensor has a known, pre-recorded id known by the central system and passed down 
 * to the gateway, which sends a serialNumber check command and the serial number down the 
 * bus for all nodes to verify. The serial number message also contains the bus id, which 
 * the nodes will use to tag each command sent over the bus. Each node will compare the 
 * serial number sent from the gateway with all recognised device addresses and if one 
 * matches the serial number is accepted. Once the node is identified it will not process
 * checkSerial commands anymore.
 */
// search for all one wire devices 
void discoverOneWireDevices(void) {
  byte i;
  byte present = 0;
  byte addr[8];
  Serial.print("Looking for 1-Wire devices...\n\r");
  while(oneWire.search(addr)) {
    for( i = 0; i < 8; i++) {
      Serial.print("0x");
      if (addr[i] < 16) {
        Serial.print('0');
      }
      Serial.print(addr[i], HEX);
      if (i < 7) {
        Serial.print(", ");
      }
    }
    for( i = 0; i < 8; i++) {
      deviceList[present][i]=addr[i];                    // store the id
      tempSensorStatusList[present]=TSENS_PRESENT;
    }
    present++;
    Serial.println();
    if ( OneWire::crc8( addr, 7) != addr[7]) {
        Serial.print("CRC is not valid!\n");
        return;
    }
  }
  numTempSensors = present;                              // assuming all one wire devices are temp sensors
  Serial.print("Number of Sensors: ");
  Serial.println(present);
  oneWire.reset_search();
  return;
}
/* ----------------------------------------------------------------------------------------------------------
 *    Handling serial number tasks
 * ---------------------------------------------------------------------------------------------------------- */
void checkSerial(){
  int partOffset = readBuffer[3];
  for(int i=0; i<4; i++) {
      serialNumber[partOffset*4 + i]=readBuffer[4+i];
  }
  if (partOffset == 1) {                                      // serial number is complete 
    if (compareDeviceAdress(serialNumber)){
      ourClientId = readBuffer[0];
      gatewayId = CAN.getCanId();
      idInitialised = true;
      Serial.println("My serial is matched");
      storeSerial();
    }
  }
}
// write the serial number to the EEPROM for recovery after reset 
// we do not need to bother the gateway with identifying us 
void storeSerial() {
     for (int i=0; i<8;i++) {
       EEPROM.write(EE_SerialNumber+i, serialNumber[i]);
     }  
     EEPROM.write(EE_idInitialised, 0x10);
     EEPROM.write(EE_dataAvailable, 0x01);
     EEPROM.write(EE_ourClientId, ourClientId);
     
}
// read the serial number from the EEPROM 
void retrieveSerial() {
     for (int i=0; i<8;i++) {
       serialNumber[i]  = EEPROM.read(EE_SerialNumber+i);
       Serial.print(serialNumber[i],HEX); 
       Serial.print(",");
     }  
      Serial.println();
      idInitialised=true;
      ourClientId = EEPROM.read(EE_ourClientId);
      Serial.print("Client Id: "); 
      Serial.print(ourClientId,HEX);
}
boolean compareSerial (DeviceAddress adr1, DeviceAddress adr2) {
     for (int i=0; i<8;i++) {
        if (adr1[i]!=adr2[i])
           return false;
     }
     return true;
}
boolean compareDeviceAdress(DeviceAddress adr1) {
   Serial.print("Comparing Adr :");
    Serial.println(numTempSensors);
   for (int i=0; i<numTempSensors; i++) {
      if (tempSensorStatusList[i]>TSENS_UNKNOWN){
         if (compareSerial(adr1, deviceList[i])){
            return true;
         }
      }
   }   
   return false; 
}
/* ----------------------------------------------------------------------------------------------------------
 *    Temperature sensor routines
 * ----------------------------------------------------------------------------------------------------------*/
// set sensor to 10 bit resolution
void setTempSensorResolution(int index) {
  if (tempSensorStatusList[index]>TSENS_UNKNOWN)
     sensors.setResolution(deviceList[index], 10);            // set the resolution to 10 bit
}
// read all temperatures into array of float variables
void readTemps(float* temps, DeviceAddress* devices,int numSensors,int offset){
  sensors.requestTemperatures();
  for (int i=0; i< numSensors; i++ ) {
    if (tempSensorStatusList[i+offset]>TSENS_UNKNOWN){
    float tempC =  sensors.getTempC(devices[i+offset]);
       if (tempC != -127) {
         temps[i+offset] =tempC ;
         if (tempSensorStatusList[i+offset]==TSENS_FAULT) {
           Serial.print("Temperature sensor online: ");
           Serial.println(i+offset);
         }
           tempSensorStatusList[i+offset]=TSENS_ACTIVE;
       }
       else {
         if (tempSensorStatusList[i+offset]!=TSENS_ACTIVE) {
           tempSensorStatusList[i+offset]=TSENS_FAULT;
           Serial.print("Temperature sensor offline: ");
           Serial.println(i+offset);
         }
       }  
    }
 }  
}
// transfer the first or second 4 bytes of the sensor id from message buffer to deviceList
void setTemperatureSensorId(){
  int deviceNumber = readBuffer[2];
  int isSecondHalf = readBuffer[3];
  for(int i=0; i<4; i++) {
      deviceList[deviceNumber][isSecondHalf*4 + i]=readBuffer[4+i];
  }
  if (isSecondHalf==1){                                      // the second half was tranfered
      tempSensorStatusList[deviceNumber]=TSENS_PRESENT;
      setTempSensorResolution(deviceNumber);                 // we have an id, we can set resolution
      Serial.println("Temperature sensor defined");
   }
}
#define CHECK_SERIAL 0x0A
#define SEND_TEMPDATA 0x01
#define SEND_SENSORIDS 0x11
#define SENSOR_OFFLINE 0xF0
#define RESTART_MESSAGE 0xFF
/* ----------------------------------------------------------------------------------------------------------
 *    CAN BUS routines
 * ----------------------------------------------------------------------------------------------------------*/
// Interrupt service for CAN request
// we do not send any CAN messages from here, only set flags to do that after we have 
// serviced the interrupt. This should be as quick as possible.
void handleCanRequest() {
    while (CAN_MSGAVAIL == CAN.checkReceive()) {
      CAN.readMsgBuf(&readLength, readBuffer);                 // read the data
      switch   (readBuffer[1]) {                             
      case CHECK_SERIAL:   
                  if (idInitialised == false)                  // only process the request when we need to
                      checkSerial();                           // check serial number , set our id
                   break;
      case SEND_TEMPDATA:
                  if (idInitialised)  {                        // 1== read temperatures command
                      sendTempData=1;                          // only send when we are initialised
                   }
                   else
                      sendRestartMessage();                    // if undefined force a restart
                   break;
       case SEND_SENSORIDS:   
                   Serial.println("Send temp sensor ids");
                   sendTempSensorId(0,ourClientId);
                   sendTempSensorId(1,ourClientId);
                   break;
      }
    }
}
// send one temperature per CAN message 
void sendTemperatures() {
       sendTempData=0;
       for (int i=0; i<numTempSensors; i++) {                
        if (tempSensorStatusList[i]==2) {
           formatMessageBuffer(stmp,i,(int)( tempList[i]*100),ourClientId+i);
           sendCanMessage(ourClientId, 0, MESSAGE_LENGTH, stmp); 
        }else{
           sendOfflineMessage(stmp,i,ourClientId); 
        }
      }
}
/* ----------------------------------------------------------------------------------------------------------
 *    CAN bus message functions
 * ----------------------------------------------------------------------------------------------------------*/
// send all temperature sensor ids to the gateway so the central system 
// will know what signals we are dealing with.
void sendTempSensorId(int sensorNumber, int id) {
  for (int i=0; i<8; i++) {
     initStmp[i] = deviceList[sensorNumber][i]; 
  }
  sendCanMessage(id, 0, MESSAGE_LENGTH, initStmp);  
}
// Sending a restart message will trigger the gateway to repeat identification and configuration
// of our id and the connected sensors. Whenever something serious happens a restart is the best
// recovery. Everything is reset. At this stage I don't believe a require a specific memory of 
// any variable since the central unit will resend all parameters through the gateway.
void sendRestartMessage() {
   clearBuffer(initStmp);
   initStmp[0] = gatewayId;         // send to id
   initStmp[1] = RESTART_MESSAGE;   // Restart Command
   sendCanMessage(ourClientId, 0, MESSAGE_LENGTH, initStmp);
}
/*   Status definitions from the CAN library - we do not use this yet
#define CAN_OK         (0)
#define CAN_FAILINIT   (1)
#define CAN_FAILTX     (2)
#define CAN_MSGAVAIL   (3)
#define CAN_NOMSG      (4)
#define CAN_CTRLERROR  (5)
#define CAN_GETTXBFTIMEOUT (6)
#define CAN_SENDMSGTIMEOUT (7)
#define CAN_FAIL       (0xff)
*/
void sendCanMessage(int clientId,int format, int length,  unsigned char* buf) {
   int canStatus = CAN.sendMsgBuf(clientId, format, length, buf); 
   if (canStatus != 0) {
     Serial.print("CAN bus status ");
     Serial.print(canStatus);
     Serial.println();    
   }else {
     heartBeat++;
   }
   delay(10);                          // send all temperature values with 50ms delay
}
// create the packet to send a temperature - 4 digits with 2 decimals and no decimal point
void formatMessageBuffer(unsigned char* buf,  int sensorNumber, int value, int tempId) {
      buf[0]=gatewayId;                 // target is gateway
      buf[1]=sensorNumber;              // the relevant sensor number
      String str = String(value);
      buf[2]=str[0];                    // 4 bytes of temperature
      buf[3]=str[1];
      buf[4]=str[2];
      buf[5]=str[3];
}
void sendOfflineMessage(unsigned char* buf,int sensorNumber, int tempId) {
      clearBuffer(buf);
      buf[0]=gatewayId;
      buf[1]=SENSOR_OFFLINE;            // temp sensor offline message
      buf[2]=sensorNumber;
      sendCanMessage(ourClientId, 0, MESSAGE_LENGTH, buf); 
}
// pre initialise the message buffer with 0
void clearBuffer(unsigned char* buf) {
    for (int i=0; i<readLength;i++) {
       buf[i]=0; 
    }
}

/* ----------------------------------------------------------------------------------------------------------
 *    Timed functions
 * ----------------------------------------------------------------------------------------------------------*/
void checkBusHeartbeat() {
    // Serial.println("Check Heartbeat"); 
    if (heartBeat == 0 ) {
       // we may have a frozen bus -- we need to restart 
       Serial.println("No Heartbeat"); 
       resetFunc();
    }
    heartBeat = 0;
}

void readTemperatures() {
    readTemps(tempList,deviceList,numTempSensors,0);
}

/* ----------------------------------------------------------------------------------------------------------
 *    MAIN program loop
 * ----------------------------------------------------------------------------------------------------------*/
void loop() {
     watchDogTimer.update();
     temperatureTimer.update();
     // t.update();
     handleCanRequest();
     if (sendTempData==1){ 
        sendTemperatures();
     }
}