【UART 串口】循环缓冲区+命令解析
下载例程代码:
如何使用例程
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
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"23// 指令的最小长度4#define COMMAND_MIN_LENGTH 456// 循环缓冲区大小7#define BUFFER_SIZE 1288// 循环缓冲区9uint8_t buffer[BUFFER_SIZE];10// 循环缓冲区读索引11uint8_t readIndex = 0;12// 循环缓冲区写索引13uint8_t writeIndex = 0;1415/**16* @brief 增加读索引17* @param length 要增加的长度18*/19void Command_AddReadIndex(uint8_t length) {20 readIndex += length;21 readIndex %= BUFFER_SIZE;22}2324/**25* @brief 读取第i位数据 超过缓存区长度自动循环26* @param i 要读取的数据索引27*/2829uint8_t Command_Read(uint8_t i) {30 uint8_t index = i % BUFFER_SIZE;31 return buffer[index];32}3334/**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_SIZE47// 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//}5758uint8_t Command_GetLength() {59 return (writeIndex + BUFFER_SIZE - readIndex) % BUFFER_SIZE;60}616263/**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}7374/**75* @brief 向缓冲区写入数据76* @param data 要写入的数据指针77* @param length 要写入的数据长度78* @return 写入的数据长度79*/80uint8_t Command_Write(uint8_t *data, uint8_t length) {81 // 如果缓冲区不足 则不写入数据 返回082 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}9798/**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_34#include "main.h"5#include <string.h>67uint8_t Command_Write(uint8_t *data, uint8_t length);89uint8_t Command_GetCommand(uint8_t *command);1011#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循环中获取命令 根据命令控制小灯
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 */67/* 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 }2829/* USER CODE END WHILE */3031/* USER CODE BEGIN 3 */32}