一、FOC 引入
1.1 无刷电机控制结构
无刷电机由内部线圈与外部的六颗 mos 管相连接。
与mos管相连接的线圈称之为定子。
中间能转动的磁铁叫转子。
1.2 使用六步换相进行方波控制
打开两个 mos 管,线圈磁极改变,磁力引导转子旋转。
外部的霍尔传感器检测到旋转到指定角度后则换一组 mos 开启方式,一共有六种,所以叫六步换相。
下图我们将转子控制在 240° 内,箭头展示了向量合成。
六步换向动态示意:
下图更为详细的介绍了六步换相原理:
其中 commutation logic 是换相逻辑图,AH 是 AL 是电机的 A 相的上桥臂和下桥臂所接的 mos。
以下是更完善的一个无刷电机方波控制示意图, 在上图的基础上加入了 PID 调速。
1.3 方波控制的缺陷
1.3.1 提前换相角
foc 发明以前,无刷电机采取的是六步换向法。电机每旋转60度,控制器就要进行一次“电子换向”。控制器输出的,是同一时刻只有2条线有输出的方波。
航模的电调,有一个参数叫“换向提前角”。电调会在本不是换向的时间提前换向。这个提前的时间点,从时间上来说自然是和转速有关。从角度上来说,就和转速无关了。所以叫提前角,而不是提前时间。
foc 之所以需要电流采样,本质上就是为了确定提前换向角。而不是像航模的电调那样还得人工配置。
因为磁场是电流建立的,不是电压建立的。而绕组是一个电感。电感的电流会滞后电压。准时换向,不过是让电压和转子呈 90° 角度。而电流滞后,会导致电流并不和转子呈90°,也就是磁场没有和转子呈90° 夹角,也就是扭矩没有最大化发挥。
在下图中,我们看到电压与转子不是在 90° 下换相,而且提前了一点时间,如果在转子在 90° 的情况换相就并不能确保其中存在的磁场与转子呈 90°。
1.3.2 电机发热的力矩损耗
我们知道,电机发热,会使电阻变大:
也就是最终会导致电流,也就是力矩波动,此时最佳的提前换向角的值则不是一个定值。
1.3.3 仿真下的六步换向电流波动
下图是六步换向的三项磁场建立示意,右侧是电流图,黄色区域是当前电流:
当换向时,之前的 AC 磁场崩溃,右侧电流图 C 相在换向时磁场重新建立而电流突变。
在右侧是电压波形图,因为电流突变,线圈是感性原件,为了维持这一点电流,也会提升自身电压维持电流。
这张图表示了方波控制下速度和力矩的波动,上面的波形是pid控制的速度,下边是电流,可见均是波形的。
对于电机而言,突变的电流意味着力矩的波动,我们清楚的看到,此时定子磁场和转子磁场之间一直在60°~120°波动,力矩并没有恒定在转子的90°中,也就是不能获得最大力矩。而且突变的电流也导致线圈电压突变。所以方波控制并不是很好的电机控制方式。
1.3 理想的控制效果
我们想要实现力矩,也就是磁场完全和转子呈 90° 实现,这样电机的效率是最高的,所以我们需要另一种算法,磁场定向控制 (FOC),他比方波控制降低了系统响应的波纹,使电机运行更加平稳。并且还可以使用弱磁技术使电机高于额度速度运行。
所以 FOC 技术翻译过来就是定向磁场控制,本质上他是在实现磁场与转子保持 90° 运行,而不仅是六步换相的 MOS 开关和转子保持 90° 运行。
二、FOC控制流程
概括一下,FOC控制的整个过程是这样的:
- 对电机三相电流进行采样得到 Ia、Ib、Ic。
- 将 Ia、Ib、Ic 经过 Clark 变换得到 Iα、Iβ。
- Iα、Iβ 和当前电机角度数据经过 Park 变换得到 Iq、Id。
- 计算 Iq、Id 和设定值 Iq_ref、Id_ref 的误差。
- 将上述误差输入两个 PID(只用到速度 PI)控制器,得到电流 Uq、Ud。
- 将 Uq、Ud 进行反 Park 变换得到 Uα、Uβ。
- 用 Uα、Uβ 合成电压空间矢量,输入 svpwm 模块,对电机进行 pwm调制。
2.1 Clark 变换
clark变化就是把三项电压矢量(a、b、c)坐标,转化为两轴(α、β)。
转化效果如下:
2.1.1 Clark 变换公式
由三项(a、b、c)坐标我们观察到,这三轴间距为 120°。
于是我们转化公式如下:
分别是:
lα 等于 a 轴电流减去 b 轴和 c 电流在 x 坐标的分量。
lα 等于 b 轴电流在 y 坐标的分量减去 c 轴在 y 坐标的分量。
投影到矩阵可得:
2.1.2 等幅克 Clark 变换
基尔霍夫定律可知,三项中,电流关系为:
那么以计算电流可知 A、B、C 可得分别为 -1、1/2、1/2。
如果我们把它带入到 Clark 公式中,发现得出的值并不是等幅的 1A,而是-3/2。
尽管我们仅在 a 相输入的是 1A 电流,但是与之相同角度的 α 却只有 -2/3,这与我们期望的输出并不相符。所以为了让式子等幅值,让结果乘上 2/3 即可。
化简可得:
最后我们再通过 化简可得:
2.1.2 Clark 逆变换形式
首先, 的逆变换我们是不需要研究的,因为我们已经知道了
然后我们研究一下 :
最后根据 化简得到
:
最后得出整个 Clark 逆变换的过程:
2.2 Park 转换
Iq、Id 坐标系相对与定子来说是旋转的坐标系,转速的角速度和转子旋转的角速度相同。
相对于转子来说,Iq、Id 坐标系就是静止的坐标系;而 Iq、Id 则是恒定不变的两个值,具体如下图所示:
在右图中,箭头所指的方向是 Iq,与之呈90°的是 Id 轴,我们需要让Iq为当前期望电流输出,Id 为0,这样能使得电机效率最大。
帕克变换就是能够帮助我们求得各种旋转情况下的 Iα 和 Iβ。
那么我们就有了两个坐标系了,一个是固定在定子上的坐标系 Iα - Iβ,一个是固定在转子上的坐标系 Iq−Id。
通常在简单的FOC应用中,我们只需要控制 Iq - Id 的电流大小,而把 Id 设置为 0。此时,Iq 的大小间接就决定了定子三相电流的大小,进而决定了定子产生磁场的强度。进一步我们可以说,它决定了电机产生的力矩大小。
而 Iq 是旋转的矢量;在前面说了,同时 Iq 又会间接影响磁场的强度,这正是FOC的名称磁场定向控制的由来。
2.2.1 park变换公式
其中,Iq - Id 坐标系随转子转动,D 轴在此处设定为指向电机的 N 级,Iq - Id 坐标系因转动而造成的与 Iα - Iβ 坐标系的差角θ,就被称为电角度。
那么,很轻松的,还是利用简单的三角函数构建的旋转矩阵,在知道电角度的前提下,我们很容易就能够把 Iα - Iβ 坐标系上的值映射(旋转)到 Iq - Id 坐标系上,式子如下:
矩阵形式:
2.2.1 Park 逆变换公式
根据矩阵乘法,取逆,我们可进行帕克逆变换,也就是知道 Iq - Id 值和电角度的前提下,反求 Iα - Iβ 式子如下:
写成等式结果:
三、FOC 开环控制方式
3.1 极对数、定子转子比例和电角度
极对数指的是转子的磁极对数,左 1 示例中,极对数是 1,左 2、3 则是 2。
定子和转子有多种比例,下图左 1 的定子转子比是 1:1,也就是一对磁极对应一对三相定子。
左 3 的电机示例转定子比例也是 1:1,两对磁极对应两对三项定子。
电角度:描述的是电流或者电压的在一个电周期内的相位角度。
电角度则是机械角度乘极对数,因为对于极对数是 1 的电机,一次六步换相电机会旋转一圈;但是对于极对数 2 的电机,一次六步换相只会转动半圈。所以机械角度需要乘极对数。这也显而易见的发现,极对数越大,扭矩的就越大。
如极对数为 1 的电机,电角度和机械角度有如下关系:
对于极对数为 2 的电机则是这样的:
3.2 开环控制流程
基本流程:
3.1 loop() 循环函数
loop() 无限循环,5为速度
float voltage_power_supply = 12; // 电源电压// 主循环
void loop()
{velocityOpenloop(5);
}
3.2 velocityOpenloop() 开环速度函数
我们设置的母线电压是 12v,Uq 在数学上可以是有负电压的,所以Uq的此时的范围是 [-6v,+6v]
在这里直接给出最大力矩,也就是给 Uq = 6v。
// 开环速度函数
float velocityOpenloop(float target_velocity)
{// shaft_angle 是轴角度,模拟霍尔信号 ,归一化以确保其值在 0 到 2π 之间。float shaft_angle = normalizeAngle(shaft_angle + target_velocity);// 最大电压为12V,我们假设 Uq 有负电压,此时范围应该在 +6v 到 -6v 之间// 在这里给出最大力矩 +6vfloat Uq = voltage_power_supply / 2;float pole_pairs = 7; // 极对数setPhaseVoltage(Uq, 0, shaft_angle * 7); // 电角度 = 机械角度 * 极对数
}
3.3 velocityOpenloop() 开环速度函数
经过帕克变换和克拉克变换后,值的区间依然是 [-6v,+6v],因为三角函数值域是 [0,1],但是因为pwm电压调制不能输出负电压,所以需要加上 6v 的电压偏置。
// 计算相电压并设置 PWM
void setPhaseVoltage(float Uq, float Ud, float angle_el)
{// 电角度归一化float angle_el = normalizeAngle(angle_el);// 帕克逆变换// 其中 Uaplha 和 Ubeta 的区间是 [-6v, +6v]float Ualpha = -Uq * sin(angle_el);float Ubeta = Uq * cos(angle_el);// 克拉克逆变换// 其中 Ua Ub Uc 的区间是 [-6v, +6v]float Ua = Ualpha;float Ub = (sqrt(3) * Ubeta - Ualpha) / 2;float Uc = (-Ualpha - sqrt(3) * Ubeta) / 2;// 由于 pwm 生成器不能输出负电压,我们需要在这里加入 6v 的电压偏置float voltage_offset = voltage_power_supply / 2;Ua = Ua + voltage_offset;Ub = Ub + voltage_offset;Uc = Uc + voltage_offset;// 其中 Ua Ub Uc 的区间是 [0v, +12v]setPwm(Ua, Ub, Uc);
}
3.4 setPwm() 调制 pwm 函数
此时传入的值经过 +6v 的电压偏置后,值域应该是
[0,+12v],此时直接除 12 求出占比后输出给 pwm 即可。
// 设置 PWM 输出
void setPwm(float Ua, float Ub, float Uc)
{// 限制占空比从0到1float dc_a = _constrain(Ua / voltage_power_supply, 0.0f, 1.0f);float dc_b = _constrain(Ub / voltage_power_supply, 0.0f, 1.0f);float dc_c = _constrain(Uc / voltage_power_supply, 0.0f, 1.0f);// 其中通道1对应A相,通道2对应B相,通道3对应C相 占空比为0-1setPwm1(0, dc_a);setPwm2(1, dc_b);setPwm3(2, dc_c);
}
3.5 完整代码
全部代码:
float voltage_power_supply = 12; // 电源电压// 设置 PWM 输出
void setPwm(float Ua, float Ub, float Uc)
{// 限制占空比从0到1float dc_a = _constrain(Ua / voltage_power_supply, 0.0f, 1.0f);float dc_b = _constrain(Ub / voltage_power_supply, 0.0f, 1.0f);float dc_c = _constrain(Uc / voltage_power_supply, 0.0f, 1.0f);// 其中通道1对应A相,通道2对应B相,通道3对应C相 占空比为0-1setPwm1(0, dc_a);setPwm2(1, dc_b);setPwm3(2, dc_c);
}// 归一化角度到 [0,2PI]
float normalizeAngle(float angle)
{// 输出做限制,使其在0到2π之间float a = fmod(angle, 2 * PI);return a >= 0 ? a : (a + 2 * PI);
}// 计算相电压并设置 PWM
void setPhaseVoltage(float Uq, float Ud, float angle_el)
{// 电角度归一化float angle_el = normalizeAngle(angle_el);// 帕克逆变换// 其中 Uaplha 和 Ubeta 的区间是 [-6v, +6v]float Ualpha = -Uq * sin(angle_el);float Ubeta = Uq * cos(angle_el);// 克拉克逆变换// 其中 Ua Ub Uc 的区间是 [-6v, +6v]float Ua = Ualpha;float Ub = (sqrt(3) * Ubeta - Ualpha) / 2;float Uc = (-Ualpha - sqrt(3) * Ubeta) / 2;// 由于 pwm 生成器不能输出负电压,我们需要在这里加入 6v 的电压偏置float voltage_offset = voltage_power_supply / 2;Ua = Ua + voltage_offset;Ub = Ub + voltage_offset;Uc = Uc + voltage_offset;// 其中 Ua Ub Uc 的区间是 [0v, +12v]setPwm(Ua, Ub, Uc);
}// 开环速度函数
float velocityOpenloop(float target_velocity)
{// shaft_angle 是轴角度,模拟霍尔信号 ,归一化以确保其值在 0 到 2π 之间。float shaft_angle = normalizeAngle(shaft_angle + target_velocity);// 最大电压为12V,我们假设 Uq 有负电压,此时范围应该在 +6v 到 -6v 之间// 在这里给出最大力矩 +6vfloat Uq = voltage_power_supply / 2;float pole_pairs = 7; // 极对数setPhaseVoltage(Uq, 0, shaft_angle * 7); // 电角度 = 机械角度 * 极对数
}// 主循环
void loop()
{velocityOpenloop(5);
}
四、FOC 位置闭环
4.1 闭环控制最大误差
假设我们供电的电压在 12V,也就是 Uqmax = ±6V。
如果我们希望电机在偏差 45° 时产生最大回正力矩,那么可以计算 Kp 系数为:
如果我们误差在最大 -45° 时,其中 e 为误差,此时回正力矩 Uq:
如果我们在 -10°,回正力矩 Uq 为:
此时如果我们满足于在 ±45° 提供最大反馈力矩,就可以使用 Kp 作为 0.133。
4.2 代码演示
基本流程:
4.2.1 修改过的 loop() 函数
我们基于之前的开环速度修改,在下面的代码弃用掉模拟角度发生器,而是使用
getAngle();
获得霍尔角度。
为了方便描述过程,在下面的案例中只使用了 Kp 参数,并没有使用完成的 PID 控制。
void loop()
{float Kp = 0.133; // Kpfloat pole_pairs = 7; // 极对数float motor_target = 0; // 目标角度float Sensor_Angle = getAngle(); // 转子角度float electricalAngle = Sensor_Angle * 7; // 电角度float err = motor_target - Sensor_Angle; // 误差角度//限制电压幅度 使其范围在 [-6v,6v] 之间if (err > voltage_power_supply / 2)err = voltage_power_supply;else if (err < voltage_power_supply / 2)err = -voltage_power_supply;setPhaseVoltage(Kp * (motor_target - Sensor_Angle), 0, electricalAngle);
}
4.2.2 完整代码
完整代码,其他除了删掉了 velocityOpenloop() 和修改了 loop() 函数外没有做其他改动。
float voltage_power_supply = 12; // 电源电压// 设置 PWM 输出
void setPwm(float Ua, float Ub, float Uc)
{// 限制占空比从0到1float dc_a = _constrain(Ua / voltage_power_supply, 0.0f, 1.0f);float dc_b = _constrain(Ub / voltage_power_supply, 0.0f, 1.0f);float dc_c = _constrain(Uc / voltage_power_supply, 0.0f, 1.0f);// 其中通道1对应A相,通道2对应B相,通道3对应C相 占空比为0-1setPwm1(0, dc_a);setPwm2(1, dc_b);setPwm3(2, dc_c);
}// 归一化角度到 [0,2PI]
float normalizeAngle(float angle)
{// 输出做限制,使其在0到2π之间float a = fmod(angle, 2 * PI);return a >= 0 ? a : (a + 2 * PI);
}// 计算相电压并设置 PWM
void setPhaseVoltage(float Uq, float Ud, float angle_el)
{// 电角度归一化float angle_el = normalizeAngle(angle_el);// 帕克逆变换// 其中 Uaplha 和 Ubeta 的区间是 [-6v, +6v]float Ualpha = -Uq * sin(angle_el);float Ubeta = Uq * cos(angle_el);// 克拉克逆变换// 其中 Ua Ub Uc 的区间是 [-6v, +6v]float Ua = Ualpha;float Ub = (sqrt(3) * Ubeta - Ualpha) / 2;float Uc = (-Ualpha - sqrt(3) * Ubeta) / 2;// 由于 pwm 生成器不能输出负电压,我们需要在这里加入 6v 的电压偏置float voltage_offset = voltage_power_supply / 2;Ua = Ua + voltage_offset;Ub = Ub + voltage_offset;Uc = Uc + voltage_offset;// 其中 Ua Ub Uc 的区间是 [0v, +12v]setPwm(Ua, Ub, Uc);
}// 主循环
void loop()
{float Kp = 0.133; // Kpfloat pole_pairs = 7; // 极对数float motor_target = 0; // 目标角度float Sensor_Angle = getAngle(); // 转子角度float electricalAngle = Sensor_Angle * 7; // 电角度float err = motor_target - Sensor_Angle; // 误差角度//限制电压幅度 使其范围在 [-6v,6v] 之间if (err > voltage_power_supply / 2)err = voltage_power_supply;else if (err < voltage_power_supply / 2)err = -voltage_power_supply;setPhaseVoltage(Kp * (motor_target - Sensor_Angle), 0, electricalAngle);
}
五、FOC 速度闭环
5.1 动态的低通滤波器
速度环和位置环代码类似,将位置采集函数换成速度采集做 PID 运算。但是速度环却需要必要的使用低通滤波器对速度进行滤波。
动态的低通滤波器,它基于指数衰减滤波器的原理。该公式的目的是根据时间常数和时间间隔的比例来控制滤波器对当前输入信号和前一次滤波结果的加权比例。
实现方式:
float alpha = Tf / (Tf + dt); // 计算滤波器的alpha系数,dt是循环时间
float y = alpha * y_prev + (1.0f - alpha) * x; // 计算当前输出值 x是本次的值,y是上次的值
timestamp_prev = timestamp; // 更新状态变量
y_prev = y; // 更新上一次的值
完整代码:
float low_pass_filter(float x) {static int timestamp_prev = 0; // 上一次的时间戳static float y_prev = 0; // 上一次的输出值static float Tf = 0.1f; // 时间常数,可以根据需要调整int timestamp = micros(); // 获取当前时间戳(微秒)float dt = (timestamp - timestamp_prev) * 1e-6f; // 计算时间差并转换为秒// 检查时间差是否合理if (dt < 0.0f || dt > 0.3f) {// 在极端情况下,重置状态并返回输入值(或者您可以选择其他处理方式)timestamp_prev = timestamp;y_prev = x;return x;}// 计算滤波器的alpha系数float alpha = Tf / (Tf + dt);// 计算当前输出值float y = alpha * y_prev + (1.0f - alpha) * x;// 更新状态变量timestamp_prev = timestamp;y_prev = y;// 返回当前输出值return y;
}
5.2 PI的速度运算
// 主循环
void loop()
{float pole_pairs = 7; // 极对数float motor_speed = 1; // 目标速度float Sensor_Angle = getAngle(); // 当前转子角度float Sensor_Angle_speed = getAngleSpeed(); // 当前转子速度//使用滤波器滤波Sensor_Angle_speed = low_pass_filter(Sensor_Angle_speed);float electricalAngle = Sensor_Angle * 7; // 电角度float err = motor_speed - Sensor_Angle_speed; // 误差角度float Kp = 0.1; // Kpfloat Ki = 0.01; // 积分增益static float integral = 0; // 积分项,用于累积误差static float previous_error = 0; // 上一次的误差// 限制误差if (err > 10.0f)err = 10.0f;else if (err < -10.0f)err = -10.0f;// 累积积分项float integral_delta = err - previous_error;integral += integral_delta;// 防止积分项过大导致系统不稳定(积分抗饱和)// 这里设置了一个积分项的上下限if (integral > 10.0f)integral = 10.0f;else if (err < -10.0f)integral = -10.0f;// 计算PI控制器的输出float control_output = Kp * err + Ki * integral;//设定Uq、Ud和电角度setPhaseVoltage(control_output , 0, electricalAngle);// 更新上一次误差的值previous_error = err;
}
六、FOC 电流闭环
6.1 硬件采样方式选择
在 FOC 引入章节已经描述了为什么要做电流采样,本章来介绍一下电流闭环的实现方式。
硬件设计上 MOS 高侧后加入电阻通过欧姆定理还原出电流值。
6.1.1 在线电流检测
在线电流检测技术是最易用和精确的一种。采样电阻与电机相串联,无论 PWM 占空比的状态如何,在这些采样电阻上测量的电流都是电机相位电流。
缺点是不能实现单电阻采样。
6.1.2 低侧电流检测
低侧电流采样是指:采样电阻在接地侧采集电流。
由于受监控电路的接地与系统中的其他负载的电位不同,因此可能存在接地回路问题(指负载的接地与系统的其他部分的接地存在电位差),干扰附近的设备。由于这一限制,低端电流检测通常用于我们处理一个隔离负载或负载对接地噪声不敏感的应用中。
这种方法的主要缺点是,由于只有相应的低侧 MOS 开启时,通过采样电阻的电流才是相电流,而我们只能在这些时刻测量到相电流。
不过这种采样方式却可以实现单电阻采样,具体采集流程就不展开描述了。
6.1.3 高侧电流检测
高侧电流采样是指:采样电阻在接电源侧采集电流。
对于高端采样,虽然可以避免接地回路的问题,但是分流电阻器两端电压的共模电平非常接近负载电源电压。这导致需要共模抑制比(cmrr)较大的运放采集电压。
同时只有相应的低侧 MOS 开启时,通过采样电阻的电流才是相电流,而我们只能在这些时刻测量到相电流。
6.2 电流闭环过程
七、空间电压矢量 SVPWM
7.1 SPWM 的不足
在这种方式驱动下,如果我们测量相电压,也就是a、b、c 三项与低的电势差,波形会是这样的:
按照本图 1V 为母线电压,在任意两相其最大电压差仅有0.866V,也就是浪费掉了 13.4%的电压驱动能力。
所以我们希望做功区域能覆盖整个电压差,这就需要 SVPWM 技术了,他的输出波形会是一个马鞍面:
此时便利用了全部的电压效率。
7.2 七个基本矢量
SVPWM技术可以帮助我们合成任意方向的磁场,首先我们先观察一下怎么生成一个和 A 相平行的磁场,很显然,我们把A上管打开,也就是按照 100 的上管开启顺序启动即可生成:
我们可以根据分压原理来分析当前点定子的电压,我们以定子电压为 0V 作为参考分析:
三个 MOS:A 上管、B 下管和 C 下管,等效看作电阻 R。
根据分压公式可知:
此时 Van 电压分走了 2/3 的电压,也就是 2/3Vdc。
剩下两个 mos 管并联,电压一样,分走了 1/3Vdc,但是我们按照定子为 0V 计算,此时 Vcn 为 -1/3Vdc。
我们根据上管开启,一共总结出七个基本矢量,分别为 100、011、111、110、001、000、010、101.
其中电压关系如下:
Vector | a | b | c | VAN | VBN | VCN |
U0 | 0 | 0 | 0 | 0 | 0 | 0 |
U1 | 1 | 0 | 0 | |||
U2 | 1 | 1 | 0 | |||
U3 | 0 | 1 | 0 | |||
U4 | 0 | 1 | 1 | |||
U5 | 0 | 0 | 1 | |||
U6 | 1 | 0 | 1 | |||
U7 | 1 | 1 | 1 | 0 | 0 | 0 |
7.3 合成任意矢量
对于合成任意矢量,我们可以得到一个离散化公式:
UmTs = U4T4 + U6T6 + U0T0 + U7T7
其中 U4T4 是矢量 4(100) 导通时间,U6T6 是 矢量 6(110) 导通时间。U0T0 和 U7T7 则为 0 矢量和满矢量时间,UmTs 则为我们合成的向量。
虚线是我们构建的平行四边形,对于上图我们根据正弦定理可以得到:
之后变换公式,我们得到:
再次化简可得:
m 为 svpwm 的调制比系数:
7.3.1 调制比系数的另一种解释
在三相电系统中,线电压(线与线的电势差)的电压是 380V,但是相电压(线对地)是 220V,其中的波形的差距为:
如图所示:
SVPWM 所计算的占空比仍然按照Udc的相电压计算,其中设置调制系数为根号 3 也就是将这个结果投影到线电压当中。
7.4 七段式 SVPWM 调整法
7.5 代码演示
// 定义一个函数,用于设置电机的扭矩,输入参数为电压Uq和电角度angle_el
void setTorque(float Uq, float angle_el)
{// 如果Uq小于0,说明需要反转电角度,即将angle_el加上π(π的值用_PI表示)if (Uq < 0)angle_el += _PI;// 取Uq的绝对值,因为我们只关心电压的大小,不关心方向(方向已经通过调整angle_el处理)Uq = abs(Uq);// 计算当前angle_el所在的扇区,每个扇区是60度(π/3弧度),通过angle_el除以_PI_3(π/3)并向下取整,然后加1得到int sector = floor(angle_el / _PI_3) + 1;// 根据空间矢量脉宽调制(SVPWM)原理,计算每个相(T1, T2)的占空比,以及零矢量(T0)的占空比// 这里_SQRT3是根号3的值,voltage_power_supply是电源电压float T1 = _SQRT3 * sin(sector * _PI_3 - angle_el) * Uq / voltage_power_supply;float T2 = _SQRT3 * sin(angle_el - (sector - 1.0) * _PI_3) * Uq / voltage_power_supply;float T0 = 1 - T1 - T2; // 零矢量的占空比等于1减去T1和T2的占空比之和// 定义三个变量Ta, Tb, Tc,用于存储三相的占空比float Ta, Tb, Tc;// 根据扇区号,选择对应的占空比计算公式switch (sector){case 1:Ta = T1 + T2 + T0 / 2; // 扇区1的Ta计算公式Tb = T2 + T0 / 2; // 扇区1的Tb计算公式Tc = T0 / 2; // 扇区1的Tc计算公式break;case 2:Ta = T1 + T0 / 2; // 扇区2的Ta计算公式Tb = T1 + T2 + T0 / 2; // 扇区2的Tb计算公式Tc = T0 / 2; // 扇区2的Tc计算公式break;case 3:Ta = T0 / 2; // 扇区3的Ta计算公式Tb = T1 + T2 + T0 / 2; // 扇区3的Tb计算公式Tc = T2 + T0 / 2; // 扇区3的Tc计算公式break;case 4:Ta = T0 / 2; // 扇区4的Ta计算公式Tb = T1 + T0 / 2; // 扇区4的Tb计算公式Tc = T1 + T2 + T0 / 2; // 扇区4的Tc计算公式break;case 5:Ta = T2 + T0 / 2; // 扇区5的Ta计算公式Tb = T0 / 2; // 扇区5的Tb计算公式Tc = T1 + T2 + T0 / 2; // 扇区5的Tc计算公式break;case 6:Ta = T1 + T2 + T0 / 2; // 扇区6的Ta计算公式Tb = T0 / 2; // 扇区6的Tb计算公式Tc = T1 + T0 / 2; // 扇区6的Tc计算公式break;default:Ta = 0; // 如果扇区号不正确,则设置Ta, Tb, Tc为0Tb = 0;Tc = 0;}// 将占空比转换为实际的电压值,voltage_power_supply是电源电压float Ua = Ta * voltage_power_supply;float Ub = Tb * voltage_power_supply;float Uc = Tc * voltage_power_supply;// 调用setPwm函数,设置三相的PWM电压值,以控制电机的扭矩setPwm(Ua, Ub, Uc);
}