最后代码修改注意事项

Tutorial: 卓越工程师 Category: 嵌入式 Published: 2026-10-23 11:05:29 Views: 0 Likes: 0 Comments: 0

175.10最后代码修改注意事项

/* ==================== Gizwits/gizwits_product.c ==================== */
extern UART_HandleTypeDef huart3;                 /* 引用 usart.c 中定义的 USART3 句柄;视频中的小笔误最终改成了 huart3。 */

void timerInit(void)                              /* 课程已有的定时器启动函数。 */
{                                                 /* 函数体开始。 */
	HAL_TIM_Base_Start_IT(&htim2);                 /* 以中断模式启动 TIM2,继续为按键和机智云协议提供节拍。 */
	HAL_TIM_Base_Start_IT(&htim4);                 /* 以中断模式启动 TIM4,让 ZigBee 心跳超时计数真正运行。 */
}                                                 /* 定时器启动函数结束。 */

void uartInit(void)                               /* 课程已有的串口接收启动函数。 */
{                                                 /* 函数体开始。 */
	HAL_UART_Receive_IT(&huart2, (uint8_t *)&aRxBuffer, 1);//开启下一次接收中断  /* 先挂起 USART2 的单字节中断接收,供 ESP8266/机智云协议使用。 */
	HAL_UART_Receive_IT(&huart3, uart3_recv_buff, 5); /* 首次挂起 USART3 的 5 字节中断接收;只开 NVIC 还不会替代这一步。 */
}                                                 /* 串口接收启动函数结束。 */

/* HAL_UART_RxCpltCallback() 的 USART3 分支处理完一包数据后的原语句。 */
HAL_UART_Receive_IT(&huart3, uart3_recv_buff, 5); /* HAL 的定长中断接收完成一次即结束,因此这里重新挂起下一包 5 字节接收。 */

/* userHandle() 中本节为覆盖旧字符而修改的两条显示语句。 */
sprintf(OLED_BUFF,"H:%2d%%T:%2dC ",currentDataPoint.valuetemp1,currentDataPoint.valuehumi1); /* 末尾增加空格,用它擦除上一次较长内容遗留的字符。 */
OLED_ShowString(35,2,OLED_BUFF,16);               /* 从 OLED 的 x=35、第 2 个 16 像素文本行显示缓冲区内容。 */

/* 工程已有的 printf 重定向代码,本节用它查看串口日志。 */
PUTCHAR_PROTOTYPE                                 /* 根据编译器宏展开为 __io_putchar() 或 fputc()。 */
{                                                 /* 单字符输出函数开始。 */
  /* Place your implementation of fputc here */
  /* e.g. write a character to the USART1 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF); /* 把 printf 的一个字符经 USART1 阻塞发送。 */
	
 
  return ch;                                      /* 返回已经发送的字符。 */
}                                                 /* 单字符输出函数结束。 */

/* ==================== Src/main.c ==================== */
timerInit();                                      /* 生成的外设初始化完成后,尽早启动 TIM2 和 TIM4。 */
uartInit();                                       /* 尽早挂起 USART2、USART3 的第一次中断接收。 */

userInit();                                       /* 再执行传感器、OLED 等耗时较多的用户初始化。 */
gizwitsInit();                                    /* 初始化机智云协议相关状态。 */
keyInit();                                        /* 初始化机智云按键状态机。 */

void key2LongPress(void)                          /* KEY2 长按回调,课程用它进入 AirLink 配网。 */
{                                                 /* 长按回调开始。 */
    //AirLink mode
    GIZWITS_LOG("KEY2 PRESS LONG ,AirLink mode\n"); /* 通过日志串口打印已经进入 AirLink 模式。 */
    #if !MODULE_TYPE                              /* 仅在当前模块类型配置允许时编译下面的 Wi-Fi 配网调用。 */
    gizwitsSetMode(WIFI_AIRLINK_MODE);            /* 请求 ESP8266 进入 AirLink 配网模式。 */
    #endif                                        /* 条件编译结束。 */
}                                                 /* KEY2 长按回调结束。 */

/* ==================== Inc/main.h ==================== */
#define KEY2_Pin GPIO_PIN_8                       /* 工程把逻辑 KEY2 配置在 GPIO 第 8 脚,即 PA8。 */
#define KEY2_GPIO_Port GPIOA                      /* KEY2 所属端口为 GPIOA。 */

