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

经验分享 | 多个SPI的DMA传输为何仅1个有效?

[复制链接]
STMCU小助手 发布时间:2025-10-24 13:51

这里分享三个STM32应用中遇到的小问题,一起看看。

一、多个SPI的DMA传输为何仅1个有效?

有人使用STM32F407配置了3个SPI并开启DMA来实现通信。他在调试中发现,虽然配置了3个SPI以及各自DMA功能,但每次只能启动1个SPI的DMA收发功能。总是先使能哪个SPI的DMA功能,该SPI就工作,其它则不工作。也就是说,3个SPI及相应的DMA都能工作,就是不能同时工作。

客户使用Cubemx进行配置,基于LL库组织代码。下面截图是该用户提供的部分配置代码【SPI2和SPI3的初始化配置】:

image.png

如果SPI2的DMA使能代码在前SPI3在后,SPI2工作正常,SPI3则不工作。

image.png

如果SPI3的DMA使能代码在前,SPI3工作正常,SPI2就不工作。

image.png

但是,如果只配置任意1个SPI的DMA传输,都工作正常。

这样看来,他的SPI及DMA配置应该没啥问题。那问题在哪呢?

我看他的初始代码里有开启各DMA传输完成中断,怀疑是不是每次一使能某个SPI的DMA后,就触发了DMA传输完成中断。如果用户没有在DMA传输完成中断程序里做标志的清零,就有可能来不及继续运行后续的代码,即后续其它SPI的DMA传输的使能代码。

我拿STM32F407开发板,使用SPI1和SPI2进行测试验证。使用CubeMx进行配置并基于LL库组织代码。SP1和SP2的配置基本一样,都开启了各自的收发的DMA传输。

image.png

创建工程后,添加相应用户代码,进行测试。

/* USER CODE BEGIN 2 */

//+++++++++spi1 config++++++++++++
// LL_DMA_SetChannelSelection(DMA2, LL_DMA_STREAM_0, LL_DMA_CHANNEL_3);   /* SPI1_RX Init */
LL_DMA_ConfigAddresses(DMA2, LL_DMA_STREAM_0,  (uint32_t)&SPI1->DR,(uint32_t)&SPI1RXData[0],LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_0, SIZE);
LL_DMA_EnableIT_TC(DMA2, LL_DMA_STREAM_0);
LL_SPI_EnableDMAReq_RX(SPI1);


//  LL_DMA_SetChannelSelection(DMA2, LL_DMA_STREAM_3, LL_DMA_CHANNEL_3);   /* SPI1_TX Init */LL_DMA_ConfigAddresses(DMA2, LL_DMA_STREAM_3,  (uint32_t)&SPITXData[0],(uint32_t) &(SPI1->DR),LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_3, SIZE);
LL_DMA_EnableIT_TC(DMA2, LL_DMA_STREAM_3);
LL_SPI_EnableDMAReq_TX(SPI1);


//+++++++++spi2 config++++++++++++
//LL_DMA_SetChannelSelection(DMA1, LL_DMA_STREAM_3, LL_DMA_CHANNEL_0);   /* SPI2_RX Init */LL_DMA_ConfigAddresses(DMA1, LL_DMA_STREAM_3,  (uint32_t)&(SPI2->DR),(uint32_t)&SPI2RXData[0],LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_3, SIZE);
LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_3);
LL_SPI_EnableDMAReq_RX(SPI2);


// LL_DMA_SetChannelSelection(DMA1, LL_DMA_STREAM_4, LL_DMA_CHANNEL_0); /* SPI2_TX Init */
LL_DMA_ConfigAddresses(DMA1, LL_DMA_STREAM_4,  (uint32_t)&SPITXData[0], (uint32_t)&(SPI2->DR),LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_4, SIZE);
LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_4);
LL_SPI_EnableDMAReq_TX(SPI2);



 LL_SPI_Enable(SPI2);
 LL_SPI_Enable(SPI1);


LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_3); //for spi2LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_4); //for spi2

LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_0); //for spi1LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_3); //for spi1
/* USER CODE END 2 */

刚开始测试时,我每次让SPI1/SPI2收发8个数据,循环收发。结果发现,不论我先使能谁的DMA传输,都没有问题。

