无刷电机有感方波闭环控制
前面已经实现了无刷电机的六步换向控制以及三相电流采集、电压温度采集。本章将学习如何利用PID实现速度环闭环控制和速度+电流双闭环控制。
速度闭环控制
想要实现速度闭环,必须知道此时的速度大小。所以要先知道无刷电机是如何测速的。
无刷电机测速原理
根据前面学习到的霍尔传感器在转子N极靠近时候输出1,S极靠近时候输出0。
1:当只有一对级,转子转一圈,霍尔输出一个完整脉冲(高电平与低电平时间分别为180电角度。)
2:计算其中高电平的持续时间,其中:t = C/F_t,F_t为计数频率。C为高电平的计数次数。因为计数频率就是一秒中数多少次。C次表示计数高电平的次数。所以可以得到高电平持续的时间。这里需要注意一下,因为当出现上升沿时候,就是低电平转为高电平的时候,一定是另一个半圈的开始。所以此时统计高电平持续的时间就是刚好一对极情况半圈的时间。
换句话来说霍尔信号的上升沿 / 下降沿定义了半圈的边界 → 计数频率和计数次数测量出边界内的时间(半圈时间) → 进而推算出 1 圈时间和转速。
3:所以转一圈的时间为:T=2*C/F_t。此时单位秒/圈。倒数即为圈/秒,转换为RPM即为:F_t/2C * 60。当为两对极时候,也就是高电平的时间会翻倍。所以变为:F_t/4C * 60。
速度环
速度环如上图所示:通过霍尔计算出来实际转速,然后设定目标转速。两者存在偏差,然后输入PID控制器,输出结果给无刷电机。
PID控制
位置式PID
位置式PID是一种位置闭环控制方法,根据编码器的脉冲计数累加进一步测量电机的位置信息。并且与目标值进行比较得到控制偏差。然后对偏差进行比例、积分、微分的控制。使偏差趋近于0。这种方法通常应用于需要精准位置控制的场合。
位置式PID特点
位置式PID有以下特点:
1:位置型 PID 控制的输出与整个过去的状态有关,用到了偏差的累加值,容易产生累积偏差:
位置型 PID 的输出公式为:
从上述公式可以看出来积分项会从一开始就累计系统的误差,如果系统一直存在小的偏差,最终可能导致积分项饱和,产生较大的累积偏差。比如在一个温度控制系统中,设定温度为80℃,但由于加热设备功率不足等原因,实际温度一直维持在 60℃,偏差始终为20℃。 此时,位置型PID控制中的积分项会随着时间不断累加,使得PID控制器的输出不断增大。 然而,实际的执行机构(如加热设备的功率、阀门的开度等)往往是有上限的。当PID控制器的输出达到执行机构的极限值后,就无法再继续增大了,但积分项却还在因为偏差的存在而继续累加 。
积分饱和后会导致出现以下几点:
1:控制响应缓慢:当系统偏差的方向发生改变后,由于积分项处于饱和状态,控制器的输出不能及时地做出相应的调整,导致系统响应变得缓慢。还是以温度控制系统为例,假如后来增加了加热设备的功率,实际温度开始上升,但因为积分饱和,PID控制器的输出并不能迅速减小,使得温度上升过度,需要较长时间才能回到设定温度。
2: 超调量增大: 当积分饱和的影响逐渐消除时,积分项会突然释放,导致控制器输出出现较大的变化,进而使系统产生较大的超调。比如温度控制系统中,温度可能会超过设定的80℃很多,然后再慢慢回落,严重影响系统的稳定性和控制精度。
通常解决方法有以下方法 :
1:积分限幅法:给积分项设定一个上限和下限值。当积分项的值达到上限时,就不再继续增加;当积分项的值达到下限时,就不再继续减小。在上述温度控制系统中,通过设定积分项的上限,比如当积分项累积到一定程度,使PID输出达到加热设备最大功率对应的控制值时,就不再让积分项继续增大,从而避免积分饱和。
2:- 遇限削弱积分法:当PID控制器的输出达到执行机构的极限值时,停止或者削弱积分项的累加。例如,当加热设备达到最大功率后,不再对偏差进行积分累加,或者按照一个较小的系数进行积分累加,以此来防止积分饱和的产生。
3: - 变速积分法:根据偏差的大小调整积分项的积分速度。当偏差较大时,减小积分系数,使积分作用变弱,防止积分项快速累积;当偏差较小时,增大积分系数,增强积分作用,以便更好地消除静差。比如在温度控制中,刚开始温度偏差大的时候,让积分作用弱一些,随着温度接近设定值,偏差变小,再增强积分作用 。
2:位置型 PID 适用于执行机构不带积分部件的对象。积分作用的强弱取决于积分时间常数(T_i),(T_i)越大,积分作用越弱,反之则越强
所以看出Ti越大,Ki越小。积分作用就越约,反之就越强。
3:位置型的输出直接对应对象的输出,对系统的影响比较大
位置型 PID 的输出 u(k)直接对应执行机构的 “绝对位置” 或 “绝对输出量”。比如控制电机转速时,输出直接对应电机的目标转速值;控制阀门时,输出直接对应阀门的目标开度。一旦输出值有较大变化,执行机构的动作幅度就会很大,从而对系统产生较大影响。
分析上图可以得到以下结论:
-
积分时间 “大(弱)”:积分作用弱(因为积分时间 \(T_i\) 越大,积分系数\(K_i=\frac{K_p}{T_i}\)越小)。
- 对应曲线 A:系统有振荡,但超调小;因为积分作用弱,对偏差的累积 “修正” 慢,系统趋向稳定的过程会伴随小幅度振荡,不过不会有大的超调。
- 对应曲线 C:系统无振荡,但稳定慢;积分作用很弱,几乎没有 “推动” 系统快速消除偏差的能力,所以系统平稳但达到稳定的时间长。
-
积分时间 “小(强)”:积分作用强(积分系数 (K_i) 大)。
- 对应曲线 B:超调大,但稳定快;积分作用强,会快速累积偏差并施加控制,容易使系统响应 “冲过头”(超调大),但能较快让系统稳定下来。
- 对应曲线 D:长周期振荡,稳定慢;积分作用过强,加上其他因素(比如比例系数配合等),会导致系统反复调整,出现长时间的振荡,且稳定耗时久。
-
当比例系数大(横向轴右侧方向):比例作用强,对偏差的 “即时反应” 更剧烈。
- 若积分时间也小(积分作用强),就容易出现像曲线 B 那样超调大但稳定快的情况;或者像曲线 D 那样,比例和积分作用共同导致长周期振荡。
-
当比例系数小(横向轴左侧方向):比例作用弱,对偏差的 “即时反应” 平缓。
- 若积分时间大(积分作用弱),就会出现像曲线 A 有小振荡但超调小,或曲线 C 无振荡但稳定慢的情况。
1. 比例环节(P):即时响应偏差
场景:控制房间温度,目标 25℃,当前 20℃(偏差 5℃)。
- 比例环节的输出与偏差成正比:
输出 = Kp × 偏差
。 - 若(Kp=2),则输出为(2×5=10)(可理解为加热器功率 10 档)。
- 当温度升至 23℃(偏差 2℃),输出变为(2×2=4)档,随偏差减小而减小。
特点:偏差越大,调节力度越强,但仅用 P 环节会有 “静差”(比如最终稳定在 24℃,无法完全达到 25℃)。因为到达24摄氏度时候,偏差很小,根据比例控制公式,控制器输出的加热功率也变得很小。但实际系统中,这个很小的加热功率可能刚好只能维持当前温度,无法再进一步升高温度至目标值 。因为实际系统存在各种损耗,如向周围环境散热等,当加热功率与散热功率达到平衡时,温度就不再上升,此时就出现了稳态误差,温度稳定在低于目标值的某个数值。
2. 积分环节(I):消除累积偏差
场景:延续上面的温度控制,P 环节单独作用时稳定在 24℃(静差 1℃)。
- 积分环节的输出与偏差的累积量成正比:
输出 = Ki × ∑偏差
(∑表示累加)。 - 即使偏差只有 1℃,随着时间推移,累积值会越来越大,推动加热器继续工作。
- 例如:第 1 分钟偏差 1℃,第 2 分钟仍 1℃,累积偏差 = 2℃,若(Ki=1),积分输出 = 2 档,叠加 P 环节的输出后,总输出增大,最终消除静差,达到 25℃。
特点:能消除静差,但积分作用过强(Ki 太大)会导致超调(比如温度冲到 27℃再回落)。
3. 微分环节(D):预判偏差变化趋势
场景:热水壶加热,目标 100℃,当前 90℃,且温度正以 5℃/ 秒快速上升(偏差 10℃,但变化率大)。
- 微分环节的输出与偏差的变化率成正比:
输出 = Kd × (当前偏差 - 上一时刻偏差)
。 - 若温度从 85℃(15℃偏差)升到 90℃(10℃偏差),1 秒内偏差减少 5℃,则变化率 =-5℃/ 秒。
- 若(Kd=0.5),微分输出 = 0.5×(-5)=-2.5 档,意味着提前减弱加热(因为预判到温度会快速接近目标)。
特点:抑制超调,让系统更平稳(比如避免水壶水沸腾后溢出),但对高频噪声敏感(如测量值抖动时会误判)。
假设目标温度是$ 25^\circ\text{C}$,实际温度稳定在 $25^\circ\text{C} $附近,但由于传感器噪声,测量值出现如下波动:
- 第 (k-1) 时刻:\(25.0^\circ\text{C}\)(偏差 (e(k-1) = 0))
- 第 k 时刻:\(25.1^\circ\text{C}\)(偏差 (e(k) = 0.1))
- 第 (k+1) 时刻:\(24.9^\circ\text{C}\)(偏差 (e(k+1) = -0.1))
此时,微分环节的输出为:
- 第 k 时刻:\(u_D(k) = K_d [0.1 - 0] = 0.1K_d\)(认为温度在快速上升,需要降温)
- 第 (k+1) 时刻:\(u_D(k+1) = K_d [-0.1 - 0.1] = -0.2K_d\)(认为温度在快速下降,需要升温)
但实际上,温度本身是稳定的,这些控制信号完全是由噪声抖动引起的误判,会导致执行机构(如加热器、制冷器)频繁、无意义地动作,既浪费能量,又可能损害设备,还会破坏系统的稳定。
位置PID各个系数对控制系统影响:
(1)比例(P)参数
比例环节的核心是依据偏差的大小来产生控制作用,比例系数 P 决定了这种作用的强弱。
- 增大 P 值
- 提高响应速度:比例系数增大,意味着对于同样的偏差,控制器会输出更大的控制量。例如在温度控制中,当实际温度与目标温度有偏差时,大的 P 值会让加热或冷却设备更快地做出反应,使系统的响应速度加快,动作更加灵敏。
- 减小静差:静差是系统达到稳定状态后,实际值与目标值之间的偏差。在系统稳定的情况下,增大比例系数,控制器对偏差的 “纠正” 力度更强,能够减小系统的稳态误差,从而提高控制精度。
- 稳定性影响:如果比例系数过大,控制器的反应会过于灵敏。比如在温度控制中,可能会导致加热或冷却设备的功率频繁且大幅度地变化,使系统产生振荡,调节时间拉长,严重时甚至可能使系统不稳定。
- 减小 P 值
- 降低响应速度:比例系数减小,控制器对偏差的反应变缓。还是以温度控制为例,当有偏差时,加热或冷却设备的动作会变得迟缓,系统的响应速度就会变慢。
- 增大静差:由于比例作用减弱,控制器对偏差的 “纠正” 能力下降,系统的稳态误差会增大,控制精度也就降低了。
(2)积分(I)参数
当系统达到“稳定状态”(不再剧烈波动,接近目标值)时,实际值和目标值之间的偏差,就叫“稳态误差”。比如,想把温度控制在100℃,稳定后实际是98℃,那2℃就是稳态误差。
积分的核心作用是累积过去的误差,通过持续的调整来消除稳态误差。
当增大积分值时:
- 减小静差(提高控制精度):积分值越大,“累积误差并调整”的力度越强。因为它会把过去所有的误差都加起来,然后根据这个总误差去调整控制量,所以能更彻底地消除稳态误差,让系统实际值更接近目标值(比如从98℃精准到100℃)。
- 稳定性变差:但“累积误差”的力度过强,会让系统的调整变得“过度敏感”。比如,误差刚有一点变化,积分的累积就会让控制量大幅波动,导致系统反复震荡(像温度在99℃和101℃之间来回晃),甚至彻底失去稳定(温度直接失控)。
当减小积分值时,“累积误差并调整”的力度变弱:
-
增大静差(控制精度下降):因为积分作用弱了,对过去误差的累积和调整不够,稳态误差就会变大(比如从100℃掉到99℃,甚至更多)。
-
提高稳定性:积分的“过度调整”变少了,系统不会因为积分的强累积而剧烈震荡,更不容易失控。但代价是,因为误差没被彻底消除,控制精度就不如积分值大的时候了。
-
想要高精度(让实际值更接近目标值),就需要较大的积分值,但要承担“系统震荡甚至失稳”的风险; - 想要高稳定性(系统不容易失控、震荡),就需要较小的积分值,但要接受“稳态误差变大、精度下降”的结果。 实际应用中,需要根据系统的需求(更看重精度,还是更看重稳定),去调整积分值,找到二者的平衡。
(3)微分(D)参数
微分的核心是根据误差的变化速度提前调整。当增大微分(D)值时:
- 改善动态性能:因为微分能 “预判” 误差的变化(比如误差即将快速变大或变小),提前对控制量进行修正。这样可以减少系统的 “超调量”(比如目标是让温度到 100℃,如果没微分,可能冲到 105℃再掉回来;有微分且 D 值合适,就可能只冲到 102℃),还能缩短 “调节时间”(从开始调整到稳定在目标值附近的时间更短)。
- 稳定性变差:但 D 值过大时,系统会对 “误差的微小变化” 过度敏感。比如系统里的一些微小噪声(像传感器的轻微波动),会被微分大幅放大,导致控制量剧烈波动,让系统不稳定。
当减小微分(D)值时,“预判误差变化并提前调整” 的力度变弱:
- 降低动态性能:系统对误差变化的 “预判” 不足,响应速度会变慢(比如要很久才反应过来误差在变大),超调量会增大(可能冲到更高温度才回落),调节时间也会延长(需要更久才能稳定)。
- 提高稳定性:因为对微小变化的 “过度反应” 减少了,系统不容易因为噪声或小波动而震荡,稳定性变好。但代价是,动态性能(响应速度、超调控制等)会受到影响。
因此:
- 想要好的动态性能(快响应、少超调),需要较大的 D 值,但要承担 “放大噪声、稳定性下降” 的风险;
- 想要高稳定性,需要较小的 D 值,但动态性能(响应速度、超调控制能力)会打折扣。实际应用中,要根据系统需求(更看重反应快,还是更看重稳定)来调整 D 值,找到平衡。
#include <stdio.h>// PID控制器结构体
typedef struct {float kp; // 比例系数float ki; // 积分系数float kd; // 微分系数float dt; // 控制周期(时间间隔)float setpoint; // 目标值float integral; // 积分项累计值float prev_error; // 上一次的误差值float output_min; // 输出最小值(限幅)float output_max; // 输出最大值(限幅)
} PIDController;// 初始化PID控制器
void pid_init(PIDController *pid, float kp, float ki, float kd, float dt,float setpoint, float output_min, float output_max) {pid->kp = kp;pid->ki = ki;pid->kd = kd;pid->dt = dt;pid->setpoint = setpoint;pid->integral = 0.0f;pid->prev_error = 0.0f;pid->output_min = output_min;pid->output_max = output_max;
}// 更新PID控制器输出(位置式)
float pid_update(PIDController *pid, float process_value) {// 计算当前误差float error = pid->setpoint - process_value;// 计算比例项float p_term = pid->kp * error;// 计算积分项(累加误差)pid->integral += error * pid->dt;float i_term = pid->ki * pid->integral;// 计算微分项(误差变化率)float derivative = (error - pid->prev_error) / pid->dt;float d_term = pid->kd * derivative;// 保存当前误差用于下一次计算pid->prev_error = error;// 计算总输出float output = p_term + i_term + d_term;// 输出限幅(防止执行器饱和)if (output > pid->output_max) {output = pid->output_max;} else if (output < pid->output_min) {output = pid->output_min;}return output;
}
增量式PID
- 计算依赖少:不需要像位置式 PID 那样对误差进行累加,只和最近几次的偏差值有关,受计算偏差的影响比较小。
- 对系统影响温和:它得出的是控制量的 “增量”,不是直接给出控制量的绝对值,所以对系统的影响相对较小,不会让系统突然有很大的变化。
- 切换更平滑:采用增量型 PID 算法,很容易实现从手动控制到自动控制的无扰动切换,不会因为控制模式的改变而让系统产生剧烈波动。和位置式 PID 对比,位置式 PID 因为有积分信号,容易积累偏差,达到设定值时可能会震荡(比如控制一个物体到某个位置,可能在目标位置附近来回动);而增量型 PID 只涉及前两次的误差,不容易震荡,更适合像 “控制小人到墙面处就停止,不能有震荡” 这样的场景。
增量型 PID 控制算法是在位置式 PID 基础上推导得出的,它计算的是控制量的增量\(Delta u(k)\),公式如下:
各部分含义:
- 比例部分:\(K_p [e(k) - e(k - 1)]\),根据当前误差和上一次误差的差值来进行调节,能快速响应误差的变化,对误差变化的趋势做出反应。
- 积分部分:\(K_i e(k)\),和位置式 PID 中的积分项不同,它只考虑当前时刻的误差,用于消除稳态误差,增强系统的控制精度。
- 微分部分:\(K_d \frac{e(k) - 2e(k - 1) + e(k - 2)}{T}\),反映误差的变化率,能预测误差变化的趋势,提前进行调节,减少超调量,提高系统的稳定性 。也就是K时刻的微分项减去上K-1时刻的微分项。
#include <stdio.h>// 定义PID结构体
typedef struct {float kp; // 比例系数float ki; // 积分系数float kd; // 微分系数float dt; // 采样周期float prev_error; // 上一次的误差float prev_prev_error; // 上上一次的误差float last_output; // 上一次的控制输出
} IncrementalPID;// 初始化增量型PID控制器
void incremental_pid_init(IncrementalPID *pid, float kp, float ki, float kd, float dt) {pid->kp = kp;pid->ki = ki;pid->kd = kd;pid->dt = dt;pid->prev_error = 0.0f;pid->prev_prev_error = 0.0f;pid->last_output = 0.0f;
}// 更新增量型PID控制器输出
float incremental_pid_update(IncrementalPID *pid, float setpoint, float process_value) {float error = setpoint - process_value; // 当前误差// 计算比例项float p_term = pid->kp * (error - pid->prev_error); // 计算积分项float i_term = pid->ki * error; // 计算微分项float d_term = pid->kd * (error - 2 * pid->prev_error + pid->prev_prev_error) / pid->dt; float delta_output = p_term + i_term + d_term; // 控制量增量float output = pid->last_output + delta_output; // 当前控制量// 保存当前误差,用于下一次计算pid->prev_prev_error = pid->prev_error;pid->prev_error = error;pid->last_output = output;return output;
}
// 模拟被控对象(例如温度控制系统)
float simulate_system(float current_value, float control_output) {// 简单模拟: 控制输出影响当前值的变化// 加入一些延迟和惯性模拟真实系统current_value += control_output * 0.05f;// 加入微小扰动模拟实际噪声current_value += (rand() % 10 - 5) * 0.01f;return current_value;
}
int main() {IncrementalPID pid;// 初始化PID参数incremental_pid_init(&pid, 2.0f, 0.5f, 0.1f, 0.1f); float setpoint = 50.0f; // 设定值float current_value = 20.0f; // 当前值// 模拟控制过程printf("控制周期\t当前值\t\t设定值\t\t控制输出\n");printf("-----------------------------------------\n");for (int i = 0; i < 200; i++) {float control_output = incremental_pid_update(&pid, setpoint, current_value);current_value = simulate_system(current_value, control_output);// 每隔10个周期打印一次数据if (i % 10 == 0) {printf("%d\t\t%.2f\t\t%.2f\t\t%.2f\n",i, current_value, setpoint, control_output);}}return 0;
}
速度计算
前面的无刷电机测速原理已经告诉计算的原理,那么我们实际去计算出其中一个霍尔传感器高电平持续的时间即可。
首先统计其中一段高电平持续的时间。高电平持续的时间就是卡在上升沿和下降沿之间。主要是检测信号从0 - 1 在从 1 - 0的过程,即高电平所持续的过程。
接下来需要检测霍尔传感器为高电平的时候,输出标志位为1,否则为0,用来计算速度。
if(HAL_GPIO_ReadPin(HALL1_TIM_CH3_GPIO,HALL1_TIM_CH3_PIN) != GPIO_PIN_RESET) /* 霍尔传感器状态获取 */{state |= 0x04U;g_bldc_motor1.hall_single_sta = 1; /* 单个霍尔状态,计算速度用到 */}elseg_bldc_motor1.hall_single_sta = 0;
接下来还需要检测单个霍尔信号的变化:
/*** @brief 检测输入信号是否发生变化* @param val :输入信号* @note 测量速度使用,获取输入信号状态翻转情况,计算速度* @retval 0:计算高电平时间,1:计算低电平时间,2:信号未改变*/
uint8_t uemf_edge(uint8_t val)
{/* 主要是检测val信号从0 - 1 在从 1 - 0的过程,即高电平所持续的过程 */static uint8_t oldval=0;if(oldval != val){oldval = val;if(val == 0) return 0;else return 1;}return 2;
}if(g_bldc_motor1.hall_sta_edge == 0) /* 统计单个霍尔信号的高电平时间,当只有一对级的时候,旋转一圈为一个完整脉冲。一高一低相加即旋转一圈所花的时间*/{/*计算速度*/if(g_bldc_motor1.dir == CW)temp_speed = (SPEED_COEFF/g_bldc_motor1.count_j);elsetemp_speed = -(SPEED_COEFF/g_bldc_motor1.count_j);FirstOrderRC_LPF(g_bldc_motor1.speed,temp_speed,0.2379f); /* 一阶滤波 */g_bldc_motor1.no_single = 0;g_bldc_motor1.count_j = 0;}if(g_bldc_motor1.hall_sta_edge == 1) /* 当采集到下降沿时数据清0 */{g_bldc_motor1.no_single = 0;g_bldc_motor1.count_j = 0;}if(g_bldc_motor1.hall_sta_edge == 2) /* 霍尔值一直不变代表未换向 */{g_bldc_motor1.no_single++; /* 不换相时间累计 超时则判定速度为0 */if(g_bldc_motor1.no_single > 15000){g_bldc_motor1.no_single = 0;g_bldc_motor1.speed = 0; /* 超时换向 判定为停止 速度为0 */}}
- 当
hall_sta_edge == 0
(检测到下降沿)
此时说明霍尔信号从 1→0 跳变,意味着高电平持续时间结束(count_j
累计了这段高电平的时间)。
-
速度计算公式:
速度 = 系数(SPEED_COEFF) / 高电平时间(count_j)
定时器计时频率为18khz。根据测速原理推导出的公式进一步可得速度为:\((18000/4)*60*count_j\)*
- 根据电机转向(
dir
)决定速度正负(CW
为正方向) - 用一阶低通滤波(
FirstOrderRC_LPF
)平滑速度值,减少噪声 - 重置计数器(
count_j
)和超时计数(no_single
),准备下一次测量
- 当
hall_sta_edge == 1
(检测到上升沿)
此时说明霍尔信号从 0→1 跳变,意味着低电平持续时间结束。
- 这里只做了重置操作:清零计数器(
count_j
)和超时计数(no_single
) - (注:代码注释提到 “统计单个霍尔信号的高电平时间”,因此低电平时间不参与速度计算,仅作为高电平测量的间隔)
- 当
hall_sta_edge == 2
(无跳变)
此时霍尔信号长时间保持不变(既不升也不降),可能意味着电机停转。
- 用
no_single
累计无跳变的时间 - 当
no_single
超过阈值(15000)时,判定电机已停止,强制将速度设为 0 - 避免电机停转后因无信号跳变导致的速度值异常