news 2026/9/1 1:28:24

基于STM32的电机状态检测实战:编码器测速、ADC采样与故障保护

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于STM32的电机状态检测实战:编码器测速、ADC采样与故障保护

在写嵌入式项目时,电机状态检测几乎是绕不开的一环。无论是智能小车、机器人底盘,还是小型工业设备,都需要知道电机当前是否在转、转速是多少、电流是否正常、有没有堵转或超温。很多初学者在做 STM32 电机控制时,往往只关注“怎么让电机转起来”,却忽略了“怎么判断电机状态好不好”。本文围绕基于 STM32 单片机的电机状态检测项目,从系统方案、硬件选型、CubeMX 配置、核心代码到常见问题,完整梳理一遍设计与实现思路,希望能给正在做相关电机项目的朋友提供一份可直接参考的实战教程。

1. 项目背景:为什么要做电机状态检测

1.1 电机状态检测解决什么问题

电机是机电系统中常见的执行机构,但它并不是一个“通电就能永远正常转”的装置。实际运行中,电机可能因为负载过重而堵转,可能因为供电电压跌落而转速不稳,可能因为连续工作导致绕组温度升高,也可能因为编码器接线松动导致测速数据跳变。如果控制系统无法感知这些异常,电机带载能力下降、系统定位不准、甚至烧毁驱动模块的问题就会接连出现。

电机状态检测的核心,就是通过单片机采集电机的转速、方向、电流、温度等运行参数,并基于这些参数判断电机当前处于什么工作状态,比如正常启动、稳定运行、堵转、过流、超温。这样系统才能在异常发生时及时做出保护动作,例如切断 PWM 输出、报警提示、降低运行速度,从而避免设备损坏。

1.2 单片机能检测哪些电机状态

以 STM32 单片机为核心,常见的电机状态检测项包括:

状态项检测方式用途
启停状态GPIO 读取使能信号、PWM 输出标志判断电机是否允许运行
转速编码器脉冲计数、霍尔传感器判断实际转速是否达到目标
转向编码器计数方向、方向引脚电平判断电机正转还是反转
电流采样电阻分压 + ADC判断是否过流、堵转
电压电源电压分压 + ADC判断供电是否正常
温度NTC 热敏电阻、DS18B20判断电机是否过热
故障标志驱动芯片 FAULT 引脚判断驱动模块是否异常

这里需要说明的是,并不是所有电机都一定要做全部检测项。如果做的是普通直流电机开环控制,可能只需要电流和启停状态;如果做的是带编码器的闭环调速系统,那么转速和转向的检测就是核心。本文以一个常见的带编码器直流减速电机为例,把转速、方向、电流、温度几类状态检测串起来。

1.3 适用场景与学习收益

这套状态检测方案可以用于智能小车测速、机器人关节电机监测、传送带速度反馈、云台电机位置检测等场景。通过学习这个项目,你能掌握 STM32 定时器编码器模式的使用、ADC 多通道采集、状态机程序设计、串口数据上报,以及电机故障保护的基本思路。对单片机项目实战来说,是一个比较完整的综合练习。

2. 系统整体方案设计

2.1 功能需求拆分

在设计电机状态检测系统之前,先把需求拆清楚:

  1. 电机能启动和停止,支持正反转控制。
  2. 能实时读取电机转速和转向。
  3. 能采集电机工作电流。
  4. 能采集电机或驱动板温度。
  5. 能通过串口将状态数据发送到上位机,方便调试。
  6. 当电流过大、转速异常或者温度过高时,进入故障保护状态。

这样一个系统并不复杂,但涉及了 MCU 的 GPIO、定时器、ADC、UART 多个外设,非常适合用来练习 STM32 的综合开发。

2.2 整体框图

整个系统的数据流可以这样理解:

STM32 输出 PWM → 电机驱动模块 → 直流电机 ↓ 编码器脉冲反馈到 STM32 定时器 ↓ 电流采样电路 → STM32 ADC ↓ 温度采样电路 → STM32 ADC ↓ 状态判断逻辑 → 串口输出/故障保护

