Arduino fast ADC sampling - какое управление пакетами лучше всего?

adc

Если кто-то хочет делать быстрые всплески свободно работающих преобразований АЦП, следует ли сделать паузу и перезапустить с помощью:

Бит включения АЦП: 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 на заказ, но сейчас ничего доступного для тестирования нет.

, 👍0

Обсуждение

Использование ADIE может привести к ошибочным показаниям, так как вы можете получить прерывание для преобразования рекламы, которое началось до того, как вы включили прерывания. Использование ADEN приведет к тому, что первое преобразование займет больше времени, чего вы, возможно, не захотите. Я бы установил/очистил бит ADATE. В качестве альтернативы, если время не так критично, вы можете сохранить АЦП включенным и установить значение "sample" обратно на ноль, когда захотите начать запись нового набора измерений., @Gerben

Спасибо. Я не подумала об АДАТЕ. Я думаю, что мне может понадобиться сделать больше, чем один бит, один, чтобы остановить следующее прерывание, а другие, чтобы очистить статус при первом захвате следующего цикла. И лучше защищайте образцы в ISR., @Dave X

@Gerben - Да, позволить АЦП работать автоматически и переключать запись сэмплов казалось самым чистым., @Dave X


1 ответ


Лучший ответ:

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