问题描述
DMA连续发送多帧但是只有最后一帧数据发出
原因分析
DMA发送未完成时,下次DMA请求启动,导致之前的数据被放弃传输了
解决办法
创建DMA发送缓冲区,当启动DMA请求的时候,检测DMA设备是不是正在忙,如果正在忙,就把数据放入发送缓冲区等待,上次DMA发送完成的时会产生DMA发送完成中断,在发送完成中断处理函数中再次发起DMA传输任务,直到发送缓冲区清空。
1单片机串口发送数据的一般处理方法
单片机串口有一个发送数据缓冲区,当上一个字节被发送完成时下一个字节进入发送缓冲区。但如果在上一个字节被发送完毕前,下一个字节进入发送缓冲区,则上一个字节会被覆盖掉。因此CPU必须在上一个字节发送完毕之后发送下一个字节。针对这一问题,开发者们一般在串口发送数据时,不断循环查询串口发送中断标志位,当一个字节的数据正在发送过程中时等待,直到当前字节数据发送完毕再发送下一个字节。如此循环,直到整个通信协议发送完成。
以波特率9600为例,一帧数据包括:1个起始位:8个数据位;1个停止位;总共10bits;现有12bytes数据要发送因此有1210bits发送;1bit的发送时间=1000/9600(bit/ms),发送12bytes数据的时间=1210*(1000/9600)ms=12.5ms;STM32系列芯片(Cortex-M3)有三级流水线,指令周期不定,ARM给出的Cortex-M3核单片机的平均执行速度是1.25MIPS/Mhz。MIPS(MillionInstructionsPerSecond,每秒百万指令),Mhz(兆赫兹,是指单片机CPU的主频)。采用72Mhz系统时钟,则为1.2572=90MIPS,可见仅仅是等待串口发送12bytes数据,将浪费9012.5/1000=1.125,即浪费百万条指令执行时间。
这种串口数据发送方式,解决了单片机处理串口多字节通信的问题,是初学者常用的处理方式。但是相对于CPU,串口是速度非常慢的外设,尤其是串口波特率较低的时候,需要CPU浪费大量的时间在查询串口发送中断标志位上。单片机串口自带的数据缓冲区只能按字节处理串口数据,而单片机串口通信协议一般由多个字节组成,且通信频率较高,要求对数据处理速度快,这种处理方式在系统能够系统实时性要求高时无法满足要求。
2采用DMA技术串口发送数据的一般方法
使用DMA技术后,开发者们可以将要发送的数组的首地址、数组长度赋值给相应的DMA通道,并且使能一次DMA传输。DMA发送数据的过程中,CPU不再参与,从而大量节省了CPU占用时间。
但是在实际应用中可能会出现虽然用了DMA,但仍不能把CPU空闲出来做其它事情,因为后面的语句影响串口正在发送的数据,所以必须等到DMA发送完成以后,再执行后面的语句。例如需要连续发送多条数据的情况,此时还要使用DMA技术就需要判断上次的数据传输有没有发送完成,也就是要CPU查询DMA中断标志位并等待,否则在上次传输的数据还没有发送完成时,开启下一次DMA传输会导致上次的传输数据丢失。这样做其实没有发挥出DMA最大的优势,把CPU空闲出来做其他事情。所以这样使用DMA不会在发送数据的效率方面有所提高,因为在串口发送数据的时候,CPU不能做其他事情。
if(t==KEY0_PRES)//KEY0按下
{
LCD_ShowString(30,150,200,16,16,"Start Transimit....");
LCD_ShowString(30,170,200,16,16," %");//显示百分号
printf("\r\nDMA DATA:\r\n");
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送
MYDMA_Enable(DMA1_Channel4);//开始一次DMA传输!
//等待DMA传输完成,此时我们来做另外一些事,点灯
//实际应用中,传输数据期间,可以执行另外的任务
while(1)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET) //判断通道4传输完成
{
DMA_ClearFlag(DMA1_FLAG_TC4);//清除通道4传输完成标志
break;
}
pro=DMA_GetCurrDataCounter(DMA1_Channel4);//得到当前还剩余多少个数据
pro=1-pro/SEND_BUF_SIZE;//得到百分比
pro*=100; //扩大100倍
LCD_ShowNum(30,170,pro,3,16);
}
LCD_ShowNum(30,170,100,3,16);//显示100%
LCD_ShowString(30,150,200,16,16,"Transimit Finished!");//提示传送完成
}
3采用DMA技术串口发送数据改进后的方法
为解决上述问题,本文在采用DMA技术缓存数组的方法基础上进行了改进,规定了循环存储的方式缓存串口发送数据。
3.1方法解析
适用于循环存储发送的串口发送方法,包括以下四个要素:循环数组、DMA状态标志、读写指针、容量计算。定义一个固定长度的数组用于循环存储要发送的数据,数组大小可自定义满足使用要求即可。
初始状态时,读写指针都指向循环数组首地址。需要发送数据时向数组内写入数据同时移动写指针。此时读指针和写指针不相等,开启一次DMA传输,并且将DMA状态标志置位,表示DMA传输正在进行。在DMA传输正在进行的过程中,如果有发送数据的需求,只需要将需要发送的数据循环写入环形存储区域即可。
但同时需要判断环形存储区域的剩余容量,如果剩余容量不足放入本次要发送的数据,则不能放入本次要发送的数据,否则会造成数据错误。
一次传输完成后DMA产生传输完成中断,在中断服务函数中判断是否读写指针相等,若相等就清除DMA正在进行的标志位等待下一次开启DMA传输,若不相等就直接开启下一次DMA传输。环形DMA串口发送数据方法流程如下图1。
图1环形DMA串口发送数据方法流程图
3.2循环存储
首先,定义一个固定长度的数组,该数组的长度应能保证可存储多条通信协议。以本文为例,设置缓存数组长度为256bytes。定义读指针tr和写指针tw,其中读指针tr指向DMA准备发送的字节在数组中的地址,写指针tw指向最新准备发送的数据在数组中的地址。当开始一次DMA传输时,发送数据的首地址为读指针tr,发送数据的长度为读写指针tr和tw之间的数据长度。发送完当前数据后,读指针tr递增至原写指针tw的位置,并检查有没有新数据,即检查tw是否改变。当指针地址偏移量达到256bytes,将相应指针重新指向数组首地址。示意图如图2所示。
图2循环缓存示意图
指针tw和tr在本文方法中用来表示串口数据存储和发送的进度,定义len表示一次DMA发送的数据大小,剩余空间即(256-len)bytes。