You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ATmega2560切换ADC通道为何需添加3ms长延时?

ATmega2560多通道ADC采样通道不匹配问题排查与解决

问题描述

我编写了ATmega2560的多通道ADC转换代码,采样数据通过串口发送。现在遇到的问题是:必须在set_ADMUX()之后加3ms延时,否则采样结果和对应通道不匹配。但这个延时太长,不符合预期,更短的延时也无法解决问题。怀疑是ADC配置或ISR使用有问题,但找不到具体原因,已精简代码保留ADC相关部分。

原代码

//setup adc
void set_adc(void) {
  ADCSRA = (( 1<<ADEN ) | ( 1<<ADATE ) | ( 1<<ADIE ) | (1 << ADSC) ); // enable adc, auto trigger, interrupt enable, start first rilevation
  ADCSRB = (( 1<<ADTS2 ) | ( 1<<ADTS0 )); // Timer/Counter 1 Compare Match B 
  ADMUX = (1 << REFS0) | (1 << ADLAR); //set Voltage reference to Avcc (5v), left adjust converted value, if this is commented we use AREF
}

//timer for adc conversions
uint16_t ticks;
void set_timer1 ( uint16_t ticks )
{
  TCCR1A  = 0;
  TCCR1B  = 0;
  TCNT1   = 0;
  TIMSK1  = 0;
  TCCR1B  = ( 1 << WGM12 ) ;  // configure for CTC mode 4 OCR1A=TOP
  OCR1A   = ticks;  // set CTC TOP value
  TCCR1B |= ( 1 << CS10 ); // start timer, give it a clock, fcpu/1
}

//set channel for adc
volatile uint8_t i=0;
void set_ADMUX(void){ 
  switch(i){
    case 0:
        ADMUX = (1 << REFS0) | (1 << ADLAR);
        break;
    case 1:
        ADMUX = (1 << REFS0) | (1 << ADLAR) | (1 << MUX0);
        break;
    case 2:
        ADMUX = (1 << REFS0) | (1 << ADLAR) | (1 << MUX1);
        break;
    case 3:
        ADMUX = (1 << REFS0) | (1 << ADLAR) | (1 << MUX0) | (1 << MUX1);
        break;
    case 4:
        ADMUX = (1 << REFS0) | (1 << ADLAR) | (1 << MUX2);
        break;
    case 5:
        ADMUX = (1 << REFS0) | (1 << ADLAR) | (1 << MUX2) | (1 << MUX0);
        break;
    case 6:
        ADMUX = (1 << REFS0) | (1 << ADLAR) | (1 << MUX2) | (1 << MUX1);
        break;
    case 7:
        ADMUX = (1 << REFS0) | (1 << ADLAR) | (1 << MUX2) | (1 << MUX1) | (1 << MUX0);
        break;
  }
}

//adc interrupt
uint32_t key=0x55aa;
volatile uint8_t sample_count=0;
volatile uint8_t send_flag=0;
uint8_t buffull_count=0;
ISR(ADC_vect) {
  while(channel_buffer[i]!=1){
      i++;  
      if(i>=8){
          i=0;
          }
      }
  set_ADMUX();  
  _delay_ms(3);//<-------------------------------------------------------------
  switch(i){
      case 0:
        if(mode==1){
            buffer1[sample_count]=ADCH;
            }
        channel=1;
        //printf("1");
        break;
      case 1:
        if(mode==1){
            buffer2[sample_count]=ADCH;
            }
        channel=2;
        //printf("2");
        break;
      case 2:
        if(mode==1){
            buffer3[sample_count]=ADCH;
            }
        channel=3;
        //printf("3");
        break;
      case 3:
        if(mode==1){
            buffer4[sample_count]=ADCH;
            }
        channel=4;
        //printf("4");
        break;
      case 4:
        if(mode==1){
            buffer5[sample_count]=ADCH;
            }
        channel=5;
        //printf("5");
        break;
      case 5:
        if(mode==1){
            buffer6[sample_count]=ADCH;
            }
        channel=6;
        //printf("6");
        break;
      case 6:
        if(mode==1){
            buffer7[sample_count]=ADCH;
            }
        channel=7;
        //printf("7");
        break;
      case 7:
        if(mode==1){
            buffer8[sample_count]=ADCH;
            }
        channel=8;
        //printf("8");
        break;
      }
  if(mode==0){  
    printf("%d\n", channel);
    printf("%d\n", ADCH);
    structure.seq++;
    }  
  if(mode==1){sample_count++;}
  if(sample_count==BUFFER_SIZE-1){
        buffull_count++;
        sample_count=0;
        if(buffull_count==num_channels){
            sample_count=0; 
            if(mode==1){
              send_flag=1;
              buffull_count=0;
              }
            }
    }
  i++;
  TIFR1 = ( 1<<OCF1B ); // clear Compare Match B Flag
}

