[016] [STM32] 串口HAL库轮询、中断、DMA方式传输数据

STM32
Contents
STM32串口硬件框架
串口收发单
元功能框图
HAL库串口通信框架
串口的数据类型定义
串口初始化流程
轮询方式的串口通信
中断方式的串口通信
DMA方式的串口通信

1 STM32串口硬件框架

1.1 串口收发单元功能框图

image-20220330215127032

串口收发单元主要利用:数据寄存器DR、发送引脚TX、接收引脚RX,以及状态寄存器SR的数据寄存器为空TXE标志、数据传输完成TC标志、接收寄存器非空RXNE标志。

1.1.1 数据寄存器DR

image-20220330215610294

  • 数据寄存器DR在硬件上分为TDR和RDR两个寄存器,采用双缓冲结构(数据收发过程中,可同时写入新的数据或读取已接收的数据,提高数据传输效率)
  • 发送时,数据通过D-Code总线送入TDR,然后传送到移位寄存器完成数据转换,从并行数据转为串行数据,最后通过TX引脚发送
  • 接收时,数据通过RX引脚逐位送入接收移位寄存器,8位数据接收完成后,送入RDR寄存器,供用户读取

1.1.2 通信状态标志位

在这里插入图片描述

2 HAL库串口通信框架

2.1 串口的数据类型定义

2.1.1 串口句柄定义

typedef uint32_t HAL_UART_RxTypeTypeDef;

typedef struct __UART_HandleTypeDef
{
    
    
  USART_TypeDef            *Instance;                /* 串口寄存器的基地址定义 */

  UART_InitTypeDef         Init;                     /* 串口初始化的通信参数(波特率/停止位/奇偶校验等) */

  UART_AdvFeatureInitTypeDef AdvancedInit;           /* 串口高级特性初始化参数 */

  // 串口的I/O缓冲区
  uint8_t                  *pTxBuffPtr;              /* 串口发送缓冲区首地址 */
  uint16_t                 TxXferSize;               /* 串口待发送数据个数 */
  __IO uint16_t            TxXferCount;              /* 串口发送数据计数器 */
  uint8_t                  *pRxBuffPtr;              /* 串口接收缓冲区首地址 */
  uint16_t                 RxXferSize;               /* 串口待接收数据的个数 */
  __IO uint16_t            RxXferCount;              /* 串口接收数据计数器 */

  uint16_t                 Mask;                     /* 串口RDR寄存器掩码 */
  __IO HAL_UART_RxTypeTypeDef ReceptionType;         /* 持续接收的类型 */

  void (*RxISR)(struct __UART_HandleTypeDef *huart); /*!< Function pointer on Rx IRQ handler */
  void (*TxISR)(struct __UART_HandleTypeDef *huart); /*!< Function pointer on Tx IRQ handler */

  // 串口发送和接收的DMA通道句柄
  DMA_HandleTypeDef        *hdmatx;                  /* 串口发送的DMA通道句柄定义 */
  DMA_HandleTypeDef        *hdmarx;                  /* 串口接收的DMA通道句柄定义 */

  // 串口工作状态
  HAL_LockTypeDef           Lock;                    /* 保护锁类型定义 */
  __IO HAL_UART_StateTypeDef    gState;              /* 串口全局状态和发送状态信息 */
  __IO HAL_UART_StateTypeDef    RxState;             /* 串口接收状态信息 */
  __IO uint32_t                 ErrorCode;           /* 串口错误代码 */
} UART_HandleTypeDef;

2.1.2 串口初始化数据类型

typedef struct
{
    
    
  uint32_t BaudRate;                /* 设置通信波特率 */

  uint32_t WordLength;              /* 设置通信数据的位数 */

  uint32_t StopBits;                /* 设置停止位 */

  uint32_t Parity;                  /* 奇偶校验位(当奇偶校验启用时,计算的奇偶校验被插入到传输数据的MSB位置(当字长度设置为9位时,为第9位;当字长设置为8位时,为第8位) */

  uint32_t Mode;                    /* 设置接收和发送模式是否使能 */

  uint32_t HwFlowCtl;               /* 设置硬件控制流是否使能 */

  uint32_t OverSampling;            /* 设置采样频率和信号传输频率的比例(最大f_PCLK/8) */

  uint32_t OneBitSampling;          /* 选择采样位方法:三采样位法/单采样位法 */

} UART_InitTypeDef;
  • WordLength
  • StopBits:
