这一步应该算是这个项目的核心了,需要依赖工具NanoEdge AI Studio,这个工具需要去st官网上下载,下载链接为:NanoEdgeAIStudio | 软件 - 意法半导体 ,下载时会要求输入邮箱信息,用于接收激活码。
环境安装
下载到的软件叫NanoEdge-AI-Studio-Setup-xxx.exe,其中xxx是版本号,我下载的版本是5.2.0。双击这个软件便可执行安装步骤,安装过程中会弹出输入序列号激活的内容,此时需要去邮箱中找到对应的邮件复制序列号贴进来继续安装。
数据采集
创建工程
安装完工具后,便是双击安装好的软件NanoEdge AI Studio,此时会弹出主界面,在界面中选择 “+ NEW PROJECT”,在弹出的界面中选择分类(Classify variations of your signal)模式,此时项目已创建。
在新建的项目中,把Target选择为NUCLEO-C562RE,同时在sensor里选择三轴加速度传感器(感觉可能不需要选也行,默认就是三轴,问题是也没看到有开关),之后点击SAVE,工程创建下来了
数据提供
数据格式确认
保存完工程后,界面就自动弹到了signal页面了,这个我的理解就是采集原始数据的。在正常数据页面点击 ADD SIGNAL按钮,就弹出了添加数据的界面:

这个页面很直接地说明了可以在线获取数据,也可以直接按照右侧的格式提供数据,这里我选择了右侧的方法(自己控制电机,同时读取加速度传感器数据并打印)
硬件准备
到这一步,就是需要把所有模块都接上测试了,最终的接线如下(其实就是打通驱动时的接线方法,唯一新增的是用胶布把加速度传感器贴到步进电机上了):

