RT-Thread 串口DMA发送代码解析

## RT-Thread 串口DMA发送代码解析
说明:该代码为基于现有的stm32例程对fm33lc02单片机进行的修改之后的代码。
#### 初始化代码
设备初始化,在fm33_uart_get_dma_config()函数里面对串口的dma参数进行了初始化。
```c
int rt_hw_usart_init(void)
{
    rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct _uart);
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
    rt_err_t result = 0;


fm33_uart_get_dma_config();


    for (int i = 0; i < obj_num; i++)
    {
        /* init UART object */
        uart_obj[i].config = &uart_config[i];
        uart_obj[i].serial.ops    = &_uart_ops;
        uart_obj[i].serial.config = config;


        /* register UART device */
        result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
                                       RT_DEVICE_FLAG_RDWR
                                       | RT_DEVICE_FLAG_INT_RX
                                       | RT_DEVICE_FLAG_INT_TX
                                       | uart_obj[i].uart_dma_flag
                                       , NULL);
        RT_ASSERT(result == RT_EOK);
    }


    return result;
}
```


```c
static void fm33_uart_get_dma_config(void)
{
#ifdef BSP_USING_UART0
    uart_obj[UART0_INDEX].uart_dma_flag = 0;
#ifdef BSP_UART0_RX_USING_DMA
    uart_obj[UART2_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_RX;
#endif
#ifdef BSP_UART0_TX_USING_DMA
    uart_obj[UART0_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_TX;
#endif
#endif
#ifdef BSP_USING_UART1
    uart_obj[UART1_INDEX].uart_dma_flag = 0;
#ifdef BSP_UART1_RX_USING_DMA
    uart_obj[UART1_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_RX;
#endif
#ifdef BSP_UART1_TX_USING_DMA
    uart_obj[UART1_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_TX;
#endif
#endif


#ifdef BSP_USING_UART4
    uart_obj[UART4_INDEX].uart_dma_flag = 0;
#ifdef BSP_UART4_RX_USING_DMA
    uart_obj[UART4_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_RX;
#endif
#ifdef BSP_UART4_TX_USING_DMA
    uart_obj[UART4_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_TX;
#endif
#endif


#ifdef BSP_USING_UART5
    uart_obj[UART5_INDEX].uart_dma_flag = 0;
#ifdef BSP_UART5_RX_USING_DMA
    uart_obj[UART5_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_RX;
#endif
#ifdef BSP_UART5_TX_USING_DMA
    uart_obj[UART5_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_TX;
#endif
#endif
}
```
#### 控制函数
该函数通过fm33_dma_config对dma外设进行初始化。
```c
static rt_err_t uart_control(struct rt_serial_device *serial, int cmd, void *arg)
{
    struct _uart *uart;
#ifdef RT_SERIAL_USING_DMA
    rt_ubase_t ctrl_arg = (rt_ubase_t)arg;
#endif


    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct _uart, serial);


    switch (cmd)
    {
    /* disable interrupt */
    case RT_DEVICE_CTRL_CLR_INT:
        /* disable rx irq */
        NVIC_DisableIRQ(uart->config->irq_type);
        /* disable interrupt */
      FL_UART_DisableIT_RXBuffFull(uart->config->InitTypeDef);


        break;


    /* enable interrupt */
    case RT_DEVICE_CTRL_SET_INT:
        /* enable rx irq */
        NVIC_SetPriority(uart->config->irq_type, 1); 
        NVIC_EnableIRQ(uart->config->irq_type);
        /* enable interrupt */
      FL_UART_EnableIT_RXBuffFull(uart->config->InitTypeDef);
        break;


#ifdef RT_SERIAL_USING_DMA
    case RT_DEVICE_CTRL_CONFIG:
        fm33_dma_config(serial, ctrl_arg);
        break;
#endif


    case RT_DEVICE_CTRL_CLOSE:
        if (FL_UART_DeInit(uart->config->InitTypeDef) != FL_PASS )
        {
            RT_ASSERT(0)
        }
        break;


    }
    return RT_EOK;
}
```
dma初始化代码,该部分只实现了tx部分的dma传输。
```c
void fm33_dma_config(struct rt_serial_device *serial,rt_ubase_t ctrl_arg)
{
    FL_DMA_InitTypeDef  *DMA_Handle;
    struct dma_config *dma_config;
    struct _uart *uart;


    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct _uart, serial);


    if (RT_DEVICE_FLAG_DMA_RX == ctrl_arg)
    {
        DMA_Handle = &uart->dma_rx.handle;
        dma_config = &uart->config->dma_rx;
    }
    if (RT_DEVICE_FLAG_DMA_TX == ctrl_arg)
    {
        DMA_Handle = &uart->dma_tx.handle;
        dma_config = &uart->config->dma_tx;
    }
    LOG_D("%s dma config start", uart->config->name);


    /* enable DMA clock && Delay after an RCC peripheral clock enabling*/


    if (RT_DEVICE_FLAG_DMA_RX == ctrl_arg)
    {
        DMA_Handle->direction                = FL_DMA_DIR_PERIPHERAL_TO_RAM;
    }
    if (RT_DEVICE_FLAG_DMA_TX == ctrl_arg)
    {
        DMA_Handle->direction                = FL_DMA_DIR_RAM_TO_PERIPHERAL;
    }
if(uart->config->irq_type == UART0_IRQn)
{
if (RT_DEVICE_FLAG_DMA_RX == ctrl_arg)
{
dma_config->channel = FL_DMA_CHANNEL_1;
DMA_Handle->periphAddress = FL_DMA_PERIPHERAL_FUNCTION1;
}
if (RT_DEVICE_FLAG_DMA_TX == ctrl_arg)
{
dma_config->channel = FL_DMA_CHANNEL_2;
DMA_Handle->periphAddress = FL_DMA_PERIPHERAL_FUNCTION2;
}
}
if(uart->config->irq_type == UART1_IRQn)
{
if (RT_DEVICE_FLAG_DMA_RX == ctrl_arg)
{
dma_config->channel = FL_DMA_CHANNEL_3;
DMA_Handle->periphAddress = FL_DMA_PERIPHERAL_FUNCTION3;
}
if (RT_DEVICE_FLAG_DMA_TX == ctrl_arg)
{
dma_config->channel = FL_DMA_CHANNEL_4;
DMA_Handle->periphAddress = FL_DMA_PERIPHERAL_FUNCTION5;
}
}
if(uart->config->irq_type == UART4_IRQn)
{
if (RT_DEVICE_FLAG_DMA_RX == ctrl_arg)
{
dma_config->channel = FL_DMA_CHANNEL_2;
DMA_Handle->periphAddress = FL_DMA_PERIPHERAL_FUNCTION3;
}
if (RT_DEVICE_FLAG_DMA_TX == ctrl_arg)
{
dma_config->channel = FL_DMA_CHANNEL_3;
DMA_Handle->periphAddress = FL_DMA_PERIPHERAL_FUNCTION4;
}
}
if(uart->config->irq_type == UART5_IRQn)
{
if (RT_DEVICE_FLAG_DMA_RX == ctrl_arg)
{
dma_config->channel = FL_DMA_CHANNEL_4;
DMA_Handle->periphAddress = FL_DMA_PERIPHERAL_FUNCTION6;
}
if (RT_DEVICE_FLAG_DMA_TX == ctrl_arg)
{
dma_config->channel = FL_DMA_CHANNEL_6;
DMA_Handle->periphAddress = FL_DMA_PERIPHERAL_FUNCTION4;
}
}
DMA_Handle->memoryAddressIncMode = FL_DMA_MEMORY_INC_MODE_INCREASE;
    DMA_Handle->flashAddressIncMode = FL_DMA_CH7_FLASH_INC_MODE_INCREASE;
    DMA_Handle->dataSize = FL_DMA_BANDWIDTH_8B;
    DMA_Handle->priority = FL_DMA_PRIORITY_HIGH;
    DMA_Handle->circMode = FL_DISABLE;


FL_DMA_Init(DMA,DMA_Handle,dma_config->channel);


if (ctrl_arg == RT_DEVICE_FLAG_DMA_TX)
    {
FL_DMA_DisableIT_TransferComplete(DMA,dma_config->channel);
FL_DMA_DisableIT_TransferHalfComplete(DMA,dma_config->channel);
FL_UART_SetTXIFMode(uart->config->InitTypeDef,FL_UART_TXIF_MODE_AFTER_DMA);
}


    LOG_D("%s dma %s instance: %x", uart->config->name, flag == RT_DEVICE_FLAG_DMA_RX ? "RX" : "TX", DMA_Handle->Instance);
    LOG_D("%s dma config done", uart->config->name);
}
```
#### 数据发送
在数据发送的时候通过调用下面函数进行数据发送。在函数中引用了dataqueue数据队列的函数(介绍见:https://blog.csdn.net/whj123999/article/details/114175258),在函数中先将数据写入到数据队列中,之后将数据进行填充发送。


```c
rt_inline int _serial_dma_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{
    rt_base_t level;
    rt_err_t result;
    struct rt_serial_tx_dma *tx_dma;


    tx_dma = (struct rt_serial_tx_dma*)(serial->serial_tx);
//将数据写入消息队列
    result = rt_data_queue_push(&(tx_dma->data_queue), data, length, RT_WAITING_FOREVER);  
    if (result == RT_EOK)
    {
        level = rt_hw_interrupt_disable();
        //判断串口是否正在进行收发
        if (tx_dma->activated != RT_TRUE)
        {
        //串口没有进行发送操作。
            tx_dma->activated = RT_TRUE;  //切换到发送操作中
            rt_hw_interrupt_enable(level);
//数据发送
            /* make a DMA transfer */
            serial->ops->dma_transmit(serial, (rt_uint8_t *)data, length, RT_SERIAL_DMA_TX);
        }
        else
        {
        //数据正在发送中
            rt_hw_interrupt_enable(level);
        }


        return length;
    }
    else
    {
        rt_set_errno(result);
        return 0;
    }
}
```
当一次数据发送完成之后会调用回调函数,回调函数中会识别数据队列是否还有数据,有的话,会自动进行发送。
```c
void rt_hw_serial_isr(struct rt_serial_device *serial, int event)
{
    switch (event & 0xff)
    {
        case RT_SERIAL_EVENT_RX_IND:
        {
            int ch = -1;
            rt_base_t level;
            struct rt_serial_rx_fifo* rx_fifo;


            /* interrupt mode receive */
            rx_fifo = (struct rt_serial_rx_fifo*)serial->serial_rx;
            RT_ASSERT(rx_fifo != RT_NULL);


            while (1)
            {
                ch = serial->ops->getc(serial);
                if (ch == -1) break;




                /* disable interrupt */
                level = rt_hw_interrupt_disable();


                rx_fifo->buffer[rx_fifo->put_index] = ch;
                rx_fifo->put_index += 1;
                if (rx_fifo->put_index >= serial->config.bufsz) rx_fifo->put_index = 0;


                /* if the next position is read index, discard this 'read char' */
                if (rx_fifo->put_index == rx_fifo->get_index)
                {
                    rx_fifo->get_index += 1;
                    rx_fifo->is_full = RT_TRUE;
                    if (rx_fifo->get_index >= serial->config.bufsz) rx_fifo->get_index = 0;


                    _serial_check_buffer_size();
                }


                /* enable interrupt */
                rt_hw_interrupt_enable(level);
            }


            /* invoke callback */
            if (serial->parent.rx_indicate != RT_NULL)
            {
                rt_size_t rx_length;


                /* get rx length */
                level = rt_hw_interrupt_disable();
                rx_length = (rx_fifo->put_index >= rx_fifo->get_index)? (rx_fifo->put_index - rx_fifo->get_index):
                    (serial->config.bufsz - (rx_fifo->get_index - rx_fifo->put_index));
                rt_hw_interrupt_enable(level);


                if (rx_length)
                {
                    serial->parent.rx_indicate(&serial->parent, rx_length);
                }
            }
            break;
        }
        case RT_SERIAL_EVENT_TX_DONE:
        {
            struct rt_serial_tx_fifo* tx_fifo;


            tx_fifo = (struct rt_serial_tx_fifo*)serial->serial_tx;
            rt_completion_done(&(tx_fifo->completion));
            break;
        }
#ifdef RT_SERIAL_USING_DMA
//dma数据发送完成
        case RT_SERIAL_EVENT_TX_DMADONE:
        {
            const void *data_ptr;
            rt_size_t data_size;
            const void *last_data_ptr;
            struct rt_serial_tx_dma *tx_dma;


            tx_dma = (struct rt_serial_tx_dma*) serial->serial_tx;
//取出当前发送完成的数据
            rt_data_queue_pop(&(tx_dma->data_queue), &last_data_ptr, &data_size, 0);
            //判断消息队列中是否还有未发送完成的数据
            if (rt_data_queue_peak(&(tx_dma->data_queue), &data_ptr, &data_size) == RT_EOK)
            {
            //有数据未发送完成,继续发送数据
                /* transmit next data node */
                tx_dma->activated = RT_TRUE;
                serial->ops->dma_transmit(serial, (rt_uint8_t *)data_ptr, data_size, RT_SERIAL_DMA_TX);
            }
            else
            {
            //将外设改为空闲状态
                tx_dma->activated = RT_FALSE;
            }


            /* invoke callback */
            if (serial->parent.tx_complete != RT_NULL)
            {
                serial->parent.tx_complete(&serial->parent, (void*)last_data_ptr);
            }
            break;
        }
        case RT_SERIAL_EVENT_RX_DMADONE:
        {
            int length;
            rt_base_t level;


            /* get DMA rx length */
            length = (event & (~0xff)) >> 8;


            if (serial->config.bufsz == 0)
            {
                struct rt_serial_rx_dma* rx_dma;


                rx_dma = (struct rt_serial_rx_dma*) serial->serial_rx;
                RT_ASSERT(rx_dma != RT_NULL);


                RT_ASSERT(serial->parent.rx_indicate != RT_NULL);
                serial->parent.rx_indicate(&(serial->parent), length);
                rx_dma->activated = RT_FALSE;
            }
            else
            {
                /* disable interrupt */
                level = rt_hw_interrupt_disable();
                /* update fifo put index */
                rt_dma_recv_update_put_index(serial, length);
                /* calculate received total length */
                length = rt_dma_calc_recved_len(serial);
                /* enable interrupt */
                rt_hw_interrupt_enable(level);
                /* invoke callback */
                if (serial->parent.rx_indicate != RT_NULL)
                {
                    serial->parent.rx_indicate(&(serial->parent), length);
                }
            }
            break;
        }
#endif /* RT_SERIAL_USING_DMA */
    }
}
```
声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 1
收藏 2
关注 9
成为作者 赚取收益
全部留言
0/200
成为第一个和作者交流的人吧