image.png

然后,我将DMA传输中断服务程序屏蔽掉,再行测试。不论我先使能谁的DMA传输,仍然都没有问题。显然,此时因为DMA传输中断服务程序被屏蔽,该中断会被没完没了的触发。

可能是传输数据较多的原因,在上一个DMA传输完成中断来临之前,不至于没有时间和机会来运行后续代码。于是我不断减少每轮DMA传输的个数,当减到1个时,果真出现了用户反馈的情形,即先使能哪个SPI的DMA传输,谁就工作正常,另外的就不工作了。

下图就是DMA传输个数为1,先使能SPI1的DMA的运行情况。

image.png

下图就是DMA传输个数为1,先使能SPI2的DMA的运行情况。

image.png

看到这里,看官应该明白怎么回事了。该问题来自于论坛,该用户估计是新手,可能他根本就没有准备相应的中断服务程序,刚好测试时又只收发1个数据,导致一使能某个DMA就立即触发中断,由于中断标志没能及时清零,导致CPU高速循环进出中断而不能运行其它代码了。【注:如果他使用cubemx创建基于HAL库的代码,就不会发生这个问题,除非他在cubeMx特意关闭DMA中断。因为HAL库里对各种中断最基本的标志清零管理都帮用户做了。用户往往只需补充些回调函数的代码。】

该问题虽然比较极端,但因类似响应时间、代码运行时序、中断管理方面问题我们在开发时还得多加留意。

二、多行UART的DMA输出代码怎么只有后面输出行才有效?

经常有STM32用户在做产品开发时遇到这种情况,就是基于DMA方式做UART输出时,连续几行的发送输出,只能看到最后一行的输出结果。

下图就是一种应用情景,本来希望相继输出2行不同内容,结果只输出后面代码的相应内容。

image.png

本来是希望输出电压信息和温度信息,结果只看到温度信息。

对于初次使用DMA的人来说,很容易发生类似问题。其实,这里的DMA发送API函数只是完成相关配置及使能操作,并不等于DMA的传输完成,这也是非阻塞式启动代码的特点。具体到这里,刚完成前面配置马上又来改变配置了,最后就变成只有最后那行启动代码真正生效了。

这个地方被调用的外设是同一个UART,即使使用DMA模式,也得等前次执行完再来执行后面的。我们可以查询外设状态量、或者基于DMA产生的完成事件作为结束标标,或者简单粗暴点来个合适的阻塞式延时。要不就别使用DMA或中断方式,而使用阻塞式的查询操作。我这里为了演示,就简单点,直接加点延时。[注:其实ST官方库代码里的外设状态量就有避免出现类似问题的作用。但有时我们的代码又未必完全按照库的逻辑走,会手动调整状态量,或者说对库里那些状态量的功能也未必很清楚。]

image.png

类似问题,感觉遇到的人较多。之前聊过这个话题,这里再次提醒下。

三、如何避免UART基于DMA方式接收时出现数据错位?

有人在使用STM32G4系列和STM32H5系列的UART,并开启DMA方式的接收,DMA工作在Normal模式。下图是UART接收出错的情形。 image.png

如上图所示,在基于UART DMA方式做定长接收时,由于实际发送的数据个数因为各种因素可能大于接收定长,出现接收数据错位的情形,而且总是第一个数据是错的,导致整串数据错位无法使用。【注:这里都是基于HAL库函数和CubeMx的配置而言的。】

要解决这个问题的方法不是唯一的,具体跟你是否开启UART出错监视、是否使能UART中断等。这里有个简单稍显粗暴、适用性较强的可靠方法,就是每次启动UART的DMA接收之前将UART初始化代码再运行一次。

比方像下面这样:

image.png

上面的RxCplt标志和DlyCnt变量在DMA接收完成中断里被赋值,DlyCnt延时一段时间确保每次通信结束后再开启下次接收。

这个做法我在STM32G4和STM32H5的芯片上都测试过,可靠有效。至于其它办法需结合具体芯片和特定配置来看,在此不做延伸。

OK,今天的STM32应用分享就到这里,供君参考。

收藏 评论0 发布时间:2025-10-24 13:51

举报

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