☰
基于TMS320F28335的永磁同步电机FOC控制完整工程实现
2026/10/16 5:04:09 网站建设 项目流程

简介:围绕基于TMS320F28335的电机FOC控制整理的一份工程源码包,面向电机驱动、嵌入式控制领域的开发人员和学生,解决磁场定向控制与SVPWM调制在DSP平台上的落地实现问题。资源共135个文件,压缩包1.25MB,核心以33个h、25个c源文件构成,另有asm启动及中断例程、lib库、cmd链接脚本和gel调试配置文件,可配合CCS环境直接导入,便于学习与二次开发。已有2211人学习下载。包内提供Main.c、DSP2833x_DefaultIsr.c等关键代码,覆盖传感器数据采集、Clarke/Park坐标变换、d/q轴PI控制器设计以及SVPWM脉冲生成环节,同时带有DSP2833x标准外设库与调试配置文件,能帮助读者理解FOC完整控制流程,并根据实际电机参数调整适配。整体工程结构清晰,兼顾原理讲解与工程应用,适合用作电机控制入门与进阶的参考资料。

1. 为什么 28335 的 FOC 是工程问题,不只是算法问题

很多人第一眼看到“基于 28335 的电机 FOC 控制”这个标题,会默认难点在坐标变换的数学推导上。实际拆完这套代码你会发现,Clarke、Park、SVPWM 的公式只占很小一块面积,真正让电机稳定转起来的是 ADC 采样时序和 ePWM 载波的对齐、编码器电角度的初始对准、电流环 PI 参数的估算这些工程细节。它是一套完整的永磁同步电机矢量控制落地工程,覆盖相电流采样、坐标变换、双电流环控制到 SVPWM 生成 PWM 波形的完整链路,适合做伺服驱动、机器人和电动工具的开发者拿来当平台起点。这套代码的运行平台是 TMS320F28335,一颗带硬件浮点单元的 DSP,跑 10kHz 电流环绰绰有余,还有专门为电机控制设计的片上外设,这也是它在电机控制领域长期被选用的原因。

2. 平台与工程结构:为什么 28335 适合做 FOC

2.1 选 28335 的三个硬理由

先看芯片本身的竞争力。FOC 控制环路上全是浮点运算,坐标变换、PI 输出、SVPWM 计算,每一步都有小数运算。28335 是 C2000 系列里较早带单精度硬件浮点单元(FPU)的型号,主频能做到 150MHz,写代码的时候直接用 float 声明变量,编译器翻译成浮点指令一条条执行,不用像老一代 C28x 那样手动做 Q 格式定点换算。这一点对开发效率的提升非常大,我见过不少从定点 DSP 转过来的工程师,头一次在这颗芯片上写 FOC,被 IDF 数学库省掉的几百行救回来。

其次,电机控制需要的外设它基本都集成了。三个 ePWM 模块可以产生三相六路 PWM,自带死区生成单元,不用外部逻辑芯片插死区;ADC 模块可以被 ePWM 硬件触发,做到与 PWM 载波严格同步的电流采样;正交编码器接口(EQEP)直接接增量编码器的 A、B、Z 信号,四倍频计数由硬件完成。也就是说,从采样到输出 PWM 的整条通路都不需要 CPU 实时干预,CPU 只负责在每个载波周期里跑一遍 FOC 算法。

最后是中断响应能力。28335 的 PIE 中断控制器支持外设中断的快速响应和嵌套,ePWM 周期中断触发电流环,中断进入后直接读 ADC 结果、跑坐标变换和 PI、更新比较寄存器,整个过程可以控制在十几微秒以内。10kHz 的电流环在它上面属于比较常规的负载。

2.2 硬件拓扑:三相逆变、电流采样与编码器反馈

整套系统的硬件拓扑是标准的 FOC 驱动结构。直流母线电压经过三相全桥逆变器(一般用 IPM 智能功率模块)给电机供电,IPM 内部集成 IGBT 和驱动电路,28335 的 ePWM 输出经过六路缓冲后直接进 IPM 的输入引脚。母线电压通过电阻分压采样,送入 ADC 的其中一个通道,用于电压前馈和过压保护。

