资讯详情

基于STM32的宠物智能项圈设计:定位、计步与充电显示源码解析

📅 2026/9/15 13:53:38 | 华诺云谱 👁 阅读
基于STM32的宠物智能项圈设计:定位、计步与充电显示源码解析
简介基于STM32的宠物智能项圈完整源码资料包面向嵌入式方向毕业设计学生及STM32初学者涵盖定位、计步、充电显示三大核心功能模块。项目源自导师指导并通过答辩的毕业设计评审98分所有源码均经本地编译调试可稳定运行。资源共627个文件以C源文件341个、头文件112个、汇编文件43个为主体另含链接脚本、编译配置及工程文件等压缩包19.51MB目录结构清晰便于按功能检索。除了基础外设驱动还集成HAL库定时器、I2C通信等底层实现适合学习STM32外设配置、传感器数据处理及低功耗设计思路。目前已有146人学习下载对于需要完整项目参考或快速验证功能的学习者而言性价比高。1. 宠物智能项圈的三个硬需求正好对应 STM32 的三个强项拿到“基于STM32宠物智能项圈源码资料-定位计步充电显示源码亲测可用”这个标题懂行的人第一反应是这不只是一个功能列表而是一个典型的电池供电、多传感器协同、需要低功耗管理的嵌入式综合项目。GPS/BDS 定位模块负责给出经纬度六轴传感器或加速度计负责计步电源管理芯片负责充电状态检测和电量显示而 STM32 则把这些外设串起来并通过 OLED 屏或手机 App 把数据呈现给用户。相比用 Linux 开发板做同类产品STM32 的优势很直白单片解决方案、微安级待机电流、外设接口丰富、调试工具链成熟。对做毕业设计的学生、想快速出样机的创客或者需要在真实环境中验证定位与运动算法的工程师来说这个项目都能同时覆盖硬件选型、驱动编写、算法移植和功耗调优几个层面的能力。值得强调的是标题里的“亲测可用”四个字在嵌入式源码圈里基本等同于“已经有人在真实 PCB 上跑通了”这意味着方案里的电源拓扑、传感器配置和外设引脚分配是经过实际验证的不是纸上谈兵。接下来我按自己动手做这类项目的惯用路径把方案拆开来讲。2. 硬件架构与核心选型先定外设再定引脚最后写代码2.1 主控选型STM32F103 还是 STM32L4常见的方案有两种。一种是 STM32F103C8T6蓝色药丸板那种1 元级价格、资料海量、库函数和 HAL 都顺手缺点是功耗偏高停止模式还有几个毫安。另一种是 STM32L4 系列比如 STM32L431主打低功耗停止模式能到微安级但价格贵一倍以上而且 Keil 的芯片包和低功耗外设配置对新手不太友好。我的建议是如果是做毕业设计或者验证算法F103 完全够用不必在功耗上死磕如果目标是一个能连续跑一周以上的真实产品原型L4 的收益更明显。两者在代码层面的主要差异在时钟配置和低功耗模式外设驱动的写法基本一致。选好芯片后直接用 STM32CubeMX 生成工程骨架避免手写启动文件和时钟树。# 用 STM32CubeMX 生成工程时的关键配置版本 6.x # MCU: STM32F103C8T6 # 时钟: HSE 8MHz - PLL - 72MHz SYSCLK # USART1: 115200 8-N-1连接 GPS/BDS 模块 # USART2: 9600 8-N-1连接蓝牙模块调试用 # I2C1: 400kHz连接 OLED 显示屏和六轴传感器 # ADC1: 采集电池电压采样时间拉到最大这里有一个容易被忽略的点GPS 模块上电后首次定位可能要拉高天线而且它的串口波特率出厂默认是 9600但很多模块支持通过指令切到 115200。如果你在代码里初始化 USART1 时写错了波特率串口助手能看到乱码但定位数据永远解析不出来。我一般会让 USART1 同时支持 9600 和 115200 两个波特率的自动切换。2.2 定位模块的选型与接线从 NMEA-0183 说起常见的定位模块有 ATGM336H中科微北斗GPS 双模、NEO-6M老牌已停产但淘宝仍有大量库存、以及国产替代型号。推荐 ATGM336H因为它支持北斗国内实测定位速度和精度比纯 GPS 好价格在 20 元左右串口直接输出 NMEA-0183 协议数据。NMEA 协议是纯文本以$开头GPRMC是最常用的定位语句包含 UTC 时间、定位状态A/V、纬度和经度。STM32 只需要在串口中断里逐字节接收拼出完整的一行再用sscanf或者手写解析器提取字段即可。注意 WGS-84 坐标系和 GCJ-02 火星坐标系的差异国内地图上会偏移几百米如果你把数据传到微信小程序或者网页地图必须要做坐标转换。// GPS 串口接收处理的核心逻辑简化版 void GPS_UART_IRQHandler(void) { uint8_t ch; if (LL_USART_IsActiveFlag_RXNE(USART1)) { ch LL_USART_ReceiveData8(USART1); if (ch $) { gps_rx_index 0; // 新语句开始重置缓冲区 } if (gps_rx_index GPS_BUF_SIZE) { gps_rx_buf[gps_rx_index] ch; if (ch \n) { gps_line_ready 1; // 收到完整一行置标志位 gps_rx_index 0; } } } } // 在主循环中解析 GPRMC 语句 if (gps_line_ready) { gps_line_ready 0; if (strstr((char *)gps_rx_buf, $GPRMC)) { float latitude, longitude; char lat_dir, lon_dir, status; // 按逗号分隔提取字段 sscanf((char *)gps_rx_buf, $GPRMC,%f,%c,%f,%c,%f,%c, utc_time, status, latitude, lat_dir, longitude, lon_dir); if (status A) { // A有效定位V无效 // 将 ddmm.mmmm 格式转换为十进制小数 gps_data.lat convert_nmea_to_decimal(latitude, lat_dir); gps_data.lon convert_nmea_to_decimal(longitude, lon_dir); gps_data.fix_valid 1; } } }上述代码里convert_nmea_to_decimal的算法是取前两位为度剩余为分用度 分 / 60.0得到十进制度数。latitude变量本身就是ddmm.mmmm格式直接除 100 是不可行的必须用这个逻辑转换。很多新手在这一步直接数值相除结果定位点差几十公里。2.3 充电与电量显示从 TP4054 到 ADC 采集充电显示在标题里单独被点名说明这个模块不能只用一颗 LED 应付。真实场景中用户需要知道“正在充电”“充满”“当前电量百分比”三个状态。充电管理芯片用 TP4054——单节锂电池线性充电方案最大 500mA 充电电流引脚少外围只要一个电阻设定电流。常用配置是 1.2k 欧姆电阻对应 400mA 充电电流既能兼顾充电速度又不会让芯片太烫。充电状态的读取通过 CHRG 引脚实现充电中为低电平充满或电池未接入时为高电平。用 STM32 的 GPIO 输入模式读取该引脚即可。电量百分比则依赖电池电压的 ADC 采样这里有一个绕不开的坑锂电池电压范围是 3.0V 到 4.2V而 STM32 的 ADC 参考电压是 3.3V如果直接用分压电阻把电池电压降到 ADC 可测范围低电量时分辨率会不足。这个时候可以让 ADC 以 12 位采样计算得到电压后通过查表法映射电量。业界常见的做法是采样两个分压电阻100k 和 100k得到 1/2 的电池电压再用查表法做电量映射。// 电池电压 ADC 采集与电量换算 #define ADC_REF_VOLTAGE 3.3f #define ADC_RESOLUTION 4096.0f #define VOLTAGE_DIVIDER_RATIO 2.0f // 分压系数100k:100k uint16_t battery_adc_read_raw(void) { LL_ADC_StartConversion(ADC1); while (!LL_ADC_IsActiveFlag_EOC(ADC1)); return LL_ADC_GetConversionValue(ADC1); } float battery_get_voltage(void) { uint32_t raw_sum 0; for (uint8_t i 0; i 8; i) { raw_sum battery_adc_read_raw(); // 多次采样取平均 } float raw_avg raw_sum / 8.0f; return (raw_avg / ADC_RESOLUTION) * ADC_REF_VOLTAGE * VOLTAGE_DIVIDER_RATIO; } uint8_t battery_get_percent(float voltage) { // 典型锂电池 3.0V~4.2V分段线性映射 if (voltage 4.2f) return 100; else if (voltage 3.0f) return 0; else return (uint8_t)((voltage - 3.0f) / (4.2f - 3.0f) * 100.0f); }代码里连续采样 8 次取平均值是因为锂电池在充电过程中会有微小的电压波动单次采样结果不稳定。需要强调的是ADC 的采样时间参数如果默认太短内部采样电容可能没有完全充放电导致数值整体偏低。建议在 CubeMX 里把采样周期配置到最大如 239.5 周期。另外充电过程中电池电压会被充电芯片拉高约 0.1V 到 0.2V这个误差在电量显示时肉眼可见可以在代码里增加一个判断如果检测到正在充电则自动降低 5 个百分点再做显示。电池电压区间对应电量显示适用场景4.0V - 4.2V100% - 80%刚充满日常使用3.7V - 4.0V80% - 30%正常放电平台期3.3V - 3.7V30% - 10%快速下降建议准备充电低于 3.3V低电量警告应及时充电2.4 计步方案加速度计的选型与安装位置计步传感器优先选择 MPU6050六轴含三轴加速度和三轴陀螺仪或成本更低的 LIS3DH纯三轴加速度计。前者的好处是数据精度更高、可以通过 DMP 库直接输出姿态角缺点是驱动复杂、功耗偏高后者在计步场景已经够用驱动简单待机电流可以到 1 微安。我的选择是 MPU6050因为代码可以直接复用成熟的运动传感器驱动而且网上基于它做计步的算法参考非常多。硬件的安装位置直接影响计步准确性。传感器应该固定在项圈的内侧紧贴宠物的颈部下方X 轴指向运动方向。如果装反了或者悬空算法很难区分步伐和噪声。3. 计步算法的工程实现从原始数据到稳定的步数3.1 加速度数据的读取与预处理计步的原始数据来自 MPU6050 的加速度计量程设置为 ±2g 或 ±4g输出频率 50Hz 即可。每读取一个原始值先换算成实际重力加速度值然后做高通滤波——去除重力分量和低频漂移。宠物静止时加速度计 Z 轴读数接近 1g运动时三个轴都会有明显波动但步伐检测主要靠合成加速度向量。合成加速度是常用的计算方式。每次运动时三个轴的加速度向量合成为一个总加速度值再对合成值做高通滤波呈现一个以 1g 为中心上下波动的曲线。具体实现时用一阶高通滤波器消除直流分量然后取波峰波谷的差值作为判断依据。// MPU6050 数据读取与合成加速度计算 #define FILTER_ALPHA 0.8f // 高通滤波系数越大滤掉越多低频成分 float acc_synthetic[3]; // 滤波后的三轴加速度 float acc_filtered_magnitude 1.0f; void accel_step_detect(void) { float raw[3], magnitude; mpu6050_read_accel(raw); // 读取原始加速度单位 g magnitude sqrtf(raw[0]*raw[0] raw[1]*raw[1] raw[2]*raw[2]); // 高通滤波去除重力直流分量 acc_filtered_magnitude FILTER_ALPHA * (acc_filtered_magnitude magnitude - acc_filtered_magnitude); // 此时 acc_filtered_magnitude 围绕 0 波动正值表示有向上加速度 // 阀值判断 峰值检测逻辑在下一小节展开 }高通滤波的思想是每一个新采样值中只保留当前值与历史滤波值之间的差值增量从而不断衰减低频成分。这个滤波器不需要数组计算量极低适合 STM32F103 这种主频不太高的 MCU。3.2 峰值检测与动态阈值计步算法的核心是峰值检测但单纯检测峰值不可靠——狗摇尾巴、低头喝水、被人抱起来加速度波形都会出现峰值。需要引入两个机制动态阈值和最小步间隔。动态阈值的基本思路是维护一个滑动窗口持续跟踪最近若干次波动的平均值把平均值的 60% 到 70% 作为当前步进的判定阈值。这样当宠物从慢走切换到快跑时算法能自动适应幅度的变化。最小步间隔则直接限制两步步峰的最小时间差常见取值是 200ms 到 250ms对应每秒最多 4 到 5 步——狗跑步时每秒步数不会超过这个值。// 动态阈值步数检测状态机 #define STEP_MIN_INTERVAL_MS 200 // 最小步间隔 200ms uint32_t step_count 0; uint32_t last_step_time 0; static float max_peak 0.0f; static float min_trough 5.0f; static float current_threshold 0.5f; static uint8_t in_peak_tracking 0; void step_detector_update(float magnitude, uint32_t tick_ms) { // 状态1超过当前阈值开始跟踪峰值 if (!in_peak_tracking magnitude current_threshold) { in_peak_tracking 1; max_peak magnitude; } // 状态2已经进入峰值跟踪更新当前最大值 if (in_peak_tracking) { if (magnitude max_peak) max_peak magnitude; // 回到阈值以下认为一个步伐周期结束 if (magnitude current_threshold) { if (tick_ms - last_step_time STEP_MIN_INTERVAL_MS) { step_count; last_step_time tick_ms; // 根据本次步幅更新阈值 current_threshold (max_peak 1.0f) * 0.6f; } in_peak_tracking 0; max_peak 0.0f; } } }整个状态机围绕加速值是否超过阈值展开阈值会不断被上一步的波峰更新。需要注意dog 的步频和加速度幅度远大于人不能直接套用人手环的算法参数需要在自己的狗身上实测后调整。另外如果宠物佩戴项圈后持续抖动比如甩毛计步值会明显偏大可以在代码里增加连续快速抖动的识别逻辑连续多个高峰且间隔极短时暂停计步。4. 系统整合从裸机到带电源管理的完整固件4.1 状态机式主循环而不是死循环堆功能三个功能都开发完后系统的整体实现最终要落在代码组织上。许多人第一版直接在while(1)里按顺序处理 GPS、计步、OLED 显示、充电检测结果 GPS 解析一阻塞计步数据就延迟了几秒。更好的结构是拉一个简单的状态机系统在“运行态”和“低功耗态”之间切换运行态又按优先级轮询各模块。// 主循环状态机示意 typedef enum { ST_BOOT 0, ST_ACTIVE, ST_LOW_POWER } sys_state_t; sys_state_t sys_state ST_BOOT; while (1) { switch (sys_state) { case ST_BOOT: // 初始化所有外设与传感器约2秒 sys_state ST_ACTIVE; break; case ST_ACTIVE: // 每 50ms 触发的计步解析 step_detector_update(get_magnitude(), HAL_GetTick()); // 每 200ms 处理一次 OLED 刷新含充电状态显示 if (elapsed_time_ms(oled_timer) 200) { oled_refresh_all(); } // 每秒尝试解析一次 GPS 数据 if (elapsed_time_ms(gps_timer) 1000) { gps_parse_pending_data(); } // 连续 30 秒无操作进入休眠模式 if (elapsed_time_ms(idle_timer) 30000) { sys_state ST_LOW_POWER; } break; case ST_LOW_POWER: enter_stop_mode(); // 进入 STM32 停止模式 while (sys_state ST_LOW_POWER) { // 等待外部中断唤醒按键/充电插拔 __WFI(); } break; } }低功耗设计的关键在于不是所有芯片都支持中途完全断电。GPS 模块的电流接近 30mA远超 STM32 本身——想要真正省电应使用 MOSFET 开关直接断掉定位模块的电源仅在需要定位时才供电。OLED 屏同样可以断电只保留电平触发引脚作为唤醒。这样整机平均电流可以控制在 10mA 以内一块 500mAh 的锂电池能撑两天以上。如果不开 GPS 只做计步能撑一周左右。4.2 数据存储与 OTA一项“亲测可用”源码里容易被忽略的价值标题说“亲测可用”并不只是跑通硬件还涉及数据能存下来、程序能升级。STM32F103C8T6 只有 64KB Flash存储一套定位数据和计步记录不现实需要外挂 SPI Flash 芯片比如华邦 W25Q162MB用 FATFS 文件系统进行管理。以 CSV 格式按天写文件每小时记录一次经纬度和步数值一个文件几十 KB2MB 能存好几天的数据。这段逻辑在调试时尤其有用狗戴着项圈跑一圈回来后导出 CSV 文件能在电脑上直接比对轨迹和步数的关系。OTA 升级对毕业设计可能用不上但对产品原型意义重大。常见的做法是使用 STM32 的 IAP 机制Bootloader 通过串口或蓝牙接收新固件写入指定 Flash 扇区后重启跳转。核心配置是链接脚本里把 Flash 分区为 Bootloader0x08000000 起 16KB和 App0x08004000 起。在 Keil 的 Target 选项卡里需要同步修改 IROM1 起始地址和大小。如果跳过这一步App 编译出来可能覆盖 Bootloader刷机一次就变砖。4.3 排错与验证这次是“亲测可用”下次就是“量产可复制”排查问题的顺序应该是按信号从外部到内部先确认电源电压正常再检查传感器输出然后看串口数据最后动代码逻辑。很多无法定位的问题并不在代码而是天线旁边有金属遮挡或者陶瓷天线在室内收不到卫星。判断办法很简单把板子放在室外开阔地用串口助手以 9600 波特率直接观察 NMEA 数据看到$GPRMC末尾状态为 A 就说明定位成功。如果一直输出 V要么模块坏了要么天线区域有问题跟固件无关。充电显示不灵敏的问题也有排查套路。TP4054 的 CHRG 引脚是开漏输出需要外部上拉电阻才能被 STM32 正确识别。直接读引脚电平为低就说明在充电为高就有区别充满是高、电池未接是高、充电芯片坏了也是高。调试时接一个 10k 电位器模拟电池电压可以快速验证 ADC 采样和电量映射逻辑正确。光照环境下 OLED 的显示对比度会下降可以在初始化时把对比度调高到 0xCF 以上。最后提一个很多人不知道的“亲测可用”技巧把调试信息输出和业务数据输出分开用不同的串口。STM32 有两个串口USART1 接 GPSUSART2 接蓝牙。调试时把 printf 重定向到 USART2业务数据走 USART1不要共用。原因很简单GPS 模块的 NMEA 数据流是持续不断的如果混入调试信息上位机解析会错乱而这个错乱不会体现在开发板调试阶段——因为你可能只在开发板上看过一个串口的数据。直到整机联调时才发现问题回头拆线、改代码白白浪费一下午。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。