之前完成串口控制开发板外设、加速度传感器数据采集以及步进电机的指令控制。在此基础上,将加速度计固件到电机上,采集电机在不同转速下的加速度传感器数据,用于在NanoEdge AI Studio中训练电机状态识别模型。
加速度传感器和MCU的安装如下所示

结合之前的加速采集工程以及电机串口控制工程,整合两个工程的代码,通过串口控制电机转动,采集加速度传感器的数据,用于Nano Edge AI Studio部署根据加速度数据识别电机运转状态的模型。
边缘AI模式使用加速计的三轴加速作为训练数据,采集到数据以单行数据的形式通过串口发送出去,XYZ轴的加速度值通过空格进行分割,Nano Edge AI Studio在记录数据时可以自动识别并存储数据。
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");
}
}电机的运动通过串口控制,分为停止和三种不同的速度,控制电机运动的代码和主循环中调用的代码如下
/* 档位 -> 相位间延时(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=逆时针 */
/**
* @brief 解析 {"speed": N},N ∈ [0,3]
* @retval 0 成功,-1 失败
*/
static int parse_speed_cmd(int *level)
{
char *p = strstr((char *)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 (rx_data_ready)
{
int lvl = speed_level;
int ok = parse_speed_cmd(&lvl);
rx_data_len = 0;
memset(rx_buf, 0, sizeof(rx_buf));
rx_data_ready = 0;
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");
}
/* 清空接收缓冲区 */
memset(rx_buf, 0, sizeof(rx_buf));
rx_data_len = 0;
rx_data_ready = 0;
HAL_UART_ReceiveToIdle_IT(mx_usart2_uart_gethandle(), rx_buf, RX_BUF_SIZE);
}
/* ---------- 2. 走一个完整周期(8 相位)后立即返回 ---------- */
if (speed_level != 0)
{
stepper_step(motor_dir, speed_delay_tbl[speed_level]);
}
}
int main(void)
{
/** System Init: this code placed in targets folder initializes your system.
* It calls the initialization (and sets the initial configuration) of the peripherals.
* You can use STM32CubeMX to generate and call this code or not in this project.
* It also contains the HAL initialization and the initial clock configuration.
*/
if (mx_system_init() != SYSTEM_OK)
{
return (-1);
}
else
{
/*
* You can start your application code here
*/
/* 初始化步进电机 */
mx_basic_stdio_init();
stepper_init();
....
printf("LIS3DH initialized OK!\r\n");
while (1)
{
motor_poll();
acc_print_poll();
}
}
} /* end main */2、NanoEdge AI Studio训练Nano Edge AI Studio 下载页面提交申请信息,获得软件安装文件和注册码。
https://stm32ai.st.com/zh/download-nanoedgeai/
软件中选择Classify工程用于导入自定义训练模型

Project Settings界面中选择开发板为NUCLEO-C562RE,设置传感器为三轴加速度计

添加用于训练的数据集,通过串口采集数据

设置串口的通讯参数以及采集数据的最大个数

完成一种电机运动状态下的数据采集,设置数据集的标签和数据分割符

在BenchMark页面,根据采集的数据来测试模型的效果,页面上实时显示BenchMark的进度

BenchMarking不一定需要跑完,可以暂停查看验证模型效果,选择一个模型进行部署

在Deployment界面部署选择工具链,并生成可部署的库文件

下载生成的库文件用于后续的AI模型部署
3、电机状态识别解压得到的压缩包,得到其中的NanoEdgeAI.h和libneai.a文件,添加到工程的顶层文件夹中,CMakeLists.txt文件夹中添加源代码和库文件的设置

CMake工程配置时会出现下面的错误

根据提示在cmake\components.cmake中添加库链接限定符

在程序中使用训练的模型运行电机状态检测的代码如下,调用neai库中的函数,输入采集到的加速度传感器的数据,对当前的电机状态进行识别。
#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;
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)
{
/** System Init: this code placed in targets folder initializes your system.
* It calls the initialization (and sets the initial configuration) of the peripherals.
* You can use STM32CubeMX to generate and call this code or not in this project.
* It also contains the HAL initialization and the initial clock configuration.
*/
if (mx_system_init() != SYSTEM_OK)
{
return (-1);
}
else
{
/*
* You can start your application code here
*/
/* 初始化步进电机 */
mx_basic_stdio_init();
// 初始化模型
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\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_ReceiveToIdle_IT(mx_usart2_uart_gethandle(), rx_buf, RX_BUF_SIZE);
printf("\r\nSystem Ready!\r\n");
printf("Waiting for JSON command: {\"speed\": 1}\r\n");
/* 填充驱动上下文,handle 直接指向 I2C1 */
lis3dh_ctx.write_reg = platform_write;
lis3dh_ctx.read_reg = platform_read;
lis3dh_ctx.mdelay = platform_delay;
lis3dh_ctx.handle = mx_i2c1_i2c_gethandle(); /* 或 &hi2c1 */
/* 读取 WHO_AM_I,验证 I2C 通信 */
if (lis3dh_device_id_get(&lis3dh_ctx, &whoami) != 0)
{
printf("LIS3DH read WHO_AM_I failed!\r\n");
while (1);
}
printf("LIS3DH WHO_AM_I = 0x%02X\r\n", whoami);
if (whoami != LIS3DH_ID)
{
printf("LIS3DH ID mismatch!\r\n");
while (1);
}
/* 配置 LIS3DH */
lis3dh_block_data_update_set(&lis3dh_ctx, PROPERTY_ENABLE); /* BDU */
lis3dh_full_scale_set(&lis3dh_ctx, LIS3DH_2g); /* ±2g */
lis3dh_data_rate_set(&lis3dh_ctx, LIS3DH_ODR_100Hz); /* 100Hz */
if (lis3dh_operating_mode_set(&lis3dh_ctx, LIS3DH_HR_12bit) != 0)
{
printf("LIS3DH high-resolution mode configuration failed!\r\n");
while (1);
}
printf("LIS3DH initialized OK!\r\n");
while (1)
{
motor_poll();
//acc_print_poll();
neai_poll(); /* 状态预测 */
}
}
} /* end main */编译并烧录代码到开发板。运行电机状态识别代码

电机状态的识别效果见视频所示
电机状态识别的效果并不理想,和电机没有固定、传感器的安装方式和训练数据集的大小都有关。通过本次活动,熟悉加速度计、电机控制以及电机状态AI模型训练过程。
我要赚赏金
