这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【Let'sdoDigiKey2026第3期】成果贴-----基于边缘AI的电机

共1条 1/1 1 跳转至页

【Let'sdoDigiKey2026第3期】成果贴-----基于边缘AI的电机状态检测实践

专家
2026-11-03 12:11:46     打赏

一、任务目标

利用AI识别正常转动、异常转动、停止转动三种状态。

二、整体方案基本设计

1、使用ESP8266驱动步进电机,根据需要,设计为为正常转动、异常转动(堵转)、停止

2、将加速度传感器固定在步进电机上,获取AI用的三种状态的训练数据

3、使用AI工具进行训练,并获取训练结果,反馈给NUCLEO-H533RE的测试工程

4、根据训练结果,转换工程为识别电机转动状态的运行方式,识别电机的转动状态。

三、方案实施

1、电机驱动侧

1)运行逻辑

使用4个GPIO口驱动步进电机,一个按钮用来选择正常运转、异常转动、停止三种状态的交替执行,另外两个GPIO口用来作指示。其中一个绿色的LED亮表示电机正常转动;另一个红色LED亮表示异常转动,两个LED都灭掉表示处于停转状态。

电路设计框图

 image.png

2) 处理逻辑:

image.png 

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)硬件的搭建

 image.png

电机固定在一款环氧树脂板上,三轴传感器也固定在这块板子,但是离电机有一定距离,不知对数据的采集结果会不会产生影响,进而对AI判断电机状态产生什么影响。

 

2、收集数据侧

1)运行逻辑

收集数据侧其实是由两个不同的任务,一个是收集数据;一个是检测电机状态。利用板载的用户按钮作为采集数据的启动按钮。调试心机以及AI检测信息可通过OLED以及串口输出。

2) 电路设计框图

image.png 

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)硬件的搭建

 image.png

 

四、系统的调试运行

在收集数据阶段,需要上位机端提供串口工具接收数据。为了能快速完成数据的上传,NUCLEO-H533RE的串口和上位机串口工具的通讯速度均配置为921600波特率。

在收集数据阶段,使NUCLEO-H533RE工作于收集数据的模式。通过定时器中断完成了100行数据的收集后,把上位机串口工具收集到的数据以文本形式形成txt文件。总共收集了三种状态、6个数据包。电机的每种状态都收集了两个数据包,一个用于训练,一个用于验证。

image.png 

image.png 

五、AI学习与训练

在NanoEdge AI Studio中建立“Classify variations of your signal”工程,基本上按照默认的参数即可。

image.png 

 image.png

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

 image.png

接下来在“Benchmark”阶段添加新的训练。这个过程比较长。

image.png 

在完成训练过程后,进入“Validation”验证阶段。这个阶段你可以选择最优解,使用Signals阶段的三类文件进行验证,也可以另外准备验证用的三类文件。我是使用另外一组数据包进行验证的。而且验证结果也还不错。

image.png 

如果觉得可以了,就可以在“Deployment”把训练结果形成库(压缩文件)。

 image.png

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

 image.png

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

 image.png

 image.png

具体操作可通过AI获得,这里不详细说明了。

工程经过改造后,将获得的完整数据包交由neai_classification函数处理,就可以检测电机的运行状态了。

如:

 image.png 

 image.png

image.png

我的数据训练结果中Stop状态的检测非常完美,正常状态的检测也比较好,异常状态的检测还有不尽人意的地方,不知道是不是整体硬件搭建上导致问题。

六、总结

以前看别人晒AI成果,自己很羡慕。觉得那是很高深的东西,离我太远了、太高深了,望而却步(实际上从设计层面,依旧很高深,嘻嘻),不知从何学起。是EEPW给我们提供这样的平台,跟着大佬学习、动手参与,让我们有机会亲身体验如何使用AI工具进行DIY实践。

事实上,报名的时候,自己心里是有担心的,怕完不成任务。通过这次活动,打破了自己给自己建立的知识壁垒。尽管比较波折,但最终完成了活动任务。最后再次感谢EEPW、活动组织者以及技术大佬给我们提供这个机会,跟着学习前沿技术!

 

 

 





关键词: AI边缘检测     Let's do 2026年第3期    

共1条 1/1 1 跳转至页

回复

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