【let's do|2026年第3期】边缘 AI 步进电机
本文介绍了 Let's Do|2026年第3期 活动,使用 NUCLEO-C562RE 开发板结合 LIS3DH 加速度传感器采集步进电机振动数据,训练 AI 模型,实现运行状态推理的项目设计。
项目介绍
NUCLEO-C562RE 开发板通过采集 LIS3DH 传感器数据、模型训练、AI 推理,判断步进电机运行状态的项目设计。
准备工作:硬件连接、环境搭建、软件获取;
数据采集:电机串口控制、加速度数据打印、AI 模型创建;
模型训练:类别数据加载、Benchmark训练、效果验证;
状态识别:模型函数调用、关键代码、效果演示。

环境搭建
下载并安装 STM32CubeMX2 软件;
下载并安装 VS Code 软件;
安装 STM32CubeIDE for VS Code 插件;
下载、安装并运行 NanoEdge AI Studio 软件;


收到电子邮件,复制注册码;


运行软件,输入注册码激活,进入主页面;


硬件连接
使用纳米双面胶将 LIS3DH 模块固定在 28BYJ-48 步进电机表面,以探测振动加速度变化;


粘贴时尽量使接触面积最大,以提升检测精度;


最好使用没有弹性的双面胶,避免吸收振动,提高不同电机状态下的信号差异;
将步进电机驱动板、LIS3DH 模块和开发板对应引脚连接;


接线方式详见:【let's_do|2026年第3期】步进电机的串口控制 、【let's_do|2026年第3期】加速度计 。
准备工作
串口打印加速度数据,同时串口中断控制步进电机转速,以便后期通过串口采集不同状态类别的训练数据。
步进电机控制
使用串口中断控制步进电机转速。
打开 STM32CubeMX2 软件,新建工程并配置步进电机所需 GPIO、串口通信 USART2、LIS3DH 模块对应 I2C1;
详见:【let's_do|2026年第3期】步进电机的串口控制 、【let's_do|2026年第3期】加速度计 。
代码
修改 main.c 文件,添加如下代码
#include "main.h"
#include "stepmotor.h"
/* 档位 -> 相位间延时(ms):0=停 1=慢 2=快 3=最快 */
static const uint16_t speed_delay_tbl[4] = {0, 5, 2, 0};
static int speed_level = 0; /* 0=停止 1=慢 2=快 3=最快 */
static int motor_dir = 1; /* 1=顺时针 -1=逆时针 */
int __io_getchar(void) {}
int __io_putchar(int ch) {}
void HAL_UART_RxCpltCallback(hal_uart_handle_t *huart, uint32_t size_byte, hal_uart_rx_event_types_t rx_event){}
/**
* @brief 解析 {"speed": N},N ∈ [0,3]
* @retval 0 成功,-1 失败
*/
static int parse_speed_cmd(int *level)
{
char *p = strstr((char *)uart_rx_buf, "\"speed\"");
int v;
if (p == NULL) return -1;
p = strchr(p, ':');
if (p == NULL) return -1;
v = atoi(p + 1);
if (v < 0 || v > 3) return -1;
*level = v;
return 0;
}
/**
* @brief 电机轮询:处理串口速度指令 + 走一个完整周期(8 相位)
* @note 主循环反复调用即可;先处理指令再走步,新指令当圈立即生效
*/
static void motor_poll(void)
{
/* ---------- 1. 收指令:随时可打断转动 ---------- */
if (uart_rx_ready)
{
int lvl = speed_level;
int ok = parse_speed_cmd(&lvl);
uart_rx_len = 0;
memset(uart_rx_buf, 0, sizeof(uart_rx_buf));
uart_rx_ready = false;
if (ok == 0)
{
speed_level = lvl;
if (speed_level == 0)
{
stepper_stop(); /* 0 档:四相断电 */
}
printf("OK: speed=%d\r\n", lvl);
}
else
{
printf("ERR: {\"speed\":0|1|2|3}\r\n");
}
}
/* ---------- 2. 走一个完整周期(8 相位)后立即返回 ---------- */
if (speed_level != 0)
{
stepper_step(motor_dir, speed_delay_tbl[speed_level]);
}
}
int main(void)
{
/* 初始化步进电机 */
stepper_init();
printf("\r\n=== Stepper Motor Speed Control ===\r\n");
printf("Usage: {\"speed\": 0|1|2|3}\r\n");
printf(" 0 = stop\r\n");
printf(" 1 = slow (5 ms/phase)\r\n");
printf(" 2 = fast (2 ms/phase)\r\n");
printf(" 3 = max (0 ms/phase -> real 1ms)\r\n\r\n");
/* 启动 UART 中断接收 */
HAL_UART_Receive_IT(mx_usart2_uart_gethandle(), &rx_temp, 1);
printf("\r\nSystem Ready!\r\n");
printf("Waiting for JSON command: {\"speed\": 1}\r\n");
while (1)
{
motor_poll();
}
} /* end main */保存代码。
效果
编译并上传工程,打开串口调试助手;
发送速度指令,控制步进电机转速;


