我正在制作一个基于数字时钟的项目,在面包板上使用
atmega328p
。我将 4 个按钮连接到 ADC3
通道,并使用 INT0
作为触发器来启动 ADC 转换。我还启用了 TimerCounter1
来计算时间。
我在使用增加按钮增加时间时遇到问题,因为
setTime(3, 30)
函数调用位于 int main()
函数中,按下按钮后,该函数 setTime(3, 30)
会一次又一次地执行。仅当执行 ISR(ADC_vect)
时才会发生这种情况。
这是我认为相关的代码部分:
xxx.ino
#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 comapare A
TIMSK1 |= (1<<OCIE1A);
// Set TimerCounter1 in CTC mode
TCCR1A &= ~(1<<WGM10);
TCCR1A &= ~(1<<WGM11);
TCCR1B |= (1<<WGM12);
TCCR1B &= ~(1<<WGM13);
// Set the prescalr 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 referance - 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 registor
SREG |= (1<<7);
ADCSRA |= (1<<ADIE);
// Set the prescalar to 128 - 125KHz
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
为什么当
int main()
执行时,ISR(ADC_vect)
函数中的指令会被执行一遍又一遍,而当ISR(TIMER1_COMPA_vect)
执行时却不执行?仅当执行 PB5
时,ISR(ADC_vect)
引脚才会切换。
任何函数中的代码都会从函数开始到结束按顺序运行一次。就你的
main()
函数而言,它会达到无限循环,并永远停留在那里。
中断(以及附加到其调用向量的函数)是不按顺序调用的“特殊”代码段。也就是说,它们会在需要运行时(例如发生定时器比较时)中断正常的程序流程,然后将 CPU 返回到原来的位置。
在您的情况下,无限循环点的指令流最终会产生以下效果:
loop:
nop ; do absolutely nothing
rjmp loop ;go back to the "loop" label
发生中断时,执行路径基本上如下所示:
loop:
rjmp interrupt ;to the interrupt
(... interrupt here ...)
return here ; back from interrupt
nop ; do absolutely nothing
rjmp loop ;go back to the "loop" label
最后我发现了问题所在 - 为什么
main()
函数只有在ISR(ADC_vect)
中断发生时才再次执行。
就像上面
djph
的回答所说,main()
函数只执行一次,然后在while(1);
中永远循环,当发生中断时,程序暂停当前任务并从循环while(1);
跳转到相应的中断例程 -ISR()
然后程序跳回循环 -while(1)
,意味着在 main()
中断执行后不会再次执行 ISR()
函数。
因此,就我而言,仅当 ISR(ADC_vect) 中断发生时,
main()
函数才会再次执行。为什么?
如果我们看看我如何初始化 ADC ->
void __init__button()
功能:
// Select the voltage referance - 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 registor
SREG |= (1<<7);
ADCSRA |= (1<<ADIE);
// Set the prescalar to 128 - 125KHz
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);
ADC 自动触发启用位,ADC 将在所选触发信号的上升沿开始转换 - 即
INT0
。
ADCSRA |= (1<<ADATE);
// Enable the INT0
EIMSK |= (1<<INT0);
EICRA |= (1<<ISC00);
EICRA |= (1<<ISC01);
所以这意味着如果我按下按钮,不仅会发生
ADC
中断,还会发生INT0
中断,但是在我的代码中没有ISR(INT0_vect)
例程。这就是问题......
将 ISR(INT0_vect)
例程添加到我的代码中后,当 main()
中断发生时,ISR(ADC_vect)
函数不会再次执行,
ISR(INT0_vect)
{
// Do nothing
}
最后在我的数字时钟项目中,我现在可以在按下增加按钮时增加时间......谢谢。
可能有更好的方法来做到这一点,但这对我有用。
我不知道为什么如果发生特定中断而我们忘记将相应的例程 -
main()
添加到代码中,ISR()
函数会再次执行。
我想我现在知道为什么
main()
再次被处决 -
如果发生意外中断(启用中断且未安装处理程序,这通常表明存在错误),则默认操作是通过跳转到重置向量来重置设备。
ISR()
中断详细信息:www.nongnu.org/avr-libc/user-manual/group__avr__interrupts
这是处理空中断的更好方法 -
EMPTY_INTERRUPT(INT0_vect);