STM32电机状态检测系统实战:ADC采样与转速测量完整教程
2026/9/1 1:24:06 网站建设 项目流程

先问一个很实际的问题:做单片机项目时,你是怎么判断电机“现在转得正不正常”的?

很多初学者做电机类项目,只会给电机通电,让它转起来,然后发现转速不对、电流偏大、电机发烫,却完全不知道问题出在哪一步。要解决这个问题,就需要给电机加一套“体检系统”,实时检测它的电压、电流、转速和温度,再让 STM32 根据这些数据判断电机当前处于什么状态。

这篇教程就围绕“STM32 单片机电机状态检测”这个主题展开。我会从硬件方案选型开始讲,然后给出基于 STM32 的 ADC 多通道采集、电机参数计算、状态判断的完整代码,最后补充常见问题的排查思路和工程实践建议。无论是做毕业设计、课程设计,还是准备电赛、蓝桥杯嵌入式项目,这套思路都可以直接复用。

1. 电机状态检测:到底要检测什么

1.1 什么是电机状态检测

电机状态检测,简单说就是通过传感器和单片机,实时获取电机运行过程中的关键物理量,然后判断电机是否处于正常工作状态。

电机不是只要通电就会好好转的。它可能遇到这些情况:

  • 电源电压跌落,电机力矩不足,转速下降。
  • 电机堵转,电流急剧上升,绕组发热。
  • 负载变化,导致电流波动、转速不稳定。
  • 电机轴承磨损,振动异常,噪音增大。
  • 长时间运行导致温度过高,可能烧毁绕组。

如果不做状态检测,这些隐患只能在电机彻底损坏之后被发现。而做好状态检测,就能提前感知异常,及时停机或报警。

1.2 状态检测的核心参数

一个比较完整的电机状态检测系统,通常关注以下几个参数:

参数检测方式反映的问题
电源电压电阻分压 + ADC供电是否正常
工作电流采样电阻 / 电流传感器 + ADC是否过流、堵转
转速霍尔传感器 / 编码器 / 红外对射是否失速、停转
温度NTC 热敏电阻 / DS18B20是否过热
振动振动传感器(如 SW-420)机械故障

对于入门项目,优先做“电压 + 电流 + 转速”三合一检测,既能覆盖大部分异常情况,硬件成本也不高。温度可以作为扩展项。

1.3 为什么用 STM32 做这个项目

很多人会问:“51 单片机不也能做 ADC 检测吗?为什么选 STM32?”

51 单片机确实可以做,但 STM32 的优势很明显:

  • STM32 的 ADC 是 12 位分辨率,精度远高于 51 单片机常用的 8 位/10 位 ADC。
  • STM32 支持多通道扫描,可以同时采集电压、电流、温度等多路信号。
  • STM32 主频高,可以跑更复杂的滤波算法,比如滑动平均滤波、卡尔曼滤波(入门可以先不做)。
  • STM32 外设丰富,可以一边采集 ADC,一边输出 PWM 控制电机,还能通过串口、CAN、OLED 显示屏和人交互。
  • 做毕业设计或项目开发时,STM32 的工程资料和参考代码更多,遇到问题更容易解决。

所以,基于 STM32 做电机状态检测,既是学习单片机外设的好例子,也是一个具有工程价值的完整项目。

2. 硬件设计与方案选型

2.1 系统整体架构

整个电机状态检测系统的硬件可以分成四个部分:

  1. 电机驱动部分:STM32 输出 PWM,通过驱动芯片(如 L298N、TB6612、DRV8833)或 MOS 管驱动电机。
  2. 信号采集部分:电压采样电路、电流采样电路、转速传感器。
  3. 主控部分:STM32 最小系统板,负责 ADC 采集、数据处理、逻辑判断。
  4. 人机交互部分:OLED 显示屏显示数据,按键设置阈值,蜂鸣器报警,串口输出调试信息。

结构图可以用文字表述,大致是这样的:

