这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【let'sdo2026年第3期】任务4:NanoEdgeAIStudio训练和

共1条 1/1 1 跳转至页

【let'sdo2026年第3期】任务4:NanoEdgeAIStudio训练和部署电机状态模型

高工
2026-11-11 21:30:07     打赏

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

加速度传感器和MCU的安装如下所示

motor_hardware.png

1、加速度数据采集工程

结合之前的加速采集工程以及电机串口控制工程,整合两个工程的代码,通过串口控制电机转动,采集加速度传感器的数据,用于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工程用于导入自定义训练模型

Create_AI_Classify_Project.png

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

Project_Settings.png

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

log_signal_from_usb.png

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

data_collect.png

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

data_biaoqian1.png

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

AI_Model_Traning.png

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

Validation_Deployment.png

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

Get_library.png

下载生成的库文件用于后续的AI模型部署

3、电机状态识别

解压得到的压缩包,得到其中的NanoEdgeAI.h和libneai.a文件,添加到工程的顶层文件夹中,CMakeLists.txt文件夹中添加源代码和库文件的设置

edit_Project_Cmake.png

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

Cmake_error.png

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

Cmake_Public.png

在程序中使用训练的模型运行电机状态检测的代码如下,调用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 */

编译并烧录代码到开发板。运行电机状态识别代码

motor_regonize.png

电机状态的识别效果见视频所示

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





关键词: Let do 3         

共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]