简介:这是一套面向嵌入式初学者与物联网项目实践者的完整健康监测手环开发资源,基于STM32单片机实现步数统计、心率血氧检测、环境温度与实时时钟显示,并支持蓝牙数据上传至手机APP,适用于课程设计、毕业设计、工程实训及学科竞赛等实践场景。资源包共509个文件,含88个C语言源码(.c)、82个头文件(.h)、80个编译中间文件(.o/.d)及17个说明文档(.txt),另有Keil工程文件(.uvprojx)、调试配置(.dbgconf)、可执行镜像(.hex/.axf)、接线图、原理图、PPT汇报材料、手机APP安装包(HCbluetooth.apk)及详细项目文档,总大小416.35MB。已有622人学习下载,所有功能经实测运行成功,配套说明涵盖模块选型链接、接线示意图、传感器驱动逻辑与低功耗配置要点,便于快速复现或二次开发。 做单片机项目的朋友应该都有过这种经历:下载了一个压缩包,解压出一堆代码、原理图、PPT、还有手机APP安装包,但愣是不知道从哪儿看起。这个基于STM32的健康监测智能蓝牙手环项目就是很典型的案例——资料编号2,整套工程文件里塞了STM32系统源代码、系统配置与程序运行说明、原理图、模块选型购买链接、接线图、答辩PPT、手机APP安装包和项目文档。它解决的是一个非常实在的需求:用一块几十块钱的MCU搭出一个能测心率、测体温、能通过蓝牙把数据传输到手机上的可穿戴设备原型。
无论你是要交课程设计,还是在准备毕业设计,这个项目的技术栈都足够经典:单片机端做传感器采集和协议封装,蓝牙做无线透传,手机端做数据接收和展示。三端联动之间其实没有特别高深的技术,但每一个环节都有不少坑——传感器读不到数据、蓝牙连不上、APP闪退、心率数值乱跳,这些我全都踩过。这篇文章就把这套项目从硬件接线、固件逻辑、APP联调到问题排查完整拆开讲,给大家一份可以直接照着做的实操方案。
1. 项目整体设计与方案选型
1.1 系统架构:三端联动的数据链路
先把这个项目的数据流捋清楚。整个手环本质上是一个三层结构:
- 感知层:MAX30102心率血氧传感器通过I2C采集光电容积脉搏波数据,DS18B20或者MLX90614负责体温采集,有的版本还会加MPU6050做动作识别。
- 控制层:STM32F103C8T6作为主控,读传感器数据、做滤波算法、把结果封装成自定义数据帧,通过UART发给蓝牙模块。
- 传输与展示层:HC-05蓝牙模块把数据透传给手机APP,本地的0.96寸OLED同时显示实时数据。
这个架构最巧妙的地方在于各层之间耦合度很低。传感器模块只负责输出原始数据,STM32只负责处理和封装,蓝牙模块只做透传,APP只负责解析和展示。每一层出问题都能单独调试,不用从头到尾找bug,这一点对新手做项目非常友好。
很多物联网项目一上来就想着上云、上MQTT、上服务器,但作为课程设计和毕设,本地蓝牙方案已经完全够用,而且演示效果直观。你拿出手机,打开APP,心率曲线实时刷新,这种“看得见”的效果比后台日志里一串数字打动人多得多。
1.2 为什么选STM32加蓝牙串口方案
说实话,现在做无线监测可选的方案太多了。ESP32自带WiFi和蓝牙,Arduino写代码门槛更低,树莓派甚至能跑完整Linux系统。但为什么这类项目几乎都默契地选了STM32?我用下来觉得有几个很现实的原因:
第一,STM32F103C8T6的资料密度是其他芯片没法比的。无论是正点原子、野火还是各种教程,从GPIO到定时器、从I2C到USART,几乎每个外设都有现成例程。遇到问题搜一下,答案铺天盖地。
第二,性能对这个项目来说刚好合适。72MHz主频,20KB RAM,跑一个裸机主循环加定时器中断,处理心率滤波算法完全没问题。用ESP32反而有点“杀鸡用牛刀”,而且ESP32的BLE协议栈对新手上手来说更绕。
第三,蓝牙这块选HC-05而不是BLE模块,核心原因是SPP(串口透传协议)调试太方便了。HC-05在手机端就是一个普通的蓝牙串口,配对之后你甚至不需要自己写APP,用现成的“蓝牙串口助手”就能看到单片机发来的所有数据。而BLE模块要处理GATT服务、UUID、notify通知,调试链路长了一大截。开发效率和排错难度完全不在一个量级。
1.3 核心模块选型对比
| 模块 | 型号 | 接口 | 关键参数 | 选型理由 |
|---|---|---|---|---|
| 主控 | STM32F103C8T6 | 多种 | 72MHz主频,64KB Flash | 资料多、成本低,课程设计和毕设标配 |
| 心率血氧 | MAX30102 | I2C | 红光+红外双LED,内置ADC | 集成度高,一个芯片搞定PPG采集 |
| 体温 | DS18B20 | 单总线 | 精度0.5℃,分辨率可配置 | 硬件接线最简单,一根线传数据 |
| 红外测温(备选) | MLX90614 | I2C/SMBus | 非接触测量,精度0.5℃ | 做手环不用贴皮肤,更“智能” |
| 蓝牙 | HC-05 | UART-TTL | 经典蓝牙SPP,主从一体 | 手机端连接简单,串口透传 |
| 显示 | SSD1306 0.96寸 | I2C | 128x64像素 | 功耗低,显示信息足够 |
关于心率传感器的选择,MAX30102是目前这类项目的主流。它内部集成了红光LED、红外LED、光电二极管和ADC转换器,直接输出数字信号,不需要额外的模拟放大电路。相比之下,以前用MAX30100做心率采集,I2C时钟一高就容易卡死,而且没有血氧功能,MAX30102算是把那些痛点都解决了。
体温传感器如果要追求“手环气质”,我建议用MLX90614红外测温模块,因为它完全不接触皮肤,测的是额头或者手腕表面的红外辐射温度,APP端看起来更符合“智能手环”的定位。DS18B20虽然便宜稳定,但需要贴着手腕才能测准,实际戴在手上体验一般。两种方案代码驱动和接线差异不大,看自己需求选就行。
2. 硬件搭建与接线细节
2.1 最小系统板与引脚规划
做这种项目不需要自己画最小系统板,直接买STM32F103C8T6蓝色核心板就行,二十块钱左右,板载晶振、USB转串口、LED和复位按键,该有的都有。连接传感器和蓝牙模块时,用杜邦线在面包板上搭电路,完全能跑通。
引脚规划是整个硬件设计的核心,我在做这个项目时用的是下面这套方案:
- 心率传感器MAX30102:SDA接PB7,SCL接PB6,走I2C1总线。
- OLED显示屏:同样挂I2C1,SDA接PB7,SCL接PB6,和MAX30102共用总线,靠芯片地址区分。
- 蓝牙模块HC-05:TXD接PA10(USART1_RX),RXD接PA9(USART1_TX),注意是交叉连接。
- 体温传感器DS18B20:DQ接PA0,单总线协议,任意GPIO都行。
- 供电方案:所有模块统一用3.3V供电,或者用锂电池加TP4056充电板做便携电源。
这里有一个新手容易踩的大坑:I2C总线上挂多个设备时,地址不能冲突。MAX30102的7位地址是0x57,SSD1306 OLED的默认地址是0x3C,两者不冲突,可以安全共用。如果你的OLED模块地址被改过,或者传感器版本不同,初始化时先扫描一下总线,别上来就死磕寄存器。
2.2 接线表:可以直接抄作业的版本
| 模块 | 引脚 | STM32引脚 | 说明 |
|---|---|---|---|
| MAX30102 | VIN | 3.3V | 模块内置稳压,3.3V即可 |
| MAX30102 | GND | GND | 注意共地 |
| MAX30102 | SDA | PB7 | I2C1 SDA |
| MAX30102 | SCL | PB6 | I2C1 SCL |
| OLED | SDA | PB7 | I2C地址0x3C |
| OLED | SCL | PB6 | 与MAX30102共用总线 |
| HC-05 | TXD | PA10 | 接MCU的RX |
| HC-05 | RXD | PA9 | 接MCU的TX |
| DS18B20 | DQ | PA0 | 需接4.7k上拉到3.3V |
| DS18B20 | VDD | 3.3V | 外部供电模式 |
特别注意HC-05的RXD端。有些模块标注的VCC是5V,板上带了稳压芯片,但逻辑电平是3.3V,这种情况下接到STM32的3.3V系统里问题不大。但如果你买的模块是纯TTL电平版本,RXD直接接3.3V的PA9一般也能工作,因为高电平阈值兼容。反过来如果STM32是5V供电,接HC-05时就要注意电平匹配,最好加电平转换或者分压电阻。
2.3 电源设计与PCB画板注意事项
如果只是做实验,面包板加杜邦线就够了。但要是想把项目做成一个像样的手环,或者拿去参加答辩展示,建议画一块PCB。打样回来自己焊接,可靠性比杜邦线高几个档次。
画PCB时有几个细节值得注意:
- 电源线加宽。手环的整机电流不大,但蓝牙发射瞬间会有几十毫安的尖峰,电源线走1mm以上宽度,避免压降导致蓝牙模块重启。
- 每个芯片电源引脚旁边加100nF去耦电容,并且尽量靠近芯片引脚。
- MAX30102是光学传感器,PCB上对应的位置不要铺铜挡光,并且结构上要留出透光窗口。
- 锂电池的充电接口要注意防反接,TP4056模块的输出端要加保护电阻。
- I2C总线上拉电阻:很多模块板上已有上拉,如果自己画板,4.7k上拉到3.3V是稳妥值。
OLED和蓝牙模块是整个系统里最耗电的两个部分。如果要做成真正佩戴的手环,待机时建议关掉OLED显示,或者降低刷新率,蓝牙模块也尽量让它休眠。不过课程设计一般不考虑这么深的低功耗优化,能跑通演示就完成主要任务了。
3. STM32固件开发与核心逻辑
3.1 开发环境搭建与工程结构
开发STM32主要有两条路线:Keil MDK5配标准外设库,或者STM32CubeIDE配HAL库。资料包里提供的系统源代码大概率是基于Keil工程的,因为这是国内课程设计最主流的组合。打开工程后,第一件事不是急着编译,而是先看两个地方:工程的目标芯片型号,以及编译器版本。
如果是老工程,用新版本的Keil打开,默认编译器是AC6,老代码可能编译出一堆“warning”甚至直接报错。解决办法是在魔法棒(Options for Target)里的Target标签页,把ARM Compiler切换到“Use default compiler version 5”或者AC5兼容模式。这是我从“代码打开编译不过”到“跑起来”之间踩的最深的一个坑。
工程结构上,建议至少分成几个模块:main.c负责主循环和初始化,sensor.c封装MAX30102和DS18B20的驱动,ble.c处理串口和蓝牙数据帧封装,oled.c负责屏幕显示。每个模块一个头文件,接口清晰,后面要加功能或排错都很方便。
3.2 MAX30102心率采集与数据读取
MAX30102的数据读取是整个项目最容易卡住的地方。它的I2C地址是0x57,内部寄存器比较多,但核心流程并不复杂:初始化配置寄存器,然后循环读FIFO数据寄存器(地址0x04到0x07),每次能读出6字节,分别是红光高字节、红光低字节、红外高字节、红外低字节。
初始化时几个关键寄存器:
- REG_MODE_CONFIG(0x09):设为0x03,进入心率血氧模式(SpO2 mode)。
- REG_SPO2_CONFIG(0x0A):把采样率设为100Hz,LED脉冲宽度设成411us,这组参数在后续心率算法中用得上。
- REG_LED1_PA(0x0C)和REG_LED2_PA(0x0D):LED电流,初始值设在6mA左右,测试后根据信号质量微调。
读取FIFO时有个细节:每次读之前先判断数据就绪标志位,否则读到的是旧数据。具体实现可以轮询寄存器0x00的bit6,置1后再读FIFO。这个标志位没判的话,数据看起来也能读到,但会出现同一帧数据重复出现的情况,心率算法算出来的结果就会乱七八糟。
3.3 心率算法:从原始波形到BPM值
这是整个项目技术含量最高的部分。MAX30102直接读出来的是光电容积脉搏波(PPG)的原始ADC值,这个波形叠了环境光噪声、呼吸产生的低频漂移和各种高频干扰,不能直接拿来数峰值。要得到稳定的心率值,必须走完一整套信号处理流程。
我的做法是四个步骤:
第一步,采样。用定时器中断以100Hz的频率读取红外通道的数据,写入环形缓冲区。缓冲区大小设为5秒的数据量,也就是500个点,足够做后续处理。
第二步,去直流。原始信号的直流分量远大于交流分量,直接做阈值检测很难。用滑动平均窗口算出一个缓慢变化的直流基线,然后用原始值减去基线,得到交流分量。
第三步,低通滤波。交流分量里还残留高频噪声,用一阶低通滤波,系数设在0.2左右。系数太大信号平滑但延迟高,系数太小滤不干净,这个值需要实测调整。
第四步,峰值检测。对滤波后的信号找局部极大值,记录相邻两个波峰之间的采样点间隔,用采样率换算成每分钟心跳次数。
/* 简化版心率计算思路 */ #define SAMPLE_RATE 100 #define BUFFER_SIZE 500 float dc_baseline = 0.0f; float filtered = 0.0f; uint32_t last_peak_index = 0; float heart_rate_bpm = 0.0f; void heart_rate_task(void) { int16_t raw_ir = max30102_read_ir(); /* 读取红外通道 */ static float dc_sum = 0.0f; static uint16_t read_count = 0; /* 1. 计算直流基线 */ if (read_count < BUFFER_SIZE) { dc_sum += raw_ir; read_count++; dc_baseline = dc_sum / read_count; } else { dc_sum = dc_sum - dc_baseline + raw_ir; dc_baseline = dc_sum / BUFFER_SIZE; } /* 2. 去直流,得到交流信号 */ float ac_value = raw_ir - dc_baseline; /* 3. 一阶低通滤波 */ filtered = 0.2f * ac_value + 0.8f * filtered; /* 4. 峰值检测 */ static float prev_filtered = 0.0f; if (filtered > peak_threshold && filtered > prev_filtered) { static uint32_t sample_index = 0; uint32_t interval = sample_index - last_peak_index; if (interval > 30 && interval < 120) { /* 合理范围50-200bpm */ heart_rate_bpm = (float)(SAMPLE_RATE * 60) / interval; } last_peak_index = sample_index; } prev_filtered = filtered; sample_index++; }这套算法看着简单,但实际调参很考验耐心。阈值设太高会漏检,心率偏低;阈值设太低会把噪声当波峰,心率虚高。我最后是先用串口把滤波后的波形数据发到电脑上画图,观察波形幅度之后再定的阈值。调试阶段用串口把数据发出来看波形,比在开发板上盲调参数高效得多。
3.4 蓝牙数据包设计:协议是联调的关键
单片机发给手机的数据不能是一堆堆裸数据,必须封装成有结构的帧。我见过很多人直接把心率值用printf发出来,手机端按字符串解析,虽然能跑,但数据稍微一多就乱套。强烈建议从一开始就设计一个简单的帧协议。
推荐一个极简的帧格式:
帧头(0xAA 0x55) | 数据长度(1字节) | 命令字(1字节) | 数据载荷(1~N字节) | 校验字节(1字节)其中校验字节用异或校验,把前面所有字节做异或得到。比如上报心率,可以用命令字0x01,载荷是2字节的int16心率值;上报体温,用命令字0x02,载荷是4字节的float或者2字节的定点数。
/* 发送心率数据包 */ void ble_send_heart_rate(uint16_t bpm) { uint8_t tx_buffer[8]; tx_buffer[0] = 0xAA; tx_buffer[1] = 0x55; tx_buffer[2] = 3; /* 长度:命令字1 + 数据2 */ tx_buffer[3] = 0x01; /* 命令字:心率 */ tx_buffer[4] = (bpm >> 8) & 0xFF; /* 心率高字节 */ tx_buffer[5] = bpm & 0xFF; /* 心率低字节 */ tx_buffer[6] = tx_buffer[0] ^ tx_buffer[1] ^ tx_buffer[2] ^ tx_buffer[3] ^ tx_buffer[4] ^ tx_buffer[5]; HAL_UART_Transmit(&huart1, tx_buffer, 7, 100); }有协议的好处是手机端可以稳定地按帧解析,哪怕串口数据偶尔丢一帧,下一帧也能正常对齐。异或校验虽然简单,但对这类低速传感器数据足够用,比CRC16写起来省事,出错的概率也低得多。
数据包发送频率也要控制。心率没必要每秒发20次,因为蓝牙串口通量有限,发太快会造成数据堆积。我一般把发送频率控制在2Hz到5Hz,也就是每200到500毫秒发一帧,显示端效果已经很流畅。
3.5 体温采集:DS18B20时序坑与MLX90614替代方案
DS18B20是单总线器件,时序要求特别严格。初始化时主机拉低总线480us以上,然后释放,等待60~240us的应答脉冲。写时序和读时序的延时单位都是us级别,不能用HAL库的HAL_Delay(毫秒级)直接做,必须用for循环或者DWT实现微秒延时。
还有一个隐蔽的坑:单总线通信期间不能有关中断打断时序。如果在代码里开启了其他中断,而中断处理时间过长,刚好打断了DS18B20的读时序窗口,读出来的数据就会是0xFF或者错乱。我最后是把温度读取放到一个比较短的临界区里,读取期间暂时屏蔽中断,读完后立刻恢复。
如果你觉得自己调单总线时序太麻烦,直接换成MLX90614红外测温模块。读它的RAM地址0x07,得到的就是物体温度,一次I2C读操作就搞定,不需要复杂时序。它对APP展现也更友好,因为是“非接触测温”,在答辩现场演示时直接照着手腕一测就有数值,很有“科技感”。
4. 手机APP开发与联调
4.1 APP技术选型:经典蓝牙SPP的Android实现
资料包里带了手机APP安装包,说明整套项目已经闭环了。如果你要二次开发或者想自己写一遍,这里分享一下技术路线。
HC-05是经典蓝牙SPP协议,Android端不需要引入第三方框架,直接用系统BluetoothAdapter就能实现。基本流程是先获取蓝牙适配器,然后搜索周围设备,配对成功后建立RFCOMM套接字连接:
// Android经典蓝牙SPP连接核心代码 UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); BluetoothDevice device = adapter.getRemoteDevice("HC-05的MAC地址"); BluetoothSocket socket = device.createRfcommSocketToServiceRecord(SPP_UUID); socket.connect(); InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream();这个UUID是蓝牙串口服务的标准固定值,不需要改。connect()是阻塞方法,不能在主线程里直接调用,否则会ANR,要放到子线程里。
新版Android的权限是重灾区。Android 12及以上要求动态申请BLUETOOTH_SCAN、BLUETOOTH_CONNECT权限,目标SDK版本如果太高,还要在Manifest里显式声明,否则一打开APP就闪退。很多老教程的代码主要跑在Android 10以下的手机上,放到新手机上直接搜不到设备,原因就在这。
4.2 手机端帧解析与UI展示
APP端最重要的逻辑是从蓝牙输入流里解析出结构化的数据帧。我的做法是用一个循环读取输入流,查找帧头0xAA 0x55,找到后按长度字节读取后续内容,再做异或校验,校验通过后按命令字分发到对应界面。
private void parseFrame(byte[] buffer, int len) { // 假设buffer里已经按帧边界截取好 if (buffer[0] == (byte)0xAA && buffer[1] == (byte)0x55) { byte checksum = 0; for (int i = 0; i < len - 1; i++) { checksum ^= buffer[i]; } if (checksum != buffer[len - 1]) { return; // 校验失败,丢弃 } int cmd = buffer[3]; if (cmd == 0x01) { int bpm = ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); runOnUiThread(() -> heartRateView.setText(bpm + " BPM")); } else if (cmd == 0x02) { // 解析体温数据 } } }界面显示方面,心率部分建议画一个实时变化的曲线,用自定义View的onDraw方式也不是不行,但直接引入MPAndroidChart库更省心,几十行代码就能出一个带网格的折线图。体温和血氧可以用大号字体显示,搭配一个正常范围的配色提示,比如体温在36到37.3度之间显示绿色,超过37.3度显示红色,这样演示效果一目了然。
4.3 联调顺序:先分模块,再整体跑
联调是整个项目最考验耐心的环节。我的建议是严格按下面三步走,每一步都确认没问题再进下一步:
第一步,不带蓝牙,用USB转TTL模块把STM32的串口接到电脑,打开串口助手。如果能看到完整的数据帧,说明传感器采集和协议封装这半边是通的。这一步可以先确认数据内容有没有问题。
第二步,接上HC-05蓝牙模块,用手机自带的蓝牙设置搜索并配对,然后打开“蓝牙串口助手”这类现成工具连接模块。如果能在串口助手里看到和第一步一样的数据帧,说明蓝牙通信链路是通的。
第三步,安装资料包里的APP,连接HC-05,看APP是否能正确解析,或者直接显示数值。如果显示不对,先回到串口助手比对原始数据,判断是发送端的问题还是APP解析的问题。
这种分层排查的方式能把一次复杂的联调拆成多个简单的验证步骤。我见过太多人一上来就把三端全部接通,出了问题根本不知道在哪一层,最后只能从头到尾反复检查,耗时又心累。
5. 常见问题与排查心得
5.1 HC-05蓝牙模块连不上
这是提问率最高的问题,我把它列成速查表:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 手机搜不到模块 | 模块没上电,或没进入AT模式 | 上电前按住模块上的小按键再上电 |
| 能搜到但配对失败 | 配对密码错误 | 默认密码一般是1234,部分模块是0000 |
| 配对成功但收不到数据 | 串口波特率不匹配 | 用AT指令查询或修改模块波特率 |
| 收到数据但全是乱码 | 波特率不一致或者串口未共地 | 确认STM32和HC-05共地,波特率一致 |
| 连上后发数据没反应 | RXD/TXD接反 | 交叉连接:模块TXD接MCU的RX,RXD接MCU的TX |
HC-05的AT指令配置也是个常见问题。进入AT模式的方法是按住模块上的按键再上电,此时模块指示灯变成慢闪,才能识别AT指令。查询波特率用“AT+UART?”,当前值会回显出来。如果模块默认波特率和代码里的不一样,直接发“AT+UART=9600,0,0”改掉。注意AT模式下波特率是38400,不要用默认的9600去发AT指令,不然没反应。
5.2 心率数值乱跳,怎么排查
心率数值忽高忽低几乎是每个人都会遇到的一关。我总结下来主要四个原因:
第一个原因,传感器佩戴姿势不对。MAX30102是光反射式传感器,必须紧贴皮肤,但又不能按压太紧,否则毛细血管被压扁反而测不到脉搏波。用手捂住传感器是肯定不行的,因为手指的皮肤厚度和手腕不一样。
第二个原因,运动伪迹。手一抖,PPG波形里就会叠加大幅度的运动干扰。解决办法是增加滑动平均窗口,把窗口从10个点提到30个点,波形会平滑不少,代价是心率变化响应变慢,但对手环场景完全够用。
第三个原因,峰值检测阈值不合理。阈值大了漏检,阈值小了误检。最靠谱的做法是先用串口把滤波后的波形数据发到电脑,用串口绘图工具画出来,直观看到波峰幅度,再设定阈值。
第四个原因,电源纹波干扰。如果系统用USB供电,或者电源模块质量差,纹波会直接叠加到PPG信号里。排查方法是用电池供电对照测试,如果电池供电时数据明显更稳定,那就是电源纹波的问题。
5.3 程序下载烧录失败
STM32程序下载失败的原因其实就几个,逐个排查就能解决:
BOOT0引脚要拉到GND。STM32上电时,如果BOOT0被拉高,芯片会从系统存储器启动而不是用户Flash区,下载器会连不上或者下载后不运行。
ST-LINK驱动没装好。设备管理器里如果看到一个带黄色感叹号的未知设备,就是驱动问题。重新安装ST-LINK驱动,确认枚举成“ST-Link dongle”这类设备。
还有一类情况是程序里把JTAG引脚复用成了普通GPIO。默认情况下PA13、PA14、PA15、PB3、PB4是SWD/JTAG调试口,如果代码初始化GPIO时把这些引脚配置成了普通功能,调试器就再也连不上芯片了。解决方法是下载前按住板子的复位键,在点击Download的瞬间松开复位,有时候能抓住窗口期。最稳妥的办法是用串口ISP把Flash擦除,再恢复调试接口。
5.4 代码编译报错和运行异常
老工程在新版Keil里编译报错,十有八九是编译器版本不兼容。打开Options for Target,把ARM Compiler从默认的AC6改成“Use default compiler version 5”,大部分老代码就能编译通过。
找不到头文件的报错(比如“main.h: No such file”)是在Include Path里没加路径。在C/C++选项卡里把工程目录下的所有子文件夹路径都加进去,尤其是Hardware、Core、Drivers这些目录。
还有一个和运行逻辑相关的坑:STM32如果用了HAL库,串口接收可以采用中断方式,但发送如果也用中断,发送期间又开启新的发送请求,会导致数据丢帧。我的做法是发送用一个标志位保护,上一帧没发完不允许发下一帧,或者直接用阻塞式HAL_UART_Transmit,低波特率下也不会有明显卡顿。
6. 资料包使用指南:如何高效从零复现
拿到这套资料编号2的压缩包之后,不要急着把所有文件都打开看一遍。我的建议是按这个顺序来:
先看接线图和系统配置与程序运行说明,把硬件连接搞清楚。这一步能避免很多低级错误,比如引脚接错、供电电压不对导致模块烧毁。
再打开原理图,对照自己手里的模块确认型号。原理图里的模块型号如果和你买的不完全一样,很可能引脚排列也不同,不能直接照搬。
接着装好开发环境,编译源代码,烧录到板子里。资料包里的源代码大概率是能直接跑起来的,但前提是工程路径、编译器和库文件都正确。第一次编译如果报错,优先检查前文说的编译器版本和Include Path。
系统跑通之后,安装手机APP,连接蓝牙测试。如果APP的包名和签名与源代码不一致,可能连接不上或者搜索不到设备,这时候用Android Studio重新构建一次APK就能解决。
最后再看PPT和项目文档。这两个是答辩和报告的重要参考,但一定要自己重新梳理一遍,理解每个模块的原理。答辩时老师最喜欢问的就是“你这里为什么选这个芯片”“这个参数怎么确定的”,如果答不上来,就算代码是跑通的也很难拿高分。
6.1 从这套资料里能学到什么
这个项目虽然不算复杂,但麻雀虽小五脏俱全。从中学到的核心技能包括:
- 传感器数据采集和信号滤波处理,尤其是从原始PPG波形中提取心率这类生物信号处理思路。
- 自定义通信协议的设计思想,帧头、长度、命令、校验这一套框架在工业通信协议里也普遍适用。
- 蓝牙透传和Android端蓝牙开发的基础流程。
- 嵌入式项目调试的方法论:分层排查、先测再改、用串口输出波形辅助调试。
这些能力本身是通用的,换一个传感器、换一个MCU、换一种通信方式,核心方法还是这一套。
6.2 如果想扩展这个项目
这个手环的扩展空间非常大。如果你想让项目更出彩,有几个方向可以考虑:
接入阿里云物联网平台或者OneNET,把数据转发上云,手环变云端应用。这时蓝牙就变成了WiFi模块ESP8266或者4G模块,APP端变成小程序或者Web端,物联网的味道就更浓了。
增加运动检测功能,用MPU6050识别走路、跑步、睡眠状态,结合心率数据做一个活动强度评分。这部分算法不复杂,但对项目深度提升明显。
OTA固件升级,让手机APP通过蓝牙给STM32升级固件。这部分涉及Bootloader设计,难度上了一个档次,但做出来很加分。
数据存储与长期趋势分析,在手机端用SQLite存历史数据,画一周的趋势图,能直接对标商业手环的健康报告功能。
写在后面的一些体会
这套项目资料我翻来覆去看了不少遍,真正让我觉得有价值的不是那几份说明文档,而是代码里写着的各种“因为改了这里所以能跑”的细节。比如串口波特率为什么要和蓝牙模块保持一致,比如I2C时钟为什么不能设太高,比如峰值检测阈值为什么要根据实际波形调整——这些都是在文档里看不到的实战经验。
如果你正好要复现这个项目,我建议给自己留出至少两天的时间。第一天把硬件接好,烧录官方固件,用蓝牙串口助手把数据流跑通;第二天再研究代码逻辑和APP解析,尝试修改协议或者界面。按这个节奏,大多数人第三天就能让手环亮起来。要是第一天就卡在蓝牙配对,不用慌,对照上面的排查表一步步来,这本来就是最耗时间的环节。
踩过坑才会长记性。我最后的建议是:先把“能亮、能连、能显示”做出来,再考虑“做的更漂亮、更智能”。完成一个闭环的小项目,比看十篇教程都管用。后面想做什么升级,路就顺多了。
本文还有配套的精品资源,点击获取