16Bit ADC, Interrupts and CAN_BUS [Arduino Due]

Hi,

actually I’m trying to read 2 Sensores with 4-20mA and a Resolution of 16Bit.
Also I do have 2 more Sensors that do give me Interrupts ( a lot of them ).

My Setup is:

  • 1 Arduino Due
  • 1 Adafruit ADS1115

So actually I am able to read evertything seperately. and it is also possible to send that stuff to the CAN-Bus.

After figuring out, that the AD-Convertion is a bit too slow, i decided to switch to the I2Cdev lib.
With that Lib I was able to save about 6-8ms for 2 AD Ports.

The Problem I do have right now, is that the externel Interrups, may kill my arduino…

I have already scaled them down form about 150 000 Interrupts per Second to 12 000 Interrupts per Second.

The AD-Convertion takes about 8 -10 ms ( my Goal would be <4 ms ).

The Interrupts are generated by movement of the external device, so and this is not really planable, but after a short time driving ( not even full speed ) the Arduino stops working and kills the CAN-Bus…

do you guys have any idea what i can do?

#include <Adafruit_FRAM_I2C.h>
#include <Arduino.h>
#include <DueTimer.h>
#include <Scheduler.h>
#include <Wire.h>
#include <ADS1115.h>
#include <I2Cdev.h>
#include "variant.h"
#include <due_can.h>

ADS1115 ads1(0x48);
int16_t adc10, adc11, adc12, adc13;
short int adc10_temp, adc11_temp, adc12_temp, adc13_temp;


#define CAN_COMM_MB_IDX               0
#define CAN0_TX_PRIO                  15
#define CAN_MSG_DUMMY_DATA            0x66FFBB11   
#define MAX_CAN_FRAME_DATA_LEN        8

#define CAN_TRANSFER_ID_SPINDEL_SEN   0xA1       
#define CAN_TRANSFER_ID_ZAHNRAD_SEN   0xA2        
#define CAN_TRANSFER_ID_RO_PRE_SEN    0xA3         
// #define CAN_TRANSFER_ID_RESERVE       0xA4        

// Message variable to be send
uint32_t CAN_MSG_1 = 0;
CAN_FRAME CAN_msg_spindel_sen, CAN_msg_zahnrad_sen, CAN_msg_ro_pre_sen;

// *****************************************************************************************
// Define Pins
// *****************************************************************************************

int ref_null = 37;      // Referenz NULL Taster

int zahnrad_ch_2  = 41; 
int zahnrad_ch_1  = 43; 

int spindel_ch_2  = 45; 
int spindel_ch_1  = 47; 

// *****************************************************************************************
// Ref-Taster Entprellen
// *****************************************************************************************

bool tasterGedrueckt = false;
int  tasterStatus = LOW;
int  entprellZeit = 200;
unsigned long tasterZeit = 0;

// *****************************************************************************************
// Variablen fuer Spindel und Zahnrad Sensor
// *****************************************************************************************

int   spindel_interpol  = 10;   
int   zahnrad_interpol  = 16;   
float spindel_Vm;               // in [ml]
float zahnrad_Vm;               // in [ml]
float spindel_ink_in_mm;        
float zahnrad_ink_in_mm;        

float zahnrad_weg_neu,    zahnrad_weg_alt,    spindel_weg_neu,    spindel_weg_alt;
float zahnrad_geschw_neu, zahnrad_geschw_alt, spindel_geschw_neu, spindel_geschw_alt;
float zahnrad_beschl,     spindel_beschl;

long spindel_Ink, zahnrad_Ink;

// Hilfsvariablen fuer CAN
short int spindel_s_temp2, zahnrad_s_temp2, spindel_v_temp2, zahnrad_v_temp2, spindel_a_temp2, zahnrad_a_temp2;
unsigned short int spindel_s_temp, zahnrad_s_temp;
int spindel_v_temp, spindel_a_temp, zahnrad_v_temp, zahnrad_a_temp;


unsigned long interval = 2000; // in µs
unsigned long dt, currentMicros, previousMicros;

bool toggle_Send = false;
bool zahnrad_ch_1_sw = true;