电机 ← 驱动电路 ← STM32 PWM 输出 ↑ ↑ 传感器信号 → 信号调理电路 → STM32 ADC 输入 ↓ OLED / 串口 / 蜂鸣器

2.2 电压检测方案

电机工作电压通常是 5V、12V 或 24V。STM32 的 ADC 输入范围一般是 0~3.3V,所以不能直接把电源电压接到 ADC 引脚。

最常用的方案是电阻分压:

Vmotor ---[R1]---+---[R2]--- GND | ADC引脚

选择 R1 和 R2 的比例,让分压后的电压落在 0~3.3V 范围内。

比如检测 12V 电压,可以取 R1 = 30K,R2 = 10K,分压比是 10 / (30 + 10) = 0.25。12V × 0.25 = 3V,在 ADC 量程内。

需要注意的是,分压电阻的精度会影响测量结果,工程上最好用 1% 精度的电阻。

2.3 电流检测方案

电流检测常见有两种思路:

方法一:采样电阻法

在电机供电回路中串联一个低阻值采样电阻,比如 0.1Ω。电流流过采样电阻时会产生压降,通过运放放大后送到 ADC。

这个方案在直流电机项目中非常常用。核心公式是:

U = I × R

如果电流是 1A,采样电阻是 0.1Ω,压降就是 0.1V。这个电压比较小,通常需要用运放放大后送入 ADC。

方法二:ACS712 电流传感器模块

ACS712 是 Allegro 公司生产的霍尔电流传感器,不需要串联采样电阻,可以直接把电流转换为电压输出。使用很方便,模块输出接 ADC 引脚就能采集。

ACS712 有不同量程,常见的有 5A、20A、30A 版本。用 ACS712 的话,代码里要按模块的灵敏度做换算。

2.4 转速检测方案

转速检测是电机状态检测里比较有意思的部分。常用方案:

方案原理优点缺点
霍尔传感器(A3144)电机轴上贴磁铁,霍尔传感器感知磁场变化非接触,耐用需要贴磁铁
红外对射模块电机轴上装遮光片,每转一圈遮挡一次简单,低成本容易受环境光干扰
光电编码器输出两路正交脉冲可以测方向,精度高成本较高
电机自带霍尔输出有些电机内部集成霍尔信号线直接读取脉冲不是所有电机都有

入门推荐用“霍尔传感器 + 小磁铁”,或者在转轴上贴一个黑白相间的码盘配合红外对射,然后使用 STM32 的定时器输入捕获或外部中断统计脉冲数。

转速的计算思路:

每分钟转速 = 每秒脉冲数 × 60 / 每转脉冲数

比如每转产生 1 个脉冲,1 秒内数到 10 个脉冲,那么转速就是 10 × 60 / 1 = 600 RPM。

3. 环境准备与工程搭建

3.1 硬件清单

器件说明
STM32 开发板推荐 STM32F103C8T6 最小系统板,性价比高
直流电机12V 直流减速电机或普通直流电机
电机驱动模块TB6612 或 L298N
ACS712 电流传感器5A 或 20A 量程均可
OLED 显示屏0.96 寸 SSD1306,I2C 接口
蜂鸣器模块有源蜂鸣器,低电平触发或高电平触发按模块而定
电阻若干做分压用
杜邦线、面包板接线用

3.2 软件环境

我用的是比较通用的开发环境组合:

  • Keil MDK5(也可以使用 STM32CubeIDE)
  • STM32CubeMX(用于生成初始化代码)
  • STM32 标准外设库或者 HAL 库都可以,本文示例以 HAL 库为主

如果你已经安装了 Keil5,但是还没有添加 STM32 芯片支持包,需要先在 STM32 官网下载对应芯片的 Pack 包,或者在 Keil 的 Pack Installer 里在线安装。安装完成后才能新建 STM32 工程。

版本相关的提醒:不同版本的 HAL 库在 API 名称上基本一致,但个别参数可能有差异。本文代码以最常见的 STM32F1 系列 HAL 库为例,如果你用的是其他系列,注意检查 ADC 时钟配置和引脚定义。

