2

My question is: how can I use an ISR and store the data I get from my analog inputs into an array for easier data analysis.

More specifically, I want to collect my data using an ISR function (internal timer interrupt), then store the collected data into an array. This way I can analyze the data more easily.

I have posted the code below. I have posted code showing how I implemented my ISR without storing the data into an array. I was wondering I anyone could help me out with this (how to store that data into an array). Thank you.

const int A0_pin = PC0;
const int A1_pin = PC1;

const uint16_t t1_load = 0;
const uint16_t t1_comp = 3173;

float count = 0;
float num = 0;

void setup() {
 
  //Set A0 and A1 pins as inputs
  DDRC &= ~(1 << A0_pin);
  DDRC &= ~(1 << A1_pin);

  //Reset Timer1 Control Reg A
  TCCR1A = 0;

  //Set CTC mode
  TCCR1B &= ~(1 << WGM13);
  TCCR1B |= (1 << WGM12);

  //Set to prescaler of 256
  TCCR1B |= (1 << CS12);
  TCCR1B &= ~(1 << CS11);
  TCCR1B &= ~(1 << CS10);

  //Reset Timer1 and set compare value
  TCNT1 = t1_load;
  OCR1A = t1_comp;

  //Enable Timer1 compare interrupt
  TIMSK1 = (1 << OCIE1A);

  //Enable global interrupt
  sei();
  
  Serial.begin(9600);
  Serial.println("CLEARDATA"); //clears up any data left from previous projects
  Serial.println("LABEL,TIME,TIMER,AVG,GAUGE0, GUAGE1"); //always write LABEL, so excel knows the next things will be the names of the columns 
  Serial.println("RESETTIMER"); //resets timer to 0
}

void loop() {
}

ISR(TIMER1_COMPA_vect) {
  TCNT1 = t1_load;
  float Gauge0 = analogRead(A0_pin); 
  float Gauge1 = analogRead(A1_pin); //44
        
  //conversion of read data, to voltage range (0-100 percentVolts)
  float gauge0 = Gauge0 * (100.0 / 1023.0);
  float gauge1 = Gauge1 * (100.0 / 1023.0);
  float add = gauge0 + gauge1;
  float AVG = ((gauge0 + gauge1) / 2); 

  Serial.print("DATA,TIME,TIMER,"); 
  Serial.print(add); 
  Serial.print(",");
  Serial.print(gauge0);
  Serial.print(",");
  Serial.println(gauge1);
}

In the code, I want to store the gauge1 and the gauge0 values into an array essentially.

1
  • 2
    How many interrupts/second do you expect? Using Serial.print() in the interrupt routine is introducing a significant performance penalty, especially since you've set the communication speed to only 9600 baud. – PMF Feb 23 at 10:20
3

Storing the data into arrays is not an end in itself. The question is: what do you want to do with these arrays? The way to proceed depends on that purpose.

If you want the data acquisition (analogRead()) and the processing (computation and printing) to proceed in parallel, then you will need the arrays for buffering the data between those two processes. As mentioned by Michel Keijzers in his answer, you will have to research the concept of ring buffer.

There is a ring buffer implementation in the Arduino core you can use as inspiration: when reading data from the Serial port, the incoming bytes are received by an ISR and stored in a ring buffer. The Serial.read() method can then be used to retrieve these bytes and process them outside the interrupt context.

Another option is to alternate between an acquisition phase and a processing phase. This can be an interesting approach if the acquisition rate is too fast for the processing to be performed in real time. In this case, you do not need a ring buffer. Instead, you have the ISR fill the arrays and, when they are full, the main program can take on and start the processing. For coordinating these jobs, you just need a boolean to keep track of whether you are performing the acquisition phase or the processing phase. Note that, since accessing a boolean is an atomic operation, you do not need to disable interrupts for this.

Here is an example implementation of this strategy. The variable sampling_done is used to coordinate the two processes:

const size_t sample_count = 256;

volatile int gauge0[sample_count];
volatile int gauge1[sample_count];
volatile bool sampling_done = false;

