本节是介绍如何使用CubeMX配置STM32的串口,并通过DMA方式实现串口通信,包含发送和接受数据。

    开发环境:CubeMX+MDK5.27
    芯片型号:STM32F103ZET6
    时间:2020/07/12

    DMA:全称Direct Memory Access,翻译为直接存储器访问。其作用是在外设与存储器之间、存储器与存储器之间进行高速数据传输。DMA的传输过程的初始化和启动均由CPU完成,传输过程则靠DMA控制器执行,无需CPU参与,这大大节省了CPU资源,提高了CPU的利用率。DMA最大的优点就是传输数据的过程完全不需要CPU参与。

    DMA四要素:
    1.传输源——DMA数据的来源。
    2.传输目标——DMA数据传输的目的。
    3.传输数量——DMA传输数据的数量。
    4触发信号——启动一次DMA数据传输的动作。

    STM32F103ZET6(以下简称F1)中的DMA:
    F1中具备两个DMA控制器:DMA1和DMA2。DMA1拥有七个通道,DMA2则只有五个通道。每个DMA控制器用于管理一个或多个外设的存储器访问请求,并通过总线仲裁器来协调各个DMA请求的优先级。

    DMA数据传输模式:
    1——普通模式
    传输结束后(即传输数据的数量为零时),将不再产生DMA操作,若开始新的DMA传输,需要关闭DMA通道,然后重新启动DMA传输。本节将采用普通模式传输数据。
    2——循环模式
    可用于处理环形缓冲区和连续数据流(例如ADC扫描模式)。当激活循环模式后,每轮传输结束时,要传输的数据数量将自动用设置的初始值进行加载,并继续响应DMA请求。

    实现步骤
    第一步,在CubeMX中配置串口,前面的章节配置的都是串口1,既然已经在串口1上成功通信,那么本次我们配置串口2,如果想使用串口1完成也没有问题。CubeMX配置图如下
    DMA.png
    DMA_Settings页面点击Add添加如图所示的两个通道即可,DMA Request Settings保持默认。再点击NVIC_Settings并勾选USART2_global_interrupt的enable选项。
    DMA2.png

    第二步、编写代码,在main.c的USER CODE BEGIN PV下定义下列变量

    1. uint8_t Rx_Buff[100]={0};
    2. uint8_t Rx_Count = 0;
    3. uint8_t Rx_Flag = 0;

    在USER CODE BEGIN 2下使能串口空闲中断,启动DMA接收

    1. _HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
    2. HAL_UART_Receive_DMA(&huart2, (uint8_t*)Rx_Buff,sizeof(Rx_Buff));

    在stm32f1xx_it.c的USART2_IRQHandler函数中添加对空闲中断的判断

    1. void USART2_IRQHandler(void)
    2. {
    3. /* USER CODE BEGIN USART2_IRQn 0 */
    4. /* USER CODE END USART2_IRQn 0 */
    5. HAL_UART_IRQHandler(&huart2);
    6. /* USER CODE BEGIN USART2_IRQn 1 */
    7. //空闲中断处理
    8. if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET)
    9. {
    10. extern void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart);
    11. __HAL_UART_CLEAR_IDLEFLAG(&huart2);
    12. HAL_UART_IdleCpltCallback(&huart2);
    13. }
    14. /* USER CODE END USART2_IRQn 1 */
    15. }

    此处简单介绍一下空闲中断的作用:在串口传输数据时,UART_FLAG_IDLE串口空闲标志位始终为0,当数据传输完成串口空闲标志位就为1。此时就产生了空闲中断,我们编写空闲中断回调函数,使Rx_Flag=1,接着在while(1)中判断Rx_Flag从而进行后面的步骤。

    在USER CODE BEGIN 3下添加如下代码

    1. if(Rx_Flag == 1)
    2. {
    3. Rx_Flag = 0;
    4. Rx_Count = sizeof(Rx_Buff) - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
    5. HAL_UART_Transmit_DMA(&huart2,(uint8_t*)Rx_Buff,Rx_Count);
    6. Rx_Count = 0;
    7. __HAL_DMA_DISABLE(&hdma_usart2_rx);
    8. }

    上述代码的作用:首先判断Rx_Flag的值从而判断接收是否完成,若完成,先清除Rx_Flag,再计算接收数据长度,用总长度sizeof(Rx_Buff)减去DMA数据通道中待接受的数据个数HAL_DMA_GET_COUNTER(&hdma_usart2_rx)就得到了已接收到的数据长度Rx_Count。再调用HAL_UART_Transmit_DMA将数据发送出去,然后清零Rx_Count保证下一次能够正常接收数据。最后关闭DMA,此时会触发DMA中断,调用DMA中断回调函数启动下一次DMA接收,具体可追踪stm32f1xx_it.c中的DMA1_Channel16_IRQHandler中的函数。此处不作详细分析。另外,如果在调用HAL_DMA_GET_COUNTER(&hdma_usart2_rx)函数时,提示hdma_usart2_rx找不到,则需要在usart.h中添加以下代码

    1. DMA_HandleTypeDef hdma_usart2_rx;
    2. DMA_HandleTypeDef hdma_usart2_tx;

    第三步、实验结果
    DMA.png

    总结:DMA的使用对于我来说是第一次,目前来说还没有感受到DMA的全部魅力,希望可以慢慢发现它的更高级的用法,……….后续待更新**