波特律动
课程中心/STM32基础教程/【UART 串口】循环缓冲区+命令解析

【UART 串口】循环缓冲区+命令解析

下载例程代码

注意

CubeIDE:请按照 例程使用方法🔗 导入例程,否则下载的可能不是例程而是其他工程。

Keil:请使用 ArmCC V6 编译,否则可能会出现编译错误。点击此处查看切换编译器方法🔗

如何使用例程

1️⃣ 编译并下载程序到学习板

2️⃣ 使用配套TYPE-C数据线,将学习板连接到计算机

3️⃣ 打开 波特律动 串口助手 在线串口调试助手,点击“选择串口”,选择USB Single Serial

4️⃣ 发送相应的命令测试功能

红色小灯亮起:

纯文本
1AA 05 01 01 B1

红色小灯熄灭 绿色小灯亮起:

纯文本
1AA 07 01 00 02 01 B5

测试长命令解析:

纯文本
1AA 1A 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 D8

例程讲解

下面介绍了如何自己实现该例程的功能

1、工程配置

1️⃣ 分配引脚:如图,将 PA6、PA7、PB0 配置为 GPIO_Output,并分别设置 User label 为 BLUE、GREEN、RED

信息

左键点击对应的引脚,选择 GPIO_Output; 右键点击对应的引脚,选择 User label,分别输入 BLUE、GREEN、RED

2️⃣ 打开串口2外设:Pinout&Configuration -> Connectivity -> USART2,将 Mode 选择为 Asynchronous

3️⃣ 使能串口中断:在 USART2 -> Configuration -> NVIC Settings 标签卡中,勾选 USART2 global interrupt 的 Enable

2、代码

1️⃣ 在Core/Src文件夹下新建command.c文件

C语言
1#include "command.h"
2
3// 指令的最小长度
4#define COMMAND_MIN_LENGTH 4
5
6// 循环缓冲区大小
7#define BUFFER_SIZE 128
8// 循环缓冲区
9uint8_t buffer[BUFFER_SIZE];
10// 循环缓冲区读索引
11uint8_t readIndex = 0;
12// 循环缓冲区写索引
13uint8_t writeIndex = 0;
14
15/**
16* @brief 增加读索引
17* @param length 要增加的长度
18*/
19void Command_AddReadIndex(uint8_t length) {
20 readIndex += length;
21 readIndex %= BUFFER_SIZE;
22}
23
24/**
25* @brief 读取第i位数据 超过缓存区长度自动循环
26* @param i 要读取的数据索引
27*/
28
29uint8_t Command_Read(uint8_t i) {
30 uint8_t index = i % BUFFER_SIZE;
31 return buffer[index];
32}
33
34/**
35* @brief 计算未处理的数据长度
36* @return 未处理的数据长度
37* @retval 0 缓冲区为空
38* @retval 1~BUFFER_SIZE-1 未处理的数据长度
39* @retval BUFFER_SIZE 缓冲区已满
40*/
41//uint8_t Command_GetLength() {
42// // 读索引等于写索引时,缓冲区为空
43// if (readIndex == writeIndex) {
44// return 0;
45// }
46// // 如果缓冲区已满,返回BUFFER_SIZE
47// if (writeIndex + 1 == readIndex || (writeIndex == BUFFER_SIZE - 1 && readIndex == 0)) {
48// return BUFFER_SIZE;
49// }
50// // 如果缓冲区未满,返回未处理的数据长度
51// if (readIndex <= writeIndex) {
52// return writeIndex - readIndex;
53// } else {
54// return BUFFER_SIZE - readIndex + writeIndex;
55// }
56//}
57
58uint8_t Command_GetLength() {
59 return (writeIndex + BUFFER_SIZE - readIndex) % BUFFER_SIZE;
60}
61
62
63/**
64* @brief 计算缓冲区剩余空间
65* @return 剩余空间
66* @retval 0 缓冲区已满
67* @retval 1~BUFFER_SIZE-1 剩余空间
68* @retval BUFFER_SIZE 缓冲区为空
69*/
70uint8_t Command_GetRemain() {
71 return BUFFER_SIZE - Command_GetLength();
72}
73
74/**
75* @brief 向缓冲区写入数据
76* @param data 要写入的数据指针
77* @param length 要写入的数据长度
78* @return 写入的数据长度
79*/
80uint8_t Command_Write(uint8_t *data, uint8_t length) {
81 // 如果缓冲区不足 则不写入数据 返回0
82 if (Command_GetRemain() < length) {
83 return 0;
84 }
85 // 使用memcpy函数将数据写入缓冲区
86 if (writeIndex + length < BUFFER_SIZE) {
87 memcpy(buffer + writeIndex, data, length);
88 writeIndex += length;
89 } else {
90 uint8_t firstLength = BUFFER_SIZE - writeIndex;
91 memcpy(buffer + writeIndex, data, firstLength);
92 memcpy(buffer, data + firstLength, length - firstLength);
93 writeIndex = length - firstLength;
94 }
95 return length;
96}
97
98/**
99* @brief 尝试获取一条指令
100* @param command 指令存放指针
101* @return 获取的指令长度
102* @retval 0 没有获取到指令
103*/
104uint8_t Command_GetCommand(uint8_t *command) {
105 // 寻找完整指令
106 while (1) {
107 // 如果缓冲区长度小于COMMAND_MIN_LENGTH 则不可能有完整的指令
108 if (Command_GetLength() < COMMAND_MIN_LENGTH) {
109 return 0;
110 }
111 // 如果不是包头 则跳过 重新开始寻找
112 if (Command_Read(readIndex) != 0xAA) {
113 Command_AddReadIndex(1);
114 continue;
115 }
116 // 如果缓冲区长度小于指令长度 则不可能有完整的指令
117 uint8_t length = Command_Read(readIndex + 1);
118 if (Command_GetLength() < length) {
119 return 0;
120 }
121 // 如果校验和不正确 则跳过 重新开始寻找
122 uint8_t sum = 0;
123 for (uint8_t i = 0; i < length - 1; i++) {
124 sum += Command_Read(readIndex + i);
125 }
126 if (sum != Command_Read(readIndex + length - 1)) {
127 Command_AddReadIndex(1);
128 continue;
129 }
130 // 如果找到完整指令 则将指令写入command 返回指令长度
131 for (uint8_t i = 0; i < length; i++) {
132 command[i] = Command_Read(readIndex + i);
133 }
134 Command_AddReadIndex(length);
135 return length;
136 }
137}

