【let'sdo|2026年第3期】成果贴1:基于STNanoEdgeAIStudio的端侧电机状态识别项目——模型训练及测试 https://forum.eepw.com.cn/thread/402328/1 | 2026-11-14 16:22:39 |
【let'sdo|2026年第3期】过程贴:按键启停步进电机 https://forum.eepw.com.cn/thread/402326/1 | 2026-11-14 15:47:48 |
【let'sdo|2026年第3期】过程贴:CubeMX2+CubeIDE2按键中断点灯(迎接变化) https://forum.eepw.com.cn/thread/402317/1 | 2026-11-13 20:05:40 |
【let'sdo|2026年第3期】过程贴:搞定3轴加速度计读取 https://forum.eepw.com.cn/thread/402196/1 | 2026-11-08 08:08:48 |
【let'sdo|2026年第3期】过程贴:使用全新的CubeMX2+CubeIDE2点亮NUCLEO-C562RE https://forum.eepw.com.cn/thread/402048/1 | 2026-10-25 15:40:06 |
【let'sdo|2026年第3期】开箱贴:为什么我要选NUCLEO-C562RE https://forum.eepw.com.cn/thread/402045/1 | 2026-10-25 14:47:26 |
上一篇成果贴1,我们训练并测试了模型。本期将模型下载下来,并安装到MCU,让他脱离电脑,真正独立运行。
在第4步选择好模型后,STNanoEdgeAIStudio 的第5步,就是下载模型。
可以看到右侧的代码示例区
/* =============
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
* 演示:集成到主程序中的 NanoEdge AI 分类流程
*
* @note This program must be completed and customized by the user
* @note 用户需要自行完善和修改这份代码
**************************************************************************
*/
/* Includes --------------------------------------------------------------------*/
#include "NanoEdgeAI.h" // NanoEdge AI 库头文件,ST 轻量端侧AI库
/* Private define --------------------------------------------------------------*/
// 私有宏定义区域,用户自行添加
/* Private variables defined by user -------------------------------------------*/
// 输入信号缓冲区:样本长度 × 轴数量(如三轴加速度就是3轴)
// 存放传感器采集的原始AI输入数据
float input_signal[NEAI_INPUT_SIGNAL_LENGTH * NEAI_INPUT_AXIS_NUMBER];
// 各类别的概率输出缓冲区,长度等于训练时设置的分类类别总数
float probabilities[NEAI_NUMBER_OF_CLASSES];
/* Private function prototypes defined by user ---------------------------------*/
/**
* @brief Collect data process
* @brief 数据采集函数
*
* This function is defined by user, depends on applications and sensors
* 该函数由用户实现,逻辑随项目、传感器不同而变化
*
* @param input_signal: [in, out] buffer of sample values
* @param input_signal: [输入输出] 采样数据缓冲区
* @retval None
* @retval 无返回值
* @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)
* @note 如果是3轴传感器(NEAI_INPUT_AXIS_NUMBER=3),数据排布格式:
* [x0 y0 z0 x1 y1 z1 ... xn yn zn]
* xi yi zi 代表第i个采样点的三轴数据,总采样点数为 NEAI_INPUT_SIGNAL_LENGTH
*/
void fill_buffer(float *input_signal)
{
/* USER BEGIN */
// ========= 用户代码区:在这里读取传感器,填充 input_signal 缓冲区 =========
// 示例:读取加速度计、陀螺仪等,按上面规定的顺序放入buffer
/* USER END */
}
/* -----------------------------------------------------------------------------*/
int main(void)
{
/* Initialization ------------------------------------------------------------*/
// 初始化 NanoEdge AI 分类模型
enum neai_state error_code = neai_classification_init();
if (error_code != NEAI_OK) {
/* Check the returned error code (cf NanoEdgeAI.h). */
/* 初始化失败!根据返回错误码排查问题,错误定义在 NanoEdgeAI.h */
}
/* Classification ------------------------------------------------------------*/
int id_class = 0; // 模型输出:识别到的类别ID(分类结果编号)
while (1)
{
fill_buffer(input_signal); // 第一步:采集传感器数据,填充输入buffer
neai_classification(input_signal, probabilities, &id_class); // 第二步:执行AI推理分类
/* 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.).
*/
// ========= 用户业务逻辑区:根据分类结果 id_class 做动作 =========
// probabilities[] 数组保存每一类的置信概率
// 可用 neai_get_class_name(id_class) 获取类别名字符串
// 例:LED闪烁、串口打印结果、触发报警、电机控制等
/* USER END */
}
}相当于一个代码框架,我们按这个框架填入具体功能,加上执行动作。
核心代码就3步
第1步:将数据(3轴加速度)填充入buffer,需要注意,这里的数据格式要跟训练时保持一致
如32个3轴加速度数据为一帧。
fill_buffer(input_signal);
第2步:运行模型
neai_classification(input_signal, probabilities, &id_class);
第3步:根据模型识别结果,执行对应动作:如识别到过载,LED灯亮,就完成了端侧AI的部署。
分类的编号和数量跟模拟时的编号保持一致
if(probabilities[1]>0.8) 分类1的概率>0.8
{
HAL_GPIO_TogglePin(PA5_PORT, PA5_PIN);
}下载下的模型是一个zip文件


