4. MSP430中断系统:中断向量表、中断优先级、外部中断配置、定时器中断
中断系统,说白了就是微控制器的“紧急事务处理机制”。
我刚开始用MSP430那会儿,觉得中断就是个“后台任务”。后来踩过坑才明白——中断用不好,整个系统都会乱套。今天咱们就把中断系统彻底讲透。
4.1 中断向量表——中断的“通讯录”
每个中断源都有一个固定的入口地址。这些地址排成一张表,就是中断向量表。
MSP430的中断向量表放在Flash的最高地址区域。比如MSP430F149,向量表从0xFFE0开始,到0xFFFF结束。
关键点:每个中断向量占2个字节,存放的是中断服务函数的入口地址。
我见过不少新手直接往向量表里写数字。千万别这么干!用编译器提供的宏定义或者#pragma指令,既安全又省心。
举个例子,定时器A的中断向量是这样定义的:
// IAR编译器写法
#pragma vector = TIMERA0_VECTOR
__interrupt void Timer_A0_ISR(void)
{
// 中断处理代码
}
// GCC编译器写法
__attribute__((interrupt(TIMERA0_VECTOR)))
void Timer_A0_ISR(void)
{
// 中断处理代码
}
嗯,这里要注意:不同编译器语法不一样。我习惯用IAR,但不管用哪个,原理都一样。
4.2 中断优先级——谁先处理?
MSP430的中断优先级是固定的。说白了,中断向量号越小,优先级越高。
| 中断源 | 向量地址 | 优先级 |
|---|---|---|
| 复位 (Reset) | 0xFFFE | 最高 |
| NMI | 0xFFFC | 次高 |
| Timer_B7 | 0xFFF8 | ... |
| Timer_A3 | 0xFFF0 | ... |
| UART接收 | 0xFFE8 | 较低 |
| ... | ... | ... |
你想想看,复位优先级最高,这很合理——系统都乱了,当然得先复位。
我的经验:别在低优先级中断里做耗时操作。如果高优先级中断来了,你的低优先级中断会被“打断”。我曾经在UART中断里做浮点运算,结果定时器中断丢了——血的教训。
4.3 外部中断配置——按键、传感器信号处理
外部中断,就是通过引脚触发的。MSP430的P1和P2口都支持外部中断。
配置步骤其实就三步:
- 设置引脚方向为输入——PxDIR &= ~BITx
- 选择中断触发边沿——上升沿还是下降沿
- 使能中断——PxIE |= BITx
来看个实际例子。我做一个低功耗按键唤醒项目时,这样配置的:
void Key_Init(void)
{
// P1.3作为按键输入
P1DIR &= ~BIT3; // 输入方向
P1REN |= BIT3; // 使能上拉电阻
P1OUT |= BIT3; // 上拉
// 配置下降沿触发
P1IES |= BIT3; // 下降沿触发
// 清除中断标志
P1IFG &= ~BIT3;
// 使能中断
P1IE |= BIT3;
// 总中断使能
__enable_interrupt();
}
// 中断服务函数
#pragma vector = PORT1_VECTOR
__interrupt void Port1_ISR(void)
{
if(P1IFG & BIT3)
{
// 按键被按下
// 这里加20ms延时消抖
__delay_cycles(20000);
if(!(P1IN & BIT3))
{
// 确认按键按下
Key_Action();
}
P1IFG &= ~BIT3; // 清除标志
}
}
注意:退出中断前一定要清除中断标志!我见过有人忘了清标志,结果中断反复触发,系统直接卡死。
4.4 定时器中断——精准的时间控制
定时器中断是嵌入式开发里最常用的。MSP430的Timer_A和Timer_B都支持多种模式。
我个人最常用的是“增计数模式”。说白了就是计数器从0加到设定值,然后触发中断,再回到0重新加。
配置定时器中断的步骤:
- 选择时钟源——SMCLK、ACLK还是TACLK
- 设置分频系数——ID0、ID1位控制
- 设定计数模式——增计数、连续计数还是增减计数
- 写入比较值——CCR0决定周期
- 使能中断——CCIE位
举个例子,我要产生1ms的定时中断:
void TimerA_Init(void)
{
// 假设SMCLK = 1MHz
// 1ms = 1000个时钟周期
TA0CTL = TASSEL_2 + ID_0 + MC_1; // SMCLK, 不分频, 增计数
TA0CCR0 = 1000 - 1; // 1ms定时
TA0CCTL0 = CCIE; // 使能比较中断
__enable_interrupt();
}
#pragma vector = TIMERA0_VECTOR
__interrupt void Timer_A0_ISR(void)
{
// 每1ms执行一次
static uint16_t ms_counter = 0;
ms_counter++;
if(ms_counter >= 1000)
{
ms_counter = 0;
// 1秒到了,做点什么
LED_Toggle();
}
}
避坑指南:我曾经把CCR0设成1000,以为就是1ms。结果忘了考虑时钟频率——SMCLK实际是1.048576MHz。算下来差了将近5%。所以一定要先确认时钟频率,再算比较值。
4.5 中断嵌套与注意事项
MSP430默认不支持中断嵌套。也就是说,一个中断正在处理时,其他中断进不来。
如果想实现中断嵌套,得在中断服务函数里手动开总中断:
#pragma vector = TIMERA0_VECTOR
__interrupt void Timer_A0_ISR(void)
{
__bis_SR_register(GIE); // 手动开中断,允许嵌套
// 这里可以被打断
// 处理耗时任务
// 退出时自动恢复SR
}
警告:中断嵌套要慎用!我见过一个项目,开了嵌套后,低优先级中断被高优先级中断反复打断,堆栈直接溢出。除非你很清楚自己在做什么,否则别开嵌套。
最后总结几个要点:
- 中断服务函数要短小精悍,别在里面做复杂运算
- 全局变量如果被中断和主循环共用,记得加volatile关键字
- 退出中断前一定要清标志位
- 调试时可以用LED翻转来观察中断是否正常触发
中断系统是MSP430的“灵魂”。用好了,你的程序就能高效响应各种事件。用不好,那就是一场灾难。希望今天的分享能帮你少走弯路。