一、任务目标
利用AI识别正常转动、异常转动、停止转动三种状态。
二、整体方案基本设计
1、使用ESP8266驱动步进电机,根据需要,设计为为正常转动、异常转动(堵转)、停止
2、将加速度传感器固定在步进电机上,获取AI用的三种状态的训练数据
3、使用AI工具进行训练,并获取训练结果,反馈给NUCLEO-H533RE的测试工程
4、根据训练结果,转换工程为识别电机转动状态的运行方式,识别电机的转动状态。
三、方案实施
1、电机驱动侧
1)运行逻辑
使用4个GPIO口驱动步进电机,一个按钮用来选择正常运转、异常转动、停止三种状态的交替执行,另外两个GPIO口用来作指示。其中一个绿色的LED亮表示电机正常转动;另一个红色LED亮表示异常转动,两个LED都灭掉表示处于停转状态。
电路设计框图

2) 处理逻辑:
3) 程序代码
#include <AccelStepper.h>
// ULN2003 与 Arduino 的引脚连接
#define IN1 5
#define IN2 4
#define IN3 0
#define IN4 2
// 按钮连接(使用内部上拉,按下为 LOW)
#define BUTTON_PIN 14 // 输入引脚
#define OK_LED_PIN 12 // 输出引脚 OK_LED, 外部接LED、限流电阻、3.3V
#define NG_LED_PIN 13 // 输出引脚 NG_LED, 外部接LED、限流电阻、3.3V
// 28BYJ-48 使用 4 线全步进模式,接口类型设为 4
// 注意引脚顺序:IN1, IN3, IN2, IN4 是 28BYJ-48 的常见相序
AccelStepper stepper(4, IN1, IN3, IN2, IN4);
bool lastButtonState = HIGH; // 上一次按钮读数(用于检测“按下瞬间”)
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50; // 消抖时间(毫秒)
uint8_t state = 0; // 0:停止,1:正常转动,2:正常转动,3:异常转动
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(OK_LED_PIN, OUTPUT);
pinMode(NG_LED_PIN, OUTPUT);
digitalWrite(OK_LED_PIN, HIGH);
digitalWrite(NG_LED_PIN, HIGH);
stepper.setMaxSpeed(500); // 最大速度(步/秒)
stepper.setAcceleration(200); // 加速度(步/秒²),让启停更平滑
stepper.setCurrentPosition(0);
}
void loop() {
// ---------- 按钮扫描与消抖 ----------
bool reading = digitalRead(BUTTON_PIN);
// 如果读数发生变化,重置消抖计时器
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
// 如果读数稳定超过 debounceDelay,才认为是有效信号
if ((millis() - lastDebounceTime) > debounceDelay) {
// 检测“按下”的下降沿(HIGH -> LOW)
static bool stableButtonState = HIGH;
if (reading != stableButtonState) {
stableButtonState = reading;
if (stableButtonState == LOW) { // 确认按下
// 切换状态
state = (state + 1) % 4;
if (state == 1) {
// 先停止转动
stepper.stop();
Serial.println("正常转动......");
// 点亮正常转动指示灯
digitalWrite(OK_LED_PIN, LOW);
digitalWrite(NG_LED_PIN, HIGH);
stepper.setMaxSpeed(500); // 最大速度(步/秒)
stepper.setAcceleration(200); // 加速度(步/秒²),让启停更平滑
// 启动:设置一个足够大的目标位置,使电机持续旋转
stepper.moveTo(stepper.currentPosition() + 1000000);
} else if (state == 3) {
// 先停止转动
stepper.stop();
Serial.println("异常转动......");
// 点亮正常转动指示灯
digitalWrite(OK_LED_PIN, HIGH);
digitalWrite(NG_LED_PIN, LOW);
// 异常转动,通过提高转速、加速度来完成
stepper.setMaxSpeed(1000); // 最大速度(步/秒)
stepper.setAcceleration(200); // 加速度(步/秒²),让启停更平滑
// 启动:设置一个足够大的目标位置,使电机持续旋转
stepper.moveTo(stepper.currentPosition() + 1000000);
} else {
Serial.println("停止!");
// 停止:调用 stop(),电机会按设定加速度减速停止
stepper.stop();
// 灭掉所有指示灯
digitalWrite(OK_LED_PIN, HIGH);
digitalWrite(NG_LED_PIN, HIGH);
}
}
}
}
lastButtonState = reading;
// ---------- 电机控制 ----------
if ((state % 2) == 1) {
// 只要处于运行状态,就持续调用 run()
// run() 会根据加速度曲线计算当前应有的速度,并驱动电机前进一步(非阻塞)
stepper.run();
}
}4)硬件的搭建

电机固定在一款环氧树脂板上,三轴传感器也固定在这块板子,但是离电机有一定距离,不知对数据的采集结果会不会产生影响,进而对AI判断电机状态产生什么影响。
2、收集数据侧
1)运行逻辑
收集数据侧其实是由两个不同的任务,一个是收集数据;一个是检测电机状态。利用板载的用户按钮作为采集数据的启动按钮。调试心机以及AI检测信息可通过OLED以及串口输出。
2) 电路设计框图
2) 处理逻辑
A .“收集数据模式/AI检测模式”按钮
切换程序运行状态为收集数据模式/AI检测模式。
B .“启动采集数据”按钮
在收集数据模式下,按照“NanoEdge-AI-Studio”的处理格式要求,将采集到的数据通过串口发给上位机。在上位机将收集到的数据以文件形式提供给NanoEdge-AI-Studio”工具。收集数据按照“正常、异常、停止”三种方式存储为文件。具体格式为一行256组数据+换行符。每组数据中按照x、y、z三轴方向顺序数据。收集完100行数据后,形成学习用数据包给“NanoEdge-AI-Studio”训练。
C . 系统定时器中断
以1mS的周期,定时采集来自三轴传感器的x、y、z数据。存入内存变量。
D . 主程序
在收集到一个完整的训练用数据包后,将数据包通过串口发送给上位机。并在OLED上显示数据包中第一组数据。
3) 程序代码
主程序:
int main(void) {
uint8_t i = 0;
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_ICACHE_Init();
MX_USART2_UART_Init();
/* 定时器初始化 */
MX_TIM_Sample_Init();
// 初始化LED2关联外设
BSP_LED_Init(LED2);
// 配置外部中断
EXTI_IRQHandler_Config();
enum neai_state error_code = neai_classification_init();
if (error_code != NEAI_OK) {
// 初始化失败,处理错误(例如通过串口打印错误码)
printf("初始化 AI 库支持失败。\n");
//Error_Handler();
} else {
// 获取类别总数
uint8_t num_classes = neai_get_number_of_classes();
// 获取索引对应的类别名称(返回字符串,如 "Stop", "Normal", "Error")
for (i = 0; i<num_classes; i++) {
printf(" AI 验证类型 %d = %s \n", i, neai_get_class_name(i));
}
}
LIS3DHTR_Init(&hi2c1);
HAL_Delay(200);
SH1106_Init(&hi2c1);
SH1106_Clear();
HAL_Delay(200);
SH1106_ShowString6x8(0, 0, "Check ");
SH1106_ShowString6x8(0, 2, "X:");
SH1106_ShowString6x8(72, 2, "g");
SH1106_ShowString6x8(0, 3, "Y:");
SH1106_ShowString6x8(72, 3, "g");
SH1106_ShowString6x8(0, 4, "Z:");
SH1106_ShowString6x8(72, 4, "g");
//// 测试串口输出
//HAL_UART_Transmit(&huart2, (uint8_t*)"Start ......\r\n", 15, HAL_MAX_DELAY);
// 启动定时器中断(开始采样)
HAL_TIM_Base_Start_IT(&htim_sample);
while (1) {
if (buffer_ready) {
// 已经准备好一包训练数据(一行256组数据(x,y,z),总计100行)
// OLED 上只显示0号数据
SH1106_ShowString6x8(18, 2, " ");
SH1106_ShowString6x8(18, 3, " ");
SH1106_ShowString6x8(18, 4, " ");
SH1106_ShowFixed6x8(18, 2, (int16_t)(input_buffer[0]*1000), 1, 3); // 宽度 3 位整数
SH1106_ShowFixed6x8(18, 3, (int16_t)(input_buffer[1]*1000), 1, 3);
SH1106_ShowFixed6x8(18, 4, (int16_t)(input_buffer[2]*1000), 1, 3);
// 采集数据中场合,晕粗发出数据
if (collect_state == Begin) {
// 收集训练数据方式并且启动了收集后,才向串口输出数据
if (run_mode == Collect && collect_state == Begin) {
/* 逐点输出:每点 x,y,z 用逗号分隔 */
for (uint16_t i = 0; i < SAMPLES_PER_AXIS; i++) {
printf("%1.3f,%1.3f,%1.3f",
input_buffer[i * AXIS_NUM + 0],
input_buffer[i * AXIS_NUM + 1],
input_buffer[i * AXIS_NUM + 2]);
if (i < SAMPLES_PER_AXIS - 1) {
printf(",");
}
}
printf("\n"); // 一个缓冲区结束,换行!
// 发送行数加1
line_sent++;
/* 达到目标行数,自动停止 */
if (line_sent >= TARGET_LINES) {
// 完成100行数据的发送
SH1106_ShowString6x8(80, 0, " "); // 清除开始采集对应的显示信息(Begin)
collect_state = End; // 修改工作状态为数据采集完成
line_sent = 0;
sample_count = 0; // 清零,准备下一行
BSP_LED_Off(LED2);
}
}
}
if (run_mode == Check) {
// 执行一次分类推理
check_motor_state();
} else {
SH1106_ShowString6x8(0, 6, " ");
}
buffer_ready = 0;
}
}
}
按键处理:
/**
* @brief 外部中断回调函数 :
* @param GPIO_Pin: Specifies the pins connected EXTI line
* @retval None
*/
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == BUTTON_USER_PIN) {
// 禁止定时器中断中采集数据
buffer_ready = 1;
// 板载USER按钮,改变工作状态:收集数据(训练模式)、检测电机运行状态(AI检测模式)
run_mode = 1 - run_mode;
sample_count = 0; // 256组数据计数清零
buffer_ready = 0; // 重新采集数据包清零
line_sent = 0; // 已发送的行数清零
if (run_mode == Collect ) {
SH1106_ShowString6x8(0, 0, "Collect");
} else {
SH1106_ShowString6x8(0, 0, "Check ");
}
collect_state = End;
SH1106_ShowString6x8(80, 0, " ");
// 允许采集
buffer_ready = 0;
} else if (GPIO_Pin == BUTTON_CONFIRM_PIN) {
if (collect_state == Begin || run_mode == Check ) {
// 什么也不做,已经处于采集数据的状态
} else {
// 禁止定时器中断中采集数据
buffer_ready = 1;
line_sent = 0; // 清零已发送的行数
sample_count = 0; // 清零256组数据的计数值
collect_state = Begin; // 采集数据状态
SH1106_ShowString6x8(80, 0, "Begin");
BSP_LED_On(LED2);
// 允许采集
buffer_ready = 0;
}
}
}定时中断处理:
/* HAL 定时器更新回调:每 1ms 触发一次 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
float x, y, z;
// 如果上一个缓冲区还没被主循环处理完,不采集数据
if (!buffer_ready) {
// buffer没有被装满时,继续采集
// 读取一次三轴数据(函数耗时要远小于 1ms)
if (LIS3DHTR_ReadAccel(&hi2c1, &x, &y, &z) == HAL_OK) {
input_buffer[sample_count * AXIS_NUM + 0] = x;
input_buffer[sample_count * AXIS_NUM + 1] = y;
input_buffer[sample_count * AXIS_NUM + 2] = z;
// 训练数据完成一组
sample_count++;
// 是否采集完256组
if (sample_count >= SAMPLES_PER_AXIS) {
buffer_ready = 1; // 通知主循环:一个完整缓冲区已就绪
sample_count = 0; // 清零,准备下一行
}
}
}
}
}5)硬件的搭建

四、系统的调试运行
在收集数据阶段,需要上位机端提供串口工具接收数据。为了能快速完成数据的上传,NUCLEO-H533RE的串口和上位机串口工具的通讯速度均配置为921600波特率。
在收集数据阶段,使NUCLEO-H533RE工作于收集数据的模式。通过定时器中断完成了100行数据的收集后,把上位机串口工具收集到的数据以文本形式形成txt文件。总共收集了三种状态、6个数据包。电机的每种状态都收集了两个数据包,一个用于训练,一个用于验证。
五、AI学习与训练
在NanoEdge AI Studio中建立“Classify variations of your signal”工程,基本上按照默认的参数即可。

在Signals阶段,将前面建立的数据文件按照正常、异常、停止三个种类添加给NanoEdge AI Studio。要求每个类别提供一个独立的文件。

接下来在“Benchmark”阶段添加新的训练。这个过程比较长。
在完成训练过程后,进入“Validation”验证阶段。这个阶段你可以选择最优解,使用Signals阶段的三类文件进行验证,也可以另外准备验证用的三类文件。我是使用另外一组数据包进行验证的。而且验证结果也还不错。
如果觉得可以了,就可以在“Deployment”把训练结果形成库(压缩文件)。

我这里用的默认名:libneai_project-2026-10-29-17-58_2.zip

红色框内的两个文件,就是以后要集成到你的工程中的AI检测用工具文件。


具体操作可通过AI获得,这里不详细说明了。
工程经过改造后,将获得的完整数据包交由neai_classification函数处理,就可以检测电机的运行状态了。
如:


我的数据训练结果中Stop状态的检测非常完美,正常状态的检测也比较好,异常状态的检测还有不尽人意的地方,不知道是不是整体硬件搭建上导致问题。
六、总结
以前看别人晒AI成果,自己很羡慕。觉得那是很高深的东西,离我太远了、太高深了,望而却步(实际上从设计层面,依旧很高深,嘻嘻),不知从何学起。是EEPW给我们提供这样的平台,跟着大佬学习、动手参与,让我们有机会亲身体验如何使用AI工具进行DIY实践。
事实上,报名的时候,自己心里是有担心的,怕完不成任务。通过这次活动,打破了自己给自己建立的知识壁垒。尽管比较波折,但最终完成了活动任务。最后再次感谢EEPW、活动组织者以及技术大佬给我们提供这个机会,跟着学习前沿技术!
我要赚赏金
