【物联网学习笔记】串口接收

发布时间:2026/7/10 6:42:16

【物联网学习笔记】串口接收 前言本文是本人备赛物联网赛项的学习笔记主要供本人学习、复习不是经验分享或教学若有错误大佬轻喷。一、学习目标理解串口IDLE 空闲中断的工作原理与使用场景掌握DMA 串口 IDLE 中断实现不定长数据收发的核心方案从零完成 CubeMX 工程配置→代码编写→功能验证全流程实现串口数据回环、LED 控制、按键读取三大基础功能生成可复用的app.c/app.h驱动文件替代网课品牌缩写代码二、核心理论基础2.1 为什么用 IDLE 中断 DMA传统串口接收存在两大痛点固定长度接收无法适配上位机发送的任意长度数据轮询接收需要 CPU 持续占用资源等待数据效率极低IDLE 空闲中断 DMA是 STM32 最主流的不定长串口接收方案优势DMA串口收到的数据直接由 DMA 搬运到内存全程无需 CPU 干预极大节省资源IDLE 中断检测到串口总线空闲一帧数据发送完成时触发中断精准判断一帧数据的结束完美适配不定长数据2.2 核心概念详解串口 IDLE 空闲中断定义串口总线在接收到第一个数据后持续 1 个字节的传输时间内没有新数据到来就会触发 IDLE 空闲中断关键特性无数据时不会触发必须先收到数据再进入空闲状态才会触发标志位不会自动清零必须在中断服务函数中手动清除清除标志位后必须再次收到新数据才会重新触发下一次中断DMA 直接存储器访问定义无需 CPU 参与直接实现外设与内存、内存与内存之间的数据高速传输本场景用法配置 DMA 为外设→内存模式串口收到的每一个字节都会被 DMA 自动搬运到我们定义的接收缓存数组中CPU 只需要在一帧数据接收完成IDLE 中断触发后读取缓存数组即可三、开发环境与硬件准备3.1 软件环境STM32CubeMX任意版本通用配置逻辑MDK-Keil5ARMCC V5 编译器串口调试助手如 SSCOM、串口助手3.2 硬件准备STM32 物联网开发板本教程以 CT127C/STM32WLE5CC 为例其他型号通用USB 转 TTL 模块Type-C 数据线 / 下载器杜邦线若干四、CubeMX 保姆级全步骤配置步骤 1新建工程选择 MCU 型号打开 STM32CubeMX点击「ACCESS TO MCU SELECTOR」在搜索框输入你的开发板 MCU 型号如 STM32WLE5CCU6选中后点击「Start Project」步骤 2配置系统核心时钟 RCC左侧侧边栏点击「RCC」High Speed Clock (HSE) 选择「Crystal/Ceramic Resonator」外部高速晶振其他保持默认完成基础时钟配置步骤 3配置调试接口防止芯片锁死左侧侧边栏点击「SYS」Debug 选项选择「Serial Wire」串行线调试模式其他保持默认步骤 4配置 USART2 串口基础参数左侧侧边栏点击「USART2」Mode 选择「Asynchronous」异步串口通信模式下方参数配置通用串口参数波特率Baud Rate115200 Bits/s数据位Word Length8 Bits校验位ParityNone停止位Stop Bits1数据流控制Hardware Flow ControlDisabled其他保持默认步骤 5配置 USART2 RX 接收 DMA在 USART2 配置界面点击上方「DMA Settings」选项卡点击「Add」按钮添加 DMA 请求选择「USART2_RX」对新增的 DMA 通道进行如下配置方向DirectionPeripheral To Memory外设→内存串口外设数据搬到内存数组优先级PriorityLow可根据需求调整数值越小优先级越高模式ModeNormal正常模式非循环模式数据宽度Data WidthByte字节与串口数据位匹配地址递增Increment AddressMemory 勾选Peripheral 不勾选关键内存地址自增才能把每个字节依次存到数组的不同位置否则数据会被覆盖配置完成后保持界面步骤 6配置 NVIC 中断优先级在 USART2 配置界面点击上方「NVIC Settings」选项卡勾选「USART2 global interrupt」使能串口全局中断左侧侧边栏点击「NVIC」找到你配置的 DMA 通道如 DMA1 Channel1 global interrupt勾选使能配置两个中断的抢占优先级与响应优先级数值越小优先级越高保持两个中断优先级一致即可如抢占优先级 1响应优先级 0步骤 7配置时钟树点击上方「Clock Configuration」选项卡配置系统时钟为开发板最高主频如 STM32WLE5CC 配置为 48MHz确保 USART2 的时钟源配置正确无红色报错提示步骤 8工程管理与代码生成点击上方「Project Manager」选项卡「Project」栏目配置工程名称、工程保存路径路径不能有中文、空格Toolchain/IDE 选择「MDK-ARM V5」「Code Generator」栏目勾选「Generate peripheral initialization as a pair of .c/.h files per peripheral」每个外设生成独立的.c/.h 文件点击右上角「GENERATE CODE」等待工程生成完成点击「Open Project」直接打开 Keil 工程五、工程代码完整实现app.c/app.h5.1 文件添加说明app.h头文件存放宏定义、函数声明添加到工程的Inc文件夹下app.c源文件存放函数实现添加到工程的Src文件夹下5.2 app.h 完整代码#ifndef __APP_H #define __APP_H #include string.h #include stdio.h #include gpio.h // 状态宏定义 #define APP_ON 1 // 开/有效状态 #define APP_OFF 2 // 关/无效状态 #define APP_TOGGLE 3 // 翻转状态 // 函数声明 // LED控制相关 void app_write_AL(unsigned short int ALx, unsigned char state); unsigned char app_read_AL(unsigned short int ALx); // 按键读取相关 unsigned char app_read_ASW(GPIO_TypeDef *ASWx); // 串口收发相关 void app_tx_UART(const unsigned char *Data); void app_UART_IDLE_rx(void); void app_UART_rx_loop(void); void app_UART_rx_deal(void); // 外设初始化 void app_init(void); #endif5.3 app.c 完整代码#include app.h // 串口接收相关全局变量定义 #define APP_UART_RX_BUFFER_LEN 100 // 串口接收缓存区最大长度可按需修改 unsigned char g_u8app_rx_len; // 一帧串口数据的实际接收长度 unsigned char g_u8app_rx_end_flag; // 一帧数据接收完成标志位 unsigned char g_u8app_rx_buffer[APP_UART_RX_BUFFER_LEN]; // 串口接收数据缓存数组 /************************************************* * 函数名app_write_AL * 作用控制LED灯的亮灭/翻转 * 形参ALx - LED对应的GPIO引脚如GPIO_PIN_0 * state - LED状态APP_ON亮/APP_OFF灭/APP_TOGGLE翻转 * 返回值无 *************************************************/ void app_write_AL(unsigned short int ALx, unsigned char state) { switch (state) { case APP_ON: HAL_GPIO_WritePin(GPIOB, ALx, GPIO_PIN_RESET); break; case APP_OFF: HAL_GPIO_WritePin(GPIOB, ALx, GPIO_PIN_SET); break; case APP_TOGGLE: HAL_GPIO_TogglePin(GPIOB, ALx); break; default: break; } } /************************************************* * 函数名app_read_AL * 作用读取LED灯当前的状态 * 形参ALx - LED对应的GPIO引脚 * 返回值LED当前状态 APP_ON/APP_OFF *************************************************/ unsigned char app_read_AL(unsigned short int ALx) { unsigned char state HAL_GPIO_ReadPin(GPIOB, ALx); if(state GPIO_PIN_SET) return APP_OFF; else return APP_ON; } /************************************************* * 函数名app_read_ASW * 作用读取按键状态带消抖处理 * 形参ASWx - 按键对应的GPIO端口如GPIOA * 返回值按键状态 APP_ON-按下/APP_OFF-松开 * 备注默认按键引脚为GPIO_PIN_8可根据硬件修改 *************************************************/ unsigned char app_read_ASW(GPIO_TypeDef *ASWx) { unsigned char state APP_OFF; if(HAL_GPIO_ReadPin(ASWx, GPIO_PIN_8) GPIO_PIN_RESET) { HAL_Delay(20); // 延时消抖 if(HAL_GPIO_ReadPin(ASWx, GPIO_PIN_8) GPIO_PIN_RESET) { state APP_ON; while(HAL_GPIO_ReadPin(ASWx, GPIO_PIN_8) GPIO_PIN_RESET); // 等待按键松开 } } return state; } /************************************************* * 函数名app_tx_UART * 作用串口发送字符串数据 * 形参Data - 要发送的字符串首地址 * 返回值无 *************************************************/ void app_tx_UART(const unsigned char *Data) { extern UART_HandleTypeDef huart2; // 引入CubeMX生成的串口句柄 HAL_UART_Transmit(huart2, Data, strlen((const char *)Data), 0xFFFF); } /************************************************* * 函数名fputc * 作用重定向C库printf函数到串口支持printf串口打印 * 形参系统标准库函数固定参数 * 返回值发送的字符 *************************************************/ int fputc(int ch, FILE *f) { extern UART_HandleTypeDef huart2; HAL_UART_Transmit(huart2, (unsigned char*)ch, 1, 0xFFFF); return ch; } /************************************************* * 函数名app_UART_IDLE_rx * 作用串口IDLE空闲中断处理函数判断一帧数据接收完成 * 形参无 * 返回值无 * 备注必须在USART2_IRQHandler中断服务函数中调用 *************************************************/ void app_UART_IDLE_rx(void) { extern UART_HandleTypeDef huart2; extern DMA_HandleTypeDef hdma_usart2_rx; // 引入CubeMX生成的DMA句柄 if(__HAL_UART_GET_FLAG(huart2, UART_FLAG_IDLE) SET) // 检测到空闲标志位 { __HAL_UART_CLEAR_IDLEFLAG(huart2); // 手动清除空闲标志位 HAL_UART_DMAStop(huart2); // 停止本次DMA传输 // 计算实际接收的数据长度总缓存长度 - DMA剩余未传输计数 g_u8app_rx_len APP_UART_RX_BUFFER_LEN - __HAL_DMA_GET_COUNTER(hdma_usart2_rx); g_u8app_rx_end_flag 1; // 置位接收完成标志位 } } /************************************************* * 函数名app_UART_rx_loop * 作用串口接收主循环处理函数需在main函数while(1)中调用 * 形参无 * 返回值无 *************************************************/ void app_UART_rx_loop(void) { extern UART_HandleTypeDef huart2; if(g_u8app_rx_end_flag) // 判断是否接收到一帧完整数据 { // 串口数据处理函数可在此自定义数据解析逻辑 app_UART_rx_deal(); // 接收完成后清空标志位与长度重新开启DMA接收 g_u8app_rx_len 0; g_u8app_rx_end_flag 0; HAL_UART_Receive_DMA(huart2, g_u8app_rx_buffer, APP_UART_RX_BUFFER_LEN); } } /************************************************* * 函数名app_UART_rx_deal * 作用串口接收数据处理函数可自定义解析逻辑 * 形参无 * 返回值无 * 备注默认实现串口回环收到什么发回什么 *************************************************/ void app_UART_rx_deal(void) { extern UART_HandleTypeDef huart2; // 回环功能将收到的数据原样发送回上位机 HAL_UART_Transmit(huart2, g_u8app_rx_buffer, g_u8app_rx_len, 0xFFFF); } /************************************************* * 函数名app_init * 作用外设功能初始化需在main函数中while(1)之前调用 * 形参无 * 返回值无 *************************************************/ void app_init(void) { extern UART_HandleTypeDef huart2; __HAL_UART_ENABLE_IT(huart2, UART_IT_IDLE); // 使能串口IDLE空闲中断 HAL_UART_Receive_DMA(huart2, g_u8app_rx_buffer, APP_UART_RX_BUFFER_LEN); // 开启DMA接收 }5.4 串口中断服务函数修改在 Keil 工程中打开stm32wle5xx_it.c文件文件名根据你的 MCU 型号变化找到USART2_IRQHandler函数在其中添加app_UART_IDLE_rx()函数调用必须放在 HAL 库自带的中断处理函数之前修改后代码如下void USART2_IRQHandler(void) { /* USER CODE BEGIN USART2_IRQn 0 */ app_UART_IDLE_rx(); // 新增调用我们写的空闲中断处理函数 /* USER CODE END USART2_IRQn 0 */ HAL_UART_IRQHandler(huart2); /* USER CODE BEGIN USART2_IRQn 1 */ /* USER CODE END USART2_IRQn 1 */ }关键提醒必须把app_UART_IDLE_rx()放在HAL_UART_IRQHandler之前否则 HAL 库会提前清除 IDLE 标志位导致我们的函数无法检测到中断配图USART2_IRQHandler 函数修改完成后的代码截图六、主函数调用与功能集成在 Keil 工程中打开main.c文件在文件顶部的 USER CODE BEGIN Includes 区域包含我们的头文件/* USER CODE BEGIN Includes */ #include app.h /* USER CODE END Includes */在 main 函数中MX_GPIO_Init()、MX_USART2_UART_Init()、MX_DMA_Init()等初始化函数之后while (1) 之前调用我们的初始化函数/* USER CODE BEGIN 2 */ app_init(); // 初始化串口IDLEDMA、使能中断 /* USER CODE END 2 */在 while (1) 主循环中调用串口接收循环处理函数while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ app_UART_rx_loop(); // 串口接收数据循环处理 }七、核心函数逐行详解7.1 空闲中断处理函数app_UART_IDLE_rx核心作用在串口中断中检测空闲标志位计算接收数据长度标记接收完成关键步骤读取 IDLE 标志位判断是否触发空闲中断手动清除 IDLE 标志位避免重复触发中断停止 DMA 传输防止数据继续写入缓存计算实际接收长度总缓存长度 - DMA 剩余未传输的计数值置位接收完成标志位通知主循环处理数据7.2 主循环处理函数app_UART_rx_loop核心作用在主循环中轮询接收完成标志位处理数据并重新开启 DMA 接收关键逻辑检测到一帧数据接收完成后调用数据处理函数清空标志位重新开启 DMA等待下一次数据接收实现持续的不定长数据接收7.3 数据处理函数app_UART_rx_deal核心作用自定义串口数据的解析逻辑默认实现串口回环将收到的数据原样发回上位机可扩展可在此添加指令解析比如收到 ON 点亮 LED收到 OFF 熄灭 LED 等自定义逻辑八、实验验证与现象说明8.1 基础串口回环实验编译工程无报错后下载程序到开发板用 USB 转 TTL 模块连接开发板的 USART2 TX/RX 引脚连接电脑打开串口调试助手配置参数波特率 115200、8 位数据位、1 停止位、无校验在串口调试助手发送框输入任意字符 / 字符串点击发送实验现象串口调试助手接收区会收到你发送的完全一致的数据实现不定长数据回环8.2 附加功能测试LED 控制在 main 函数中调用app_write_AL(GPIO_PIN_5, APP_ON);可点亮对应 LED按键读取在主循环中调用app_read_ASW(GPIOA);按下按键会返回 APP_ON可配合 LED 实现按键控制灯效printf 打印在代码中直接使用printf(Hello STM32\r\n);串口调试助手会收到对应的打印内容九、常见问题排查指南串口发送数据后没有回传数据中断不触发排查 1检查app_UART_IDLE_rx()是否放在USART2_IRQHandler中HAL_UART_IRQHandler之前排查 2检查 CubeMX 中是否使能了 USART2 全局中断和 DMA 通道中断排查 3检查app_init()是否在 main 函数中正确调用是否开启了 IDLE 中断和 DMA 接收串口收到的数据乱码排查 1检查串口调试助手的波特率、数据位、停止位、校验位与 CubeMX 配置完全一致排查 2检查开发板的系统时钟配置是否正确时钟树无报错排查 3检查 USB 转 TTL 模块的接线是否牢固TX/RX 是否交叉连接只能收到第一帧数据后续数据无法接收排查 1检查app_UART_rx_loop()中是否在处理完数据后重新调用了HAL_UART_Receive_DMA开启下一次接收排查 2检查是否正确清空了g_u8app_rx_end_flag和g_u8app_rx_lenprintf 函数无法打印编译报错排查 1在 Keil 工程中点击魔术棒→Target勾选「Use MicroLIB」排查 2检查fputc函数是否正确实现串口句柄是否正确收到的数据长度不对或只有最后一个字节排查 1检查 DMA 配置中是否勾选了 Memory 的地址自增 Increment Address排查 2检查接收缓存数组的长度是否足够发送的数据长度是否超过了APP_UART_RX_BUFFER_LEN

相关新闻