相电流采样有两种常见方式:双电阻采样和单电阻采样。这套工程里用的是双电阻方案,在逆变器下桥臂串联采样电阻,分别采 U、V 两相的电流,W 相电流由基尔霍夫定律计算得到。采样电阻上的电压经过运放放大和偏置处理后送 ADC 引脚,因为电流有正有负,运放会把信号抬升到 ADC 输入范围的中间值附近,代码里要做偏移校准。

转子位置反馈用的是增量式编码器,A、B 两路正交信号接 EQEP 模块,Z 信号用于每转一圈的零点校准。编码器线数常见的是 2500 线,四倍频后每圈 10000 个脉冲,对应 0.036 度的机械角度分辨率,对于 FOC 的电流环控制来说足够。因为没有绝对位置,上电后需要先做一次转子预定位,让转子转到已知电角度,这个细节后面单独讲。

2.3 工程目录与模块划分

这套工程的文件组织我拆完是这么个结构,也符合一般的 28335 DSP 项目习惯:

28335_foc/ ├── src/ │ ├── main.c // 系统初始化、主循环 │ ├── foc.c // FOC 主流程:电流环状态机 │ ├── svpwm.c // SVPWM 扇区判断与占空比计算 │ ├── pid.c // 位置式 PID 控制器 │ ├── encoder.c // EQEP 初始化和转子角度解算 │ ├── adc_drv.c // ADC 初始化、结果读取与偏移校准 │ └── pwm_drv.c // ePWM 初始化、死区配置、PWM 更新 ├── include/ │ ├── foc.h │ ├── svpwm.h │ ├── pid.h │ └── motor_cfg.h // 电机参数、极对数、编码器线数 ├── debug/ │ └── 28335_foc.out └── linker.cmd // DSP 内存分配脚本

foc.c 是核心,里面管 FOC 的运行状态;motor_cfg.h 里放电机参数和控制参数,换电机时只需改这个头文件。pid.c 独立出来是为了同时被电流环和速度环复用,后面加转速环不需要重写。linker.cmd 是 DSP 的内存分配脚本,如果要把代码跑在 RAM 里获得更高速度,也需要在这里面做段分配。

3. 核心算法实现:电流采样到 SVPWM 的完整链路

3.1 电流采样与坐标变换:Clarke 和 Park 的实现

FOC 的第一步是把三相定子电流变换到转子同步旋转坐标系,让电流控制变得像直流电机控制一样直观。先做 Clarke 变换,把三相电流从 U、V、W 轴系变换到静止的 α、β 轴系,再做 Park 变换,把 α、β 轴系的电流变换到随转子旋转的 d、q 轴系。

typedef struct { float i_alpha; float i_beta; } clark_out_t; clark_out_t clark_transform(float ia, float ib) { clark_out_t out; /* 三相平衡时 ic = -ia - ib,省掉一路 ADC */ float ic = -ia - ib; out.i_alpha = ia; /* 1.0f / 1.73205f,等幅值变换系数 */ out.i_beta = (ia + 2.0f * ib) * 0.57735f; (void)ic; return out; }

等幅值变换的好处是变换后的 α、β 轴幅值与实际相电压幅值一致,后面 SVPWM 的调制深度计算可以直接用电压值做比较,不需要额外换算。i_beta 的系数来自矩阵推导,等幅值变换下 Clarke 矩阵第二行是 1/√3 乘以 [1, 2, 0] 的组合。

typedef struct { float id; float iq; } park_out_t; park_out_t park_transform(float i_alpha, float i_beta, float theta) { park_out_t out; float cos_t = cosf(theta); float sin_t = sinf(theta); /* d 轴对齐转子磁极方向,q 轴超前 90° 电角度 */ out.id = i_alpha * cos_t + i_beta * sin_t; out.iq = -i_alpha * sin_t + i_beta * cos_t; return out; }

