FOC开环速度控制
·


DengFOC开环无刷电机控制代码完整解读
这是灯哥开源的纯手写ESP32无刷电机开环FOC控制代码,不依赖任何第三方FOC库,仅使用ESP32原生LED PWM外设实现三相PWM输出,是学习FOC原理的绝佳入门代码。
一、完整IO配置清单(核心问题)
代码中总共使用了4个GPIO引脚,全部配置为输出模式,具体如下:
| 引脚号 | 功能定义 | 工作模式 | 详细配置 |
|---|---|---|---|
| GPIO12 | 电机驱动使能引脚 | 数字输出 | 初始电平:HIGH(高电平使能V4版本电机驱动板) |
| GPIO32 | PWM A相输出 | LED PWM输出 | 绑定通道0,频率30kHz,8位分辨率(0-255) |
| GPIO33 | PWM B相输出 | LED PWM输出 | 绑定通道1,频率30kHz,8位分辨率(0-255) |
| GPIO25 | PWM C相输出 | LED PWM输出 | 绑定通道2,频率30kHz,8位分辨率(0-255) |
IO配置关键说明
- 30kHz PWM频率:选择这个频率是为了完全避开人耳听觉范围(20Hz-20kHz),消除电机运行时的高频啸叫声
- 8位分辨率:对应占空比范围0-255,对于开环FOC控制已经足够,同时能保证30kHz的高频率输出
- GPIO12使能逻辑:DengFOC V4驱动板采用高电平使能,低电平会切断电机电源
- ESP32 LED PWM特性:最多支持16个独立PWM通道,可任意映射到GPIO引脚,非常适合多相电机控制
二、代码整体结构与核心逻辑
1. 全局变量与宏定义
// 三相PWM输出引脚定义
int pwmA = 32;
int pwmB = 33;
int pwmC = 25;
// 数值约束宏,替代Arduino原生constrain函数
#define _constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)))
// 系统参数
float voltage_power_supply = 12.6; // 电源电压(3S锂电池满电电压)
float shaft_angle = 0; // 电机机械角度(开环积分得到)
float open_loop_timestamp = 0; // 上一次循环的时间戳
float zero_electric_angle = 0; // 电角度零点偏移(开环时无需校准)
2. 初始化函数setup()
void setup() {
Serial.begin(115200); // 串口初始化,用于调试输出
// 电机驱动使能
pinMode(12, OUTPUT);
digitalWrite(12, HIGH); // 开启电机驱动电源
// PWM引脚模式设置
pinMode(pwmA, OUTPUT);
pinMode(pwmB, OUTPUT);
pinMode(pwmC, OUTPUT);
// LED PWM控制器配置
ledcSetup(0, 30000, 8); // 通道0,30kHz,8位分辨率
ledcSetup(1, 30000, 8); // 通道1,30kHz,8位分辨率
ledcSetup(2, 30000, 8); // 通道2,30kHz,8位分辨率
// 将PWM引脚绑定到对应的LED PWM通道
ledcAttachPin(pwmA, 0);
ledcAttachPin(pwmB, 1);
ledcAttachPin(pwmC, 2);
Serial.println("完成PWM初始化设置");
delay(3000); // 3秒延时,方便观察初始化完成
}
3. 核心数学辅助函数
// 机械角度转换为电角度
// 电角度 = 机械角度 × 极对数
float _electricalAngle(float shaft_angle, int pole_pairs) {
return (shaft_angle * pole_pairs);
}
// 将任意角度归一化到 [0, 2π] 范围
// 解决角度溢出和负数问题
float _normalizeAngle(float angle) {
float a = fmod(angle, 2 * PI);
return a >= 0 ? a : (a + 2 * PI);
}
4. PWM输出设置函数
void setPwm(float Ua, float Ub, float Uc) {
// 将三相电压转换为占空比(0-1)
// 占空比 = 相电压 / 电源电压
dc_a = _constrain(Ua / voltage_power_supply, 0.0f, 1.0f);
dc_b = _constrain(Ub / voltage_power_supply, 0.0f, 1.0f);
dc_c = _constrain(Uc / voltage_power_supply, 0.0f, 1.0f);
// 写入PWM寄存器(8位分辨率对应0-255)
ledcWrite(0, dc_a * 255);
ledcWrite(1, dc_b * 255);
ledcWrite(2, dc_c * 255);
}
5. FOC核心变换函数
void setPhaseVoltage(float Uq, float Ud, float angle_el) {
// 电角度归一化,加上零点偏移
angle_el = _normalizeAngle(angle_el + zero_electric_angle);
// 帕克逆变换(d-q坐标系 → α-β坐标系)
// 开环控制时Ud=0,只需要Uq分量
Ualpha = -Uq * sin(angle_el);
Ubeta = Uq * cos(angle_el);
// 克拉克逆变换(α-β坐标系 → 三相静止坐标系)
// 这里使用了中心对齐PWM的简化计算,加上电源电压的一半作为直流偏置
Ua = Ualpha + voltage_power_supply / 2;
Ub = (sqrt(3) * Ubeta - Ualpha) / 2 + voltage_power_supply / 2;
Uc = (-Ualpha - sqrt(3) * Ubeta) / 2 + voltage_power_supply / 2;
// 设置三相PWM输出
setPwm(Ua, Ub, Uc);
}
6. 开环速度控制函数
float velocityOpenloop(float target_velocity) {
// 获取当前系统时间(微秒)
unsigned long now_us = micros();
// 计算两次循环之间的时间间隔(秒)
float Ts = (now_us - open_loop_timestamp) * 1e-6f;
// 时间戳溢出保护(micros()约70分钟溢出一次)
if (Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
// 开环角度积分:通过目标速度和时间间隔计算当前机械角度
// 这是开环控制的核心,没有编码器反馈,纯靠时间积分估算角度
shaft_angle = _normalizeAngle(shaft_angle + target_velocity * Ts);
// 设置q轴电压(决定输出力矩)
// 最大安全值为 voltage_power_supply/2,超过会导致电压限幅失真
float Uq = voltage_power_supply / 3;
// 计算并输出三相电压
// 极对数为7,对应DengFOC配套的2208电机
setPhaseVoltage(Uq, 0, _electricalAngle(shaft_angle, 7));
// 更新时间戳
open_loop_timestamp = now_us;
return Uq;
}
7. 主循环函数
void loop() {
// 以5rad/s的角速度开环运行
// 1rad/s ≈ 9.55rpm,5rad/s ≈ 47.75rpm(机械转速)
velocityOpenloop(5);
}
三、代码关键特性与注意事项
- 纯手写无库依赖:代码不依赖SimpleFOC等任何第三方库,所有FOC算法都由基础数学运算实现,非常适合学习原理
- 开环控制特性:
- 不需要编码器或霍尔传感器
- 低速力矩较小,高速容易丢步
- 适合入门学习和简单应用
- 可修改参数:
voltage_power_supply:根据实际电源电压修改(如12V电源改为12.0)pole_pairs:根据电机极对数修改(代码中为7,对应2208电机)Uq:调整输出力矩,最大不超过voltage_power_supply/2target_velocity:修改电机目标转速(单位:rad/s)
- 硬件兼容性:仅在DengFOC V4开发板上测试过,其他ESP32开发板需要根据实际引脚定义修改
需要我帮你把这段代码改成支持正反转切换和串口调速的版本吗?
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)