#define UART_STOPBITS_0_5                    USART_CR2_STOP_0                     /* 0.5 stop bit  */
#define UART_STOPBITS_1                     0x00000000U                           /* 1 stop bit    */
#define UART_STOPBITS_1_5                   (USART_CR2_STOP_0 | USART_CR2_STOP_1) /* 1.5 stop bits */
#define UART_STOPBITS_2                      USART_CR2_STOP_1                     /* 2 stop bits   */
  • Parity
    image-20220330223352589

  • Mode

  • HwFlowCtl
    image-20220330223538839

硬件流控可以控制数据传输的进程,防止数据丢失,该功能主要在收发双方传输速度不匹配的时候使用。

  • OverSampling

  • OneBitSampling:

#define UART_ONE_BIT_SAMPLE_DISABLE         0x00000000U         /*!< One-bit sampling disable */
#define UART_ONE_BIT_SAMPLE_ENABLE          USART_CR3_ONEBIT    /*!< One-bit sampling enable  */

2.2 串口初始化流程

  • 串口初始化函数
void MX_USART2_UART_Init(void)
{
    
    
  huart2.Instance = USART2;										// 配置串口2
  huart2.Init.BaudRate = 115200;								// 波特率115200
  huart2.Init.WordLength = UART_WORDLENGTH_8B;					// 数据位为8位
  huart2.Init.StopBits = UART_STOPBITS_1;						// 停止位为1位
  huart2.Init.Parity = UART_PARITY_NONE;						// 无奇偶校验			
  huart2.Init.Mode = UART_MODE_TX_RX;							// 使能接收和发送模式
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;					// 不使能硬件控制流
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;				// 16倍过采样
  huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;		// One-bit sampling disable 
  huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; // 不使用高级特性
  if (HAL_UART_Init(&huart2) != HAL_OK)		// 执行串口初始化操作
  {
    
    
    Error_Handler();
  }
}
  • 与MCU相关的初始化函数
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
    
    
  GPIO_InitTypeDef GPIO_InitStruct = {
    
    0};
  if(uartHandle->Instance==USART2)		
  {
    
    
    __HAL_RCC_USART2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_USART2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}

2.3 轮询方式的串口通信

2.3.1 发送数据