// *****************************************************************************************
// Konstanten fuer Stielzylinder
// *****************************************************************************************

float zyl_Vol = 1162.075123;    
int   Stielzyl_radius = 3;      


void setup()
{

  //  Wire.setClock(400000);
  Wire.begin();
  Serial.begin(115200);


  // *****************************************************************************************
  // Pin-Configuration
  // *****************************************************************************************

  // Spindel Sensor: 0.5815 [ml/Imp]
  pinMode(spindel_ch_1, INPUT);
  pinMode(spindel_ch_2, INPUT);

  // Zahnrad Sensor: 1 [ml/Imp]
  pinMode(zahnrad_ch_1, INPUT);
  pinMode(zahnrad_ch_2, INPUT);

  // Referenz Null Taster
  pinMode(ref_null, INPUT);


  // *****************************************************************************************
  // Sensor-Messvolumen [ml/Ink]
  // *****************************************************************************************

  spindel_Vm = 0.5815 / spindel_interpol;
  zahnrad_Vm = 0.0625; //in [cm³]
  spindel_ink_in_mm = (10 * spindel_Vm) / (PI * Stielzyl_radius * Stielzyl_radius); // mm pro inkrement
  zahnrad_ink_in_mm = (10 * zahnrad_Vm) / (PI * Stielzyl_radius * Stielzyl_radius); // mm pro inkrement

attachInterrupt(zahnrad_ch_1, Sensor_Ink, FALLING);

  ads1.initialize();
  ads1.setMode(ADS1115_MODE_SINGLESHOT);
  ads1.setRate(ADS1115_RATE_475);
  ads1.setGain(ADS1115_PGA_4P096); // +/- 4.096V


  CAN.begin(CAN_BPS_1000K);
  CAN.init(CAN_BPS_1000K);

  CAN_msg_spindel_sen.id = CAN_TRANSFER_ID_SPINDEL_SEN;
  CAN_msg_zahnrad_sen.id = CAN_TRANSFER_ID_ZAHNRAD_SEN;
  CAN_msg_ro_pre_sen.id = CAN_TRANSFER_ID_RO_PRE_SEN;

  CAN_msg_spindel_sen.length = MAX_CAN_FRAME_DATA_LEN;
  CAN_msg_zahnrad_sen.length = MAX_CAN_FRAME_DATA_LEN;
  CAN_msg_ro_pre_sen.length = MAX_CAN_FRAME_DATA_LEN;

  CAN_msg_spindel_sen.priority = 4;
  CAN_msg_zahnrad_sen.priority = 3;
  CAN_msg_ro_pre_sen.priority = 2;

  CAN_msg_spindel_sen.extended = false;
  CAN_msg_zahnrad_sen.extended = false;
  CAN_msg_ro_pre_sen.extended = false;

}

void loop()
{

  AD_Stiel_Read();  

  Ref_Null(); // Stielzylinder Position Nullen

  
  currentMicros = micros();
  if (currentMicros - previousMicros > interval)
  {
    Sensor_Calc_and_Send();
    previousMicros = currentMicros;
  }

}

void AD_Stiel_Read()
{
  ads1.triggerConversion();
  adc10 = ads1.getConversionP0GND();   
  adc11 = ads1.getConversionP1GND();   

  CAN_msg_ro_pre_sen.data.byte[4] = (adc10 >> 8);
  adc10_temp = (adc10 << 8);
  CAN_msg_ro_pre_sen.data.byte[5] = (adc10_temp >> 8);

  CAN_msg_ro_pre_sen.data.byte[6] = (adc11 >> 8);
  adc11_temp = (adc11 << 8);
  CAN_msg_ro_pre_sen.data.byte[7] = (adc11_temp >> 8);

  CAN.sendFrame(CAN_msg_ro_pre_sen);
}

void Ref_Null()
{
  // Ref-Taster-Abfrage inkl. entprellen
  tasterStatus = digitalRead(ref_null);

  if (tasterStatus == HIGH)
  {
    tasterZeit = millis();
    tasterGedrueckt = true;
  }

  if ((millis() - tasterZeit > entprellZeit) && tasterGedrueckt == 1)
  {
    spindel_Ink = 0;
    zahnrad_Ink = 0;
    tasterGedrueckt = false;
  }
}

