M-Arch(8)第七个示例:串口和DMA

前言

回顾下之前的章节:

  • 第一章节中我们描述了整个框架的核心设计思路以及主要的文件架构

  • 第二章节中我们基于一个简单的定时器OS实现了串口的数据打印,并完成了通用crc模块的设计和测试

  • 第三章节中我们给出了真随机数和伪随机数的概念和代码示例,并在架构上对接口进行了重构

  • 第四章节中我们回顾了FMC的基本知识,并给出了示例,后面我们将在设计IAP的时候再次使用到FMC

  • 第五章节中我们使用ADC和DMA搭建了一个通用的采样框架,并通过串口给出了采样的数据示例

  • 第六章节中我们总结了DAC的基本使用方法,并通过DAC生成了任意频率的正弦波,三角波和方波

  • 第七章节中我们总结下时钟的概念,并给出了获取系统中各模块的时钟频率的代码

本文我们介绍如何通过串口的DMA来实现串口数据的收发。

由于之前我们已经使用了串口1(STM中是USART1,GD中是USART0),本文中以串口2作为示例(STM中是USART2,GD中是USART1)。

串口DMA设计

前文中我们已经讲过了DMA的概念和原理:

DMA即直接存储器访问控制器,DMA提供了一种硬件的方式在外设和存储器之间或者存储器和存储器之间传输数据,而无需CPU的介入,避免了CPU多次进入中断进行大规模的数据拷贝,最终提高整体的系统性能。

简单而言,DMA相当于是外请(DMA硬件)的搬运工(数据拷贝),节约宝贵的CPU资源。

DMA一般需要配置的内容包括:

  1. IO配置(时钟)

  2. DMA参数配置(拷贝的方向,内容,地址,通道,模式和数量等)

  3. 中断(使能)配置

DMA的配置需要注意通道的匹配:

9f0f2364a65b78e979d1180c06a582ed.png fdda9b2b9b89ed6f172a72a6183764a1.png

这里我们就不赘叙,直接给出串口DMA的设计思路:

98f704890541020551e93dcbb92aff4f.png

简单一句话理解:把DMA缓存当做二级缓存!

串口发送和接收的DMA流程

串口发送和接收的DMA流程(手册中的):

05cf4cb841e8213e53833ec55de0629a.png

注意的几个点手册中有描述,我在图中也给出来了,这几个坑描述如下:

  1. DMA的几个标志位(传输完成,半传输完成等)需要软件自己清除。

  2. DMA的配置在使能的情况下不可写,需要先失能才能写。

  3. 串口的空闲中断需要先读状态寄存器,再读数据寄存器才能清!

串口基本配置代码

代码中包括:

时钟配置,GPIO配置,串口参数配置,收发使能,DMA使能,使能空闲中断

GD32代码:

void uart2_init(uint32_t baudrate)
{
    uint32_t com = USART1;

    usart_deinit(com);
    rcu_periph_clock_enable(RCU_USART1);
    rcu_periph_clock_enable(RCU_GPIOA);

    ///<  USART1_TX   PA.2
    gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_2);
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_2);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2);
    
    ///<  USART1_RX   PA.3
    gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_3);
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_3);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3);   
    
    usart_baudrate_set(com, baudrate);
    usart_word_length_set(com, USART_WL_8BIT);
    usart_stop_bit_set(com, USART_STB_1BIT);
    usart_parity_config(com, USART_PM_NONE);
    usart_hardware_flow_rts_config(com, USART_RTS_DISABLE);
    usart_hardware_flow_cts_config(com, USART_CTS_DISABLE);
    usart_enable(com);

    usart_receive_config(com, USART_RECEIVE_ENABLE);
    usart_transmit_config(com, USART_TRANSMIT_ENABLE);
    
    usart_dma_receive_config(com, USART_DENR_ENABLE);
    usart_dma_transmit_config(com, USART_DENT_ENABLE);

    usart_flag_clear(com, USART_FLAG_TC);
    
    usart_interrupt_enable(com, USART_INT_IDLE);
    nvic_irq_enable(USART1_IRQn, 3, 3);
}

STM32代码:

void uart2_init(uint32_t baudrate)
{
    USART_InitTypeDef USART_InitStructure;
    USART_TypeDef* USART = USART2;

    USART_DeInit(USART);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    ///<  USART1_TX   PA.2
    gpio_init(GPIOA, GPIO_Mode_AF_PP, GPIO_Speed_50MHz, GPIO_Pin_2);  ///<  USART2_TX   PA.2
    gpio_init(GPIOA, GPIO_Mode_IN_FLOATING, GPIO_Speed_50MHz, GPIO_Pin_3);  ///<  USART2_RX   PA.3

    USART_StructInit(&USART_InitStructure);
    USART_InitStructure.USART_BaudRate = baudrate;  ///<  波特率
    USART_Init(USART, &USART_InitStructure);    
    USART_Cmd(USART, ENABLE);  ///<  使能串口

    USART_DMACmd(USART, USART_DMAReq_Tx, ENABLE);
    USART_DMACmd(USART, USART_DMAReq_Rx, ENABLE);

    USART_ClearFlag(USART, USART_FLAG_TC);
    
    USART_ITConfig(USART, USART_IT_IDLE, ENABLE);
    nvic_irq_enable(USART2_IRQn, 3, 3);
}

串口DMA发送代码

uart2_dma_tx_init:串口发送DMA初始化

uart2_dma_send_ascii:DMA发送数据,严格按照手册中流程

【注】GD32 DMA是DMA0的通道6,STM DMA是DMA1的通道7。

GD32代码:

void uart2_dma_tx_init(uint32_t addr, uint32_t number)
{
    dma_single_data_parameter_struct dma_parameter;
    /* enable DMA1 */
    rcu_periph_clock_enable(RCU_DMA0);

    /* 发送 dm0 channel6(USART1 tx) */
    dma_deinit(DMA0, DMA_CH6);
    dma_parameter.direction = DMA_MEMORY_TO_PERIPH;
    dma_parameter.periph_addr = (uint32_t)(&USART_DATA(USART1));
    dma_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_parameter.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
    dma_parameter.memory0_addr = addr;
    dma_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_parameter.number = number;
    dma_parameter.priority = DMA_PRIORITY_ULTRA_HIGH;
    dma_parameter.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
    dma_single_data_mode_init(DMA0, DMA_CH6, &dma_parameter);
    
    /* configure DMA mode */
    dma_channel_subperipheral_select(DMA0, DMA_CH6, DMA_SUBPERI4);
}

void uart2_dma_send_ascii(uint8_t *buff, uint32_t count)
{
    usart_flag_clear(USART1, USART_FLAG_TC);

    dma_channel_disable(DMA0, DMA_CH6);
    //dma_flag_clear(DMA0, DMA_CH6, DMA_CHINTF_RESET_VALUE);
    dma_flag_clear(DMA0, DMA_CH6, DMA_FLAG_FTF);
    dma_memory_address_config(DMA0, DMA_CH6, DMA_MEMORY_0, (uint32_t)buff);
    dma_transfer_number_config(DMA0, DMA_CH6, count);
    dma_channel_enable(DMA0, DMA_CH6);
    while (usart_flag_get(USART1, USART_FLAG_TC)!=RESET);
}

void uart2_dma_send_string(char *str)
{
    uart2_dma_send_ascii((uint8_t *)str, strlen(str));
}

STM32代码:

void uart2_dma_tx_init(uint32_t addr, uint32_t number)
{
    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    DMA_DeInit(DMA1_Channel7);

    /* 发送 dma1 channel7(USART2 tx) */
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART2->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = addr;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = number;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel7, &DMA_InitStructure);

    DMA_Cmd(DMA1_Channel7, ENABLE);
}

void uart2_dma_send_ascii(uint8_t *buff, uint32_t count)
{
    USART_ClearFlag(USART2, USART_FLAG_TC);

    DMA_Cmd(DMA1_Channel7, DISABLE);
    DMA_ClearFlag(DMA1_FLAG_GL7);
    DMA1_Channel7->CMAR = (uint32_t)buff;
    DMA1_Channel7->CNDTR = count;
    DMA_Cmd(DMA1_Channel7, ENABLE);
    
    while (USART_GetFlagStatus(USART2, USART_FLAG_TC)!=RESET);
}

void uart2_dma_send_string(char *str)
{
    uart2_dma_send_ascii((uint8_t *)str, strlen(str));
}

串口DMA接收代码

uart2_dma_rx_init:串口接收DMA初始化

uart2_dma_rx_get_count:获取DMA接收数据长度,并置位DMA,在串口空闲中断中调用