/**
  * @brief  在轮询方式下发送一定数量的数据
  * @note   1. 该函数连续发送数据,发送过程中通过判断TXE标志来发送下一个数据,通过判断TC标志来结束数据的发送。
  *         2. 如果在等待时间内没有完成发送,则不再发送,返回超时标志
  * @param huart   UART handle.
  * @param pData   指向发送数据缓冲区的指针 (u8 or u16 data elements).
  * @param Size    待发送数据的个数(u8 or u16)
  * @param Timeout 超时等待时间
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

返回值:

  • HAK_OK:发送成功
  • HAL_ERROR:参数错误
  • HAL_BUSY:串口被占用
  • HAL_TIMEOUT:接收超时

2.3.2 接收数据

/**
  * @brief  在轮询方式下接收一定数量的数据
  * @note   1. 该函数连续接收数据,在接收过程中通过判断RXNE标志来接收新的数据
  *         2.如果在等待时间内没有完成接收,则不再接收,返回超时标志
  * @param huart   UART handle.
  * @param pData   指向接收数据缓冲区的指针(u8 or u16 data elements).
  * @param Size    待接收数据的个数 (u8 or u16)
  * @param Timeout 超时等待时间
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

返回值:

  • HAK_OK:接收成功
  • HAL_ERROR:参数错误
  • HAL_BUSY:串口被占用
  • HAL_TIMEOUT:接收超时

2.3.3 CubeMX配置

image-20220331003016454

2.3.4 轮询应用示例

2.3.4.1 数据回显

uint8_t rec_buf[10];
void usart_polling(void)
{
    
    
    if (HAL_UART_Receive(&huart2, rec_buf, 5, 1000) == HAL_OK)
        HAL_UART_Transmit(&huart2, rec_buf, 5, 1000);
}

2.3.4.2 串口重定向

int fputc(int ch, FILE* f)
{
    
    
    HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

int fgetc(FILE *f)
{
    
    
    uint8_t ch;
    HAL_UART_Receive(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

测试:

char data;
while (1) {
    
    
    scanf("%c", &data);
    if (data == 'y')
        printf("receive y!\r\n");
    else
        printf("receive others!\r\n");
}

2.4 中断方式的串口通信

2.4.1 串口中断方式的特点

  • 发送数据时,将一字节数据放入数据寄存器DR;接收数据时,将DR的内容存放到用户存储区;
  • 中断方式不必等待数据的传输过程,只需要在每字节数据收发完成后,由中断标志位触发中断,在中断服务程序中放入新的一字节数据或者读取接收到的一字节数据;
  • 传输数据量较大,且通信**波特率较高(大于38400)**时,如果采用中断方式,每收发一个字节的数据,CPU都会被打断,造成CPU无法处理其他事务因此在批量数据传输,通信波特率较高时,建议采用DMA方式。

2.4.2 发送数据

/**
  * @brief  在中断方式下发送一定数量的数据
  * @param huart UART handle.
  * @param pData 指向发送数据缓冲区的指针 (u8 or u16 data elements).
  * @param Size  待发送数据的个数(u8 or u16)
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

注意

  • 函数将使能串口发送中断
  • 函数将置位TXEIETCIEUSART->CR1),即使能发送数据寄存器空中断发送完成中断。完成数据发送后,将会关闭发送中断,即清零TXEIETCIE,因此采用中断方式连续发送数据时,需要重复调用该函数,以便重新开启中断
  • 当指定量数据发送完成后,将调用发送中断回调函数HAL_UART_TxCpltCallback进行后续处理

即每发送一次数据进入一次中断,在中断中根据发送数据的个数判断数据是否发送完成。

2.4.3 接收数据

/**
  * @brief  在中断方式下接收一定数量的数据
  * @param huart UART handle.
  * @param pData 指向接收数据缓冲区的指针 (u8 or u16 data elements).
  * @param Size  待接收数据的个数(u8 or u16)
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

注意

  • 函数将使能串口接收中断
  • 函数将置位RXNEIE,即使能接收数据寄存器非空中断RXNEIE。完成数据接收后,将会关闭接收中断,即清零RXNEIE,因此采用中断方式连续发送数据时,需要重复调用该函数,以便重新开启中断
  • 当指定量数据发送完成后,将调用发送中断回调函数HAL_UART_RxCpltCallback进行后续处理

即每接收一次数据进入一次中断,在中断中根据接收数据的个数判断数据是否接收完成。

2.4.4 串口中断通用处理函数

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)

作为所有串口中断发生后的通用函数,会调用包括上述两回调函数的各种callback函数。

注意

  • 函数内部先判断中断类型,并清除对应的中断标志,最后调用回调函数完成对应的中断处理。

  • 此函数有cubemx自动生成。

2.4.5 串口发送中断回调函数

__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)

用于处理所有串口的发送中断。

注意

  • 函数在HAL_UART_IRQHandler中调用,完成所有发生中断处理
  • 函数内部根据串口句柄判断是哪个串口产生的发送中断

2.4.6 串口接收中断回调函数

__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

用于处理所有串口的接收中断。

注意

  • 函数在HAL_UART_IRQHandler中调用,完成所有接收中断处理
  • 函数内部根据串口句柄判断是哪个串口产生的接收中断

2.4.7 串口中断使/失能宏

/** @brief  Enable the specified UART interrupt.
  * @param  __HANDLE__ specifies the UART Handle.
  * @param  __INTERRUPT__ specifies the UART interrupt source to enable.
  *          This parameter can be one of the following values:
  *            @arg @ref UART_IT_WUF   Wakeup from stop mode interrupt
  *            @arg @ref UART_IT_CM    Character match interrupt
  *            @arg @ref UART_IT_CTS   CTS change interrupt
  *            @arg @ref UART_IT_LBD   LIN Break detection interrupt
  *            @arg @ref UART_IT_TXE   Transmit Data Register empty interrupt
  *            @arg @ref UART_IT_TC    Transmission complete interrupt
  *            @arg @ref UART_IT_RXNE  Receive Data register not empty interrupt
  *            @arg @ref UART_IT_RTO   Receive Timeout interrupt
  *            @arg @ref UART_IT_IDLE  Idle line detection interrupt
  *            @arg @ref UART_IT_PE    Parity Error interrupt
#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)
  • __HANDLE__:串口句柄地址
  • __INTERRUPT__:串口中断类型

失能宏:

__HAL_UART_DISABLE_IT(__HANDLE__, __INTERRUPT__) 

2.4.8 串口中断标志查询宏

/** @brief  Check whether the specified UART flag is set or not.
  * @param  __HANDLE__ specifies the UART Handle.
  * @param  __FLAG__ specifies the flag to check.
  *        This parameter can be one of the following values:
  *            @arg @ref UART_FLAG_REACK Receive enable acknowledge flag
  *            @arg @ref UART_FLAG_TEACK Transmit enable acknowledge flag
  *            @arg @ref UART_FLAG_WUF   Wake up from stop mode flag
  *            @arg @ref UART_FLAG_RWU   Receiver wake up flag (if the UART in mute mode)
  *            @arg @ref UART_FLAG_SBKF  Send Break flag
  *            @arg @ref UART_FLAG_CMF   Character match flag
  *            @arg @ref UART_FLAG_BUSY  Busy flag
  *            @arg @ref UART_FLAG_ABRF  Auto Baud rate detection flag
  *            @arg @ref UART_FLAG_ABRE  Auto Baud rate detection error flag
  *            @arg @ref UART_FLAG_CTS   CTS Change flag
  *            @arg @ref UART_FLAG_LBDF  LIN Break detection flag
  *            @arg @ref UART_FLAG_TXE   Transmit data register empty flag
  *            @arg @ref UART_FLAG_TC    Transmission Complete flag
  *            @arg @ref UART_FLAG_RXNE  Receive data register not empty flag
  *            @arg @ref UART_FLAG_RTOF  Receiver Timeout flag
  *            @arg @ref UART_FLAG_IDLE  Idle Line detection flag
  *            @arg @ref UART_FLAG_ORE   Overrun Error flag
  *            @arg @ref UART_FLAG_NE    Noise Error flag
  *            @arg @ref UART_FLAG_FE    Framing Error flag
  *            @arg @ref UART_FLAG_PE    Parity Error flag
  * @retval The new state of __FLAG__ (TRUE or FALSE).
  */
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->ISR & (__FLAG__)) == (__FLAG__))
  • __HANDLE__:串口句柄地址
  • __FLAG__:串口标志

