1. 核心架构:模块化状态机 (FSM)
本项目采用高度解耦的模块化状态机架构,通过全局模式管理器实现任务的平滑切换。
1.1 分任务管理 (GLOBAL 模块)
系统通过
CurrMode(当前状态)和NextMode(下一状态)双标志位驱动。
状态一致 :系统执行当前模块的
Loop函数。状态不一致 :触发切换流程------退出旧模式 (
Exit) -> 初始化新模式 (Init) -> 更新标志位 -> 进入新循环。 这种设计保证了每个功能模块(如时钟、秒表、手电筒)的内存和逻辑独立性。
1.2 时间基准 (Heartbeat)
依靠 TIM2 定时器 产生高精度心跳(Tick),驱动以下关键任务:
按键状态机扫描:确保 20ms 的消抖逻辑与多种触发识别。
秒表计时:提供毫秒级的时间步进。
低功耗倒计时:实时监测用户操作空闲时间。
2. 交互与 UI 设计:多维感知与动效
2.1 复合按键驱动
通过状态机识别,将有限的按键映射为多维操作:
基础层:按下 (Down)、松开 (Up)、持续按住 (Hold)。
应用层:单击、双击、长按、超长按。
2.2 滚动式图形化菜单
在 OLED 屏幕上实现了类手机端的水平滚动效果。
视觉算法:逻辑上维护 5 个图像槽位,中心槽位(第 3 个)为焦点。
平滑动效:通过步进计算坐标位移(Offset),实现左右切换时的丝滑动画感,提升了电子表的"高级感"。
3. 功能模块一览
目前系统已集成 13 个模式,核心包括:
MODE 1:实时时钟 (RTC) 主界面。
MODE 2-9:细分的时间/日期配置中心。
MODE 10:图标导航菜单。
MODE 11-12:秒表工具与手电筒(PC13 LED 控制)。
4. 功耗管理策略:两层睡眠机制
为了延长续航,项目设计了严格的功耗管理:
轻度睡眠 (STOP Mode) :6 秒无操作且无后台计时任务时触发。关闭主时钟,保留 SRAM 数据,任意键中断唤醒。
深度关机 (STANDBY Mode) :通过超长按 Key 3 触发。进入待机模式,电流降至微安级,仅能通过 WakeUp Pin 唤醒(类似设备关机/重启)。
5. 技术难题:停止模式下的"唤醒死循环"陷阱
5.1 遇到的现象
在开发初期,我尝试将低功耗逻辑直接写在定时器中断回调(
HAL_TIM_PeriodElapsedCallback)中。结果发现:待机模式能正常唤醒,但停止模式唤醒后系统直接卡死。
5.2 深度原因剖析
这涉及到了 STM32 执行流与中断优先级的深层冲突:
执行流差异:
STANDBY 模式 :唤醒等同于"复位",程序从
main重新开始,避开了所有中断嵌套。STOP 模式:唤醒属于"现场恢复",程序从进入睡眠的那一行代码继续向下执行。
主时钟恢复死锁:
当在 TIM2 中断里进入 STOP 模式后,唤醒后的第一件事是调用
SystemClock_Config()恢复外部高速时钟(HSE/PLL)。
HAL_RCC_OscConfig等函数会通过HAL_GetTick()循环等待晶振稳定。致命伤 :
HAL_GetTick()依赖 SysTick 中断。由于此时程序仍处于 TIM2 的中断服务函数中,如果 SysTick 的优先级没有高于 TIM2,SysTick 将无法抢占。结果 :
uwTick永远不会增加,时钟配置函数进入死循环,系统彻底卡死。
5.3 解决方案:标志位驱动法
核心原则:永远不要在中断服务函数(ISR)中执行阻塞式任务或复杂的时钟重配置。
我将逻辑重构为:
中断(ISR)只负责"打标" :定时器中断仅检测超时并置位
low_flag。主循环(Main Loop)负责"执行" :在
while(1)的最顶层检查low_flag。一旦触发,在主程序流中进入睡眠。唤醒后处理:唤醒后在主循环中恢复时钟。此时没有中断屏蔽,SysTick 正常运行,时钟配置顺利完成。
6. 硬件底层避坑:RTC 夺取 PC13 控制权之谜
6.1 遇到的现象
在实现 RTC 万年历功能时,出现了一个诡异的 Bug:原本受 GPIO 逻辑控制的 PC13 引脚(板载 LED) 变得不受控制,经常莫名其妙地亮起,即便代码中并没有调用控制它的指令。
6.2 深度原因剖析:多重身份与"霸道"的备份域
PC13 在 STM32 中是一个非常特殊的引脚,它位于备份域(Backup Domain),拥有多重身份:
GPIO 身份:普通 IO 口,控制 LED。
TAMP 身份:侵入检测引脚(Tamper Detection),用于安全性检测。
RTC 输出身份:用于输出 RTC 校准时钟(512Hz/1Hz)或闹钟事件。
为什么 RTC 会"强占" PC13? 在调用
HAL_RTC_Init()时,如果配置参数不当,HAL 库可能会误开启 RTC 的校准输出或侵入检测功能。由于 RTC 属于备份域外设,其硬件优先级高于普通 GPIO 复用。一旦相关功能被使能,RTC 硬件电路会自动接管 PC13 的输出驱动器 ,导致你写的HAL_GPIO_WritePin完全失效。
6.3 解决方案:彻底释放引脚控制权
要解决此问题,必须在 RTC 初始化时明确禁止所有非必要的输出功能。
方案 A:CubeMX 图形化修改(推荐)
在 RTC 配置页面的 Control 选项卡中:
将 OutPut 选项手动改为
No RTC Output。确保所有的 Tamper(侵入检测) 选项均处于
Disable状态。
方案 B:底层代码手动修正
进入
rtc.c文件,找到MX_RTC_Init函数。重点检查hrtc.Init.OutPut这一行,确保其被显式配置为 NONE:
/* rtc.c 修正代码 */
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
// 关键点:禁用 RTC 对 PC13 的自动接管输出
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}