void Sensor_Ink()
{
  if (digitalRead(spindel_ch_1) == LOW && digitalRead(spindel_ch_2) == LOW)
  {
    spindel_Ink++;
    
  }
  else
  {
    spindel_Ink--;
    
  }

  if (digitalRead(zahnrad_ch_1) != zahnrad_ch_1_sw && digitalRead(zahnrad_ch_1) == LOW)
  {
    
    if (digitalRead(zahnrad_ch_1) == LOW && digitalRead(zahnrad_ch_2) == LOW)
    {
      zahnrad_Ink++;
    }
    else
    {
      zahnrad_Ink--;
    }
    zahnrad_ch_1_sw = false;
  }
  else if (digitalRead(zahnrad_ch_1) == HIGH)
  {
     zahnrad_ch_1_sw = true;
  }
}

void Sensor_Calc_and_Send()
{
  dt = interval + 55; // in [µs]

  //   Berechnungen für den Spindelsensor
  spindel_weg_neu     = spindel_ink_in_mm * spindel_Ink;
  spindel_geschw_neu  = ((spindel_weg_neu - spindel_weg_alt) * 1000000) / dt; 
  spindel_beschl      = ((spindel_geschw_neu - spindel_geschw_alt) * 1000000) / dt; 

  spindel_weg_alt     = spindel_weg_neu;
  spindel_geschw_alt  = spindel_geschw_neu;

  spindel_s_temp = (unsigned short int)(spindel_weg_neu * 100);  
  spindel_v_temp = (int)(spindel_geschw_neu * 1000);    
  spindel_a_temp = (int)(spindel_beschl * 1000);        


  // Berechnungen für den Zahnradsensor
  zahnrad_weg_neu     = zahnrad_ink_in_mm * zahnrad_Ink;
  zahnrad_geschw_neu  = ((zahnrad_weg_neu - zahnrad_weg_alt) * 1000000) / dt;       
  zahnrad_beschl      = ((zahnrad_geschw_neu - zahnrad_geschw_alt) * 1000000) / dt; 

  zahnrad_weg_alt     = zahnrad_weg_neu;
  zahnrad_geschw_alt  = zahnrad_geschw_neu;

  zahnrad_s_temp = (unsigned short int)(zahnrad_weg_neu * 100);   
  zahnrad_v_temp = (int)(zahnrad_geschw_neu * 1000);              
  zahnrad_a_temp = (int)(zahnrad_beschl * 1000);                 

 ... Sending to Code
}

I don't understand your ISR - is it reading a quadrature encoder? If so the logic looks odd.

You have a lot of libraries, this could be hard to track down.

Can you explain what you do in the ISR?

That code could be optimized heavily - How many times are you calling digitalRead(zahnrad_ch_1) for example?

Or in this if statement

