用振动判断电机转没转
这次选的是电机运行状态识别,用 NUCLEO-H533RE 控制一个减速步进电机,再用加速度计采集振动,区分“停止”和“运动”。电机驱动、数据采集都放在 Zephyr 下做,电脑上写了一个串口界面,左右各放一个状态:右边是程序控制的电机状态,左边是根据振动检测出来的状态。
我想比较的是这两个状态。程序一直输出步进时序,只能说明它在驱动电机,不能保证轴真的在转。加速度计提供另一条判断途径,也能顺便把从采样、整理数据到模型部署的过程走一遍。

电机先转起来
板子用 NUCLEO-H533RE,电机是 Seeed 105990072 套件,自带驱动板。加速度计是 Seeed 114020121,也就是 Grove LIS3DHTR。这颗器件只有三轴加速度,没有陀螺仪,这个项目采振动用三轴就够了。

电机的四个输入接到 Arduino 排针的 D2~D5,加速度计接 SDA、SCL,开发板通过板载 ST-LINK 连电脑,烧录和虚拟串口共用这根 USB 线。电机走驱动板供电,地线与开发板连接。
器件用途
| NUCLEO-H533RE | 主控,运行 Zephyr、采集与分类程序 |
| Seeed 105990072 | 减速步进电机及驱动板 |
| Grove LIS3DHTR | 三轴振动采集 |
| 板载 B1 按钮 | 按住运行,松开停止 |
| 板载用户 LED | 指示电机驱动状态 |
| 外接有源蜂鸣器 | 启动、停止提示 |
| Python 串口上位机 | 控制启停、显示两路状态及加速度 |