加速度打印
串口打印 LIS3DH 传感器三轴加速度值,同时串口中断控制步进电机转速切换。
代码
修改 main.c 文件,添加如下代码
#include "main.h"
#include "stepmotor.h"
#include <math.h>
#include "lis3dh_reg.h"
#define LIS3DH_I2C_ADDR (LIS3DH_I2C_ADD_H & 0xFEU)
static stmdev_ctx_t lis3dh_ctx;
uint32_t last_acc_tick = 0; // 数据打印的时间戳
static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *buf, uint16_t len);
static int32_t platform_read(void *handle, uint8_t reg, uint8_t *buf, uint16_t len);
static void platform_delay(uint32_t millisec);
/**
* @brief 加速度打印轮询(非阻塞):每 100ms 读一次 LIS3DH 并打印
* @note 主循环里反复调用即可,未到时间立即返回
*/
static void acc_print_poll(void)
{
static uint32_t last_acc_tick = 0;
int16_t acc_raw[3];
float acc_mg[3];
float acc_g[3];
if ((uint32_t)(HAL_GetTick() - last_acc_tick) < 100U)
return;
last_acc_tick = HAL_GetTick();
if (lis3dh_acceleration_raw_get(&lis3dh_ctx, acc_raw) == 0)
{
/* 已配置为高分辨率(HR_12bit)模式,±2g 用 hr 换算 */
acc_mg[0] = lis3dh_from_fs2_hr_to_mg(acc_raw[0]);
acc_mg[1] = lis3dh_from_fs2_hr_to_mg(acc_raw[1]);
acc_mg[2] = lis3dh_from_fs2_hr_to_mg(acc_raw[2]);
acc_g[0] = acc_mg[0] / 1000.0f;
acc_g[1] = acc_mg[1] / 1000.0f;
acc_g[2] = acc_mg[2] / 1000.0f;
printf("%.3f,%.3f,%.3f\r\n",
(double)acc_g[0], (double)acc_g[1], (double)acc_g[2]);
}
else
{
printf("Read LIS3DH data failed!\r\n");
}
}
int main(void)
{
// 初始化 LIS3DH 和步进电机
while (1)
{
acc_print_poll();
motor_poll();
}
} /* end main */保存代码。
效果
编译并上传工程至板端,打开串口调试助手;
连续打印三轴加速度值;


发送 JSON 指令控制电机转速,观察加速度值的变化;
数据采集
使用 NanoEdge AI Studio 软件创建工程并串口采集 LIS3DH 数据。
工程创建
点击左上角 Home 键进入主页,点击 New Project 按钮,选择信号类型为 Classify,点击 Create 按钮;


进入工程设置页面,配置工程名称,选择目标设备 NUCLEO-C562RE ;


根据芯片型号设置存储空间,设置传感器为 Accelerometer ,轴数为 3,点击 Save 进入下一步;


进入 Signals 标签页,点击 New Class 按钮,可选择文件导入、串口采集等方式(数据格式见右侧说明);


串口打印
通过串口发送指令 {"speed": 0|1|2|3} 实现静止、低速、中速、高速旋转控制;
点击 Add signals 按钮,配置端口、波特率、采集组数,点击 Start 开始接收串口数据;


采集完成后点击 Next ,对数据集进行分类命名,点击 Import 导入;


重复上述过程,添加多个分类信号;


模型训练
分类信号采集添加完成后,进入 Benchmark 标签页,点击新建,项目命名后选择 Start;


自动开始训练模型;


由于模型和算法种类较多,训练过程需耗费2小时以上;


训练完成,耗时 3 个多小时;


验证导出
待训练完成后,点击 Valiation 标签页进行模型验证;
在量化准确度最高的模型(第1个)点击右侧的 Serial Emulator 按钮,对串口采集的数据进行实时分析汇总,最后给出置信度最高的分类状态;


验证模型需固定步进电机,使其所处环境与训练时一致。
勾选目标模型,点击左侧 New Experiment 按钮,添加不同分类数据,对识别准确度进行估计和验证;


验证通过后,进入 Deployment 标签页,设置构建工具链等,点击 Deploy Library 生成 C 代码文件;


右侧显示模型的使用示例代码,可根据需要进行修改;


状态识别
将采集的 LIS3DH 加速度数据传递给 AI 模型识别函数,串口打印预测结果。
流程图