数据获取
这里还是交给AI实现了,由于前面加驱动和验证驱动效果的实现时,有要求驱动写法是通过注册方式实现的,因此在这一步的采集的修改就变得很简单了,要求AI加宏注释掉测试的业务代码,再增加一个采集数据的任务就行了。
这个任务设定为:先启动测试环境,待测试环境启动后,启动加速度传感器采集并打印数据,打印够256个后,关闭加速度传感器并终止测试环境。测试环境有三个:静止、转动、堵转(最后验证还是转,最早调试驱动时遇到的问题,给了波形,但电机只有内部齿轮声,不转且发热。但自己理解并记录的原因是错误的)。
最终核心的采集代码如下:
app_data_acq
/** Self-contained state capture; no dependency on the legacy application apps. */
#include "app_data_acq.h"
#include "drv_lis3dh.h"
#include "drv_gear_stepper.h"
#include "mx_tim1.h"
#include "mx_usart2.h"
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
static drv_lis3dh_t s_sensor;
static drv_gear_stepper_t s_motor;
static app_data_acq_result_t s_acq_result;
static volatile drv_gear_stepper_status_t s_irq_error;
static uint32_t s_previous_sample_ms;
static uint32_t s_wait_started_ms;
static int app_data_acq_marker(uint32_t begin)
{
static const char *const names[] = {"static", "normal", "abnormal"};
char line[96];
int length;
if (begin != 0U)
{
length = snprintf(line, sizeof(line),
"# ACQ BEGIN mode=%s samples=%u\r\n",
names[s_acq_result.mode], (unsigned)APP_ACQ_SAMPLE_COUNT);
}
else
{
length = snprintf(line, sizeof(line),
"# ACQ END mode=%s count=%" PRIu32 " status=%d\r\n",
names[s_acq_result.mode], s_acq_result.sample_count,
(int)s_acq_result.status);
}
return (length > 0) && ((size_t)length < sizeof(line)) &&
(HAL_UART_Transmit(mx_usart2_uart_gethandle(), line,
(uint32_t)length, 100U) == HAL_OK);
}
/* Mode completion is dispatched in the foreground after motor cleanup. */
static void app_data_acq_mode_done(void)
{
drv_lis3dh_status_t cleanup = drv_lis3dh_deinit(&s_sensor);
s_acq_result.mode_done_count++;
if (cleanup != DRV_LIS3DH_OK)
{
s_acq_result.sensor_status = cleanup;
if (s_acq_result.status == APP_DATA_ACQ_OK)
{
s_acq_result.status = APP_DATA_ACQ_SENSOR_ERROR;
}
}
s_acq_result.finished_ms = HAL_GetTick();
if (!app_data_acq_marker(0U))
{
s_acq_result.status = APP_DATA_ACQ_UART_ERROR;
}
if (s_acq_result.status != APP_DATA_ACQ_OK)
{
s_acq_result.state = APP_DATA_ACQ_FAILED;
}
else if (s_acq_result.mode == APP_ACQ_MODE_ABNORMAL)
{
s_acq_result.state = APP_DATA_ACQ_DONE;
}
else
{
s_wait_started_ms = HAL_GetTick();
s_acq_result.state = APP_DATA_ACQ_WAITING;
}
}
static void app_data_acq_finish(void)
{
drv_gear_stepper_status_t status = drv_gear_stepper_deinit(&s_motor);
if (status != DRV_GEAR_STEPPER_OK)
{
s_acq_result.motor_status = status;
if (s_acq_result.status == APP_DATA_ACQ_OK)
{
s_acq_result.status = APP_DATA_ACQ_MOTOR_ERROR;
}
}
app_data_acq_mode_done();
}
void app_data_acq_init(void)
{
memset(&s_acq_result, 0, sizeof(s_acq_result));
s_wait_started_ms = HAL_GetTick();
s_acq_result.state = APP_DATA_ACQ_WAITING;
}
static void app_data_acq_start(uint32_t mode)
{
drv_lis3dh_config_t sensor_config;
drv_gear_stepper_config_t motor_config;
drv_lis3dh_data_t discarded;
uint32_t done_count = s_acq_result.mode_done_count;
memset(&s_acq_result, 0, sizeof(s_acq_result));
s_acq_result.mode_done_count = done_count;
s_acq_result.mode = mode;
s_acq_result.state = APP_DATA_ACQ_RUNNING;
s_irq_error = DRV_GEAR_STEPPER_OK;
if (!app_data_acq_marker(1U))
{
s_acq_result.status = APP_DATA_ACQ_UART_ERROR;
app_data_acq_finish();
return;
}
/* Also retries a retained binding after an earlier failed motor cleanup. */
s_acq_result.motor_status = drv_gear_stepper_deinit(&s_motor);
if (s_acq_result.motor_status != DRV_GEAR_STEPPER_OK)
{
s_acq_result.status = APP_DATA_ACQ_MOTOR_ERROR;
app_data_acq_finish();
return;
}
drv_lis3dh_get_default_config(&sensor_config);
sensor_config.address_7bit = DRV_LIS3DH_ADDRESS_HIGH;
#if APP_ACQ_SAMPLE_RATE_HZ == 50U
sensor_config.odr = LIS3DH_ODR_50_HZ;
#elif APP_ACQ_SAMPLE_RATE_HZ == 100U
sensor_config.odr = LIS3DH_ODR_100_HZ;
#elif APP_ACQ_SAMPLE_RATE_HZ == 200U
sensor_config.odr = LIS3DH_ODR_200_HZ;
#else
sensor_config.odr = LIS3DH_ODR_400_HZ;
#endif
s_acq_result.sensor_status = drv_lis3dh_init(&s_sensor, &sensor_config);
if (s_acq_result.sensor_status == DRV_LIS3DH_OK)
{
/* Do not count the sample produced before the selected mode starts. */
s_acq_result.sensor_status = drv_lis3dh_read(&s_sensor, &discarded);
}
if ((s_acq_result.sensor_status != DRV_LIS3DH_OK) &&
(s_acq_result.sensor_status != DRV_LIS3DH_NO_DATA))
{
s_acq_result.status = APP_DATA_ACQ_SENSOR_ERROR;
app_data_acq_finish();
return;
}
if (mode != APP_ACQ_MODE_STATIC)
{
drv_gear_stepper_get_default_config(&motor_config);
motor_config.step_interval_ms = mode == APP_ACQ_MODE_NORMAL ?
APP_ACQ_NORMAL_STEP_MS : APP_ACQ_ABNORMAL_STEP_MS;
s_acq_result.motor_status = drv_gear_stepper_init(&s_motor, &motor_config);
if (s_acq_result.motor_status == DRV_GEAR_STEPPER_OK)
{
/* Foreground stops this move once the sample target is reached. */
s_acq_result.motor_status = drv_gear_stepper_move(
&s_motor, GEAR_STEPPER_FORWARD, 1000000U);
}
if (s_acq_result.motor_status != DRV_GEAR_STEPPER_OK)
{
s_acq_result.status = APP_DATA_ACQ_MOTOR_ERROR;
app_data_acq_finish();
return;
}
}
s_acq_result.started_ms = HAL_GetTick();
}
void app_data_acq_loop(void)
{
static const uint32_t mode_order[] = {
APP_ACQ_MODE_NORMAL, APP_ACQ_MODE_STATIC, APP_ACQ_MODE_ABNORMAL
};
drv_lis3dh_data_t sample;
char line[48];
int length;
uint32_t now;
if (s_acq_result.state == APP_DATA_ACQ_WAITING)
{
if ((uint32_t)(HAL_GetTick() - s_wait_started_ms) < APP_ACQ_SETTLE_MS)
{
return;
}
app_data_acq_start(mode_order[s_acq_result.mode_done_count]);
}
if (s_acq_result.state != APP_DATA_ACQ_RUNNING)
{
return;
}
if (s_irq_error != DRV_GEAR_STEPPER_OK)
{
s_acq_result.motor_status = s_irq_error;
s_acq_result.status = APP_DATA_ACQ_MOTOR_ERROR;
app_data_acq_finish();
return;
}
now = HAL_GetTick();
if ((uint32_t)(now - s_acq_result.started_ms) >= APP_ACQ_TIMEOUT_MS)
{
s_acq_result.status = APP_DATA_ACQ_TIMEOUT;
app_data_acq_finish();
return;
}
s_acq_result.sensor_status = drv_lis3dh_read(&s_sensor, &sample);
if (s_acq_result.sensor_status == DRV_LIS3DH_NO_DATA)
{
return;
}
if (s_acq_result.sensor_status != DRV_LIS3DH_OK)
{
s_acq_result.status = APP_DATA_ACQ_SENSOR_ERROR;
app_data_acq_finish();
return;
}
now = HAL_GetTick();
length = snprintf(line, sizeof(line), "%" PRId32 " %" PRId32 " %" PRId32 "\r\n",
sample.x_mg, sample.y_mg, sample.z_mg);
if ((length <= 0) || ((size_t)length >= sizeof(line)) ||
(HAL_UART_Transmit(mx_usart2_uart_gethandle(), line, (uint32_t)length, 100U) != HAL_OK))
{
s_acq_result.status = APP_DATA_ACQ_UART_ERROR;
app_data_acq_finish();
return;
}
if (s_acq_result.sample_count > 0U)
{
uint32_t gap = now - s_previous_sample_ms;
if (gap > s_acq_result.max_sample_gap_ms)
{
s_acq_result.max_sample_gap_ms = gap;
}
}
s_previous_sample_ms = now;
if (++s_acq_result.sample_count == APP_ACQ_SAMPLE_COUNT)
{
app_data_acq_finish();
}
}
void HAL_TIM_UpdateCallback(hal_tim_handle_t *timer)
{
if (timer == mx_tim1_gethandle())
{
drv_gear_stepper_status_t status = drv_gear_stepper_on_timer_update(&s_motor);
if ((status != DRV_GEAR_STEPPER_OK) && (status != DRV_GEAR_STEPPER_RUNNING))
{
s_irq_error = status;
}
}
}
const app_data_acq_result_t *app_data_acq_get_result(void)
{
return &s_acq_result;
}main
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
*/
app_data_acq_init();
while (1) {
app_data_acq_loop();
HAL_Delay(1U);
}
}
} /* end main */采集到的数据
程序运行后,串口输出了以下内容(其中,abnormal是无效数据,我对之前遇到的只发热不转问题的根因的理解错误,实际按照理解去复现时,还是转动的)
# ACQ BEGIN mode=normal samples=256 -108 -384 876 288 -408 876 -336 -264 852 108 -324 900 252 -240 996 36 -144 1104 -228 -132 1080 -240 -132 1068 -84 -144 996 312 -144 996 336 -156 1044 -48 -12 1080 -156 -48 1176 -72 -72 1164 -48 -72 1284 264 -108 1392 228 -96 1464 -60 -36 1476 -132 -96 1452 -180 -24 1428 48 -72 1356 360 -72 1308 324 -72 1200 -60 24 1176 -12 -24 1152 108 -12 1104 240 0 1092 600 0 1092 312 96 1068 84 96 1080 132 108 1020 -24 132 1008 276 24 936 432 -24 936 156 0 948 -48 0 960 -108 48 1008 84 48 972 384 24 984 480 -12 948 -24 72 864 -96 -24 804 -48 -24 708 0 36 684 288 -12 696 108 -48 720 -216 -48 804 -228 -60 888 -420 -84 948 -144 -84 1056 216 -96 1164 36 -48 1236 -396 -48 1236 -456 -72 1260 -360 -72 1188 -96 -60 1128 36 -84 1092 -456 -96 996 -588 -108 888 -552 -144 804 -660 -156 804 -168 -84 852 -192 -108 816 -576 -144 876 -720 -120 852 -1032 -180 852 -744 -132 996 -276 0 1056 -480 -60 1080 -936 -84 1104 -960 -96 1032 -948 -156 1104 -732 -96 1236 -384 -108 1308 -720 -204 1272 -900 -168 1368 -828 -180 1296 -1044 -216 1284 -528 -156 1332 -204 -84 1212 -444 -132 1176 -624 -108 1056 -732 -192 864 -564 -192 864 -120 -108 888 -24 -132 804 -456 -144 792 -516 -108 768 -504 -144 732 -600 -144 852 -204 -120 840 -240 -192 696 -636 -252 756 -732 -252 732 -1080 -396 672 -780 -336 732 -360 -264 720 -588 -408 624 -1020 -456 600 -1020 -444 552 -1008 -504 480 -756 -396 492 -408 -348 492 -660 -444 432 -744 -348 492 -552 -312 480 -696 -348 468 -264 -228 552 -24 -216 456 -432 -384 384 -672 -432 324 -828 -564 264 -636 -564 252 -192 -372 264 -180 -432 216 -600 -564 120 -696 -468 156 -708 -564 96 -744 -600 96 -312 -516 132 -360 -624 132 -660 -720 156 -696 -648 204 -900 -684 204 -528 -576 204 -96 -420 216 -276 -516 156 -624 -540 132 -624 -528 144 -588 -600 96 -312 -468 156 -48 -444 180 -432 -612 120 -660 -588 276 -684 -636 240 -864 -672 288 -348 -588 396 -120 -552 432 -432 -600 492 -612 -564 468 -780 -600 432 -552 -576 540 -60 -408 660 -12 -468 684 -396 -468 804 -420 -396 828 -396 -468 840 -360 -384 900 108 -312 912 48 -312 924 -336 -288 888 -336 -348 792 -408 -228 780 -156 -168 768 312 -84 816 288 -96 876 -12 12 912 12 -48 948 108 12 900 252 48 948 648 84 1068 336 72 1044 -24 24 1200 -60 -60 1368 -132 -48 1488 144 -60 1620 468 -60 1704 336 0 1692 -72 60 1584 -48 0 1416 24 48 1152 300 24 972 456 12 936 108 144 792 -60 168 756 72 180 660 60 192 648 408 168 672 516 168 684 132 192 768 0 144 792 -216 108 792 -96 72 780 264 48 840 120 12 924 -408 -48 1020 -384 -216 1188 -228 -216 1320 -24 -132 1512 324 -120 1668 144 0 1680 -96 60 1632 -48 60 1488 -228 72 1332 96 48 1212 360 96 1128 84 120 1044 -144 120 960 -240 108 912 -144 108 888 252 108 828 276 24 852 -228 -36 840 -300 0 804 -372 -12 804 -480 -48 840 -60 -60 840 -240 -132 864 -660 -168 960 -696 -156 972 -1008 -216 1044 -636 -180 1164 -156 -72 1164 -408 -132 1188 -828 -108 1188 -792 -96 1104 -732 -60 1104 -360 96 1176 -36 144 1092 -444 132 1068 -576 144 1032 -564 84 912 -744 48 936 -276 84 960 -144 84 900 -552 -36 984 -708 -60 936 -936 -156 912 -684 -144 1008 -216 -48 1056 -276 -108 1032 -756 -192 1044 -720 -168 996 -744 -192 996 -672 -156 1092 -240 -108 1056 -384 -180 948 -636 -156 996 -660 -156 912 -936 -228 948 -540 -120 984 -132 -48 900 -408 -120 840 -732 -132 804 -816 -204 708 -696 -204 708 -324 -60 804 -144 -120 696 -588 -300 600 -720 -252 636 -744 -348 576 -912 -408 576 -432 -312 576 -372 -348 504 -744 -444 504 -840 -408 492 # ACQ END mode=normal count=256 status=0 # ACQ BEGIN mode=static samples=256 -96 -72 1212 -96 -84 1236 -72 -84 1248 -36 -84 1236 -12 -84 1260 12 -72 1236 -24 -72 1224 -12 -48 1188 0 -24 1104 -24 -24 1020 -72 -12 996 -96 -36 960 -144 -48 960 -168 -72 936 -228 -120 936 -264 -132 948 -300 -156 960 -336 -120 960 -336 -144 984 -360 -144 1056 -384 -156 1116 -396 -156 1152 -372 -156 1188 -372 -132 1212 -360 -108 1212 -372 -36 1188 -348 12 1140 -372 0 1080 -372 0 1020 -360 -24 996 -372 -48 984 -372 -72 996 -384 -84 1032 -372 -84 1116 -372 -84 1200 -372 -84 1200 -336 -108 1224 -324 -132 1212 -312 -96 1188 -312 -132 1140 -324 -132 1080 -288 -120 1020 -252 -132 960 -276 -132 876 -276 -144 816 -312 -168 756 -348 -192 756 -360 -204 756 -372 -228 792 -384 -252 792 -384 -228 768 -432 -276 804 -480 -300 804 -528 -312 828 -552 -324 840 -504 -348 840 -492 -348 828 -492 -348 804 -504 -360 792 -480 -372 732 -480 -372 660 -492 -372 612 -516 -396 588 -504 -432 516 -516 -456 492 -504 -456 492 -468 -492 480 -396 -444 504 -348 -408 456 -264 -372 408 -240 -276 360 -216 -240 276 -192 -228 216 -192 -264 168 -264 -312 156 -396 -348 168 -480 -384 204 -540 -468 228 -612 -528 264 -600 -588 300 -588 -564 384 -600 -564 504 -588 -576 564 -576 -588 600 -552 -564 600 -480 -540 612 -432 -480 636 -408 -420 588 -360 -384 540 -348 -348 540 -324 -336 528 -264 -276 516 -228 -252 696 -168 -204 696 -108 -204 684 -132 -180 756 -96 -132 816 -48 -108 828 -12 -84 864 -12 -72 912 -24 -60 1008 -60 -72 1080 -96 -96 1152 -120 -120 1260 -60 -108 1380 24 -60 1452 12 0 1560 -24 -12 1572 -72 -12 1524 -132 -48 1416 -144 -72 1332 -120 -84 1296 -72 -36 1272 -24 48 1224 48 84 1176 84 96 1056 144 144 1032 96 168 960 60 168 864 48 132 768 36 132 708 60 108 732 72 96 744 48 72 816 60 24 864 24 -24 984 24 -60 1128 0 -84 1224 -12 -96 1272 0 -144 1308 -24 -120 1344 -24 -60 1296 12 -36 1260 12 -36 1164 -12 -24 1080 0 -12 1020 0 -24 948 -36 -60 936 -108 -60 912 -156 -60 900 -180 -72 888 -216 -108 936 -252 -120 948 -264 -132 1020 -276 -144 1092 -300 -132 1164 -300 -120 1212 -312 -108 1260 -324 -108 1260 -288 -96 1272 -324 -84 1212 -324 -72 1176 -360 -36 1116 -384 0 1068 -396 -24 1056 -432 -48 1008 -492 -84 996 -516 -108 1032 -540 -144 1032 -528 -156 1044 -540 -144 1068 -540 -156 1068 -516 -144 1056 -456 -144 1056 -420 -132 1032 -396 -144 1008 -384 -156 984 -396 -156 972 -396 -180 936 -384 -192 888 -372 -192 888 -324 -192 888 -348 -204 852 -372 -192 864 -384 -204 828 -408 -192 828 -420 -252 840 -492 -264 792 -468 -288 744 -504 -300 744 -552 -300 732 -564 -288 720 -576 -312 672 -552 -348 600 -540 -336 588 -516 -348 588 -492 -360 576 -480 -372 516 -480 -360 516 -468 -348 528 -444 -336 528 -432 -300 540 -408 -288 552 -420 -312 576 -456 -360 612 -516 -396 624 -528 -396 612 -552 -420 600 -552 -432 588 -552 -456 600 -564 -492 612 -564 -492 588 -540 -468 576 -540 -456 552 -480 -420 528 -432 -396 516 -408 -348 528 -384 -300 564 -360 -252 576 -372 -276 552 -384 -276 528 -384 -300 528 -372 -312 516 -348 -288 504 -336 -264 492 -360 -264 492 -360 -300 504 -336 -264 492 -360 -228 588 -348 -204 588 -348 -228 636 -324 -216 732 -300 -204 792 -252 -168 852 -216 -144 936 -204 -108 972 -168 -84 1044 -156 -72 1056 -168 -84 1032 -180 -96 1020 -168 -96 1032 -168 -96 1056 -168 -72 1068 -144 -48 1056 -168 0 996 -168 12 1008 -180 0 1020 -156 12 1032 -168 -24 1080 -156 48 1164 -144 48 1284 -168 -24 1392 -180 -60 1464 -192 -72 1548 -180 -84 1560 -156 -60 1536 -72 -60 1536 -36 -36 1428 -12 -24 1368 -24 -12 1260 -36 -12 1140 -24 -36 1092 -72 -48 1032 -48 -72 996 -48 -84 1044 -60 -84 1140 # ACQ END mode=static count=256 status=0 # ACQ BEGIN mode=abnormal samples=256 216 180 888 360 144 876 312 120 888 48 96 840 156 48 864 120 -12 948 324 48 972 144 0 1008 -264 12 1020 180 -12 1116 288 36 1224 192 96 1236 156 144 1308 108 108 1296 396 108 1284 336 60 1284 -156 60 1248 216 72 1248 348 120 1188 288 132 1140 24 120 1140 -300 96 1116 108 36 1092 84 -12 1092 -408 -48 1092 -228 -72 1092 -144 -84 1080 -48 -60 1080 -204 -48 1068 -672 -108 1068 -216 -96 1044 -132 -96 1032 -432 -108 1008 -420 -120 1032 -396 -132 1056 -192 -108 1044 -300 -96 1044 -840 -156 1068 -396 -108 1056 -192 -72 1044 -396 -96 1080 -552 -108 1056 -732 -108 972 -276 -36 984 -276 -60 1068 -840 -156 984 -576 -192 936 -408 -144 948 -324 -144 1008 -552 -204 1020 -912 -288 936 -372 -204 948 -240 -144 1068 -696 -216 1008 -612 -264 924 -480 -216 888 -204 -168 960 -348 -144 960 -828 -264 840 -312 -204 792 -156 -144 888 -432 -192 864 -528 -276 792 -576 -300 672 -120 -192 780 -180 -168 888 -840 -348 780 -480 -336 732 -300 -264 768 -324 -276 792 -576 -348 756 -888 -396 684 -324 -276 720 -312 -252 648 -912 -360 540 -672 -432 468 -528 -396 468 -372 -384 456 -576 -468 396 -996 -588 360 -408 -492 408 -312 -420 432 -756 -504 444 -648 -576 480 -576 -492 456 -288 -444 456 -444 -480 396 -1056 -624 300 -492 -552 264 -324 -480 228 -612 -528 192 -660 -624 192 -696 -624 180 -288 -528 192 -348 -528 192 -960 -624 120 -540 -588 108 -300 -468 156 -396 -468 192 -588 -588 192 -864 -684 96 -312 -528 204 -192 -468 336 -768 -564 348 -528 -636 324 -336 -528 372 -144 -456 552 -312 -504 588 -804 -612 504 -216 -480 540 -24 -348 720 -468 -396 744 -444 -492 636 -432 -456 564 -72 -372 744 -204 -372 864 -828 -516 816 -276 -492 828 -36 -348 1020 -204 -384 1116 -240 -324 1200 -336 -348 1224 48 -264 1200 0 -288 1152 -552 -300 1176 -120 -324 1188 12 -240 1128 -24 -252 1080 -60 -180 1008 -144 -168 1008 288 -144 1008 252 -156 984 -156 -132 1044 96 -180 1080 132 -192 1056 252 -252 1056 156 -240 1128 -36 -180 1200 444 -156 1308 480 -120 1308 96 -72 1260 288 -48 1200 252 -48 1092 516 -48 948 360 -72 840 -12 -72 756 408 -120 720 408 -108 744 168 -144 792 276 -108 840 264 -72 876 588 -72 924 552 -72 984 144 24 996 516 -36 1056 552 -12 1116 420 -24 1080 384 0 1044 288 48 1008 624 12 972 624 -24 960 180 24 972 552 -48 1020 564 -24 1056 624 -48 1008 528 24 972 288 108 852 696 120 780 756 132 720 348 168 672 636 132 648 648 132 636 756 96 612 660 48 636 276 72 612 696 24 672 708 0 696 456 -24 708 540 -36 780 468 -12 852 708 -48 960 672 -84 1056 180 -84 1020 636 -84 1056 684 -48 1080 660 -60 1044 588 -24 1116 312 48 1056 624 24 1044 648 0 1020 168 -48 912 492 -72 864 552 -108 828 564 -96 744 468 -108 720 -132 -156 720 336 -276 816 480 -276 852 48 -312 816 -24 -444 984 -72 -480 1092 288 -408 1260 276 -396 1440 -480 -468 1416 -60 -468 1332 264 -336 1356 108 -288 1320 -96 -444 1236 -492 -480 1128 24 -360 1128 180 -348 1152 -672 -516 1104 -480 -672 1056 -168 -528 1056 -156 -456 1068 -264 -528 1008 -756 -612 804 -228 -468 756 108 -396 660 -456 -408 492 -252 -480 348 -84 -408 168 -36 -408 12 -48 -480 -84 -660 -612 -228 -192 -576 -216 0 -552 -240 -480 -564 -312 -444 -684 -300 -480 -648 -324 -228 -600 -312 -252 -636 -252 -936 -756 -384 -504 -744 -288 -384 -708 -192 -612 -744 -132 -672 -864 0 -864 -888 12 -408 -744 84 -372 -720 144 -936 -816 0 -552 -744 0 -432 -648 -48 -420 -636 -108 -528 -696 -120 -912 -792 -240 -456 -684 -216 -348 -648 -204 -816 -696 -264 -612 -732 -276 -576 -660 -276 -408 -660 -216 -480 -672 -108 -984 -804 -168 -456 -696 -84 -336 -648 -24 # ACQ END mode=abnormal count=256 status=0
把这份log中的static(静止)和normal(正常转动)部分的log保存成txt文件即可。
数据训练
把采集到的数据分别提交到数据并命名,之后点击save便会跳到Benchmark界面,在此界面中点击 New benchmark,分别选中刚刚添加的两份数据后保存,便自动进行了数据训练了(可能电脑性能不行,导致训练过程有点长,一轮训练下来3小时没了)。这里我试着把带噪数据(除了电机是否运转,手还拿着电机各个角度摆)和不带噪数据分别喂进去,发现如果是不带噪数据,训练准确度上升非常快。