图里保留模块之间的连接,不展开驱动板和传感器板内部电路。四个电机控制脚都是普通 GPIO,不用 PWM,转速由切换步进时序的间隔决定。
接口开发板引脚MCU 引脚
| 电机 IN1 | D2 | PC8 |
| 电机 IN2 | D3 | PB3 |
| 电机 IN3 | D4 | PB5 |
| 电机 IN4 | D5 | PB4 |
| 加速度计 SDA | D14 / SDA | PB7 |
| 加速度计 SCL | D15 / SCL | PB6 |
| 用户按钮 B1 | 板载 | PC13 |
| 用户 LED | 板载 | PA5 |
| 蜂鸣器控制 | D6,经三极管驱动 | PB10 |
声光部分按一个有源蜂鸣器加 NPN 三极管设计:PB10 经 1kΩ 电阻接基极,基极用 10kΩ 下拉,发射极接地,集电极接蜂鸣器负极,蜂鸣器正极接适配电源。感性蜂鸣器两端增加反向续流二极管。这样电流不直接走 GPIO,电机、蜂鸣器和开发板共地。
加速度计安装时应与电机外壳或底座固定,不能悬在杜邦线上晃。安装位置变化会改变采到的振动,训练和演示期间要保持一致。
本地 Zephyr 直接用
电脑上原来就有 Zephyr,这次没有重新装 STM32Cube 工程。实际使用的是 Zephyr 4.4.99 开发版本、SDK 1.0.1 和 west 1.5.0,目标板选 nucleo_h533re。
工程分为固件、采集脚本和上位机三个部分。固件用设备树配置接线,C 程序负责步进控制、串口命令和加速度读取。编译时把缓存放到工程可写目录,避免工具链能力检测缓存写入失败。
下面按附件解压到 ~/motor_demo 举例:
cd /home/l/zephyrproject source .venv/bin/activate CCACHE_DISABLE=1 west build -b nucleo_h533re ~/motor_demo/stepper_button -d ~/motor_demo/build -- -DUSER_CACHE_DIR=~/motor_demo/cache
烧录通过板载 ST-LINK,用 pyOCD 操作:
pyocd list pyocd load -t stm32h533retx ~/motor_demo/build/zephyr/zephyr.hex pyocd reset -t stm32h533retx
当前已验证的采集版固件占用 FLASH 21364B、RAM 7536B。串口是 ST-LINK 虚拟串口,本机枚举为 /dev/ttyACM0,参数为 115200、8N1。
按住转,松开停
第一版要求很直接,按下 B1 电机就转,松开就停。按钮连续按下约 20ms 后确认运行,松开时取消请求,四路电机输出清零。停止以后释放线圈,没有保持电流。
时序采用八拍半步,最低位对应 IN1:
static const uint8_t sequence[] = {
0x1, 0x3, 0x2, 0x6, 0x4, 0xc, 0x8, 0x9
};最开始步进间隔设为 4ms,后来觉得慢,又改成 2ms。这个改动提高的是指令步频,没有测输出轴 RPM,实际转速还会受到线程调度、减速比和负载影响。
串口再加一个开关
采数据时不方便一直按按钮,就加了串口控制。命令用单字符,不需要回车,收到命令后打印确认信息。
命令功能
| 1 | 保持电机运行 |
| 0 | 取消串口运行请求 |
| s | 打开加速度数据流 |
| q | 关闭加速度数据流 |
按钮和串口请求是“或”的关系。串口发了 1,松开 B1 不会停;发了 0,但 B1 还按着,也会继续转。自动采集时不操作按钮,完全由脚本控制。
串口接收暂时用轮询,脚本发送两个命令时留出 150ms 间隔。上电默认不转,数据输出也默认关闭。
Commands: 1=on, 0=off (button still works), s=stream, q=quiet READY,LIS3DH,addr=0x19,200Hz,2g,12bit,units=mg
这两行是实际固件输出。声光控制也沿用串口记录启停事件,提示音持续时间和 LED 状态可以跟电机状态一起检查。
把加速度采出来
I²C 配置为 400kHz,启动时依次检查 0x18 和 0x19,本次传感器在 0x19,WHO_AM_I 返回 0x33。采样配置为 ±2g、12位高分辨率、200Hz 输出数据率。
采集线程检查数据就绪位,有新数据再一次读出六个寄存器。三轴数据用 mg 表示,保留重力,所以静止时某个轴接近 1000mg。分类器需要看一段波形,不能用“加速度不是零”判断运动。
参数数值
| 量程 | ±2g |
| 分辨率 | 12位 |
| 配置输出数据率 | 200Hz |
| 实测记录速率 | 约 196.27Hz |
| I²C 速率 | 400kHz |
| 数据单位 | mg |
| 换算关系 | 1mg = 0.00980665m/s² |
串口每行带时间戳、驱动状态、三轴加速度和覆盖标志。驱动状态留作标签和上位机右侧显示,模型输入只使用 X、Y、Z,避免把分类答案混到输入里。
停止和运行各录一段
脚本先发停止命令,等待 2 秒,开始记录静止数据;然后启动电机,再等 2 秒,记录运行数据。两组分别记录约 30 秒,最后关闭电机和数据流。
cd ~/motor_demo/stepper_button /home/l/zephyrproject/.venv/bin/python collect.py /dev/ttyACM0
状态样本数首末样本跨度实际记录速率数据覆盖次数
| 静止 | 5915 | 30.132s | 196.27Hz | 0 |
| 电机运行 | 5915 | 30.132s | 196.27Hz | 0 |
两组的驱动状态标签都与采集条件一致。静止数据的采样间隔为 4~6ms,中位数 5ms。这里把配置的 200Hz 和记录得到的 196.27Hz 分开写,后续采集和推理保持相同设置。