2.4.9 空闲中断标志清除宏

#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__)   __HAL_UART_CLEAR_FLAG((__HANDLE__), UART_CLEAR_IDLEF)

其中__HAL_UART_CLEAR_FLAG宏可以清除相应的标准:

/** @brief  Clear the specified UART pending flag.
  * @param  __HANDLE__ specifies the UART Handle.
  * @param  __FLAG__ specifies the flag to check.
  *          This parameter can be any combination of the following values:
  *            @arg @ref UART_CLEAR_PEF      Parity Error Clear Flag
  *            @arg @ref UART_CLEAR_FEF      Framing Error Clear Flag
  *            @arg @ref UART_CLEAR_NEF      Noise detected Clear Flag
  *            @arg @ref UART_CLEAR_OREF     Overrun Error Clear Flag
  *            @arg @ref UART_CLEAR_IDLEF    IDLE line detected Clear Flag
  *            @arg @ref UART_CLEAR_TCF      Transmission Complete Clear Flag
  *            @arg @ref UART_CLEAR_RTOF     Receiver Timeout clear flag
  *            @arg @ref UART_CLEAR_LBDF     LIN Break Detection Clear Flag
  *            @arg @ref UART_CLEAR_CTSF     CTS Interrupt Clear Flag
  *            @arg @ref UART_CLEAR_CMF      Character Match Clear Flag
  *            @arg @ref UART_CLEAR_WUF      Wake Up from stop mode Clear Flag
  * @retval None
  */
