Arduino fast ADC sampling - какое управление пакетами лучше всего?
Если кто-то хочет делать быстрые всплески свободно работающих преобразований АЦП, следует ли сделать паузу и перезапустить с помощью:
Бит включения АЦП: bitClear(ADCSRA,ADEN)
и BitSet(ADCSRA,ADEN)
,
Прерывание enable bit bitClear(ADCSRA,ADIE)
& BitSet(ADCSRA, ADIE)
,
Начальный бит преобразования bitClear(ADCSRA,ADSC)
& BitSet(ADCSRA,ADSC)
,
Автоматический триггер включает бит bitClear(ADCSRA,ADATE)
и BitSet(ADCSRA, ADATE)
,
или вам нужна какая - то более сложная комбинация битов?
Вот код, в котором я пытаюсь вызвать импульс и серию выборок, а затем сообщить об этом хост-компьютеру:
const int numSamples = 20;
const byte startPin = PD0; // start signal (pullup)
const byte pulsePin = PD6; // Normally low
const int pulse_us = 10 ; // output pulse length
int16_t sampleData[numSamples];
volatile int sample;
unsigned long t0, t;
// state machine:
typedef enum { STATE_NONE, // idle
STATE_SAMPLING, // record ADC
STATE_DONE, // report data
} states;
// current state-machine state
volatile states state = STATE_NONE;
void setup()
{
Serial.begin(115200);
pinMode(startPin,INPUT_PULLUP);
pinMode(pulsePin,OUTPUT);
// Clear ADC
ADCSRA = 0;
ADCSRB = 0;
ADMUX = 0;
ADMUX |= (0b0000 << MUX0); // Choose ADC channel 0
ADMUX |= (0b01 << REFS0); // Choose VCC reference voltage
bitSet(ADMUX,ADLAR); // ADC left align ADC result into ADCH register
// sampling rate is [ADC clock] / [prescaler] / [conversion clock cycles]
// for Arduino Uno ADC clock is 16 MHz and a conversion takes 13 clock cycles
//ADCSRA |= (0b101 << ADPS0); // /32 : 16M/32/13=38461Hz 26us
//ADCSRA |= (0b100 << ADPS0); // /16 : 16M/16/13=76923Hz 13us 10 bit precision
ADCSRA |= (0b011 << ADPS0); // /8 : 16M/8/13=153.8KHz 6.5us low precision
//ADCSRA |= (0b010 << ADPS0); // /4 : 16M/4/13=307.6KHz 3.2us lower precision
//ADCSRA |= (0b001 << ADPS0); // /2 : 16M/2/13=615.4Hz 1.6us bad precision
ADCSRB |= (0b000 << ADTS0); // Choose free running trigger mode
bitSet(ADCSRA,ADATE); // enable auto trigger mode per ADSCRB:ADTSx
bitSet(ADCSRA,ADIE); // enable interrupts when measurement complete
bitSet(ADCSRA,ADEN); // enable ADC
bitSet(ADCSRA,ADSC); // start ADC measurements
}
ISR(ADC_vect) // Record samples
{
sampleData[sample++] = ADC; // read 10bit value from ADC
if (sample >= numSamples) {
bitClear(ADCSRA,ADEN); // stop recording
state = STATE_DONE;
}
}
void report(){
t = micros()-t0; // calculate elapsed time
Serial.print("Sampling frequency: ");
Serial.print((float)1000000/t);
Serial.println(" KHz");
for (int i = 0; i < numSamples; i++){
Serial.print(i);
Serial.print(' ');
Serial.print(sampleData[i]);
Serial.println();
}
}
void startSampling(){
sample = 0;
t0 = micros();
digitalWrite(pulsePin,HIGH);
bitSet(ADCSRA,ADEN); // start ADC
delayMicroseconds(pulse_us);
digitalWrite(pulsePin,LOW);
}
void loop()
{
switch (state){
case STATE_NONE:
if(digitalRead(startPin)==LOW){
startSampling();
state = STATE_SAMPLING;
}
break;
case STATE_SAMPLING:
break;
case STATE_DONE:
report();
state = STATE_NONE;
break;
default:
;;
}
}
Переключение ADEN
было моей первой мыслью, но ADIE
кажется, что это будет быстрее, а ADSC
кажется самым чистым, если он работает для модуляции свободного режима АЦП. Что было бы самым быстрым и чистым?
У меня есть Nano на заказ, но сейчас ничего доступного для тестирования нет.
@Dave X, 👍0
Обсуждение1 ответ
Лучший ответ:
После некоторой неуклюжести важно несколько моментов:
ADCSRA &= ~(bit(ADATE) | bit (ADSC) | bit (ADIE)); // Disable
ADCSRA |= bit(ADATE) | bit (ADSC) | bit (ADIE); // Enable
Бит включения прерывания bitClear(ADCSRA,ADIE)
& BitSet(ADCSRA,ADIE)
остановит текущее измерение АЦП от запуска события прерывания.
Автоматический триггер enable bit bitClear(ADCSRA,ADATE)
& BitSet(ADCSRA, ADATE)
запускает новое преобразование, как только заканчивается текущее.
Начальный бит преобразования bitClear(ADCSRA,ADSC)
и BitSet(ADCSRA,ADSC)
важен для начала первого преобразования.
ADC enable bit bitClear(ADCSRA,ADEN)
& BitSet(ADCSRA, ADEN)
включает и выключает подсистему АЦП, но при запуске возникает небольшая задержка.
В конце концов я оставил включенным свободно работающий автоматический АЦП и включил и выключил запись данных, используя ADCSRA&bit(ADIE)
в качестве переменной состояния "BURST". Я думаю, что это достаточно хорошо для общего тестирования импульсного отклика:
//: ArduinoPulseADCSample.ino -- Импульсный вывод и выборочный отклик на АЦП
// Для Arduino Uno/Nano/mega
// https://github.com/drf5n/foxyPulseInduction_Discrimination/tree/discrimination/ArduinoPulseADCSample
// Адаптировано из http://www.gammon.com.au/forum/?id=11488&reply=5#reply5
// и https://github.com/pedvide/ADC/tree/master/examples/analogContinuousRead
//
// Этот код испускает цифровой импульс на A1 и собирает выборку высокоскоростного АЦП от A0
// Он настраивает свободно работающий АЦП для быстрой записи набора данных АЦП в
// реакция на импульс. Размер импульса, контакты, количество выборок,
// скорость выборки и т. Д. - все это настраивается.
// Обработчик прерываний АЦП управляет длиной импульса и заполнением буфера дискретизации
//
// Последовательные команды:
//
// * Напишите v и нажмите enter на последовательной консоли, чтобы получить значение
// * Напишите c и нажмите enter на последовательной консоли, чтобы проверить состояние преобразования,
/ / * Напишите s, чтобы остановить преобразования, вы можете перезапустить его, написав r.
// * Напишите r, чтобы перезапустить преобразования.
// * Запись p для испускания импульса и записи пакета значений
// * Запись d для просмотра данных, записанных во время пакета (совместимо с SerialPlotter)
// * Напишите m, чтобы увидеть метаданные о размере выборки и интервале
//
// Loopback test с перемычкой или RC - сетью между A1 и A2.
//
// Конфигурационные файлы:
const byte adcPin = 0; // A0 -- Pin для считывания данных АЦП с
const byte pulsePin = A1; // Рядом с A0 -- pin для импульса
const byte oneshot_pin = 12; // OC1B pin на Mega, управляемый Timer1
const int pulse_us = 50; // длительность импульса
const int numSamples = 20; // набор образцов
// Измените скорость дискретизации с помощью конфигурации prescaler в setup()
volatile uint16_t samples[numSamples];
volatile int sample = 0; // position in sample state variable
#define BURST (ADCSRA & bit(ADIE)) /* Sampling burst state variable */
int adcReading;
unsigned long sampleEnd;
unsigned long pulseStart, pulseEnd;
// ########## Timer1 One-shot functions
// The one shot pulses are output on Digital pin OC1B (Arduino Uno D10, Mega D12, Nano D3)
// Modified by Dave Forrest 2020-01-11 for Timer 1
// Based on Josh Levine's Nov 23, 2015 work at https://github.com/bigjosh/TimerShot/blob/master/TimerShot.ino
// Setup the one-shot pulse generator and initialize with a pulse width that is (cycles) clock counts long
#define OSP_SET_WIDTH(cycles) (OCR1B = 0xffff-(cycles-1))
void osp_setup(uint16_t cycles) { // 1 idles, 0xffff never matches, 2-0xfffe makes pulses
const byte prescaler = 0b010; // Choose /8 for 0.5us resolution
// 0b001: /1, 16MHz, 62.5ns resolution, 4ms max
// 0b010: /8, 2MHz, 0.5us resolution, 32ms max
// 0b011: /64, 250kHz, 4us resolution, 263ms max
// 0b100: /256, 62.5kHz 16us resolution, 1.048s max
// 0b101: /1024, 15625Hz, 64us resolution, 4.194176s max
TCCR1B = 0; // Halt counter by setting clock select bits to 0 (No clock source).
// This keeps anyhting from happening while we get set up
TCNT1 = 0x00; // Start counting at bottom.
OCR1A = 0; // Set TOP to 0. This effectively keeps us from counting because the counter just keeps resetting back to 0.
// We break out of this by manually setting the TCNT higher than 0, in which case it will count all the way up to MAX and then overflow back to 0 and get locked up again.
OSP_SET_WIDTH(cycles); // This also makes new OCR values get loaded frm the buffer on every clock cycle.
TCCR1A = 0b11 << COM1B0 | 0b11 << WGM10; // OC1B=Set on Match, clear on BOTTOM. Mode 15 Fast PWM.
TCCR1B = (0b11 << WGM12) | (prescaler << CS10); // Start counting now. WGM 15=0b1111 to select Fast PWM mode
// Setup the OC1B pin for one-shot output:
//DDRB |= _BV(2); // Uno Set pin to output (Note that OC1B = GPIO port PB2 = Arduino Uno Digital Pin 10)
DDRB |= _BV(6); // Mega Set pin to output (Note that OC1B = GPIO port PB6 = Arduino Mega Digital Pin 12)
//DDRD |= _BV(3); // Nano Set pin to output (Note that OC1B = GPIO port PD3 = Arduino Nano Digital Pin 3)
}
// Setup the one-shot pulse generator to idle:
void osp_setup() {
osp_setup(1); // 1 puts it in idle mode, 0xffff never triggers, 2-0xfffe produces pulses.
}
// Macro to Fire a one-shot pulse. Use the most recently set width.
#define OSP_FIRE() (TCNT1 = OCR1B - 1)
// Macro to Test there is currently a pulse still in progress
#define OSP_INPROGRESS() (TCNT1>0)
// Macro to Fire a one-shot pulse with the specififed width.
// Order of operations in calculating m must avoid overflow of the unint16_t.
// TCNT2 starts one count lower than the match value because the chip will block any compare on the cycle after setting a TCNT.
#define OSP_SET_AND_FIRE(cycles) {uint16_t m=0xffff-(cycles-1); noInterrupts();OCR1B=m;TCNT1 =m-1;interrupts();}
// ########## End of Timer1 One-shot functions
ISR(TIMER1_OVF_vect) { //When overflow...
// OCRA1=ICR1; // Constant off in WGM 14, COM1A1:0==0b11
TCCR1B &= ~(0b111 << CS10) ; //turn off clock
}
//######### ADC Bit-bang for free-running capture of A0
void adc_setup_freerunning(const byte adcPin){
// set the analog reference (high two bits of ADMUX) and select the
// channel (low 4 bits). this also sets ADLAR (left-adjust result)
// to 0 (the default).
ADMUX = bit (REFS0) | (adcPin & 0x07);
// Set the ADC ADPSx prescaler flags to control sampling speed/accuracy
ADCSRA &= ~(bit (ADPS0) | bit (ADPS1) | bit (ADPS2)); // clear prescaler bits
//ADCSRA |= 0b001 << ADPS0; // 2 5 bit,
//ADCSRA |= 0b010 << ADPS0; // 4 6 bit, 5.36us
//ADCSRA |= 0b011 << ADPS0; // 8 9 bit, 6.51us
ADCSRA |= 0b100 << ADPS0; // 16 10 bit, 13us
//ADCSRA |= 0b101 << ADPS0; // 32 10 bit, 26us
//ADCSRA |= 0b110 << ADPS0; // 64 10 bit, 52us
//ADCSRA |= 0b111 << ADPS0; // 128 10 bit, 104us
//enable automatic conversions, start them and interrupt on finish
//ADCSRA |= bit(ADATE) | bit (ADSC) | bit (ADIE);
ADCSRA |= bit(ADATE) | bit (ADSC) ;
}
void setup ()
{
Serial.begin (115200);
//pinMode(pulsePin, OUTPUT);
osp_setup(); // Config Mega Digital 12 for oneshot output
adc_setup_freerunning(adcPin);
} // end of setup
// ADC complete ISR
ISR (ADC_vect) // Store ADC burst values
{
if (sample >= numSamples) // watch off-by-one
{
ADCSRA &= ~bit(ADIE); // end of sampling burst so stop interrupting
return;
}
// Handle samples
samples[sample++ ] = ADC;
//if ( sample == 0){
// OSP_SET_AND_FIRE(pulse_us * 2); //
//}
} // end of ADC_vect
void loop ()
{
char c;
char buff[80];
unsigned long sampleDuration;
unsigned long pulseDuration;
// if last reading finished, process it
// if we aren't taking a reading, start a new one
if (Serial.available()) {
c = Serial.read();
switch (c) {
case 'c': // Converting?
Serial.print("ADCSRA: ");
Serial.println(ADCSRA, BIN);
break;
case 's': // stop conversions
ADCSRA &= ~(bit (ADSC) | bit (ADIE));
break;
case 'r': // restart conversions
ADCSRA |= bit (ADSC) | bit (ADIE);
break;
case 'v':
adcReading = ADC;
Serial.print(adcReading);
Serial.print(' ');
Serial.print((0.5 + adcReading) * 5.0 / 1024, 4);
Serial.println(" V");
break;
case 'm':
{
unsigned long sampleDuration = sampleEnd - pulseStart;
unsigned long pulseDuration = pulseEnd - pulseStart;
Serial.print("# Pulse: ");
Serial.print(pulseDuration);
Serial.print("us and ");
Serial.print(numSamples);
Serial.println(" samples.");
Serial.print("# Time: ");
Serial.print(sampleDuration);
Serial.print("us burst of ");
Serial.print(numSamples);
Serial.print(" samples at ");
Serial.print(1.0 * (unsigned long)(sampleEnd - pulseStart) / numSamples);
Serial.println("us/sample");
sprintf(buff, " sampleEnd, pulseEnd, pulseStart %02lu : %02lu : %02lu \n", sampleEnd, pulseEnd, pulseStart);
Serial.print(buff);
break;
}
case 'p': // start pulse
sample = 0; // reset sample SV
pulseStart = micros();
OSP_SET_AND_FIRE(pulse_us * 2); // pulse outside ISR
ADCSRA |= bit(ADIF) | bit(ADIE); // clear existing ADInterruptFlag and enable ADC Interrupts
while(BURST); // block until burst done
sampleEnd = micros(); // Record times
pulseEnd = pulseStart + pulse_us;
break;
case 'D':
case 'd': {// report Data
sampleDuration = sampleEnd - pulseStart;
for (int ii = 0 ; ii < numSamples ; ii++) {
if (c == 'D') {
Serial.print(ii * sampleDuration / numSamples);
Serial.print(' ');
}
Serial.println(samples[ii]);
}
}
break;
case ' ':
case '\n':
case '\r':
break;
case 'h':
case '?':
default:
{
Serial.println("\nArduinoPulseADCSample.in -- Pulse A1 and read a burst of samples from A0\n"
"based on serial commands");
Serial.println("# https://github.com/drf5n/foxyPulseInduction_Discrimination/tree/discrimination/ArduinoPulseADCSample\nCommands: [vcsrpdm] ?");
Serial.println("Commands:\n"
"p: Pulse -- Start a pulse cycle on A1 and record data on A0\n"
"d,D: Data -- Dump the data from the last pulse\n"
"m: Metadata -- Print the length of pulse, number of samples and rate\n"
"v: Voltage -- Conver voltage on A0\n"
"c: Converting? -- show ADCSSRA register\n"
"h?: Help -- Print this\n"
);
}
break;
;;
}
}
// do other stuff here
} // end of loop
Это вывод команд p,m,d с перемычкой, соединяющей вывод 12 с A0 с прескалером /16:
# Pulse: 50us and 20 samples.
# Time: 276us burst of 20 samples at 13.80us/sample
sampleEnd, pulseEnd, pulseStart 19270968 : 19270742 : 19270692
0
1020
1023
1023
1023
3
0
0
0
0
0
0
0
0
0
0
0
0
0
0
И снова с прескалером /8. 7.2 us/sample или 139Ksamples/sec для 8-9bits не плохо:
# Pulse: 50us and 20 samples.
# Time: 144us burst of 20 samples at 7.20us/sample
sampleEnd, pulseEnd, pulseStart 4229952 : 4229858 : 4229808
0
792
1020
1022
1022
1020
1022
1022
1022
305
3
0
0
0
0
0
0
0
0
0
ArduinoPulseADCSample.in -- Pulse A1 and read a burst of samples from A0
based on serial commands
# https://github.com/drf5n/foxyPulseInduction_Discrimination/tree/discrimination/ArduinoPulseADCSample
Commands: [vcsrpdm] ?
Commands:
p: Pulse -- Start a pulse cycle on A1 and record data on A0
d,D: Data -- Dump the data from the last pulse
m: Metadata -- Print the length of pulse, number of samples and rate
v: Voltage -- Conver voltage on A0
c: Converting? -- show ADCSRA register
h?: Help -- Print this
Вот трассировка импульсного затухания R-C на последовательном плоттере с резистором 22K через A0 и 12 и конденсатором 22uF от GND до A0.
Re “_I оставил включенным свободно работающий автоматический АЦП и включил и выключил запись данных._”: Я думаю, вы могли бы сделать это, переключив только "ADIE", не касаясь ADSC
. Возможно, снимите флаг прерывания при установке "ADIE", чтобы избежать прерывания от последнего преобразования предыдущего пакета., @Edgar Bonet
@EdgarBonet -- С помощью трюка ADIF=1
, ADCSRA |= bit(ADIF) | bit(ADIE)
очистит любое ожидающее прерывание и включит будущие прерывания. Звучит неплохо. Затем я мог бы использовать "ADCSRA & bit(ADIE)" вместо "burst" в качестве переменной состояния "sampling" и сохранить тест. Я настраивал вещи, чтобы добраться до порога 13us/sample, который я могу получить для скорости ADPS2:0=0b100 /16 1MKz, которая имеет полную точность 10bit, но я не урезал ISR достаточно для часов /8. Мне нравится импульсный контроль в ISR для синхронизации в моем случае использования, но он неаккуратен. Может быть, pulse on sample
vs micros() state var?, @Dave X
.. Управление длиной импульса происходит неаккуратно, потому что импульс начинается несинхронно с выборкой и заканчивается синхронно с выборкой . Мне нравится синхронизация конца с образцами для моего конкретного приложения, но было бы лучше настроить таймер как одноразовый и запустить его в ISR, когда sample == 0
, тогда вы могли бы получить более высокую длину импульса разрешения с синхронизацией начала и конца импульса с отбором проб. Возможно, он может достичь 6,5 us/sample с разрешением 8-9 бит., @Dave X
- Постоянный выход тактовой частоты Arduino
- Расширение аналоговых входов для Arduino
- ESP8266: system_adc_read_fast() всегда возвращает 1024
- Высокоскоростной внешний АЦП
- Измерение напряжения литий-ионного элемента, используемого для питания Arduino через повышающий модуль
- Шумный analogRead
- Расширенная настройка АЦП на Due (SAM3X8E) для повышения точности
- Arduino/ESP8266 нет данных SPI, поступающих от MCP3008
Использование ADIE может привести к ошибочным показаниям, так как вы можете получить прерывание для преобразования рекламы, которое началось до того, как вы включили прерывания. Использование ADEN приведет к тому, что первое преобразование займет больше времени, чего вы, возможно, не захотите. Я бы установил/очистил бит ADATE. В качестве альтернативы, если время не так критично, вы можете сохранить АЦП включенным и установить значение "sample" обратно на ноль, когда захотите начать запись нового набора измерений., @Gerben
Спасибо. Я не подумала об АДАТЕ. Я думаю, что мне может понадобиться сделать больше, чем один бит, один, чтобы остановить следующее прерывание, а другие, чтобы очистить статус при первом захвате следующего цикла. И лучше защищайте образцы в ISR., @Dave X
@Gerben - Да, позволить АЦП работать автоматически и переключать запись сэмплов казалось самым чистым., @Dave X