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 }
问题根源分析
- ISR逻辑顺序完全颠倒:ADC中断触发时,
ADCH存储的是上一次转换的结果。但原代码先切换通道,再读取ADCH,导致读取的结果和当前设置的通道不对应,必须加延时等待下一次转换完成,这完全是逻辑错误。 - 未配置ADC预分频器:ATmega2560的ADC时钟必须在50kHz~200kHz之间才能保证转换精度。原代码
ADCSRA未设置ADPS位,默认ADC时钟等于系统时钟(16MHz),远超上限,转换结果会失真,还会加剧通道切换后的不稳定。 - ISR中使用
_delay_ms():中断服务程序中绝对不能用延时函数,会阻塞整个系统,而且_delay_ms()依赖主循环的时钟上下文,在ISR中延时时间根本不准确。 - 自动触发时序冲突:配置了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
相关产品推荐
相关产品推荐

