STM32串口发送接收字符HAL库配置步骤

495 阅读5分钟

USART/UART

USART:通用同步异步收发器 UART:通用异步收发器

USART通常以用于异步通信。

异步通信

没有时钟信号,通过在数据信号中接入起始位和停止位等一些同步信号。

image.png

USART简化版框图

image.png

USART/UART异步通信配置步骤

image.png

1、配置串口工作参数

image.png UART句柄结构定义: 在这里插入图片描述

image.png 需要配置波特率,字长,停止位,校验位可以不使用,模式可以是收/发/收发。

代码如下:

uint8_t g_rx_buffer[1];          /* HAL库使用的串口接收数据缓冲区 */
UART_HandleTypeDef g_uart1_handle;  /* UART句柄 */
/* 串口1初始化函数 */
void usart_init(uint32_t baudrate)
{
    g_uart1_handle.Instance = USART1;/* USART1*/
    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.Mode = UART_MODE_TX_RX;/* 收发模式 */
    HAL_UART_Init(&g_uart1_handle);/* HAL_UART_Init()会使能UART1 */
    
    HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t*)g_rx_buffer, 1);//开启串口异步接收中断
}

第三步开启异步接收中断也在初始化中。

2、串口底层初始化

HAL库外设初始化MSP回调机制:

image.png

在使用HAL_UART_Init()使能UART1时,会自动调用MSP回调函数HAL_UART_MspInit()。而HAL_UART_MspInit()在stm32f1xx_hal_uart.c中,并且是弱函数

__weak void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function should not be modified, when the callback is needed,
           the HAL_UART_MspInit could be implemented in the user file
   */
}

用户重新定义MSP回调函数:

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef gpio_init_struct;
    if(huart->Instance == USART1)                /* 如果是串口1,进行串口1 MSP初始化 */
    {
    	//(1)使能USART1和对应IO时钟
        __HAL_RCC_USART1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
		//(2)初始化IO
        gpio_init_struct.Pin = GPIO_PIN_9;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;            /* 推挽式复用输出 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;      /* 高速 */
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);            /* 初始化串口1的TX引脚 */
        
        gpio_init_struct.Pin = GPIO_PIN_10;
        gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;         /* 输入 */
        gpio_init_struct.Pull = GPIO_PULLUP;                /* 上拉 */
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);            /* 初始化串口1的RX引脚 */
        //(3)使能USART1中断,设置优先级
        HAL_NVIC_SetPriority(USART1_IRQn, 3, 3);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
    }
}

3、开启串口异步接收中断

已经在第一步设置

4、设置优先级,使能中断

已经在第二步最后配置

5、编写中断服务函数

image.png 编写中断服务函数并调用HAL库中断共用处理函数:

/* 串口1中断服务函数 */
void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&g_uart1_handle);//调用HAL库中断共用处理函数
    HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t*)g_rx_buffer, 1);
}

然后HAL库会自己调用中断回调函数(上图中那些函数):用户选择自己需要的回调函数进行重新定义。 这里重新定义接收完成回调函数HAL_UART_RxCpltCallback(),判断接收完成后去做某件事。

uint8_t g_usart1_rx_flag = 0;    /* 串口接收到数据标志 */
/* 串口数据接收完成回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)//首先判断是不是串口1
    {
        g_usart1_rx_flag = 1;//串口接受标志置为1,表示接收完成
    }
}

6、串口数据发送

最后编写main函数

usart_init(115200);                         /* 波特率设为115200 */
while(1)
{
    if(g_usart1_rx_flag == 1)
    {
        printf("您输入的字符为:\r\n");
        HAL_UART_Transmit(&g_uart1_handle, (uint8_t*)g_rx_buffer, 1, 1000);
        while(__HAL_UART_GET_FLAG(&g_uart1_handle, UART_FLAG_TC) != 1);
        printf("\r\n");
        g_usart1_rx_flag = 0;
    }
    else
    {
        delay_ms(10);
    }
}

使用HAL_UART_Transmit()发送数据:

image.png 根据状态寄存器SR的TC位来判断是否发送完成,如果TC位为1则发送完成。

image.png 使用__HAL_UART_GET_FLAG()判断UART_FLAG_TC这一位是否为1,不为1说明没有发送完成,一直在while循环里面。发送完成接收标志g_usart1_rx_flag置为0。

发送接收多字符

上面配置的接收完成回调函数只适用于一次发送接收一个字符的情况,下面是发送接收多字符: 主要区别在接收完成回调函数中

uint16_t g_usart_rx_sta = 0;
uint8_t g_rx_buffer[RXBUFFERSIZE];  /* HAL库使用的串口接收缓冲 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART_UX)                    /* 如果是串口1 */
    {
        if ((g_usart_rx_sta & 0x8000) == 0)             /* 接收未完成 */
        {
            if (g_usart_rx_sta & 0x4000)                /* 接收到了0x0d(即回车键) */
            {
                if (g_rx_buffer[0] != 0x0a)             /* 接收到的不是0x0a(即不是换行键) */
                {
                    g_usart_rx_sta = 0;                 /* 接收错误,重新开始 */
                }
                else                                    /* 接收到的是0x0a(即换行键) */
                {
                    g_usart_rx_sta |= 0x8000;           /* 接收完成了 */
                }
            }
            else                                        /* 还没收到0X0d(即回车键) */
            {
                if (g_rx_buffer[0] == 0x0d)
                    g_usart_rx_sta |= 0x4000;
                else
                {
                    g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0];
                    g_usart_rx_sta++;

                    if (g_usart_rx_sta > (USART_REC_LEN - 1))
                    {
                        g_usart_rx_sta = 0;             /* 接收数据错误,重新开始接收 */
                    }
                }
            }
        }
    }
}

解释:

重点是这个函数,一开始数据接收标志是无符号整型16位并且赋值为0,因为接收中断,按一个字节一个字节接收每接收到一个字节都会中断,进入接收完成回调函数。所以当接收完第一个字节时,rx_sta还是0,判断位15是否为1是否接收到换行,&0x8000肯定等于0,表示接受未完成,然后判断是否接收到回车(位14)\r,&0x4000还是0,进入else,然后判断接收到的这个数据是否是回车(0x0d),如果是,那么rx_sta的位14置1,如果不是回车,那么就把这个数据存储到rx_buf里面,rx_sta&0x3fff,就是判断位0-13计算有多少的有效字节数据,然后rx_sta+ +,数据长度加1。

如果接收到回车,那么位14置为1了,在接收到数据进来后,&0x8000还是0,但&0x4000是1,所以表示已经接收到回车,再判断此次数据是不是换行(0x0a),如果不是那么数据错误,如果是,那么位15置1。表示此次数据接收完成。main函数中就会执行相应代码,执行完后rx_sta置为0,重新接收数据。

这就是接收一次数据的过程

main函数中

if (g_usart_rx_sta & 0x8000)        /* 接收到了数据? */
{
    len = g_usart_rx_sta & 0x3fff;  /* 得到此次接收到的数据长度 */
    printf("\r\n您发送的消息为:\r\n");

    HAL_UART_Transmit(&g_uart1_handle,(uint8_t*)g_usart_rx_buf, len, 1000);    /* 发送接收到的数据 */
    while(__HAL_UART_GET_FLAG(&g_uart1_handle,UART_FLAG_TC) != SET);           /* 等待发送结束 */
    printf("\r\n\r\n");             /* 插入换行 */
    g_usart_rx_sta = 0;
}