3.3 新建工程的整体思路

这个项目里用到的 STM32 外设主要有:

外设用途
GPIO控制电机驱动方向引脚、蜂鸣器
TIM输出 PWM 控制电机速度
ADC采集电压、电流信号
TIM + 外部中断测转速
I2C驱动 OLED 显示
USART串口打印调试信息

在 CubeMX 里,你需要做以下配置:

  1. 选择芯片型号,比如 STM32F103C8T6。
  2. 配置时钟树,将系统主频设置为 72MHz。
  3. 配置 ADC1,启用两个或三个通道。
  4. 配置一个定时器输出 PWM。
  5. 配置另一个定时器或外部中断引脚用于转速脉冲计数。
  6. 配置 I2C1 用于 OLED。
  7. 配置 USART1 用于串口调试。

4. 核心原理与数据处理

4.1 ADC 采样原理

STM32 的 ADC 是逐次逼近型 ADC。它将模拟电压转换为一个 12 位的数字值,范围是 0~4095。

转换公式:

ADC值 = 输入电压 / 参考电压 × 4096

反过来,从 ADC 值算回电压:

输入电压 = ADC值 × 参考电压 / 4096

如果参考电压是 3.3V,那么:

输入电压 = ADC值 × 3.3 / 4096

示例:

ADC值 = 2048 → 输入电压 = 2048 × 3.3 / 4096 = 1.65V

多通道采集时,STM32 可以配置为扫描模式,在通道之间按顺序轮询。缺点是每次只能读一个通道的结果,必须等待转换完成后再启动下一次转换。

4.2 数据处理:为什么要滤波

ADC 采集到的原始数据往往带有噪声,原因是电机运行时电刷会产生火花,引入电磁干扰。如果不做任何处理,ADC 数值会在真实值附近剧烈跳动,导致电压电流显示不准、误报警。

最简单的处理方法是滑动平均滤波。核心思路是维护一个长度为 N 的数组,每次采集到新值就放入数组末尾,去掉最前面的值,然后求平均。

滑动平均滤波的优点:

  • 算法简单,计算量小,适合 STM32。
  • 能有效抑制周期性噪声。
  • 实时性较好。

4.3 电机状态判断逻辑

采集到电压、电流、转速之后,不能只看单个数据,还需要综合判断。

常用的判断逻辑:

如果 电流 > 过流阈值: 状态 = 过流 / 堵转 否则如果 转速 < 失速阈值: 状态 = 失速 / 停转 否则如果 电压 < 欠压阈值: 状态 = 欠压 否则: 状态 = 正常运行

这里要注意优先级。比如电机堵转时,转速为 0,电流很大。此时如果先判断转速,会得到“停转”结论,但根本原因是堵转。所以应该先判断电流是否过大,再判断转速是否过低。

判断逻辑需要添加“连续多次满足条件才确认异常”的防抖机制。比如电流连续 5 次超过阈值,才认为真的过流了。这样可以避免瞬时波动导致的误判。

5. 完整实战:STM32 电机状态检测系统

下面进入本教程的核心部分。我会按照实际开发流程,从 CubeMX 配置到代码编写一步步走。

5.1 项目硬件接线

先明确接线方式。这里以 STM32F103C8T6 为例:

模块STM32 引脚
电机驱动 PWMAPA0(TIM2_CH1)
电机驱动 AIN1PA1
电机驱动 AIN2PA2
ACS712 输出PA4(ADC1_IN4)
电源分压输出PA5(ADC1_IN5)
OLED SCLPB6
OLED SDAPB7
霍尔传感器信号PB0(外部中断)
蜂鸣器PB1
串口 TXPA9
串口 RXPA10

注意:默认直流电机驱动中的方向引脚逻辑,请根据你实际使用的驱动模块调整。TB6612 与 L298N 的方向控制引脚电平定义可能不同。

5.2 STM32CubeMX 关键配置

