
简介面向毕业设计与课程设计的单片机步数检测/计步器资料包整合了两套完整方案其一为51单片机步数检测计步器支持无线蓝牙APP上传其二为STM32单片机实现温度、心率、步数多参数检测。资源覆盖原理图、源程序、元件清单、开题报告、论文、实物图等全流程内容尤其适合电子、自动化、嵌入式方向学生参考或二次开发。包内共有290个文件大小约25MB。主要文件类型包括C/H源程序、JPG原理图与实物图、PDF/Word论文文档、HEX烧录文件、MP4演示视频、APK手机客户端安装包等可按功能模块快速定位代码工程用于程序学习图片与文档用于设计与论文撰写视频和APK便于验证整体效果。此外元件清单、开题报告与论文等文件也一并收录方便对照硬件连接与设计文档完成实物制作。目前已有338人学习下载能够为步数检测系统提供从硬件到APP的直观参照。两大方案的源程序与原理图均可直接查阅配套的开题报告和论文可大幅节省写作时间STM32版本还额外提供功能描述适合在此基础上扩展传感器融合或健康监测功能。1. 为什么同样是51单片机计步器有的交开题报告就能过有的必须现场演示很多拿到这套资料的人第一反应是去翻main.c但真正决定计步器能不能现场跑起来的不是按键扫描而是ADC采样窗口和蓝牙中断优先级。这份资料同时给了两套设计一套是51单片机加蓝牙加Android APP的步数检测仪另一套是STM32上的温度、心率、步数多参数检测方案。第一套适合做完整毕业设计原理图、源程序、元件清单、开题报告、论文和实物图都能直接对照第二套可以给智能穿戴方向做功能扩展。如果你正在找“单片机 步数检测 计步器”这类关键词对应的源码和论文这套资料能让你跳过大部分查手册时间直接进入调阈值、抓包、改协议的状态。问题在于很多人把资料里的源码编译通过就以为万事大吉结果焊完板子后发现APP上步数乱跳、蓝牙偶尔断、开题报告里写的算法和实际代码对不上。下面我按硬件链路、计步算法、蓝牙协议、整机调试和离线验证五块拆开讲。2. 硬件链路从加速度传感器到AT89C51再到HC-05蓝牙2.1 两套资料里的传感器选型与信号调理第一套51版本常见方案是模拟输出三轴加速度模块例如MMA7361或ADXL335输出03.3V模拟电压直接接入外部ADC。为什么强调“常见”因为许多毕业设计套件的原理图几乎是同一套拿到实物后先确认板子丝印再对照元件清单避免把ADXL345数字传感器当模拟传感器接。第二套STM32版本里步数传感器可能换成了I2C接口的MPU6050或ADXL345主控不同驱动方式也不同。工程文件名里大量出现ADC.uvproj、ADC.axf、STARTUP.A51说明第一套的设计主线就是“加速度计模拟输出 → 外部ADC → 51串口 → 蓝牙”不是I2C方案。传感器输出的模拟量不是直接进单片机的。原理图上通常有一个RC低通滤波常见值是10k电阻串联、0.1uF电容到地截止频率约160Hz。这个电容不是普通去耦电容它负责滤掉人体抖动和电磁干扰。如果把它去掉步数显示会跳得很快如果电阻换成1k滤波效果又不够。焊接时一定要按原理图原值焊不要觉得“多个电容没必要”。下面是这套51系统里最常见的硬件链路可以对着元件清单看模块常见型号接口作用加速度传感器MMA7361 / ADXL335模拟电压输出把步行加速度转换成电压ADC转换芯片ADC0832 / ADC0804串行/并行把模拟电压转成数字量主控AT89C51 / STC89C52IO口采样、计步算法、串口发送蓝牙模块HC-05 / HC-06UART TTL把步数透传到手机APP显示LCD1602 / OLED并口/I2C本地显示计步结果电源AMS1117-3.3稳压给传感器和蓝牙供电需要注意MMA7361和ADXL335的零g电压不是0V而是约1.65V供电3.3V时。也就是说静止时ADC读数不是0而是中间值。后面算法里的初始阈值就是按照这个中间值设定的。2.2 ADC0832连接与读取代码51单片机大多没有片上ADC所以需要外扩。最常见的是ADC08328位、双通道、串行控制。接线一般是CS接P1^0CLK接P1^1DIO接P1^2。读取一个通道的代码可以这样写sbit ADC_CS P1^0; sbit ADC_CLK P1^1; sbit ADC_DIO P1^2; unsigned char adc0832_read(unsigned char channel) { unsigned char i, tmp1 0, tmp2 0; ADC_CS 0; ADC_CLK 0; ADC_DIO 1; // start bit ADC_CLK 1; ADC_CLK 0; ADC_DIO channel; // 选择CH0或CH1 ADC_CLK 1; ADC_CLK 0; ADC_DIO 0; // ODD/SIGN位 ADC_CLK 1; ADC_CLK 0; ADC_DIO 1; // 采样模式 ADC_CLK 1; ADC_CLK 0; for (i 0; i 8; i) { ADC_CLK 1; ADC_CLK 0; tmp1 (tmp1 1) | ADC_DIO; } for (i 0; i 8; i) { ADC_CLK 1; ADC_CLK 0; tmp2 (tmp2 1) | ADC_DIO; } ADC_CS 1; return (tmp1 tmp2) ? tmp1 : adc0832_read(channel); }这段代码里channel传0读CH0传1读CH1。ADC0832会在一次转换中输出两次8位数据正常情况下两次结果一致不一致就重新读用来滤掉DIO线上的毛刺。很多源程序里读回来全是0xFF不是传感器坏了而是DIO方向没有切换导致数据线一直被拉高。另外静止时传感器输出1.65V8位ADC参考电压3.3V计数值应该在128附近上电后先串口打印这个值如果差很多要查传感器供电和AGND是否和单片机共地。2.3 STM32版本里温度、心率与步数通道第二套STM32资料多了温度和心率。常见做法是步数用数字加速度计通过I2C读取心率用模拟脉冲传感器接到ADC引脚温度用DHT11单总线。STM32F103的ADC是12位心率信号较弱采样时间不能选最短。初始化片段大致如下void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig {0}; hadc1.Instance ADC1; hadc1.Init.ScanConvMode DISABLE; hadc1.Init.ContinuousConvMode ENABLE; hadc1.Init.NbrOfConversion 1; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; if (HAL_ADC_Init(hadc1) ! HAL_OK) { Error_Handler(); } sConfig.Channel ADC_CHANNEL_1; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_55CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); }这里连续转换模式打开后最好配DMA否则主循环不断读ADC会占用大量时间。采样时间不使用1.5周期而用55.5周期因为心率信号幅度小、内阻高采样时间太短会拉低信号。DHT11不要接到ADC通道它的单总线时序要求微秒级延时放在普通GPIO上用定时器或延时函数更合适。温度和步数数据最后通过同一个串口帧发送协议格式可以复用第一套51方案的帧结构。3. 步数检测算法从ADC原始值到稳定计数3.1 固定阈值为什么不行很多初版源程序里只有一句话if (adc_value 140) step;这在实验室慢走时勉强能数对但跑起来会翻倍坐着抖腿也会计数。原因是步行时加速度波形不是正弦波而是一个周期内有两个波峰脚跟触地和脚尖蹬地。如果只设固定阈值一个完整步行周期可能触发两三次。坐公交时车身震动频率大约10Hz半秒就能“走出”十几步。正确做法是看加速度波形的整体起伏而不是单点和固定值比较。正常情况下步频大约12.5Hz一个周期4001000ms所以采样周期取5ms、滤波窗口取16个点比较合适。这样既不会放过一次抬脚也不会把高频抖动当作步数。3.2 滑动窗口均值滤波ADC原始值会有毛刺最便宜且有效的滤波方式是滑动平均。以下这段在51和STM32上都能跑#define SAMPLE_WIN 16 static unsigned int sample_buf[SAMPLE_WIN] {0}; static unsigned char sample_idx 0; static unsigned long sample_sum 0; unsigned int adc_smooth(unsigned int raw) { sample_sum - sample_buf[sample_idx]; sample_buf[sample_idx] raw; sample_sum sample_buf[sample_idx]; sample_idx (sample_idx 1) % SAMPLE_WIN; return (unsigned int)(sample_sum / SAMPLE_WIN); }逻辑很简单每次采样前把最老的值从累加和里减掉把新值加进去再求平均。窗口16在5ms采样周期下对应80ms低通步频2Hz的周期是500ms80ml的窗口只会做平滑不会把波形压平。需要注意sample_buf初始全0时前几个输出值会偏低上电后可以先连续采样16次填满缓冲再开放计步逻辑。3.3 动态阈值加状态机实现把原始ADC值平滑之后下一步是找波峰和波谷。这里用动态阈值阈值取波峰和波谷的均值比固定阈值适应快走、慢走和上下楼。状态机代码如下#define ST_DOWN 0 #define ST_UP 1 static unsigned char step_state ST_DOWN; static unsigned int peak_val 0; static unsigned int valley_val 1023; static unsigned int dyn_threshold 128; static unsigned int last_val 128; static unsigned int step_count 0; unsigned int step_detector(unsigned int smooth) { if (smooth last_val) { if (smooth peak_val) { peak_val smooth; } if (step_state ST_DOWN smooth dyn_threshold) { step_state ST_UP; } } else if (smooth last_val) { if (smooth valley_val) { valley_val smooth; } if (step_state ST_UP smooth dyn_threshold) { step_count; step_state ST_DOWN; dyn_threshold (peak_val valley_val) 1; peak_val 0; valley_val 1023; } } last_val smooth; return step_count; }状态机的逻辑是从静止状态出发只有信号从下方穿过动态阈值进入ST_UP再从上方穿过阈值进入ST_DOWN才算一步。每完成一次穿越就把阈值更新为上一波的峰谷均值这样连续走和突然改变速度都不会卡死。valley_val复位成1023是因为8位ADC最大只有255用一个大值占位第一次更新前低谷会被立即记成第一个低点。这个状态机还有一个需要注意的地方如果长时间静止传感器会有微弱漂移峰谷差可能很小但如果始终在阈值附近来回穿步数还是会增加。常见的做法是在主循环里加一个最小幅值判断peak_val - valley_val小于8就认为不是有效踏步。下表是这套算法推荐参数参数推荐值作用SAMPLE_WIN16滤波窗口抑制高频毛刺采样周期5ms定时器中断触发最小峰谷差8小于该值视为抖动蓝牙发送周期1000ms降低串口压力3.4 定时器中断里的调度推荐在定时器中断里做采样、滤波和步数判断主循环只处理显示和蓝牙发送。51的定时器0初始化可以这样写void timer0_isr(void) __interrupt(1) { static unsigned char tick 0; unsigned int raw, smooth; TH0 0xEC; // 12MHz晶振5ms重装值 TL0 0x78; TF0 0; raw adc0832_read(0); smooth adc_smooth(raw); step_detector(smooth); if (tick 200) { tick 0; send_step_packet(step_count); } }12MHz晶振下定时器初值0xEC78对应5ms。如果板子实际用的是11.0592MHz晶振这个初值要改否则串口波特率对但实际计时会飘步数会忽快忽慢。定时器中断里做ADC读取比较耗时间把蓝牙发送放在中断里并不可取因为串口发送是阻塞的一个字节等一个TI标志还好发6个字节会占用接近1ms可能丢掉下一次采样。所以上面代码里只设置发送标志由主循环发送帧。4. 蓝牙通信与APP联动从AT指令到Android解析4.1 HC-05配置HC-05最常见的坑是波特率不匹配。很多模块出厂波特率是38400而单片机源程序用的是9600结果APP收到的全是乱码。先用USB转TTL接HC-05按住模块按键上电进入AT模式然后发送ATUART9600,0,0返回OK后断电重上电再用手机配对配对密码一般是1234。如果要把设备名改成自己的课题名可以用ATNAME51Pedometer注意HC-05的RXD要接单片机TXDTXD接单片机RXD两者必须共地。实物图里蓝牙线和USB转TTL交叉接法经常相反这是连不上APP的头号原因。4.2 自定义帧协议蓝牙透传是纯字节流没有包边界因此单片机端必须自己组帧。这套51工程里最清晰的做法是每秒钟发一帧6字节数据字节内容说明00xAA帧头10x55帧头220x01命令字段3steps_hi步数高8位4steps_lo步数低8位5checksum异或校验帧头用两个字节是为了降低APP误判概率。开发中遇到过不知道哪里飘来的0xAA如果只有一个帧头很容易把垃圾数据解析成步数。步数按unsigned int处理超过65535步后建议清零重计。4.3 单片机发送函数发送函数如下void send_step_packet(unsigned int steps) { unsigned char pkt[6]; pkt[0] 0xAA; pkt[1] 0x55; pkt[2] 0x01; pkt[3] (steps 8) 0xFF; pkt[4] steps 0xFF; pkt[5] pkt[0] ^ pkt[1] ^ pkt[2] ^ pkt[3] ^ pkt[4]; for (unsigned char i 0; i 6; i) { SBUF pkt[i]; while (!TI); TI 0; } }这段代码里while(!TI)是阻塞的所以不适合放在定时器中断里。可以用一个标志让主循环调用。异或校验的计算开销非常小51单片机执行一帧异或只有几十条指令比CRC8更划算。如果APP收不到完整帧先用串口助手看数据里有没有连续的AA 55 01没有就要查单片机串口波特率和蓝牙透传是否正常。4.4 Android BluetoothSocket解析Android端使用经典蓝牙SPP协议核心UUID是UUID SPP_UUID UUID.fromString(00001101-0000-1000-8000-00805F9B34FB);连接建立后APP从InputStream里读字节并解析帧。一个可用的解析片段是BluetoothSocket socket device.createRfcommSocketToServiceRecord(SPP_UUID); socket.connect(); InputStream is socket.getInputStream(); int b; while ((b is.read()) ! -1) { if ((b 0xFF) 0xAA) { int b2 is.read(); if ((b2 0xFF) 0x55) { int cmd is.read(); int hi is.read(); int lo is.read(); int sum is.read(); int calcSum 0xAA ^ 0x55 ^ cmd ^ hi ^ lo; if ((sum 0xFF) (calcSum 0xFF)) { int steps ((hi 0xFF) 8) | (lo 0xFF); runOnUiThread(() - tvStep.setText(步数 steps)); } } } }这里每次读到一个0xAA就继续读后续5个字节然后做校验。runOnUiThread把结果更新到界面因为InputStream.read()是阻塞的必须在子线程执行不能直接操作UI。Android 6以上扫描蓝牙设备需要动态申请定位权限否则BluetoothAdapter.startDiscovery()扫描不到HC-05这类经典蓝牙模块。4.5 STM32版本里BLE的注意点第二套STM32资料如果用的是BLE模块就不能按上面的Socket方式连接。BLE是GATT协议Android端要扫描BLE广播、连接GATT服务再找到notify特征值。工程文件名和原理图决定方案如果原理图上是HC-05这类串口透传模块就按经典蓝牙处理如果模块是CC2541、CH9141或者JDY-23就要走BLE。调试时先看手机蓝牙列表里能不能搜到模块名搜到再看是“配对连接”还是“可连接设备”两种模式对应完全不同的Android API。5. 从原理图到实物图焊接、烧录与答辩演示排错5.1 上电检查顺序拿到实物后不要急着下载程序。先按以下顺序检查电源部分VCC对地有没有5V传感器和蓝牙供电是不是3.3V晶振是否起振最好用示波器看两个引脚复位电路在按下复位键的时候复位脚会被拉低最后再检查串口TXD/RXD是否交叉。很多实物图拍摄效果很好但现场演示时蓝牙连不上原因就是HC-05的VCC被接到了5V源上而模块板载稳压器质量差导致模块持续发热重启。第一套资料里的元件清单拿出来和板子实物对照重点看三处加速度传感器的方向箭头是否和PCB丝印一致ADC0832的第一脚是否对应正确蓝牙模块的电源脚旁边有没有100uF电容。实物图里往往能看到焊接痕迹但在原理图不完整的情况下用万用表量模块供电点比盯屏幕更有用。5.2 常见故障定位表把调试中最容易遇到的问题整理成表故障现象可能原因排除方法APP收不到数据蓝牙波特率不匹配ATUART9600,0,0步数猛跳滤波未开启或阈值太小检查滑动窗口和峰谷差串口工具显示乱码晶振频率和串口初始化不一致确认12MHz或11.0592MHz蓝牙连上但断流电源压降蓝牙单独3.3V供电开题报告和代码方案不一致资料版本不对先核对元件清单和工程文件名答辩时老师经常指着原理图问“这里为什么加一个电容”如果回答“滤高频”只能算合格如果回答“10k和0.1uF组成约160Hz的低通保留12.5Hz步行频带同时抑制步行中裤腿摩擦和电机干扰”这个分数就上去了。5.3 用单片机打印ADC日志验证蓝牙调不通的时候先不要折腾手机。把蓝牙模块拆下来用USB转TTL直接接到单片机串口先确认源程序的主循环在正常发帧。为了验证算法可以在单片机里加一个临时函数void dump_adc_log(void) { unsigned char i; for (i 0; i 100; i) { printf(%d\r\n, adc0832_read(0)); delay10ms(); } }这段代码会把100个原始ADC值按每10ms一个打印到串口。使用printf前要先重定向putchar到串口否则什么都不会输出。把串口助手的接收内容保存成adc.log这就是后面离线调整阈值的原始素材。实际走路时打开这个函数走几步再关上手里拿到的数据就是算法调整的第一步。6. 用动态阈值参数表改出第二个“自己的”计步器6.1 用Python离线回放ADC日志烧录一次程序然后在走路、跑步、抖腿三种场景下各抓一份adc.log接下来可以在电脑上离线测试算法。下面的Python脚本可以在不碰单片机的情况下快速观察不同阈值带来的步数差异import numpy as np vals [] with open(adc.log) as f: for line in f: line line.strip() if line.isdigit(): vals.append(int(line)) smooth np.convolve(vals, np.ones(16) / 16, modesame) step 0 state 0 peak 0 valley 1023 threshold 128 for v in smooth: if v peak: peak v if v valley: valley v if state 0 and v threshold: state 1 elif state 1 and v threshold: step 1 state 0 threshold (peak valley) // 2 peak 0 valley 1023 print(步数:, step)这里把单片机里的状态机原样搬到了Python里np.convolve是滑动平均的向量化实现。同一个日志文件里把窗口从16改成8或者把状态机里加一个最小峰谷差就能看到步数变化。你可以快速试出适合自己步态的参数再把这组参数写回单片机源程序省去每改一次阈值就重新烧录一次板子的时间。6.2 减少LCD刷新对蓝牙发送的影响最后一个实战细节LCD1602刷新一次要几十毫秒如果每次步数变化都刷屏再同时在主循环里发蓝牙帧就会出现APP数据卡顿。常见做法是把LCD刷新放在步数变化标志置位时执行蓝牙帧保持每秒发送一次两件事不要在同一个循环里抢串口时间。这样改完之后APP上的步数是平滑递增的实物显示屏也不闪。把这套逻辑扩展到STM32第二套资料时只需把温度、心率读数放到同一个帧的不同命令字段一个点对点通信协议就同时兼容了步数检测和生理参数上报。本文还有配套的精品资源点击获取