实际上我们只需要用到libneai.a 和NanoEdgeAI.h
把该目录(libneai_project-2026-11-13-21-12_6)解压至main.c同一个文件夹。

以下是CubeIDE2(基于CMAKE)的方法:
在程序的CMakeLists.txt include文件夹添加上述目录,让程序可以识别NanoEdgeAI.h 和。
同时添加链接 neai 模型库,让程序可以识别neai 指令
# file-format: 1.0.0
cmake_minimum_required(VERSION 3.30)
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
set(CMAKE_C_COMPILER_FORCED TRUE)
set(CMAKE_CXX_COMPILER_FORCED TRUE)
include(cmake/target.cmake)
include(cmake/${TOOLCHAIN_FILE})
project(STM32C5_IMU)
add_executable(STM32C5_IMU)
include(cmake/files.cmake)
include(cmake/flags.cmake)
include(cmake/components.cmake)
# Stubs to be able to add compiler flag overrides, e.g. -Os
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
# Add additional source files here
)
# 告诉链接器:去哪里找NanoEdgeAI.h
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
${CMAKE_SOURCE_DIR}/libneai_project-2026-11-13-21-12_6
)
target_compile_definitions(${CMAKE_PROJECT_NAME} PUBLIC
# Add additional defined symbols here
)
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC
# Add additional compiler flags here
)
target_link_options(${CMAKE_PROJECT_NAME} PUBLIC
# Add additional linker flags here
)
# 告诉链接器:去哪里找libneai.a
target_link_directories(${CMAKE_PROJECT_NAME} PUBLIC
${CMAKE_SOURCE_DIR}/libneai_project-2026-11-13-21-12_6
)
# 链接 neai 模型库 + 数学库
target_link_libraries(${CMAKE_PROJECT_NAME}
neai
m
)如果是CubeIDE1
选择项目,右键点击属性。然后在属性框下,C/C++Build->Settings->MCU/MPU GCC Linker的Libraries下,在 Libraries 部分添加 “neai”
在 Library search path 部分添加 libneai.a 路径。单击 Apply and Close (应用并关闭)