ISR(TIMER1_COMPA_vect) {
    if (sampling_done) return;  // nothing to do

    static size_t sample_idx = 0;
    gauge0[sample_idx] = analogRead(A0);
    gauge1[sample_idx] = analogRead(A1);
    if (++sample_idx >= sample_count) {
        sample_idx = 0;  // prepare for next round
        sampling_done = true;
    }
}

void process_samples(int Gauge0, int Gauge1) {
    // Whatever needs to be done with the samples:
    // computation, printing...
}

void loop() {
    if (sampling_done) {
        for (size_t i = 0; i < sample_count; i++) {
            process_samples(gauge0[i], gauge1[i]);
        }
        sampling_done = false;  // start next sampling round
    }
}

Now some random remarks:

  • There is no need to sei() in setup(): interrupts have already been enabled by the Arduino core initialization.

  • Do not reset TCNT1 within the ISR: this is handled automatically by the CTC mode. If you do this in software, you risk missing timer ticks.

  • Do not store the readings in float variables: they take twice the memory of an int and all operations on floats are incredibly slow compared to the equivalent operations on ints.

  • The scale factor for converting the readings to percent scale is 100.0/1024: the maximum possible analog reading (1023) is one ADC step below the reference voltage: see the datasheet.

  • You should remove unused variables as soon as they become unused (count, num, AVG). Once you do that you should notice you do not need floats at all.

2
  • Thank you for the suggestions. A few side notes: what do you mean by ``` process_samples(gauge0[i], gauge1[i]);``` – Dema Govalla Feb 25 at 3:31
  • @DemaGovalla: I mean “do whatever you want with the samples”. See amended answer. – Edgar Bonet Feb 25 at 7:54
2

My DaquinOscope https://www.daqarta.com/dw_rroo.htm uses the open-source DaqPort sketch https://www.daqarta.com/dw_rraa.htm to sample in batches, then transfer the data to the host in one high-speed blast. It takes 1024 total samples, which in your case of 2 channels would be 512 per channel. It stores only the raw data, unscaled... the host is the place for that. The host processes and displays the results in real time. Since this uses a UNO with only 2K RAM, tricky array storage was required to fit in 1024 samples. In 8-bit mode this is just a 1024-byte array, but for 10-bit it uses a supplemental array of 256 bytes. On each sample the low byte is stored in the 1024-byte array, and the upper 2 bits are shifted into a 4-sample byte of the 256-byte array. Feel free to download and use Daqarta for this... no charge for Arduino use. You may want to do this just to work out other issues even if you plan to later use your own host software. Or you can use Daqarta macros to do whatever you ultimately want.

1

You can create two arrays, e.g.:

#define MAX_LENGTH 20

volatile float _gauge0[MAX_LENGTH];
volatile float _gauge1[MAX_LENGTH];

volatile int _filled = 0;

The _filled value shows that _gauge0 is filled from 0 upto (excluding) _filled.

So when adding two values you use:

_gauge0[_filled] = some value;
_gauge1[_filled] = some_other_value;
_filled++;

After processing, you reset _filled to 0 to refill both arrays again.

Because the Arduino has a limited amount of SRAM it's best to use a fixed array size (20 in this case). You have to make it volatile as it's used from within the ISR.

I'm not sure what you want to do with the data. If you want to gather like 20 (or a fixed amount of values), you can use the above.

If you need a continuous amount of memory and get the last x results, you can better use a so-called ring buffer (search for this, there are many articles about it).

Note that because of the limited amount of SRAM, it might be best not to store calculated values (like average and sum), unless you need the highest performance. This is a tradeoff which I cannot make as I don't know your requirements.

While processing, you use noInterrupts() before and interrupts after the processing of the data, otherwise interrupts will continue during processing. Make sure the handling of the processing takes as less time as possible as you might miss interrupts. Preferably only do the calculations and switch on interrupts before sending results via serial for example.

2
  • 2
    You would likely need to read _filled when processing the data, so it too should be volatile. – Edgar Bonet Feb 23 at 11:06
  • @EdgarBonet Good point, I updated my answer. – Michel Keijzers Feb 23 at 11:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.