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

为何仅触发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中断无此问题,具体拆解:

  1. 未处理的INT0中断导致程序重启
    你配置了INT0上升沿触发,同时用INT0作为ADC触发源,但启用了INT0的中断使能(EIMSK |= (1<<INT0))却没有编写ISR(INT0_vect)处理函数。当按键按下触发INT0时,MCU找不到对应的中断服务程序,会默认跳转到复位向量(0地址),直接导致程序重启,重复执行main函数的初始化代码,包括PB5翻转和setTime()调用。

  2. 高频中断引发的栈溢出
    按键抖动会导致INT0短时间内多次触发ADC转换,ISR(ADC_vect)被频繁调用,而Serial.println()需要占用大量栈空间处理字符串和串口发送。ATmega328P仅2KB RAM,频繁调用会快速耗尽栈空间,覆盖中断返回地址,导致中断返回时跳转到main函数起始位置,重复执行初始化流程。定时器1中断3秒触发一次,栈溢出概率极低,因此无此现象。

  3. 修复方案

    • 禁用INT0中断使能:去掉EIMSK |= (1<<INT0),你只需要INT0作为ADC触发源,不需要响应INT0中断。
    • 添加按键防抖:通过硬件RC电路或软件延时过滤按键抖动,减少ADC中断触发频率。
    • 优化ISR中的串口操作:改用预定义字符串的轻量化输出方式,减少栈空间占用,或者在主循环中处理串口输出,ISR仅标记按键状态。

内容的提问来源于stack exchange,提问作者in.yssh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 05:30:09