深入分析STM32_HAL库中串口接收相关函数

STM32_HAL库串口接收相关函数分析:


串口接收的程序整体分为三个部分:初始化部分,开启中断部分,中断函数部分:


  • 初始化部分:
    该部分主要完成相关引脚的初始化,串口的初始化(设置波特率,校验位,字长等),为了逻辑清晰,把初始化相关代码放在本文的最后。

  • 开启中断部分:
    调用HAL_UART_Receive_IT函数,开启中断,这个函数原型如下:
  • HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
    

    第一个参数是串口句柄,第二个参数指向自定义的接收缓冲数组,第三个参数很重要,它的值被赋给串口句柄的RxXferCount成员,规定了接收到几个数据帧之后,会调用接收回调函数HAL_UART_RxCpltCallback,详见下文。


  • 中断函数部分:中断函数部分主要位两个函数:中断函数和回调函数
    以USART1的中断函数为例:
  • 当调用HAL_UART_Receive_IT之后每次接收到一个字节的数据就会触发USART1_IRQHandler中断,USART1_IRQHandler中需要调用串口外设公用的中断函数HAL_UART_IRQHandler,给其传入初始化时使用的串口句柄g_uart1_handle。

    在HAL_UART_IRQHandler内部会判断此次的中断源,如果判断出来是接收中断(此中断在HAL_UART_Receive_IT中已被开启),则调用另外一个HAL库函数UART_Receive_IT,在该函数中会使串口句柄的RxXferCount成员递减一,如果递减一之后,RxXferCount变为零了,则关闭接收中断,清除相关标志位,调用回调函数HAL_UART_RxCpltCallback,否则函数直接返回。

    可见,RxXferCount的初始值决定了在接收几个字节之后会调用回调函数,上文提到过,它的初始值由HAL_UART_Receive_IT的第三个参数决定。在实际应用中,一般让RxXferCount等于一即每接收到一个字节,就调用一次回调函数。由于每次调用回调函数之后,都会关闭接收中断(上一段提到过),所以再USART1_IRQHandler要再次开启中断,为接收下一个字节做准备

    串口中断函数:

    void USART1_IRQHandler(void)
    {
    	HAL_UART_IRQHandler(&g_uart1_handle);/*串口公共中断函数*/
    	HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, 1);/*再次开启中断*/
    }
    

    串口接收回调函数:(RxXferCount==0时才会调用)

    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
    	/*通过读取自定义的g_rx_buffer[0]获取此次接收的字符,然后在此回调函数中确定回调行为*/
    }
    

    初始化相关的代码:

    #define USART_TX_GPIO_PORT                  GPIOA
    #define USART_TX_GPIO_PIN                   GPIO_PIN_9
    #define USART_TX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */
    
    #define USART_RX_GPIO_PORT                  GPIOA
    #define USART_RX_GPIO_PIN                   GPIO_PIN_10
    #define USART_RX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */
    
    #define USART_UX                            USART1
    #define USART_UX_IRQn                       USART1_IRQn
    #define USART_UX_IRQHandler                 USART1_IRQHandler
    #define USART_UX_CLK_ENABLE()               do{ __HAL_RCC_USART1_CLK_ENABLE(); }while(0)  /* USART1 时钟使能 */
    
    /******************************************************************************************/
    
    #define USART_REC_LEN               200         /* 定义最大接收字节数 200 */
    #define USART_EN_RX                 1           /* 使能(1)/禁止(0)串口1接收 */
    #define RXBUFFERSIZE   1                        /* 缓存大小 */
    
    extern UART_HandleTypeDef g_uart1_handle;       /* HAL UART句柄 */
    
    extern uint8_t  g_usart_rx_buf[USART_REC_LEN];  /* 接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 */
    extern uint16_t g_usart_rx_sta;                 /* 接收状态标记 */
    extern uint8_t g_rx_buffer[RXBUFFERSIZE];       /* HAL库USART接收Buffer */
    
    /******************************************************************************************/
    /* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */
    
    #if 1
    
    #if (__ARMCC_VERSION >= 6010050)            /* 使用AC6编译器时 */
    __asm(".global __use_no_semihosting\n\t");  /* 声明不使用半主机模式 */
    __asm(".global __ARM_use_no_argv \n\t");    /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */
    
    #else
    /* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
    #pragma import(__use_no_semihosting)
    
    struct __FILE
    {
        int handle;
        /* Whatever you require here. If the only file you are using is */
        /* standard output using printf() for debugging, no file handling */
        /* is required. */
    };
    
    #endif
    
    /* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
    int _ttywrch(int ch)
    {
        ch = ch;
        return ch;
    }
    
    /* 定义_sys_exit()以避免使用半主机模式 */
    void _sys_exit(int x)
    {
        x = x;
    }
    
    char *_sys_command_string(char *cmd, int len)
    {
        return NULL;
    }
    
    
    /* FILE 在 stdio.h里面定义. */
    FILE __stdout;
    
    /* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
    int fputc(int ch, FILE *f)
    {
        while ((USART_UX->SR & 0X40) == 0);     /* 等待上一个字符发送完成 */
    
        USART_UX->DR = (uint8_t)ch;             /* 将要发送的字符 ch 写入到DR寄存器 */
        return ch;
    }
    #endif
    /******************************************************************************************/
    
    #if USART_EN_RX /*如果使能了接收*/
    
    /* 接收缓冲, 最大USART_REC_LEN个字节. */
    uint8_t g_usart_rx_buf[USART_REC_LEN];
    
    /*  接收状态
     *  bit15,      接收完成标志
     *  bit14,      接收到0x0d
     *  bit13~0,    接收到的有效字节数目
    */
    uint16_t g_usart_rx_sta = 0;
    
    uint8_t g_rx_buffer[RXBUFFERSIZE];  /* HAL库使用的串口接收缓冲 */
    
    UART_HandleTypeDef g_uart1_handle;  /* UART句柄 */
    
    /**
     * @brief       串口X初始化函数
     * @param       baudrate: 波特率, 根据自己需要设置波特率值
     * @note        注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常.
     *              这里的USART的时钟源在sys_stm32_clock_init()函数中已经设置过了.
     * @retval      无
     */
    void usart_init(uint32_t baudrate)
    {
        /*UART 初始化设置*/
        g_uart1_handle.Instance = USART_UX;                                       /* USART_UX */
        g_uart1_handle.Init.BaudRate = baudrate;                                  /* 波特率 */
        g_uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;                      /* 字长为8位数据格式 */
        g_uart1_handle.Init.StopBits = UART_STOPBITS_1;                           /* 一个停止位 */
        g_uart1_handle.Init.Parity = UART_PARITY_NONE;                            /* 无奇偶校验位 */
        g_uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;                      /* 无硬件流控 */
        g_uart1_handle.Init.Mode = UART_MODE_TX_RX;                               /* 收发模式 */
        HAL_UART_Init(&g_uart1_handle);                                           /* HAL_UART_Init()会使能UART1 */
    
        /* 该函数会开启接收中断:清除标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量 */
        HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE); 
    }
    
    /**
     * @brief       UART底层初始化函数
     * @param       huart: UART句柄类型指针
     * @note        此函数会被HAL_UART_Init()调用
     *              完成时钟使能,引脚配置,中断配置
     * @retval      无
     */
    void HAL_UART_MspInit(UART_HandleTypeDef *huart)
    {
        GPIO_InitTypeDef gpio_init_struct;
    
        if (huart->Instance == USART_UX)                            /* 如果是串口1,进行串口1 MSP初始化 */
        {
            USART_TX_GPIO_CLK_ENABLE();                             /* 使能串口TX脚时钟 */
            USART_RX_GPIO_CLK_ENABLE();                             /* 使能串口RX脚时钟 */
            USART_UX_CLK_ENABLE();                                  /* 使能串口时钟 */
    
            gpio_init_struct.Pin = USART_TX_GPIO_PIN;               /* 串口发送引脚号 */
            gpio_init_struct.Mode = GPIO_MODE_AF_PP;                /* 复用推挽输出 */
            gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
            gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* IO速度设置为高速 */
            HAL_GPIO_Init(USART_TX_GPIO_PORT, &gpio_init_struct);
                    
            gpio_init_struct.Pin = USART_RX_GPIO_PIN;               /* 串口RX脚 模式设置 */
            gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;    
            HAL_GPIO_Init(USART_RX_GPIO_PORT, &gpio_init_struct);   /* 串口RX脚 必须设置成输入模式 */
            
    #if USART_EN_RX
            HAL_NVIC_EnableIRQ(USART_UX_IRQn);                      /* 使能USART1中断通道 */
            HAL_NVIC_SetPriority(USART_UX_IRQn, 3, 3);              /* 组2,最低优先级:抢占优先级3,子优先级3 */
    #endif
        }
    }
    

    参考文献:《正点原子_HAL库开发指南》

    物联沃分享整理
    物联沃-IOTWORD物联网 » 深入分析STM32_HAL库中串口接收相关函数

    发表评论