这里的关键是 theta 必须是电角度,不是编码器测出来的机械角度。电角度等于机械角度乘以电机极对数,如果电机是 4 对极,编码器转一圈,电角度已经转了四圈。这个乘关系如果漏掉,FOC 直接失效,电流环输出会乱跳。

3.2 电流环 PI 控制器:限幅和积分饱和处理

电流环用位置式 PID,d 轴电流环和 q 轴电流环各一个实例。FOC 里常用 PI 而非完整 PID,因为微分项会放大电流采样噪声。代码里我保留了 kd 字段但默认设为 0,想测试微分效果时可以直接调。

typedef struct { float kp; float ki; float kd; float integral; float i_limit; /* 积分限幅,防止积分饱和导致超调 */ float last_err; } pid_t; float pid_run(pid_t *pid, float ref, float fdb) { float err = ref - fdb; /* 积分项 */ pid->integral += pid->ki * err; if (pid->integral > pid->i_limit) pid->integral = pid->i_limit; if (pid->integral < -pid->i_limit) pid->integral = -pid->i_limit; /* 微分项,默认 kd=0 */ float d_err = err - pid->last_err; float out = pid->kp * err + pid->integral + pid->kd * d_err; pid->last_err = err; return out; }

这个 PID 实现有几个工程细节。积分限幅是必备的,电机堵转时误差一直在,积分会无限累加,一旦堵转解除,积分项释放会产生很大的过冲,限幅把积分项限制在 PI 输出的 20% 左右。另一个细节是 PI 输出本身也建议做限幅,比如 q 轴电流环输出限幅到额定电压的 80%,给 SVPWM 留出线性调制空间。

3.3 SVPWM 扇区判断与占空比计算

SVPWM 是整个算法的落地点,它把电压矢量从 α、β 轴系变成六路 PWM 的占空比。这套代码用经典的扇区判断加 X、Y、Z 查表法,计算量小,适合在中断里跑。

#define SQRT3 1.73205f typedef struct { uint16_t sector; /* 物理扇区 1~6 */ float t1, t2; /* 相邻基本矢量作用时间 */ float cnt_a, cnt_b, cnt_c; /* 三相比较值(相对量 0~1) */ } svpwm_out_t; svpwm_out_t svpwm_calc(float u_alpha, float u_beta, float udc, float ts) { svpwm_out_t out; uint16_t sec = 0; float x, y, z, ta, tb, tc; /* 扇区判断:三条边界线把矢量平面切成六个 60° 扇区 */ if (u_beta > 0.0f) sec |= 1; if (SQRT3 * u_alpha - u_beta > 0.0f) sec |= 2; if (-SQRT3 * u_alpha - u_beta > 0.0f) sec |= 4; /* sec 值 3/1/5/4/6/2 对应物理扇区 1~6,建表映射 */ static const uint16_t map[7] = {0, 2, 6, 1, 4, 3, 5}; out.sector = map[sec]; /* X、Y、Z 中间变量,单位是秒 */ x = SQRT3 * u_beta * ts / udc; y = (1.5f * u_alpha + 0.866f * u_beta) * ts / udc; /* 0.866 = sqrt3/2 */ z = (-1.5f * u_alpha + 0.866f * u_beta) * ts / udc; /* 按扇区分配两个相邻矢量的作用时间 */ switch (out.sector) { case 1: out.t1 = -z; out.t2 = x; break; case 2: out.t1 = y; out.t2 = z; break; case 3: out.t1 = x; out.t2 = -y; break; case 4: out.t1 = z; out.t2 = -x; break; case 5: out.t1 = -y; out.t2 = -z; break; case 6: out.t1 = -x; out.t2 = y; break; } /* 过调制保护:作用时间超出周期时按比例压缩 */ float sum = out.t1 + out.t2; if (sum > ts) { out.t1 = out.t1 / sum * ts; out.t2 = out.t2 / sum * ts; } /* 七段式发波切换时刻,T0 均分在波形两端和中间 */ float t0 = ts - out.t1 - out.t2; ta = t0 * 0.25f; tb = ta + out.t1 * 0.5f; tc = tb + out.t2 * 0.5f; out.cnt_a = ta / ts; out.cnt_b = tb / ts; out.cnt_c = tc / ts; return out; }

