适用于初学者的AVR教程。
目标
理解中断的定义,学会封装函数。通过中断使按键可以控制LED的状态。
使用到的硬件
- Arduino uno
- 面包板
- 杜邦线
- 电阻
- 开关
- LED
- 三极管
完整代码
1 |
|
电路图
需要注意的是,这里关于LED部分使用了一个三极管开关电路。如果你没有三极管,可以直接将Pin和LED及电阻连接。Arduino有足够的电流供给,该电路适用于弱电流GPIO的开发板,比如Jetson Nano。
中断和中断向量
如上图所示中断的流程大概分为以下几步:
- 达到一定条件触发中断
- 根据被触发的中断编号,找到对应的中断向量,再由中断向量指向对应的中断程序
- 执行中断程序,结束后返回中断点
中断向量可能听起来相对陌生,对于它,可以简单的理解为一个指针。该指针指向中断程序的地址。整个中断的过程可以看作是双重指针的应用。首先当中断被触发后,cup记录当前地址,并将下一步运行的地址被指向中断向量表。然后经过查表,找到所使用的中断向量。再由该向量指向需要被执行的中断程序。执行结束后通过触发中断时记录的地址返回中断点。
在一些老式的单片机上,比如瑞萨的M16C。当使用中断时(定时器也是中断的一种),需要手动改写中断向量表来释放中断资源。而Arduino IDE将这一步操作自动执行了,通常来说并不需要更改向量表。如果需要修改中断向量表,请参考数据手册P52。由于这部分内容,新手用不到,老鸟看得懂,在此不做描述。
Atmega328P上的硬件中断
在Atmega328上有两个正常的硬件中断,从中断向量表中可以看到分别是INT0和INT1。这两个中断对应的引脚为PD2和PD3。除了这两个中断外,其余的硬件中断为“Pin-Change Interrupt”,简记为PCINT。
那么这两种中断有什么区别呢?通常情况下,每个中断都有自己的中断向量,用来寻址。但是Atmega328p的所有GPIO都可以用作中断引脚,如果每个Pin都有一个自己的中断向量,那么向量表就会变得特别冗长。因此PCINT被设计了出来。不同于一般的硬件中断,PCINT分为三组。每组对应8个Pin,通过寄存器PCICR、PCIFR和PCMSKn(此处以PCMSK1为例)来配置:
这三个寄存器很容易记忆,PC代表着Pin-Change。ICR指的是interrupt control register,IFR指的是interrupt flage register,MSK指的是mask(掩码)。PCICR和PCIFR用来使能你所使用的PCINT0/1/2。PCMSK用来选择具体的引脚位置。示例程序使用PC1作为按键接入口,其中断部分的初始化为:
1 | PCICR=1<<PCIE1; |
在完成初始化之后,即可以开始使用该PCINT了。首先使用sei();
来打开中断。然后使用下面的结构编写中断程序:
1 | ISR(你的中断向量){ |
需要注意的是,由于PCINT0/1/2的每个中断向量都对应着多个Pin,因此还需要在中断程序中对于具体Pin的位置进行判断。比如,示例中更改参数a的状态:
1 | if (~PINC & (1 << 1)) { |
再说回普通的硬件中断,INT0和INT1。他们初始化使用的寄存器为EICRA、EIMSK和EIFR。其初始化方法与PCINT类似,详细描述可以参考手册P54。与PCINT的区别在于由于具有具有单独的中断向量,INT0和INT1不需要通过额外的判断来确定具体的Pin的位置。且通过配置寄存器EICRA,可以选择4种中断被触发的方式:高电平触发,低电平触发,上升沿触发和下降沿触发。而在PCINT中,触发方式需要程序员在判断Pin的位置时自行编写。示例程序中的触发方式为低电平触发。
Tips:
对于函数sei();
,有一个功能相反的函数cli();
。该函数的功能是禁用所有中断。当你有一段程序不想被仍和中断打扰时,可以在该段程序前添加函数cli();
、末尾添加函数sei();
来在该段程序中屏蔽所有中断功能。