尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

基于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 功能需求拆分在设计电机状态检测系统之前先把需求拆清楚电机能启动和停止支持正反转控制。能实时读取电机转速和转向。能采集电机工作电流。能采集电机或驱动板温度。能通过串口将状态数据发送到上位机方便调试。当电流过大、转速异常或者温度过高时进入故障保护状态。这样一个系统并不复杂但涉及了 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 的 CH1PA0和 CH2PA1连接电机编码器 A/B 相PWM 输出使用 TIM3 的 CH1PA6控制电机速度驱动模块的方向引脚接 PB0、PB1电流采样输出接 PA4ADC1_IN4温度采样输出接 PA5ADC1_IN5串口 USART1 的 TXPA9、RXPA10接串口模块。具体引脚不一定要照搬可以根据自己手头的板子调整。关键是把编码器引脚接到支持编码器模式的定时器上比如 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 的 PWM72MHz 时钟下可以设置 Prescaler0Counter Period3600-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_Start加HAL_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 判断容易出现逻辑混乱。比如电机启动过程中电流本来就比正常运行大如果直接按“电流过大就报警”处理电机一启动就会误报。所以更推荐用状态机来管理。本文示例将电机状态分为四个状态空闲状态电机不转等待启动指令启动状态电机正在提速电流和转速都处于动态变化中运行状态电机速度达到目标值持续检测各项参数故障状态检测到过流、堵转或超温停止电机并报警。状态转移的条件参考如下状态机逻辑可以用一个函数实现并在主循环中周期性调用。这样代码结构清晰后续增加“软启动”“减速停止”等状态也比较容易。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同时检查串口调试助手的波特率是否一致。确认时钟配置是否正确如果系统时钟不是 72MHzUSART 波特率计算公式会与实际值偏差从而产生乱码。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 调速。基本思路是编码器测速获得实际转速将实际转速与目标转速的偏差输入 PID 控制器PID 输出作为 PWM 占空比通过串口打印目标速度、实际速度和 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、串口、状态机等嵌入式开发中的常见知识点。把这一套流程跑通之后再做更复杂的运动控制项目比如平衡小车、机械臂关节、风机监测会有一种“基础终于打牢了”的感觉。如果你也在做类似的单片机项目不妨照着这个思路把状态检测模块加上先让电机状态看得见再谈更高级的控制策略。
返回列表