这段代码的输入是上一级逆 Park 变换输出的 u_alpha、u_beta,母线电压 udc 和 PWM 周期 ts。u_alpha、u_beta 的单位是伏特,udc 也是伏特,中间变量算出来的是作用时间秒数。输出 cnt_a、cnt_b、cnt_c 是归一化到 0~1 的比较值,外面乘上时基周期 TBPRD 就是写入 ePWM 比较寄存器的值。

再把三相比较值按扇区映射到具体的 ePWM 模块,这个映射决定触发矢量的开关顺序:

/* 扇区 1~6 对应的 a、b、c 相切换时刻分配 */ static const uint8_t phase_map[6][3] = { {0, 1, 2}, /* 扇区1: cnt_a = ta, cnt_b = tb, cnt_c = tc */ {1, 0, 2}, /* 扇区2 */ {2, 0, 1}, /* 扇区3 */ {2, 1, 0}, /* 扇区4 */ {1, 2, 0}, /* 扇区5 */ {0, 2, 1} /* 扇区6 */ };

3.4 电角度获取与转子初始定位

电角度的准确性直接决定 FOC 能不能稳定出力。这套工程用增量编码器,上电时不知道转子在哪个位置,必须先做预定位。常见做法是向 d 轴注入固定电流,让转子被磁场拉到与 d 轴对齐的位置,这时把编码器计数值清零,这个位置就是电角度零点。

void foc_align(void) { /* 开环: d 轴注入正向电流,q 轴电流为 0 */ pid_id.ref = 0.3f * MOTOR_RATED_CURRENT; pid_iq.ref = 0.0f; park_out_t align; align.id = pid_id.ref; align.iq = 0.0f; /* 电角度按 0 处理, 直接把电压矢量打在 d 轴上 */ svpwm_out_t sv = svpwm_calc(align.id, -align.iq, UDC, PWM_PERIOD); update_pwm_compare(&sv); DELAY_US(500000); /* 等待转子稳定, 500ms 足够 */ /* 清零编码器计数, 以此位置为电角度零点 */ EQep1Regs.QPOSCNT = 0; g_align_done = 1; }

注意这里的 svpwm_calc 输入 u_alpha、u_beta 时,如果电角度取 0 且 id > 0,等价的 u_alpha = id 而不是 0,这是为了让电压矢量方向与 d 轴重合。实际工程里有人直接让 u_alpha = 0、u_beta = 0,那电机永远不动。预定位之后,正常运行时编码器计数乘上 2π 除以每圈脉冲数得到机械角度,再乘极对数得到电角度。

4. 外设配置与中断架构:把算法落到 DSP 上

4.1 ePWM 配置:中心对齐模式与死区

三相全桥下桥臂和上桥臂不能同时导通,必须插死区。ePWM 模块自带死区生成功能,配置如下:

void pwm_init(void) { /* 时基: 150MHz 时钟, 中心对齐, 10kHz PWM */ EPwm1Regs.TBPRD = 7500; /* 150MHz / (2 * 7500) = 10kHz */ EPwm1Regs.TBCTL.bit.CTRMODE = 2; /* 增减计数模式 */ EPwm1Regs.TBCTL.bit.HSPCLKDIV = 0; /* 高频分频 1 分频 */ EPwm1Regs.TBCTL.bit.CLKDIV = 0; /* 时基分频 1 分频 */ /* 动作限定: 下溢输出高, 向上匹配 CMPA 输出低 */ EPwm1Regs.AQCTLA.bit.ZRO = 2; /* 下溢时置高 */ EPwm1Regs.AQCTLA.bit.CAU = 1; /* 递增匹配时清低 */ EPwm1Regs.AQCTLA.bit.CAD = 2; /* 递减匹配时置高 */ /* 死区: 0.5us, 150MHz 下 75 个时钟 */ EPwm1Regs.DBCTL.bit.OUT_MODE = 2; /* A/B 都经过死区模块 */ EPwm1Regs.DBCTL.bit.POLSEL = 0; /* A 不反相, B 反相 */ EPwm1Regs.DBRED = 75; EPwm1Regs.DBFED = 75; /* 周期中断: 下溢时触发 CPU 中断 */ EPwm1Regs.ETSEL.bit.INTEN = 1; EPwm1Regs.ETSEL.bit.INTSEL = 1; /* 计数器=0 时中断 */ EPwm1Regs.ETCLR.bit.INT = 1; /* 硬件触发 ADC: 计数器=0 时发 SOCA 信号 */ EPwm1Regs.ETSEL.bit.SOCAEN = 1; EPwm1Regs.ETSEL.bit.SOCASEL = 1; }