【注】GD32 DMA是DMA0的通道5,STM DMA是DMA1的通道6。

GD32代码:

void uart2_dma_rx_init(uint32_t addr, uint32_t number)
{
    dma_single_data_parameter_struct dma_parameter;
    /* enable DMA1 */
    rcu_periph_clock_enable(RCU_DMA0);

    /* 接收 dm0 channel5(USART1 rx) */
    dma_deinit(DMA0, DMA_CH5);
    dma_parameter.direction = DMA_PERIPH_TO_MEMORY;
    dma_parameter.periph_addr = (uint32_t)(&USART_DATA(USART1));
    dma_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_parameter.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
    dma_parameter.memory0_addr = addr;
    dma_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_parameter.number = number;
    dma_parameter.priority = DMA_PRIORITY_ULTRA_HIGH;
    dma_parameter.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
    dma_single_data_mode_init(DMA0, DMA_CH5, &dma_parameter);

    /* configure DMA mode */
    dma_channel_subperipheral_select(DMA0, DMA_CH5, DMA_SUBPERI4);
    dma_channel_enable(DMA0, DMA_CH5);
}

uint32_t uart2_dma_rx_get_count(uint32_t dma_rx_count_max)
{
    uint32_t receive_len = dma_rx_count_max - dma_transfer_number_get(DMA0, DMA_CH5);
    if (receive_len != 0)
    {
        dma_channel_disable(DMA0, DMA_CH5);
        dma_transfer_number_config(DMA0, DMA_CH5, dma_rx_count_max);
        dma_flag_clear(DMA0, DMA_CH5, DMA_FLAG_FTF);
        dma_channel_enable(DMA0, DMA_CH5);
    }
    return receive_len;
}
void USART1_IRQHandler(void)
{
    uint8_t temp;
    if (usart_interrupt_flag_get(USART1, USART_INT_FLAG_IDLE) != RESET)
    {
        // 软件先读USART_STAT0,再读USART_DATA可清除该位。
        temp = USART_STAT0(USART1);
        temp = USART_DATA(USART1);
        temp = temp;

        process_uart2_dma_rx_data();
    }
}

STM32代码:

void uart2_dma_rx_init(uint32_t addr, uint32_t number)
{
    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    DMA_DeInit(DMA1_Channel6);

    /* 发送 dma1 channel6(USART2 rx) */
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART2->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = addr;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = number;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel6, &DMA_InitStructure);

    DMA_Cmd(DMA1_Channel6, ENABLE);
}

uint32_t uart2_dma_rx_get_count(uint32_t dma_rx_count_max)
{
    uint32_t receive_len = dma_rx_count_max - DMA_GetCurrDataCounter(DMA1_Channel6);
    if (receive_len != 0)
    {
        DMA_Cmd(DMA1_Channel6, DISABLE);
        DMA_ClearFlag(DMA1_FLAG_GL6);
        DMA_SetCurrDataCounter(DMA1_Channel6, dma_rx_count_max);
        DMA_Cmd(DMA1_Channel6, ENABLE);
    }
    return receive_len;
}
void USART2_IRQHandler(void)
{
    uint8_t temp;
    if (USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)
    {
        // 软件先读USART_SR,再读USART_DR可清除该位。
        temp = USART2->SR;
        temp = USART2->DR;
        temp = temp;

        process_uart2_dma_rx_data();
    }
}

process_uart2_dma_rx_data:处理数据主函数

void process_uart2_dma_rx_data(void)
{
    uint32_t receive_len = uart2_dma_rx_get_count(UART_DATA_DMA_RX_COUNT);
    uint32_t index;
    if (receive_len != 0)
    {
        printf("receivelen = %d\r\n", receive_len);
        for (index = 0; index < receive_len; index++)
        {
            printf("%02X ", uart_dma_rx[index]);
        }
        printf("\r\n");
    }
}

串口DMA发送例行结果展示

COM3接串口2

MCU发送数据为:Hello, My Name is Cortex M !\r\n

9bb795c263e42b61e63df59c05b69f3f.gif

串口DMA接收例行结果展示

COM9接串口1,COM3接串口2

COM3循环发数据给MCU,MCU通过串口1打印给电脑

b24f78b2988639882c367cd6dc80913b.gif

【注:STM32的串口DMA没找到串口头子,没测试】

--EOF--

例行求粉,谢谢!

7e291c131989fa0ac1472707492fa8ad.png
求粉
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值