这里分析一下RT-Thread中串口DMA方式的实现,以供做新处理器串口支持时的参考。
背景
在如今的芯片性能和外设强大功能的情况下,串口不实现DMA/中断方式操作,我认为在实际项目中基本是不可接受的,但遗憾的是,rt-thread现有支持的实现中,基本上没有支持串口的DMA,文档也没有关于串口DMA支持相关的说明,这里以STM32实现为背景,梳理一下串口DMA的实现流程,以供新处理器实现时以作参考。
DMA接收准备
启用DMA接收,需要在打开设备的时候做一些处理,入口函数为rt_device_open()。主体实现是:
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag) { ...... result = device_init(dev); ...... result = device_open(dev, oflag); ...... }
device_init()就是rt_serial_init()函数,其主要是调用configure()函数,
static rt_err_t rt_serial_init(struct rt_device *dev) { ...... if (serial->ops->configure) result = serial->ops->configure(serial, &serial->config); ...... }
在stm32下,其configure()函数是stm32_configure(),其根据设备打开参数,配置STM32外设的寄存器。包括波特率、校验等串口工作参数。
device_open()函数就是rt_serial_open()函数,其主要实现是:
static rt_err_t rt_serial_open(struct rt_device *dev, rt_uint16_t oflag) { ...... #ifdef RT_SERIAL_USING_DMA else if (oflag & RT_DEVICE_FLAG_DMA_RX) { if (serial->config.bufsz == 0) { struct rt_serial_rx_dma* rx_dma; rx_dma = (struct rt_serial_rx_dma*) rt_malloc (sizeof(struct rt_serial_rx_dma)); RT_ASSERT(rx_dma != RT_NULL); rx_dma->activated = RT_FALSE; serial->serial_rx = rx_dma; } else { struct rt_serial_rx_fifo* rx_fifo; rx_fifo = (struct rt_serial_rx_fifo*) rt_malloc (sizeof(struct rt_serial_rx_fifo) + serial->config.bufsz); RT_ASSERT(rx_fifo != RT_NULL); rx_fifo->buffer = (rt_uint8_t*) (rx_fifo + 1); rt_memset(rx_fifo->buffer, 0, serial->config.bufsz); rx_fifo->put_index = 0; rx_fifo->get_index = 0; rx_fifo->is_full = RT_FALSE; serial->serial_rx = rx_fifo; /* configure fifo address and length to low level device */ serial->ops->control(serial, RT_DEVICE_CTRL_CONFIG, (void *) RT_DEVICE_FLAG_DMA_RX); } dev->open_flag |= RT_DEVICE_FLAG_DMA_RX; } #endif /* RT_SERIAL_USING_DMA */ ...... #ifdef RT_SERIAL_USING_DMA else if (oflag & RT_DEVICE_FLAG_DMA_TX) { struct rt_serial_tx_dma* tx_dma; tx_dma = (struct rt_serial_tx_dma*) rt_malloc (sizeof(struct rt_serial_tx_dma)); RT_ASSERT(tx_dma != RT_NULL); tx_dma->activated = RT_FALSE; rt_data_queue_init(&(tx_dma->data_queue), 8, 4, RT_NULL); serial->serial_tx