/* ==================== Src/tim.c、Src/usart.c ==================== */
HAL_NVIC_SetPriority(TIM4_IRQn, 0, 0);            /* 设置 TIM4 全局中断的抢占优先级和子优先级。 */
HAL_NVIC_EnableIRQ(TIM4_IRQn);                    /* 在 NVIC 中放行 TIM4 全局中断。 */
HAL_NVIC_SetPriority(USART3_IRQn, 0, 0);          /* 设置 USART3 全局中断优先级。 */
HAL_NVIC_EnableIRQ(USART3_IRQn);                  /* 在 NVIC 中放行 USART3 全局中断。 */

/* ==================== Src/stm32f1xx_it.c ==================== */
void TIM4_IRQHandler(void)                        /* TIM4 的中断向量入口。 */
{                                                 /* TIM4 中断服务函数开始。 */
  HAL_TIM_IRQHandler(&htim4);                     /* 交给 HAL 清标志并进一步调用定时器周期回调。 */
}                                                 /* TIM4 中断服务函数结束。 */

void USART3_IRQHandler(void)                      /* USART3 的中断向量入口。 */
{                                                 /* USART3 中断服务函数开始。 */
  HAL_UART_IRQHandler(&huart3);                   /* 交给 HAL 处理接收状态;满 5 字节后会进入接收完成回调。 */
}                                                 /* USART3 中断服务函数结束。 */

源码范围说明:顶部只摘录 175 视频实际修改、检查或直接用于调试的工程语句,并逐行加注释。HAL_UART_Receive_IT()、HAL_TIM_Base_Start_IT() 的 HAL 库内部实现没有在本课展开,因此不强行补入;CubeMX 自动生成文件也只保留与本次故障链直接相关的 NVIC 和中断入口。各片段来自不同函数、不同文件,不能把整段直接粘进同一个函数。OLED 格式化语句中 H 接 valuetemp1、T 接 valuehumi1 与成员命名语义疑似相反,本课只增加末尾空格,并未修改这项旧逻辑,故顶部忠实保留最终工程原句。

一、最后代码修改及注意事项

这节不是继续增加功能,而是把 174 编译通过但上板失败的工程调通。烧录后的主要现象是 OLED 显示 Zig:ERR、手机 App 无法正常控制窗帘或灯、远端温湿度没有更新。多个功能同时依赖 STM32 的 USART3 与 51 端通信,因此应先检查公共通信链路,而不是分别修改每个业务功能。

1.1 问题现象分析

原笔记中的“b 处显示 error”“b 通信”是转写错误。屏幕实际状态是 Zig:ERR,表示 175 以前写好的 zig_count 没有被有效心跳持续清零。这个现象可能来自多层原因:

  1. USART3 句柄或接线错误,STM32 根本没有收到 51 发来的 5 字节帧;
  2. USART3 全局中断未使能,或没有调用 HAL_UART_Receive_IT() 挂起第一次接收;
  3. 收满一包后没有再次调用接收函数,只能成功接收一次;
  4. TIM4 未以中断模式启动,心跳超时计数逻辑没有按设计运行;
  5. ZigBee 模块参数、供电或杜邦线接触不良。

因此 Zig:ERR 只是“规定时间内没收到有效心跳”的结果,不等于已经证明 ZigBee 模块本身损坏。课程最后通过补齐软件链路、调整初始化顺序并检查硬件连接恢复了运行。

1.2 代码修改内容

1.2.1 小问题修改

视频提到先发现了一个串口句柄的笔误,但口述没有给出可可靠还原的完整错误文本。配套最终工程可以确认正确声明是:

extern UART_HandleTypeDef huart3;

它与后续 HAL_UART_Receive_IT(&huart3, ...)、USART3_IRQHandler() 和 PB10/PB11 接线相互对应。这里不把原笔记乱码“一月二题”“一没写写的”继续当作源码,也不臆造视频修改前的错误行。

1.2.2 大问题分析

一次 USART3 中断接收能进入业务回调,需要整条调用链成立:

CubeMX 使能 USART3 global interrupt
→ 生成 USART3_IRQn 的 NVIC 配置和 USART3_IRQHandler()
→ uartInit() 调用 HAL_UART_Receive_IT(..., 5) 挂起第一次接收
→ 收满 5 字节后 HAL 进入 HAL_UART_RxCpltCallback()
→ 业务代码校验、分流
→ 再次调用 HAL_UART_Receive_IT(..., 5) 挂起下一包