2️⃣ 在Core/Inc文件夹下新建command.h文件

C语言
1#ifndef INC_COMMAND_H_
2#define INC_COMMAND_H_
3
4#include "main.h"
5#include <string.h>
6
7uint8_t Command_Write(uint8_t *data, uint8_t length);
8
9uint8_t Command_GetCommand(uint8_t *command);
10
11#endif /* INC_COMMAND_H_ */

3️⃣ 在main.c中 引入command.c 定义串口接收数组 实现串口接收空闲中断回调函数

信息

注意将代码写到对应的注释对中

C语言
1/* Private includes ----------------------------------------------------------*/
2/* USER CODE BEGIN Includes */
3#include "command.h"
4/* USER CODE END Includes */
C语言
1/* Private define ------------------------------------------------------------*/
2/* USER CODE BEGIN PD */
3uint8_t readBuffer[10];
4/* USER CODE END PD */
C语言
1/* USER CODE BEGIN 0 */
2void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){
3 if (huart == &huart2){
4 Command_Write(readBuffer, Size);
5 HAL_UARTEx_ReceiveToIdle_IT(&huart2, readBuffer, sizeof(readBuffer));
6 }
7}
8/* USER CODE END 0 */

4️⃣ 开启串口接收 while循环中获取命令 根据命令控制小灯

信息

rx_data[0] 第一个字符为小灯(0x01:红色,0x02:绿色,0x03:蓝色)

rx_data[1] 第二个字符为状态(0:灭,1:亮)

C语言
1/* USER CODE BEGIN 2 */
2HAL_UARTEx_ReceiveToIdle_IT(&huart2, readBuffer, sizeof(readBuffer));
3uint8_t command[50];
4int commandLength = 0;
5/* USER CODE END 2 */
6
7/* Infinite loop */
8/* USER CODE BEGIN WHILE */
9while (1)
10{
11 commandLength = Command_GetCommand(command);
12 if (commandLength != 0){
13 HAL_UART_Transmit(&huart2, command, commandLength, HAL_MAX_DELAY);
14 for (int i = 2; i < commandLength - 1; i += 2){
15 GPIO_PinState state = GPIO_PIN_SET;
16 if (command[i + 1] == 0x00){
17 state = GPIO_PIN_RESET;
18 }
19 if (command[i] == 0x01){
20 HAL_GPIO_WritePin(RED_GPIO_Port, RED_Pin, state);
21 }else if (command[i] == 0x02){
22 HAL_GPIO_WritePin(GREEN_GPIO_Port, GREEN_Pin, state);
23 }else if (command[i] == 0x03){
24 HAL_GPIO_WritePin(BLUE_GPIO_Port, BLUE_Pin, state);
25 }
26 }
27 }
28
29/* USER CODE END WHILE */
30
31/* USER CODE BEGIN 3 */
32}