无感FOC(Field Oriented Control)算法在电机控制领域一直是个热门话题。我最近在DSP28335平台上折腾这个算法时,发现网上资料要么过于理论化,要么就是直接给代码不解释原理。这次我想把整个实现过程掰开揉碎讲清楚,特别是那些容易踩坑的细节。
为什么选择DSP28335?这颗TI的2000系列DSP在电机控制领域堪称经典,150MHz主频、硬件浮点单元、12位ADC,还有专门针对PWM优化的外设。最关键的是它的CLA(Control Law Accelerator)协处理器,能实现真正的并行计算——主核跑逻辑,CLA专攻算法,这种架构对实时性要求高的FOC控制再合适不过。
无感FOC相比有传感器方案,省去了霍尔或编码器,降低了系统成本和复杂度。但难点在于要实时估算转子位置和速度,这对算法精度和处理器算力都是考验。我采用的滑模观测器(SMO)方案,在DSP28335上实测能达到±1°的角度估算精度,足够驱动大多数永磁同步电机(PMSM)。
DSP28335核心板需要特别注意电源设计。芯片需要1.9V内核电压和3.3V IO电压,必须采用TPS767D301这类双路LDO。我在第一版设计时犯了个错误——把1.9V和3.3V的滤波电容都用成了10μF,结果高频响应很差。后来改成1.9V用1μF陶瓷电容+10μF钽电容组合,3.3V用0.1μF陶瓷电容并联,纹波立即改善。
电机驱动部分推荐使用DRV8305+MOSFET的方案。这个预驱芯片集成电流采样放大器和Buck稳压器,特别省心。注意栅极电阻要选对——我用的是10Ω+二极管并联的方案,既能保证开关速度,又不会引起过大的电压尖峰。
PWM模块配置有讲究:
c复制EPwm1Regs.TBPRD = SYSTEM_FREQ / (2 * PWM_FREQ); // 15kHz PWM
EPwm1Regs.TBPHS.half.TBPHS = 0; // 相位清零
EPwm1Regs.CMPA.half.CMPA = 0; // 占空比初始值
EPwm1Regs.AQCTLA.bit.CAU = AQ_SET; // 比较匹配时置高
EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR; // 周期匹配时置低
ADC采样要配合PWM触发,建议在PWM周期中点采样电流。这样能避开开关噪声:
c复制AdcRegs.ADCTRL1.bit.ACQ_PS = 0xF; // 采样窗口=15个时钟周期
AdcRegs.ADCTRL3.bit.SMODE_SEL = 1; // 顺序采样模式
AdcRegs.ADCTRL1.bit.SEQ_CASC = 1; // 级联序列器模式
滑模观测器的核心方程:
code复制Eα = Vα - Rs*Iα - Ls*dIα/dt + ω*λq
Eβ = Vβ - Rs*Iβ - Ls*dIβ/dt - ω*λd
在DSP上实现时,微分项用后向差分法离散化:
c复制float Ialpha_prev, Ibeta_prev;
void SMO_Update() {
float dIalpha = (Ialpha - Ialpha_prev) / Ts;
float dIbeta = (Ibeta - Ibeta_prev) / Ts;
Ealpha = Valpha - Rs*Ialpha - Ls*dIalpha + we*Lambda_q;
Ebeta = Vbeta - Rs*Ibeta - Ls*dIbeta - we*Lambda_d;
Ialpha_prev = Ialpha;
Ibeta_prev = Ibeta;
}
注意:Rs和Ls参数必须准确,建议先用LCR表实测电机参数。我有个项目因为用了标称值,结果观测器一直发散。
角度估算的PLL参数直接影响动态响应:
c复制#define Kp_PLL 0.05f
#define Ki_PLL 0.001f
void PLL_Update(float Ealpha, float Ebeta) {
float theta_err = atan2f(Ebeta, Ealpha) - theta_est;
we_integral += Ki_PLL * theta_err;
we = Kp_PLL * theta_err + we_integral;
theta_est += we * Ts;
}
调试技巧:
电流环PID参数经验公式:
code复制Kp = Ls * 2π * BW
Ki = Rs * 2π * BW / Ls
其中BW取1/10 PWM频率。我的实测参数:
c复制PID_Id.Kp = 0.35f; // d轴
PID_Id.Ki = 120.0f;
PID_Iq.Kp = 0.35f; // q轴
PID_Iq.Ki = 120.0f;
调试时先只开d轴(Iq=0),观察Id能否跟踪设定值。有个坑要注意:电流采样偏移会导致零电流时有输出,一定要做ADC偏移校准:
c复制// 电机静止时采样100次取平均
AdcOffset = 0;
for(int i=0; i<100; i++) {
AdcOffset += AdcResult.ADCRESULT0;
DELAY_US(100);
}
AdcOffset /= 100;
无感FOC最难的是启动。我采用的三段式启动:
关键代码逻辑:
c复制if(startup_step == 0) {
// 预定位阶段
theta_forced = 0;
Vout = STARTUP_VOLTAGE;
if(timer > 200ms) startup_step++;
} else if(startup_step == 1) {
// 开环加速
theta_forced += we_forced * Ts;
Vout += STARTUP_RAMP_RATE * Ts;
if(we_forced > SWITCH_SPEED) startup_step++;
} else {
// 正常闭环运行
Vout = PID_Out;
theta_forced = theta_est;
}
现象:高速时角度估算突然跳变
排查过程:
c复制Ealpha = ... + K_comp * (Ialpha - Ialpha_prev);
问题:转速<5%额定转速时转矩脉动明显
优化措施:
c复制K_slide = K_base + K_adapt * fabs(we);
负载突变时转速波动大?试试这些技巧:
c复制if(fabs(we_err) > THRESHOLD) {
PID_Speed.Kp = Kp_high;
} else {
PID_Speed.Kp = Kp_low;
}
c复制Iq_ref += J * dw_ref / (1.5 * Pp * Flux);
把耗时操作卸载到CLA:
c复制#pragma CLA_OBJECT(smo_task)
void CLA_smo_task() {
// CLA专属内存访问更快
__shared float Ialpha_CLA, Ibeta_CLA;
Ialpha_CLA = AdcResult.ADCRESULT0;
Ibeta_CLA = AdcResult.ADCRESULT1;
// SMO计算...
}
注意:CLA和主核共享变量要用#pragma DATA_SECTION指定到特定段。
对性能敏感的部分改用Q格式:
c复制#define Q15 (1.0f / 32768.0f)
int16_t Iq_ref_Q15 = (int16_t)(Iq_ref / Q15);
// 在PID计算中使用
int32_t temp = (int32_t)Kp_Q15 * err_Q15;
利用DSP的SCI模块输出调试数据:
c复制struct {
float theta;
float speed;
uint16_t crc;
} telemetry;
void send_telemetry() {
telemetry.theta = theta_est;
telemetry.speed = we;
telemetry.crc = calc_crc(&telemetry, sizeof(telemetry)-2);
scia_xmit((uint8_t*)&telemetry, sizeof(telemetry));
}
配合上位机工具(如Python+PySerial)可实时绘制波形。
测试平台:
性能指标:
| 项目 | 空载 | 额定负载 |
|---|---|---|
| 转速波动 | ±2rpm | ±5rpm |
| 效率 | 92% | 89% |
| 启动时间 | 0.3s | 0.5s |
最终参数表(供参考):
c复制#define RS 0.5f // 定子电阻(Ω)
#define LS 0.0015f // 定子电感(H)
#define FLUX 0.05f // 永磁体磁链(Wb)
#define J 0.0001f // 转动惯量(kg·m²)
#define SMO_K 50.0f // 滑模增益
#define PLL_BW 50.0f // 锁相环带宽(rad/s)
调参时建议先用小功率电机(<100W)验证,等算法稳定后再上大功率。我实验室的57电机就因此逃过了至少三次炸机命运。