中心对齐模式对应 SVPWM 的七段式发波。TBPRD=7500 是用 150MHz 时基时钟除以 2 倍 PWM 频率算出来的,增减计数一个完整周期要计数 15000 拍,正好对应 10kHz。死区 75 个时钟在 150MHz 下是 0.5 微秒,对小功率 IPM 模块够用,功率稍大的建议放到 1 微秒以上。

4.2 ADC 触发采样:避开开关噪声

电流采样的时序是 FOC 调试里最容易踩坑的地方。采样点必须避开开关管开关瞬间,否则采样到的是充满振铃的开关噪声。这套代码用 ePWM 的 SOC 信号硬件触发 ADC,采样点自动落在 PWM 周期的下溢位置,也就是载波周期的中点,远离开关沿。

void adc_init(void) { AdcRegs.ADCTRL1.bit.ACQ_PS = 0; /* 采样窗 0 */ AdcRegs.ADCTRL1.bit.SEQ_CAS = 0; /* 双排序器 */ AdcRegs.ADCTRL3.bit.SMODE_SEL = 1; /* 同步采样模式 */ /* 3 路转换: U/W 相电流 + 母线电压 */ AdcRegs.ADCMAXCONV.all = 2; AdcRegs.ADCCHSELSEQ1.bit.CONV00 = 0; /* ADCINA0: U 相电流 */ AdcRegs.ADCCHSELSEQ1.bit.CONV01 = 1; /* ADCINA1: W 相电流 */ AdcRegs.ADCCHSELSEQ1.bit.CONV02 = 2; /* ADCINA2: 母线电压 */ }

ADC 的偏移校准放在初始化阶段做。运放电路的零电流偏置电压不一定在 ADC 量程正中,需要在上电后、母线电容放电完成时采一批数据算平均值存起来,后续每次电流采样先减这个偏移量再进 Clarke 变换。

4.3 EQEP 编码器接口与角度解算

void encoder_init(void) { /* 位置计数模式, 使能索引门控 */ EQep1Regs.QDECCTL.bit.QSP = 0; EQep1Regs.QDECCTL.bit.IGATE = 1; /* 四倍频计数上限: 2500 线 * 4 - 1 */ EQep1Regs.QPOSMAX = ENCODER_LINES * 4 - 1; EQep1Regs.QEPCTL.bit.QPEN = 1; } float encoder_get_theta(void) { uint32_t pos = EQep1Regs.QPOSCNT; float angle_mech = (float)pos / (float)(ENCODER_LINES * 4) * 2.0f * PI; /* 电角度 = 机械角度 * 极对数, 取余保证角度在 0~2π */ float theta = angle_mech * MOTOR_POLE_PAIRS; while (theta >= 2.0f * PI) theta -= 2.0f * PI; return theta; }

QPOSMAX 必须设成编码器线数乘 4 再减 1,EQEP 的硬件四倍频器会对 A、B 两路的每个沿计数,2500 线编码器一圈有 10000 个计数。如果 QPOSMAX 设置错误,计数溢出后角度会跳变,FOC 在高速时表现为突然失步。

4.4 控制环路调度:三层环路分工

