使用定时器触发 DMA 发送串口数据,解决了串口不支持 DMA 的问题。本驱动适用于 GD32F303 系列,…
背景
有些时候串口不支持 DMA,就比如 GD32F303RCT6,该处理器有 5 个串口,分别是 UART0 ~ UART4,其中 UART0 ~ UART3 支持 DMA,但 UART4 并不支持 DMA。使用 DMA 做数据搬运确实方便,如果我们想让用 DMA 发送或接收 UART4 数据,怎么办?这时候可以去参看手册查看 DMA1 各通道请求表,可以看到 TIMERx_UP,这就是定时器更新通道,我们可以用定时器的更新事件以一定的周期触发 DMA 传输。
外设 | Channel0 | Channel1 | Channel2 | Channel3 | Channel4 |
TIMER4 | TIMER4_CH3 TIMER4_TG | TIMER4_CH2 TIMER4_UP | – | TIMER4_CH1 | TIMER4_CH0 |
TIMER5 | – | – | TIMER5_UP | – | – |
TIMER6 | – | – | – | TIMER6_UP | – |
TIMER7 | TIMER7_CH2 TIMER7_UP | TIMER7_CH3 TIMER7_TG TIMER7_CMT | TIMER7_CH0 | – | TIMER7_CH1 |
ADC2 | – | – | – | – | ADC2 |
DAC | – | – | DAC_CH0 | DAC_CH1 | – |
SPI/I2S | SPI/I2S2_RX | SPI/I2S_TX | – | – | – |
USART | – | – | UART3_RX | – | UART3_TX |
SDIO | – | – | – | SDIO | – |
那么如何使用定时器触发 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); //禁用内存到内存