为何仅触发ISR(ADC_vect)时main()函数会重复执行?
我正在制作基于ATmega328P的数字时钟项目,将四个按键连接到ADC3通道,使用INT0触发ADC转换,同时启用TimerCounter1进行计时。目前遇到问题:按下时间增加按键时,main()函数中的setTime(3, 30)会重复执行,且该现象仅在ISR(ADC_vect)触发时出现,PB5引脚也仅此时会翻转。
相关代码
#include <avr/io.h> int main() { Serial.begin(9600); // PB5 DDRB |= (1<<DDB5); // ### Want to see how many times these functions - __init__timer(), setTime(), __init__button() gets executed after the Interrupt ### PORTB ^= (1<<PORTB5); __init__timer(); //setTime(3, 30); __init__button(); while(1) { } return 0; } void __init__timer() { // Enable the global Interrupt SREG |= (1<<7); // Enable the output compare match on compare A TIMSK1 |= (1<<OCIE1A); // Set TimerCounter1 in CTC mode TCCR1A &= ~(1<<WGM10); TCCR1A &= ~(1<<WGM11); TCCR1B |= (1<<WGM12); TCCR1B &= ~(1<<WGM13); // Set the prescaler to 1024, so we get 15625 Hz pulses (counts) per second TCCR1B |= (1<<CS10); TCCR1B &= ~(1<<CS11); TCCR1B |= (1<<CS12); // Assign the OCR1A to 46875 (3 seconds) OCR1A = 46875; } ISR(TIMER1_COMPA_vect) { Serial.println("ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled"); } void __init__button() { // Select the voltage reference - AREF pin ADMUX &= ~(1<<REFS1); ADMUX &= ~(1<<REFS0); // Select the input channel - ADC3 ADMUX |= (1<<MUX0); ADMUX |= (1<<MUX1); ADMUX &= ~(1<<MUX2); ADMUX &= ~(1<<MUX3); // Configure the ADCSRA register SREG |= (1<<7); ADCSRA |= (1<<ADIE); // Set the prescaler to 128 - 125 kHz ADCSRA |= (1<<ADPS0); ADCSRA |= (1<<ADPS1); ADCSRA |= (1<<ADPS2); // Enable the INT0 EIMSK |= (1<<INT0); EICRA |= (1<<ISC00); EICRA |= (1<<ISC01); ADCSRB &= ~(1<<ADTS0); ADCSRB |= (1<<ADTS1); ADCSRB &= ~(1<<ADTS2); ADCSRA |= (1<<ADATE); // This will start the conversion if trigger INT0 is pressed ADCSRA |= (1<<ADEN); } ISR(ADC_vect) { if((ADC < 750) && (ADC > 600)) { Serial.println("Button 1 -> PB5 toggled"); } if((ADC < 500) && (ADC > 350)) { Serial.println("Increase time button pressed... -> PB5 toggled"); //increaseTime(); } if((ADC < 300) && (ADC > 200)) { Serial.println("Decrease button pressed... -> PB5 toggled"); //decreaseTime(); } if((ADC < 200) && (ADC > 100)) { Serial.println("button 4 -> PB5 toggled"); } }
串口输出
ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled Increase time button pressed... -> PB5 toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled Increase time button pressed... -> PB5 toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled Increase time button pressed... -> PB5 toggled I⸮Increase time button pressed... -> PB5 toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled Increase time button pressed... -> PB5 toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled ISR(TIMER1_COMPA_vect) executed. -> PB5 Not toggled
请问为何仅ISR(ADC_vect)触发时main()函数指令会重复执行,而ISR(TIMER1_COMPA_vect)触发时却不会?
核心原因是ADC中断触发时程序的执行流程被破坏,而定时器1中断无此问题,具体拆解:
未处理的INT0中断导致程序重启
你配置了INT0上升沿触发,同时用INT0作为ADC触发源,但启用了INT0的中断使能(EIMSK |= (1<<INT0))却没有编写ISR(INT0_vect)处理函数。当按键按下触发INT0时,MCU找不到对应的中断服务程序,会默认跳转到复位向量(0地址),直接导致程序重启,重复执行main函数的初始化代码,包括PB5翻转和setTime()调用。高频中断引发的栈溢出
按键抖动会导致INT0短时间内多次触发ADC转换,ISR(ADC_vect)被频繁调用,而Serial.println()需要占用大量栈空间处理字符串和串口发送。ATmega328P仅2KB RAM,频繁调用会快速耗尽栈空间,覆盖中断返回地址,导致中断返回时跳转到main函数起始位置,重复执行初始化流程。定时器1中断3秒触发一次,栈溢出概率极低,因此无此现象。修复方案
- 禁用INT0中断使能:去掉
EIMSK |= (1<<INT0),你只需要INT0作为ADC触发源,不需要响应INT0中断。 - 添加按键防抖:通过硬件RC电路或软件延时过滤按键抖动,减少ADC中断触发频率。
- 优化ISR中的串口操作:改用预定义字符串的轻量化输出方式,减少栈空间占用,或者在主循环中处理串口输出,ISR仅标记按键状态。
- 禁用INT0中断使能:去掉
内容的提问来源于stack exchange,提问作者in.yssh

