基于STM32的UART串口通信实现【单片机教程】

基于STM32的UART串口通信

  • 一、前言
  • 二、UART相关知识
  • 1、UART简介
  • 2、UART通信协议
  • 3、UART功能说明
  • (1)正常 USART 模式下,通过这些引脚以帧的形式发送和接收串行数据:
  • (2)在同步模式下连接时需要以下引脚:
  • 4、UART工作原理
  • (1)发送接收
  • (2)波特率产生
  • (3)数据收发
  • (4)中断控制
  • (5)FIFO操作
  • (6)回环操作
  • 三、STM32CubeMx配置
  • 四、UART发送
  • 1、初始化说明
  • 2、HAL库函数说明
  • 3、代码实现UART发送
  • (1)直接发送
  • (2)字符串发送
  • 五、UART接收
  • 1、初始化说明
  • 2、函数说明
  • (1)CubeMx生成的UART中断处理函数(在stm32f1xx_it.c中)
  • (2)HAL库函数HAL_UART_Transmit(在stm32f4xx_hal_uart.c中)
  • (3)HAL库函数HAL_UART_Receive(在stm32f4xx_hal_uart.c中)
  • 3、代码编写:实现UART接收
  • (1)直接接收(不建议)
  • (2)中断接收(接收并发送)(不推荐)
  • (3)中断接收(先接收完,后处理)(推荐)
  • 一、前言

    简单讲解一下UART通信协议,以及UART能够实现的一些功能,还有有关使用STM32CubeMX来配置芯片的一些操作。实验内容基于正点原子精英板开发板,单片机芯片为STM32F103ZET6
    在后面我会以我使用的STM32F429开发板来举例讲解(其他STM32系列芯片大多数都可以按照这些步骤来操作的),如有不足请多多指教。

    二、UART相关知识

    1、UART简介

    嵌入式开发中,UART串口通信协议是我们常用的通信协议(UART、I2C、SPI等)之一,全称叫做通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),是异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输,它能将要传输的资料在串行通信与并行通信之间加以转换,能够灵活地与外部设备进行全双工数据交换。
    类似的,USART(Universal Synchronous Asynchronous Receiver and Transmitter通用同步异步收发器)串口的,USART相当于UART的升级版,USART支持同步模式,因此USART 需要同步始终信号USART_CK(如STM32 单片机),通常情况同步信号很少使用,因此一般的单片机UART和USART使用方式是一样的,都使用异步模式。因为USART的使用方法上跟UART基本相同,所以在此就以UART来讲该通信协议了。

    2、UART通信协议

    1. 起始位
      当未有数据发送时,数据线处于逻辑“1”状态;先发出一个逻辑“0”信号,表示开始传输字符。
    2. 数据位
      紧接着起始位之后。资料位的个数可以是4、5、6、7、8等,构成一个字符。通常采用ASCII码。从最低位开始传送,靠时钟定位。
    3. 奇偶校验位
      资料为加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验资料传送的正确性。
    4. 停止位
      它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
    5. 空闲位或起始位
      处于逻辑“1”状态,表示当前线路上没有资料传送,进入空闲状态。
      处于逻辑“0”状态,表示开始传送下一数据段。
    6. 波特率
      表示每秒钟传送的码元符号的个数,是衡量数据传送速率的指标,它用单位时间内载波调制状态改变的次数来表示。
      常用的波特率有:9600、115200……
      时间间隔计算:1秒除以波特率得出的时间,例如,波特率为9600的时间间隔为1s / 9600(波特率) = 104us。

    3、UART功能说明

    接口通过三个引脚从外部连接到其它设备。任何 USART 双向通信均需要 至少两个引脚:接收数据输入引脚 (RX) 和发送数据引脚输出 (TX):
      RX:接收数据输入引脚就是串行数据输入引脚。过采样技术可区分有效输入数据和噪声,从而用于恢复数据。
      TX:发送数据输出引脚。如果关闭发送器,该输出引脚模式由其 I/O 端口配置决定。如果使 能了发送器但没有待发送的数据,则 TX 引脚处于高电平。在单线和智能卡模式下,该 I/O 用于发送和接收数据(USART 电平下,随后在 SW_RX 上接收数据)。

    (1)正常 USART 模式下,通过这些引脚以帧的形式发送和接收串行数据:

  • 发送或接收前保持空闲线路
  • 起始位
  • 数据(字长 8 位或 9 位),最低有效位在前
  • 用于指示帧传输已完成的 0.5 个、1 个、1.5 个、2 个停止位
  • 该接口使用小数波特率发生器 – 带 12 位尾数和 4 位小数
  • 状态寄存器 (USART_SR)
  • 数据寄存器 (USART_DR)
  • 波特率寄存器 (USART_BRR) – 12 位尾数和 4 位小数
  • 智能卡模式下的保护时间寄存器 (USART_GTPR)
  • (2)在同步模式下连接时需要以下引脚:

  • SCLK:发送器时钟输出。该引脚用于输出发送器数据时钟,以便按照 SPI 主模式进行同步发送(起始位和结束位上无时钟脉冲,可通过软件向最后一个数据位发送时钟脉冲)。RX 上可同步接收并行数据。这一点可用于控制带移位寄存器的外设(如 LCD 驱动器)。时钟相位和极性可通过软件编程。在智能卡模式下,SCLK 可向智能卡提供时钟。在硬件流控制模式下需要以下引脚:
  • nCTS:“清除以发送”用于在当前传输结束时阻止数据发送(高电平时)。
  • nRTS:“请求以发送”用于指示 USART 已准备好接收数据(低电平时)。
  • 4、UART工作原理

    (1)发送接收

    发送逻辑对从发送FIFO 读取的数据执行“并→串”转换。控制逻辑输出起始位在先的串行位流,并且根据控制寄存器中已编程的配置,后面紧跟着数据位(注意:最低位 LSB 先输出)、奇偶校验位和停止位。
    在检测到一个有效的起始脉冲后,接收逻辑对接收到的位流执行“串→并”转换。此外还会对溢出错误、奇偶校验错误、帧错误和线中止(line-break)错误进行检测,并将检测到的状态附加到被写入接收FIFO 的数据中。

    (2)波特率产生

    波特率除数(baud-rate divisor)是一个22 位数,它由16 位整数和6 位小数组成。波特率发生器使用这两个值组成的数字来决定位周期。通过带有小数波特率的除法器,在足够高的系统时钟速率下,UART 可以产生所有标准的波特率,而误差很小。

    (3)数据收发

    发送时,数据被写入发送FIFO。如果UART 被使能,则会按照预先设置好的参数(波特率、数据位、停止位、校验位等)开始发送数据,一直到发送FIFO 中没有数据。一旦向发送FIFO 写数据(如果FIFO 未空),UART 的忙标志位BUSY 就有效,并且在发送数据期间一直保持有效。BUSY 位仅在发送FIFO 为空,且已从移位寄存器发送最后一个字符,包括停止位时才变无效。即 UART 不再使能,它也可以指示忙状态。

    在UART 接收器空闲时,如果数据输入变成“低电平”,即接收到了起始位,则接收计数器开始运行,并且数据在Baud16 的第8 个周期被采样。如果Rx 在Baud16 的第8 周期仍然为低电平,则起始位有效,否则会被认为是错误的起始位并将其忽略。
      
    如果起始位有效,则根据数据字符被编程的长度,在 Baud16 的每第 16 个周期(即一个位周期之后)对连续的数据位进行采样。如果奇偶校验模式使能,则还会检测奇偶校验位。

    最后,如果Rx 为高电平,则有效的停止位被确认,否则发生帧错误。当接收到一个完整的字符时,将数据存放在接收FIFO 中。

    (4)中断控制

    出现以下情况时,可使UART 产生中断:

  • FIFO 溢出错误
  • 线中止错误(line-break,即Rx 信号一直为0 的状态,包括校验位和停止位在内)
  • 奇偶校验错误
  • 帧错误(停止位不为1)
  • 接收超时(接收FIFO 已有数据但未满,而后续数据长时间不来)
  • 发送
  • 接收
    由于所有中断事件在发送到中断控制器之前会一起进行“或运算”操作,所以任意时刻 UART 只能向中断产生一个中断请求。通过查询中断状态函数,软件可以在同一个中断服务函数里处理多个中断事件(多个并列的if 语句)。
  • (5)FIFO操作

    FIFO 是“First-In First-Out”的缩写,意为“先进先出”,是一种常见的队列操作。 Stellaris 系列ARM 的UART 模块包含有2 个16 字节的FIFO:一个用于发送,另一个用于接收。可以将两个FIFO 分别配置为以不同深度触发中断。可供选择的配置包括:1/8、 1/4、1/2、3/4 和7/8 深度。例如,如果接收FIFO 选择1/4,则在UART 接收到4 个数据时产生接收中断。
     
    发送FIFO的基本工作过程: 只要有数据填充到发送FIFO 里,就会立即启动发送过程。由于发送本身是个相对缓慢的过程,因此在发送的同时其它需要发送的数据还可以继续填充到发送 FIFO 里。当发送 FIFO 被填满时就不能再继续填充了,否则会造成数据丢失,此时只能等待。这个等待并不会很久,以9600 的波特率为例,等待出现一个空位的时间在1ms 上下。发送 FIFO 会按照填入数据的先后顺序把数据一个个发送出去,直到发送 FIFO 全空时为止。已发送完毕的数据会被自动清除,在发送FIFO 里同时会多出一个空位。

    接收FIFO的基本工作过程: 当硬件逻辑接收到数据时,就会往接收FIFO 里填充接收到的数据。程序应当及时取走这些数据,数据被取走也是在接收FIFO 里被自动删除的过程,因此在接收 FIFO 里同时会多出一个空位。如果在接收 FIFO 里的数据未被及时取走而造成接收FIFO 已满,则以后再接收到数据时因无空位可以填充而造成数据丢失。

    收发FIFO 主要是为了解决UART 收发中断过于频繁而导致CPU 效率不高的问题而引入的。在进行 UART 通信时,中断方式比轮询方式要简便且效率高。但是,如果没有收发 FIFO,则每收发一个数据都要中断处理一次,效率仍然不够高。如果有了收发FIFO,则可以在连续收发若干个数据(可多至14 个)后才产生一次中断然后一并处理,这就大大提高了收发效率。

    完全不必要担心FIFO 机制可能带来的数据丢失或得不到及时处理的问题,因为它已经帮你想到了收发过程中存在的任何问题,只要在初始化配置UART 后,就可以放心收发了, FIFO 和中断例程会自动搞定一切。

    (6)回环操作

    UART 可以进入一个内部回环(Loopback)模式,用于诊断或调试。在回环模式下,从Tx 上发送的数据将被Rx 输入端接收。

    三、STM32CubeMx配置

    正常创建工程,启用USART1,相关设置如下:

    四、UART发送

    1、初始化说明

    CubeMX生成的UART初始化代码(在usart.c中)

    1 UART_HandleTypeDef huart1;
     2 
     3 /* USART1 init function */
     4 
     5 void MX_USART1_UART_Init(void)
     6 {
     7 
     8   huart1.Instance = USART1;
     9   huart1.Init.BaudRate = 115200;
    10   huart1.Init.WordLength = UART_WORDLENGTH_8B;
    11   huart1.Init.StopBits = UART_STOPBITS_1;
    12   huart1.Init.Parity = UART_PARITY_NONE;
    13   huart1.Init.Mode = UART_MODE_TX_RX;
    14   huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    15   huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    16   if (HAL_UART_Init(&huart1) != HAL_OK)
    17   {
    18     Error_Handler();
    19   }
    20 
    21 }
    22 
    23 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
    24 {
    25 
    26   GPIO_InitTypeDef GPIO_InitStruct = {0};
    27   if(uartHandle->Instance==USART1)
    28   {
    29   /* USER CODE BEGIN USART1_MspInit 0 */
    30 
    31   /* USER CODE END USART1_MspInit 0 */
    32     /* USART1 clock enable */
    33     __HAL_RCC_USART1_CLK_ENABLE();
    34   
    35     __HAL_RCC_GPIOA_CLK_ENABLE();
    36     /**USART1 GPIO Configuration    
    37     PA9     ------> USART1_TX
    38     PA10     ------> USART1_RX 
    39     */
    40     GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
    41     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    42     GPIO_InitStruct.Pull = GPIO_PULLUP;
    43     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    44     GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    45     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    46 
    47   /* USER CODE BEGIN USART1_MspInit 1 */
    48 
    49   /* USER CODE END USART1_MspInit 1 */
    50   }
    51 }
    52 
    53 void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
    54 {
    55 
    56   if(uartHandle->Instance==USART1)
    57   {
    58   /* USER CODE BEGIN USART1_MspDeInit 0 */
    59 
    60   /* USER CODE END USART1_MspDeInit 0 */
    61     /* Peripheral clock disable */
    62     __HAL_RCC_USART1_CLK_DISABLE();
    63   
    64     /**USART1 GPIO Configuration    
    65     PA9     ------> USART1_TX
    66     PA10     ------> USART1_RX 
    67     */
    68     HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
    69 
    70   /* USER CODE BEGIN USART1_MspDeInit 1 */
    71 
    72   /* USER CODE END USART1_MspDeInit 1 */
    73   }
    74 } 
    USART init
    

    2、HAL库函数说明

    HAL_UART_Transmit(在stm32f4xx_hal_uart.c中),该函数能够通过huart串口发送Size位pData数据。
    参数说明:

  • huart :选择用来发送的UART串口
  • pData :指向将要发送的数据的指针
  • Size :发送数据的大小
  • Timeout:超时时间
  • 3、代码实现UART发送

    (1)直接发送

    在main主函数中定义一个数组:

    1   /* USER CODE BEGIN 1 */
    2     unsigned char uTx_Data[5] = {0x41, 0x42, 0x43, 0x44, 0x45};    //数组内十六进制代表“ABCDE”
    3   /* USER CODE END 1 */
    

    在main主函数中的while循环中调用HAL库UART发送函数:

    /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
            /* UART发送 */
          HAL_UART_Transmit(&huart1, uTx_Data, sizeof(uTx_Data), 0xffff);
            /* 延迟1s */
            HAL_Delay(1000);
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    

    整体的main函数如下:

    int main(void)
    {
      /* USER CODE BEGIN 1 */
        unsigned char uTx_Data[5] = {0x41, 0x42, 0x43, 0x44, 0x45};    //数组内十六进制代表“ABCDE”
      /* USER CODE END 1 */
    
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
            /* UART发送 */
          HAL_UART_Transmit(&huart1, uTx_Data, sizeof(uTx_Data), 0xffff);
            /* 延迟1s */
            HAL_Delay(1000);
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    

    通过编译下载,可在串口助手中显示发送的数据:

    (2)字符串发送

    前面的发送方式,不仅要传入句柄参数,还有数组、长度、超时时间参数。

    为了简便发送,我们可以专门写一个字符串发送函数,可以直接传入一个数组即可发送,可以更简便地实现字符串发送。

    优点是,发送数据更简便,能够一次性发送很长的数据数组。

    但缺点就是不能控制发送的长度,会将整个数据数组发出。

    在Uart.c中添加vUser_UART_SendString函数

    /* USER CODE BEGIN 1 */
    void vUser_UART_SendString(UART_HandleTypeDef* uartHandle, unsigned char * uData)
    {
        /* -1- 判断数据是否发送完毕 */
        while(*uData)        //若为空即发送完毕,若不为空则还有数据
        {
            /* -2- 发送1Byte */
            HAL_UART_Transmit(uartHandle, uData, 1, 0xffff);
            /* -3- 移至下1Byte */
            uData++;
        }
    }
    /* USER CODE END 1 */
    

    在Uart.h中声明一下vUser_UART_SendString函数(声明后就可以在别的地方调用该函数)

    1 /* USER CODE BEGIN Prototypes */
    2 extern void vUser_UART_SendString(UART_HandleTypeDef* uartHandle, unsigned char * uData);
    3 /* USER CODE END Prototypes */
    

    在main主函数中定义一个数组

    1   /* USER CODE BEGIN 1 */
    2     unsigned char uTx_Data[] = "\r\n Hallo World! 你好,世界!";
    3   /* USER CODE END 1 */
    

    在main主函数的while循环中调用字符串发送函数

    /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
            /* 字符串发送 */
          vUser_UART_SendString(&huart1, uTx_Data);
            /* 延迟1s */
            HAL_Delay(1000);
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    

    整个main函数如下:

    int main(void)
    {
      /* USER CODE BEGIN 1 */
        unsigned char uTx_Data[] = "\r\n Hallo World! 你好,世界!";
      /* USER CODE END 1 */
    
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
            /* UART发送 */
          vUser_UART_SendString(&huart1, uTx_Data);
            /* 延迟1s */
            HAL_Delay(1000);
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    

    编译下载后在串口助手中显示如下:

    这种发送方式就是相当于编写c语言的时候,在小黑框中打印自己想要打印的东西;通过printf发送,我们也可以在串口助手上实现一样的功能。

    五、UART接收

    1、初始化说明

    UART接收在原本配置CubeMx的基础上,添加一些UART的中断配置来实现中断接收操作。

    使能串口中断

    设置中断优先级(如果没开启其他中断,那就默认即可,直接跳过)

    重新生成代码

    2、函数说明

    (1)CubeMx生成的UART中断处理函数(在stm32f1xx_it.c中)

    当USART1发生中断事件时,程序会进行该函数,所以我们会在这个函数编写好程序,来处理我们的中断事件。

    /**
     * @brief This function handles USART1 global interrupt.
      */
    void USART1_IRQHandler(void)
    {
      /* USER CODE BEGIN USART1_IRQn 0 */
    
      /* USER CODE END USART1_IRQn 0 */
      HAL_UART_IRQHandler(&huart1);
      /* USER CODE BEGIN USART1_IRQn 1 */
    
      /* USER CODE END USART1_IRQn 1 */
    }
    

    (2)HAL库函数HAL_UART_Transmit(在stm32f4xx_hal_uart.c中)

    该函数能够通过huart串口发送Size位pData数据。
    参数说明:

  • huart :选择用来发送的UART串口
  • pData :指向将要发送的数据的指针
  • Size :发送数据的大小
  • Timeout:超时时间
  • (3)HAL库函数HAL_UART_Receive(在stm32f4xx_hal_uart.c中)

  • huart :选择用来接收的UART串口
  • pData :指向将要存放数据的指针
  • Size :发送数据的大小
  • Timeout:超时时间
  • 3、代码编写:实现UART接收

    (1)直接接收(不建议)

    1)在main主函数中定义一个变量,负责接收数据

    1   /* USER CODE BEGIN 1 */
    2     unsigned char uRx_Data = 0;
    3   /* USER CODE END 1 */
    

    2)在main主函数while循环中调用HAL库UART接收函数

    1   /* Infinite loop */
     2   /* USER CODE BEGIN WHILE */
     3   while (1)
     4   {
     5         /* 判断是否接收成功 */
     6         if(HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000) == HAL_OK)
     7         {
     8             /* 将接收成功的数据通过串口发出来 */
     9             HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff);
    10         }
    11         
    12     /* USER CODE END WHILE */
    13 
    14     /* USER CODE BEGIN 3 */
    15   }
    16   /* USER CODE END 3 */
    

    整个main函数如下:

    1 /**
     2   * @brief  The application entry point.
     3   * @retval int
     4   */
     5 int main(void)
     6 {
     7   /* USER CODE BEGIN 1 */
     8     unsigned char uRx_Data = 0;
     9   /* USER CODE END 1 */
    10   
    11 
    12   /* MCU Configuration--------------------------------------------------------*/
    13 
    14   /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    15   HAL_Init();
    16 
    17   /* USER CODE BEGIN Init */
    18 
    19   /* USER CODE END Init */
    20 
    21   /* Configure the system clock */
    22   SystemClock_Config();
    23 
    24   /* USER CODE BEGIN SysInit */
    25 
    26   /* USER CODE END SysInit */
    27 
    28   /* Initialize all configured peripherals */
    29   MX_GPIO_Init();
    30   MX_USART1_UART_Init();
    31   /* USER CODE BEGIN 2 */
    32 
    33   /* USER CODE END 2 */
    34 
    35   /* Infinite loop */
    36   /* USER CODE BEGIN WHILE */
    37   while (1)
    38   {
    39         /* 判断是否接收成功 */
    40         if(HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000) == HAL_OK)
    41         {
    42             /* 将接收成功的数据通过串口发出来 */
    43             HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff);
    44         }
    45         
    46     /* USER CODE END WHILE */
    47 
    48     /* USER CODE BEGIN 3 */
    49   }
    50   /* USER CODE END 3 */
    51 }
    

    3)编译、下载烧写后实现效果如下

    这种接收方式是直接在main函数里的while循环里不断接收,会严重占用程序的进程,且接收较长的数据时,会发生接收错误,如下:

    (2)中断接收(接收并发送)(不推荐)

    1)在HAL_UART_MspInit(在usart.c中)使能接收中断

    1   /* USER CODE BEGIN USART1_MspInit 1 */
    2     __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE);
    3   /* USER CODE END USART1_MspInit 1 */
    

    整个HAL_UART_MspInit函数如下:

    1 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
     2 {
     3 
     4   GPIO_InitTypeDef GPIO_InitStruct = {0};
     5   if(uartHandle->Instance==USART1)
     6   {
     7   /* USER CODE BEGIN USART1_MspInit 0 */
     8 
     9   /* USER CODE END USART1_MspInit 0 */
    10     /* USART1 clock enable */
    11     __HAL_RCC_USART1_CLK_ENABLE();
    12   
    13     __HAL_RCC_GPIOA_CLK_ENABLE();
    14     /**USART1 GPIO Configuration    
    15     PA9     ------> USART1_TX
    16     PA10     ------> USART1_RX 
    17     */
    18     GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
    19     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    20     GPIO_InitStruct.Pull = GPIO_PULLUP;
    21     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    22     GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    23     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    24 
    25     /* USART1 interrupt Init */
    26     HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    27     HAL_NVIC_EnableIRQ(USART1_IRQn);
    28   /* USER CODE BEGIN USART1_MspInit 1 */
    29     __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE);
    30   /* USER CODE END USART1_MspInit 1 */
    31   }
    32 }
    

    2)在USART1_IRQHandler(在stm32f4xx_it.c中)定义一个变量,负责接收数据

    1 unsigned char uRx_Data = 0;
    

    3)在USART1_IRQHandler(在stm32f4xx_it.c中)调用HAL库的UART接收函数以及发送函数

    1     /* -1- 接收 */
    2     HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000);
    3     /* -2- 将接收成功的数据通过串口发出去 */
    4     HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff);
    

    整个USART1_IRQHandler(在stm32f4xx_it.c中)函数如下:

    1 /**
     2   * @brief This function handles USART1 global interrupt.
     3   */
     4 void USART1_IRQHandler(void)
     5 {
     6   /* USER CODE BEGIN USART1_IRQn 0 */
     7     unsigned char uRx_Data;
     8     
     9     /* -1- 接收 */
    10     HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000);
    11     /* -2- 将接收成功的数据通过串口发出去 */
    12     HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff);
    13     
    14   /* USER CODE END USART1_IRQn 0 */
    15   HAL_UART_IRQHandler(&huart1);
    16   /* USER CODE BEGIN USART1_IRQn 1 */
    17 
    18   /* USER CODE END USART1_IRQn 1 */
    19 }
    

    4)编译、下载烧写实现效果如下

    相对于前面的直接接收方式,该中断接收方式就显得特别人性化了,在没有什么特别事件的时候,单片机会按照原本的程序运行着,等到有数据从UART串口发送过来时,会马上进入UART串口的中断处理函数中,完成相应的中断处理操作,完成后会退出中断函数,并继续原本在进行的程序,这样就不会占用单片机程序太多的进程了。

    但仍会发生前面直接接收方式的接收异常状况,主要原因是,在中断处理函数中,我们在接收了数据后并紧接着作出发送的操作,这会出现一个状况,还没来得及将上一次接收到的数据发送出去,就进入下一次接收的中断,然而导致失去了一些数据了。

    (3)中断接收(先接收完,后处理)(推荐)

    这种接收方式,是在方式2的基础上稍作改进的,较于前两种接收方式,是更好的一种接收方式,不会给原本的程序进程造成太大影响。还可以先接收全部数据(提示:通过定义一个较大的数组来存储),再将数据进行处理,这样能确保接收数据的完整性,并能将数据进行有效的处理、分析。

    既然这种方式明显会好一点,那为什么一开始不用这个方式呢?因为通过前面两种方法,可以更容易明白UART接收的操作。

    而这次就只要在方式2的基础上作出一些简单的修改就可以了。

    1)在HAL_UART_MspInit(在usart.c中)使能接收中断(与方式2相同)

    1   /* USER CODE BEGIN USART1_MspInit 1 */
    2     __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE);
    3   /* USER CODE END USART1_MspInit 1 */
    

    整个HAL_UART_MspInit(在usart.c中)函数如下:

    1 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
     2 {
     3 
     4   GPIO_InitTypeDef GPIO_InitStruct = {0};
     5   if(uartHandle->Instance==USART1)
     6   {
     7   /* USER CODE BEGIN USART1_MspInit 0 */
     8 
     9   /* USER CODE END USART1_MspInit 0 */
    10     /* USART1 clock enable */
    11     __HAL_RCC_USART1_CLK_ENABLE();
    12   
    13     __HAL_RCC_GPIOA_CLK_ENABLE();
    14     /**USART1 GPIO Configuration    
    15     PA9     ------> USART1_TX
    16     PA10     ------> USART1_RX 
    17     */
    18     GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
    19     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    20     GPIO_InitStruct.Pull = GPIO_PULLUP;
    21     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    22     GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    23     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    24 
    25     /* USART1 interrupt Init */
    26     HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    27     HAL_NVIC_EnableIRQ(USART1_IRQn);
    28   /* USER CODE BEGIN USART1_MspInit 1 */
    29     __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE);
    30   /* USER CODE END USART1_MspInit 1 */
    31   }
    32 }
    

    2)在USART1_IRQHandler(在stm32f4xx_it.c中)定义三个静态变量

    1     static unsigned char     uRx_Data[1024] = {0}     ;    //存储数组
    2     static unsigned char  *  pRx_Data       = uRx_Data;    //指向存储数组将要存储数据的位
    3     static unsigned char     uLength        =  0  
    

    3)在USART1_IRQHandler(在stm32f4xx_it.c中)调用HAL库的UART接收函数以及发送函数

    注:
      如下的第2、3步都可以根据自身要求进行改进。

  • 第2步:判断接收结束条件,这个可以根据自己想要接收何种类型的数据而定。

  • 第3步:数据处理,大家可以在这一步执行自己想要对数据做的一些操作,我这里只是将接收到的数据重新发送出去而已。

  • 1     /* -1- 接收数据 */
     2     HAL_UART_Receive(&huart1, pRx_Data, 1, 1000);
     3     
     4     /* -2- 判断数据结尾 */
     5     if(*pRx_Data == '\n')
     6     {
     7         /* -3- 将接收成功的数据通过串口发出去 */
     8         HAL_UART_Transmit(&huart1, uRx_Data, uLength, 0xffff);
     9         
    10         /* -4- 初始化指针和数据长度 */
    11         pRx_Data = uRx_Data;  //重新指向数组起始位置
    12         uLength  = 0;         //长度清零
    13     }
    14     /* -5- 若未结束,指针往下一位移动,长度自增一 */
    15     else
    16     {
    17         pRx_Data++;
    18         uLength++;
    19     }
    

    整个USART1_IRQHandler(在stm32f4xx_it.c中)函数如下:

    1 /**
     2   * @brief This function handles USART1 global interrupt.
     3   */
     4 void USART1_IRQHandler(void)
     5 {
     6   /* USER CODE BEGIN USART1_IRQn 0 */
     7     static unsigned char   uRx_Data[1024] = {0}     ;    //存储数组
     8     static unsigned char * pRx_Data       = uRx_Data;    //指向存储数组将要存储数据的位
     9     static unsigned char   uLength        =  0      ;    //接收数据长度
    10     
    11     /* -1- 接收数据 */
    12     HAL_UART_Receive(&huart1, pRx_Data, 1, 1000);
    13     
    14     /* -2- 判断数据结尾 */
    15     if(*pRx_Data == '\n')
    16     {
    17         /* -3- 将接收成功的数据通过串口发出去 */
    18         HAL_UART_Transmit(&huart1, uRx_Data, uLength, 0xffff);
    19         
    20         /* -4- 初始化指针和数据长度 */
    21         pRx_Data = uRx_Data;   //重新指向数组起始位置
    22         uLength  = 0;          //长度清零
    23     }
    24     /* -5- 若未结束,指针往下一位移动,长度自增一 */
    25     else
    26     {
    27         pRx_Data++;
    28         uLength++;
    29     }
    30     
    31     
    32   /* USER CODE END USART1_IRQn 0 */
    33   HAL_UART_IRQHandler(&huart1);
    34   /* USER CODE BEGIN USART1_IRQn 1 */
    35 
    36   /* USER CODE END USART1_IRQn 1 */
    37 }
    

    4)编译、下载烧写后实现效果如下

    除了上面的方法,还有DMA接收方法没介绍。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于STM32的UART串口通信实现【单片机教程】

    发表评论