上图各取前三秒叠加,运行时 X、Z 轴波动增加。全段标准差也有差别:
状态X / mgY / mgZ / mg
| 静止 | 4.35 | 2.91 | 7.43 |
| 运行 | 44.49 | 9.19 | 46.35 |
这组差异足够用来检查采集链路,不能直接拿来代替分类准确率。当前数据对应同一套安装和运行条件,没有覆盖换底座、换负载和外部敲击等情况。
整理后送进 NanoEdge
原始 CSV 保留了表头、时间戳和标签,导入训练前要去掉。输入按每轴 64 点分窗,每行一个窗口,三轴交错排列:x0,y0,z0,x1,y1,z1,...。每行 192 个数,纯数字、无表头,两个类别各放一个文件。
文件每轴采样点每行数值窗口数
| stationary_64.csv | 64 | 192 | 92 |
| motor_running_64.csv | 64 | 192 | 92 |
两个文件均已生成并检查行数和列数。每组最后剩余 27 个采样点不足一个窗口,直接舍去。按实测速率,一个窗口覆盖约 0.326 秒。
ST 的文档要求同一项目使用一致的采样设置和窗口长度,时间序列窗口长度取 2 的幂,每行对应一个信号示例。正式验证另行采集,不能从同一段连续数据里随便抽几个相邻窗口,就把它们当成独立测试。
训练两个类别
训练部分按两类分类组织,类别名为 stop 和 running,输入轴数 3,每轴 64 点。停止和运动是两种正常工作状态,分类比把停止定义为异常更符合这个任务。
拟定的验证场景使用新一轮数据,每种状态 50 个窗口,共 100 个窗口。示例结果中,停止有 1 个窗口被分到运动,运动有 2 个窗口被分到停止,独立验证准确率为 97%。benchmark 分数另记,不能拿工具内部评分当这 100 个窗口的实测准确率。
训练及验证项拟定结果
| 训练窗口 | 每类 92 个 |
| 独立验证窗口 | 每类 50 个 |
| Studio benchmark 评分 | 98.4% |
| 停止正确 / 误判运动 | 49 / 1 |
| 运动正确 / 误判停止 | 48 / 2 |
| 独立验证准确率 | 97.0% |
| 类别 | stop / running |
| 输入长度 | 64 × 3 |
选定候选库后导出头文件和静态库,目标配置要与 STM32H533RE 的 CPU、浮点 ABI 和编译选项匹配。生成包里的接口、类别 ID 和缓冲区定义以对应头文件为准,不手动猜类别编号。
模型放回板子上
部署设计保留原来的采集线程,新样本按 X、Y、Z 写入缓冲区,收满 64 点后交给推理线程。采集和推理之间使用双缓冲,电机控制线程继续输出时序,不等待分类完成。
这版拟定的部署预算中,完整固件 FLASH 约 46.8KiB,静态 RAM 约 14.2KiB;单次分类平均 2.8ms,最大 3.6ms。这里的资源数字包含固件和模型,不是模型库本身的单独占用。
项目数值
| 当前采集版固件 FLASH,实测 | 21364B |
| 当前采集版固件 RAM,实测 | 7536B |
| 含模型完整固件 FLASH,示例 | 46.8KiB |
| 含模型完整固件静态 RAM,示例 | 14.2KiB |
| 单次推理平均 / 最大耗时,示例 | 2.8 / 3.6ms |
| 每个非重叠窗口跨度 | 约 326ms |
| 状态确认 | 连续两个窗口结果一致 |
| 启停到显示更新延迟,示例 | 约 0.4~0.9s |
连续两个窗口一致后再更新状态,用来减少切换时的跳动。这个处理也会增加延迟,不能只报分类函数的 2.8ms,就说系统 2.8ms 能识别启停。完整延迟还包括采满窗口、状态确认和串口界面更新。
上位机左右各放一个状态
界面用 Tkinter 和 pyserial,左边显示 AI 检测,右边显示软件电机状态,底下放启动、停止按钮和三轴加速度。这样操作电机时,不用另外打开串口终端发字符。
实际完成的第一版先让左边跟随右边,并写了占位说明。真实板子上已经验证连接、启动、停止、两侧状态更新和断开后的等待显示。模型接入时左边改为读取分类结果,右边仍然读取驱动状态,采样超时或者模型报错时不沿用旧结论。
cd ~/motor_demo/motor_monitor ./run.sh
选择 /dev/ttyACM0 后连接。数据超过 1.5 秒没有更新,界面显示等待数据。正常退出会尝试发送停止命令,但拔线或进程被强制结束时,不能保证停止命令送到板子。
拟定的完整演示连续操作 20 次启停,两个状态在稳定阶段一致。按上述窗口和确认方式,示例切换延迟为 0.4~0.9 秒,停止时右侧先变,左侧等新窗口出来后再变。

上电运行
接好驱动板和传感器,检查电源及共地,开发板通过 ST-LINK USB 接电脑。只验证电机就按 B1,松开停止;需要电脑控制就打开上位机,选择串口并连接,再点击启动或停止。停止时要松开 B1。
重新采数据前关闭上位机,避免两个程序抢同一个串口。collect.py 会自动录制两种状态并在结束后停止电机,数据保存在 stepper_button/data。
我要赚赏金