在 CubeMX 中逐步完成以下设置。

RCC 配置:

选择 HSE(外部高速时钟)为 Crystal/Ceramic Resonator。

时钟树:

将 HCLK 设置为 72MHz,APB1 分频为 36MHz,APB2 分频为 72MHz。ADC 时钟不要超过 14MHz,所以 ADC 分频选择 PCLK2/6。

ADC1 配置:

  • 开启通道 4(PA4)
  • 开启通道 5(PA5)
  • 扫描模式:Enable(多通道必须打开)
  • 连续转换:Disable(这里我们用软件触发单次转换)
  • 采样时间:设置为 55.5 Cycles 或更大,保证采样精度

注意:如果使用 HAL 库,多通道 ADC 还可以使用 DMA 模式。本文为了降低初学难度,先使用“连续采样 + 软件切换通道”的方式。

TIM2 配置:

  • Channel1:PWM Generation CH1
  • 预分频 PSC:72-1(得到 1MHz 计数频率)
  • 自动重装载 ARR:9999(得到 100Hz PWM,你也可以根据电机驱动要求调整)
  • 初始占空比:50%

GPIO 配置:

  • PA1、PA2:输出,用于控制电机方向
  • PB0:外部中断模式(上升沿触发),用于转速脉冲计数
  • PB1:输出,用于控制蜂鸣器

I2C1 配置:

  • 开启 I2C,默认 100kHz 即可。

USART1 配置:

  • 异步模式
  • 115200-8-N-1

5.3 编写代码

CubeMX 生成的代码已经完成了底层初始化。我们只需要在用户代码区域补充业务逻辑。

5.3.1 定义全局变量和数据结构

/* USER CODE BEGIN PV */ #define ADC_SAMPLE_TIMES 10 typedef enum { MOTOR_STOP = 0, MOTOR_RUNNING, MOTOR_OVER_CURRENT, MOTOR_STALL, MOTOR_UNDER_VOLTAGE } MotorState; MotorState motor_state = MOTOR_STOP; uint16_t adc_buffer[ADC_SAMPLE_TIMES][2]; uint8_t adc_index = 0; float motor_voltage = 0.0f; float motor_current = 0.0f; volatile uint32_t pulse_count = 0; volatile uint8_t speed_timer_flag = 0; float motor_speed_rpm = 0.0f; /* USER CODE END PV */

5.3.2 启动外部中断和定时器

在主函数中初始化外设后,需要启动外部中断和用于测速的定时器。

这里用 SysTick 来产生 1 秒的时基,每秒计算一次转速。

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_TIM2_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); HAL_ADC_Start(&hadc1); // 使能外部中断,用于霍尔传感器脉冲计数 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_NVIC_EnableIRQ(EXTI0_IRQn); OLED_Init(); OLED_Clear(); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 5000); // 初始占空比50% HAL_UART_Transmit(&huart1, (uint8_t*)"Motor Detect System Start\r\n", 27, 100); /* USER CODE END 2 */ while (1) { // 主循环逻辑 Motor_Task(); HAL_Delay(50); } }

注意:OLED_Init()是 SSD1306 显示屏驱动函数,具体实现取决于你使用的 OLED 库,这里不做展开。如果你不用 OLED,可以把显示部分去掉,只保留串口输出。

5.3.3 ADC 多通道采样函数

/* 获取指定ADC通道的值 */ uint16_t ADC_GetChannelValue(uint32_t channel) { ADC_ChannelConfTypeDef sConfig = {0}; uint16_t value = 0; sConfig.Channel = channel; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { return 0; } HAL_ADC_Start(&hadc1); if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) { value = HAL_ADC_GetValue(&hadc1); } HAL_ADC_Stop(&hadc1); return value; }

这段代码的核心思路是:每次读取之前,把 ADC 切换到指定通道。虽然效率不高,但对于电机状态检测这种低速场景完全够用。

5.3.4 滑动平均滤波

