1. STM32电机控制系统概述
这个基于STM32的智能电机控制平台,是我在工业自动化项目中实际验证过的一套完整解决方案。系统通过STM32微控制器产生PWM信号驱动L298N电机驱动模块,同时采集光电编码器信号计算实时转速,并在板载LCD屏幕上显示。上位机采用LabVIEW开发,通过串口通信实时监控电机运行状态。
系统最核心的价值在于实现了两种控制算法的实时切换:经典PID控制和模糊PID控制。通过开发板上的物理按键,可以随时切换控制模式并调整电机转速,非常适合用于控制算法的教学演示和实际性能对比。
2. 硬件系统设计与关键配置
2.1 电机驱动电路设计
L298N作为经典的H桥驱动芯片,其接线方式直接影响系统可靠性。我的实际接线方案如下:
-
电源部分:
- 驱动电源12V直接接入L298N的VS引脚
- 逻辑电源5V取自STM32开发板的输出
- 务必在VS和GND之间并联1000μF电解电容
-
信号连接:
- STM32的PWM输出接L298N的ENA引脚
- IN1和IN2分别接PA6和PA7,用于控制转向
- OUT1和OUT2接电机两端
重要提示:电机外壳必须接地,否则PWM干扰会导致STM32频繁复位
2.2 PWM生成配置详解
TIM2的PWM配置代码虽然简单,但有几个关键参数需要特别注意:
c复制TIM_OC_InitTypeDef ocConfig;
ocConfig.OCMode = TIM_OCMODE_PWM1;
ocConfig.Pulse = 720; // 初始占空比50%
ocConfig.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim2, &ocConfig, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
参数计算过程:
- 定时器时钟72MHz
- 预分频器设为71,得到1MHz计数频率
- 自动重装载值ARR=99,得到10kHz PWM频率(1MHz/(99+1))
- 初始Pulse值720对应50%占空比(720/(99+1))
实际调试中发现,当占空比低于15%时电机容易进入死区,因此在代码中加入保护:
c复制void setMotorSpeed(uint8_t duty) {
if(duty < 15 && duty != 0) duty = 0;
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
}
3. 转速测量与滤波算法实现
3.1 光电编码器接口设计
系统采用增量式光电编码器,每转输出1个脉冲。虽然分辨率低,但通过优化算法仍可获得较好效果。接线方式:
- 编码器A相接PC13(EXTI13)
- 编码器电源接3.3V
- 必须加10kΩ上拉电阻
中断服务函数实现:
c复制void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
static uint32_t lastTick = 0;
if(GPIO_Pin == ENCODER_PIN) {
uint32_t currentTick = HAL_GetTick();
motorRpm = 60000 / (currentTick - lastTick); // 每转1脉冲的简化计算
lastTick = currentTick;
}
}
3.2 滑动窗口滤波算法
针对低速测量不准确的问题,我实现了基于环形队列的滑动窗口滤波:
c复制#define FILTER_WINDOW_SIZE 10
uint32_t timeQueue[FILTER_WINDOW_SIZE];
uint8_t queueIndex = 0;
void updateRpmFilter(uint32_t interval) {
timeQueue[queueIndex++] = interval;
if(queueIndex >= FILTER_WINDOW_SIZE) queueIndex = 0;
uint32_t sum = 0;
uint8_t validCount = 0;
for(uint8_t i=0; i<FILTER_WINDOW_SIZE; i++) {
if(timeQueue[i] != 0) {
sum += timeQueue[i];
validCount++;
}
}
if(validCount > 0) {
filteredRpm = 60000 * validCount / sum;
}
}
这个算法将转速计算误差降低了约70%,特别适合低速场合。
4. 控制算法实现与切换
4.1 经典PID控制器实现
PID结构体定义:
c复制typedef struct {
float Kp, Ki, Kd;
float integral;
float prevError;
uint32_t lastTime;
} PIDController;
float pidCompute(PIDController *pid, float setpoint, float input) {
uint32_t now = HAL_GetTick();
float dt = (now - pid->lastTime) / 1000.0f;
pid->lastTime = now;
float error = setpoint - input;
pid->integral += error * dt;
float derivative = (error - pid->prevError) / dt;
pid->prevError = error;
return pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative;
}
4.2 模糊PID控制器设计
模糊PID在经典PID基础上增加了模糊逻辑:
c复制typedef struct {
PIDController base;
float errorBound; // 误差边界值
float deltaKp, deltaKi, deltaKd; // 参数调整量
} FuzzyPIDController;
float fuzzyPidCompute(FuzzyPIDController *fpid, float setpoint, float input) {
float error = setpoint - input;
float normalizedError = fabs(error) / fpid->errorBound;
// 简单的模糊规则
if(normalizedError > 0.8f) {
fpid->base.Kp = fpid->base.Kp + fpid->deltaKp;
} else if(normalizedError > 0.4f) {
fpid->base.Ki = fpid->base.Ki + fpid->deltaKi;
} else {
fpid->base.Kd = fpid->base.Kd + fpid->deltaKd;
}
return pidCompute(&fpid->base, setpoint, input);
}
4.3 算法切换机制
通过状态机实现算法切换:
c复制typedef enum {PID_MODE, FUZZY_PID_MODE} CtrlMode;
CtrlMode currentMode = PID_MODE;
void switchControlMode() {
currentMode = (currentMode == PID_MODE) ? FUZZY_PID_MODE : PID_MODE;
pidReset(&pid); // 重置积分项
pidReset(&fuzzyPid.base);
}
void controlTask() {
float output;
if(currentMode == PID_MODE) {
output = pidCompute(&pid, targetRpm, filteredRpm);
} else {
output = fuzzyPidCompute(&fuzzyPid, targetRpm, filteredRpm);
}
setMotorSpeed((uint8_t)output);
}
5. 上位机通信与LabVIEW实现
5.1 STM32串口数据发送
采用简单的文本协议传输数据:
c复制void sendDataToPC() {
static uint32_t lastSend = 0;
if(HAL_GetTick() - lastSend >= 100) { // 100ms发送间隔
printf("RPM:%.1f,P:%.2f,I:%.2f,D:%.2f,M:%d\r\n",
filteredRpm, pid.Kp, pid.Ki, pid.Kd, currentMode);
lastSend = HAL_GetTick();
}
}
5.2 LabVIEW串口配置关键点
-
VISA配置:
- 波特率115200
- 数据位8
- 无校验
- 停止位1
- 必须启用TermChar并设置为\n
-
数据解析流程:
- 使用"匹配模式"函数提取数据
- 正则表达式:
RPM:([0-9.]+),P:([0-9.]+),I:([0-9.]+),D:([0-9.]+),M:([01])
-
波形显示优化:
- 使用双Y轴图表
- 左侧显示转速,右侧显示PID参数
- 添加模式切换指示灯
6. 系统调试经验与问题排查
6.1 常见问题及解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 电机抖动严重 | PWM频率过低 | 将频率提高到10kHz以上 |
| 转速显示不稳定 | 编码器信号干扰 | 添加0.1μF滤波电容 |
| LabVIEW数据丢失 | 串口缓冲区溢出 | 增加接收缓冲区大小 |
| 切换模式时电机暴走 | 积分项未重置 | 在切换时清除积分项 |
6.2 PID参数整定技巧
- 先调P:逐渐增大Kp直到系统开始振荡,然后取该值的50%
- 再调I:从Kp/100开始,逐步增加直到静差消除
- 最后调D:从Kp/10开始,抑制超调
- 模糊PID参数调整:
- deltaKp设为Kp的20%
- deltaKi设为Ki的10%
- deltaKd设为Kd的30%
6.3 抗饱和处理
为防止积分饱和,必须增加抗饱和逻辑:
c复制void pidCompute(PIDController *pid, float setpoint, float input) {
// ...原有计算...
// 抗饱和处理
if(output > MAX_OUTPUT) {
output = MAX_OUTPUT;
pid->integral -= error * dt; // 回退积分
} else if(output < MIN_OUTPUT) {
output = MIN_OUTPUT;
pid->integral -= error * dt;
}
}
7. 系统扩展与优化方向
- 增加网络接口:通过ESP8266实现WiFi远程监控
- 添加位置控制模式:实现精确的角度定位
- 改进编码器接口:使用正交编码器提高分辨率
- 开发手机APP:替代LabVIEW实现移动监控
- 增加安全保护:过流、堵转、过热检测
在实际项目中,我发现模糊PID在变负载情况下表现更稳定。特别是在电机负载突然变化时,传统PID需要约500ms恢复稳定,而模糊PID仅需200ms左右。不过模糊PID的参数调整更复杂,需要更多实际测试数据支持。