你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

经验分享 | DMA发送函数只能第一次调用有效?

[复制链接]
攻城狮Melo 发布时间:2026-3-25 14:00

在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,否则返回出错。

image.png

当USART完成发送或中止发送时,软件将gState设置回Ready。下面截图的前两个方框是UART在发送超时或正常结束时将gState改为Ready的情形,第三个方框是先在DMA完成中断里使能UART发送完成中断,然后通过UART中断将gState改为Ready。也就是说,使用DMA方式实现UART发送,gState终究还是通过UART中断服务程序完成修改。

image.png

如果说你调用的查询式发送函数,即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中断服务程序里完成,上面贴过图了。

image.png

如果说你调用的是DMA方式发送函数,即HAL_UART_Transmit_DMA(),同样,也沿用CubeMx创建的中断服务程序代码框架,且在Cubemx那里使能了UART及相关DMA的中断响应。

程序会在相应的DMA完成中断代码里使能UART的发送完成中断,然后在UART发送完成中断里再将gState从busy改为ready。即gState的改变由两个步骤完成,涉及UART和DMA两个外设的中断。关于这点在前面也提过。

image.png

如果像下面配置和组织用户代码,每次基于DMA方式的UART发送都是有效的。

image.png

如果说,你虽然调用了HAL_UART_Transmit_DMA()函数,但在CubeMx那边没有使能DMA或UART的中断响应,也没有在适当的位置做状态切换,这时就会发生即使你多次调用HAL_UART_Transmit_DMA()函数而只做一次发送的情形。因为在后续的HAL_UART_Transmit_DMA()函数里做gState检查,发现busy就会强行退出。具体到下面的测试就是卡在gState的while等待处。【在下面的CubeMx配置里没有使能UART及相应DMA通道的中断响应】

image.png

如果说,在CubeMx那边只使能DMA的中断响应,然后调用DMA方式的UART发送函数HAL_UART_Transmit_DMA(),即使多次调用也只有第一次有效【在下面的CubeMx配置里仅仅UART发送相关的DMA通道的中断响应】。

image.png

如果在CubeMx那边只使能UART的中断响应,不使能DMA的中断响应会怎么样呢?照样多次调用DMA方式的UART发送也只是第一次有效。因为按照目前库代码的逻辑,UART的TC中断使能是在DMA中断里完成的,TC中断没有被使能自然就没法进入UART发送完成中断去修改gState的状态。当然,如果你手动额外使能UART的TC中断就另当别论。

显然,基于事件触发在中断里进行修改状态有其合理和实时性。如果我们只是想调用库函数实现些功能,而不想开启相关中断,我们适时地手动修改gState状态也是可以的,但要保证合适的时间点。【下面在CubeMx那里未使能UART及相关DMA的中断响应】

image.png

比方像下面这样使用DMA方式依次做三次串口输出:

image.png

从上面实现代码不难发现,我在调用第2次、第3次的DMA方式的UART发送函数前,不仅修改了UART的发送状态gState,也修改了跟当前的DMA状态,而且还多了针对DMA的解锁操作。

不难理解,UART外设有状态机管理,DMA也可以有,再次调用时修改DMA状态为Ready可以理解,为什么又多了个解锁操作呢?这是HAL库针对这种共享性资源增加的管理机制。

针对状态机和锁机制,个人是这样理解的,状态机主要用于管理系统或外设的运行状态,确保操作按正确的顺序进行。它跟踪资源的当前状态,并根据状态决定下一步的操作。侧重行为的有序管理。

锁机制主要用于管理资源的占用,确保在多任务环境中对共享资源的访问安全,防止多任务或中断同时访问同一个资源,导致数据竞争和不一致的问题。侧重资源占用管理。

下面截图是STM32库代码里有关DMA完成中断的部分处理代码:

image.png

我们可以看到,在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

收藏 评论0 发布时间:2026-3-25 14:00

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版