当前位置: 首页 > 知识库问答 >
问题:

由于定时器和中断驱动的多通道ADC采集,在Arduino上设置正确的ADC预分频器

严信瑞
2023-03-14

我正试图遵循、改编、理解(并稍微清理)关于Arduino到期版可用代码的变化:https://forum.arduino.cc/index.php?topic=589213.0 . 我不喜欢论坛的形式,因为事情最终埋得很深,所以在这里提问。不幸的是,这意味着问题之前有很多解释。如果你认为这是错误的张贴在这里,让我知道,我可以移动。

基本上,这个想法是使用基于计时器的触发来记录缓冲区中的几个ADC通道。有一点设置:

// sample rate in Hz
constexpr int sample_rate = 1000;

constexpr uint8_t channels[] = {7, 6, 5, 4, 3};
constexpr int nbr_channels = sizeof(channels);

然后将时间计数器0通道2设置为触发ADC转换的正确频率:

// use time counter 0 channel 2 to generate the ADC start of conversion signal
// i.e. this sets a rising edge with the right frequency for triggering ADC conversions corresponding to sample_rate
// for more information about the timers: https://github.com/ivanseidel/DueTimer/blob/master/TimerCounter.md
// NOTE: TIOA2 should not be available on any due pin https://github.com/ivanseidel/DueTimer/issues/11
void tc_setup() {
  PMC->PMC_PCER0 |= PMC_PCER0_PID29;                       // TC2 power ON : Timer Counter 0 channel 2 IS TC2
  TC0->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2   // clock 2 has frequency MCK/8, clk on rising edge
                              | TC_CMR_WAVE                // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC        // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR          // Clear TIOA2 on RA compare match
                              | TC_CMR_ACPC_SET;           // Set TIOA2 on RC compare match

  constexpr int ticks_per_sample = F_CPU / 8 / sample_rate; // F_CPU / 8 is the timer clock frequency, see MCK/8 setup
  constexpr int ticks_duty_cycle = ticks_per_sample / 2; // duty rate up vs down ticks over timer cycle; use 50%
  TC0->TC_CHANNEL[2].TC_RC = ticks_per_sample;
  TC0->TC_CHANNEL[2].TC_RA = ticks_duty_cycle;

  TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC2 counter and enable
}

最后,这可以用来触发ADC:

// start ADC conversion on rising edge on time counter 0 channel 2
// perform ADC conversion on several channels in a row one after the other
// report finished conversion using ADC interrupt
void adc_setup() {
  PMC->PMC_PCER1 |= PMC_PCER1_PID37;                     // ADC power on
  ADC->ADC_CR = ADC_CR_SWRST;                            // Reset ADC
  ADC->ADC_MR |=  ADC_MR_TRGEN_EN |                      // Hardware trigger select
                  ADC_MR_PRESCAL(1) |                    // the pre-scaler: as high as possible for better accuracy, while still fast enough to measure everything
                                                         // see: https://arduino.stackexchange.com/questions/12723/how-to-slow-adc-clock-speed-to-1mhz-on-arduino-due
                  ADC_MR_TRGSEL_ADC_TRIG3;               // Trigger by TIOA2 Rising edge

  ADC->ADC_IDR = ~(0ul);
  ADC->ADC_CHDR = ~(0ul);
  for (int i = 0; i < nbr_channels; i++)
  {
    ADC->ADC_CHER |= ADC_CHER_CH0 << channels[i];
  }
  ADC->ADC_IER |= ADC_IER_EOC0 << channels[nbr_channels - 1];
  ADC->ADC_PTCR |= ADC_PTCR_RXTDIS | ADC_PTCR_TXTDIS;    // Disable PDC DMA
  NVIC_EnableIRQ(ADC_IRQn);                              // Enable ADC interrupt
}

ADC输出可以在相应的ISR中捕获:

void ADC_Handler() {
for (size_t i = 0; i < nbr_channels; i++)
  {
      SOME_BUFFER[i] = static_cast<volatile uint16_t>( * (ADC->ADC_CDR + channels[i]) & 0x0FFFF ); // get the output
  }
}

我认为这是可以理解的,但我有一个问题:预定标器的设置。

>

如果我理解得很好,我们希望在给定先前约束的情况下将这种预缩放器值设置得尽可能高,以便ADC频率尽可能低,因为这提高了ADC转换质量

是这样吗?

问题是,我对如何设置预缩放器以及什么值对应于什么感到困惑,因为我在数据表中发现的内容与我阅读的其他在线响应不一致。

从数据表中https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-11057-32-bit-Cortex-M3-Microcontroller-SAM3X-SAM3A_Datasheet.pdf :“如果PRESCAL为0,则ADC时钟范围在MCK/2和MCK/512之间,如果PRESCAL设置为255(0xFF)。”。这与我在第1334页上的发现一致:“ADCClock=MCK/((PRECAL 1)*2)”。但第1318页写着转换率为1MHz。那么,这与到期日的MCK频率为84MHz有什么关系呢?84/2=48MHz,84/512=0.164MHz,高频值太高。

然后,为了增加混乱,我发现了这个问题:https://arduino.stackexchange.com/questions/12723/how-to-slow-adc-clock-speed-to-1mhz-on-arduino-due/21054#21054似乎也与1MHz上限相冲突。

知道我哪里误解了吗?(还有关于该程序一般工作的更多评论吗?)。

共有1个答案

秦宜修
2023-03-14

好的,所以我对代码做了一些测试,根据定时器频率和预分频器值来检查何时缺少一些转换。代码有点长,所以我把它贴在答案的末尾。基本上:

// pre-scalor analysis using 5 channels;
// quantities indicated are sampling frequency of the 5 channels
// i.e. necessary ADC sampling frequency is 5 x higher, and value
// of the prescaler ps
// --------------------
// 100kHz ps 1 ok
// 100kHz ps 2 ok
// 100kHz ps 3 fail
// 100kHz ps 255 fail
// 100kHz ps 256 ok
// this indicates: prescaler is 8 bits from 0 to 255, after this wraps up
// ADC frequency is max something like 1MHz in practice: 5 * 100 * 2 (may loose a bit
// due to other interrupts hitting ours?)
// --------------------
// 10kHz ps 38 ok
// 10kHz ps 39 fail
// 10 * 5 * 40 = 2000kHz: ADC is lower than 2MHz
// --------------------
// 1kHz ps 255 ok
// --------------------

我认为这表明:

>

  • 预定标器值是一个8位整数,介于0和255之间,最终为256

    我很难将结果与数据表中的公式匹配。我想这是因为有一些开销切换通道等(?)。例如:

    >

  • 结果与最高频率下的``ADC_freq=1MHz/(ps)一致,但我认为这是因为存在一些开销切换通道

    结果与"'ADC_freq=2MHz/(ps)在10 kHz时一致,并且在1kHz时,即使使用最高预定标器也没问题。

    我使用的代码如下,判断失败的标准是,代码报告5个通道的有效采样频率下降:

    // -------------------------------------------------------------------------------------------------
    // -------------------------------------------------------------------------------------------------
    // timer driven ADC convertion captured by interrupt on n adc_channels for Arduino Due
    //
    // this is for Arduino Due only!
    //
    // the interrupt based ADC measurement is adapted from:
    // https://forum.arduino.cc/index.php?topic=589213.0
    // i.e. adc_setup(), tc_setup(), ADC_handler() are inspired from the discussion there.
    //
    // written with VSCode + Platformio and Due board setup
    // -------------------------------------------------------------------------------------------------
    // -------------------------------------------------------------------------------------------------
    
    // make my linter happy
    #include "Arduino.h"
    
    //--------------------------------------------------------------------------------------------------
    //--------------------------------------------------------------------------------------------------
    
    // some vital ADC grabbing setup
    
    // sample rate in Hz, should be able to go up to several 10s ok kHz at least
    constexpr int adc_sample_rate = 1000;
    
    // size of the data buffers "in time"
    // i.e. how many consecutive measurements we buffer for each channel
    constexpr size_t adc_buffer_nbr_consec_meas = 5;
    
    // the adc_channels to read, in uC reference, NOT in Arduino Due pinout reference
    // for a mapping, see: https://components101.com/microcontrollers/arduino-due
    // i.e. A0 is AD7
    //      A1    AD6
    //      A2    AD5
    //      A3    AD4
    //      A4    AD3
    //      A5    AD2
    //      A6    AD1
    //      A7    AD0
    constexpr uint8_t adc_channels[] = {7, 6, 5, 4, 3};
    constexpr int nbr_adc_channels = sizeof(adc_channels);
    
    // the buffer containing the measurements for all adc_channels over several measurements in time
    volatile uint16_t adc_meas_buffer[adc_buffer_nbr_consec_meas][nbr_adc_channels];
    
    // flag when a full vector of conversions is available
    volatile bool adc_flag_conversion = false;
    
    // time index of the current measurement in the adc reads buffer
    volatile size_t crrt_adc_meas_buffer_idx = 0;
    
    //--------------------------------------------------------------------------------------------------
    //--------------------------------------------------------------------------------------------------
    
    // some non-vital printing config
    
    // a bit of time tracking, just to analyze how good performance
    unsigned long current_us = 0;
    unsigned long previous_us = 0;
    unsigned long delta_us = 0;
    float delta_us_as_s = 0;
    float delta_us_as_ms = 0;
    
    int nbr_readings_since_reduced_time_stats = 0;
    unsigned long current_reduced_time_stats_us = 0;
    unsigned long previous_reduced_time_stats_us = 0;
    float delta_reduced_time_stats_us_as_s = 0;
    float effective_logging_frequency = 0;
    
    // decide what to print on serial
    constexpr bool print_reduced_time_stats = true;
    constexpr bool print_time_stats = false;
    constexpr bool print_full_buffer = false;
    
    //--------------------------------------------------------------------------------------------------
    //--------------------------------------------------------------------------------------------------
    
    // low level functions for setting clock and ADC
    
    // start ADC conversion on rising edge on time counter 0 channel 2
    // perform ADC conversion on several adc_channels in a row one after the other
    // report finished conversion using ADC interrupt
    
    // tests about pre-scaler: formula should be: 
    // pre-scalor analysis using 5 channels;
    // quantities indicated are sampling frequency of the 5 channels
    // i.e. necessary ADC sampling frequency is 5 x higher, and value
    // of the prescaler ps
    // --------------------
    // 100kHz ps 1 ok
    // 100kHz ps 2 ok
    // 100kHz ps 3 fail
    // 100kHz ps 255 fail
    // 100kHz ps 256 ok
    // this indicates: prescaler is 8 bits from 0 to 255, after this wraps up
    // ADC frequency is max something like 1MHz in practice: 5 * 100 * 2 (may loose a bit
    // due to other interrupts hitting ours?)
    // --------------------
    // 10kHz ps 38 ok
    // 10kHz ps 39 fail
    // 10 * 5 * 40 = 2000kHz: ADC is lower than 2MHz
    // --------------------
    // 1kHz ps 255 ok
    // --------------------
    // CCL: use ps 2 at 100kHz with 5 channels,  20 at 10kHz, 200 at 1kHz
    void adc_setup()
    {
      PMC->PMC_PCER1 |= PMC_PCER1_PID37;      // ADC power on
      ADC->ADC_CR = ADC_CR_SWRST;             // Reset ADC
      ADC->ADC_MR |= ADC_MR_TRGEN_EN |        // Hardware trigger select
                     ADC_MR_PRESCAL(200) |    // the pre-scaler: as high as possible for better accuracy, while still fast enough to measure everything
                                              // see: https://arduino.stackexchange.com/questions/12723/how-to-slow-adc-clock-speed-to-1mhz-on-arduino-due
                                              // unclear, asked: https://stackoverflow.com/questions/64243073/setting-right-adc-prescaler-on-the-arduino-due-in-timer-and-interrupt-driven-mul
                     ADC_MR_TRGSEL_ADC_TRIG3; // Trigger by TIOA2 Rising edge
    
      ADC->ADC_IDR = ~(0ul);
      ADC->ADC_CHDR = ~(0ul);
      for (int i = 0; i < nbr_adc_channels; i++)
      {
        ADC->ADC_CHER |= ADC_CHER_CH0 << adc_channels[i];
      }
      ADC->ADC_IER |= ADC_IER_EOC0 << adc_channels[nbr_adc_channels - 1];
      ADC->ADC_PTCR |= ADC_PTCR_RXTDIS | ADC_PTCR_TXTDIS; // Disable PDC DMA
      NVIC_EnableIRQ(ADC_IRQn);                           // Enable ADC interrupt
    }
    
    // use time counter 0 channel 2 to generate the ADC start of conversion signal
    // i.e. this sets a rising edge with the right frequency for triggering ADC conversions corresponding to adc_sample_rate
    // for more information about the timers: https://github.com/ivanseidel/DueTimer/blob/master/TimerCounter.md
    // NOTE: TIOA2 should not be available on any due pin https://github.com/ivanseidel/DueTimer/issues/11
    void tc_setup()
    {
      PMC->PMC_PCER0 |= PMC_PCER0_PID29;                     // TC2 power ON : Timer Counter 0 channel 2 IS TC2
      TC0->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2 // clock 2 has frequency MCK/8, clk on rising edge
                                  | TC_CMR_WAVE              // Waveform mode
                                  | TC_CMR_WAVSEL_UP_RC      // UP mode with automatic trigger on RC Compare
                                  | TC_CMR_ACPA_CLEAR        // Clear TIOA2 on RA compare match
                                  | TC_CMR_ACPC_SET;         // Set TIOA2 on RC compare match
    
      constexpr int ticks_per_sample = F_CPU / 8 / adc_sample_rate; // F_CPU / 8 is the timer clock frequency, see MCK/8 setup
      constexpr int ticks_duty_cycle = ticks_per_sample / 2;        // duty rate up vs down ticks over timer cycle; use 50%
      TC0->TC_CHANNEL[2].TC_RC = ticks_per_sample;
      TC0->TC_CHANNEL[2].TC_RA = ticks_duty_cycle;
    
      TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC2 counter and enable
    }
    
    // ISR for the ADC ready readout interrupt
    // push the current ADC data on all adc_channels to the buffer
    // update the time index
    // set flag conversion ready
    void ADC_Handler()
    {
      for (size_t i = 0; i < nbr_adc_channels; i++)
      {
        adc_meas_buffer[crrt_adc_meas_buffer_idx][i] = static_cast<volatile uint16_t>(*(ADC->ADC_CDR + adc_channels[i]) & 0x0FFFF);
      }
    
      crrt_adc_meas_buffer_idx = (crrt_adc_meas_buffer_idx + 1) % adc_buffer_nbr_consec_meas;
    
      adc_flag_conversion = true;
    }
    
    //--------------------------------------------------------------------------------------------------
    //--------------------------------------------------------------------------------------------------
    
    // a simple script: setup and print information
    
    void setup()
    {
      Serial.begin(115200);
      delay(100);
    
      adc_setup();
      tc_setup();
    }
    
    void loop()
    {
      if (adc_flag_conversion == true)
      {
        adc_flag_conversion = false;
    
        if (print_reduced_time_stats)
        {
          nbr_readings_since_reduced_time_stats += 1;
    
          if (nbr_readings_since_reduced_time_stats == adc_sample_rate)
          {
            current_reduced_time_stats_us = micros();
            delta_reduced_time_stats_us_as_s = static_cast<float>(current_reduced_time_stats_us - previous_reduced_time_stats_us) / 1000000.0;
            effective_logging_frequency = static_cast<float>(adc_sample_rate) / delta_reduced_time_stats_us_as_s;
            previous_reduced_time_stats_us = current_reduced_time_stats_us;
    
            Serial.print(F("Effective logging freq over nbr spls that should correspond to 1 second: "));
            Serial.println(effective_logging_frequency);
    
            nbr_readings_since_reduced_time_stats = 0;
          }
        }
    
        if (print_time_stats)
        {
          current_us = micros();
    
          delta_us = current_us - previous_us;
          delta_us_as_s = static_cast<float>(delta_us) / 1000000.0;
          delta_us_as_ms = static_cast<float>(delta_us) / 1000.0;
    
          Serial.println(F("ADC avail at uS"));
          Serial.println(micros());
          Serial.println(F("elapsed us"));
          Serial.println(delta_us);
          Serial.println(F("elapsed ms"));
          Serial.println(delta_us_as_ms);
          Serial.println(F("elapsed s"));
          Serial.println(delta_us_as_s);
          Serial.println(F("updated idx:"));
          size_t last_modified_buffer_idx;
          if (crrt_adc_meas_buffer_idx > 0){
            last_modified_buffer_idx = crrt_adc_meas_buffer_idx - 1;
          }
          else{
            last_modified_buffer_idx = nbr_adc_channels - 1;
          }
          Serial.println(last_modified_buffer_idx);
    
          previous_us = current_us;
        }
    
        if (print_full_buffer)
        {
          for (size_t i = 0; i < nbr_adc_channels; i++)
          {
            Serial.print(F(" ADC "));
            Serial.print(adc_channels[i]);
            Serial.println(F(" meas in time:"));
    
            for (size_t j = 0; j < adc_buffer_nbr_consec_meas; j++)
            {
              Serial.print(adc_meas_buffer[j][i]);
              Serial.print(F(" "));
            }
            Serial.println();
          }
        }
      }
    }
    

  •  类似资料:
    • ADC 简介 ADC(Analog-to-Digital Converter) 指模数转换器。是指将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号,例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发射的数字形式。模数转换器可以实现这个功能,在各种不同的产品中都可以找到它的身影。与之相对应的 DAC(Digital-to-Analog Converter),它是 ADC

    • 我使用SPI通信尝试连接我的Arduino Mega 2560作为主从ADC芯片()在外部时钟模式,但我一直收到相同的值或所有零每当我运行我的代码。还有一个触摸屏(工作)连接。除了与Arduino的ADC芯片通信外,一切正常。 我尝试删除定义头,并改变时钟。也不要改变任何东西(这可能是因为其他错误)。 错误包括RB1=255;RB2=255;RB3=255或全部为零。

    • ADC

      machine.ADC machine.ADC 类是 machine 模块下的一个硬件类,用于指定 ADC 设备的配置和控制,提供对 ADC 设备的操作方法。 ADC(Analog-to-Digital Converter,模数转换器),用于将连续变化的模拟信号转化为离散的数字信号。 ADC 设备两个重要参数:采样值、分辨率; 采样值:当前时间由模拟信号转化的数值信号的数值; 分辨率:以二进制(或

    • ADC

      通过 ADC 设备采样电压值并转换为数值 通过 ADC 设备采样电压值并转换为数值 源码/* * Copyright (c) 2006-2018, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2018-11-29 misonyo f

    • ADC

      Advanced Direct Connect (ADC) 是一个简单的协议用于客户端和服务器之间面向消息和文件分享系统。

    • 这是 ADC (Advanced Direct Connect) 协议的 Python 用于实现。 该库中提供以下入口点: adc-server:实验性adc客户端(作为服务器实现) adc-client:实验性adc客户端(用于连接到运行中的客户端服务器) adc-tthsum:一个简单的包装程序,用于检查文件的第t个根哈希