整套控制系统是典型的级联结构。最内层是电流环,在 ePWM 周期中断里 10kHz 运行;中等层是速度环,用低速定时器 1kHz 运行;最外层位置环在 main 循环里跑,频率可以更慢。中断服务程序是整套 FOC 的核心执行现场:

__interrupt void pwm1_isr(void) { /* 1. 读 ADC 结果, 还原真实电流值 */ float ia = adc_read_current(0); float ib = adc_read_current(1); /* 2. 取转子电角度 */ float theta = encoder_get_theta(); /* 3. Clarke + Park 变换 */ clark_out_t clk = clark_transform(ia, ib); park_out_t prk = park_transform(clk.i_alpha, clk.i_beta, theta); /* 4. 电流环 PI: d 轴电流给 id_ref, q 轴电流给 iq_ref */ float ud = pid_run(&pid_id, id_ref, prk.id); float uq = pid_run(&pid_iq, iq_ref, prk.iq); /* 5. 逆 Park 变换到静止坐标系 */ float u_alpha = ud * cosf(theta) - uq * sinf(theta); float u_beta = ud * sinf(theta) + uq * cosf(theta); /* 6. SVPWM 调制成占空比, 写比较寄存器 */ svpwm_out_t sv = svpwm_calc(u_alpha, u_beta, UDC, PWM_PERIOD); update_pwm_compare(&sv); /* 7. 清中断标志, 响应下一次中断 */ EPwm1Regs.ETCLR.bit.INT = 1; PieCtrlRegs.PIEACK.all = PIEACK_GROUP3; }

id_ref 在普通 FOC 里设为 0,也就是让定子电流的励磁分量为零,所有电流都用来产生转矩。如果要跑弱磁控制,id_ref 改负值就行,代码结构不用变。

5. 调试避坑:FOC 项目的常见问题与排查记录

5.1 电流波形毛刺严重,电机运行时声音刺耳

现象:用调试器查看 ADC 采到的相电流波形,上面叠加了大量尖刺脉冲,电机低速时噪音明显。

原因:ADC 采样点落在了 PWM 开关沿附近。PWM 周期里开关管开关瞬间母线电压会有很大的 dv/dt,通过寄生电容耦合到采样电阻和运放输入端。代码里虽然用了 ePWM 触发 ADC,但触发点配置在计数上溢,和死区时段重叠了。

解决:把 SOC 触发点改到计数器下溢位置,也就是载波周期的最低点,这个位置远离所有开关动作。同时将采样窗 ACQ_PS 从 0 改到 1~2,延长采样窗时间,让采样电容有充足时间充电到输入电压。改完再看波形,毛刺幅度能降一个数量级。

5.2 电机猛烈抖动但不转,电流环输出饱和

现象:预定位时转子能吸到固定位置,但一接上速度环指令就剧烈抖动,母线电流冲到限幅值。

原因:这是相序错误。U、V、W 三相如果接反,电流环输出的电压矢量方向和转子实际位置不匹配,导致正反馈。排查时发现 IPM 输出到电机的三相线有一相接反了,Clarke 变换算出来的电流方向和实际电机磁场方向差 120 度。

解决:交换任意两相电机线,重新上电测试。做这个排查的时候最稳妥的方式是开环测试:给定一个固定电压矢量,观察转子转动的方向是否符合预期,确认相序后再合闭环。

5.3 高速时转矩明显下降,电流波形出现削顶

现象:转速到 3000rpm 附近时,q 轴电流要做很大才能维持速度,母线电流波形出现平台削顶。

原因:SVPWM 进入过调制区了。电压矢量的幅值超过了正六边形的内切圆,svpwm_calc 里过调制保护触发,把 t1、t2 按比例压缩。这时输出电压波形已经畸变,谐波分量增加,电机的转矩输出能力大幅下降。

解决:一方面在速度环输出到 iq_ref 之间加限幅,避免电流环一直往饱和里打;另一方面把 UDC 的取值从实测母线电压改成母线电压的一半,给 SVPWM 留出线性调制余量。如果高速性能是刚需,就要上弱磁控制,用负的 id_ref 去削弱转子磁场,这是后话。