main.c函数全文如下:
#include "main.h"
#include "string.h"
#include <stdint.h>
#include <stdio.h>
#include <math.h>
#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];
char msg1[] = "Hello World!\r\n";
char msg2[] = "\r\n";
char msg3[20];
char msg4[40];
// 扫描得到7bit=0x19,8bit地址0x32
#define LIS3DH_ADDR_8BIT (0x19 << 1)
#define LIS3DH_REG_WHO_AM_I 0x0F
#define LIS3DH_REG_CTRL1 0x20
#define LIS3DH_REG_CTRL4 0x23
#define LIS3DH_REG_OUT_XL 0x28
// 全局偏移变量
int16_t offset_x = 0;
int16_t offset_y = 0;
int16_t offset_z = 0;
uint8_t cal_done = 0;
#define CAL_SAMPLE_CNT 32 // 校准采样点数
volatile uint8_t ExtiDetected = 0; //USER按键中断检测
/**
* @brief 读取LIS3DH WHO_AM_I
*/
hal_status_t LIS3DH_ReadID(hal_i2c_handle_t *hi2c, uint8_t *id)
{
hal_status_t ret;
uint8_t reg = LIS3DH_REG_WHO_AM_I;
ret = HAL_I2C_MASTER_Transmit(hi2c, LIS3DH_ADDR_8BIT, ®, 1, 100);
if(ret != HAL_OK) return ret;
ret = HAL_I2C_MASTER_Receive(hi2c, LIS3DH_ADDR_8BIT, id, 1, 100);
return ret;
}
/**
* @brief LIS3DH初始化:100Hz,XYZ使能,±2g高分辨率
*/
hal_status_t LIS3DH_Init(hal_i2c_handle_t *hi2c)
{
hal_status_t ret;
uint8_t buf[2];
// CTRL1: ODR=100Hz, X Y Z enable --> 0x57
buf[0] = LIS3DH_REG_CTRL1;
buf[1] = 0x57;
ret = HAL_I2C_MASTER_Transmit(hi2c, LIS3DH_ADDR_8BIT, buf, 2, 100);
if(ret != HAL_OK) return ret;
// CTRL4: ±2g, HR=1 --> 0x08
buf[0] = LIS3DH_REG_CTRL4;
buf[1] = 0x08;
ret = HAL_I2C_MASTER_Transmit(hi2c, LIS3DH_ADDR_8BIT, buf, 2, 100);
return ret;
}
/**
* @brief 连续读取XYZ原始数据 6字节
* @param raw_buf[6] XL,XH,YL,YH,ZL,ZH
*/
hal_status_t LIS3DH_ReadRawXYZ(hal_i2c_handle_t *hi2c, uint8_t *raw_buf)
{
hal_status_t ret;
// bit7=1 自动增量
uint8_t reg = 0x80 | LIS3DH_REG_OUT_XL;
ret = HAL_I2C_MASTER_Transmit(hi2c, LIS3DH_ADDR_8BIT, ®, 1, 100);
if(ret != HAL_OK) return ret;
ret = HAL_I2C_MASTER_Receive(hi2c, LIS3DH_ADDR_8BIT, raw_buf, 6, 100);
return ret;
}
/**
* @brief 打印ID
*/
void LIS3DH_PrintID_UART2(void)
{
hal_status_t ret;
uint8_t chip_id = 0;
char msg[64];
ret = LIS3DH_ReadID(mx_i2c1_i2c_gethandle(), &chip_id);
if(ret == HAL_OK)
{
snprintf(msg, sizeof(msg), "LIS3DH ID: 0x%02X, expect 0x33\r\n", chip_id);
}
else
{
snprintf(msg, sizeof(msg), "LIS3DH I2C read FAIL");
}
HAL_UART_Transmit(mx_usart2_uart_gethandle(), (uint8_t *)msg, strlen(msg), 100);
}
/**
* @brief 读取XYZ并串口打印(mg单位)
*/
void LIS3DH_PrintXYZ_UART2(void)
{
hal_status_t ret;
uint8_t raw[6];
int16_t x_raw, y_raw, z_raw;
int16_t x, y, z;
char msg[128];
ret = LIS3DH_ReadRawXYZ(mx_i2c1_i2c_gethandle(), raw);
if(ret != HAL_OK)
{
snprintf(msg, sizeof(msg), "LIS3DH XYZ read error\r\n");
}
else
{
x_raw = (int16_t)((raw[1] << 8) | raw[0]);
y_raw = (int16_t)((raw[3] << 8) | raw[2]);
z_raw = (int16_t)((raw[5] << 8) | raw[4]);
// HR高分辨率模式:12bit,左对齐,必须右移4位!
x = x_raw >> 4;
y = y_raw >> 4;
z = z_raw >> 4;
// ±2g:1 LSB = 1mg
snprintf(msg, sizeof(msg), "ACC: X=%d mg, Y=%d mg, Z=%d mg\r\n", x, y, z);
}
HAL_UART_Transmit(mx_usart2_uart_gethandle(), (uint8_t *)msg, strlen(msg), 100);
}
/**
* @brief 零点校准函数,调用前保持传感器水平平放
*/
void LIS3DH_CalibrateZero(void)
{
uint8_t raw[6];
int16_t x_raw, y_raw, z_raw;
int32_t sum_x = 0, sum_y = 0, sum_z = 0;
for(uint16_t i = 0; i < CAL_SAMPLE_CNT; i++)
{
// 等待读取成功
while(LIS3DH_ReadRawXYZ(mx_i2c1_i2c_gethandle(), raw) != HAL_OK);
x_raw = (int16_t)((raw[1] << 8) | raw[0]);
y_raw = (int16_t)((raw[3] << 8) | raw[2]);
z_raw = (int16_t)((raw[5] << 8) | raw[4]);
int16_t x = x_raw >> 4;
int16_t y = y_raw >> 4;
int16_t z = z_raw >> 4;
sum_x += x;
sum_y += y;
sum_z += z;
HAL_Delay(20);
}
// 求平均偏移;Z理想值是1000mg
offset_x = sum_x / CAL_SAMPLE_CNT;
offset_y = sum_y / CAL_SAMPLE_CNT;
offset_z = sum_z / CAL_SAMPLE_CNT - 1000;
cal_done = 1;
}
/**
* @brief 读取并打印【校准后】XYZ,单位 mg
*/
void LIS3DH_PrintXYZ_Cal(void)
{
hal_status_t ret;
uint8_t raw[6];
int16_t x_raw, y_raw, z_raw;
int16_t x, y, z;
char msg[128];
ret = LIS3DH_ReadRawXYZ(mx_i2c1_i2c_gethandle(), raw);
if(ret != HAL_OK)
{
snprintf(msg, sizeof(msg), "LIS3DH XYZ read error, ret=%u\r\n", ret);
}
else
{
x_raw = (int16_t)((raw[1] << 8) | raw[0]);
y_raw = (int16_t)((raw[3] << 8) | raw[2]);
z_raw = (int16_t)((raw[5] << 8) | raw[4]);
// 减去偏移,得到校准值
x = (x_raw >> 4) - offset_x;
y = (y_raw >> 4) - offset_y;
z = (z_raw >> 4) - offset_z;
//snprintf(msg, sizeof(msg), "ACC: X=%d mg, Y=%d mg, Z=%d mg\r\n", x, y, z);
snprintf(msg, sizeof(msg), "%d,%d,%d,", x, y, z);
}
HAL_UART_Transmit(mx_usart2_uart_gethandle(), (uint8_t *)msg, strlen(msg), 100);
}
// 全局变量,放在函数外面(全局区)
#define MSG_BUF_LEN 96 //32个*3轴
float XYZ32[MSG_BUF_LEN]; //输入是 float 数组,给 AI 直接用(NanoEdge 模型输入是 float)
void LIS3DH_PrintXYZ_32XYZ(void)
{
hal_status_t ret;
uint8_t raw[6];
int16_t x_raw, y_raw, z_raw;
int16_t x, y, z;
//int pos = 0; // 记录当前写入缓冲区的位置
const int sample_cnt = 32; // 采集32组
// 清空缓冲区起始位置
//pos = 0;
for(int i = 0; i < sample_cnt; i++)
{
ret = LIS3DH_ReadRawXYZ(mx_i2c1_i2c_gethandle(), raw);
if(ret != HAL_OK)
{
// 读取失败,写入错误标记
// pos += snprintf(XYZ32 + pos, MSG_BUF_LEN - pos, "ERR,");
}
else
{
x_raw = (int16_t)((raw[1] << 8) | raw[0]);
y_raw = (int16_t)((raw[3] << 8) | raw[2]);
z_raw = (int16_t)((raw[5] << 8) | raw[4]);
// 减去偏移,得到校准值
x = (x_raw >> 4) - offset_x;
y = (y_raw >> 4) - offset_y;
z = (z_raw >> 4) - offset_z;
//输入是 float 数组,给 AI 直接用(NanoEdge 模型输入是 float)
XYZ32[i*3 + 0] = (float)x;
XYZ32[i*3 + 1] = (float)y;
XYZ32[i*3 + 2] = (float)z;
// 拼接:x,y,z,
//pos += snprintf(XYZ32 + pos, MSG_BUF_LEN - pos, "%d,%d,%d,", x, y, z);
}
// 可选:如果需要采样间隔,在这里加延时,控制采样速率
// HAL_Delay(1);
}
// 最后一次性发送全部32组数据
//HAL_UART_Transmit(mx_usart2_uart_gethandle(), (uint8_t *)XYZ32, pos, 100);
}
int main(void)
{
if (mx_system_init() != SYSTEM_OK)
{
return (-1);
}
else
{
LIS3DH_PrintID_UART2();
LIS3DH_Init(mx_i2c1_i2c_gethandle());
// 上电校准!【板子平放,不要晃动!】
LIS3DH_CalibrateZero();
// 初始化 NanoEdge AI 分类模型
enum neai_state error_code = neai_classification_init();
if (error_code != NEAI_OK) {
/* Check the returned error code (cf NanoEdgeAI.h). */
/* 初始化失败!根据返回错误码排查问题,错误定义在 NanoEdgeAI.h */
snprintf(msg3, 20, "error_code is %d", error_code);
HAL_UART_Transmit(mx_usart2_uart_gethandle(), (uint8_t *)msg3, 20, 100);
}
/* Classification ------------------------------------------------------------*/
int id_class = 0; // 模型输出:识别到的类别ID(分类结果编号)
while (1) {
//HAL_GPIO_TogglePin(PA5_PORT, PA5_PIN);
//HAL_UART_Transmit(mx_usart2_uart_gethandle(), msg1, strlen(msg1), 100);
/* for(int i=0;i<32;i++) //每个数据块包含32帧加速度数据
{
if(i<31)
LIS3DH_PrintXYZ_Cal(); // 读取并发送一个3轴加速度数据
else //最后一帧回车
HAL_UART_Transmit(mx_usart2_uart_gethandle(), (uint8_t *)msg2, strlen(msg2), 100);
}*/
LIS3DH_PrintXYZ_32XYZ(); //第一步:采集传感器数据,填充1帧(32个*3轴=96)数据输入buffer
//fill_buffer(input_signal); // 第一步:采集传感器数据,填充输入buffer
neai_classification(XYZ32, probabilities, &id_class); // 第二步:执行AI推理分类
snprintf(msg4, sizeof(msg4),"静止: %d%% 运行: %d%% 过载: %d%% \r\n",
(int)(probabilities[0] * 100),
(int)(probabilities[1] * 100),
(int)(probabilities[2] * 100));
HAL_UART_Transmit(mx_usart2_uart_gethandle(), (uint8_t *)msg4, strlen(msg4), 100);
if(ExtiDetected == 1)
{
HAL_GPIO_WritePin(PA4_PORT, PA4_PIN, HAL_GPIO_PIN_SET);
HAL_GPIO_WritePin(PB0_PORT, PB0_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PC5_PORT, PC5_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PC0_PORT, PC0_PIN, HAL_GPIO_PIN_RESET);
HAL_Delay(1);
HAL_GPIO_WritePin(PA4_PORT, PA4_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PB0_PORT, PB0_PIN, HAL_GPIO_PIN_SET);
HAL_GPIO_WritePin(PC5_PORT, PC5_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PC0_PORT, PC0_PIN, HAL_GPIO_PIN_RESET);
HAL_Delay(1);
HAL_GPIO_WritePin(PA4_PORT, PA4_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PB0_PORT, PB0_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PC5_PORT, PC5_PIN, HAL_GPIO_PIN_SET);
HAL_GPIO_WritePin(PC0_PORT, PC0_PIN, HAL_GPIO_PIN_RESET);
HAL_Delay(1);
HAL_GPIO_WritePin(PA4_PORT, PA4_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PB0_PORT, PB0_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PC5_PORT, PC5_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PC0_PORT, PC0_PIN, HAL_GPIO_PIN_SET);
HAL_Delay(1);
}
else
{
HAL_GPIO_WritePin(PA4_PORT, PA4_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PB0_PORT, PB0_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PC5_PORT, PC5_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(PC0_PORT, PC0_PIN, HAL_GPIO_PIN_RESET);
HAL_Delay(5);
}
}
}
}
//中断回调函数
void HAL_EXTI_TriggerCallback(hal_exti_handle_t *hexti, hal_exti_trigger_t trigger)
{
/* Prevent unused argument compilation warning */
STM32_UNUSED(trigger);
if (hexti == mx_gpio_default_exti13_gethandle())
{
//ExtiDetected = 1;
ExtiDetected ^= 1;
}
}核心代码就3步,不过踩了很多坑。
第一步:采集传感器数据,填充1帧(32个*3轴=96)数据输入buffer
核心点是,要采集32个*3轴,共计96个数据,作为一帧,在下一步喂给AI,让他识别并输出结果。
这里需要注意,投喂的数据帧必须是float数据类型,后面会再提到,所以在本函数中需要做一个转换,
将整数转换为float。
//输入是 float 数组,给 AI 直接用(NanoEdge 模型输入是 float) XYZ32[i*3 + 0] = (float)x; XYZ32[i*3 + 1] = (float)y; XYZ32[i*3 + 2] = (float)z;
LIS3DH_PrintXYZ_32XYZ(); //第一步:采集传感器数据,填充1帧(32个*3轴=96)数据输入buffer
// 全局变量,放在函数外面(全局区)
#define MSG_BUF_LEN 96 //32个*3轴
float XYZ32[MSG_BUF_LEN]; //输入是 float 数组,给 AI 直接用(NanoEdge 模型输入是 float)
void LIS3DH_PrintXYZ_32XYZ(void)
{
hal_status_t ret;
uint8_t raw[6];
int16_t x_raw, y_raw, z_raw;
int16_t x, y, z;
//int pos = 0; // 记录当前写入缓冲区的位置
const int sample_cnt = 32; // 采集32组
// 清空缓冲区起始位置
//pos = 0;
for(int i = 0; i < sample_cnt; i++)
{
ret = LIS3DH_ReadRawXYZ(mx_i2c1_i2c_gethandle(), raw);
if(ret != HAL_OK)
{
// 读取失败,写入错误标记
// pos += snprintf(XYZ32 + pos, MSG_BUF_LEN - pos, "ERR,");
}
else
{
x_raw = (int16_t)((raw[1] << 8) | raw[0]);
y_raw = (int16_t)((raw[3] << 8) | raw[2]);
z_raw = (int16_t)((raw[5] << 8) | raw[4]);
// 减去偏移,得到校准值
x = (x_raw >> 4) - offset_x;
y = (y_raw >> 4) - offset_y;
z = (z_raw >> 4) - offset_z;
//输入是 float 数组,给 AI 直接用(NanoEdge 模型输入是 float)
XYZ32[i*3 + 0] = (float)x;
XYZ32[i*3 + 1] = (float)y;
XYZ32[i*3 + 2] = (float)z;
// 拼接:x,y,z,
//pos += snprintf(XYZ32 + pos, MSG_BUF_LEN - pos, "%d,%d,%d,", x, y, z);
}
// 可选:如果需要采样间隔,在这里加延时,控制采样速率
// HAL_Delay(1);
}第二步:将采集的一帧(32个*3轴=96)传感器数据,投喂给AI
/**
* @brief Perform classification on a new input sample by returning the probability for each
* class and the predicted class ID.
* @param in [in] Pointer to the input signal array
* (size NEAI_INPUT_SIGNAL_LENGTH * NEAI_INPUT_AXIS_NUMBER).
* @param probabilities [out] Pointer to the output probabilities array
* (size NEAI_NUMBER_OF_CLASSES).
* @param id_class [out] Pointer to the predicted class ID
* (integer in range [0, NEAI_NUMBER_OF_CLASSES - 1]).
* @return NEAI_OK on success.
* Error code otherwise.
*/
enum neai_state neai_classification(float *in, float *probabilities, int *id_class);
这里可以看到输入数据类型是float,这是ST封装定义好的,无法更改,因此我们在上一步需要转换,
经过这一步,就可以probabilities数组,也就是每个对象的可能性,以及对应对象标签号。
neai_classification(XYZ32, probabilities, &id_class); // 第二步:执行AI推理分类
第3步,打印状态标签,以及对应可能性。
probabilities数组本身也是浮点数,为了避免浮点打印出错,先转换为整数,再加个百分号。
这里的状态标签顺序,按个人经验ST NanoEdgeAI训练页面的顺序一致

snprintf(msg4, sizeof(msg4),"静止: %d%% 运行: %d%% 过载: %d%% \r\n",
(int)(probabilities[0] * 100),
(int)(probabilities[1] * 100),
(int)(probabilities[2] * 100));
HAL_UART_Transmit(mx_usart2_uart_gethandle(), (uint8_t *)msg4, strlen(msg4), 100);
最终的运行结果如下:
电机静止时,识别率非常高!

电机运行时,识别率较差,容易跟过载分不开。

电机过载时(用手捏住转轴),识别率同样很高!

可能重新进行上述过程多优化应该能展现出更好效果!
视频如下:
我要赚赏金