代码
修改 main.c 文件,添加如下代码
#include "main.h"
#include "NanoEdgeAI.h"
#define NEAI_SAMPLE_PERIOD_MS 100U /* 等于训练时的采样周期:10Hz→100 */
float input_signal[NEAI_INPUT_SIGNAL_LENGTH * NEAI_INPUT_AXIS_NUMBER]; /* 3 个 */
float probabilities[NEAI_NUMBER_OF_CLASSES]; /* 4 个 */
static uint32_t neai_last_tick = 0;
/**
* @brief 加速度打印轮询(非阻塞):每 100ms 读一次 LIS3DH 并打印
* @note 主循环里反复调用即可,未到时间立即返回
*/
static void acc_print_poll(void){}
/**
* @brief 电机轮询:处理串口速度指令 + 走一个完整周期(8 相位)
* @note 主循环反复调用即可;先处理指令再走步,新指令当圈立即生效
*/
static void motor_poll(void){}
/**
* @brief NEAI 轮询:每 100ms 读一次 LIS3DH 并调用 neai_classification(),打印分类结果
* @note 主循环里反复调用即可,未到时间立即返回
* 需要在 main() 里先调用 neai_classification_init() 初始化
*/
static void neai_poll(void)
{
int16_t raw[3];
int id_class = 0;
if ((uint32_t)(HAL_GetTick() - neai_last_tick) < NEAI_SAMPLE_PERIOD_MS)
return;
neai_last_tick = HAL_GetTick();
if (lis3dh_acceleration_raw_get(&lis3dh_ctx, raw) != 0)
return; /* 读失败跳过这次 */
/* 长度=1,填 X Y Z 值 */
input_signal[0] = lis3dh_from_fs2_hr_to_mg(raw[0]) / 1000.0f; /* g 单位 */
input_signal[1] = lis3dh_from_fs2_hr_to_mg(raw[1]) / 1000.0f;
input_signal[2] = lis3dh_from_fs2_hr_to_mg(raw[2]) / 1000.0f;
if (neai_classification(input_signal, probabilities, &id_class) != NEAI_OK)
{
printf("NEAI ERR\r\n");
return;
}
printf("id=%d %s p=%d%%/%d%%/%d%%/%d%%\r\n",
id_class,
neai_get_class_name(id_class),
(int)(probabilities[0] * 100.0f),
(int)(probabilities[1] * 100.0f),
(int)(probabilities[2] * 100.0f),
(int)(probabilities[3] * 100.0f));
}
int main(void)
{
uint8_t whoami = 0;
if (mx_system_init() != SYSTEM_OK)
{
return (-1);
}
else
{
/* 配置 LIS3DH */
printf("LIS3DH initialized OK!\r\n");
// 初始化模型
if (neai_classification_init() != NEAI_OK)
{
printf("NEAI init failed!\r\n");
while (1) { }
}
printf("NEAI ready: id=%s len=%d axis=%d classes=%d\r\n",
neai_get_id(),
neai_get_input_signal_size(),
neai_get_axis_number(),
neai_get_number_of_classes()
);
printf("=== NEAI classes (%d) ===\r\n", NEAI_NUMBER_OF_CLASSES);
for (int i = 0; i < NEAI_NUMBER_OF_CLASSES; i++)
{
printf(" id=%d name=%s\r\n", i, neai_get_class_name(i));
}
/* 初始化步进电机 */
stepper_init();
printf("\r\nSystem Ready!\r\n");
printf("Waiting for JSON command: {\"speed\": 2}\r\n");
while (1)
{
//acc_print_poll(); /* 加速度打印 */
motor_poll(); /* 串口控制 */
neai_poll(); /* 状态预测 */
}
}
} /* end main */保存代码。
在 CMakeLists.txt 文件添加模型文件路径;
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
# Add additional source files here
stepmotor.c # 步进电机驱动
lis3dh_reg.c # 加速度传感器驱动
)
target_link_options(${CMAKE_PROJECT_NAME} PUBLIC
# Add additional linker flags here
-u _printf_float # 浮点运算
)
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
# Add additional linker libraries here
"${CMAKE_CURRENT_LIST_DIR}/libneai.a" # ai 模型
)保存文件。
效果
编译并上传工程至板端,打开串口调试助手;
接收连续打印状态预测结果和 4 个分类的置信度;


输出的各个字段的含义如下
| 字段 | 含义 | 来源 |
| id = x | 模型预测的类别编号 x | id_class |
| stop/speed1|2|3 | 电机转速预测结果,与类别 id 对应 | neai_get_class_name(id_class) |
| p=95%/1%/1%/1% | 类别置信度 stop / speed3|2|1 | probabilities[0..3] |
发送 JSON 指令控制电机转速,观察状态预测结果和置信度;
状态 Stop 的置信度约为 90%


对于状态 speed1 进行预测,置信度约为 60%


状态 speed2 预测的置信度约为 70% ;


状态 speed3 预测的置信度为 80%


总结
本文介绍了 Let's Do|2026年第3期 活动,使用 NUCLEO-C562RE 开发板结合 LIS3DH 加速度传感器采集步进电机振动数据,训练 AI 模型,实现运行状态推理的项目设计,为边缘 AI 等相关项目的开发和应用提供了参考。
我要赚赏金
