定时器触发 DMA 发送串口数据
定时器触发 DMA 发送串口数据

定时器触发 DMA 发送串口数据

使用定时器触发 DMA 发送串口数据,解决了串口不支持 DMA 的问题。本驱动适用于 GD32F303 系列,…

背景

有些时候串口不支持 DMA,就比如 GD32F303RCT6,该处理器有 5 个串口,分别是 UART0 ~ UART4,其中 UART0 ~ UART3 支持 DMA,但 UART4 并不支持 DMA。使用 DMA 做数据搬运确实方便,如果我们想让用 DMA 发送或接收 UART4 数据,怎么办?这时候可以去参看手册查看 DMA1 各通道请求表,可以看到 TIMERx_UP,这就是定时器更新通道,我们可以用定时器的更新事件以一定的周期触发 DMA 传输。

外设Channel0Channel1Channel2Channel3Channel4
TIMER4TIMER4_CH3
TIMER4_TG
TIMER4_CH2
TIMER4_UP
TIMER4_CH1TIMER4_CH0
TIMER5TIMER5_UP
TIMER6TIMER6_UP
TIMER7TIMER7_CH2
TIMER7_UP
TIMER7_CH3
TIMER7_TG
TIMER7_CMT
TIMER7_CH0TIMER7_CH1
ADC2ADC2
DACDAC_CH0DAC_CH1
SPI/I2SSPI/I2S2_RXSPI/I2S_TX
USARTUART3_RXUART3_TX
SDIOSDIO

那么如何使用定时器触发 DMA 实现串口发送数据呢?以 UART4 为例,如果配置使能了串口发送功能,一旦有数据写入 USART_DATA(UART4) 寄存器,那么 USRT4 外设将启动数据传输。这里的写入可以是软件写入,即在代码里直接向 USART_DATA(UART4) 寄存器写入数据,也可以是 DMA 写入。此时我们会想到使用 DMA 的内存到内存功能,这个功能常常被用于内存拷贝,配置 DMA 将一段内存里的数据写入 USART_DATA(UART4) 寄存器,不就可以实现 DMA 发送串口数据了?

这个思路是正确的,但还需要考虑一个问题,串口将一个数据发送出去是需要消耗时间的,以 115200 波特率为例,假设串口帧长度为 10 位,那么每秒钟可以传输 11520 个字节,也就是一个字节需要 86.8us 才能被发送出去。如果 DMA 拷贝的速度过快,那么就会造成数据传输出错。

此时我们可以利用定时器的更新事件来触发 DMA 传输,将 DMA 的内存地址设为需要发送的数据的首地址,目的地址(外设地址)设为串口的数据寄存器,传输方向为内存到外设,设定好传输数据量,使能 DMA 通道后即可开启自动传输。在波特率 115200 下,我们可以配置定时的周期为 100us,只要保证 DMA 的拷贝速度小于串口发送速度即可。在 GD32 中,可以通过 timer_dma_enable 来设置定时器的更新事件做为 DMA 的触发源,但必须要禁用 DMA 的内存到内存模式,如下所示。

timer_dma_enable(TIMER5, TIMER_DMA_UPD);                        //定时器更新事件作为DMA触发源
dma_memory_to_memory_disable(DMA1, DMA_CH2);                    //禁用内存到内存

源码