4. 电机控制算法:PID控制原理、速度曲线规划(梯形/S形)、堵转检测与保护逻辑
各位同学,今天我们来聊聊窗帘电机最核心的部分——控制算法。说实话,这部分内容我当年刚入行时也觉得挺玄乎的,什么PID、什么曲线规划,听着就头大。但做久了你会发现,这些东西说白了就是让电机听话的技术。
我个人习惯把电机控制分成三个层次:怎么转得稳(PID)、怎么转得顺(曲线规划)、转不动怎么办(堵转保护)。咱们一个一个来拆解。
4.1 PID控制原理——让电机精准听话
先问大家一个问题:你让电机转1000转/分钟,它真的能稳稳停在1000转吗?
答案是不能。负载变化、电压波动、摩擦力……这些因素都会让实际转速偏离目标值。那怎么办?PID就是干这个的。
PID的全称是比例-积分-微分控制。我给大家一个最直观的理解:
- P(比例):看到偏差就使劲拉回来。偏差越大,出力越大。但光有P,容易过头,产生震荡。
- I(积分):把过去的偏差累积起来。如果一直有微小偏差,I会慢慢加大力度,直到消除静差。
- D(微分):预测未来的趋势。如果偏差变化太快,D会提前刹车,防止超调。
我在项目中遇到过这样一个情况:用纯P控制窗帘电机,结果窗帘在目标位置来回晃悠,像在跳舞。后来加了D项,立马稳了。嗯,这就是微分的作用——提前感知变化趋势。
下面是一个典型的增量式PID实现,我在STM32上跑过无数次:
typedef struct {
float Kp;
float Ki;
float Kd;
float target; // 目标值
float last_err; // 上一次误差
float integral; // 积分累积
} PID_Controller;
float PID_Update(PID_Controller *pid, float current) {
float err = pid->target - current;
// 比例项
float p_out = pid->Kp * err;
// 积分项(带限幅,防止积分饱和)
pid->integral += err;
if (pid->integral > INTEGRAL_LIMIT) pid->integral = INTEGRAL_LIMIT;
if (pid->integral < -INTEGRAL_LIMIT) pid->integral = -INTEGRAL_LIMIT;
float i_out = pid->Ki * pid->integral;
// 微分项
float d_out = pid->Kd * (err - pid->last_err);
pid->last_err = err;
return p_out + i_out + d_out;
}
4.2 速度曲线规划——梯形 vs S形
你想想看,如果电机一启动就是全速,一停就急刹车,会发生什么?
窗帘会剧烈抖动,机械结构会磨损,甚至电机堵转。这就是为什么我们需要速度曲线规划。
说白了,就是让电机平滑地加速、匀速、减速。
4.2.1 梯形速度曲线
梯形曲线是最简单的方案。分为三个阶段:
- 加速段:速度从0线性增加到目标速度
- 匀速段:保持目标速度运行
- 减速段:速度线性下降到0
它的优点是实现简单,计算量小。缺点嘛……加速度在拐点处突变,会有冲击感。
我曾经在一个低成本项目里用了梯形曲线,结果客户反馈窗帘启动时“咯噔”一下。嗯,这就是加速度突变带来的问题。
梯形曲线的核心代码其实很简单:
// 梯形速度规划
float TrapezoidalProfile(float time, float accel_time, float total_time, float max_speed) {
float speed;
if (time < accel_time) {
// 加速阶段
speed = (max_speed / accel_time) * time;
} else if (time < total_time - accel_time) {
// 匀速阶段
speed = max_speed;
} else {
// 减速阶段
speed = (max_speed / accel_time) * (total_time - time);
}
return speed;
}
4.2.2 S形速度曲线
S形曲线就高级一些了。它把加速和减速过程又分成了加加速、匀加速、减加速三个阶段。说白了,就是让加速度本身也平滑变化。
这样做的好处很明显:
- 启动和停止完全没有冲击感
- 机械寿命更长
- 噪音更小
但代价是计算量大了不少。在低端MCU上跑S形曲线,你得小心别把CPU时间吃光了。
我给大家一个简化的S形曲线实现思路:
// S形速度规划(简化版)
float SCurveProfile(float t, float T, float Vmax) {
// 使用正弦函数近似S形
float normalized_t = t / T; // 归一化时间 [0, 1]
if (normalized_t < 0.5f) {
// 前半段:加速
return Vmax * (1.0f - cosf(M_PI * normalized_t)) / 2.0f;
} else {
// 后半段:减速
return Vmax * (1.0f + cosf(M_PI * (normalized_t - 0.5f))) / 2.0f;
}
}
4.3 堵转检测与保护逻辑
这是窗帘电机开发中最容易被忽视,但也是最要命的部分。
你想想看,如果窗帘被卡住了,电机还在拼命转,会发生什么?轻则烧电机,重则烧驱动板,甚至引发火灾。
所以,堵转检测不是可选项,是必选项。
4.3.1 堵转检测方法
常用的方法有三种:
| 方法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 电流检测法 | 堵转时电流会急剧上升 | 简单可靠 | 需要电流采样电路 |
| 速度检测法 | 给定速度与实际速度偏差过大 | 无需额外硬件 | 响应较慢 |
| 位置检测法 | 给定时间内位置没有变化 | 最直观 | 需要编码器或霍尔 |
我个人习惯电流检测+速度检测双重保险。为什么?因为单一方法都有盲区。比如电流检测法,如果堵转发生在轻载情况下,电流变化可能不明显。
4.3.2 保护逻辑实现
堵转保护不能简单地“检测到就停机”,那样太粗暴了。我建议这样设计:
- 初级保护:检测到堵转后,先尝试反向转动一小段距离,看能否解除卡滞
- 次级保护:如果反向无效,降低速度再试一次
- 终极保护:多次尝试失败后,彻底停机并上报故障码
下面是一个实用的堵转检测代码框架:
#define STALL_CURRENT_THRESHOLD 500 // 电流阈值,单位mA
#define STALL_TIME_THRESHOLD 200 // 堵转持续时间,单位ms
#define RETRY_COUNT 3 // 最大重试次数
typedef enum {
STALL_NONE,
STALL_DETECTED,
STALL_RECOVERING,
STALL_FAILED
} StallState;
StallState Stall_Detect(float motor_current, float motor_speed, float target_speed) {
static uint32_t stall_timer = 0;
static uint8_t retry_count = 0;
// 条件1:电流超标
// 条件2:速度偏差过大(实际速度远低于目标速度)
if ((motor_current > STALL_CURRENT_THRESHOLD) &&
(motor_speed < target_speed * 0.3f)) {
stall_timer++;
if (stall_timer > STALL_TIME_THRESHOLD) {
// 确认堵转
return STALL_DETECTED;
}
} else {
stall_timer = 0;
}
return STALL_NONE;
}
4.4 三者如何协同工作
讲到这里,你可能觉得这三个模块是独立的。其实不然,它们需要紧密配合:
- 曲线规划生成目标速度 → PID控制器跟踪目标速度 → 堵转检测监控运行状态
- 一旦堵转检测触发,立即暂停PID输出,执行保护逻辑
- 保护逻辑执行完毕后,重新从曲线规划的当前点开始运行
嗯,这里要注意:堵转恢复后,不要直接从断点全速运行,而是应该从低速重新加速。否则容易再次触发堵转。
好了,这一章的内容就到这里。下一章我们会讲电机驱动的硬件设计,包括MOS管选型、电流采样电路、以及PCB布局的注意事项。到时候我会分享一些我在EMC测试中吃过的亏,保证干货满满。