电机驱动模块根据 STM32 输出的 PWM 信号驱动电机转动。电机尾部的编码器产生脉冲信号,反馈给 STM32 的定时器,用来计算转速和识别方向。电流采样电阻和温度传感器将模拟信号送到 ADC 通道,STM32 再把所有数据汇总到一起,通过状态判断逻辑得到电机当前的工作状态。

2.3 为什么选择 STM32

做电机状态检测可以使用的单片机非常多,51 单片机也能做简单的测速和电流检测,但 STM32 有几点优势:

  • 定时器资源丰富,支持编码器接口模式,可以直接接增量式编码器;
  • ADC 转换速度快,可以较实时地采样电流;
  • 多个 DMA 通道,可以减轻 CPU 负担;
  • STM32CubeMX 配置工具成熟,外设初始化代码自动生成,开发效率高。

尤其是闭环调速、PID 控制这类需求,STM32 的资源优势会更加明显。所以本文选择 STM32F103C8T6 作为主控芯片,兼顾成本与性能。

3. 环境准备与硬件选型

3.1 开发环境

在开始之前,需要准备好以下软件环境:

  • STM32CubeMX:用来生成工程初始化和外设配置代码;
  • Keil MDK 或者 STM32CubeIDE:用来编写和编译代码;
  • ST-Link 驱动:用来下载调试程序;
  • 串口调试助手:用来查看电机状态数据。

版本方面,本文以 STM32F103C8T6 为例进行说明。实际开发时,Keil、STM32CubeMX 或者 CubeMX 固件包的版本可根据自己的安装情况调整,关键是学会配置思路。

3.2 硬件清单

下面是一份适用于电机状态检测项目的硬件清单,供参考:

模块型号/规格说明
主控板STM32F103C8T6 最小系统板性价比高,适合项目原型
电机带霍尔编码器的直流减速电机编码器用于测速和方向
电机驱动TB6612FNG 或 L298N控制电机正反转和速度
电流采样采样电阻 + 运放放大电路将电流信号转为 ADC 可读电压
温度采样NTC 热敏电阻 + 分压电阻或者用 DS18B20 数字传感器
电源12V 直流电源和 5V/3.3V 转换电路电机和单片机分开供电更稳定
调试工具ST-Link V2下载程序和在线调试
显示/上报串口转 USB 模块连接上位机查看数据

硬件接线时要注意:电机驱动部分的电源和单片机电源尽量分开,防止电机启动瞬间拉低电压导致 MCU 复位。如果使用 L298N,建议在芯片附近加上大容量电解电容。

3.3 硬件接线参考

以 STM32F103C8T6 为例,接线方案可以这样设计:

  • 定时器 TIM2 的 CH1(PA0)和 CH2(PA1)连接电机编码器 A/B 相;
  • PWM 输出使用 TIM3 的 CH1(PA6),控制电机速度;
  • 驱动模块的方向引脚接 PB0、PB1;
  • 电流采样输出接 PA4(ADC1_IN4);
  • 温度采样输出接 PA5(ADC1_IN5);
  • 串口 USART1 的 TX(PA9)、RX(PA10)接串口模块。

具体引脚不一定要照搬,可以根据自己手头的板子调整。关键是把编码器引脚接到支持编码器模式的定时器上,比如 STM32F103 的 TIM1、TIM2、TIM3、TIM4 都支持编码器接口。

4. STM32CubeMX 基础配置

4.1 创建工程并选择芯片

打开 STM32CubeMX,新建一个工程,选择 STM32F103C8T6。在 Pinout & Configuration 界面中,依次配置 RCC、SYS、定时器、ADC、UART 和 GPIO。

时钟树方面,如果使用外部晶振,可以配置 HSE 为 Crystal/Ceramic Resonator,然后在 Clock Configuration 中把系统时钟设为 72MHz。若开发板上没有外部晶振,则选择 HSI 作为时钟源,不过精度会略低一些。

4.2 配置编码器模式

在 STM32CubeMX 中,找到 TIM2。将 CH1 和 CH2 的模式都设置为 Encoder Mode,也就是编码器模式。配置界面中会出现 Encoder Mode 参数,可以设置为 TI1 and TI2,同时检测两相上升沿和下降沿。