if (digitalRead(zahnrad_ch_1) != zahnrad_ch_1_sw [color=red]&&[/color] [color=blue]digitalRead(zahnrad_ch_1) == LOW[/color]) {
    if ([color=blue]digitalRead(zahnrad_ch_1) == LOW[/color] && digitalRead(zahnrad_ch_2) == LOW)
    {

Why do you test again if zahnrad_ch_1 is LOW in the second if? If you got there you know it's LOW already.

I would suggest using PORT register to read the status of the pins once and if you layout the pins in the right way so that they are all in the same PORT then all could be read in one go then using bitmasking to access individual pin values. Also when all the pins are defined as bits in the same byte, testing a && condition is just masking the 2 bits and comparing to a constant byte value - that's fast. (For example if your PORT is B00001000 and you want to test is bit 0 is HIGH and bit 3 is LOW, you mask (bitwise & operation) with B00001001 and compare against 1 if you have any other value then your condition was not met)

I've not done it but I guess you would see at the very least a 10 fold improvement in the performance of your ISR - probably can gain x50 if done right.

Then of course there is the question (did not look at your code) of what the ISR really do versus the slow ADC (spec says you should able to get 2 conversions with that chip faster BTW) - if the 2 processes are interconnected in one way, there is no need to handle something that runs a thousand times faster than the other one... Would need to understand what you actually do to offer comment on this.

@MarkT:

Yes you are correct. I'm reading 2 quadrature encoder. The first one "spindel", I trigger on, comes actually more often so that I'm able to handle this without a second interrupt.

@J-M-L:

That really is a good point to read the Ports directly ...
I will try this today, and give you a response.

...Yes I know the code has a lot of space for optimization.
If you have any further ideas, I would like to hear them :slight_smile:

can you comment on what the quadrature encoders do versus the things you capture with the ADS1115? how are both process related (if they are)?

Both encoders are fluid sensors that are connected in row ( so they should measure the same amount of volume ).
they do have a different measureresolutionas the "zahnrad" measures 1/16 ml and the "spindel" does about 0.58/10

OK

you should declare spindel_Ink and zahnrad_Ink as volatile and make a local copy of those in the Sensor_Calc_and_Send() function before handling any computation involving their values

void Sensor_Calc_and_Send()
{
    long spindel_Ink_local, zahnrad_Ink_local;

    noInterrupts(); // enter critical section
       spindel_Ink_local = spindel_Ink;
       zahnrad_Ink_local = zahnrad_Ink;
    interrupts(); // exit critical section
    // then do all the computation with spindel_Ink_local and zahnrad_Ink_local
...
}

That's because they might change under you through interrupts as you do the computation. and because the platform will not handle those operation atomically, you might end up having some corruption in your maths if LSB and MSB are changed at the wrong time.

That is a good point!
I did rework my code after your advice, but having still small problems which I'm currently working on.
For example: The Sensorincrements deliver some strange signals which kills my calculation...
Not sure why but I try to figure out.

Another point:

I try to call the function "Sensor_Calc_and_Send()" every 4ms. for that I think a timebased Interrupt is necessary.

What do you think about implementing one more Interrupt to this program? Anything I should have a special look at?

Can you post your code?

Actually I tried to do this pretty simple and with the - in the "deuTimer.h" implemented Function.

void setup()
{
   ...
  Timer3.attachInterrupt(Sensor_Calc_and_Send);
  Timer3.start(4000); // Aufruf alle 4 ms
  ...
}

void loop()
{
   // call of Sensor_Calc_and_Send was removed
}

Actually I got some news from my experience I did yesterday:

  • A Serial.print(); or the Serial.println(); seems to reset my code to initial values
  • Setting up local variables in the Function Sensor_Calc_and_Send() did also kill the Arduino

Do you have any Idea why this happens? Or what I can do against this?

... Seems like the new Interrupt is crashing into my AD-convertion...
It reduces my Convertiontime from 6-8ms to the 4 ms that the Timerinterrupt has.

Well I'm thinking about 2 solutions and would like to hear your opinion.

  1. block and release the Interupts during the convertion
    -> I'm not sure how this will influence my Interruptcounter
  2. Maybe a Taskscheduler could help... but I'm not really experienced in that on...

... ok got smth. new:

The Serial.println() is not disturbing .... it is just the Serial.print() that makes these Problems.

Second I did try to implement the "nointerrupt()" and "interrupt()" ... well that can't be a good solution just because this one does higher my CAN_Message Latency a lot. it goes from about 4ms to approx. 136ms

I'd like to give the Scheduler a try.
The library i would use is the "Scheduler.h" and try to figure out if it is really usefull one.

Actually I'm thinking if the "yield()" would work for that, cause - to me - it seems like that after an interrupt the program starts at the next command...
If now to Command needs its time to work on, an interrupt could influence the convertion a lot...

Can someone pls help me?

Now that I removed all the prints to debug the code, I was able to higher the Samplingrate up to 860 back again, that it was before.

Now I nearly achieved my goal of 4 ms for 2 16 Bit AD-Samples and the to interrupts of the fluidsensors.

Seems like the range of 4.0 to 4.4ms is common, so that I can be happy with the results.

Also I checked, how it would influence the latency if i read one more AD-channel.
I was able to go down to 2 ms per channel which is just about 0.8ms higher than the TI-specs.

If there are any further questions, feel free to ask :slight_smile: