本项目主要使用 STM32C562RE、LIS3DH 三轴加速度传感器和步进电机,通过检测电机运行时产生的振动,实现不同运行状态的识别。使用 STM32C562RE 开发板搭配 NanoEdge AI Studio 5.2.0,实现一个基于三轴加速度数据的电机运行状态智能识别系统。
一、项目实现思路
首先通过 STM32CubeMX2 完成开发板的外设配置,包括 GPIO、I²C、USART 和定时器等。
利用 I²C 接口采集 LIS3DH 的 X、Y、Z 三轴加速度数据,并通过串口发送至 VOFA+ 上位机,实时观察电机运行过程中的振动波形。
在此基础上,分别采集两种电机状态下的加速度数据:
停止状态(Stopped):电机停止转动,整体振动较小。
正常运行(Running):电机持续转动,产生相对稳定的振动。
效果演示

二、AI 模型训练
使用 NanoEdge AI Studio 5.2.0 创建 Classification 分类项目。

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

开始训练

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

三、嵌入式部署
接下来计划将生成的 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 模型部署优化方面,希望能学习更多经验!
我要赚赏金