/* 滑动平均滤波,data是历史缓冲区,new_value是新采样值,n是缓冲区长度 */ uint16_t MovingAverage(uint16_t *data, uint16_t new_value, uint8_t n) { uint32_t sum = 0; uint8_t i = 0; for (i = 0; i < n - 1; i++) { data[i] = data[i + 1]; } data[n - 1] = new_value; for (i = 0; i < n; i++) { sum += data[i]; } return (uint16_t)(sum / n); }

这里需要注意,sum使用uint32_t,避免 ADC 值求和时溢出。ADC 12 位最大值 4095,10 次累加最大 40950,在 uint32_t 范围内,但养成用更大类型存储求和结果的习惯是好的。

5.3.5 电压和电流换算

#define VREF 3.3f #define ADC_RES 4096.0f // 分压电阻参数:R1=30K,R2=10K #define VOLTAGE_DIVIDER_RATIO (30.0f + 10.0f) / 10.0f // ACS712 灵敏度:使用 5A 量程模块时约为 185mV/A #define ACS712_SENSITIVITY 0.185f #define ACS712_ZERO_OFFSET 2.5f float ADC_To_Voltage(uint16_t adc_value) { return (float)adc_value * VREF / ADC_RES; } float Cal_Voltage(uint16_t adc_value) { float v_adc = ADC_To_Voltage(adc_value); return v_adc * VOLTAGE_DIVIDER_RATIO; } float Cal_Current(uint16_t adc_value) { float v_adc = ADC_To_Voltage(adc_value); return (v_adc - ACS712_ZERO_OFFSET) / ACS712_SENSITIVITY; }

ACS712 模块在零电流时输出电压大约是 VCC/2(2.5V),电流为正时电压升高,为负时电压降低。所以电流 = (实际电压 - 2.5V) / 灵敏度。

注意:ACS712 的灵敏度与模块量程有关,5A 模块约为 185mV/A,20A 模块约为 100mV/A,30A 模块约为 66mV/A。如果你的模块是别的量程,需要查模块数据手册。

5.3.6 转速计算