训练完成后,需要去Validation界面去选择一个认为OK的结果生成一个库,在此界面选择一个库,点击NEW EXPERIMENT,之后选择训练时提供的数据,便可以等待库的生成。

之后在Deployment目录下,点击 DEPLOY LIBRARY按钮,就可以生成并下载训练好的库,右侧也贴出了参考实现代码,代码如下:
/* =============
Copyright (c) 2026, STMicroelectronics
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that
the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote
products derived from this software without specific prior written permission.
*THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER / OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*
*/
/**
**************************************************************************
* Demo: NanoEdge AI process to include in main program body
*
* @note This program must be completed and customized by the user
**************************************************************************
*/
/* Includes --------------------------------------------------------------------*/
#include "NanoEdgeAI.h"
/* Private define --------------------------------------------------------------*/
/* Private variables defined by user -------------------------------------------*/
float input_signal[NEAI_INPUT_SIGNAL_LENGTH * NEAI_INPUT_AXIS_NUMBER]; // Buffer of input values
float probabilities[NEAI_NUMBER_OF_CLASSES]; // Buffer of class probabilities
/* Private function prototypes defined by user ---------------------------------*/
/*
* @brief Collect data process
*
* This function is defined by user, depends on applications and sensors
*
* @param input_signal: [in, out] buffer of sample values
* @retval None
* @note If NEAI_INPUT_AXIS_NUMBER = 3 (cf NanoEdgeAI.h), the buffer must be
* ordered as follow:
* [x0 y0 z0 x1 y1 z1 ... xn yn zn], where xi, yi and zi
* are the values for x, y and z axes, n is equal to
* NEAI_INPUT_SIGNAL_LENGTH (cf NanoEdgeAI.h)
*/
void fill_buffer(float *input_signal)
{
/* USER BEGIN */
/* USER END */
}
/* -----------------------------------------------------------------------------*/
int main(void)
{
/* Initialization ------------------------------------------------------------*/
enum neai_state error_code = neai_classification_init();
if (error_code != NEAI_OK) {
/* Check the returned error code (cf NanoEdgeAI.h). */
}
/* Classification ------------------------------------------------------------*/
int id_class = 0;
while (1) {
fill_buffer(input_signal);
neai_classification(input_signal, probabilities, &id_class);
/* USER BEGIN */
/*
* e.g.: Trigger functions depending on id_class
* (print output class probabilities using probabilities[id_class],
* print the name of the associated class using the getter function defined
* in NanoEdgeAI.h, neai_get_class_name(id_class),
* blink LED, ring alarm, etc.).
*/
/* USER END */
}
}效果集成
拿到了库,以及算法效果,那最后一步就是集成了,还是老方案,直接用一个测试app,这个app干两个事,一个是一直采集加速度传感器数据并喂给神经网络库,另一个是不定期(间隔时间至少1s,最多5s)地启动一两秒的电机,最终输出判定结果。
代码编写
还是交给ai写的代码,这套代码的逻辑是通过硬件定时器驱动电机,主循环随机设置电机的启停,并一直读取三轴加速度传感器数据喂给大模型,并在大模型数据输出时做统计并输出结果(由于大模型训练不是100%的质量,因此在这里做了一个统计,只有连续20次读出状态变更了,才认为是状态变了,这样就能降低偶发性错误数据干扰判断)
/** Continuous mg sampling and pretrained classification with independent motor timing. */
#include "app_neai.h"
#include "NanoEdgeAI.h"
#include "drv_lis3dh.h"
#include "drv_gear_stepper.h"
#include "mx_tim1.h"
#include "mx_usart2.h"
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#if (NEAI_INPUT_SIGNAL_LENGTH != 1) || (NEAI_INPUT_AXIS_NUMBER != 3) || \
(NEAI_NUMBER_OF_CLASSES != APP_NEAI_CLASS_COUNT)
#error "This application requires the exported single-XYZ, two-class model"
#endif
#define APP_NEAI_TX_DEPTH 8U
#define APP_NEAI_TX_LENGTH 256U
/* A finite guard beyond the 1 s deadline; the IRQ normally cancels first. */
#define APP_NEAI_MOVE_STEPS (APP_NEAI_MOTOR_RUN_MS / APP_NEAI_NORMAL_STEP_MS + 2U)
static drv_lis3dh_t s_sensor;
static drv_gear_stepper_t s_motor;
static app_neai_result_t s_result;
static float s_input[NEAI_INPUT_SIGNAL_LENGTH * NEAI_INPUT_AXIS_NUMBER];
static const char *s_class_names[NEAI_NUMBER_OF_CLASSES];
static uint32_t s_initialized;
static uint32_t s_random;
static uint32_t s_off_started_ms;
static volatile uint32_t s_motor_on;
static volatile uint32_t s_motor_started_ms;
static volatile uint32_t s_motor_stopped_ms;
static volatile uint32_t s_stop_pending;
static volatile drv_gear_stepper_status_t s_irq_error;
static char s_tx_lines[APP_NEAI_TX_DEPTH][APP_NEAI_TX_LENGTH];
static uint32_t s_tx_lengths[APP_NEAI_TX_DEPTH];
static uint32_t s_tx_head;
static uint32_t s_tx_count;
static uint32_t s_tx_active;
static uint32_t s_tx_started_ms;
static uint32_t s_uart_failed;
/* Only the foreground formats/queues lines. An active slot is retained until
* HAL reports TX idle, so the UART IRQ never reads a modified buffer. */
static int app_neai_log_state(uint32_t timestamp_ms, int id_class)
{
/* UTF-8: class 0 = startup/running, class 1 = stationary. */
static const char *const states[] = {
"startup/running", "stationary"
};
uint32_t slot;
int length;
if ((s_uart_failed != 0U) || (s_tx_count == APP_NEAI_TX_DEPTH))
{
return 0;
}
slot = (s_tx_head + s_tx_count) % APP_NEAI_TX_DEPTH;
length = snprintf(s_tx_lines[slot], APP_NEAI_TX_LENGTH,
"%" PRIu32 " %s\r\n", timestamp_ms, states[id_class]);
if ((length <= 0) || ((uint32_t)length >= APP_NEAI_TX_LENGTH))
{
return 0;
}
s_tx_lengths[slot] = (uint32_t)length;
s_tx_count++;
return 1;
}
static int app_neai_tx_service(void)
{
hal_uart_handle_t *uart = mx_usart2_uart_gethandle();
if (s_uart_failed != 0U)
{
return 0;
}
if (s_tx_active != 0U)
{
if (HAL_UART_GetTxState(uart) != HAL_UART_TX_STATE_IDLE)
{
if ((uint32_t)(HAL_GetTick() - s_tx_started_ms) < APP_NEAI_UART_TIMEOUT_MS)
{
return 1;
}
s_uart_failed = 1U;
(void)HAL_UART_AbortTransmit(uart);
return 0;
}
s_tx_active = 0U;
s_tx_head = (s_tx_head + 1U) % APP_NEAI_TX_DEPTH;
s_tx_count--;
}
if (s_tx_count != 0U)
{
if (HAL_UART_Transmit_IT(uart, s_tx_lines[s_tx_head], s_tx_lengths[s_tx_head]) != HAL_OK)
{
s_uart_failed = 1U;
return 0;
}
s_tx_active = 1U;
s_tx_started_ms = HAL_GetTick();
}
return 1;
}
static void app_neai_fail(app_neai_status_t status)
{
if (s_result.state == APP_NEAI_FAILED)
{
return;
}
s_result.state = APP_NEAI_FAILED;
s_result.status = status;
/* Deinit stops TIM1 before releasing coils/restoring high-Z. Preserve the
* original error and retain separate cleanup status on failure. */
s_result.motor_cleanup_status = drv_gear_stepper_deinit(&s_motor);
s_motor_on = 0U;
s_result.motor_on = 0U;
s_result.sensor_cleanup_status = drv_lis3dh_deinit(&s_sensor);
}
static uint32_t app_neai_random_wait(void)
{
/* Non-cryptographic xorshift. Sampling also mixes changing sensor bits into
* the state; repeated resets are not guaranteed to produce different waits. */
s_random ^= s_random << 13;
s_random ^= s_random >> 17;
s_random ^= s_random << 5;
return APP_NEAI_MOTOR_OFF_MIN_MS +
s_random % (APP_NEAI_MOTOR_OFF_MAX_MS - APP_NEAI_MOTOR_OFF_MIN_MS + 1U);
}
static int app_neai_motor_service(void)
{
if (s_irq_error != DRV_GEAR_STEPPER_OK)
{
s_result.motor_status = s_irq_error;
app_neai_fail(APP_NEAI_MOTOR_ERROR);
return 0;
}
if (s_stop_pending != 0U)
{
/* TIM1 is stopped; these values cannot change until the next move. */
s_off_started_ms = s_motor_stopped_ms;
s_stop_pending = 0U;
s_result.last_motor_run_ms = s_motor_stopped_ms - s_motor_started_ms;
s_result.motor_off_wait_ms = app_neai_random_wait();
}
if ((s_motor_on == 0U) &&
((uint32_t)(HAL_GetTick() - s_off_started_ms) >= s_result.motor_off_wait_ms))
{
s_motor_started_ms = HAL_GetTick();
s_motor_on = 1U;
s_result.motor_status = drv_gear_stepper_move(
&s_motor, GEAR_STEPPER_FORWARD, APP_NEAI_MOVE_STEPS);
if (s_result.motor_status != DRV_GEAR_STEPPER_OK)
{
app_neai_fail(APP_NEAI_MOTOR_ERROR);
return 0;
}
s_result.motor_run_count++;
}
s_result.motor_on = s_motor_on;
return 1;
}
void app_neai_init(void)
{
drv_lis3dh_config_t sensor_config;
drv_gear_stepper_config_t motor_config;
drv_lis3dh_data_t discarded;
if (s_initialized != 0U)
{
return;
}
s_initialized = 1U;
s_result.id_class = -1;
s_result.confirmed_class = -1;
s_result.candidate_class = -1;
s_random = 0x9E3779B9U ^ HAL_GetTick();
if (s_random == 0U)
{
s_random = 1U;
}
/* Export uses short enums. The only enum at this library boundary is a
* return value (0..6), extended in r0 by AAPCS32; no enum pointer, struct or
* enum parameter crosses the boundary. Keep the HAL/driver enum ABI intact. */
s_result.model_status = neai_classification_init();
if (s_result.model_status != NEAI_OK)
{
app_neai_fail(APP_NEAI_MODEL_ERROR);
return;
}
s_class_names[0] = neai_get_class_name(0);
s_class_names[1] = neai_get_class_name(1);
if ((neai_get_input_signal_size() != NEAI_INPUT_SIGNAL_LENGTH) ||
(neai_get_axis_number() != NEAI_INPUT_AXIS_NUMBER) ||
(neai_get_number_of_classes() != NEAI_NUMBER_OF_CLASSES) ||
(s_class_names[0] == NULL) || (s_class_names[1] == NULL) ||
(strcmp(s_class_names[0], "run_1") != 0) ||
(strcmp(s_class_names[1], "not_run1") != 0))
{
app_neai_fail(APP_NEAI_MODEL_MISMATCH);
return;
}
drv_gear_stepper_get_default_config(&motor_config);
motor_config.step_interval_ms = APP_NEAI_NORMAL_STEP_MS;
s_result.motor_status = drv_gear_stepper_init(&s_motor, &motor_config);
if (s_result.motor_status != DRV_GEAR_STEPPER_OK)
{
app_neai_fail(APP_NEAI_MOTOR_ERROR);
return;
}
drv_lis3dh_get_default_config(&sensor_config);
sensor_config.address_7bit = DRV_LIS3DH_ADDRESS_HIGH;
sensor_config.range = LIS3DH_RANGE_16G;
sensor_config.mode = LIS3DH_MODE_HIGH_RESOLUTION;
#if APP_NEAI_SAMPLE_RATE_HZ == 50U
sensor_config.odr = LIS3DH_ODR_50_HZ;
#elif APP_NEAI_SAMPLE_RATE_HZ == 100U
sensor_config.odr = LIS3DH_ODR_100_HZ;
#elif APP_NEAI_SAMPLE_RATE_HZ == 200U
sensor_config.odr = LIS3DH_ODR_200_HZ;
#else
sensor_config.odr = LIS3DH_ODR_400_HZ;
#endif
s_result.sensor_status = drv_lis3dh_init(&s_sensor, &sensor_config);
if (s_result.sensor_status == DRV_LIS3DH_OK)
{
s_result.sensor_status = drv_lis3dh_read(&s_sensor, &discarded);
}
if ((s_result.sensor_status != DRV_LIS3DH_OK) &&
(s_result.sensor_status != DRV_LIS3DH_NO_DATA))
{
app_neai_fail(APP_NEAI_SENSOR_ERROR);
return;
}
s_off_started_ms = HAL_GetTick();
s_result.last_sample_ms = s_off_started_ms;
s_result.motor_off_wait_ms = app_neai_random_wait();
s_result.state = APP_NEAI_RUNNING;
}
/* Count only successful fresh classifications. Returning to the confirmed
* state cancels a pending transition; changing the candidate restarts at one.
* Initial state acquisition uses the same threshold as later transitions. */
static int app_neai_confirm_state(int id_class)
{
if (id_class == s_result.confirmed_class)
{
s_result.candidate_class = -1;
s_result.consecutive_count = 0U;
return 0;
}
if (id_class != s_result.candidate_class)
{
s_result.candidate_class = id_class;
s_result.consecutive_count = 1U;
}
else if (s_result.consecutive_count < APP_NEAI_STATE_CONFIRM_COUNT)
{
s_result.consecutive_count++;
}
if (s_result.consecutive_count < APP_NEAI_STATE_CONFIRM_COUNT)
{
return 0;
}
s_result.confirmed_class = id_class;
s_result.candidate_class = -1;
s_result.consecutive_count = 0U;
return 1;
}
void app_neai_loop(void)
{
drv_lis3dh_data_t sample;
uint32_t now;
uint32_t started_ms;
uint32_t duration_ms;
int id_class = -1;
float probabilities[NEAI_NUMBER_OF_CLASSES];
if (s_initialized == 0U)
{
return;
}
if (!app_neai_tx_service())
{
app_neai_fail(APP_NEAI_UART_ERROR);
return;
}
if ((s_result.state != APP_NEAI_RUNNING) || !app_neai_motor_service())
{
return;
}
now = HAL_GetTick();
if ((uint32_t)(now - s_result.last_sample_ms) >= APP_NEAI_SENSOR_TIMEOUT_MS)
{
app_neai_fail(APP_NEAI_SENSOR_TIMEOUT);
return;
}
s_result.sensor_status = drv_lis3dh_read(&s_sensor, &sample);
if (s_result.sensor_status == DRV_LIS3DH_NO_DATA)
{
return;
}
if (s_result.sensor_status != DRV_LIS3DH_OK)
{
app_neai_fail(APP_NEAI_SENSOR_ERROR);
return;
}
now = HAL_GetTick();
if (s_result.sample_count != 0U)
{
uint32_t gap = now - s_result.last_sample_ms;
if (gap > s_result.max_sample_gap_ms)
{
s_result.max_sample_gap_ms = gap;
}
if (gap > ((1000U + APP_NEAI_SAMPLE_RATE_HZ - 1U) / APP_NEAI_SAMPLE_RATE_HZ + 1U))
{
s_result.delayed_sample_count++;
}
}
s_result.last_sample_ms = now;
s_result.sample_count++;
s_input[0] = (float)sample.x_mg;
s_input[1] = (float)sample.y_mg;
s_input[2] = (float)sample.z_mg;
s_random ^= (uint32_t)sample.x_mg ^ ((uint32_t)sample.y_mg << 11) ^
((uint32_t)sample.z_mg << 22) ^ now;
if (s_random == 0U)
{
s_random = 1U;
}
started_ms = HAL_GetTick();
s_result.model_status = neai_classification(s_input, probabilities, &id_class);
duration_ms = HAL_GetTick() - started_ms;
if (duration_ms > s_result.max_inference_ms)
{
s_result.max_inference_ms = duration_ms;
}
if (s_result.model_status != NEAI_OK)
{
app_neai_fail(APP_NEAI_MODEL_ERROR);
return;
}
if ((id_class < 0) || (id_class >= NEAI_NUMBER_OF_CLASSES) ||
!isfinite(probabilities[0]) || !isfinite(probabilities[1]) ||
(probabilities[0] < 0.0f) || (probabilities[0] > 1.0f) ||
(probabilities[1] < 0.0f) || (probabilities[1] > 1.0f))
{
s_result.model_status = NEAI_INVALID_PARAM;
app_neai_fail(APP_NEAI_MODEL_ERROR);
return;
}
s_result.id_class = id_class;
memcpy(s_result.probabilities, probabilities, sizeof(probabilities));
s_result.inference_count++;
/* A timer fault/stop may have happened during I2C or classification. */
if (!app_neai_motor_service())
{
return;
}
now = HAL_GetTick();
if (app_neai_confirm_state(id_class))
{
if (!app_neai_log_state(now, s_result.confirmed_class))
{
app_neai_fail(APP_NEAI_UART_ERROR);
return;
}
}
if (!app_neai_tx_service())
{
app_neai_fail(APP_NEAI_UART_ERROR);
}
}
void HAL_TIM_UpdateCallback(hal_tim_handle_t *timer)
{
drv_gear_stepper_status_t status;
if (timer != mx_tim1_gethandle())
{
return;
}
if ((s_motor_on != 0U) &&
((uint32_t)(HAL_GetTick() - s_motor_started_ms) >= APP_NEAI_MOTOR_RUN_MS))
{
/* Stop independently of foreground inference/I2C/UART latency. This is
* evaluated every normal step (3 ms); no formatting or I2C in IRQ. */
status = drv_gear_stepper_stop(&s_motor, GEAR_STEPPER_RELEASE);
s_motor_stopped_ms = HAL_GetTick();
s_motor_on = 0U;
s_stop_pending = 1U;
}
else
{
status = drv_gear_stepper_on_timer_update(&s_motor);
}
if ((status != DRV_GEAR_STEPPER_OK) && (status != DRV_GEAR_STEPPER_RUNNING))
{
s_irq_error = status;
}
}
const app_neai_result_t *app_neai_get_result(void)
{
return &s_result;
}最终效果

实际测试发现,也就采集数据的方式一样的场景,才能正常区分转动和静止,也就是说,实际工程落地的路会更长,采集的数据量会更大,要穷举所有可能的原因(或者把采集到的数据转换成这种坐标后再去喂给大模型)。
我要赚赏金
