整体工程架构解析|文件分层、模块分工、整个程序的运行流程 一、先看整体你的工程里都有啥打开你的 Keil 工程是不是看到一堆.c 和.h 文件头晕别慌火禾的代码结构其实非常清晰完全按照模块化分层设计来的没有乱七八糟的耦合。我把整个工程的文件结构整理成了思维导图一眼就能看懂分层文件名核心功能关键备注核心系统层main.c程序唯一入口主循环调度只包含main()stm32f10x_it.c所有中断服务函数入口本项目仅使用 SysTick 中断几乎无需修改system_stm32f10x.c系统时钟配置默认配置为 72MHz不要随意改动硬件驱动层Delay.c/h微秒 / 毫秒级延时函数基于 SysTick 定时器实现精度高Key.c/h3 个独立按键驱动轮询方式读取自带 20ms 硬件消抖LED.c/hLED / 手电筒驱动控制 PB12/PB13 引脚输出高低电平MyRTC.c/hRTC 实时时钟驱动实现时间读写、自动计时功能AD.c/hADC 电池电量检测读取 PA0 引脚电压换算成电池百分比MyI2C.c/h软件 I2C 驱动连接 PB10 (SCL)/PB11 (SDA)供 MPU6050 和 MAX30102 使用OLED.c/h硬件 I2C OLED 驱动连接 PB6 (SCL)/PB7 (SDA)实现屏幕绘制、清屏、更新等功能MPU6050.c/h六轴陀螺仪驱动读取加速度和角速度原始数据解算欧拉角max30102.c/h心率血氧传感器驱动✅本人扩展功能实现传感器初始化、FIFO 数据读取、心率血氧算法算法工具层math.h标准数学库提供三角函数、绝对值、平方根等基础数学运算deno.c/h谷歌小恐龙游戏核心逻辑包含恐龙动画、障碍物生成、碰撞检测、分数计算、游戏状态机应用逻辑层menu.c/h核心滑动菜单系统包含Peripheral_Init()结构极简实现菜单滑动动画、页面跳转、功能入口调度SetTime.c/h时间设置页面提供 RTC 时间和日期的修改界面StopWatch.c/h秒表功能实现精确到 0.01 秒的计时、暂停、清零功能Game.c/h谷歌小恐龙游戏包含角色动画、障碍物生成、碰撞检测、分数统计Emoji.c/h动态表情包用帧动画实现眨眼、微笑等表情效果Gradienter.c/h水平仪功能基于 MPU6050 姿态数据绘制气泡水平仪数据资源层OLED_Data.h所有显示资源包含 ASCII 字模 (3 种大小)、汉字字模、所有图标、游戏资源重点看main.c的结构整个程序的入口就在这里非常简洁#include stm32f10x.h // Device header #include Delay.h #include OLED.h #include menu.h #include Timer.h #include Key.h #include Deno.h #include max30102.h /** * 坐标轴定义 * 左上角为(0, 0)点 * 横向向右为X轴取值范围0~127 * 纵向向下为Y轴取值范围0~63 * * 0 X轴 127 * .------------------------------- * 0 | * | * | * | * Y轴 | * | * | * | * 63 | * v * */ int main(void) { /*OLED初始化*/ OLED_Init(); OLED_Clear(); Peripheral_Init(); int clkflag1; extern int press_time; extern uint8_t key_Num; Timer_Init(); while (1) { //OLED_ShowNum(64,0,press_time,4,OLED_6X8); //OLED_ShowNum(64,8,key_Num,1,OLED_6X8); // OLED_Update(); clkflag1First_Page_Clock(); if(clkflag11){ Menu();}//菜单 else if(clkflag12){SettingPage();}//设置 } } // 定时器中断函数可以复制到使用它的地方 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) SET) { MAX30102_Tick(); Key3_Tick(); Key_Tick(); StopWatch_Tick(); Dino_Tick(); TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }最棒的一点这个架构是可扩展的我加 MAX30102 的时候完全没有修改原有代码只是新增了两个文件然后在菜单里加了一行跳转完美融入。二、核心初始化顺序踩过坑才知道有多重要初始化顺序错了硬件绝对跑不起来我一开始把MAX30102_Init()放在MyI2C_Init()前面调了好久天传感器都不亮。你的代码里已经是完全正确的初始化顺序执行顺序函数名核心作用为什么要这个顺序1OLED_Init()初始化 OLED 屏幕最先初始化方便后续显示调试信息和错误提示2OLED_Clear()清屏防止上电时屏幕显示乱码3Peripheral_Init()初始化所有其他外设按依赖顺序初始化RTC→按键→LED→MPU6050→ADC→软件 I2C→MAX301024Timer_Init()初始化 TIM2 定时器最后初始化确保所有外设都准备好再开启中断三、模块调用与页面跳转关系整个项目的模块调用关系非常清晰遵循从上到下调用的原则不会出现底层调用上层的情况上层函数可调用的下层函数触发条件返回条件main()First_Page_Clock()程序启动永远不会返回First_Page_Clock()Menu()选中 菜单 按 Key3按 Key3 确认进入SettingPage()选中 设置 按 Key3按 Key3 确认进入Menu()StopWatch()选中秒表按 Key3按 Key3 返回LED()选手电筒按 Key3按 Key3 返回MPU6050()选陀螺仪按 Key3按 Key3 返回Game()选小恐龙按 Key3按 Key3 返回Emoji()选表情包按 Key3按 Key3 返回Gradienter()选水平仪按 Key3按 Key3 返回HeartRate()选心率血氧按 Key3按 Key3 返回每个功能页面都是一个独立的死循环比如int HeartRate(void) { while(1) { // 检测按键 KeyNum Key_GetNum(); // 按返回键退出回到菜单 if(KeyNum 3) { OLED_Clear(); OLED_Update(); return 0; } // 读取传感器数据 MAX30102_ReadFIFO(red, ir); // 计算心率血氧 heart_rate MAX30102_GetHeartRate(ir); // 显示界面 Show_HeartRate_UI(heart_rate, spo2); OLED_Update(); Delay_ms(20); } }这种设计的好处是每个功能完全独立修改一个功能不会影响其他功能添加新功能也只需要加一个新的函数就行。四、完整运行流程程序从开机到运行的每一步我把整个程序从开机到你能操作的完整流程拆成了 8 步每一步都对应代码里的具体位置步骤执行内容对应代码位置执行时机1系统上电时钟初始化SystemInit()单片机上电自动执行2跳转到 main 函数初始化 OLEDmain.c第 15-16 行程序启动3初始化所有外设和 TIM2 定时器main.c第 17-18 行只执行一次4进入时钟首页死循环First_Page_Clock()初始化完成后5时钟首页每秒刷新时间和电量检测按键输入First_Page_Clock()内部 while 循环持续执行6按 Key3 进入滑动菜单显示滑动动画Menu()时钟首页按 Key37选择功能按 Key3 进入对应页面各个功能页面函数菜单中按 Key38功能页面按 Key3 返回上一级各个功能页面内部 Key3 判断按 Key3 时五、3 个按键全局功能对照表你的工程全程只用 3 个按键完成所有操作逻辑非常简洁按键编号全局通用功能时钟首页滑动菜单功能页面小恐龙游戏Key1上选 / 左移切换 菜单 选项向上滑动菜单数值减 / 上一项恐龙跳跃Key2下选 / 右移切换 设置 选项向下滑动菜单数值加 / 下一项无功能Key3确认 / 返回进入选中页面进入选中功能返回上一级退出游戏六、这个架构的优缺点对比优点可以改进的地方模块化程度高每个功能都是独立的函数耦合度极低没有低功耗处理所有外设一直运行电池续航比较短可扩展性强添加新功能只需要加一个.c 文件和一行菜单跳转没有任务调度所有功能都是死循环同一时间只能运行一个功能代码结构清晰文件分层明确很容易找到对应的代码数据没有持久化掉电后 RTC 时间会重置可以加个纽扣电池解决✅定时器驱动架构比网上纯轮询版本响应更快、计时更准、游戏更流畅没有错误处理机制硬件异常时会直接死机适合入门学习没有复杂的操作系统都是裸机代码容易理解屏幕刷新效率不高每次都是全屏刷新这篇我们从上帝视角把整个项目的骨架拆明白了下一篇我们深入最核心的 TIM2 定时器调度系统还有主页面跳转菜单以及设置逻辑逐行拆解为什么要用定时器驱动所有任务1ms 中断里到底做了什么按键消抖和长按检测是怎么实现的秒表为什么能做到 1ms 精度为什么clkflag返回1就是进入菜单clkflag返回2就是设置关注我下一篇更干有什么问题评论区留言我都会回