ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

重温STM32基础:外部中断(2)

重温STM32基础:外部中断(2) 文章目录什么时候用中断课程硬件中断配置流程代码RCC 配置GPIO配置AFIO配置EXTI配置配置NVIC中断函数ISR (Interrupt Service Routine/Interrupt Handler)中断函数使用注意点中断函数名字中断源判断清除标志位其他使用注意点中断函数无参无返回旋转编码器实验摘要本文以 STM32F1 系列为例系统讲解外部中断的完整配置流程与中断函数ISR的编写要点。内容涵盖何时适合使用中断按键检测建议用轮询或定时器中断而非外部中断、课程所用旋转编码器的硬件原理以及从 RCC → GPIO → AFIO → EXTI → NVIC 的逐步配置方法含 AFIO 重映射、EXTI 标志位 API 的细节辨析。随后介绍中断函数的使用注意点固定函数名、中断源判断、清除标志位、避免耗时操作、无参无返回的原因最后通过旋转编码器实验复盘了面包板供电断开、双边沿触发导致计数抖动等实际踩坑经验。什么时候用中断设备产生的信号是one-shot的模式不是latched按键检测不建议使用外部中断因为不好处理抖动和松手检测而且按键的输出波形也不是one-shot的形式比较建议的方法是系统简单就在主程序中做轮询最优的做法是用定时器中断每10ms来检测一次按键这种方法不阻塞主程序这样也可以很好的处理按键抖动和松手检测的问题。课程硬件课程用的旋转编码器可以检测旋转速度也可以知道旋转方向。但课程用到的是接触式的不适合用于测速。旋转方向检测原理编码器中码盘的光栅位置经过比较精心地“布置”使得当A通道被遮光时B通道被透光。A和B通道输出维持90度地相位差。具体B超前还是滞后90度对应顺时针/逆时针旋转取决于传感器本身。对于课程用的模块红框部分是按键不按下的时候A端输出高电平被上拉电阻上拉按下之后A端输出低电平中断配置流程代码配置流程参考图片对每个外设先配RCC然后按照流程 GPIO - AFIO - EXTI - NVICRCC 配置GPIO和AFIO可以在库里找到但是EXTI虽然属于外设但是在库函数中RCC并不支持对它的相应配置EXTI不需要配RCCNVIC是内核外设RCC只控制内核外的外设无法控制到内核所以NVIC也不需要配RCC对于这里用到的几个外设的RCC配置会有一些疑惑AFIO在RM中似乎就是属于GPIO为什么它还有特定的时钟控制如果是自己写程序可能就认为开启GPIO的时钟就可以了。尝试理解了一下AFIO应该还是属于一个独立的外设。虽然AFIO和GPIO是在同一组但是它们应该有各自的门控开关RCC寄存器的对应位会对此进行控制。什么是一个独立的外设大概就是有独立的基地址和寄存器块可以在memory map中看到在RCC使能函数中有它的名号EXTI居然没有RCC。当然这也是课程中老师觉得比较惊讶的。虽然EXTI没有RCC但一开始我们是不知道它没有的。所以我们还是要找一下。总之在写程序的时候先识别出我们会用到的外设在配置每一个外设的时候都要先考虑配置RCC通过看库函数里的RCC使能函数、RM等真的没有就再说。如果没有配置RCC我们是无法写入相应寄存器的。如果发生写入无效、外设不工作等先考虑RCC配置是否正确。另外可以注意的是AFIO的这种配置似乎只有在STM32F1系列中有。在后面的STM32F4和其他系列中重映射的配置包括EXTI的配置和寄存器组都不太一样了。GPIO配置引脚将被复用为另一个外设具体的GPIO模式可以参考 GPIO Configurations for device peripheralsAFIO配置AFIO相关函数和GPIO的函数在同一个文件大部分的名字也叫GPIO_xxx而不是AFIO_xxx。几个和AFIO相关的关键函数是voidGPIO_EXTILineConfig(uint8_tGPIO_PortSource,uint8_tGPIO_PinSource);voidGPIO_PinRemapConfig(uint32_tGPIO_Remap,FunctionalState NewState);voidGPIO_EventOutputConfig(...);GPIO_EXTILineConfig配置的是把端口重映射为EXTI。EXTI和端口号的对应关系很规则内部实现相对简单。GPIO_PinRemapConfig里面的实现非常复杂。它里面主要操作的是MAPR寄存器如果看RM可以发现这个寄存器塞了各种不同外设的可能的重映射组合有的外设的重映射是一位宽被重映射和不被重映射有的是两位宽不被重映射部分重映射无效值/第二种部分重映射可能和全部重映射如上图红框。而且它里面处理了SWJ_CFG相关bits如上图绿框中它是只可写的。如果读的话读出来的就是无效值。这就会带来问题Arm架构至少Cortex-M3这个芯片架构规定外设访问必须通过总线而总线本身没有“位寻址”功能所以32-bit寄存器只能32bits同步读出或者写入无法单独只修改一个bit。所以我们在修改寄存器的时候一般都是“读改写”的操作在程序上一般就是“|”。如果一个寄存器中有一些WO的bits比如MAPR中的24~26bit,且它们读出来是随机值因为硬件就没有提供读的功能假设我们只修改 bit1读出的时候24~26 bits是随机值然后改了bit1之后把32位再写入此时24~26是随机值我们把随机值写到了SWJ_CFG里了结合SWJ_CFG的功能这回启用或者关闭某些调试口给开发人员造成很大困惑。可以发现一个寄存器里存在多种读写权限的bits还是有点风险的。虽然这种风险已经很大程度被库函数/HAL库等化解但是如果有一天我们自己操作寄存器这个是需要注意的。EXTI配置EXTI相关配置API在stm32f10x_exti.c中。voidEXTI_DeInit(void);voidEXTI_Init(EXTI_InitTypeDef*EXTI_InitStruct);voidEXTI_StructInit(EXTI_InitTypeDef*EXTI_InitStruct);voidEXTI_GenerateSWInterrupt(uint32_tEXTI_Line);FlagStatusEXTI_GetFlagStatus(uint32_tEXTI_Line);voidEXTI_ClearFlag(uint32_tEXTI_Line);ITStatusEXTI_GetITStatus(uint32_tEXTI_Line);voidEXTI_ClearITPendingBit(uint32_tEXTI_Line);可以发现几乎每个外设都有这些APIxxx_DeInitxxx_Initxxx_StructInit这些可以被称作模板函数。EXTI_GenerateSWInterrupt 则是产生软件中断的API最后四个2组一个感觉可以一起看它们的实现中有细微的区别对于GetFlagStatus和GetITStatus它们本质都是读取EXTI的PR寄存器(pending register)可以发现RM中PR有19或者20个有效的bit位对应于19或者20根connectivity line device外部中断线。这里的rc_w1在前面的abbreviation中会有说明是“Software can read as well as clear this bit by writing 1. Writing ‘0’ has no effect on the bit value.”。GetFlagStatus只是单纯地检查了PR这一个寄存器而GetITStatus则还有读取了另一个寄存器IMR。那这两个API到底什么时候用哪个呢可以从下面的图中发现PR是在IMR的下游的。也就是说PR的某个bit被置1的前提条件必须是IMR被置1。也就是说GetITStatus能够得到1那么GetFlagStatus肯定也是1.这两个API似乎是冗余的其实不一定。咨询了一下AI考虑一个反过来的情况GetFlagStatus能够得到1GetITStatus不一定是1。当中断发生之后如果程序把IMR置0那么这个时候GetFlagStatus还是1但是GetITStatus是0.此时这两个API表示的意思是GetITStatus 表示的是pending 且未屏蔽而 GetFlagStatus 表示pending记录还在。或许这样理解好像也合理。在课程中老师说一般都是用GetITStatus这个。配置NVICNVIC是内核外设相关API 被ST发配到misc中了。配置中断分组注意整个程序NVIC分组必须只有一种。最好是放在主程序初始化中进行NVIC优先级分组配置而不是在各个模块中。初始化NVIC指定中断通道、使能中断通道、配置抢占优先级和响应优先级。中断函数ISR (Interrupt Service Routine/Interrupt Handler)中断函数使用注意点中断函数名字中断函数的名字是固定的可以在startup code里找到。最好是复制黏贴如果写错了就进不来中断了。中断源判断在课程中我们会用到EXTI15_10_IRQHandler这个ISR可以发现它时EXTI10~15任何一个发生Interrupt都会调用到这个ISR。所以进入这个ISR的第一件事就是检查中断源。而在旋转编码器的实验中会用到EXTI0和EXTI1这两个中断它们都是各自有自己的ISR的voidEXTI0_IRQHandler(void);voidEXTI1_IRQHandler(void);理论上进入之后不需要判断中断源但是要养成好习惯还是要检查一下中断源。清除标志位每次中断程序结束后都要清除中断标志位。其他使用注意点不要在中断中进行耗时的操作比如不要加delay不要在中断中操作主循环也操作的硬件比如同时操作OLED屏幕。中断函数无参无返回中断函数都是无参数无返回值的。为什么不行呢ISR不能是一个正常的函数有序言和尾声代码么了解了一下原因在AAPCSARM Architecture Procedure Call Standard中规定一个函数的前四个参数是由调用者caller放到R0R3寄存器中的更多参数的话就是压栈。但是中断是随时发生的它没有“调用者”。如果一个ISR有入参那么它就会看R0R3里面的值而由于没有caller给它存入参数此时的R0~R3的值是不可知的可能是当前正在计算的什么东西一个函数的返回值会放在R0中给caller读取。如果ISR有R0但是它没有caller来接收。ISR给R0送了个值进去会把当前程序的正常值给覆盖那程序肯定就不正常了。那既然中断任何时候都会来ISR任何时候都会被执行那怎么保存中断来时的状态呢这里用到的就是硬件解决方案当中断来了之后硬件压栈R0~R3,LR,PC,XPSR。在ISR执行结束之后会把这些再pop出来。旋转编码器实验这个实验遇到了一些问题烧录之后旋转旋钮屏幕完全没有反应。手头目前没有示波器不太好查。后来打开了弹幕。。。发现这个问题不止我遇到有人提供了解决方案我们的面包板左右两边的供电是断开的如下图橙色圆圈处。之前我把旋转编码器接在了左侧。这个图片里是正确的接法其实板上已经有提示了只是自己没有参透。。。而视频里老师的面包板这里都是连起来的说明整条面包板可以公用一个电源。接线正确了之后当转动旋钮发现数值会变但是一直保持在0抖动。如果用Keil的debug模式可以发现是能够进中断的。但是似乎这个数值被反复的override了。出现这种现象有好几种情况。有一种是中断捕获的设置不对。然后发现自己在debug第一个问题时埋下的坑开始怀疑是软件问题在AI提示下把EXTI_InitStructure.EXTI_Trigger设置成了双边触发EXTI_Trigger_Rising_Falling。由于把两个中断都设置成了这个而通道A和B的波形差90度相位。当A有下降沿的时候进入EXTI0中断counter–后面立刻B会有一个上升沿进入EXTI1中断counter所以大部分坑其实都是自己埋的。。。
返回列表