编码器模式下需要配置以下关键参数:

  • Counter Period:计数器周期,一般设为 65535,也就是 16 位计数器的最大值;
  • Input Filter:输入滤波,可以设为较大值,防止机械抖动干扰;
  • Polarity:极性,根据编码器接线调整,一般默认 Rising Edge。

这里有必要解释一下编码器模式的工作原理。增量式编码器输出的 A、B 两路方波在电机旋转时存在 90 度相位差。定时器工作在编码器模式后,会根据 A、B 相的相位关系自动进行加计数或减计数。这样单片机读取定时器计数器的当前值,就能知道电机转过了多少脉冲,同时判断方向。

4.3 配置 PWM 输出

找到 TIM3,将 CH1 设置为 PWM Generation CH1。参数配置中,设置 Prescaler 和 Counter Period 来确定 PWM 频率。例如希望产生 20kHz 的 PWM,72MHz 时钟下可以设置 Prescaler=0,Counter Period=3600-1。

PWM 输出的占空比由 CCR1 决定。代码中可以通过修改__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty)来实时调整电机速度。

4.4 配置 ADC 多通道

在 Analog 中选择 ADC1,将 IN4、IN5 设置为 Activated,也就是把 PA4、PA5 作为 ADC 输入通道。ADC 参数可以选择 Continuous Conversion Mode 为 Disabled,这样便于在需要时手动触发转换。为了代码简单,也可以开启 Scan Conversion Mode,将 Number Of Conversion 设置为 2,然后配置转换顺序。

使用 HAL 库时,可以调用HAL_ADC_Start_DMA或者HAL_ADC_StartHAL_ADC_PollForConversion的方式读取数据。DMA 方式适合需要连续采样电流的场景,而轮询方式在简单项目中更容易理解。

4.5 配置串口和 GPIO

将 USART1 设置为 Asynchronous 模式,波特率可以选择 115200,这样便于串口调试助手查看数据。GPIO 方面,把方向控制引脚 PB0、PB1 设置为 Output Push Pull,初始电平根据实际需求设置。

配置完成后,点击 Project Manager,填写工程名称,选择 Toolchain/IDE 为 MDK-ARM,生成代码。生成后的代码中已经包含了所有外设的初始化函数,我们只需要在 main 函数中补充业务逻辑即可。

5. 核心代码实现

5.1 引入全局变量和结构体

状态检测的代码适合用一个结构体来管理电机运行数据,这样后期扩展和移植都会方便很多。这里给出一个示例结构体:

// 文件路径:Core/Inc/motor_status.h #ifndef __MOTOR_STATUS_H #define __MOTOR_STATUS_H #include "main.h" typedef enum { MOTOR_STATE_IDLE = 0, MOTOR_STATE_STARTING, MOTOR_STATE_RUNNING, MOTOR_STATE_FAULT } MotorState; typedef struct { MotorState state; // 当前状态 int32_t speed_rpm; // 当前转速 int8_t direction; // 方向:1 正转,-1 反转 uint16_t current_adc; // 电流 ADC 原始值 uint16_t temp_adc; // 温度 ADC 原始值 float current_value; // 转换后的电流值 float temp_value; // 转换后的温度值 } MotorStatus; extern MotorStatus g_motor; void Motor_Status_Update(void); #endif

将电机状态相关的内容单独放到头文件和源文件中,可以让 main.c 保持简洁。这个结构体中既包含状态枚举变量,也包含 ADC 原始值和转换后的物理量,后续扩展其他状态项时只需要在结构体中添加字段。

5.2 编码器测速代码

编码器模式的定时器初始化已经在 CubeMX 中完成了,接下来需要写一个读取转速的函数。读取转速的基本思路是:每隔固定时间读取定时器计数器的值,然后根据定时器周期、编码器线数和减速比计算出实际转速。

// 文件路径:Core/Src/motor_speed.c #include "motor_speed.h" #include "tim.h" #define ENCODER_PPR 13 // 编码器每圈脉冲数(实际根据编码器规格修改) #define GEAR_RATIO 30 // 减速比 #define SAMPLE_TIME_MS 100 // 测速周期 100ms static int32_t last_count = 0; int32_t Motor_GetEncoderCount(void) { return __HAL_TIM_GET_COUNTER(&htim2); } void Motor_Speed_Init(void) { __HAL_TIM_SET_COUNTER(&htim2, 0); last_count = 0; } float Motor_GetSpeedRpm(void) { int32_t current_count = Motor_GetEncoderCount(); int32_t delta = current_count - last_count; last_count = current_count; // 编码器模式 4 倍频:每圈脉冲数 = PPR * 4 float pulse_per_round = (float)ENCODER_PPR * 4 * GEAR_RATIO; float round_per_second = (float)delta / pulse_per_round; float rpm = round_per_second * 60.0f; // 将 100ms 的采样周期转换为秒 rpm = rpm * (1000.0f / SAMPLE_TIME_MS); return rpm; }

这段代码中,__HAL_TIM_GET_COUNTER用于读取定时器当前计数脉冲数。由于编码器模式下计数器会自动加减,所以delta的正负就代表了电机转向。计算转速时,需要根据编码器线数、是否倍频以及减速比,算出电机输出轴转一圈对应的脉冲数。

需要注意的是,这个测速函数返回的是瞬时转速,单位是 RPM。实际使用时,采样周期越短,响应越快,但脉冲数较少时计算误差也会变大。所以测速周期要根据电机转速合理选择,本项目示例中 100ms 是一个比较常见的取值。

5.3 ADC 采集电流和温度

ADC 配置完成后,可以用轮询方式读取转换结果。下面用两个函数封装电流和温度的 ADC 读取操作:

// 文件路径:Core/Src/motor_adc.c #include "motor_adc.h" #include "adc.h" uint16_t Motor_ReadAdcValue(void) { HAL_ADC_Start(&hadc1); if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) { return HAL_ADC_GetValue(&hadc1); } return 0; } float Motor_GetCurrentValue(uint16_t adc_value) { // 假设 ADC 是 12 位,参考电压 3.3V float voltage = (float)adc_value / 4095.0f * 3.3f; // 根据采样电阻和运放放大倍数换算电流 // 示例:采样电阻 0.05Ω,运放放大 50 倍,电流 = 电压 / (电阻 * 放大倍数) float current = voltage / (0.05f * 50.0f); return current; } float Motor_GetTemperatureValue(uint16_t adc_value) { // 用 NTC 分压电路采样时,需要根据 NTC 阻值和 B 值查表或计算 // 这里仅给出一个示例换算,实际需根据电路设计调整 float voltage = (float)adc_value / 4095.0f * 3.3f; float temperature = voltage * 100.0f; // 示例线性换算 return temperature; }

这里将“ADC 原始值读取”和“物理量换算”分开封装,好处是以后如果更换了采样电阻或运放,只需要修改换算函数,不需要动读取逻辑。HAL_ADC_PollForConversion的超时时间设置为 10ms,如果转换失败就返回 0,避免程序卡死。

在实际项目中,电流的换算公式需要根据硬件电路来确定。常见做法是:驱动电路串联一个小阻值采样电阻,将电流信号转换为电压信号,再通过运放放大到 STM32 ADC 可采样的范围。因为 STM32 的 ADC 输入电压不能超过 3.3V,所以硬件设计时要注意放大后的电压不能超过参考电压。

5.4 电机控制基础代码

电机状态检测不能完全脱离电机控制。为了让电机既能转起来,又能被检测到状态,还需要一个简单的电机控制函数。下面给出基于 TB6612 和 PWM 的示例:

// 文件路径:Core/Src/motor_control.c #include "motor_control.h" #include "tim.h" #include "gpio.h" #define PWM_MAX_VALUE 1000 void Motor_Control_Stop(void) { __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 0); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); } void Motor_Control_SetSpeed(int16_t speed_percent) { if (speed_percent > 0) { // 正转 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); } else { // 反转 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); speed_percent = -speed_percent; } if (speed_percent > 100) { speed_percent = 100; } uint32_t duty = (uint32_t)speed_percent * PWM_MAX_VALUE / 100; __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty); }

这里的speed_percent表示目标速度百分比,取值范围是 -100 到 100。正数表示正转,负数表示反转。函数内部通过方向引脚和第二路 PWM 引脚控制电机的运行方向,并通过修改 CCR 寄存器的值来调节占空比。

方向控制引脚的数量和具体 GPIO 要根据驱动模块决定。如果是 TB6612,通常有两个输入引脚控制正反转;如果是 L298N,则有两个使能引脚和四个输入引脚。代码示例中略去了模块差异,实际接线时需要调整。

6. 电机运行状态检测逻辑

6.1 状态机设计

电机运行状态检测如果只用 if-else 判断,容易出现逻辑混乱。比如电机启动过程中电流本来就比正常运行大,如果直接按“电流过大就报警”处理,电机一启动就会误报。所以更推荐用状态机来管理。

本文示例将电机状态分为四个状态:

  1. 空闲状态:电机不转,等待启动指令;
  2. 启动状态:电机正在提速,电流和转速都处于动态变化中;
  3. 运行状态:电机速度达到目标值,持续检测各项参数;
  4. 故障状态:检测到过流、堵转或超温,停止电机并报警。

状态转移的条件参考如下:

状态机逻辑可以用一个函数实现,并在主循环中周期性调用。这样代码结构清晰,后续增加“软启动”“减速停止”等状态也比较容易。

6.2 状态机代码示例

下面是一个简单的电机状态更新函数:

// 文件路径:Core/Src/motor_status.c #include "motor_status.h" #include "motor_speed.h" #include "motor_adc.h" #include "motor_control.h" #define OVER_CURRENT_THRESHOLD 2.0f // 过流阈值,单位 A #define OVER_TEMP_THRESHOLD 70.0f // 过温阈值,单位 ℃ #define TARGET_SPEED_RPM 120.0f // 目标转速 #define FAULT_RECOVERY_TIME_MS 3000 // 故障复位时间 MotorStatus g_motor; void Motor_Status_Init(void) { g_motor.state = MOTOR_STATE_IDLE; g_motor.speed_rpm = 0; g_motor.direction = 0; g_motor.current_value = 0.0f; g_motor.temp_value = 25.0f; } void Motor_Status_Update(void) { uint16_t current_adc = Motor_ReadAdcValue(); uint16_t temp_adc = Motor_ReadAdcValue(); float speed_rpm = Motor_GetSpeedRpm(); g_motor.speed_rpm = speed_rpm; g_motor.current_value = Motor_GetCurrentValue(current_adc); g_motor.temp_value = Motor_GetTemperatureValue(temp_adc); if (speed_rpm > 0) { g_motor.direction = 1; } else if (speed_rpm < 0) { g_motor.direction = -1; } switch (g_motor.state) { case MOTOR_STATE_IDLE: // 空闲状态不做保护判断,等待外部启动信号 break; case MOTOR_STATE_STARTING: // 启动阶段突破过流阈值,进入故障状态 if (g_motor.current_value > OVER_CURRENT_THRESHOLD) { g_motor.state = MOTOR_STATE_FAULT; Motor_Control_Stop(); } else { g_motor.state = MOTOR_STATE_RUNNING; } break; case MOTOR_STATE_RUNNING: // 运行阶段检测过流、超温、堵转 if (g_motor.current_value > OVER_CURRENT_THRESHOLD) { g_motor.state = MOTOR_STATE_FAULT; Motor_Control_Stop(); } else if (g_motor.temp_value > OVER_TEMP_THRESHOLD) { g_motor.state = MOTOR_STATE_FAULT; Motor_Control_Stop(); } else if (speed_rpm < (int32_t)(TARGET_SPEED_RPM * 0.3f)) { // 转速明显低于目标,可判断为堵转 g_motor.state = MOTOR_STATE_FAULT; Motor_Control_Stop(); } break; case MOTOR_STATE_FAULT: // 故障状态下保持电机停止,可等待外部复位 Motor_Control_Stop(); break; } }

这个函数在主循环中每次执行一次,就完成了一次状态更新。需要注意,示例中的启动状态比较简单,实际项目中一般会加一个“启动计时器”,让电机在启动阶段有足够的提速时间,而不是立刻切换到运行状态。

6.3 堵转检测的补充说明

