stm32这款强大的单片机,有更多的用法可以实现高效的串口收发,本节我们就介绍使用DMA传输串口数据。
先简单介绍一下DMA,DMA全称为:Direct Memory Access,即直接存储器访问。它可以独立地将数据从一个地址空间复制到另外一个地址空间,而不占用CPU的资源。
DMA尤其在高速、大容量的数据传输时特别有用:如果使用中断传输,CPU会在传输数据的一段时间内多次进入中断处理,也会占用很多CPU的时间;而使用DMA,CPU只用作一些初始化操作,之后DMA会自动将数据从一个地址复制到另一个地址,数据量很大时,能减轻很多CPU的工作。
首先还是在cubemx中生成代码,选择器件、设置SYS(调试接口)、设置RCC(外部晶振时钟源)。
然后设置串口引脚,选择uart1,异步串口,选完后,已经使用的串口引脚PA9和PA10会变成绿色;然后选择开启串口全局中断(使用DMA时中断一定要打开):
然后,设置DMA,在DMA选项卡下面,添加RX和TX,然后将RX的mode改成Circular,
即设置为循环接收:(其他参数,可以选择地址自增、数据宽度等等,这里都默认)
之后,在时钟选项卡设置主时钟为72M;在project Manager选项卡设置工程名和路径,生成工程代码。
生成的工程中,已经有比较完善的初始化代码。
发送时,可以直接调用HAL_UART_Transmit_DMA函数实现,如下图:
直接使用DMA发送了16个字节的数据,这里我们看一下效果,在发送之后直接设置断点。我们前面讲的中断发送,如果在发送函数之后直接断点停止的话,一般只能发出两个字节的数出来,后面的要等程序跑起来,进中断处理后才能发出来。而DMA发送,可以看到,它是不受断点影响的,即使CPU被断点中断,数据仍然可以都发出来,图中看以看到16个数据都发送完了。
接收时,可以调用HAL_UART_Receive_DMA函数,如下图:
这里我们可以在断点停止时,用串口调试助手发送20个字节数据,当再次运行时,可以看到rx_data里的数据发生了变化,说明DMA在CPU未运行时也在收数据。
另外,还可以看到,因为设置了循环收16个字节,但是发送了20个字节,所以16个字节之后的4个字节又覆盖了头4个字节:
3)接收程序改写
HAL库生成的DMA的接收/发送函数,与中断的函数一样,有以下特点:发送时如果上一次的数据还未发完,则本次数据不会发送,仍然继续发送上一次未发完的数据。接收数据要提前设定长度,未达到长度返回,不方便使用。
好在发送的时间点是可以由软件控制的,所以发送时的矛盾并不特别突出。而接收不行,接收时程序并不知道外界什么时候会发数过来,也不知道每次发多少个,所以一般会改写接收的函数,使用空闲中断结合DMA来接收数据,具体做法是:
打开串口的空闲中断;打开DMA接收,接收长度设为一个较大的值,保证不会被填满;当空闲中断产生时,说明一段数据已收完,此时把数据拷贝走,并再次开启DMA接收。
由于每收完一段数据都会产生空闲,在空闲时能产生中断进行处理,所以可以实现不定长度的数据接收。
代码实现如下:
首先添加初始化函数,开启接收中断、空闲中断,打开DMA接收(这里接收数组设置为32字节,一般建议设置大一点,确保不会溢出):
在中断服务函数中添加如下:
这部分代码的作用是:检查是否是空闲中断,如果是,则获取已经接收的字节数,并置标志位;然后再次打开接收中断、开启DMA接收。
这里注意一下长度的获取,是用设置的最大长度减去未填充的空间得到的。
主循环中,只要检测标志位,然后将已收到的数据处理完即可(这里是将数据发回)。
运行后用串口调试助手发数和接收,可以看到与预期一致: