引言
- 常规串口接收数据使用的是串口接收中断,即串口每接收一个字符就进入中断,接收数据需要我们在中断里处理实现,然而大部分的场景是串口需要接收大量数据,频繁进入中断会导致程序时间开销大,cpu也需要花费大量算力取处理中断业务,所以一般都使用dma。
方法:
(空闲中断+DMA接收)
- 从系统结构可以看出,dma 有独立的总线连接外设abp总线与内部sarm 在传输数据的时候可以不由cpu处理。
- 传输动作本身是由 DMA 控制器来实现和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。这样的操作并没有让处理器参与处理,CPU可以干其他事情,当DMA传输完成的时候产生一个中断,告诉CPU我已经完成了,然后CPU知道了就可以去处理数据了,这样子提高了CPU的利用率。
STM32F1系列的MCU有两个DMA控制器(DMA2只存在于大容量产品中),DMA1有7个通道,DMA2有5个通道,每个通道专门用来管理来自于一个或者多个外设对存储器的访问请求。还有一个仲裁器来协调各个DMA请求的优先权。
串口配置成空闲中断,当数据传输的时候,总线慢,串口并不会产生中断。接收完后,总线会空闲,寄存器idle位就会被置1,产生中断事件。
实现思路:
- 采用串口1,并配置成空闲中断模式且使能DMA接收,并同时设置接收缓冲区和初始化DMA。
- 那么初始化完成之后,假设这帧数据长度是100个字节,那么在单片机接收到一个字节的时候(RXNE位被置位)并不会产生串口中断,而是DMA在后台把数据默默地搬运到你指定的缓冲区里面。当整帧数据发送完毕之后(接收停顿超过一字节时间)串口才会产生一次中断,此时可以利用DMA_GetCurrDataCounter();函数计算出本次的数据接受长度,从而进行数据处理。
- 就是在串口空闲中断中使用
Usart1RecLen = DMARecLen-DMA_GetCurrDataCounter(DMA1_Channel5); //算出接本帧数据长度 = DMA缓存的大小 - 剩余 DMA缓存大小
注意事项
串口接收完数据是要处理的,那么处理的步骤是怎么样呢?
-
暂时关闭串口接收DMA通道,有两个原因:1.防止后面又有数据接收到,产生干扰,因为此时的数据还未处理。2.DMA需要重新配置。
-
清DMA标志位。
-
从DMA寄存器中获取接收到的数据字节数(可有可无)。
-
重新设置DMA下次要接收的数据字节数,注意,数据传输数量范围为0至65535。这个寄存器只能在通道不工作(DMA_CCRx的EN=0)时写入。通道开启后该寄存器变为只读,指示剩余的待传输字节数目。寄存器内容在每次DMA传输后递减。数据传输结束后,寄存器的内容或者变为0;或者当该通道配置为自动重加载模式时,寄存器的内容将被自动重新加载为之前配置时的数值。当寄存器的内容为0时,无论通道是否开启,都不会发生任何数据传输。
-
给出信号量,发送接收到新数据标志,供前台程序查询。
-
开启DMA通道,等待下一次的数据接收,注意,对DMA的相关寄存器配置写入,如重置DMA接收数据长度,必须要在关闭DMA的条件进行,否则操作无效。
-
STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,产生的条件是这样的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一断接收的数据断流,没有接收到数据,即产生IDLE中断。如果中断发送数据帧的速率很快,MCU来不及处理此次接收到的数据,中断又发来数据的话,这里不能开启,否则数据会被覆盖。有两种方式解决:
-
- 在重新开启接收DMA通道之前,将Rx_Buf缓冲区里面的数据复制到另外一个数组中,然后再开启DMA,然后马上处理复制出来的数据。
-
- 建立双缓冲,重新配置DMA_MemoryBaseAddr的缓冲区地址,那么下次接收到的数据就会保存到新的缓冲区中,不至于被覆盖。