C语言基础回炉第六天:补漏洞、跑验收、串起 STM32 数据链路

📅 2026/6/25 18:31:59
C语言基础回炉第六天:补漏洞、跑验收、串起 STM32 数据链路
前言前五天我主要在补 C 语言和嵌入式常用数据处理能力位运算、字符串和内存函数、链表、数组查找、环形缓冲区、UART 帧解析。今天不是继续盲目往后学新知识而是先做一次集中验收再把这些训练内容放回自己的真实项目里理解。这一天的重点有两个把前几天留下的基础漏洞补掉保证练习脚本真正通过。对照smart_env_monitor和zhijing_edge_gateway两个项目理解从 STM32 采集数据到 PC 端解析、Web/API/MQTT 输出的完整链路。这比单独刷题更接近真实嵌入式开发。因为项目里不会只问一个函数能不能写出来而是要看数据从哪里来、经过哪些任务、通过什么接口传出去、异常时怎么定位。一、今天先做集中验收今天重新运行了前五天的核心练习。第一次验收时数组、环形缓冲区和 UART 帧解析相关练习已经通过但 Day1 的重写练习暴露出两个回归问题FAIL ...02_string_memory_retype.c:74 expression: MyStrcmp(abc, abd) 0 FAIL ...03_singly_linked_list_retype.c:56 expression: head nodes[4]这两个失败说明MyStrcmp找到第一个不同字符后没有返回差值而是错误地返回了 0。单链表反转函数仍然是占位实现直接返回了原头节点。这也提醒我前几天写过的内容不能只靠印象判断“会了”。必须重新运行脚本让测试结果说话。修复后再次运行 Day1 脚本结果为Building 01_bit_ops_retype.c Running 01_bit_ops_retype.exe 01_bit_ops_retype passed Building 02_string_memory_retype.c Running 02_string_memory_retype.exe 02_string_memory_retype passed Building 03_singly_linked_list_retype.c Running 03_singly_linked_list_retype.exe 03_singly_linked_list_retype passed All day01 retype exercises passedDay2 到 Day5 的综合练习也重新运行通过数组处理、环形缓冲区和 UART 帧解析三部分都没有再失败Building 01_array_tools.c Running 01_array_tools.exe 01_array_tools passed Building 02_ring_buffer.c Running 02_ring_buffer.exe 02_ring_buffer passed Building 03_uart_frame_parser.c Running 03_uart_frame_parser.exe 03_uart_frame_parser passed All embedded data structure exercises passed到这里Day1 到 Day5 的核心练习才算有了完整的代码证据。二、MyStrcmp这次错在哪里strcmp的语义不是“相同返回 0不同也随便返回一个值”而是要根据第一个不同字符的大小返回正数或负数。正确逻辑是static int MyStrcmp(const char *a, const char *b) { const unsigned char *pa (const unsigned char *)a; const unsigned char *pb (const unsigned char *)b; while (*pa ! \0 *pa *pb) { pa; pb; } return (int)(*pa) - (int)(*pb); }这里有两个点要注意比较时应该按unsigned char处理避免字符最高位导致符号扩展问题。循环结束后不管是遇到不同字符还是其中一个字符串结束都可以用当前字符差值作为结果。比如abc 和 abd 比较到 c 和 dc - d 0 abd 和 abc 比较到 d 和 cd - c 0 abc 和 abcd 比较到 \0 和 d\0 - d 0这个函数看起来很小但它训练的是边界意识字符串结束符本身也参与比较。三、链表反转和快慢指针单链表反转这次失败是因为函数还没有真正改指针。正确反转需要三个指针static Node *ReverseList(Node *head) { Node *prev NULL; Node *current head; while (current ! NULL) { Node *next current-next; current-next prev; prev current; current next; } return prev; }核心顺序不能乱先保存next否则改掉current-next后会丢失后续链表。再让current-next指向prev。最后整体向前移动。链表中点和环检测也都用快慢指针FindMiddle快指针每次走两步慢指针每次走一步偶数长度返回第二个中点。HasCycle如果链表有环快慢指针最终会相遇如果没有环快指针会先走到空。这三个函数都不是为了背模板而是为了训练指针移动顺序。嵌入式里很多队列、链表、内存块管理本质上也离不开这种指针状态维护。四、前五天内容如何接到真实项目今天看的真实项目主要有两个D:\develop\programfirst\smart_env_monitor D:\develop\programfirst\zhijing_edge_gateway第一个是 STM32F1 FreeRTOS 的环境监测项目第二个是 Windows PC 端的边缘网关程序。整体数据链路可以概括为ADC 采样 - SensorTask 滤波和换算 - 全局传感器值 g_temp_value / g_light_value - WifiTask / DTU_SendTelemetry 组 JSON - UART / DTU 输出 - PC 网关读取串口行 - 解析 [SENSOR] JSON - Web API / MQTT 发布这条链路正好把前五天内容串了起来位运算和内存操作底层寄存器、缓冲区、协议字段都会用到。数组和有效长度传感器历史、payload、接收缓存都不能只看数组容量。环形缓冲区适合 UART 连续接收解决生产者和消费者速度不一致的问题。UART 帧解析解决字节流里如何识别完整业务消息的问题。复杂度和固定内存STM32F103C8T6 只有 20KB SRAM不能随意动态分配和无界缓存。五、STM32 项目里的任务划分在smart_env_monitor里FreeRTOS 任务大致可以这样理解SensorTask 读取 ADC做滑动均值滤波更新温度和光照值 DisplayTask 刷新 OLED 显示 ControlTask 根据模式、阈值、传感器值控制蜂鸣器、LED、舵机 WifiTask 处理 DTU 连接、定时上报和下行命令 KeyTask 处理按键、模式切换、阈值调整这就是项目表达里很重要的一点任务不是随便拆的而是按职责拆。采集、显示、控制、通信、按键输入各自有清晰边界。其中SensorTask里用到了滑动均值滤波新 ADC 值 - 替换环形位置里的旧值 - 更新 sum - 求平均值这和 Day4 的环形缓冲区思想很接近只不过这里不是存 UART 字节而是存最近若干次 ADC 采样值。六、UART/DTU 接收和 Day4、Day5 的关系项目里HAL_UART_RxCpltCallback每次接收 1 个字节并在遇到\r或\n时认为一行命令结束。它做的事情比较轻收字节、拼缓冲、设置命令就绪标志然后重新开启下一次中断接收。这和 Day4 里总结的原则一致ISR 里不要做复杂业务逻辑。更合理的分层应该是UART 中断层快速收字节放入缓存或设置标志 协议解析层判断一行或一帧是否完整 业务处理层执行 SERVO、sget、阈值调整等命令Day5 的 UART 帧解析训练虽然用的是二进制帧0xAA 0x55 LEN CMD PAYLOAD CHECKSUM而当前项目里 PC 网关解析的是文本行[SENSOR] {light:74,temp:27,mode:0,servo:1500}但它们的本质是一样的底层拿到的是连续字节流应用层必须定义消息边界。二进制协议用帧头和长度文本协议用换行符和标记字符串[SENSOR]。七、PC 网关项目的验证今天也运行了zhijing_edge_gateway的本地自测.\build\Release\zhijing_edge_gateway.exe --self-test输出为self-test passed.它覆盖了几个关键点[SENSOR]JSON 解析。最新数据 JSON 输出。MQTT Remaining Length 编码。MQTT CONNECT 报文编码。MQTT PUBLISH 报文编码。再运行 demo.\build\Release\zhijing_edge_gateway.exe --demo输出为Demo input: [SENSOR] {light:74,temp:27,mode:0,servo:1500} [16:01:46] temp27 C, light74 %, mode0, servo1500 us这说明 PC 端程序能从一行串口文本里提取业务字段并转换成可展示、可上报的数据结构。八、今天必须能口述的项目表达今天整理后我需要能把项目讲成下面这段话我的项目是一个 STM32F103 FreeRTOS 的环境监测系统。STM32 端分成传感器采集、显示、控制、通信和按键任务。传感器任务周期读取 ADC并用滑动均值降低抖动控制任务根据温度、光照和模式控制 LED、蜂鸣器、舵机通信任务把实时数据组成 JSON通过 UART/DTU 上报。PC 端边缘网关从串口读取[SENSOR]数据行解析出温度、光照、模式和舵机值再提供 Web API 或通过 MQTT 发布。这个项目里我重点关注了任务职责划分、UART 数据边界、缓冲区有效长度、以及异常数据不应该阻塞主流程。这段话比单纯说“我做过 STM32 项目”更具体因为它包含了任务划分、数据流和通信链路。九、今天的收获今天最重要的不是多学了一个新知识点而是把前五天的内容合并成一条工程链路基础函数必须能重新写出来不能只靠看懂。练习是否完成要以脚本输出为准。字符串、链表、数组、缓冲区、帧解析都可以在真实项目里找到对应场景。STM32 FreeRTOS 项目表达要讲清楚任务职责而不是只列外设名字。串口通信的关键不是“能发能收”而是能稳定定义消息边界、处理异常数据、避免 ISR 里做复杂逻辑。总结Day6 完成了一次集中验收Day1 的 C 基础重写练习以及 Day2 到 Day5 的数组、环形缓冲区、UART 帧解析练习都已经重新通过。今天还把这些训练内容接到了自己的 STM32 环境监测项目和 PC 端边缘网关项目里。接下来不再围绕训练目录名展开而是固定两条主线一条是继续补 C 语言硬功重点放在指针、内存、结构体、回调函数和模块化接口另一条是继续吃透自己的项目把 STM32 端采集控制、UART/DTU 通信、PC 网关解析、Web/MQTT 输出这些链路讲清楚、改明白、跑出证据。