基于stm32f407
文章目录
- 一、利用DMA接受串口任意长数据
- 1、简介
- 2、循环模式和普通模式
- 二、DMA接受数据错位问题
- 1、程序设置
- 2、数据缓冲错位问题
- (1)问题描述:
- (2)调试过程
- (3)解决方案
- (4)后记
一、利用DMA接受串口任意长数据
1、简介
- 有时候,我们希望利用串口在设备间进行高速而稳定的数据通信,于是定义了一些通讯协议,包括帧头、功能码、数据码、校验码等等,DMA非常适合此类需求。利用串口空闲中断+DMA的方法,我们可以快速地读取整帧数据进行分析。
- 在使用串口空闲中断+DMA的方法接受数据时,其流程如下
- 初始化DMA & USART
- 主机开始发送,在从机USART->DR寄存器收到数据后,DMA立刻将数据移至指定的存储buf中(此过程不需要cpu)
- 主机一帧数据发送完成后,串口暂时空闲,触发串口空闲中断。在这里可以计算收到数据的字节数,也可以对数据帧进行解码等操作。
- 清除标志位,开始下一帧接收
具体可以参考我以前的这篇文章:stm32 利用DMA+串口空闲中断接受任意长数据
2、循环模式和普通模式
在上面的文章中有一点没有讲清楚,就是DMA的循环模式(DMA_Mode_Circular)和普通模式(DMA_Mode_Normal)
-
DMA_Mode_Normal
:在普通模式下,传输结束后(即传输计数DMA1_Streamx->NDTR
变为0)将不再产生DMA操作。要开始新的DMA传输,需要3个步骤:①关闭DMA通道,②在DMA_CNDTRx寄存器中重新写入传输数目,③然后重新开启DMA
-
DMA_Mode_Circular
:在循环模式下,最后一次传输结束时, DMA_SxNDTR寄存器的内容会自动地被重新加载为其初始数值,内部的当前外设/存储器地址寄存器也被重新加载为初始基地址。
二、DMA接受数据错位问题
1、程序设置
- 帧长8字节
- DMA普通模式
- 不使用FIFO进行节拍发送
- 从USART3->DR寄存器向一个8字节的buf转移数据
2、数据缓冲错位问题
(1)问题描述:
发送数据部分没什么问题,之前我设置的接收缓冲buf比传输数据长度多一点,接收也没问题,但今天调DMA的时候我把二者长度设成一样了(都是8字节),于是遇到以下问题
- 发送数据“12345678”,第一次发送后,只在buf[0]位置受到一个 ‘1’
- 再次发送数据“12345678”,buf[0]~buf[7]收到 “81234567”
- 再发送其他8字节数据,如“87654321”,buf[0]~buf[7]收到 “18765432”
基本就是第一次只收到第一个数据,以后都是数据错位,最后的数据跑到第一位了
(2)调试过程
注意,DMA是不受cpu控制的,一旦设置好后就会自动搬运数据,在debug过程中它导致的赋值操作不会在断点停下,所以对DMA进行debug要特别注意
- 打开debug查看,第一次发送数据“12345678”后,DMA1_Stream1的EN位为1,表明数据流处于使能状态;NDTR值为7,表明本帧数据还有7个字节待接收,看起来没有问题。
- 进一步查看USART3->DR寄存器,其值为‘1’,说明第一个数据正常DMA正常传送,奇怪的是为何只发了一个数就进入中断了,这说明串口在发了一个数后就处于空闲状态。
- debug单步调试,发现进刚进空闲中断的时候缓冲buf其实没收到值,此时USART3->DR中已经是‘1’了,buf[0]处的‘1’是在重设接收数据长度,重新使能DMA后才收到的,这就说明第一次DMA实质没有启动
- 在
USART3_Init()
函数最后一句DMA_Transfer_Enable(DMA1_Stream1,USART3_RX_BUFFER_SIZE);
处设置断点,发现进入DMA_Transfer_Enable
函数前数据长度实质已经设好了,而且EN位也是1(流已使能);而出此函数后,EN位变成0了(流处于禁止状态) - 进入
DMA_Transfer_Enable
函数单步调试,发现失能、重设两步都正常,但是最后使能流失败,看了看使能函数,里面就一个位操作,是在不知道为啥不能使能。于是查看数据手册,发现如下内容: - 按手册查看
DMA1->LISR
和DMA->HISR
,发现在DMA_Transfer_Enable
函数中DMA_Cmd(DMA_Streamx, DISABLE)
一句执行的瞬间,DMA1->LISR
立刻变为0x800,这标志 数据流1传输完成,正因为这个标志没有清除导致使能流失败 - 为什么会出现这个置位呢,进一步查看数据手册,有如下内容:
- 这样一来就真相大白了!就是因为串口初始化后进行了多余的重新设置数据传输量长度的操作,导致数据流失能,这样一来串口DR寄存器的数据就不能转移,因此串口被阻塞,触发空闲中断。接下来在中断服务函数
USART3_IRQHandler
中清除了所有传输完成和传输错误标志,使得中断函数最后重新设置数据传输量长度时数据流可以使能成功(EN成功置位)。DMA使能后立即将DR寄存器中的 '1’转移至buf[0],同时NDTR值由8减一变为7,这就出现了第一次发送“12345678”后的情形 - 第二次点击发送时,由于NDTR值为7,还可以发送“1234567”,它们被DMA转移到buf[1]~buf[7],然后DMA传输完成。此时发送数据的最后一个字节‘8’实际上已经处于USART3->DR中了,但是由于由一轮传输已经结束,故暂时无法转移到buf中。重新设置数据传输量NDTR=8后,DMA再次打开,立即把DR中的剩余’8‘搬运到buf[0],NDTR-1=7。这就产生了第二次发送“1234567”后的情况
- 此后每次发送8字节数据的情况都和上面一样了,如果不单步debug观察,宏观上看起来数据总是错位的。
(3)解决方案
- 如果不改程序想恢复正常,只要发送长度少于8字节的数据即可,这也是为什么以前缓冲buf较大时程序没有出现异常
- 删除
USART3_Init()
函数最后一句DMA_Transfer_Enable(DMA1_Stream1,USART3_RX_BUFFER_SIZE)
即可从根本上解决此问题
(4)后记
出现这种错位,本质上还是在于对底层寄存器了解太少,又没怎么看数据手册。我以前只是简单地顾名思义把DMA_Transfer_Enable
函数当成转移使能了,还以为每次传输都要加,导致了这种错位。
所以说知其然还要知其所以然,做技术还是要踏实一点,急于求成不关注细节总会出问题,这次也算给自己提个醒吧