|
在STM32HAL库里定义很多状态机变量,用于对各种外设模块的运行状态有序管理,涉及的状态有ready,busy,idle,timeout,error等。利用这些状态变量,以便正确操控各个外设,避免使用上的混乱。 以USART为例,HAL库里为USART的发送、接收操作分别定义了两个状态变量,即gState和RxState。其中gState表示USART的发送状态,RxState表示接收时可能的状态,是ready还是busy或其它诸如出错、超时状态。【下面重点聊聊gState】 USART初始化后gState为reddy状态,当要调用UART发送函数时程序将其设置为Busy。从下面的截图我们可以看到,不论以哪种方式调用UART发送库函数,都会有针对UART发送状态变量gState的检查,如果是Ready就继续执行后续程序,并将gState改为Busy,否则返回出错。
当USART完成发送或中止发送时,软件将gState设置回Ready。下面截图的前两个方框是UART在发送超时或正常结束时将gState改为Ready的情形,第三个方框是先在DMA完成中断里使能UART发送完成中断,然后通过UART中断将gState改为Ready。也就是说,使用DMA方式实现UART发送,gState终究还是通过UART中断服务程序完成修改。
如果说你调用的查询式发送函数,即HAL_UART_Transmit(),则在该函数里查询到数据发送完毕或发生超时都会将gState设置为Ready。这里就不截图了,有兴趣可以直接去阅读该函数代码。 如果说你调用的是中断式UART发送函数,即HAL_UART_Transmit_IT(),同时你沿用CubeMx创建的中断服务程序代码框架,在相应的发送中断服务程序里检测到发送完成时,也会将gState修改为ready为下次发送做准备。 如果你调用的API函数虽然是HAL_UART_Transmit_IT(),而中断服务程序是另外组织的,要记得在发送完成时将gState改为Ready,不然下次调用HAL_UART_Transmit_IT()函数会因为gstate检查不通过而不能得以执行。 下面截图就是基于中断方式完成两次UART发送的情形。gState的修改在UART中断服务程序里完成,上面贴过图了。
如果说你调用的是DMA方式发送函数,即HAL_UART_Transmit_DMA(),同样,也沿用CubeMx创建的中断服务程序代码框架,且在Cubemx那里使能了UART及相关DMA的中断响应。 程序会在相应的DMA完成中断代码里使能UART的发送完成中断,然后在UART发送完成中断里再将gState从busy改为ready。即gState的改变由两个步骤完成,涉及UART和DMA两个外设的中断。关于这点在前面也提过。
如果像下面配置和组织用户代码,每次基于DMA方式的UART发送都是有效的。
如果说,你虽然调用了HAL_UART_Transmit_DMA()函数,但在CubeMx那边没有使能DMA或UART的中断响应,也没有在适当的位置做状态切换,这时就会发生即使你多次调用HAL_UART_Transmit_DMA()函数而只做一次发送的情形。因为在后续的HAL_UART_Transmit_DMA()函数里做gState检查,发现busy就会强行退出。具体到下面的测试就是卡在gState的while等待处。【在下面的CubeMx配置里没有使能UART及相应DMA通道的中断响应】
如果说,在CubeMx那边只使能DMA的中断响应,然后调用DMA方式的UART发送函数HAL_UART_Transmit_DMA(),即使多次调用也只有第一次有效【在下面的CubeMx配置里仅仅UART发送相关的DMA通道的中断响应】。
如果在CubeMx那边只使能UART的中断响应,不使能DMA的中断响应会怎么样呢?照样多次调用DMA方式的UART发送也只是第一次有效。因为按照目前库代码的逻辑,UART的TC中断使能是在DMA中断里完成的,TC中断没有被使能自然就没法进入UART发送完成中断去修改gState的状态。当然,如果你手动额外使能UART的TC中断就另当别论。 显然,基于事件触发在中断里进行修改状态有其合理和实时性。如果我们只是想调用库函数实现些功能,而不想开启相关中断,我们适时地手动修改gState状态也是可以的,但要保证合适的时间点。【下面在CubeMx那里未使能UART及相关DMA的中断响应】
比方像下面这样使用DMA方式依次做三次串口输出:
从上面实现代码不难发现,我在调用第2次、第3次的DMA方式的UART发送函数前,不仅修改了UART的发送状态gState,也修改了跟当前的DMA状态,而且还多了针对DMA的解锁操作。 不难理解,UART外设有状态机管理,DMA也可以有,再次调用时修改DMA状态为Ready可以理解,为什么又多了个解锁操作呢?这是HAL库针对这种共享性资源增加的管理机制。 针对状态机和锁机制,个人是这样理解的,状态机主要用于管理系统或外设的运行状态,确保操作按正确的顺序进行。它跟踪资源的当前状态,并根据状态决定下一步的操作。侧重行为的有序管理。 锁机制主要用于管理资源的占用,确保在多任务环境中对共享资源的访问安全,防止多任务或中断同时访问同一个资源,导致数据竞争和不一致的问题。侧重资源占用管理。 下面截图是STM32库代码里有关DMA完成中断的部分处理代码:
我们可以看到,在DMA传输完成中断里,库代码既做了DMA本身状态机变量的修改也做了当前DMA资源的解锁操作。 前面重点分析了使用CubeMx配置和HAL库函数时,多次调用DMA方式的UART发送只有1次有效的可能原因。 这里还有一种情况,当我们基于中断或DMA方式调用UART发送,也开启了相关中断并沿用库函数的写法逻辑,还是可能出现只有第一次发送有效的情况,比方像下面这样的写法: HAL_UART_Transmit_DMA(&huart2, (uint8_t *)String1, sizeof(String1)); HAL_UART_Transmit_DMA(&huart2, (uint8_t *)String2, sizeof(String2)); HAL_UART_Transmit_DMA(&huart2, (uint8_t *)String3, sizeof(String3)); 这里的三次调用太近了,第一次调用时软件将gstate改为busy,当运行第二个第三个调用时,第一次的uart发送可能根本没有完成,自然也就没法基于其完成中断将gstate改为ready,这样导致后续的两次调用都无效了。所以像这种情况,后续调用前要确保上次传输完成了。当然,如果基于中断方式进行也要注意同样的问题。显然,这里从第二次调用前加个状态检查等待即可。 上面内容仅适用于使用STM32 HAL库函数及相关代码逻辑的情况。今天就分享到这里,供君参考。就此搁笔,下次再聊。 文章出处:茶话MCU |
经验分享 | 使用EXIT0同步触发SPI的DMA发送话题
【开发经验】LAT1500 如何通过DMA配合CRC功能
我心中的ST中文论坛
经验分享 | 为什么重启ADC的DMA传输要先停掉ADC?
经验分享 | 使用GPIO+DMA+TIM模拟SPI通信演示
经验分享 | 为什么重启ADC的DMA传输要先停掉ADC?
经验分享 | 多个SPI的DMA传输为何仅1个有效?
经验分享 | STM32 DMAMUX应用示例
经验分享 | STM32双定时器+ADC+DMA实战案例
经验分享 | STM32U5系列TIMER+DMA+DAC应用演示
微信公众号
手机版