5.4 电机转到某个位置就掉转矩,有明显的顿挫感

现象:电机匀速转动时,在某个机械角度附近转矩明显减少,甚至停顿一下才能继续转,用调试器看电角度在故障点跳变。

原因:编码器 Z 信号的标定没做。增量编码器每次上电后计数器不是从 0 开始,机械角度始终带着未知偏置。如果只做预定位、不清零 Z 信号,随着转速升高,累计的位置误差会让电角度在某个点发生跳变。

解决:有一种做法是在预定位完成后,缓慢开环转一圈,捕获 Z 信号位置并记录计数值,把这个计数值作为零点偏移。从那以后每次上电先找 Z 信号,找不到就不允许合闭环。Z 信号位置标定写进编码器初始化流程后,这个故障出现的概率几乎降为零。

5.5 参数整定越调越震荡,电流环频繁过流报警

现象:按照公式算出来的 PI 参数,跑起来电流环振荡,试着调小 P 和 I 还是振,最后报过流故障。

原因:PI 参数整定忽略了积分限幅和输出限幅的配合。我之前只改了 kp 和 ki,i_limit 赖在初始值没动,结果积分项在限幅附近来回摆动,等效为一个持续激励的振荡源。

解决:整定电流环时,先把积分限幅设为额定电压的 10%,pi 限幅设为 50%;用带宽法算出初值后,单独验证 d 轴电流阶跃响应的超调量,只调 kp,确认稳态误差能从图表上看出趋势后再逐步放开 ki。从那以后我每次换电机调电流环,都强制走一遍完整的限幅检查、阶跃测试、积分项释放验证的流程,省下的都是通电烧管的钱。

6. 二次开发:如何把这份工程改造成自己的电机驱动

6.1 换电机时的参数适配流程

这套代码的电机参数全部集中在 motor_cfg.h 头文件里,换电机只需要动这里:

参数获取方式影响
极对数看铭牌或用测功机测反电动势频率电角度与机械角度的换算比例
编码器线数编码器手册QPOSMAX 和角度分辨率
相电阻 Rs万用表测线电阻除以 2电流环 ki 初值,电机发热估算
相电感 Ls电桥测量电流环 kp 初值,电流纹波大小
额定电流铭牌电流环限幅、预定位电流

电流环 PI 初值可以按带宽法估算。设电流环带宽预设为 200Hz,kp 约等于 Ls × 2π × 200,ki 约等于 Rs × 2π × 200。这个初值不算精确,但能让闭环稳定,之后用阶跃响应微调。实际调试中我发现 kp 微调的意义比 ki 大得多。

6.2 从有感 FOC 改成无感 FOC

去掉编码器后,转子位置要靠定子电压和电流估算。常见做法是滑模观测器,通过检测反电动势间接获得转子位置,28335 的浮点运算能力跑一个滑模观测器没什么压力。改造时把 encoder_get_theta() 替换为观测器输出的估计角度,启动阶段用 I/F 强拉方式把转子带起来,达到 500rpm 左右再切换到观测器闭环。零速和极低速下无感方案没有输出能力,这个物理限制想突破就得换高频注入法,工作量会明显增加。

6.3 增加速度环和位置环

在 iq_ref 上面再加一级速度 PI 环,就能从电流控制升级成速度控制。速度环输出就是 iq_ref,反馈是编码器计算出的转速。速度环带宽远低于电流环,一般取电流环的 1/10,即 1kHz 左右,放在低速定时器中断里执行。位置环再往下就简单了,位置误差进位置控制器,输出作为速度参考。

这套代码的模块划分让加环这件事很自然:pid.c 本身就是可复用的,速度环和位置环各建一个 pid_t 实例,中断里先跑位置环再跑速度环最后跑电流环。改完架构后,我把转速阶跃响应和电流波形都拉出来对比过,逻辑清晰,数据可靠,整套东西吃透了之后,往无感、弱磁、参数辨识哪个方向掘都有底子。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询