“勾选全局中断”和“启动一次异步接收”是两个不同条件。前者让 CPU 能响应 USART3 的中断请求,后者告诉 HAL 接收缓冲区地址、长度并建立本次接收状态;只做其中一个都不能完成这里的 5 字节接收。

HAL_UART_Receive_IT(&huart3, uart3_recv_buff, 5) 是一次性的定长接收。只有累计收到 5 字节才触发完成回调;回调执行后必须再次调用它,才能等待下一包。课程代码没有检查这个函数的返回值,若它返回 HAL_BUSY 或 HAL_ERROR,程序不会留下明确故障信息,这也是调试时应注意的限制。

TIM4 链路同样需要同时满足:

CubeMX 使能 TIM4 global interrupt
→ 生成 TIM4_IRQn 的 NVIC 配置和 TIM4_IRQHandler()
→ HAL_TIM_Base_Start_IT(&htim4) 启动定时器及更新中断
→ HAL_TIM_IRQHandler(&htim4)
→ HAL_TIM_PeriodElapsedCallback(&htim4)
→ zig_count++

仅写回调函数不会让定时器自动开始计数;仅调用 HAL_TIM_Base_Start_IT() 而没有 NVIC 中断入口也到不了用户回调。

课程最终把 timerInit(); uartInit(); 放到 CubeMX 外设初始化和 MX_NVIC_Init() 之后、userInit() 之前。原因是 51 端上电后会主动发送数据,若 STM32 先执行耗时的传感器和显示初始化,早期字节可能在接收尚未挂起时到达,形成盲区,甚至留下溢出状态。视频把旧位置失败猜成“HAL 库 bug”,但没有证据证明 HAL 自身存在缺陷;从最终修改看,更准确的结论是接收启动时机过晚,提前挂起接收缩短了丢失首包的窗口。

1.3 自己做时的注意事项

  • 屏幕从较长内容切换到较短内容时,OLED 驱动不会自动擦除右侧旧像素。课程在格式字符串末尾增加一个空格,用新写入的空白字符覆盖残留的 C,解决类似 21 CC 的显示。
  • 该修改只处理残留字符,不会修复数值、标签或传感器读取错误。最终源码的 H/T 与 valuetemp1/valuehumi1 对应关系还应结合数据点定义单独核对。
  • 杜邦线松动、面包板接触不良、未共地以及 TX/RX 接反,都会造成与软件故障相似的现象。先用万用表或连续性测试确认连接,再追业务代码。
  • 可拆卸原型便于学习,但长期运行更适合焊接或使用可靠连接器,并做好供电去耦。

1.4 ZigBee调试

排查时可先拔下两端 ZigBee 模块,用有线串口直接连接 STM32 与 51:

STM32方向51 单片机作用
PB10 / USART3_TX→P3.0 / RXDSTM32 向 51 发送控制帧
PB11 / USART3_RX←P3.1 / TXD51 向 STM32 回传 ACK、温湿度和心跳
GND↔GND建立共同电位基准

TX 必须交叉接 RX,并且第三根 GND 不能省。若两块板分别供电,不要再用第四根线把它们的 VCC 直接并在一起;连接信号线前还要确认两端逻辑电平兼容,不确定时使用电平转换。这个直连测试只验证 MCU 串口、协议和业务代码,直连正常而装回模块失败,才把范围缩小到 ZigBee 配置、供电或无线链路。

1.5 机智云APP使用

1.5.1 信号检测问题

本工程通过 ESP8266 接入 Wi-Fi。ESP8266 工作在 2.4 GHz 频段,所以配网时手机应连接可用的 2.4 GHz 网络,并确认路由器没有启用会阻止设备接入的隔离或认证方式。App 暂时发现不了设备,只能说明配网或联网链路尚未完成,不能据此判断手机“没有检测到模块无线信号”。

1.5.2 配对问题

原笔记的“一二引脚”“ER 按键”“AR link”均是转写错误。配套 STM32 开发板原理图显示,板载物理 KEY2 接在 PE2,而当前工程却把逻辑 KEY2 配置为 PA8。课程临时用杜邦线把 PE2 接到 PA8,于是按下板载 KEY2 时,低电平也能送到程序实际读取的 PA8。

