基于STM32的智能手环毕业设计:心率、计步与体温监测系统 简介本资源是一套完整的STM32嵌入式毕业设计项目资料面向电子信息、自动化及物联网方向的本科生与初学者解决智能可穿戴设备中多参数生理与运动数据采集、处理与本地显示的核心问题。资源包共含数十个文件具体总数未提供涵盖Keil工程源码、Altium Designer原理图与PCB设计图、系统框图与程序流程图、开题报告与任务书、答辩技巧指南、参考论文及关键芯片STM32F103C8T6、ADXL345、心率传感器、DS18B20等技术手册与器件清单全面支撑从硬件搭建、固件开发到论文撰写与答辩全流程。压缩包大小为66.16MB结构清晰、模块对应明确便于分阶段学习与调试。已有4652人下载学习特别适合需要落地实践、理解传感器融合与低功耗嵌入式系统设计逻辑的入门者与课程设计者。1. 项目概述与整体设计思路1.1 这个毕设题目到底在做什么先说结论这是一个典型的嵌入式软硬件结合毕业设计核心任务是做一块能戴在手腕上的小型设备实时测量心率、脉搏、体温同时还能记录步数并且把数据展示在OLED小屏幕上。题目里提到的“智能手环”本质上是把一个完整的单片机系统微缩到腕带尺寸涉及传感器选型、信号采集、数据处理、显示交互、低功耗设计等多个环节。这类题目的好处在于它不依赖某一家厂商的私有平台而是用通用的STM32系列芯片作为主控能够充分体现你对GPIO、定时器、中断、ADC、I2C/SPI通信、PWM等基础外设的掌握程度。无论以后你是打算走嵌入式软件开发路线还是做硬件设计这个题目都会成为一份相当完整的作品集素材。很多同学拿到这个题第一反应是“我是不是得先写一套操作系统进去”其实完全没必要。这个毕设的本质是裸机程序加传感器驱动再加上一个有限状态机来切换菜单界面。真正的工作量分布大概是硬件设计占三成传感器数据采集与滤波算法占四成显示与交互逻辑占两成文档与联调占一成。1.2 系统框架怎么搭才算合理整个系统可以拆成四个层次来看感知层心率传感器、加速度计或六轴IMU、温度传感器主控层STM32系列芯片负责读取数据、运算处理、调度任务交互层OLED显示屏、按键实现菜单切换和数据展示扩展层可选蓝牙模块、蜂鸣器、LED指示灯用于数据上报或提醒从主控到传感器的连接方式一般是I2C或SPI总线心率传感器如MAX30102和加速度计如MPU6050都用I2C温度传感器用单总线或I2C都可以。OLED屏也用I2C这样整个系统的接线非常简单总共就用到四根线加电源线SDA和SCL可以共用总线不用额外占用大量GPIO。任务调度上不需要上操作系统直接用主循环加定时器中断就够了。一般来说心率数据每秒刷新一次步数每100毫秒累计一次体温每两秒刷新一次OLED屏幕每秒刷新一次界面。这样主循环只要负责按时间片去轮询各个任务整个系统的实时性完全够用。1.3 为什么选STM32而不是别的芯片这个问题在毕设答辩时几乎必问所以想清楚背后的逻辑很重要。STM32系列的生态太成熟了从标准库到HAL库从数据手册到网上例程各种资源极其丰富。遇到问题你能找到无数前人的踩坑记录这对于做毕设来说是最重要的隐形优势。功耗和性能的平衡也是重要考量。STM32F103C8T6这款芯片在72MHz主频下运行正常工作电流在几十毫安级别睡眠模式下可以降到微安级别非常适合做可穿戴设备。相比51单片机性能强很多处理传感器数据时不用抠抠搜搜算指令周期相比树莓派这类Linux平台又不需要跑系统响应时间和实时性都有保障。还有一个实际原因开发工具链免费且成熟。Keil MDK或者STM32CubeIDE都可以用调试器用ST-Link就够了成本低上手快。所以结论是STM32F103C8T6是这个题目的黄金选择。如果你预算充足或者想显得更“前沿”也可以选STM32L4系列或者STM32F401功耗更低、性能更强但价格贵一些调试资料也比F1系列少考虑到毕设周期F103C8T6依然是最稳妥的选项。2. 硬件选型与电路设计要点2.1 主控芯片选型与最小系统设计主控推荐STM32F103C8T6这是一颗Cortex-M3内核的芯片主频72MHzFlash是64KBRAM是20KB引脚数48个封装是LQFP48。做手环这种规模的系统资源和引脚都刚好够用。最小系统包含四部分电源电路、晶振电路、复位电路、下载调试电路。电源电路要注意手环是电池供电电池电压一般是3.7V锂聚合物电池需要降压到3.3V给系统供电。常用的方案是AMS1117-3.3线性稳压芯片电路简单、便宜、够用。但AMS1117的效率偏低静态电流比较大如果你想让电池更耐用可以考虑RT9013或XC6206这类低压差低功耗LDO实际功耗表现会好很多。这一点在答辩时可以主动提出来会显得你考虑问题更全面。晶振电路用8MHz无源晶振加两个20pF负载电容给芯片提供主时钟源内部PLL倍频到72MHz。另外还需要一个32.768kHz的低速晶振用于RTC实时时钟如果你想让手环显示时间日期这个就不能省。复位电路就是10kΩ上拉电阻加100nF滤波电容再串一个按键到地按下复位。这是ST官方推荐的典型接法。下载调试电路用SWD接口只需要PA13SWDIO和PA14SWCLK两根线加上VCC和GND配上ST-Link调试器就能下载和在线调试比JTAG省引脚非常实用。2.2 心率脉搏传感器MAX30102的使用要点MAX30102是目前做毕设手环最常用的一款心率传感器模块它把红光LED、红外光LED和光电二极管封装在一起通过检测血液对光线的吸收变化来计算心率和血氧饱和度原理是光电容积脉搏波描记法也就是PPG。模块的工作电压是1.8V但I2C引脚可以容忍3.3V输入所以可以直接和STM32连接不需要电平转换。这一点要注意很多同学第一次用MAX30102时容易在电压上踩坑。接线很简单VIN接3.3VGND接地SCL接PB6STM32的I2C1时钟SDA接PB7I2C1数据。模块还有一个INT引脚可以接一个GPIO作为中断输入用于数据准备好时通知主控读取但裸机程序里用轮询方式读取也完全可行。PCB设计时要特别注意传感器开窗的位置MAX30102是贴片式光学传感器模块上有一块透明窗口戴在手腕上时窗口必须直接接触皮肤中间不能有任何阻挡或遮挡物否则信号质量会大打折扣。如果你是自己画板子记得在传感器底部开一个通孔或者让传感器伸出板边保证光路畅通。2.2.1 传感器数据读出的关键寄存器配置MAX30102的初始化配置主要在以下寄存器模式配置寄存器0x09设置为心率和血氧模式0x03SpO2配置寄存器0x0A设置LED脉冲宽度和采样率一般选400μs脉宽采样率设为100HzLED1脉冲幅度寄存器0x0C设置红光LED电流19.7mA是一个比较稳妥的起点LED2脉冲幅度寄存器0x0D设置红外光LED电流同样从19.7mA起步中断使能寄存器0x02使能FIFO几乎满中断和新的数据就绪中断配置完成后读取FIFO数据寄存器0x07和0x06就能拿到红光的ADC值和红外光的ADC值。每次读取会返回6个字节定义是第一个字节是状态接下来两个字节是红光数据最后两个字节是红外数据。这里有个非常关键的细节传感器要稳定输出有效数据不是上电就能立刻读到的。MAX30102有一个启动稳定过程必须让它先运行几秒钟等信号稳定后再开始采集前面那段数据直接丢弃。实测下来上电后至少等10秒再开始记录数据后面拿到的波形才是有意义的。2.3 加速度计与计步方案的选择计步功能的核心传感器是加速度计最经典的模块是MPU6050它包含一个三轴加速度计和一个三轴陀螺仪。但在手环场景里陀螺仪其实用处不大计步只需要加速度数据。而且MPU6050的功耗偏高如果只用来计步有点浪费。更合理的方案是选一颗单独的加速度计比如ADXL345或者LIS3DH。这类芯片是纯三轴加速度计功耗低分辨率高内置FIFO缓冲非常适合运动监测场景。其中ADXL345是老牌经典资料超多例程遍地都是用I2C接口和STM32通信配置简单逻辑清晰。接线和MAX30102一样挂在I2C总线上SCL和SDA分别接PB6和PB7INT1可以接一个GPIO用于运动中断检测。ADXL345的地址通过SDO引脚的电平决定接地时是0x53接高电平时是0x1D。默认模块上SDO是接地的所以地址是0x53。2.3.1 ADXL345的计步数学原理计步的核心思想很简单人在走路时身体会上下起伏这个上下方向的加速度会周期性地变化。把加速度计放在手腕上Z轴数据会呈现类似正弦波的波形。走一步就对应一个波峰和一个波谷。算法流程是以一定频率如50Hz读取三轴加速度数据计算加速度矢量的幅值magnitude sqrt(x^2 y^2 z^2)对幅值做低通滤波去除高频噪声设定一个动态阈值当幅值从低于阈值变成高于阈值时认为是一步加入步间隔限制两次有效步数之间至少间隔250毫秒排除因晃动产生的误触发这个算法看似简单但有一个坑很关键手环戴在手腕上手的摆动和身体的移动是叠加在一起的如果只用固定的阈值跑步和走路会互相干扰有时候走路时手大幅度摆动也会被计入步数。比较实用的改进是动态阈值法维护一个滑动窗口实时计算最近一段时间内加速度的最大值和最小值阈值取两者的中点比如(max min) / 2。这样系统能自适应不同振幅的运动走路和跑步都能准确计数。这个思路在答辩时讲出来会非常加分说明你不是“调用例程”而是真正理解了算法。2.4 体温检测用什么传感器体温测量可以选DS18B20或者红外温度传感器。DS18B20是接触式的需要贴肤测量封装形式有TO-92、不锈钢防水探头等它的特点是用单总线通信仅占用一个GPIO精度能达到±0.5℃测量范围-55℃到125℃很便宜代码逻辑也不复杂。但DS18B20的缺点是响应速度比较慢默认12位分辨率下一次温度转换需要最多750毫秒。如果你的系统对刷新率要求高需要把采样周期设计得宽松一些比如两秒读一次否则程序会卡在读温度的地方。红外温度传感器比如MLX90614是非接触式的不需要直接接触皮肤通过检测红外辐射来测量物体表面温度。它的反应速度很快精度也能达到±0.5℃。但模块价格大约是DS18B20的三到四倍而且非接触式测量对距离和环境因素比较敏感稳定性稍差一些。毕设场景下推荐用DS18B20成本低、代码成熟、可靠稳定。在最终论文里写清楚选型的理由是接触式测量更稳定戴在手腕上直接贴肤测量误差更小。DS18B20接线只有三根线VCC接3.3VGND接地DQ信号线接一个GPIO建议PB0。需要注意DQ线必须接一个4.7kΩ上拉电阻到VCC因为单总线协议是开漏输出的没有上拉电阻通信会不稳定。这个电阻不能省模块板载的可以不接但如果你是照着最小系统自己做的板子一定要加。3. 软件架构与核心算法实现3.1 软件整体框架裸机状态机软件设计上我不建议引入RTOS因为对毕设而言会引入额外的复杂度调试难度也会增加。用裸机加定时器时间片轮询的方式就非常清晰。整体结构是系统初始化配置时钟、GPIO、I2C、定时器、串口、OLED传感器初始化分别初始化MAX30102、ADXL345、DS18B20主循环不断执行各个子任务定时器中断每1毫秒进一次中断维护一个系统Tick计数器OLED刷新每秒刷新一次界面数据采集按各自周期采集心率、步数、体温数据按键扫描每50毫秒扫描一次检测是否有按键按下主循环的伪代码大致长这样int main(void) { SystemInit(); OLED_Init(); MAX30102_Init(); ADXL345_Init(); DS18B20_Init(); TIM2_Init(); while(1) { if(tick_1ms - last_heartbeat_tick 1000) // 每秒读一次心率 { read_heart_rate(); last_heartbeat_tick tick_1ms; } if(tick_1ms - last_step_tick 100) // 每100ms采样加速度 { read_accel_and_count_steps(); last_step_tick tick_1ms; } if(tick_1ms - last_temp_tick 2000) // 每2秒读一次体温 { read_temperature(); last_temp_tick tick_1ms; } if(tick_1ms - last_display_tick 1000) // 每秒刷新界面 { refresh_display(); last_display_tick tick_1ms; } scan_key(); } }这种架构的优点在于逻辑直观、实时性可控、每个任务的执行周期可以单独调节而且代码调试友好。哪个模块功能不对就把对应的if块注释掉单独调试。3.2 心率信号处理从PPG波形到BPM数值这是整个毕设的技术难点也是答辩时老师最爱深挖的部分。MAX30102拿到的是原始的红光AD值这个值不稳定会随着血液流动产生周期性波动。想要得到准确的心率必须做三个处理步骤滤波、去基线漂移、峰值检测。3.2.1 滤波与去基线原始PPG信号里混入了大量噪声包括环境光干扰、人体运动伪迹、工频干扰等。最粗暴但有效的方法是滑动平均滤波取最近N个采样点的平均值作为当前输出N通常取10到20。这种滤波方式虽然会引入一些相位延迟但对于50Hz左右的采样率来说延迟只有200到400毫秒完全不影响心率显示。基线漂移是指信号整体在缓慢上下移动比如手轻微的晃动就会导致基线偏移。处理方式是高频通过滤波也就是减去一段时间内的平均值。更简单的做法是计算最近200个采样点的滑动平均值作为基线然后用原始值减去这个基线值得到去除基线漂移后的信号。3.2.2 峰值检测算法检测PPG波形中的波峰是计算心率的关键。检测条件有三个当前点的值大于前后各若干个点的值局部最大值当前点的值高于某个动态阈值与上一个有效波峰之间的时间间隔在合理范围内比如300毫秒到2000毫秒之间每次检测到一个波峰就记录当前时间戳。用最近四到六个波峰间隔求平均再换算成分钟心率BPM 60000 / 平均间隔毫秒。这里有一个很实际的问题PPG信号有时候会出现“双峰”现象即在一个心跳周期内出现一个主波峰和一个重搏波峰如果重搏波的幅度足够大会被误判为一次新的心跳导致心率变成两倍。解决办法是在峰值检测中加入最小间隔限制以及通过滑动窗口对BPM值做中值滤波把超过合理范围的突变值过滤掉。下面是一段简化版的心率计算核心逻辑bool detect_peak(int16_t value, int16_t threshold) { static int16_t values[10]; static uint8_t index 0; static uint8_t count 0; values[index] value; index (index 1) % 10; if(count 10) count; if(count 10) { // 当前值是局部最大值吗 if(value values[(index 8) % 10] value values[(index 9) % 10] value values[(index 1) % 10] value values[(index 2) % 10]) { if(value threshold) { return true; } } } return false; }实际项目中还要加上时间间隔判断这里为了演示只展示局部最大值判断的部分。需要注意的是真实传感器的信号质量没有这么理想尤其运动状态下会混入大量噪声所以滤波器的参数要反复调整。3.3 计步算法的核心逻辑与代码实现ADXL345的计步算法以三轴加速度幅值作为输入。简单来说用下面的公式计算合加速度float magnitude sqrt(acc_x * acc_x acc_y * acc_y acc_z * acc_z);静止时理论上的合加速度是1g约9800 mG人走路时合加速度会在1g上下波动。我们设定一个动态阈值当合加速度从下往上穿过阈值线时计一步。整个流程写成代码是void count_steps(int16_t x, int16_t y, int16_t z) { float mag sqrt(x*x y*y z*z); // 滑动平均滤波 static float mag_history[16]; static uint8_t mag_index 0; mag_history[mag_index] mag; mag_index (mag_index 1) % 16; float sum 0; for(int i 0; i 16; i) sum mag_history[i]; float filtered_mag sum / 16.0f; // 动态阈值 static float threshold 1100.0f; // 初始阈值 static float max_val 980.0f; static float min_val 980.0f; if(filtered_mag max_val) max_val filtered_mag; if(filtered_mag min_val) min_val filtered_mag; threshold (max_val min_val) / 2.0f; // 峰值检测 static bool above_threshold false; static uint32_t last_step_time 0; if(filtered_mag threshold !above_threshold) { above_threshold true; uint32_t now get_tick_ms(); if(now - last_step_time 250) // 步间隔最小250ms { step_count; last_step_time now; } } else if(filtered_mag threshold) { above_threshold false; } }有个细节值得说明阈值在静止时会缓慢收敛但如果长时间静止max和min都会趋近于1g阈值也会趋近于1g这时候只要手轻轻动一下就可能误触发一步。所以更稳妥的做法是给阈值设定一个下限比如不能低于1.05g。3.4 PB0引脚上的DS18B20驱动开发经验DS18B20的单总线协议要求时序非常严格。在72MHz主频下延时函数必须用微秒级精确延时不能靠简单循环估算。我用的是系统定时器提供的微秒延时函数确保每个时间片准确。初始化时序是主机拉低总线480μs以上然后释放总线等待60μs如果单片机上拉存在则会出现一个低电平脉冲否则高电平。跳过ROM命令是0xCC启动温度转换是0x44读暂存器是0xBE。每个读写的bit时序都很讲究。写0时要拉低总线60μs后释放写1时拉低1~15μs后释放读时序时拉低1μs后释放然后在15μs内采样总线电平。我调试DS18B20遇到的最大问题是温度读出来始终是85℃后来查资料发现这是芯片复位后的默认上电值也就是说读取流程没有真正完成或者通信失败。排查下来是因为我的延时函数在72MHz下需要精细调整很多例程默认的是8MHz或者16MHz主频下的延时直接拿来用就会出问题。所以如果你用的是HAL库或者标准库一定要检查你的延时函数是否适配当前主频。4. 系统联调与常见问题实录4.1 数据采集不稳定怎么办心率数据是最容易出问题的环节。最常见的现象是屏幕上的心率数值跳来跳去从60多跳到150多完全没有参考价值。我排查这个问题的经历可以分享一下。第一步排除硬件连接问题。检查I2C总线上拉电阻如果SDA和SCL没有接上拉电阻或者上拉电阻太大/太小通信都会不稳定。I2C标准模式上拉电阻在4.7kΩ左右通信速率400kHz时上拉电阻可以降到2.2kΩ实测下来这个范围比较稳。第二步检查传感器贴肤状态。MAX30102必须紧贴皮肤有自然光照射进来时波形会叠加大量噪声。我在测试时一开始用手捏着模块信号非常好但真正戴到手腕上数据就乱了。原因是手捏的状态下传感器和皮肤贴合紧实而戴到手腕上时由于表带不贴合传感器跟皮肤之间有微小缝隙导致光路不稳定。解决办法是选择有弹性的表带让传感器始终压迫皮肤。第三步检查运动伪迹干扰。走路时手臂晃动PPG信号会混入大幅度的运动噪声。这不是传感器问题而是算法不够强。最简单有效的办法是提高采样率到100Hz及以上然后加一个中值滤波把异常跳变值直接剔除。手环在静态测量时心率准确率很高动态测量时误差会大一些这是PPG方案的物理限制能做的是让误差尽量小。4.2 OLED屏幕不显示或显示乱码的排查路径OLED屏用的是SSD1306驱动的0.96寸屏。第一次上电如果屏幕不亮或者花屏通常是下面几个原因之一I2C地址不对。SSD1306的I2C地址取决于SA0引脚的电平最常见的是0x3C但如果SA0接了高电平地址就是0x3D。初始化代码里地址填错屏幕就不会响应。复位引脚没处理好。部分OLED模块需要MCU控制RST引脚做硬件复位如果RST悬空某些批次屏幕可能起不来。初始化序列不完整。SSD1306对上电时序有要求必须先关闭显示、设置时钟、设置复用比例、设置显示偏移、开启电荷泵、设置内存寻址模式最后才开启显示。缺了电荷泵设置屏幕上什么都不显示因为面板没有电压。显示乱码更常见的原因是字库或者取模方式不匹配。OLED屏的显存是按列扫描的取模方式必须选“列行式”而不是“行列式”否则中文字符会显示成乱码或者“碎片”。我用的是PCtoLCD2002这个工具取模设置选“阴码、列行式、逆向”这样取出来字模和SSD1306的显存布局一致。4.3 电池不耐用如何从软件层面降低功耗如果做出来的手环连半天都撑不到这个作品在答辩时就会很尴尬。功耗优化有几个思路值得分享。第一是降低主频。STM32F103默认可以72MHz运行但手环这种场景其实不需要这么高的主频把系统时钟降到48MHz甚至36MHz动态功耗明显下降而计算性能完全够用。第二是合理用睡眠。OLED屏是功耗大户满亮度显示时电流能到20mA以上。可以在无操作时进入待机显示状态比如5秒无操作后清屏或降低亮度。心率传感器也可以用低功耗模式每次测量完成后进入待机等待下一次测量的定时唤醒。第三是检查传感器的工作模式。ADXL345默认是测量模式有几档输出数据速率可选把输出速率从3200Hz降到100Hz电流可以从140μA降到不到100μA。MAX30102同样支持通过配置寄存器控制LED电流电流越高信号越好但功耗越大实测下来把两颗LED电流都从50mA降到20mA信号质量依然可用功耗却少了一半。4.4 程序跑飞和死机的排查方向裸机程序常见的问题是“死循环里出不来”。我遇到过两个典型案例。第一个是DS18B20的读时序耗时过长因为温度转换需要750ms在等待期间如果还频繁刷新OLED就会导致OLED刷新不及时画面卡顿。这种情况不算死机但其实函数卡在等待转换完成。解决办法是不要用阻塞式等待把温度读取放到独立的时间片里在等待转换的时候去执行其他任务。第二个是I2C通信卡死。STM32的硬件I2C在异常情况下会进入总线忙状态如果没有超时处理程序会一直卡在等待总线释放的循环里。解决的办法是每次I2C通信都设置一个超时变量比如10ms内没有完成就强制复位I2C外设或者重新初始化总线。这个细节在裸机程序中非常重要因为你没有操作系统帮你管理异常恢复。uint8_t I2C_Read_With_Timeout(uint8_t dev_addr, uint8_t reg_addr) { uint32_t timeout 10000; // 发起通信 while(I2C_Is_Busy() timeout--) { delay_us(1); } if(timeout 0) { I2C_DeInit(); I2C_Init(); return 0xFF; // 超时返回错误值 } // 继续正常通信流程 ... }4.5 传感器数据都是零或满量程的排查方式出现这种情况十有八九是I2C地址写错了其次是传感器初始化序列没有正确执行。排查方法很简单第一步用I2C扫描程序扫描总线上所有设备打印出所有响应地址第二步检查设备是否真的在总线上响应地址是否和代码里配置的一致。MPU6050的默认地址是0x68但有些模块把它改成了0x69AD0引脚接高电平。ADXL345默认地址是0x53MAX30102是0x57。每个传感器的地址都不一样初始化代码里的设备地址要和芯片资料对上。如果扫描总线上什么设备都没有就要检查接线和上拉电阻尤其是SCL和SDA是否接反了。5. 显示界面设计与人机交互5.1 OLED界面如何设计才清晰0.96寸OLED屏幕分辨率是128×64像素非常小能显示的信息量非常有限。我采用多屏切换的方式每屏显示一类数据按键切换。第一屏显示当前时间和心率第二屏显示步数和运动距离第三屏显示体温和当前状态。屏幕布局上第一行放标题比如“心率”第二行放数值用大字号第三行放单位或者状态图标。中文显示用16×16点阵数字显示用8×16点阵这样一行可以显示8个数字清晰度刚好。字号选择也有讲究。OLED屏虽然小但显示心率数字时用大字号能明显提高视觉效果。128×64屏上心率数字可以用24×32大小的点阵虽然只有两个数字的宽度但大数字看起来确实更有“智能手环”的感觉。不过注意字模要提前准备好取模时要对准大小否则显示会错位。5.2 按键交互的逻辑设计手环一般只配一到两个按键交互逻辑要简单直观。我用的方案是单击切换页面长按进入校准模式或者执行某个特殊功能。按键检测要用“松手检测”不能简单检测低电平就去执行动作否则会因为抖动产生重复触发。我采用的方案是每50ms扫描一次按键连续检测到两次低电平认为按下有效松开后触发一次单击事件如果按下超过1秒视为长按事件。关键代码如下void key_scan(void) { static uint8_t key_press_cnt 0; static uint32_t key_down_time 0; static uint8_t key_state KEY_RELEASED; uint8_t level GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN); if(level 0 key_state KEY_RELEASED) { key_press_cnt; if(key_press_cnt 2) { key_state KEY_PRESSED; key_down_time get_tick_ms(); } } if(level ! 0) { if(key_state KEY_PRESSED) { if(get_tick_ms() - key_down_time 1000) { key_event KEY_LONG_PRESS; } else { key_event KEY_SINGLE_CLICK; } } key_state KEY_RELEASED; key_press_cnt 0; } }这种消抖方式比简单延时消抖好不会阻塞主循环也不会漏掉快速连按。5.3 显示实时性与功耗的平衡OLED每秒刷新一次就够用了刷新太快不但人眼看不出区别功耗还大。每次刷新时尽量只更新数值变化的区域不要整屏全刷。SSD1306支持区域刷新可以设置显示窗口然后只写变化的区域。我实测过整屏刷新电流12mA左右只刷部分区域电流能降到6到8mA。对于电池供电的设备这个差距很可观。6. 蓝牙扩展与数据上报6.1 为什么推荐加一个蓝牙模块如果想让作品更有亮点可以加一个蓝牙模块把传感器数据发送到手机App上。这样不仅方便调试在答辩演示时也能展示“智能手环不仅仅是本地显示数据还能和手机联动”的能力加分效果很明显。最稳妥的方案是HC-05蓝牙串口模块它内置蓝牙2.0协议通过串口和STM32通信开发非常简单。你只要把手环的数据按照一个自定义协议通过串口发送出去手机端用任意一款“蓝牙串口助手”App就能接收并显示数据。6.2 自定义串口通信协议的设计协议设计原则是“帧头长度数据校验”保证接收端能准确拆分每一帧数据。我的协议格式是字节位置内容说明00xA5帧头10x5A帧头220x06数据长度从第3字节开始到校验前30x01数据类型心率4-5心率值高字节在前60x02数据类型步数7-10步数值四字节无符号整数110x03数据类型体温12-13温度值温度乘以100取整14checksum前面所有字节的异或和接收端根据帧头定位根据长度字段判断帧的结束位置然后校验checksum。如果校验失败就丢弃整帧。这套协议后期扩展很方便加新的传感器数据只需要增加一个数据类型ID。7. 从毕设到实物PCB设计与外观整合7.1 用洞洞板还是画PCB如果你时间紧张用洞洞板飞线完全可行但说实话毕业设计做成洞洞板视觉效果和可靠性都会打折扣。飞线太多接触不良的概率很高传感器信号容易受干扰。我建议花两三天时间用立创EDA画一块简单的双层PCB尺寸做成40mm×20mm左右把主控、传感器、OLED接口全部集成上去。画板的重点有五条STM32的每个电源引脚都要接100nF去耦电容尽量靠近引脚I2C总线上拉电阻放在传感器附近MAX30102的光学窗口不要被铜皮和丝印覆盖电池接口要加一个100μF电解电容和100nF陶瓷电容并联滤波预留SWD调试接口的测试点PCB打样成本不高嘉立创打样五片只要几十块钱在这个环节上不要省。7.2 外观固定与穿戴体验外观上如果学校有3D打印机可以打印一个手环外壳把PCB和电池装进去没有3D打印机可以买一只现成的运动手表或者智能手环拆掉内部主板把自己的PCB塞进去表带沿用原装的。这样做的好处是穿戴体验很好传感器贴肤效果好数据可靠性大大提升。这部分的成本也不高都是几十块钱的事但是做出来的最终效果完全不一样。8. 调试工具与毕设答辩准备8.1 调试过程中必不可少的工具清单调试STM32项目以下工具我认为是刚需ST-Link V2调试器大约二十块钱USB转SWD下载和单步调试都靠它逻辑分析仪USB接口的24MHz 8通道逻辑分析仪大约三十块钱可以同时观察I2C时序波形、UART波形和GPIO电平变化排查通信问题时价值极大万用表测电源电压、检查短路、验证传感器供电是否正常串口转USB模块用于调试时打印日志调试时我建议把状态信息和关键变量通过串口打印出来观察程序运行状态比盯着OLED屏幕猜要高效得多。打印格式像这样printf(HR:%d BPM, Step:%d, Temp:%.1f C\r\n, heart_rate, step_count, temperature);所有传感器的原始数据、处理后的数据、计算过程中的关键值都可以打出来对比。很多问题一眼就能在日志里看出来。8.2 演示时怎么展示才专业答辩演示是临门一脚设计几个稳定的演示场景反复排练比临时发挥要可靠得多。我的建议是准备三个演示场景静态心率展示手环戴在手腕上保持静止让屏幕显示心率数值等待十秒左右数值稳定说明PPG信号处理有效走路计步在演示区里走固定步数比如走二十步然后停下来看步数是否为二十左右。步数有小误差也完全正常能在演示时解释清楚误差来源体温展示把系统开机后等待温度读数稳定屏幕显示出体温值提前准备一份常见问答列表把老师可能问的问题都写下来比如“为什么心率数值会有波动”“步数算法是怎样避免误计”“系统功耗是多大”“如果换一颗传感器会怎么改”。把这些问题的答案提前组织好答辩时会从容很多。8.3 论文中必须写清楚的技术细节论文写作有五个重点这部分不能偷懒系统总体框图说明各模块之间的数据流和控制流每个关键模块的原理图以及选型理由心率信号处理的算法流程图和核心代码片段计步算法的阈值动态调整策略系统的功耗分析和实测数据论文中的图表不用多花哨但一定要清晰准确。所有使用的公式都要有变量说明和单位。老师基本都会看这几页内容扎实一点答辩通过率会显著提升。本文还有配套的精品资源点击获取