
最近一直在折腾可穿戴小设备手头这块 XIAO nRF54LM20A Sense 让我玩得停不下来。说实话单看这一串型号名还挺唬人的但剥开来看它其实就是一块“只有大拇指指甲盖大小、自带传感器、支持最新低功耗蓝牙”的开发板。之前我一直想做个腕戴式计步器手头也试过用手机、用普通 Arduino要么太耗电要么体积太大直到拿到这块板子才觉得路子对了。这篇笔记就把我从零做腕戴式计步器的整个过程、算法逻辑、踩过的坑以及最终标定方法完整写出来希望能帮你少走点弯路。这个项目说简单也简单传感器采集人体运动加速度算法判断“是不是一步”然后通过蓝牙把步数发到手机。说复杂也复杂两块硬币大小的硬件上既要跑算法又要维持长时间续航还要避免日常抖手腕被误判成走路——这种真实场景下的工程取舍才是这个项目的核心价值。无论你是准备做智能手环原型、老人跌倒检测设备还是单纯想拿一块低功耗蓝牙板子练手下面的内容都有参考意义。1. 项目整体设计与思路拆解1.1 为什么选 XIAO nRF54LM20A Sense 而不是其他板子手头其实还有好几块其他开发板比如经典款的 XIAO nRF52840 Sense、ESP32-C3 等。但做腕戴设备时我第一反应就是把这颗 nRF54L20 芯片列入首选原因有三个。第一是体积。腕表内部空间寸土寸金XIAO 系列这块把蓝牙天线、射频匹配电路、电源管理全部集成在 20mm x 17.5mm 左右的 PCB 上几乎没有额外飞线空间直接塞进自制表壳里非常从容。第二是功耗。nRF54L20 相比上一代芯片在睡眠电流和射频功耗上都有明显优化而计步器这种东西必须 24 小时待命每天充电一次谁都受不了。第三是计算能力。计步算法虽然不复杂但要做滤波、峰值检测、时间窗口判断MCU 主频太低会导致响应卡顿主频太高又会白白耗电nRF54L20 采用的 Cortex-M33 核心在这一代板子上属于典型“够用且算力充裕”的定位实测跑 25Hz 采样加滑动滤波后 CPU 占用率依然很轻松。至于“Sense”后缀它代表了板上自带传感器的版本。我查了板厂提供的硬件资料这块板上直接焊了一颗六轴 IMU通过 I2C 接口与 MCU 通信用户不需要额外焊接传感器模块。这套组合几乎是给可穿戴项目量身定做的——打开包装接上电池就能开干省去了前期布线、选传感器的工作量。1.2 计步器的本质我们在识别什么信号与其一上来就写代码不如先把计步的原理摸透。很多人以为计步器就是“灵敏度高点动一下加一”如果做成这种东西要么走路计不准要么手一挥就虚增几百步。真正靠谱的计步器识别的是“人走路时身体重心的周期性起伏”。当你迈左腿、换右腿身体重心会沿上下方向往复移动这个周期运动的加速度波形有一个非常明显的特征频率稳定在 1.5Hz 到 2.5Hz 之间即每秒大约 1.5 到 2.5 步。同时重心的起伏幅度相对规律加速度波形会呈现周期性尖峰。所以计步系统的完整链路应该是这样的传感器持续采集三轴加速度数据算法对原始信号做滤波把顶部的重力和高频抖动去掉检测滤波后信号中“足够高、间隔足够合理”的波峰连续若干次满足条件后确认用户真的在走路而不是甩了一下手。我在设计之初就确定了一个原则单次波峰不直接计步只有连续两个以上、时间间隔符合步频范围的波峰才被认为是有效步伐。用这个策略绝大部分无意识挥手、敲桌、翻身动作都能被过滤掉。2. 核心细节解析与实操要点2.1 传感器数据读取与采样率设置硬件接口确定后软件第一件事就是配置 IMU。技术文档里通常要求设置采样率、量程、滤波器带宽三个参数。初学的人往往对这三个参数不太敏感我用自己的实测值来说明。采样率方面常见误区是越高越好。传感器标称支持几百赫兹采样但人体运动信号的有效频段其实集中在 0.3Hz 到 10Hz 之间根据奈奎斯特采样定理采样率超过信号最高频率的两倍即可。考虑到算法还要做平滑滤波我最终把采样率设置在 25Hz。这个数值相对均衡每个采样点之间的间隔是 40ms足够捕捉步频尖峰也不会让 CPU 频繁被中断唤醒。量程方面我选的是 ±4g。手腕日常运动产生的加速度通常在 ±2g 以内偶尔有剧烈摆动±4g 的余量既避免削顶又比 ±8g 的灵敏度好。如果只为了计步用 ±2g 其实也够但放开一些可以给后续其他运动识别留空间。加速度数据还有一个必须处理的细节单位。IMU 返回的数据可能是原始寄存器值需要除以灵敏度系数转换成物理单位 g。以常见的 16 位分辨率、±4g 量程为例每 LSB 对应约 0.000122g这个系数算错会导致所有阈值失效。所以我在初始化代码里单独做了一个 sanity check当板子静止在桌面上时三轴合成矢量应当接近 1g——这个检查能快速发现配置错误。2.2 三种滤波方案对比滑动平均、低通滤波、动态阈值原始加速度信号里既有走路产生的周期性信号也有身体抖动、传感器噪声和被试者说话的振动。我用三种手段把它治理到“波形干净”的状态。首先是滑动平均滤波。它相当于一个低通滤波器把相邻 N 个采样点做平均。N 太小滤波不明显N 太大信号延迟明显容易把真实步态尖峰“抹平”。我用 4 个点的滑动平均窗口在 25Hz 采样下对应约 160ms 的平滑跨度实际效果不错。也可以用一阶低通滤波器公式是filtered alpha * raw (1-alpha) * filteredalpha 取 0.2 到 0.4 之间波形更顺滑。两者原理相似滑动平均更直观适合快速验证算法。其次是去除重力分量。加速度合成矢量sqrt(x² y² z²)天然包含了约 1g 的重力静止时是一个常量走路时这个常量会叠加在波动分量上。如果直接拿合成矢量的绝对值做阈值判断就必须把阈值设得很高对微弱的步态波动不敏感。解决办法是做一个带通滤波或者减去均值。我的做法是维护一个约 2 秒的滑动窗口均值实时估计当前重力基线然后取“瞬时合成矢量减去均值”作为有效运动分量。这个处理之后信号会在 0 附近上下波动正负峰都能被识别。最后是动态阈值。固定阈值很难同时适配大步快走、小步慢走两种场景。我的阈值不是常量而是根据过去若干个波峰幅值的移动平均值自动调整。例如当前基础阈值为 0.25g走路幅度变大时阈值会按峰值均值的一定比例上调防止震动信号淹没走路变轻时阈值也会下降保证不漏步。滤波方式实现难度延迟影响适用场景我的建议滑动平均低中实时波形观察快速原型必用一阶低通低中波形平滑代码量最小去除重力均值中低动态信号提取必须加动态阈值高低自适应计步最终版本采用2.3 峰值检测与步数判定逻辑滤波之后信号变成一串在 0 附近上下波动的曲线接下来就是核心的步数判定。我用的不是机器学习模型而是一套规则——对于 MCU 上的轻量应用规则算法最可控、最省电也最容易调试。规则分为三个层级第一层检测波峰。在时间序列中如果一个点的值大于前一个点且同时大于后一个点就认为是一个局部峰值。为了避免采样噪声在波峰附近产生多个“假尖”要求峰值点必须是当前小窗口内的最大值窗口宽度约 100ms。第二层判断峰幅是否有效。峰值的幅值必须超过当前动态阈值。这里的幅值取滤波后信号的绝对值正负峰只要满足条件都可以认定因为走路时加速度的正负波动都会出现。第三层步频判断。两步之间的时间间隔是重要的判别维度。正常人行走间隔在 250ms 到 1200ms 之间也就是 0.5 步/秒到 2 步/秒。如果检测到连续一波峰间隔只有 80ms那八成是“手抖”而不是走路直接丢弃如果间隔超过 2 秒说明用户停了下来步数计数器保持当前值并等待下一次节奏。除此之外我加了一个“连续两次确认”机制第一次检测到有效峰值时不立即加一而是进入一个“待定”状态只有接下来的 0.3 到 1.5 秒内再来一个合规峰值才把这两步同时加入计数。这个策略牺牲了一点响应实时性但换来了极低的误计率。实测甩手 20 次误计基本为零。下面是这套判定逻辑对应的核心代码结构#define WINDOW_SIZE 4 #define MIN_INTERVAL_MS 300 #define MAX_INTERVAL_MS 1200 float samples[WINDOW_SIZE]; uint8_t sampleIdx 0; bool armed false; uint32_t lastPeakTime 0; float dynamicThreshold 0.25f; void processAcceleration(float x, float y, float z, uint32_t nowMs) { // 计算合成矢量 float magnitude sqrt(x*x y*y z*z); // 滑动平均平滑 samples[sampleIdx] magnitude; sampleIdx (sampleIdx 1) % WINDOW_SIZE; float smooth 0; for (int i 0; i WINDOW_SIZE; i) smooth samples[i]; smooth / WINDOW_SIZE; // 减去均值基线estimateBaseLine由低速历史值维护 float motion smooth - currentBaseLine; // 峰值检测 if (motion dynamicThreshold nowMs - lastPeakTime MIN_INTERVAL_MS) { if (armed) { stepCount 2; // 连续两步合并确认 lastPeakTime nowMs; armed false; updateDynamicThreshold(motion); } else { armed true; lastPeakTime nowMs; } } // 超时未确认重置待定状态 if (armed nowMs - lastPeakTime MAX_INTERVAL_MS) { armed false; } }这段代码是简化版实际工程中还会把基线更新、阈值初始化、异常值保护都加进去。但核心思路就是三步——平滑、检测、校验。2.4 低功耗设计从 40mA 到 200μA计步器最容易被忽视但又最决定成败的环节是功耗。我一开始没做任何优化把板子接上 3.7V 锂电池主循环里每 40ms 读一次传感器持续开启蓝牙广播实测电流高达 35~45mA。照这个速度一块 300mAh 电池不到 8 小时就会被榨干完全不具备佩戴可行性。解决思路是从“主动轮询”改成“事件驱动”。有几条经验可以直接抄采样时只开传感器读完后立刻把 IMU 切到低功耗模式或掉电模式而不是让 IMU 始终处于加速测量状态。Sensor 本身也有低功耗模式需要在寄存器里显式配置。MCU 在两次采样之间进入睡眠利用定时器中断唤醒。nRF54L20 的睡眠电流非常低只要保证唤醒周期在 40ms 左右睡眠带来的电流几乎可以忽略。蓝牙不要实时高频广播而是把步数累计到内存里每累计 10 步或每 60 秒才通过低功耗蓝牙上报一次。手机端不要求“每秒刷新”60 秒一次的数据对计步器场景来说完全够用。完成这几项改造后我实测整机待机电流降到了 200μA 左右。具体数字当然取决于 IMU 休眠状态和蓝牙广播周期但量级已经从“几十毫安”降到了“几百微安”一块 300mAh 电池理论上可以撑几十天。如果你还想再抠可以关掉板上状态 LED再降一点采样率续航还能进一步拉长。3. 实操过程与核心环节实现3.1 开发环境搭建环境搭建其实比想象中快。XIAO 系列板卡选型的一个好处是生态成熟官方提供了 Arduino 支持。我原来想直接上 Zephyr但为了先把算法跑通还是在 Arduino 环境里做原型验证更方便。安装步骤并不复杂在 Arduino 开发板的“附加开发板管理器地址”中填上 Seeed 的板卡 JSON 链接然后开发板管理器里搜索 XIAO nRF54L20A Sense安装对应的板卡支持包。安装完成之后选择对应的开发板型号插上 USB即可开始烧录。我第一次烧录时遇到端口识别不到的问题后来发现是数据线是“仅供充电”的类型换了一根带数据传输的 USB 线就正常了。这是一个非常容易踩的坑建议新手直接排查线材。如果你后续要做量产级低功耗建议还是走 Zephyr 或者 Keil/VS Code 下的 NCS 工具链。Zephyr 对 Nordic 芯片的支持非常完整低功耗模式、蓝牙协议栈、传感器驱动都是现成的模块只是学习曲线稍陡。我的路线是先 Arduino 验证算法再迁移到 Zephyr 做功耗优化两边都很顺利。3.2 核心代码实现与烧录完整的工程文件没法在这里全部贴出但我把关键模块拆开讲透。首先是初始化阶段。这个阶段要做的事情包括初始化 I2C 通道配置 IMU 的量程±4g、采样率25Hz、数字滤波带宽记录开机后前 2 秒的静止加速度作为零偏基线初始化低功耗定时器设置 40ms 唤醒周期初始化 BLE GATT 服务创建“步数”特征。值得注意的是零偏基线。每块板子的 IMU 在出厂时都有微小的零偏差异如果不在开机时校准滤波后的信号会有一个固定的偏差波峰幅值可能整体偏移导致阈值判断失真。我强烈建议在开机后做一个“静止 2 秒检测”流程提示用户放下手腕采样几十次求平均得到一个零偏修正量。然后是主循环。Arduino 的 loop() 其实不适合裸奔大循环搞实时采样因为串口输出、其他任务都会造成事件延迟。更好的做法是使用定时器中断让采样和计步在中断上下文中执行主循环只负责蓝牙上报和状态打印。这样做的好处是采样间隔稳定不会因为某个耗时任务打乱 40ms 的节奏。核心采样中断函数的大致逻辑如下void timerHandler() { readAcceleration(ax, ay, az); processAcceleration(ax, ay, az, millis()); }这个函数体量很小中断占用时间极短实测不影响系统稳定性。蓝牙上报部分我用了一个很简单的 GATT 特征步数变化后把 uint32 类型的步数值更新到特征手机端通过通知机制读取。不需要做双向控制代码量控制在 50 行以内。如果你做的产品需要手机端看历史曲线可以再加一个历史记录数组我当前版本只在 RAM 里保存最近 100 条步数时间戳方便测试时查看波形。3.3 壳体与腕带设计让算法“戴得住”算法再好硬件固定方式不对也是白搭。这个教训我在测试第一天就体会到了板子直接用橡皮筋绑在手腕上方走路时传感器波形乱得没法看因为板子不是贴着手腕皮肤固定而是来回晃动产生大量额外加速度。后来我用 3D 打印做了一个简易表壳内壁稍微留了一点海绵胶缓冲垫让板子尽量贴近皮肤表面。佩戴位置也很有讲究手表戴在腕关节偏上一点的位置最合适既不影响手腕弯曲又能保证传感器感受到手腕侧向摆动信号。如果戴得太靠近手背手腕旋转时会产生大量耦合干扰如果戴在手臂上走路时的信号幅度又不够。老夫建议外壳设计时要注意三点板子的重心尽量靠近手臂平面避免覆重偏心导致来回甩动电池和板子之间加薄泡棉防止走路时电池撞击传感器引发毛刺留出 Type-C 充电口和复位按键开口实测按键凸出不要超过 1mm否则日常活动容易误触。表带我试过硅胶和尼龙魔术贴两种魔术贴更方便调节松紧度。松紧度对信号影响极大——太老太松的信号波形完全不同。每次换佩戴方式之后都需要重新做一次步数标定。3.4 实测定标流程这一步特别重要。代码写完后我第一版逻辑在桌面上调得好好的模拟数据测试也漂亮但戴上手腕一测试却严重漏步。原因很简单桌面振动和手腕运动的频谱、幅度完全不同必须在真实佩戴环境下标定。定标流程可以复现佩戴设备静止站立 5 秒记录传感器噪声水平直线慢走 30 步回看波形峰值幅度记下最小峰值直线正常速度快走 30 步同样记录用手机秒表计时精确记录 1 分钟正常步行步数作为地面真值根据对标结果调整 dynamicThreshold 初始值和基线窗口时间反复走 3 次 100 步计算误差率。我现在的版本在“正常步行”场景下100 步误差在 1 到 2 步以内慢走误差稍大主要在 3 步以内。这个精度对于日常计步器完全够用。如果你发现某个场景漏步特别多优先看波形如果信号根本没有尖峰说明传感器固定有问题如果有尖峰但算法没识别说明阈值或间隔窗口设置不对。4. 常见问题与排查技巧实录4.1 为什么甩一下手就被误计步手部快速甩动会产生与行走相似的加速度尖峰如果算法只看单独峰值很容易误判。这也是我前面强调“两步间隔 连续确认”的原因。实际测试中快速甩手、挥手以及敲击桌面产生的波峰间隔通常小于 200ms且波形宽度窄、幅值极大。可以在算法中增加一个“幅度上限保护”如果运动分量的峰值超过 2.5g认为不是正常走路信号因为正常步行产生的加速度峰值很少超过这个阈值。如果你担心误杀跑步场景可以把这个限制放宽到 3.5g。4.2 慢走时经常漏计尤其老年人步态老人或小孩步频较慢步幅较短加速度峰值偏低。针对这种情况我调整了 MIN_INTERVAL_MS 和 MAX_INTERVAL_MS 的范围将最大间隔拉到 1.6 秒同时把初始阈值从 0.25g 降到 0.18g。这样能明显改善慢步漏计。但要注意阈值降得过低会导致抖手腕误计——这是灵敏度与误判之间的永恒平衡。我的建议是默认参数先保持偏保守再针对目标用户做定向调优。4.3 蓝牙连接经常断开数据上报不稳定这个问题多半不是代码逻辑而是板子和手机距离太近造成的射频自干扰。起初我把板子和蓝牙天线那一侧贴在金属表壳内壁上距离测试断流严重。后来换成 PLA 塑料外壳并把板子的天线区域留空 2mm 以上问题立刻解决。另外使用低功耗蓝牙广播模式时连续广播容易导致手机系统侧资源占用我建议使用周期广播或连接后按间隔通知上报的方式而不是一直开启大功率广播。4.4 长时间运行后计步越来越不准这个问题我一开始没注意到戴了两天后发现计步数量整体偏高。原因有两个一是 IMU 长时间工作后温度升高零偏发生缓慢漂移二是我的基线估计窗口太短在某些恒定大幅度摆动的场景中均值基线跟随得过多导致有效信号被压缩。解决办法是每隔 10 分钟重新校准零偏同时将基线更新窗口从 2 秒延长到 6 秒。校准过程不需要用户配合检测到运动幅度低于阈值时后台自动微调基线即可。4.5 排查工具如何快速定位算法问题这里分享一个小经验我专门写了一个串口调试模式在开发阶段把采样值、滤波值、阈值、步频、待定状态全部通过串口打印到电脑上配合 Python 脚本画实时波形。可以直接看到每一步的波形长什么样阈值线在哪个位置波峰是否被正确捕获。这套方法比凭感觉改参数高效得多。具体操作是板子固件中开启一个宏定义DEBUG_MODE开启后每 250ms 输出一行timestamp,motion,threshold,stepCount的 CSV 数据电脑端用 pyserial 读取并绘制波形。调一次参看一遍波形基本五分钟就能定位问题。5. 几个不得不说的后续扩展思路计步器做到这个程度其实已经可以告一段落但如果继续往下做这块板子的潜力还很大。因为 nRF54L20 支持较新的蓝牙技术算力也够后续可以在同一个硬件上叠加一些新功能比较适合做产品迭代的方向包括跌倒检测利用加速度计在跌倒瞬间产生的高冲击特征再配合姿态恢复时间判断可以做成老人看护设备。算法难度比计步高不少但原理可以复用现有的峰值检测框架。姿态识别利用六轴数据判断手抬起、放下、转动等动作可实现简单的腕部手势控制。这个方向需要先做数据采集和特征分析我目前正在尝试。睡眠监测配合体动信息判断睡眠深浅度。这一块很依赖长时间数据积累和模型校准但硬件平台完全支持。这些扩展方向本质都是“传感器数据 信号处理”的组合拳与计步器的架构高度吻合。而 XIAO nRF54LM20A Sense 的低功耗优势在这里继续放大——随时戴着记录数据才能产生真正有价值的数据集。最后再分享一个小技巧如果你也想复现这个项目强烈建议准备两台设备一台固定在手腕上一台放在桌面用手敲打同时跑同一份固件做对比。这样你能更快理解“真实运动信号”和“噪声”之间的区别——计步算法的本质就是在噪声里识别出节奏而不是把硬件做得多豪华。这块板子我用了两三周最大的感受就是选对平台项目就成功了一大半剩下的细节慢慢调和就好。