#define __HAL_UART_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->ICR = (__FLAG__))

2.4.10 中断应用示例

使能串口中断:

image-20220331015725283

2.4.10.1 固定长度的数据收发

uint8_t Rxbuf[10];
uint8_t RxFlag = 0;
int main(void)
{
    
    
    HAL_UART_Receive_IT(&huart2, (uint8_t *)&Rxbuf, 10);    // 使能串口接收中断
    while(1){
    
    
        if (RxFlag){
    
    
            RxFlag = 0;
            printf("receive success!\r\n");
            HAL_UART_Transmit_IT(&huart2, (uint8_t *)&Rxbuf, 10);  
    	}
    }
}

串口接收中断回调函数:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    
    
    if (huart->Instance == USART2)
    {
    
    
        RxFlag = 1;
        HAL_UART_Receive_IT(&huart2, (uint8_t *)&Rxbuf, 10);    // 使能串口接收中断
        // UART_Start_Receive_IT(&huart2, (uint8_t *)&Rxbuf, 10);  
    }
}

HAL_UART_IRQHandler中调用HAL_UART_RxCpltCallback函数,其中HAL_UART_IRQHandler函数会在数据接收完成后清除相应中断标志位,因此需要在回调函数中调用HAL_UART_Receive_IT使能接收中断RXNEIE,该函数最终会调用UART_Start_Receive_IT

处理流程:

image-20220331030044520

UART_Receive_IT函数是STM32F1/4调用方式,STM32L0采用函数指针huart->RxISR(huart)调用。

2.4.10.2 实现简单的帧格式通信

image-20220331030329794

uint8_t Rxbuf[4];
uint8_t RxFlag = 0;
uint8_t ErrFlag = 0;
int main(void)
{
    
    
    HAL_UART_Receive_IT(&huart2, (uint8_t *)&Rxbuf, 10);    // 使能串口接收中断
    while(1){
    
    
           if (RxFlag){
    
    
        RxFlag = 0;
        if (Rxbuf[0] == 0xaa && Rxbuf[3] == 0x55)
        {
    
    
            if (Rxbuf[1] == 0x01)
            {
    
    
                if (Rxbuf[2] == 0x00)
                {
    
    
                    BOARD_LED_OFF;
                    printf("LED OFF!\r\n");
                }
                else if (Rxbuf[2] == 0x01)
                {
    
    
                    BOARD_LED_ON;
                    printf("LED ON!\r\n");
                }
                else
                    ErrFlag = 1;
            }
            else
                ErrFlag = 1;
        }
        else
            ErrFlag = 1;
        
        if (ErrFlag){
    
    
            ErrFlag = 0;
            printf("error!\r\n");
        }
        memset(Rxbuf, 0, 4);
    }
  }
    }
}
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
    
    
        if (huart->Instance == USART2)
        {
    
    
            RxFlag = 1;
            UART_Start_Receive_IT(&huart2, (uint8_t *)&Rxbuf, 4);    // 使能串口接收中断
        }
    }

image-20220331031528819

2.5 DMA方式的串口通信

2.5.1 DMA简介

DMA数据传输的4个要素:

  • 目的
  • 长度(最大65535,数据宽度通过DMA->CCR MSIZE[1:0]PSIZE[1:0]配置)
  • 触发信号:启动一次DMA数据传输的动作

