一、前言
简单讲解一下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中)函数如下:文章来源:https://www.toymoban.com/news/detail-799069.html
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接收方法没介绍。文章来源地址https://www.toymoban.com/news/detail-799069.html
到了这里,关于【单片机】基于STM32的UART串口通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!