问题根源分析

  1. ISR逻辑顺序完全颠倒:ADC中断触发时,ADCH存储的是上一次转换的结果。但原代码先切换通道,再读取ADCH,导致读取的结果和当前设置的通道不对应,必须加延时等待下一次转换完成,这完全是逻辑错误。
  2. 未配置ADC预分频器:ATmega2560的ADC时钟必须在50kHz~200kHz之间才能保证转换精度。原代码ADCSRA未设置ADPS位,默认ADC时钟等于系统时钟(16MHz),远超上限,转换结果会失真,还会加剧通道切换后的不稳定。
  3. ISR中使用_delay_ms():中断服务程序中绝对不能用延时函数,会阻塞整个系统,而且_delay_ms()依赖主循环的时钟上下文,在ISR中延时时间根本不准确。
  4. 自动触发时序冲突:配置了ADC自动触发后,切换通道后会立刻触发新的采样,此时新通道的采样电容还没充放电完成,导致采样错误。

解决方案

1. 修正ISR逻辑顺序

先读取当前转换结果(对应触发中断的通道),再切换到下一个通道,让新通道有足够时间准备下一次采样。

2. 配置ADC预分频器

设置预分频为128,让ADC时钟为16MHz/128=125kHz,符合精度要求。

3. 去掉ISR中的延时

中断里禁止使用延时,利用两次自动触发的间隔来保证采样电容稳定。

4. 简化通道切换代码

直接用通道索引计算ADMUX的MUX位,避免switch语句的冗余和错误。

5. 修正标志位处理

不需要手动清除OCF1B,ADC自动触发会自动处理该事件。

修改后的代码示例

// 全局变量(根据实际需求补充定义)
volatile uint8_t current_channel = 0;
uint8_t channel_buffer[8] = {1,1,1,1,1,1,1,1}; // 启用所有通道,可按需修改
#define BUFFER_SIZE 64
uint8_t buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE], buffer3[BUFFER_SIZE], buffer4[BUFFER_SIZE];
uint8_t buffer5[BUFFER_SIZE], buffer6[BUFFER_SIZE], buffer7[BUFFER_SIZE], buffer8[BUFFER_SIZE];
volatile uint8_t sample_count = 0;
volatile uint8_t send_flag = 0;
uint8_t buffull_count = 0;
uint8_t mode = 0;
uint8_t num_channels = 8;

// 初始化ADC
void set_adc(void) {
  // 启用ADC、自动触发、中断,预分频128(ADC时钟125kHz)
  ADCSRA = (1<<ADEN) | (1<<ADATE) | (1<<ADIE) | (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
  // 设置触发源为Timer1 Compare Match B
  ADCSRB = (1<<ADTS2) | (1<<ADTS0);
  // 初始通道配置:AVCC参考、左对齐、初始通道0
  ADMUX = (1<<REFS0) | (1<<ADLAR) | current_channel;
  // 启动第一次转换
  ADCSRA |= (1<<ADSC);
}

// 配置Timer1用于ADC自动触发
void set_timer1(uint16_t ticks) {
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TIMSK1 = 0; // 不需要Timer1中断,仅需触发事件
  TCCR1B = (1<<WGM12); // CTC模式,OCR1A为TOP值
  OCR1A = ticks; // 设置采样间隔对应的TOP值
  OCR1B = ticks / 2; // 设置Compare Match B触发点(可按需调整)
  TCCR1B |= (1<<CS10); // 启动定时器,时钟源Fcpu/1
}

// 设置ADC通道
void set_ADMUX(uint8_t channel) {
  // 直接用通道索引设置MUX位,简化代码
  ADMUX = (1<<REFS0) | (1<<ADLAR) | channel;
}

// ADC中断服务程序
ISR(ADC_vect) {
  // 第一步:读取当前通道的转换结果
  switch(current_channel) {
    case 0: if(mode == 1) buffer1[sample_count] = ADCH; break;
    case 1: if(mode == 1) buffer2[sample_count] = ADCH; break;
    case 2: if(mode == 1) buffer3[sample_count] = ADCH; break;
    case 3: if(mode == 1) buffer4[sample_count] = ADCH; break;
    case 4: if(mode == 1) buffer5[sample_count] = ADCH; break;
    case 5: if(mode == 1) buffer6[sample_count] = ADCH; break;
    case 6: if(mode == 1) buffer7[sample_count] = ADCH; break;
    case 7: if(mode == 1) buffer8[sample_count] = ADCH; break;
  }

  // 第二步:处理串口输出或缓冲区计数
  if(mode == 0) {
    printf("%d\n", current_channel + 1);
    printf("%d\n", ADCH);
    // structure.seq++; // 若使用structure需提前定义
  }

  if(mode == 1) {
    sample_count++;
    if(sample_count == BUFFER_SIZE) {
      sample_count = 0;
      buffull_count++;
      if(buffull_count == num_channels) {
        send_flag = 1;
        buffull_count = 0;
      }
    }
  }

  // 第三步:切换到下一个启用的通道
  do {
    current_channel++;
    if(current_channel >= 8) current_channel = 0;
  } while(channel_buffer[current_channel] != 1);

  // 第四步:配置新通道,等待下一次自动触发采样
  set_ADMUX(current_channel);
}

内容的提问来源于stack exchange,提问作者beginner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 22:35:53