一个跟STM32F0中断优先级有关的话题

本文作者:Miler Shao
    某日某工程师跟我交流,他在使用STM32F031的芯片开发一款电子产品. MCU跟外界有个UART串口通讯,外界经常会不定期地传送一串固定数量的数据包过来。令他郁闷的是,在从外界接受数据时偶尔会出现数据丢失一个两个的,尤其波特率高的时候容易发生。

 

     经过跟他深入沟通,了解到STM32F031跟外界有UART数据通信并开启了RXNE接收中断,还有对外的AD采样动作,通过定时器定时触发AD转换,并开启了ADC1的转换完成中断。AD触发间隔为2秒。再就是些其它对外的GPIO操作的东西。他陈述当波特率低于9600,甚至更低时就很难遇到丢包的现象,只要当波特率达到115200甚至更高时,就比较容易丢包,经常丢一个两个不等,波特率越高越容易丢。

     客户给USART1的时钟源配置的是48M系统时钟。按理说,STM32F0芯片的UART的波特率跑个200K是很轻松的事。让他用示波器在芯片RX脚监测外界传输过来的信号,当外界发送方的波特率即使在200K左右时波形还是很干净漂亮,看来不存在信号畸变的问题。

     因为他谈到开启了UART RX中断和ADC的EOC中断,我怀疑他的中断优先级配置可能有问题。察看其代码后,发现关于UARTTX/RX中断与ADC的EOC中断优先级一样的 。

     看到这里,基本算是找到原因了,只待进一步验证。

    外界不定期通过UART发送数据给MCU,当它收到一个数据本该通过RX中断请求去读取数据时,如果此刻碰上ADC的EOC中断服务程序刚刚开始或正在执行途中,由于二者优先级一样,那UART的RX 中断就得至少等待ADC中断服务程序继续执行到弹栈前的时间。若在这个等待期间内UART又收到了第二个数据甚至更多,那就会发生溢出导致数据丢失。

     那为什么会只是偶尔发生而且波特率高更容易发生呢?这也不难理解。

    波特率高意味着传送速度快,相应的每个字符的传送时间就短,即收到一个字符后,下一个字符来得也快。而每次的ADC的中断程序执行时间是相对固定的,最糟糕的情形就是产生UART RX中断请求时碰到EOC中断服务程序刚刚开始,这样等待时间最长。在UART接收到数据等待ADC中断释放CPU期间,新的数据来得越快,丢数据的几率就越高。当然了,不是每次都是碰到那个最糟糕的情形,最好的情形就是碰上ADC中断服务程序刚好执行完毕。

     反过来讲,如果UART传输波特率比较低,意味着单个字符传输时间相对比较长。碰到ADC中断服务程序先得到响应情况下,或许等人家执行完了再来取“待取走”的数据还来得及,尤其不在最糟糕的情形下。

    当该工程师将UART RX中断优先级配置为高于ADC的中断优先级后就再没那个麻烦了。

     顺便说说上面那红色语句“等待ADC中断继续执行直到弹栈前”。

    这句红色的话意思是说,在上面情况下,UART中断请求等待EOC中断运行到执行POP之前的时刻就可得到响应而去执行UART RX中断服务程序,并不急着执行EOC中断的POP弹栈动作,随之的UART中断服务程序也无需压栈操作,UART中断程序执行完毕后再回来做弹栈动作,然后回到主循环的中断处接着运行。这就是平常所说的咬尾中断。不难看出,这样可以大大提升中断响应速度。具体到本案,这个咬尾操作一定程度上减少了丢码机会。

    
    既然提到了咬尾中断,可能很多人听说过晚到中断。所谓晚到中断,简单点说就是低优先级中断服务程序正在压栈或刚压栈完毕时发生更高优先级的中断请求,高优先级中断不再做PUSH压栈操作,等到低优先级中断压栈完毕即直接运行高优先级中断服务程序,随后再返回来接着执行低优先级中断服务程序,之后再做POP弹栈操作。

    前面提到的STM32F0 的两个中断优先级相同情况下,都是假定中断请求在时间上错开了的情况。如果二者同时到达,那CPU先响应哪一个呢?就看二者在中断矢量表的序号,谁的序号小就先响应谁。

     另外,玩过CORTEX M3/M4内核MCU的人,比方STM32F1,STM32F2,STM32F3,STM32F4等芯片的人可能会发现,CORTEX M0 内核的MCU的中断管理跟其它CORTEX M3/M4内核的在中断优先级管理上是有差异的。

    M3/M4的MCU在中断优先级做分组管理,分抢占优先级和响应优先级。只有强占优先级高的中断请求才可以打断低抢占优先级的中断服务程序;抢占优先级相同的情况下,高响应优先级的中断请求顶多可以优先获得响应权。而M0内核芯片的中断优先级不再做分组管理,谁的优先级高就优先响应并可打断低优先级的中断服务程序。

     当在系统里开启多个中断事件时,要合理安排各中断源的优先级,有些时候可能还需精心安排。对于初学者,因为中断优先级问题处理不当而导致麻烦的情况时有发生。再就是对于中断服务程序,如果不是必需,代码尽量精简,不要累赘,能放到中断外部处理的就尽量放到外部去。

