这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 电机运行状态检测

共1条 1/1 1 跳转至页

电机运行状态检测

菜鸟
2026-10-22 17:44:59     打赏

本项目主要使用 STM32C562RE、LIS3DH 三轴加速度传感器和步进电机,通过检测电机运行时产生的振动,实现不同运行状态的识别。使用 STM32C562RE 开发板搭配 NanoEdge AI Studio 5.2.0,实现一个基于三轴加速度数据的电机运行状态智能识别系统。

一、项目实现思路

首先通过 STM32CubeMX2 完成开发板的外设配置,包括 GPIO、I²C、USART 和定时器等。

利用 I²C 接口采集 LIS3DH 的 X、Y、Z 三轴加速度数据,并通过串口发送至 VOFA+ 上位机,实时观察电机运行过程中的振动波形。

在此基础上,分别采集两种电机状态下的加速度数据:

  1. 停止状态(Stopped):电机停止转动,整体振动较小。

    正常运行(Running):电机持续转动,产生相对稳定的振动。

效果演示


Motor_Demo_Smooth.gif

二、AI 模型训练

使用 NanoEdge AI Studio 5.2.0 创建 Classification 分类项目。

image.png

将 VOFA+ 采集的数据进行整理,每组样本包含64个采样时刻的三轴加速度,共128数据,随后将不同状态的数据导入 NanoEdge AI Studio.

image.png

开始训练

image.png

使用 Benchmark 功能自动搜索合适的机器学习算法及预处理组合。成功生成面向 STM32C562RE 的 GCC 静态库 libneai.a。

image.png

三、嵌入式部署

