STM32CubeMX实战:基于霍尔编码器与L298N的直流电机闭环调速系统

STM32CubeMX实战:基于霍尔编码器与L298N的直流电机闭环调速系统

1. 项目背景与硬件选型

直流电机闭环调速系统在智能小车、工业自动化等领域应用广泛。这次我选择STM32F103作为主控芯片,搭配L298N电机驱动模块和霍尔编码器,构建一个完整的调速系统。这种组合性价比高,特别适合学生和电子爱好者入门学习。

L298N模块最大支持46V电压、2A持续电流,内置双H桥电路,能同时驱动两台直流电机。我在实际测试中发现,当驱动电压超过24V时,模块发热明显增加,建议工作电压控制在7-12V范围内。霍尔编码器采用磁感应原理,相比光电编码器更耐灰尘干扰,虽然精度稍低(通常每圈13-15个脉冲),但对于常规速度控制完全够用。

硬件连接有个关键细节:必须确保STM32、L298N和编码器三者共地。我曾因忘记共地导致电机转速异常,排查了半天才发现问题。建议用万用表先测量各模块GND之间的导通性。

2. STM32CubeMX工程配置

打开CubeMX新建工程时,建议直接搜索"STM32F103RB"选择对应型号。时钟配置采用默认的8MHz晶振,通过PLL倍频到72MHz主频。这里要注意在RCC配置中正确选择"HSE(外部高速时钟)"作为时钟源。

定时器配置是核心部分:

  1. TIM1通道1配置为PWM输出,用于电机调速
  2. TIM2配置为编码器接口模式,捕获AB相信号
  3. TIM3配置为10ms定时中断,用于速度采样

编码器模式有个易错点:CubeMX默认的"Encoder Mode"会自动启用四倍频。比如霍尔编码器每圈13个脉冲,实际会计数13×4=52个脉冲。我在首次测试时没注意这个细节,导致速度计算错误。

3. 电机驱动电路详解

L298N的接线需要特别注意电源配置:

  • 驱动电压(12V端子):接7-12V电源
  • 逻辑电压(5V端子):当驱动电压≥7V时,保留跳线帽使用板载5V
  • 使能端(ENA/ENB):拔掉跳线帽改用PWM控制

实测中发现一个有趣现象:当PWM占空比低于30%时,电机可能出现启动困难。解决方法是在代码中加入最小占空比限制,或者改用更先进的PID启动算法。

电机方向控制逻辑:

// 正转 HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, GPIO_PIN_RESET); // 反转 HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, GPIO_PIN_SET);

4. 编码器速度测量实现

霍尔编码器的AB相需要接到定时器的CH1和CH2引脚。在CubeMX中配置TIM2为"Encoder Mode"后,HAL库会自动处理脉冲计数。速度测量采用M法(频率法),即在固定时间间隔内统计脉冲数。

速度计算公式:

转速(rpm) = (ΔCount × 60) / (PPR × 采样周期(s) × 倍频数)

其中PPR(每转脉冲数)为13,倍频数为4。

代码实现关键点:

// 在定时器中断中读取编码器值 int32_t current_count = __HAL_TIM_GET_COUNTER(&htim2); int32_t delta = current_count - last_count; // 处理计数器溢出 if(delta > 32767) delta -= 65536; else if(delta < -32767) delta += 65536; last_count = current_count; // 计算转速 float speed_rpm = (delta * 60.0f) / (13 * 0.01 * 4);

5. PID调速算法实现

采用位置式PID算法,参数整定经验:

  • Kp:从0.1开始逐步增加,直到出现轻微振荡
  • Ki:设为Kp/10,消除静差
  • Kd:通常设为0,除非系统响应很慢

实际代码实现:

typedef struct { float Kp, Ki, Kd; float integral; float prev_error; } PID_Controller; float PID_Update(PID_Controller* pid, float error, float dt) { float proportional = pid->Kp * error; pid->integral += pid->Ki * error * dt; pid->integral = constrain(pid->integral, -100, 100); // 积分限幅 float derivative = pid->Kd * (error - pid->prev_error) / dt; pid->prev_error = error; return proportional + pid->integral + derivative; }

调试技巧:先用纯比例控制,等电机能基本跟随目标速度后再加入积分项。我在实验室测试时发现,加入微分项反而容易引起高频振荡,所以大多数情况下PI控制就足够了。

6. 系统集成与调试

将所有功能集成到主循环中:

  1. 每10ms读取编码器值计算速度
  2. 用PID算法计算PWM占空比
  3. 更新PWM输出
  4. 通过串口打印调试信息

常见问题排查:

  • 电机不转:检查使能端是否接PWM,IN1/IN2电平组合
  • 速度测量不准:确认编码器PPR和倍频设置
  • 电机振动:降低PID参数或增加死区补偿

一个实用的调试技巧:先用固定占空比让电机运转,确认编码器读数正常后再启用闭环控制。我在开发过程中就曾因编码器接线错误,导致PID控制完全失效。

7. 性能优化技巧

经过多次测试,总结了几个提升系统性能的方法:

  1. 在TIM2的编码器中断中加入防溢出处理:
if(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); if(encoder_dir == 1) overflow_count++; else overflow_count--; }
  1. 使用移动平均滤波平滑速度采样:
#define FILTER_SIZE 5 float speed_buffer[FILTER_SIZE]; float filtered_speed = 0; // 在中断中更新 for(int i=FILTER_SIZE-1; i>0; i--) { speed_buffer[i] = speed_buffer[i-1]; } speed_buffer[0] = current_speed; filtered_speed = 0; for(int i=0; i<FILTER_SIZE; i++) { filtered_speed += speed_buffer[i]; } filtered_speed /= FILTER_SIZE;
  1. 增加PWM死区补偿:当目标速度很小时,直接关闭电机驱动,避免"嗡嗡"声。

8. 扩展应用与改进方向

这个基础框架可以扩展更多实用功能:

  1. 加入速度曲线规划,实现平滑加减速
  2. 通过蓝牙或Wi-Fi接入无线控制
  3. 增加电流检测实现力矩控制

我在最近的项目中尝试加入了模糊PID控制,相比传统PID在变负载情况下表现更好。具体做法是根据速度误差和误差变化率动态调整PID参数。

对于更高精度的应用,可以考虑:

  1. 改用光电编码器(每圈数百脉冲)
  2. 使用FOC驱动方案替代L298N
  3. 升级到STM32F4系列,运行更复杂算法

这套系统虽然简单,但涵盖了嵌入式开发的完整流程:硬件选型、外设配置、算法实现和调试优化。建议初学者可以先用示波器观察PWM波形和编码器信号,加深对系统工作原理的理解。