长按 KEY2 约 2~3 秒后,机智云按键状态机调用 key2LongPress(),继而执行 gizwitsSetMode(WIFI_AIRLINK_MODE),这才是 AirLink 模式。杜邦线是对“现有硬件按键位置与现有工程引脚配置不一致”的补偿,不是 C8T6 外设本身要求这样连接。更正式的处理应在硬件和 CubeMX/代码之间统一按键引脚。

1.5.3 板子选择问题

视频建议有能力的学习者改用 STM32F103C8T6 最小系统板,并提醒不要买成 C6T6。与本项目相关的关键差异是:

型号密度/典型 Flash本课相关定时器USART对本项目的影响
STM32F103C6T6低密度 / 32 KBTIM1、TIM2、TIM3;无 TIM4USART1、USART2;无 USART3不能原样使用当前 USART3 与 TIM4 方案
STM32F103C8T6中密度 / 64 KBTIM1、TIM2、TIM3、TIM4USART1、USART2、USART3具备本课使用的 USART3 与 TIM4

原笔记称 C8T6 有“串口4”不准确:C8T6 有 USART1~USART3,没有 UART4。换成 C8T6 也不是只换芯片名称,还要重新核对封装引脚、外设复用、下载接口、供电和所有外接模块连线。视频口述的“z1t6”应为课程所用开发板上的 ZET6;而配套工程 .ioc 当前选择的是 STM32F103C8Tx,硬件与工程目标型号本身也应在复现时再次统一。

1.6 窗口调试

这里的“窗口”实际指电脑上的串口调试窗口。最终工程把 printf/GIZWITS_LOG 重定向到 USART1,配置为 115200 bit/s、8 数据位、无校验、1 停止位。配套开发板原理图又把板载 CH340C 接到 USART1,因此可在电脑串口终端选择对应端口和 115200 8-N-1,复位开发板后查看启动与协议日志。

115200 是波特率,不是“复位键”。用于 ESP8266/机智云数据传输的是 USART2;用于观察日志的是 USART1,两者不要混淆。日志能帮助判断程序运行到哪一步、是否进入 AirLink,但不能单独证明 USART3 到 51 的物理链路正确。

1.7 配对问题

推荐按以下顺序重新配对:

  1. 手机连接 2.4 GHz Wi-Fi;
  2. 长按映射到 PA8 的 KEY2,串口日志确认进入 AirLink;
  3. 在 App 中开始添加设备并输入正确网络信息;
  4. 若模块长期卡在旧状态,切断开发板外部电源、USB/ST-Link 以及其他可能的反向供电,让 ESP8266 真正掉电,再重新上电配网。

“全断电再上电”用于清除模块当前运行状态,但不保证一定连接成功。若仍失败,还应检查路由器频段、密码、模块串口通信、复位/使能脚、供电跌落以及机智云产品配置是否匹配。

1.8 供电问题

原笔记“电脑 USB 只有 500 mA、必须换手机充电器、ESP8266 必须接 5 V 不能接 3.3 V”不能照做。应区分芯片/裸模块的 3.3 V 电源脚与带稳压器开发板的 5 V 输入脚:

  • ESP8266EX 芯片工作电压为 2.5~3.6 V,常用模块建议使用稳定的 3.3 V 电源;裸 ESP-01/ESP-01S 或标有 3V3/VCC 的电源脚不能直接接 5 V。
  • 只有明确带板载 3.3 V 稳压器、并标有 5V 或 VIN 输入的载板,才可按该载板说明把 5 V 接到其输入端;5 V 逻辑电平也不能直接灌入 ESP8266 GPIO。
  • 配套 STM32 开发板原理图明确把 ESP8266 接口 J2 的电源脚接到 3V3,不是 5 V。
  • 电源需要在 Wi-Fi 发射电流峰值出现时仍保持稳定。Espressif 的入门资料建议供电能力至少 500 mA,但 USB 端口、充电器标称电流不是唯一判断依据;线缆压降、板上稳压器能力、接触电阻和模块附近去耦同样重要。

因此本项目应先测量模块 3.3 V 引脚在联网瞬间是否明显跌落,再决定更换合适的 3.3 V 稳压电源。手机充电器本身输出通常是 5 V,不能因为它“电流更大”就越过稳压环节直接接裸模块。

参考资料