中断标志:(硬件置位

image-20220331032131589

image-20220331032249385

优先级

  • 最高优先级 Very Hight
  • 高优先级 Hight
  • 中等优先级 Medium
  • 低优先级Low

软件阶段:通过DMA_CRRX: PL[1:0]配置

硬件阶段:通道编号小的优先级大;DMA1的优先级高于DMA2的优先级

数据流是数据传输的通路,可以完成以下传输:

  • 外设到存储器 (从外设读,DMA_CCRx寄存器DIR位为0)
  • 存储器到外设(从存储器读,DMA_CCRx寄存器DIR位为1)
  • 存储器到存储器 Memory to memory mode

指针增量:设置DMA_CCRx寄存器中的PINCMINC标志位,外设和存储器的指针每次传输后,下次传输的地址是前一个地址加上增量值,增量值取决于数据宽度(1/2/4)。(串口buff接收数据必须开启存储器增量模式,否则不能自增接收/发送)

DMA数据传输方式

  • 普通模式传输结束后(即要传输数据的数量达到零),将不再产生DMA操作若开始新的DMA传输,需在关闭DMA通道情况下,重新启动DMA传输。(在DMA_CNDTRx寄存器中重新写入传输数目)
  • 循环模式:可用于处理环形缓冲区连续数据流(例如ADC扫描模式)。当激活循环模式后,每轮传输结束时,要传输的数据数量将自动用设置的初始值进行加载,并继续响应DMA请求。

注意:存储器到存储器模式不能与循环模式同时使用。

DMA寄存器配置流程

  • DMA_CPARx寄存器中设置外设寄存器的地址。发生外设数据传输请求时,这个地址将 是数据传输的源或目标。

  • DMA_CMARx寄存器中设置数据存储器的地址。发生外设数据传输请求时,传输的数 据将从这个地址读出或写入这个地址。

  • DMA_CNDTRx寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减(只能在通道不工作(DMA_CCRx的EN=0)时写入

  • DMA_CCRx寄存器的PL[1:0]位中设置通道的优先级

  • DMA_CCRx寄存器中设置数据传输的方向循环模式、外设和存储器的增量模式、外设和存储器的数据宽度、传输一半产生中断或传输完成产生中断。

  • 设置DMA_CCRx寄存器的ENABLE位,启动该通道。

  • DMA_ISRDMA_IFCR寄存器分别读取和清除传输标志

寄存器的x = 1~7,共7个通道。

注意:对于DMA_CNDTRx寄存器,当寄存器内容为0时,无论通道是否开启,都不会发生任何数据传输。数据传输结束后,寄存器的内容或者变为0,因此单次传输结束后,非循环模式需要重新向该寄存器写入传输数据的数量。

2.5.2 发送数据

/**
  * @brief  DMA方式发送一定数量的数据
  * @param huart UART handle.
  * @param pData 指向发送数据缓冲区的指针 (u8 or u16 data elements).
  * @param Size  待发送数据的个数(u8 or u16)
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

注意:数据发送完成后,可触发DMA中断,在中断中调用串口送发中断回调函数HAL_UART_TxCpltCallback进行后续处理

2.5.3 接收数据

/**
  * @brief  在DMA方式下接收一定数量的数据
  * @param huart UART handle.
  * @param pData 指向接收数据缓冲区的指针 (u8 or u16 data elements).
  * @param Size  待接收数据的个数(u8 or u16)
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

注意:数据发送完成后,可触发DMA中断,在中断中调用串口接收中断回调函数HAL_UART_RxCpltCallback进行后续处理

2.5.4 获取未传输数据个数宏

stm32xx_hal_dma.h

#define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)

2.5.5 不定长数据的收发

2.5.5.1 空闲中断特点

  • 在一帧数据传输结束后,通信线路将会维持高电平,这个状态称为空闲状态;
  • 当CPU检测到通信线路处于空闲状态时,空闲状态标志IDLE将由硬件置1。如果串口控制寄存器CR1中的IDLEIE位为1,将会触发空闲中断(IDLE中断);
  • 由于空闲标志是在一帧数据传输完成后才置位,在有效数据传输过程中不会置位,因此借助空闲中断,可以实现不定长数据的收发

2.5.5.2 空闲中断时序图

假设一帧数据由2个字符构成,分别是0xaa、0x55,传输的时序图如下:
image-20220331033815748

0xaa和0x55数据帧传输过程中没有出现idle状态,传输结束后才出现。

2.5.5.3 CubeMX配置

【设计思路】

  • 使能IDLE中断,在串口2的中断服务程序USART2_IRQHandler中添加对IDLE中断的判断,该函数位于stm32f4xx_it.c文件;
  • 设置传输模式为普通模式,启动DMA传输。串口一旦接收到数据,则触发DMA操作,将数据存放到用户定义的接收缓冲区;
  • 当一帧数据发送完成后,线路处于IDLE状态,将触发IDLE中断,调用IDLE中断回调函数,设置数据接收完成标志;
  • 主程序检测到接收完成标志置位后,将接收的一帧数据原样发回到PC,并禁能DMA,以触发DMA中断。DMA中断将调用接收中断回调函数HAL_UART_RxCpltCallback,在回调函数中重新启动DMA传输。

image-20220331034614283

优先级为LOW、模式为普通模式Normal、数据宽度为字节Byte

DMA中断由CubeMX自动勾选。

USART2_RX的DMA1_CH5_CCR寄存器位配置情况:

image-20220331154549115

USART2_TX的DMA1_CH4_CCR寄存器位配置情况:

image-20220331154607308

MINC表示存储器增量模式,必须开启。

2.5.5.4 DMA初始化参数

hdma_usart2_rx.Instance = DMA1_Channel5;						// DMA1 通道5
hdma_usart2_rx.Init.Request = DMA_REQUEST_4;					// 请求数字
hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;			// 外设到存储器
hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;				// 禁用外设指针增量
hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;					// 使用存储器指针增量
hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;	// 外设字节宽度1
hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;		// 存储器字节宽度1
hdma_usart2_rx.Init.Mode = DMA_NORMAL;							// 普通模式
hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;				// 低优先级
if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
{
    
    
    Error_Handler();
}

TX基本相同,不同之处为:

hdma_usart2_tx.Instance = DMA1_Channel4;						// DMA1 通道4
hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;			// 存储器到外设

STM32L0系列有个请求数字:

image-20220331161311235

2.5.5.5 功能实现

uint8_t Rxbuf[100];
uint8_t Rec_cnt = 0;
uint8_t RxFlag = 0;
int main(void)
{
    
    
    HAL_Init();
    SystemClock_Config();
    MX_DMA_Init();
    MX_USART2_UART_Init();

    __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);                // 使能IDLE中断
    HAL_UART_Receive_DMA(&huart2, (uint8_t *)&Rxbuf, 100);      // 启动DMA接收
    while (1)
    {
    
    
        if (RxFlag)
        {
    
    
            RxFlag = 0;
            HAL_UART_Transmit_DMA(&huart2, (uint8_t *)&Rxbuf, Rec_cnt);  
            // 重新启动下一次DMA接收
            HAL_UART_Receive_DMA(&huart2, (uint8_t *)&Rxbuf, 100);   
        }
    }
}

串口中断服务例程空闲中断标志处理

void USART2_IRQHandler(void)
{
    
    
  	HAL_UART_IRQHandler(&huart2);
    if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET)
    {
    
    
        __HAL_UART_CLEAR_IDLEFLAG(&huart2);         // 清除串口空闲中断标志IDLEF
        // 关闭所有的接收和发送DMA,清空CCR寄存器EN位 否则无法修改hdma_usart2_rx.Instance->CNDTR
        // 但DMA串口发送和接收函数内部都会先清除CCR_EN,修改CNDTR后,再置位。实际上不加此函数会导致将上次接收的数据一起传输
        HAL_UART_DMAStop(&huart2);                 
        Rec_cnt = 100 - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
        RxFlag = 1;
    }
}

测试结果:每次上位机发送的数据,再发送完成后都能立马收到
image-20220331162705430

END

猜你喜欢

转载自blog.csdn.net/kouxi1/article/details/123876915