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.
3 Answers 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()
insetup()
: 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 anint
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.
-
Thank you for the suggestions. A few side notes: what do you mean by ``` process_samples(gauge0[i], gauge1[i]);```Dema Govalla– Dema Govalla2021年02月25日 03:31:00 +00:00Commented Feb 25, 2021 at 3:31
-
@DemaGovalla: I mean "do whatever you want with the samples". See amended answer.Edgar Bonet– Edgar Bonet2021年02月25日 07:54:55 +00:00Commented Feb 25, 2021 at 7:54
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.
-
2You would likely need to read
_filled
when processing the data, so it too should bevolatile
.Edgar Bonet– Edgar Bonet2021年02月23日 11:06:57 +00:00Commented Feb 23, 2021 at 11:06 -
@EdgarBonet Good point, I updated my answer.Michel Keijzers– Michel Keijzers2021年02月23日 11:12:34 +00:00Commented Feb 23, 2021 at 11:12
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.
Serial.print()
in the interrupt routine is introducing a significant performance penalty, especially since you've set the communication speed to only 9600 baud.