【单片机】基于STM32的UART串口通信

这篇具有很好参考价值的文章主要介绍了【单片机】基于STM32的UART串口通信。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

简单讲解一下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通信协议

stm32 usart通信实验 发送字符串,单片机,stm32,嵌入式硬件

  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,相关设置如下:
stm32 usart通信实验 发送字符串,单片机,stm32,嵌入式硬件

四、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 */
}

通过编译下载,可在串口助手中显示发送的数据:
stm32 usart通信实验 发送字符串,单片机,stm32,嵌入式硬件

(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 */
}

编译下载后在串口助手中显示如下:
stm32 usart通信实验 发送字符串,单片机,stm32,嵌入式硬件
这种发送方式就是相当于编写c语言的时候,在小黑框中打印自己想要打印的东西;通过printf发送,我们也可以在串口助手上实现一样的功能。

五、UART接收

1、初始化说明

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

使能串口中断
stm32 usart通信实验 发送字符串,单片机,stm32,嵌入式硬件
设置中断优先级(如果没开启其他中断,那就默认即可,直接跳过)
stm32 usart通信实验 发送字符串,单片机,stm32,嵌入式硬件
重新生成代码

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)编译、下载烧写后实现效果如下
stm32 usart通信实验 发送字符串,单片机,stm32,嵌入式硬件
这种接收方式是直接在main函数里的while循环里不断接收,会严重占用程序的进程,且接收较长的数据时,会发生接收错误,如下:
stm32 usart通信实验 发送字符串,单片机,stm32,嵌入式硬件

(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)编译、下载烧写实现效果如下
stm32 usart通信实验 发送字符串,单片机,stm32,嵌入式硬件
相对于前面的直接接收方式,该中断接收方式就显得特别人性化了,在没有什么特别事件的时候,单片机会按照原本的程序运行着,等到有数据从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)编译、下载烧写后实现效果如下
stm32 usart通信实验 发送字符串,单片机,stm32,嵌入式硬件
除了上面的方法,还有DMA接收方法没介绍。文章来源地址https://www.toymoban.com/news/detail-799069.html

到了这里,关于【单片机】基于STM32的UART串口通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • K_A19_002 基于STM32等单片机采集水位检测传感数据 串口与OLED0.96双显示

    单片机型号 测试条件 模块名称 代码功能 STC89C52RC 晶振11.0592M 水位检测传感模块 STC89C52RC驱动水位检测传感模块 串口与OLED0.96双显示 STM32F103C8T6 晶振8M/系统时钟72M 水位检测传感模块 STM32F103C8T6驱动水位检测传感模块参数 串口与OLED0.96双显示 其他资料目录 直戳跳转 工作电压:

    2024年02月07日
    浏览(51)
  • K_A16_001 基于STM32等单片机驱动HX711称重模块 串口与OLED0.96双显示

    单片机型号 测试条件 模块名称 代码功能 STC89C52RC 晶振11.0592M HX711称重模块 STC89C52RC驱动HX711称重模块 串口与OLED0.96双显示 STM32F103C8T6 晶振8M/系统时钟72M HX711称重模块 STM32F103C8T6驱动HX711称重模块参数 串口与OLED0.96双显示 其他资料目录 直戳跳转 HX711参数 1.两路可选择差分输入

    2023年04月27日
    浏览(68)
  • K_A12_033 基于STM32等单片机驱动TCS34725颜色传感 串口与OLED0.96双显示

    单片机型号 测试条件 模块名称 代码功能 STC89C52RC 晶振11.0592M TCS34725颜色传感 模块 STC89C52RC驱动TCS34725颜色传感模块串口与OLED0.96双显示 STM32F103C8T6 晶振8M/系统时钟72M TCS34725颜色传感模块 STM32F103C8T6驱动TCS34725颜色传感模块串口与OLED0.96双显示 其他资料目录 直戳跳转 工作电压:

    2024年02月06日
    浏览(51)
  • 使用标准库和HAL库的STM32单片机进行串口通信/解决因例程为HAL库的传感器,而其他模块都是标准库,需要将数据用串口传送给标准库的单片机的相关问题

    (本文使用STM32F103C8T6,在CubeMX里演示用的是RBT6,但实际上引脚是一样的)         本文 着重解决一个大工程中,某些传感器的例程是HAL库的,而其他模块(或算法)都是标准库,导致难以移植的问题。 本文的解决方法是:使用一片单片机用HAL库(CubeMX)配置例程,然后

    2024年02月06日
    浏览(64)
  • K_A16_003 基于STM32等单片机采集薄膜压力传感器参数串口与OLED0.96双显示

    单片机型号 测试条件 模块名称 代码功能 STM32F103C8T6 晶振8M/系统时钟72M 薄膜压力传感器模块 STM32F103C8T6驱动薄膜压力传感器模块 串口与OLED0.96双显示 其他资料目录 直戳跳转 厚度 :0.4mm 样式 :薄片状,柔性 触发力 :20g,默认电阻值小于 200kΩ 时触发 压力感应范围 :20g~6kg

    2024年02月05日
    浏览(51)
  • K_A18_001 基于STM32等单片机采集MQ2传感参数串口与OLED0.96双显示

    单片机型号 测试条件 模块名称 代码功能 STC89C52RC 晶振11.0592M MQ2传感模块 STC89C52RC采集MQ2传感模块参数 串口与OLED0.96双显示 STM32F103C8T6 晶振8M/系统时钟72M MQ2传感模块 STM32F103C8T6采集MQ2传感模块参数 串口与OLED0.96双显示 其他资料目录 直戳跳转 可以用于家庭和工厂的气体泄漏监

    2024年02月01日
    浏览(87)
  • K_A37_005 基于STM32等单片机驱动ADS1115 ADC模块 串口与OLED0.96双显示

    其他资料目录 直戳跳转 单片机型号 测试条件 模块名称 代码功能 STC89C52RC 晶振11.0592M ADS1115 ADC模块 STC89C52RC驱动ADS1115 ADC模块 串口与OLED0.96双显示 STM32F103C8T6 晶振8M/系统时钟72M ADS1115 ADC模块 STM32F103C8T6驱动ADS1115 ADC模块 串口与OLED0.96双显示 ADS1115 ADC模块 引脚说明 VIN 正极 5V供电

    2024年02月16日
    浏览(57)
  • STM32单片机(九)USART串口----第一节:USART串口协议

    ❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分;建议先学习51单片机,其是STM32等高级单片机的基础;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 :适用于想要从零基础开始学习入门单片机,且有一定C语言基础的的童鞋

    2024年02月16日
    浏览(72)
  • K_A18_008 基于STM32等单片机驱动SGP30气体传感器串口与OLED0.96双显示

    其他资料目录 直戳跳转 单片机型号 测试条件 模块名称 代码功能 STC89C52RC 晶振11.0592M SGP30气体传感器 模块 STC89C52RC驱动SGP30气体传感器模块串口与OLED0.96双显示 STM32F103C8T6 晶振8M/系统时钟72M SGP30气体传感器模块 STM32F103C8T6驱动SGP30气体传感器模块串口与OLED0.96双显示 SGP30气体传

    2024年02月04日
    浏览(56)
  • STM32单片机(九)USART串口----第三节:USART串口实战练习(串口发送)

    ❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分;建议先学习51单片机,其是STM32等高级单片机的基础;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 :适用于想要从零基础开始学习入门单片机,且有一定C语言基础的的童鞋

    2024年02月10日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包