STM32F0系列微控制器支持多种中断源和中断机制,包括外部中断。外部中断允许处理器在特定事件发生时响应并执行预设的操作。对于STM32F0来说,外部中断通常通过GPIO(通用输入输出)端口触发,并且可以配置成边缘检测模式或电平检测模式。 ### STM32F0外部中断工作原理 #### 配置步骤: 1. **选择中断源**:确定哪些GPIO端口将作为中断源,并设定相应的中断标志位。 2. **设置中断模式**:可以选择上升沿、下降沿或高电平触发中断。 3. **分配中断优先级**:STM32F0支持中断向量表,每个中断都可以有独立的中断优先级。 4. **初始化中断线程**:在代码中初始化中断处理函数,例如使用NVIC(嵌入式矢量中断控制器)的`NVIC_Init()`函数。 5. **启动中断**:通过开启对应的中断使能寄存器,比如`EXTI_InitStructure.EXTI_Trigger = EXTI_TRIGGER_RISING | EXTI_TRIGGER_FALLING;`表示同时支持上升沿和下降沿触发中断。 6. **编写中断服务程序**:这是处理器在收到中断请求时执行的代码段,用于处理具体的中断操作。 #### 实现示例: 下面是一个简化的示例,演示如何配置STM32F0的外部中断: ```c #include "stm32f0xx.h" #include "stm32f0xx_exti.h" // 宏定义中断优先级和初始化结构体 #define INTERRUPT_PRIORITY_LEVEL NVIC_PRIO_LOW struct EXTI_InitTypeDef EXTI_InitStructure; void EXTI_Init(void) { // 初始化EXTI_InitStructure EXTI_InitStructure.EXTI_Line = GPIO_Pin_13; EXTI_InitStructure.EXTI_Mode = EXTI_MODE_IT_FALLING; // 下降沿触发中断 EXTI_InitStructure.EXTI_Pulse = EXTI_PPUB_DISABLE; EXTI_InitStructure.EXTI_Trigger = EXTI_TRIGGER_FALLING; // 只需关注下降沿 EXTI_InitStructure.EXTI_IRQn = EXTI15_10_IRQn; // 选择中断向量 // 初始化中断线程 EXTI_Init(&EXTI_InitStructure); } void EXTI15_10_IRQHandler() { __disable_irq(); // 禁止中断 // 执行中断处理任务 printf("External Interrupt on Pin 13 triggered!\n"); // 清除中断标志位 EXTI_ClearITPendingBit(EXTI_Line15); // 或者EXTI_LINE10等其他线号 __enable_irq(); // 允许中断 } ``` ### 相关问题: 1. **如何检测和处理特定GPIO脚下的外部中断事件?** 2. **如何配置STM32F0的外部中断触发模式,使得它在特定条件下激活中断?** 3. **在STM32F0中,如何避免误触发外部中断导致的系统不稳定情况?**
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值