ATmega2560 ADC采样速率与串口输出实际不符问题排查
问题背景
使用ATmega2560,配置16MHz时钟、64分频触发ADC单次转换(单次转换需13个时钟周期),理论采样速率约19200次/秒。但实际每半秒仅输出512个样本,输出呈现每512个样本为一组、间隔约半秒的规律。
为简化测试,在ADC中断服务程序中仅发送'\n'到USART0,预期每秒输出约19000行,但实际速率远低于预期。
测试代码
#include <util/delay.h> #include <stdio.h> #include <stdint.h> #include <avr/io.h> #include <avr/interrupt.h> #include <stdbool.h> #define BAUD 57600 uint16_t myubbr= (F_CPU/16/BAUD-1); uint16_t ticks=1; uint8_t counter=0; void usart_init( uint16_t ubrr) { // Set baud rate UBRR0H = (uint8_t)(ubrr>>8); UBRR0L = (uint8_t)ubrr; UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); /* 8-bit data */ UCSR0B = _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0); /* Enable RX and TX */ } void setup_adc(void) { // enable adc, auto trigger, interrupt enable, prescale=128 ADCSRA = (( 1<<ADEN ) | ( 1<<ADATE ) | ( 1<<ADIE ) | ( 1<<ADPS2 ) | ( 1<<ADPS1 ) | ( 1<<ADPS0 )); // Timer/Counter 1 Compare Match B ADCSRB = (( 1<<ADTS2 ) | ( 1<<ADTS0 )); // ref=AVcc + adc chan ADMUX = (1 << REFS0) | (1 << ADLAR); //set Voltage reference to Avcc (5v), left adjust converted value, if this is commented we use AREF, last tree are for selecting A7 as input as shown in the table } void timer1_init ( uint16_t ticks ) { TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; TIMSK1 = 0; TCCR1B = ( 1 << WGM12 ) ; // Configure for CTC mode 4 OCR1A=TOP OCR1B = ticks; // compare value OCR1A = ticks; // Set CTC TOP value, must be >= OCR1B // start timer, give it a clock TCCR1B |= ( 1 << CS10 ) | ( 1 << CS11 ); //no prescaler //(( 1 << CS10 ) | ( 1 << CS12 )) ; // Fcpu/1024, 64us tick @ 16 MHz } void set_ADMUX(void){ ADMUX = (1 << REFS0) | (1 << ADLAR); } // ADC complete interrupt service routine ISR(ADC_vect) { counter=counter+1; while(UCSR0A&(1<<UDRE0)==0){;} UDR0 = '\n'; set_ADMUX(); TIFR1 = ( 1<<OCF1B ); // clear Compare Match B Flag } //main function int main(void){ timer1_init (ticks); usart_init(myubbr); setup_adc(); //clear interrupt registers cli(); //enable global interrupts sei(); //start first rilevation ADCSRA |= (1 << ADSC); while(1){ } }
输出现象
连续输出512个换行符,时间戳密集:
. . . [17:35:58:542] ␊ [17:35:58:542] ␊ [17:35:58:542] ␊ . . .
随后间隔约半秒,再次输出下一组512个换行符:
. . . [17:35:59:063] ␊ [17:35:59:063] ␊ [17:35:59:063] ␊ . . .
问题分析与解决方案
核心原因:USART带宽不足+中断阻塞
波特率限制传输能力
57600波特率下,每个字符(含起始/停止位)需约173.6微秒传输,每秒最多传输约5760个字符。预期的19200次/秒输出,远超USART的最大负载,这是根本矛盾。ADC中断被USART发送阻塞
在ADC中断里原地等待USART发送完成(while(UCSR0A&(1<<UDRE0)==0)),导致后续ADC触发被挂起,直到当前字符发送完毕。USART只有1级发送缓冲区,高采样速率下,系统会频繁陷入等待,采样节奏彻底打乱。定时器配置错误,采样速率远超预期
Timer1配置为CTC模式,ticks=1且分频64,定时器溢出周期为8微秒,触发ADC频率达125kHz。但ADC转换需104微秒,实际采样速率约9615次/秒,进一步加重USART负担。
修复方案
方案一:降低采样速率匹配USART能力
调整Timer1的ticks值,将采样速率降到5000次/秒以内:
// 采样速率5000次/秒时,ticks计算:(16MHz/64)/5000 -1 = 50-1=49 uint16_t ticks=49;
方案二:用USART发送中断+缓冲区,避免阻塞ADC
启用USART发送完成中断,用环形缓冲区缓存要发送的字符,ADC中断只负责写缓冲区,不等待发送:
#define BUFFER_SIZE 256 uint8_t tx_buffer[BUFFER_SIZE]; volatile uint8_t tx_head = 0; volatile uint8_t tx_tail = 0; void usart_send_char(uint8_t c) { uint8_t next_head = (tx_head + 1) % BUFFER_SIZE; while(next_head == tx_tail); // 等待缓冲区空位 tx_buffer[tx_head] = c; tx_head = next_head; UCSR0B |= _BV(UDRIE0); // 开启发送中断 } ISR(USART_UDRE_vect) { if(tx_head != tx_tail) { UDR0 = tx_buffer[tx_tail]; tx_tail = (tx_tail + 1) % BUFFER_SIZE; } else { UCSR0B &= ~_BV(UDRIE0); // 缓冲区空,关闭中断 } } ISR(ADC_vect) { counter++; usart_send_char('\n'); // 写入缓冲区后立即返回 set_ADMUX(); TIFR1 = (1<<OCF1B); } // 修改USART初始化,移除不需要的RXCIE0 void usart_init( uint16_t ubrr) { UBRR0H = (uint8_t)(ubrr>>8); UBRR0L = (uint8_t)ubrr; UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); UCSR0B = _BV(RXEN0) | _BV(TXEN0); }
方案三:提升USART波特率
如果需要更高采样率,可将波特率提至115200(需硬件支持),此时每秒可传输约11520个字符,能承载更高的输出量。
内容的提问来源于stack exchange,提问作者beginner

