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

您所在的位置:网站首页 stm32移植mm32 【单片机】基于STM32的UART串口通信

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

2023-06-07 22:34| 来源: 网络整理| 查看: 265

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3