堵转检测是电机状态检测中最常见也最容易被忽视的一项。堵转时电机没有旋转,但电流很大,如果只靠电流判断,可能在负载本来就大的场合造成误判。更好的方法是结合速度反馈:当电机已经给出了较大 PWM 输出,但编码器反馈速度仍然很低甚至为零时,就可以判断为堵转。

在闭环控制中,堵转还可以通过 PID 控制器的输出变化来判断。如果 PID 输出已经达到上限,但实际速度依然无法提升,说明电机可能被卡住或负载过大。实际工程中,可以将电流阈值和速度阈值联合起来判断,降低误报率。

6.4 串口上报数据

为了在上位机快速查看检测结果,需要把状态数据通过串口发送出去。HAL 库中可以用HAL_UART_Transmit发送字符串。下面给出一个格式化输出函数:

// 文件路径:Core/Src/main.c 中的串口发送函数示例 #include "stdio.h" #include "usart.h" void Motor_Report_Status(void) { char buf[128]; const char* state_str = "UNKNOWN"; switch (g_motor.state) { case MOTOR_STATE_IDLE: state_str = "IDLE"; break; case MOTOR_STATE_STARTING: state_str = "STARTING"; break; case MOTOR_STATE_RUNNING: state_str = "RUNNING"; break; case MOTOR_STATE_FAULT: state_str = "FAULT"; break; } sprintf(buf, "State:%s, RPM:%d, Dir:%d, Current:%.2fA, Temp:%.1fC\r\n", state_str, (int)g_motor.speed_rpm, g_motor.direction, g_motor.current_value, g_motor.temp_value); HAL_UART_Transmit(&huart1, (uint8_t *)buf, strlen(buf), 100); }

在 main 函数中,可以每隔 500ms 调用一次这个上报函数。这样串口调试助手中会周期性地看到一行行电机状态数据,方便观察状态切换是否正常。

如果希望数据更结构化,可以考虑使用 JSON 格式输出,例如{"state":"RUNNING","rpm":120,"current":0.35}。这样上位机解析更方便,也可以直接接入 Python 或者 Qt 上位机。

6.5 主函数循环

