/*************** 串口 ***************/ #include stdio.h struct __FILE { int handle; /* Whatever you require here. If the only file you are using is */ /* standard output using printf() for debugging, no file handling */ /* is required. */ }; /* FILE is typedef’d in stdio.h. */ FILE __stdout; int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1,(u8 *)ch,1,50); /* Your implementation of fputc(). */ return ch; } u32 rx_tick 0; void RX_proc() { if(uwTick - rx_tick50) return; rx_tick uwTick; if(rx_pointer 1 rx_buffer[0]#) { printf(成功翻转屏幕\r\n); } } u8 rx_pointer; // 记录当前接收到第几个字节 u8 rx_data; // 存储最新收到的1个字节 u8 rx_buffer[30]; // 缓冲区存储接收到的所有数据 u8 tx_buffer[30]; // 发送缓冲区 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { HAL_UART_Receive_IT(huart1,rx_data,1); // 重新开启接收 rx_buffer[rx_pointer] rx_data; // 保存收到的数据 }千万不要忘记初始化MX_USART1_UART_Init(); HAL_UART_Receive_IT(huart1, rx_data, 1);流程串口助手发送 hello → 板子接收 hello → 板子解析命令 → 板子发送 12345步骤1串口助手发送 hello假设你点击发送串口助手依次发送h → e → l → l → o为什么是一个一个发串口UART是串行通信接口名字中的串就是指串行意思是一个比特一个比特地发送。步骤2板子逐个字符接收中断处理每收到一个字符就会触发一次中断和回调也就是说触发一次中断可以接收8个bit起始位和停止位不接收硬件自动处理起始位和停止位。第1次回调收到 hvoid HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { HAL_UART_Receive_IT(huart1, rx_data, 1); // rx_pointer 当前是 0 rx_buffer[rx_pointer] rx_data; // rx_buffer[0] h rx_pointer; // rx_pointer 变成 1 // 也可以写成一行 // rx_buffer[rx_pointer] rx_data; }后置先使用后增加第2次回调收到 erx_buffer[1] e; // 缓冲区第1个位置存e rx_pointer 2; // 指针变成2第3次回调收到 lrx_buffer[2] l; rx_pointer 3;第4次回调收到 lrx_buffer[3] l; rx_pointer 4;第5次回调收到 orx_buffer[4] o; rx_pointer 5; // 最终指针停在5接收完成后的内存状态步骤3主循环检测到有数据void RX_proc() { if(uwTick - rx_tick 50) return; rx_tick uwTick; // 发现 rx_pointer 0说明收到了数据 if(rx_pointer 0) { // 在这里处理接收到的命令 } }步骤4解析命令u32 rx_tick 0; void RX_proc() { if(uwTick - rx_tick50) return; rx_tick uwTick; if(rx_pointer 1 rx_buffer[0]#) { printf(成功翻转屏幕\r\n); } //如果是多字符命令 hello就需要用 strcmp //if(strcmp((char*)rx_buffer, world) 0) //if(rx_pointer 2 rx_buffer[0]O rx_buffer[1]N) rx_pointer 0; //memset(要清理的内存, 要设置的值, 要清理的长度); memset(rx_buffer, 0, sizeof(rx_buffer)); }解释为什么要有rx_pointer 0;memset(rx_buffer, 0, sizeof(rx_buffer));场景智能家居控制系统假设你要控制两个设备发送ON开灯发送OFF关灯代码不加 memsetvoid RX_proc() { if(uwTick - rx_tick 50) return; rx_tick uwTick; if(rx_pointer 0) { // 不加结束符直接比较 if(rx_pointer 2 rx_buffer[0]O rx_buffer[1]N) { printf(开灯\r\n); HAL_GPIO_WritePin(LED_PIN, 1); } else if(rx_pointer 3 rx_buffer[0]O rx_buffer[1]F rx_buffer[2]F) { printf(关灯\r\n); HAL_GPIO_WritePin(LED_PIN, 0); } rx_pointer 0; // 只清指针不清缓冲区 // ⚠️ 没有 memset } }第一次操作发送 ON接收过程第1次中断rx_buffer[0] Orx_pointer1 第2次中断rx_buffer[1] Nrx_pointer2 缓冲区状态 rx_buffer [O,N, ?, ?, ?, ...] // ? 表示随机值RX_proc 处理rx_pointer 2 ✅ rx_buffer[0]O ✅ rx_buffer[1]N ✅ → 输出 开灯 → rx_pointer 0缓冲区不变处理后缓冲区rx_buffer [O,N, ?, ?, ?, ...] // O,N 还在第二次操作发送 OFF接收过程第1次中断rx_buffer[0] O覆盖原来的O rx_pointer1 第2次中断rx_buffer[1] F覆盖原来的N rx_pointer2 第3次中断rx_buffer[2] F rx_pointer3 缓冲区状态 rx_buffer [O,F,F, ?, ?, ...]看起来没问题等等仔细看原来缓冲区[O,N, x, x, x, ...] ↓ 现在缓冲区[O,F,F, x, x, ...] ↑ ↑ ↑ 新 新 新 好像很干净确实因为正好覆盖了前3个位置第三次操作又发送 ON这是问题的关键接收过程第1次中断rx_buffer[0] O rx_pointer1 第2次中断rx_buffer[1] N rx_pointer2 缓冲区现在 rx_buffer [O,N,F, ?, ?, ...] ↑ ↑ ↑ 新 新 旧F 还在RX_proc 处理rx_pointer 2 ✅ rx_buffer[0]O ✅ rx_buffer[1]N ✅ 但是rx_buffer[2] 还是 F上一次的残留问题来了程序只检查前2个字符发现是ON所以输出 开灯但实际上灯是关着的因为上一次是OFF更可怕的情况假设你发送ON后又发送STOP// 第一次发送 ON rx_buffer [O,N, ?, ?, ...] // 第二次发送 STOP 第1次rx_buffer[0]S // O → S 第2次rx_buffer[1]T // N → T 第3次rx_buffer[2]O // ? → O 第4次rx_buffer[3]P // ? → P rx_buffer [S,T,O,P, ...] // 第三次又发送 ON 第1次rx_buffer[0]O // S → O 第2次rx_buffer[1]N // T → N rx_buffer [O,N,O,P, ...] // ONOP现在缓冲区里有ONOP你想发送ON实际得到ONOP下次如果发OP程序可能误判加了 memset 后的正确行为void RX_proc() { if(uwTick - rx_tick 50) return; rx_tick uwTick; if(rx_pointer 0) { // 先加结束符再用 strcmp rx_buffer[rx_pointer] \0; if(strcmp((char*)rx_buffer, ON) 0) { printf(开灯\r\n); } else if(strcmp((char*)rx_buffer, OFF) 0) { printf(关灯\r\n); } // ✅ 清空缓冲区防止下次干扰 rx_pointer 0; memset(rx_buffer, 0, sizeof(rx_buffer)); } }执行过程第1次 ON 后memset → [0,0,0,0,0,...] 第2次 OFF 后memset → [0,0,0,0,0,...] 第3次 ON 时缓冲区是干净的 接收 O[O,0,0,0,0,...] 接收 N[O,N,0,0,0,...] strcmp 比较遇到0就停止完美匹配 ON解释是启用一次中断回调函数就可以接收完hello的吗接收 hello 需要触发 5 次中断回调函数他中断一次只接收1个字节要想接收下一个字节还得要重新中断。解释当中断还没接收完hello的话我就去if(strcmp((char*)rx_buffer, hello) 0)会怎么样strcmp 比较 h???? 和 hello → 不相等不相等就不执行printf解释那我怎么知道他有没有接收完呢然后再进行if(strcmp((char*)rx_buffer, hello)不管了我就这样理解把他什么时候结束我不管我就看我有没有接收到自己想要的解释if(uwTick - rx_tick50) return; rx_tick uwTick;本质就是让CPU少检查反正我这个数据也不会传输的那么快t1ms: uwTick1 → RX_proc 执行但被延时拦住什么叫接收中途防止超时防止在数据还没发完的时候就因为等太久而错误地认为数据发完了不对我要彻底迷糊了又要我漫长的问deepseek的时光了。总结一句话只要是接收数据的时间超不过50ms就不用在中断回调里面加刷新时间的要是超过50ms的话就得加上更新刷新时间的。我终于知道我哪里不知道了,你这个时间加就是接收数据的时候时间加就加呗你管这个rx_tick啥用啊:rx_tick uwTick 的作用目的记录最后一次收到数据的时间。为什么需要记录这个时间因为串口接收数据不是一次发完的可能是一字节一字节发过来的。比如发#123可能是t0ms收到 #t10ms收到 1t20ms收到 2t30ms收到 3如果每收到一个字节就立刻处理可能会把#123拆成4次处理这就乱了。你想让它怎么处理等数据全部收完再处理。怎么知道收完了——用超时判断。超时判断的原理cvoid RX_proc() { // 如果距离上次收到数据还不到50ms说明可能还在接收中再等等 if(uwTick - rx_tick 50) return; // 不处理 // 超过50ms没收到新数据说明这一串数据收完了开始处理 // 处理收到的数据... }具体例子假设发送#123t0ms: 收到 # → rx_tick0 t10ms: 收到 1 → rx_tick10 t20ms: 收到 2 → rx_tick20 t30ms: 收到 3 → rx_tick30 RX_proc()每50ms被调用一次或者主循环里一直调用 第一次调用(t0ms)uwTick0距离上次0ms50返回 第二次调用(t10ms)距离上次10ms50返回 第三次调用(t20ms)距离上次20ms50返回 第四次调用(t30ms)距离上次30ms50返回 第五次调用(t80ms)距离上次50ms等于50开始处理这样就能保证收到完整的#123后才处理。为什么是50ms因为串口发送一字节大概1ms9600波特率发完一串数据通常不会超过50ms。50ms是一个经验值够用了。
5蓝桥杯串口
/*************** 串口 ***************/ #include stdio.h struct __FILE { int handle; /* Whatever you require here. If the only file you are using is */ /* standard output using printf() for debugging, no file handling */ /* is required. */ }; /* FILE is typedef’d in stdio.h. */ FILE __stdout; int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1,(u8 *)ch,1,50); /* Your implementation of fputc(). */ return ch; } u32 rx_tick 0; void RX_proc() { if(uwTick - rx_tick50) return; rx_tick uwTick; if(rx_pointer 1 rx_buffer[0]#) { printf(成功翻转屏幕\r\n); } } u8 rx_pointer; // 记录当前接收到第几个字节 u8 rx_data; // 存储最新收到的1个字节 u8 rx_buffer[30]; // 缓冲区存储接收到的所有数据 u8 tx_buffer[30]; // 发送缓冲区 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { HAL_UART_Receive_IT(huart1,rx_data,1); // 重新开启接收 rx_buffer[rx_pointer] rx_data; // 保存收到的数据 }千万不要忘记初始化MX_USART1_UART_Init(); HAL_UART_Receive_IT(huart1, rx_data, 1);流程串口助手发送 hello → 板子接收 hello → 板子解析命令 → 板子发送 12345步骤1串口助手发送 hello假设你点击发送串口助手依次发送h → e → l → l → o为什么是一个一个发串口UART是串行通信接口名字中的串就是指串行意思是一个比特一个比特地发送。步骤2板子逐个字符接收中断处理每收到一个字符就会触发一次中断和回调也就是说触发一次中断可以接收8个bit起始位和停止位不接收硬件自动处理起始位和停止位。第1次回调收到 hvoid HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { HAL_UART_Receive_IT(huart1, rx_data, 1); // rx_pointer 当前是 0 rx_buffer[rx_pointer] rx_data; // rx_buffer[0] h rx_pointer; // rx_pointer 变成 1 // 也可以写成一行 // rx_buffer[rx_pointer] rx_data; }后置先使用后增加第2次回调收到 erx_buffer[1] e; // 缓冲区第1个位置存e rx_pointer 2; // 指针变成2第3次回调收到 lrx_buffer[2] l; rx_pointer 3;第4次回调收到 lrx_buffer[3] l; rx_pointer 4;第5次回调收到 orx_buffer[4] o; rx_pointer 5; // 最终指针停在5接收完成后的内存状态步骤3主循环检测到有数据void RX_proc() { if(uwTick - rx_tick 50) return; rx_tick uwTick; // 发现 rx_pointer 0说明收到了数据 if(rx_pointer 0) { // 在这里处理接收到的命令 } }步骤4解析命令u32 rx_tick 0; void RX_proc() { if(uwTick - rx_tick50) return; rx_tick uwTick; if(rx_pointer 1 rx_buffer[0]#) { printf(成功翻转屏幕\r\n); } //如果是多字符命令 hello就需要用 strcmp //if(strcmp((char*)rx_buffer, world) 0) //if(rx_pointer 2 rx_buffer[0]O rx_buffer[1]N) rx_pointer 0; //memset(要清理的内存, 要设置的值, 要清理的长度); memset(rx_buffer, 0, sizeof(rx_buffer)); }解释为什么要有rx_pointer 0;memset(rx_buffer, 0, sizeof(rx_buffer));场景智能家居控制系统假设你要控制两个设备发送ON开灯发送OFF关灯代码不加 memsetvoid RX_proc() { if(uwTick - rx_tick 50) return; rx_tick uwTick; if(rx_pointer 0) { // 不加结束符直接比较 if(rx_pointer 2 rx_buffer[0]O rx_buffer[1]N) { printf(开灯\r\n); HAL_GPIO_WritePin(LED_PIN, 1); } else if(rx_pointer 3 rx_buffer[0]O rx_buffer[1]F rx_buffer[2]F) { printf(关灯\r\n); HAL_GPIO_WritePin(LED_PIN, 0); } rx_pointer 0; // 只清指针不清缓冲区 // ⚠️ 没有 memset } }第一次操作发送 ON接收过程第1次中断rx_buffer[0] Orx_pointer1 第2次中断rx_buffer[1] Nrx_pointer2 缓冲区状态 rx_buffer [O,N, ?, ?, ?, ...] // ? 表示随机值RX_proc 处理rx_pointer 2 ✅ rx_buffer[0]O ✅ rx_buffer[1]N ✅ → 输出 开灯 → rx_pointer 0缓冲区不变处理后缓冲区rx_buffer [O,N, ?, ?, ?, ...] // O,N 还在第二次操作发送 OFF接收过程第1次中断rx_buffer[0] O覆盖原来的O rx_pointer1 第2次中断rx_buffer[1] F覆盖原来的N rx_pointer2 第3次中断rx_buffer[2] F rx_pointer3 缓冲区状态 rx_buffer [O,F,F, ?, ?, ...]看起来没问题等等仔细看原来缓冲区[O,N, x, x, x, ...] ↓ 现在缓冲区[O,F,F, x, x, ...] ↑ ↑ ↑ 新 新 新 好像很干净确实因为正好覆盖了前3个位置第三次操作又发送 ON这是问题的关键接收过程第1次中断rx_buffer[0] O rx_pointer1 第2次中断rx_buffer[1] N rx_pointer2 缓冲区现在 rx_buffer [O,N,F, ?, ?, ...] ↑ ↑ ↑ 新 新 旧F 还在RX_proc 处理rx_pointer 2 ✅ rx_buffer[0]O ✅ rx_buffer[1]N ✅ 但是rx_buffer[2] 还是 F上一次的残留问题来了程序只检查前2个字符发现是ON所以输出 开灯但实际上灯是关着的因为上一次是OFF更可怕的情况假设你发送ON后又发送STOP// 第一次发送 ON rx_buffer [O,N, ?, ?, ...] // 第二次发送 STOP 第1次rx_buffer[0]S // O → S 第2次rx_buffer[1]T // N → T 第3次rx_buffer[2]O // ? → O 第4次rx_buffer[3]P // ? → P rx_buffer [S,T,O,P, ...] // 第三次又发送 ON 第1次rx_buffer[0]O // S → O 第2次rx_buffer[1]N // T → N rx_buffer [O,N,O,P, ...] // ONOP现在缓冲区里有ONOP你想发送ON实际得到ONOP下次如果发OP程序可能误判加了 memset 后的正确行为void RX_proc() { if(uwTick - rx_tick 50) return; rx_tick uwTick; if(rx_pointer 0) { // 先加结束符再用 strcmp rx_buffer[rx_pointer] \0; if(strcmp((char*)rx_buffer, ON) 0) { printf(开灯\r\n); } else if(strcmp((char*)rx_buffer, OFF) 0) { printf(关灯\r\n); } // ✅ 清空缓冲区防止下次干扰 rx_pointer 0; memset(rx_buffer, 0, sizeof(rx_buffer)); } }执行过程第1次 ON 后memset → [0,0,0,0,0,...] 第2次 OFF 后memset → [0,0,0,0,0,...] 第3次 ON 时缓冲区是干净的 接收 O[O,0,0,0,0,...] 接收 N[O,N,0,0,0,...] strcmp 比较遇到0就停止完美匹配 ON解释是启用一次中断回调函数就可以接收完hello的吗接收 hello 需要触发 5 次中断回调函数他中断一次只接收1个字节要想接收下一个字节还得要重新中断。解释当中断还没接收完hello的话我就去if(strcmp((char*)rx_buffer, hello) 0)会怎么样strcmp 比较 h???? 和 hello → 不相等不相等就不执行printf解释那我怎么知道他有没有接收完呢然后再进行if(strcmp((char*)rx_buffer, hello)不管了我就这样理解把他什么时候结束我不管我就看我有没有接收到自己想要的解释if(uwTick - rx_tick50) return; rx_tick uwTick;本质就是让CPU少检查反正我这个数据也不会传输的那么快t1ms: uwTick1 → RX_proc 执行但被延时拦住什么叫接收中途防止超时防止在数据还没发完的时候就因为等太久而错误地认为数据发完了不对我要彻底迷糊了又要我漫长的问deepseek的时光了。总结一句话只要是接收数据的时间超不过50ms就不用在中断回调里面加刷新时间的要是超过50ms的话就得加上更新刷新时间的。我终于知道我哪里不知道了,你这个时间加就是接收数据的时候时间加就加呗你管这个rx_tick啥用啊:rx_tick uwTick 的作用目的记录最后一次收到数据的时间。为什么需要记录这个时间因为串口接收数据不是一次发完的可能是一字节一字节发过来的。比如发#123可能是t0ms收到 #t10ms收到 1t20ms收到 2t30ms收到 3如果每收到一个字节就立刻处理可能会把#123拆成4次处理这就乱了。你想让它怎么处理等数据全部收完再处理。怎么知道收完了——用超时判断。超时判断的原理cvoid RX_proc() { // 如果距离上次收到数据还不到50ms说明可能还在接收中再等等 if(uwTick - rx_tick 50) return; // 不处理 // 超过50ms没收到新数据说明这一串数据收完了开始处理 // 处理收到的数据... }具体例子假设发送#123t0ms: 收到 # → rx_tick0 t10ms: 收到 1 → rx_tick10 t20ms: 收到 2 → rx_tick20 t30ms: 收到 3 → rx_tick30 RX_proc()每50ms被调用一次或者主循环里一直调用 第一次调用(t0ms)uwTick0距离上次0ms50返回 第二次调用(t10ms)距离上次10ms50返回 第三次调用(t20ms)距离上次20ms50返回 第四次调用(t30ms)距离上次30ms50返回 第五次调用(t80ms)距离上次50ms等于50开始处理这样就能保证收到完整的#123后才处理。为什么是50ms因为串口发送一字节大概1ms9600波特率发完一串数据通常不会超过50ms。50ms是一个经验值够用了。