接下来计划将生成的 AI 模型库集成到 STM32 工程中,实现:

  • 实时采集电机三轴加速度;

  • 缓存连续64组加速度数据;

  • 调用 NanoEdge AI 分类接口进行推理;

  • 通过串口输出停止、正常运行和异常抖动的识别结果;

  • 进一步结合 LED、蜂鸣器实现异常状态报警。


    main.c文件调用生成的NaonEdge.h文件


  • /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "stepmotor.h"
    #include "lis3dh_reg.h"
    #include "NanoEdgeAI.h"
    #include "oled.h"
    #include <stdio.h>
    #include <math.h>
    
    /* Private typedef -----------------------------------------------------------*/
    /* Private define ------------------------------------------------------------*/
    /* LIS3DH detected by the I2C1 bus scan at 0x32/0x33 (8-bit format),
       i.e. 7-bit address 0x19 -> SA0 is tied HIGH on this module.
       The HAL I2C API takes the 8-bit form (7-bit address << 1), so clear the
       R/W bit of the ST driver constant (LIS3DH_I2C_ADD_H = 0x33 -> 0x32). */
    #define LIS3DH_I2C_ADDR   (LIS3DH_I2C_ADD_H & 0xFEU)
    #define I2C_TRANSFER_TIMEOUT_MS  20U
    #define UART_TRANSFER_TIMEOUT_MS 10U
    #define SENSOR_ERROR_LIMIT       3U
    
    /* Private macro -------------------------------------------------------------*/
    /* Private variables ---------------------------------------------------------*/
    static stmdev_ctx_t lis3dh_ctx;
    static uint32_t lis3dh_i2c_error_count = 0U;
    static hal_status_t lis3dh_last_i2c_status = HAL_OK;
    static uint32_t lis3dh_last_i2c_error = HAL_I2C_ERROR_NONE;
    
    /* Private functions prototype -----------------------------------------------*/
    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:  Retarget printf to USART2 (called by newlib _write()).
      */
    int __io_putchar(int ch)
    {
      hal_uart_handle_t *huart = mx_usart2_uart_gethandle();
      uint8_t data = (uint8_t)ch;
    
      /* Keep printf from becoming another long blocking path when the UART state
         is abnormal.  Normal 115200-baud output completes much sooner. */
      (void)HAL_UART_Transmit(huart, &data, 1U, UART_TRANSFER_TIMEOUT_MS);
    
      return ch;
    }
    
    /**
      * brief:  I2C write callback for the LIS3DH driver.
      */
    static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *buf, uint16_t len)
    {
      hal_i2c_handle_t *hi2c = (hal_i2c_handle_t *)handle;
    
      reg |= 0x80U; /* LIS3DH auto-increment flag (needed for multi-byte transfer) */
    
      const hal_status_t status = HAL_I2C_MASTER_MemWrite(hi2c, LIS3DH_I2C_ADDR, reg,
                                                           HAL_I2C_MEM_ADDR_8BIT,
                                                           (const uint8_t *)buf, len,
                                                           I2C_TRANSFER_TIMEOUT_MS);
      if (status != HAL_OK)
      {
        lis3dh_i2c_error_count++;
        lis3dh_last_i2c_status = status;
        lis3dh_last_i2c_error = hi2c->last_error_codes;
        return -1;
      }
    
      return 0;
    }
    
    /**
      * brief:  I2C read callback for the LIS3DH driver.
      */
    static int32_t platform_read(void *handle, uint8_t reg, uint8_t *buf, uint16_t len)
    {
      hal_i2c_handle_t *hi2c = (hal_i2c_handle_t *)handle;
      hal_status_t status;
    
      reg |= 0x80U; /* Multi-byte read with address auto-increment (needed for 6-byte accel read) */
    
      status = HAL_I2C_MASTER_MemRead(hi2c, LIS3DH_I2C_ADDR, reg, HAL_I2C_MEM_ADDR_8BIT,
                                      buf, len, I2C_TRANSFER_TIMEOUT_MS);
      if (status != HAL_OK)
      {
        /* Do not print here: this callback runs inside the sensor transaction,
           and printf itself is a blocking UART transfer. */
        lis3dh_i2c_error_count++;
        lis3dh_last_i2c_status = status;
        lis3dh_last_i2c_error = hi2c->last_error_codes;
        return -1;
      }
    
      return 0;
    }
    
    /**
      * brief:  Delay callback for the LIS3DH driver.
      */
    static void platform_delay(uint32_t millisec)
    {
      HAL_Delay(millisec);
    }
    
    
    /**
      * brief:  The application entry point.
      * retval: none but we specify int to comply with C99 standard
      */
    /* ===== NanoEdge AI integration =====
     * Input: 64 consecutive xyz samples (units: g), interleaved XYZXYZ... .
     * Class 0: Stopped; Class 1: Running (confirmed with metadata.json).
     * VOFA+ FireWater output: class_id, P_stopped, P_running.
     * This demo switches the stepper between running/stopped every ~12 s.
     */
    #define MOTOR_DEMO_AUTOSWITCH    1U /* 0: keep motor running, 1: 12s run / 12s stop */
    #define MOTOR_DEMO_SAMPLES      1200U /* approx. 12 s at LIS3DH ODR 100 Hz */
    #define NEAI_OUTPUT_VOFA        1U /* 1: numeric CSV for VOFA+; 0: human-readable text */
    
    #if NEAI_INPUT_SIGNAL_LENGTH != 64 || NEAI_INPUT_AXIS_NUMBER != 3
    #error "Unexpected NanoEdge AI input dimensions."
    #endif
    
    static float ai_signal[NEAI_INPUT_SIGNAL_LENGTH * NEAI_INPUT_AXIS_NUMBER];
    static float ai_probabilities[NEAI_NUMBER_OF_CLASSES];
    static uint32_t ai_sample_count = 0U;
    
    static void ai_push_sample(float ax_g, float ay_g, float az_g)
    {
        uint32_t idx = ai_sample_count * NEAI_INPUT_AXIS_NUMBER;
        ai_signal[idx]     = ax_g;
        ai_signal[idx + 1] = ay_g;
        ai_signal[idx + 2] = az_g;
        ai_sample_count++;
    
        if (ai_sample_count == NEAI_INPUT_SIGNAL_LENGTH)
        {
            int predicted_id = -1;
            enum neai_state rc = neai_classification(ai_signal, ai_probabilities,
                                                     &predicted_id);
            if (rc == NEAI_OK && predicted_id >= 0 &&
                predicted_id < NEAI_NUMBER_OF_CLASSES)
            {
                if (!isfinite(ai_probabilities[0]) || !isfinite(ai_probabilities[1]) ||
                    ai_probabilities[0] < 0.0f || ai_probabilities[0] > 1.0f ||
                    ai_probabilities[1] < 0.0f || ai_probabilities[1] > 1.0f)
                {
                    printf("NEAI returned invalid probabilities\r\n");
                    OLED_SetMessage("AI ERROR");
                    ai_sample_count = 0U;
                    return;
                }
    
                /* Avoid newlib's allocating float formatter: this project's heap
                   is only 512 bytes. Preserve the four-decimal serial format. */
                const uint32_t p_stopped = (uint32_t)(ai_probabilities[0] * 10000.0f + 0.5f);
                const uint32_t p_running = (uint32_t)(ai_probabilities[1] * 10000.0f + 0.5f);
                OLED_SetMotorState(predicted_id, p_stopped, p_running);
    #if NEAI_OUTPUT_VOFA
                /* FireWater receives 3 numeric channels; 0=Stopped, 1=Running. */
                printf("%d,%lu.%04lu,%lu.%04lu\r\n", predicted_id,
                       (unsigned long)(p_stopped / 10000U), (unsigned long)(p_stopped % 10000U),
                       (unsigned long)(p_running / 10000U), (unsigned long)(p_running % 10000U));
    #else
                printf("Motor: %s, P(stop)=%lu.%04lu, P(run)=%lu.%04lu\r\n",
                       (predicted_id == 0) ? "Stopped" : "Running",
                       (unsigned long)(p_stopped / 10000U), (unsigned long)(p_stopped % 10000U),
                       (unsigned long)(p_running / 10000U), (unsigned long)(p_running % 10000U));
    #endif
            }
            else
            {
                printf("NEAI classification failed: %d\r\n", (int)rc);
                OLED_SetMessage("AI ERROR");
            }
            ai_sample_count = 0U;
        }
    }
    
    int main(void)
    {
        uint8_t whoami = 0U;
        uint8_t lis3dh_ready = 0U;
        uint8_t ai_ready = 0U;
        uint8_t motor_running = 1U;
        uint32_t motor_state_sample_count = 0U;
        uint32_t sensor_error_streak = 0U;
        int32_t sensor_probe_status = -1;
        int16_t acc_raw[3];
    
        /* CubeMX2-generated STM32C5 initialization, unchanged from your original. */
        if (mx_system_init() != SYSTEM_OK)
        {
            return -1;
        }
    
        /* Original stepmotor.c drives the 4-phase motor using TIM2 interrupts. */
        stepper_init();
        stepper_tim_start(2000U);
    
        lis3dh_ctx.write_reg = platform_write;
        lis3dh_ctx.read_reg  = platform_read;
        lis3dh_ctx.mdelay    = platform_delay;
        lis3dh_ctx.handle    = mx_i2c1_i2c_gethandle();
    
        /* Allow the sensor's power-on boot to finish before probing its ID. */
        HAL_Delay(10U);
        for (uint32_t attempt = 0U; attempt < 3U; attempt++)
        {
            sensor_probe_status = lis3dh_device_id_get(&lis3dh_ctx, &whoami);
            if (sensor_probe_status == 0 && whoami == LIS3DH_ID)
            {
                break;
            }
            HAL_Delay(20U);
        }
    
        if (sensor_probe_status != 0 || whoami != LIS3DH_ID)
        {
            printf("LIS3DH not detected, WHO_AM_I=0x%02X\r\n", whoami);
        }
        else
        {
            /* Keep exactly the same sensor units and range as the training capture. */
            int32_t st = 0;
            st |= lis3dh_block_data_update_set(&lis3dh_ctx, PROPERTY_ENABLE);
            st |= lis3dh_full_scale_set(&lis3dh_ctx, LIS3DH_2g);
            st |= lis3dh_data_rate_set(&lis3dh_ctx, LIS3DH_ODR_100Hz);
            st |= lis3dh_operating_mode_set(&lis3dh_ctx, LIS3DH_HR_12bit);
            if (st == 0)
            {
                lis3dh_ready = 1U;
                printf("LIS3DH ready (100Hz, HR12, +/-2g)\r\n");
            }
            else
            {
                printf("LIS3DH setup failed: %ld\r\n", (long)st);
            }
        }
    
        if (lis3dh_ready != 0U)
        {
            enum neai_state st = neai_classification_init();
            if (st == NEAI_OK)
            {
                ai_ready = 1U;
                printf("NEAI classifier ready: %s\r\n", neai_get_id());
            }
            else
            {
                printf("NEAI init error: %d\r\n", (int)st);
            }
        }
    
        /* I2C1 总线扫描:看总线上有哪些设备 */
        {
            hal_i2c_handle_t *scan = mx_i2c1_i2c_gethandle();
            printf("I2C1 scan:\r\n");
            for (uint32_t a = 0x08U; a < 0x80U; a++)
            {
                if (HAL_I2C_MASTER_PollForSlaveReady(scan, a, 10U) == HAL_OK)
                {
                    printf("  found 0x%02lX\r\n", (unsigned long)a);
                }
            }
            printf("scan done.\r\n");
        }
    
        /* OLED 最后初始化,避免其通信失败锁死 I2C1 影响传感器 */
        if (OLED_Init() == 0U)
        {
            printf("OLED unavailable (I2C address 0x3C)\r\n");
        }
    
        if (lis3dh_ready == 0U) OLED_SetMessage("SENSOR ERROR");
        else if (ai_ready == 0U) OLED_SetMessage("AI INIT ERROR");
    
        while (1)
        {
            uint8_t fresh_sample = 0U;
            /* Poll LIS3DH's new XYZ-data flag instead of rereading old values.
             * Do NOT printf each sample: blocking UART transfers can cause overrun.
             */
            if (lis3dh_ready != 0U)
            {
                const int32_t status = lis3dh_xl_data_ready_get(&lis3dh_ctx, &fresh_sample);
                if (status != 0)
                {
                    sensor_error_streak++;
                }
                else if (fresh_sample == 0U)
                {
                    sensor_error_streak = 0U;
                }
                else if (lis3dh_acceleration_raw_get(&lis3dh_ctx, acc_raw) == 0)
                {
                    sensor_error_streak = 0U;
                    /* LIS3DH high-resolution 12-bit mode, +/-2g; convert mg -> g. */
                    const float ax_g = lis3dh_from_fs2_hr_to_mg(acc_raw[0]) / 1000.0f;
                    const float ay_g = lis3dh_from_fs2_hr_to_mg(acc_raw[1]) / 1000.0f;
                    const float az_g = lis3dh_from_fs2_hr_to_mg(acc_raw[2]) / 1000.0f;
    
                    if (ai_ready != 0U)
                    {
                        ai_push_sample(ax_g, ay_g, az_g);
                    }
    
    #if MOTOR_DEMO_AUTOSWITCH
                    /* Demo uses motor state only to control the motor, never as AI input. */
                    motor_state_sample_count++;
                    if (motor_state_sample_count >= MOTOR_DEMO_SAMPLES)
                    {
                        motor_state_sample_count = 0U;
                        if (motor_running != 0U)
                        {
                            stepper_tim_stop();
                            motor_running = 0U;
                        }
                        else
                        {
                            stepper_tim_start(2000U);
                            motor_running = 1U;
                        }
                    }
    #endif
                }
                else if (fresh_sample != 0U)
                {
                    sensor_error_streak++;
                }
    
                if (sensor_error_streak >= SENSOR_ERROR_LIMIT)
                {
                    lis3dh_ready = 0U;
                    stepper_tim_stop();
                    printf("LIS3DH communication lost, I2C errors=%lu status=0x%08lX error=0x%08lX\r\n",
                           (unsigned long)lis3dh_i2c_error_count,
                           (unsigned long)lis3dh_last_i2c_status,
                           (unsigned long)lis3dh_last_i2c_error);
                    OLED_SetMessage("SENSOR ERROR");
                }
            }
            /* Share I2C1 using short transfers after servicing the sensor. */
            OLED_Task();
            HAL_Delay(1U);
        }
    }

四、项目总结

通过这次实践,对嵌入式 AI 有了更直观的认识。原来不一定需要自己从头编写神经网络,也可以通过 NanoEdge AI Studio 对真实传感器数据进行训练,再将模型部署到资源有限的 STM32 微控制器中。

后续还计划进一步优化采样频率与数据质量,并尝试加入电机堵转等更多异常状态,提高系统的实用性。

欢迎各位大佬交流指正,特别是在振动信号采集、模型泛化能力和 STM32 模型部署优化方面,希望能学习更多经验!



共1条 1/1 1 跳转至页

回复

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