把以上模块组合到 main.c 的 main 函数中,整体运行流程如下:

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_TIM2_Init(); MX_TIM3_Init(); MX_USART1_UART_Init(); Motor_Status_Init(); Motor_Control_Stop(); Motor_Speed_Init(); uint32_t last_report_time = HAL_GetTick(); while (1) { // 启动电机示意:这里直接给定 60% 转速 Motor_Control_SetSpeed(60); g_motor.state = MOTOR_STATE_STARTING; // 更新电机状态 Motor_Status_Update(); // 每 500ms 通过串口上报一次 if (HAL_GetTick() - last_report_time >= 500) { last_report_time = HAL_GetTick(); Motor_Report_Status(); } HAL_Delay(20); } }

需要注意的是,这个主循环中电机一直以 60% 速度运行,没有外部指令控制启停。实际项目中,可以根据按键、串口指令或者上位机命令来设置目标速度和启停状态。这里只是为了演示状态检测逻辑。

7. 常见问题与排查思路

7.1 编码器计数始终为 0

如果串口输出的转速始终是 0,首先检查编码器供电是否正常。很多编码器需要 5V 供电,但输出信号可能是 5V 电平,如果 STM32 使用的是 3.3V 引脚,可能需要加电平转换电路或者分压电阻。其次检查定时器是否配置为编码器模式,如果只是普通输入捕获模式,计数器不会自动加减。

另外还要检查编码器 A、B 相是否接对引脚。不同定时器的编码器通道是固定的,比如 TIM2 的 CH1 和 CH2 对应 PA0、PA1。如果接到 PB6、PB7,那就必须使用 TIM4 的编码器模式,否则无法工作。

7.2 ADC 采到的电流值跳变很大

电机运行过程中电流本身存在波动,尤其是 PWM 驱动方式下,绕组电流是脉动的。如果 ADC 采样时间点正好在 PWM 开关瞬间,读到的电压可能忽高忽低。解决方法包括:

  • 在采样电阻输出端加 RC 低通滤波;
  • 使用 ADC 多次采样取平均值;
  • 将 ADC 采样与 PWM 同步,在 PWM 周期中间时刻采样;
  • 使用 DMA 连续采集多个数据后软件滤波。

加滤波电容时要注意,电容太大会导致电流信号响应变慢,影响过流保护的实时性。一般 RC 截止频率根据 PWM 频率来设计。

7.3 电机转一段时间后自动停机并进入故障状态

这种情况大多是保护阈值设置不合理。启动瞬间电流会比正常运行电流大很多,如果过流阈值设置得过低,电机会在启动阶段被误判为故障。处理方式:

  • 将启动状态和运行状态分开判断;
  • 启动阶段可以放宽电流阈值,或者设置启动保护延时;
  • 通过串口打印当前的电流和转速数值,分析实际阈值范围后再调整。

7.4 串口数据乱码

乱码通常是波特率不一致造成的。检查 STM32CubeMX 中配置的波特率是否为 115200,同时检查串口调试助手的波特率是否一致。确认时钟配置是否正确,如果系统时钟不是 72MHz,USART 波特率计算公式会与实际值偏差,从而产生乱码。

7.5 电机运行不受控制

先检查 PWM 是否正常输出,可以用示波器看 PA6 引脚是否有方波信号。其次检查驱动模块的使能引脚是否已经设置为有效电平。L298N 的使能引脚如果没接,电机不会转动;TB6612 的 STBY 引脚也需要拉高才能进入工作状态。

8. 工程实践与扩展建议

8.1 代码分层与模块化

电机状态检测涉及外设较多,建议代码按模块拆分:

  • motor_control.c:负责 PWM、方向控制;
  • motor_speed.c:负责编码器读取和速度计算;
  • motor_adc.c:负责 ADC 读取和物理量换算;
  • motor_status.c:负责状态机判断和保护逻辑;
  • main.c:负责初始化、状态上报和主循环调度。

这样写的好处是,当更换电机型号或者驱动模块时,不需要修改整段业务逻辑,只需要调整对应模块的底层函数。对以后做更复杂的电机控制项目也很有帮助。

8.2 保护功能的优先级

在实际项目中,故障保护优先级应高于其他运行逻辑。当检测到过流、堵转或超温时,不仅要停止电机,最好还能记录故障发生时的运行参数。建议把故障标志和故障码保存下来,通过串口或 OLED 显示,方便后续排查。

加入故障锁定机制后,即使电机已经停止,电流恢复正常,也不能自动重新启动。必须由操作人员确认并手动复位,这样可以避免设备在无人值守时反复尝试启动,造成安全隐患。

8.3 从开环检测到闭环控制

本文主要围绕状态检测展开,但状态检测往往是闭环控制的基础。如果当前项目需要精确控制电机转速,可以在状态检测的基础上加入 PID 调速。基本思路是:

  1. 编码器测速获得实际转速;
  2. 将实际转速与目标转速的偏差输入 PID 控制器;
  3. PID 输出作为 PWM 占空比;
  4. 通过串口打印目标速度、实际速度和 PID 输出,用于调参。

再加上电流检测,还可以做电流环保护,限制电机最大电流。这样系统的可靠性会明显提升。

8.4 扩展无线状态监测

状态检测数据除了通过串口发送,还可以进一步扩展为无线传输。比如用 STM32 连接 ESP8266 或蓝牙模块,将电机状态上报到手机或上位机。如果是大型设备,还可以对接 Modbus 协议,定时上报电流、温度、转速等寄存器数据,方便 PLC 或工控系统读取。

不过要注意,无线模块和电机驱动电路之间的电源隔离和抗干扰设计需要重视。电机启停瞬间会产生较强的电磁干扰,如果电源处理不好,无线模块可能频繁掉线。

8.5 关于 STM32 开发环境的补充

在做 STM32 开发时,不少新手会在 Keil 工程搭建上花了很多时间。STM32CubeMX 生成代码之后,如果 Keil 中找不到头文件或者编译报错,可以检查 Target Options 中的 C/C++ Include Paths 是否已经包含所有需要的文件夹。也可以直接使用 STM32CubeIDE,它自带编译器和调试器配置,学习成本会低一些。

如果是从标准库切换到 HAL 库,注意有些外设宏定义不同,比如编码器模式配置、ADC 通道映射,建议先阅读对应型号的参考手册,再写代码。由于 STM32 系列型号很多,固件包版本也有差异,实际开发中遇到具体问题时,优先查看 ST 官方参考手册和 HAL 库头文件说明。

9. 总结与下一步学习方向

到这里,一个基于 STM32 单片机的电机状态检测方案就完整梳理了一遍。从系统设计、硬件选型、CubeMX 配置到代码实现,整个流程的核心点在于:通过编码器读取电机转速和方向,通过 ADC 读取电流和温度,再通过状态机对运行状态进行综合判断,最后通过串口把数据上报出来。这样电机就不再是一个“黑盒子”,而是可以实时监测和保护的执行机构。

如果你想把这个项目继续深入,下面几个方向可以优先尝试:

  • 加入 PID 闭环调速,让电机转速在不同负载下保持稳定;
  • 加入 OLED 屏幕,把实时状态显示在设备端;
  • 加入无线通信模块,实现远程状态监测;
  • 加入 PID 参数自动整定,减少手动调参时间;
  • 引入 FreeRTOS,将测速、状态检测、串口上报拆分成多个任务。

电机状态检测本身不复杂,但结合了定时器、ADC、串口、状态机等嵌入式开发中的常见知识点。把这一套流程跑通之后,再做更复杂的运动控制项目,比如平衡小车、机械臂关节、风机监测,会有一种“基础终于打牢了”的感觉。如果你也在做类似的单片机项目,不妨照着这个思路把状态检测模块加上,先让电机状态看得见,再谈更高级的控制策略。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 1:23:02

互斥锁管理之unique_lock学习

前面学习了std::lock_guard和std::scoped_lock,lock_guard的功能是自动解锁&#xff0c;能有效避免忘记unlock()的情况。但只能锁定一个互斥锁&#xff0c;scoped_lock比lock_guard高级&#xff0c;可以同时锁定多个互斥体。而unique_lock比它们更灵活。它‌独占管理互斥量所有…

作者头像 李华
网站建设 2026/9/1 1:22:22

基于微信小程序的景区实名核验与门票预约服务系统(程序+文档+讲解)

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/1 1:21:11

长视频AI总结工具盘点:2026年6款产品功能与使用场景

两三个小时的课程、访谈或会议视频&#xff0c;可以先让AI生成摘要、章节和思维导图&#xff0c;再回到逐字稿或时间位置查看细节。 这篇盘点6款长视频总结工具&#xff0c;帮助你快速了解它们能处理哪些长内容。 先看这几款工具工具支持版本这篇里主要能做什么Ai好记网页端、A…

作者头像 李华
网站建设 2026/9/1 1:21:09

2026年6款AI视频学习工具:从视频总结、问答到复习笔记

用AI学习视频&#xff0c;可以先快速看重点&#xff0c;再通过逐字稿、导图、画面笔记和问答补充细节&#xff0c;最后整理成自己的复习资料。 这篇盘点6款视频学习工具&#xff0c;看看它们分别覆盖学习过程中的哪些环节。 先看这几款工具工具支持版本这篇里主要能做什么Noteb…

作者头像 李华
网站建设 2026/9/1 1:20:20

如何实现千牛订单批量处理自动化?单机管理200+店铺零关联的底层架构

如何实现千牛订单批量处理自动化&#xff1f;单机管理200店铺零关联的底层架构 店群运营的本质不是开多少店&#xff0c;而是单店运营成本能不能压到零。千牛的订单批量处理&#xff0c;是店群运营中最耗人力也最容易出错的环节。 店群日均订单量大&#xff0c;打单发货是纯重…

作者头像 李华
网站建设 2026/9/1 1:19:23

onnxruntime-win-x86-1.16.2.zip 部署指南:32位Windows下的模型推理实践

简介&#xff1a;本资源是专为Windows 32位平台定制的ONNX Runtime C推理引擎分发包&#xff08;v1.16.2&#xff09;&#xff0c;面向C开发者及嵌入式AI部署工程师&#xff0c;解决在x86环境下的轻量级模型推理集成难题&#xff0c;尤其适用于资源受限设备、工业控制终端或需兼…

作者头像 李华