转速测量使用外部中断统计霍尔脉冲数,每秒读取一次。

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_0) { pulse_count++; } } void Speed_Calculate(void) { if (speed_timer_flag == 1) { speed_timer_flag = 0; // 假设每转1个脉冲,如果你的电机每转多个脉冲,需要调整除数 motor_speed_rpm = (float)(pulse_count * 60); pulse_count = 0; } }

这里用 SysTick 中断设置speed_timer_flag每 1 秒置 1。

SysTick_Handler中增加:

void SysTick_Handler(void) { /* USER CODE BEGIN SysTick_IRQn 0 */ static uint32_t tick_count = 0; tick_count++; if (tick_count >= 1000) // 1秒 { tick_count = 0; speed_timer_flag = 1; } /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); }

需要注意,pulse_count是 volatile 变量,在中断中累加,在主循环中读取清零,这样在高速计数时可以减少主循环被打断的影响。

5.3.7 电机状态判断

void Motor_State_Update(void) { if (motor_current > 1.5f) // 过流阈值,按实际电机调整 { motor_state = MOTOR_OVER_CURRENT; } else if (motor_speed_rpm < 100.0f) // 失速阈值 { motor_state = MOTOR_STALL; } else if (motor_voltage < 10.5f) // 欠压阈值 { motor_state = MOTOR_UNDER_VOLTAGE; } else { motor_state = MOTOR_RUNNING; } }

5.3.8 主任务函数

void Motor_Task(void) { uint16_t voltage_adc = 0; uint16_t current_adc = 0; char msg[64]; // 采集电流通道(PA4) current_adc = ADC_GetChannelValue(ADC_CHANNEL_4); current_adc = MovingAverage(&adc_buffer[0][0], current_adc, ADC_SAMPLE_TIMES); // 采集电压通道(PA5) voltage_adc = ADC_GetChannelValue(ADC_CHANNEL_5); voltage_adc = MovingAverage(&adc_buffer[0][1], voltage_adc, ADC_SAMPLE_TIMES); // 换算物理量 motor_voltage = Cal_Voltage(voltage_adc); motor_current = Cal_Current(current_adc); // 计算转速 Speed_Calculate(); // 更新状态 Motor_State_Update(); // 串口打印 sprintf(msg, "V:%.2fV I:%.2fA Speed:%.0fRPM State:%d\r\n", motor_voltage, motor_current, motor_speed_rpm, motor_state); HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 100); // 异常报警 if (motor_state == MOTOR_OVER_CURRENT || motor_state == MOTOR_STALL) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); // 蜂鸣器响 } else { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); // 蜂鸣器关 } }

5.4 编译与下载

在 Keil 中完成编译后,使用 ST-Link 下载程序。

下载时选择 ST-Link Debugger,如果出现下载失败,常见原因是:

  • 芯片没有连接好。
  • ST-Link 驱动没装好。
  • Keil 中 Flash Download 配置里没有选择正确的 Flash 算法。

使用 ST-Link Utility 也可以下载,但日常开发建议在 Keil 里直接 F8 下载,配合调试功能更高效。

5.5 预期运行结果

程序下载成功后,打开串口助手,波特率设为 115200,能够看到类似下面的输出:

V:12.10V I:0.35A Speed:1200RPM State:2 V:12.08V I:0.38A Speed:1198RPM State:2 V:12.11V I:1.20A Speed:900RPM State:1 V:12.09V I:1.80A Speed:50RPM State:1

这里 State 的含义:

含义
0电机停止
1过流/堵转
2正常运行
3失速
4欠压

正常运行时,电压稳定在电源电压附近,电流较小,转速稳定在一个值。用手捏住电机轴模拟堵转,电流迅速上升,串口输出的 State 会变为过流状态,蜂鸣器报警。

6. 常见问题与排查思路

6.1 ADC 采集值跳动很大

问题现象常见原因解决思路
ADC 数值不稳定,上下跳动几十甚至几百电机干扰、采样时间过短增加采样时间,改用滑动平均滤波
ADC 数值整体偏高参考电压不稳定检查 VREF 引脚供电,使用独立 3.3V 基准
某一通道数据为零通道配置错误检查通道号是否对应正确引脚

6.2 电机不转

问题现象常见原因解决思路
电机完全不动PWM 没有输出检查 TIM 配置是否正确,确认占空比不是 0
电机嗡嗡响但不转驱动方向引脚电平错误检查 AIN1、AIN2 的高低组合
电机电压不足电源功率不够使用独立电机电源,不要用 STM32 USB 供电

6.3 转速显示为 0

问题现象常见原因解决思路
转速一直是 0霍尔传感器信号没接对检查霍尔传感器输出脚是否接在 PB0
转速偶尔跳变磁铁离霍尔传感器太远调整磁铁和霍尔传感器的距离
转速偏大或偏小每转脉冲数设置不对确认电机每转产生几个脉冲,调整除法因子

6.4 OLED 不显示

问题现象常见原因解决思路
OLED 黑屏I2C 地址不对确认 SSD1306 地址是 0x3C 还是 0x3D
OLED 显示乱码接线接触不良检查 SCL、SDA 是否接反
程序卡在等待标志位I2C 总线被占用重新上电,检查超时设置

6.5 电流显示负值

如果 ACS712 在没有电流时输出低于 2.5V,电流计算会得到负数。解决方法是给模块增加一个校准步骤:

float current_offset = 0.0f; void Current_Calibration(void) { uint16_t adc_value = ADC_GetChannelValue(ADC_CHANNEL_4); float v_adc = ADC_To_Voltage(adc_value); current_offset = v_adc; } float Cal_Current_Calibrated(uint16_t adc_value) { float v_adc = ADC_To_Voltage(adc_value); return (v_adc - current_offset) / ACS712_SENSITIVITY; }

在电机启动前调用一次校准函数,把零电流时的输出电压记录为偏移量。

7. 最佳实践与工程建议

7.1 电源设计

电机启动瞬间电流很大,如果和 STM32 共用一个电源,会导致单片机复位或 ADC 采集异常。正确的做法是:

  • 电机电源和逻辑电源分开。
  • 两个电源共地。
  • 电机电源加大电容,减少启动瞬间电压跌落。

如果要用一个电源给全系统供电,建议选择功率足够的电源,并在电机电源输入端并联一个大电容和一个 0.1uF 小电容。

7.2 采样稳定性

工程中提升 ADC 采样稳定性的常用方法:

  • 在 ADC 引脚对地并联一个 0.1uF 滤波电容或 10nF 电容。
  • 采样时间尽量长一点,比如 55.5 Cycles 或 239.5 Cycles。
  • 软件上使用滑动平均滤波,不建议使用简单的单次采样。
  • 对于要求更高的场景,可以考虑 DMA 采样模式加环形缓冲区。

7.3 阈值不要写死

电机参数会随着负载、电源电压变化,把阈值写死在代码里不利于项目扩展。建议:

  • 在文件开头用宏定义集中管理阈值。
  • 通过按键或串口指令在线修改阈值。
  • 关键阈值保存到 Flash 或 EEPROM,掉电不丢失。

例如:

#define OVER_CURRENT_THRESHOLD 1.5f #define STALL_SPEED_THRESHOLD 100.0f #define UNDER_VOLTAGE_THRESHOLD 10.5f

这样后续调整参数只需要修改宏定义。

7.4 状态显示要直观

不要只在串口输出状态码,建议在 OLED 上显示对应的文字状态,方便现场调试。比如:

  • 运行正常
  • 电流过大
  • 电机堵转
  • 电压偏低

同时可配合不同颜色的 LED 或蜂鸣器声音频率做区分。

7.5 添加保护动作

真正的电机保护系统不能只报警,还需要自动停机。在检测到过流或堵转时,应立即关闭 PWM 输出,否则电机可能烧毁。

if (motor_state == MOTOR_OVER_CURRENT || motor_state == MOTOR_STALL) { HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1); // 也可以直接设置占空比为0 __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0); } else { HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); }

7.6 调试信息分级

开发阶段可以通过串口输出所有调试信息,比如 ADC 原始值、滤波后的值、计算后的物理量、状态判断结果。但在实际项目部署时,应减少无用的日志输出,避免大量串口打印占用 CPU 时间。

8. 总结与学习路线

到这里,一个基于 STM32 的电机状态检测系统就完整落地了。

回顾一下完成的工作:

  • 理解了电机状态检测的常见参数和应用场景。
  • 掌握了电压分压采集、电流传感器、霍尔测速的硬件方案。
  • 在 STM32F103 上配置了 ADC 多通道采集。
  • 添加了滑动平均滤波,提高了数据稳定性。
  • 实现了电压、电流、转速的物理量换算。
  • 编写了电机状态判断逻辑和报警保护功能。
  • 通过串口和 OLED 实时显示电机运行状态。

如果你是用 51 单片机做类似项目,原理也相通,只是 ADC 精度和代码组织上要适当调整。

接下来想继续深入,可以从这几个方向选一个:

  • 学习 PID 闭环控制,把状态检测和电机调速结合起来。
  • 学习 CAN 总线通信,把状态数据上传到上位机或车载网络。
  • 学习 FreeRTOS,把 ADC 采集、数据显示、状态判断放到不同任务中运行。
  • 学习无刷直流电机(BLDC)控制,用 STM32 检测反电动势实现电机换相。

电机状态检测是嵌入式开发中一个很典型的多外设综合项目,它把 GPIO、定时器、ADC、中断、串口、显示全都串起来了。做完这个项目,你对 STM32 的整体掌握程度会上一个台阶。建议自己动手把硬件搭起来,亲手改一遍代码,观察不同工况下的数据变化,这比只看文章理解得深刻得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询