I am using Arduino UNO together with a SPI ADC (LTC1859) and SRAM (23CL1024) to record a series of data points at approx 2 kHz sampling rate (let's say a few thousand data points).
I have some questions about how to ensure the data points are sampled at uniform time intervals, down to the precision of a clock cycle.
What are the limitations of Arduino macros in respect to this? Can I replace SPI.transfer() with some lower-level code that will make an improvement?
Even if my code is precise, is the relatively low CPU speed of 16 MHz something to worry about for data sampling rates up to a few kHz?
The time-critical part of my code is as follows, and I'd appreciate suggestions:
**RE-EDIT. As has been requested, the ADC is LTC1859 and triggers on the slave select falling edge active high of input on the CONVST pin, which is the same time that I make ADC SS low (first line inside the for loop).
Digital outputs:
CONVST = Port B, bit 2
SS ADC = Port B, bit 1
SS SRAM = Port B, bit 0
for(unsigned int i=0;i<DATA_LENGTH;i++){
PORTB=B11000101; // ADC Slave select
__builtin_avr_delay_cycles(112); // ADC conversion time
DATA_MSB=SPI.transfer(ADCbyte1); // Get 16-bit ADC value as 2 bytes
DATA_LSB=SPI.transfer(ADCbyte2);
PORTB=B11000010; // SRAM Slave select
SPI.transfer(WRITE); // Write data value to SRAM as 2 bytes
SPI.transfer((uint8_t)(DATA_ADDRESS >> 16)&0xFF);
SPI.transfer((uint8_t)(DATA_ADDRESS >> 8)&0xFF);
SPI.transfer((uint8_t)(DATA_ADDRESS));
SPI.transfer(DATA_MSB);
SPI.transfer(DATA_LSB);
PORTB=B11000011; // Deselect both ADC and SRAM slaves
DATA_ADDRESS++;
DATA_ADDRESS++;
__builtin_avr_delay_cycles(7600); // Delay makes 1/sampling rate. 7600 = 2 kHz
} // Repeat for number of data points
**EDIT. Also, I have done some further tests and replaced occurrences of SPI.transfer(data);
with SPDR = data;
followed by waiting a constant number of clock cycles to ensure the data clears. I checked out the timing accuracy by measuring a sine wave voltage input and analyzing the FFT of the recorded data. Overall timing accuracy was improved but I do not see evidence that this part of code limits how uniformly the data are sampled. I have not tried Nick Gammon's suggestion yet to use timer interrupts.
Below is an example of the real-world data that I am recording. The ADC is digitizing data at 16 bit, 2 KHz from a magnetometer with around 10-15 T/Hz1/2 sensitivity, then passing to a PC running Qt where I plot the time-domain data and frequency-domain data after FFT.
The signal is laboratory magnetic background and contains harmonics of the 50 Hz line frequency from mains cables etc. The point of showing the data is that I am not stuck getting the data acquisition to work. I want to improve the data acquisition code as far as possible within the limits of Arduino.
SOLUTION: I modified Edgar Bonet's code in the accepted answer to use timer1 for PWM control of ADC trigger (CONVST, OC1B = Arduino pin 10). This worked successfully.
Learning points for me were:
(1) much greater accuracy in ADC sampling rate. The resonator frequency is accurate enough for my purposes at kHz sampling that I don't need fiddly calibrations of for
loop duration;
(2) much better way of programming a DAQ. Using the state machine concept, I can run other processes in parallel with the DAQ without penalties in timing accuracy.
void loop(){
switch (statemachine){
// other cases, including reading serial data from PC
case 2: // Initialize data acquisition
cli();
data_address=0;
data_counter=0;
statemachine=3;
TCCR1A=0;
TCCR1B=0;
OCR1A=124; // 16 MHz / (64 * 125) = 2000 Hz
OCR1B=1;
TCCR1A = _BV(COM1B1) // Clear pin OC1B on compare match, set at BOTTOM, noninverting
| _BV(WGM10) // fast PWM mode 15, TOP = OCR1A
| _BV(WGM11) ; // fast PWM mode 15.
TCCR1B = _BV(WGM12) // fast PWM mode 15
| _BV(WGM13) // fast PWM mode 15
| _BV(CS11) // Set timer prescaler to 64.
| _BV(CS10) ; // Set timer prescaler to 64.
TCNT1=0x03;
TIFR1 |= _BV(OCF1A); // Clear match flag
break;
case 3: // Acquire data and store.
loop_until_bit_is_set(TIFR1,OCF1A);
TIFR1 |= _BV(OCF1A); // Clear match flag
PORTB=B11000101; // ADC SS
__builtin_avr_delay_cycles(160); // 10 us
msb=SPI.transfer(ADCbyte1);
lsb=SPI.transfer(ADCbyte2);
PORTB=B11000010; // SRAM SS
SPI.transfer(WRITE);
SPI.transfer((uint8_t)(data_address >> 16) & 0xFF);
SPI.transfer((uint8_t)(data_address >> 8) & 0xFF);
SPI.transfer((uint8_t)(data_address));
SPI.transfer(msb);
SPI.transfer(lsb);
PORTB=B11000011;
data_address++;
data_address++;
data_counter++;
if(data_counter==data_length){statemachine=4;} // Data capture complete
break;
// other cases, including data transfer to PC
}
}
for()
loop is the most likely source of error? I have considered hardware timing but thought that eachSPI.transfer()
waits for an interrupt to conclude the transaction. If I did do hardware timing, what timer of the AVR is best to use? Note that I care most about uniform timing and not absolute accuracy.