1. 从零开始手写FOC电机控制代码的动机
作为一名嵌入式开发工程师,我最近在正点原子STM32F407电机开发板上学习FOC(Field Oriented Control,磁场定向控制)时遇到了不小的挫折。官方教程虽然提供了完整的电机控制代码库,但直接跳过了代码实现细节,这对于想要深入理解FOC原理的学习者来说实在不够友好。
我购买的正点原子STM32F407电机开发板配套了完整的硬件环境,包括三相逆变电路、电流采样电路和编码器接口。板载的STM32F407ZGT6芯片具有足够的计算能力来实现FOC算法,官方提供的电机控制库虽然能用,但作为学习者,我更想知道每个寄存器配置背后的意义,每个PWM波形产生的原理,以及每个数学变换的实际作用。
2. FOC控制基础与开发环境搭建
2.1 FOC控制原理概述
FOC控制的核心思想是将三相交流电机等效为直流电机来控制。通过Clarke变换将三相静止坐标系转换为两相静止坐标系,再通过Park变换将两相静止坐标系转换为两相旋转坐标系。在旋转坐标系下,我们可以像控制直流电机一样分别控制转矩电流(Iq)和励磁电流(Id)。
2.2 开发环境准备
硬件准备:
- 正点原子STM32F407电机开发板
- 配套的永磁同步电机(PMSM)
- 12V/2A直流电源
- ST-Link调试器
- 示波器(用于观测PWM波形)
软件环境:
- Keil MDK-ARM开发环境
- STM32CubeMX配置工具
- STM32F4xx HAL库
- 串口调试助手
提示:建议使用STM32CubeMX生成基础工程框架,这样可以确保时钟配置和引脚分配正确,避免在基础配置上浪费时间。
3. 从开环控制开始构建FOC系统
3.1 PWM信号生成配置
在STM32F407上,我们使用TIM1的高级定时器功能生成三相六路PWM信号。关键配置如下:
c复制// PWM频率设置为16kHz
htim1.Instance = TIM1;
htim1.Init.Prescaler = 84-1; // 84MHz/84 = 1MHz
htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3;
htim1.Init.Period = 62; // 1MHz/63 ≈ 16kHz
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
3.2 开环速度控制实现
开环控制是验证硬件是否正常工作的第一步。我们通过固定占空比的PWM信号驱动电机旋转:
c复制void Motor_OpenLoop_Run(float duty)
{
// 计算比较值
uint32_t cmp = (uint32_t)(duty * htim1.Init.Period);
// 设置三相PWM占空比
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, cmp);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, cmp);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, cmp);
// 启动PWM输出
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_3);
}
注意:开环运行时需要逐步增加占空比,突然施加高占空比可能导致电机过流损坏。
4. 增加电流采样实现闭环控制
4.1 电流采样硬件配置
正点原子开发板使用三个采样电阻和运放电路来检测相电流。我们需要配置ADC进行同步采样:
c复制// ADC配置示例
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_TRGO;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 3;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
4.2 Clarke和Park变换实现
电流采样后,我们需要进行坐标变换:
c复制// Clarke变换
void Clarke_Transform(float ia, float ib, float ic, float *ialpha, float *ibeta)
{
*ialpha = ia;
*ibeta = (ia + 2*ib) * ONE_BY_SQRT3;
}
// Park变换
void Park_Transform(float ialpha, float ibeta, float sin_theta, float cos_theta, float *id, float *iq)
{
*id = ialpha * cos_theta + ibeta * sin_theta;
*iq = -ialpha * sin_theta + ibeta * cos_theta;
}
4.3 PI控制器实现
FOC系统需要两个PI控制器分别控制Id和Iq:
c复制typedef struct {
float Kp;
float Ki;
float integral;
float max_output;
float max_integral;
} PI_Controller;
float PI_Update(PI_Controller *pi, float error, float dt)
{
pi->integral += error * dt;
// 积分限幅
if(pi->integral > pi->max_integral) pi->integral = pi->max_integral;
if(pi->integral < -pi->max_integral) pi->integral = -pi->max_integral;
float output = pi->Kp * error + pi->Ki * pi->integral;
// 输出限幅
if(output > pi->max_output) output = pi->max_output;
if(output < -pi->max_output) output = -pi->max_output;
return output;
}
5. 位置检测与速度估算
5.1 霍尔传感器接口配置
正点原子开发板支持霍尔传感器接口,我们需要配置TIM2为接口模式:
c复制htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFFFFFF;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = 0;
HAL_TIM_IC_ConfigChannel(&htim2, &sConfig, TIM_CHANNEL_1);
5.2 速度估算算法
基于霍尔信号变化间隔估算电机速度:
c复制float Get_Speed_From_Hall(uint32_t delta_t)
{
if(delta_t == 0) return 0;
// 每转6个边沿,电机极对数为P
float speed_rps = 1.0f / (6 * delta_t * P);
return speed_rps * 60; // 转换为RPM
}
6. SVPWM波形生成
6.1 空间矢量调制原理
SVPWM通过组合8种基本电压矢量来合成任意方向的电压矢量。我们需要将逆Park变换后的Vα和Vβ转换为三相占空比:
c复制void SVPWM_Generate(float Valpha, float Vbeta, float *Ta, float *Tb, float *Tc)
{
// 计算扇区
int sector = 0;
if(Vbeta > 0) sector += 1;
if(Valpha * SQRT3 > Vbeta) sector += 2;
if(-Valpha * SQRT3 > Vbeta) sector += 4;
// 根据扇区计算占空比
switch(sector) {
case 1:
*Ta = (SQRT3 * Ts * Vbeta) / Vdc;
*Tb = (SQRT3 * Ts * (SQRT3/2 * Valpha + 0.5 * Vbeta)) / Vdc;
*Tc = 0;
break;
// 其他扇区类似...
}
// 归一化占空比
float Tmax = fmaxf(fmaxf(*Ta, *Tb), *Tc);
float Tmin = fminf(fminf(*Ta, *Tb), *Tc);
float Toffset = (1 - Tmax - Tmin) / 2;
*Ta += Toffset;
*Tb += Toffset;
*Tc += Toffset;
}
6.2 PWM寄存器更新
将计算得到的占空比写入TIM1的比较寄存器:
c复制void Update_PWM_Duty(float Ta, float Tb, float Tc)
{
uint32_t cmp_a = (uint32_t)(Ta * htim1.Init.Period);
uint32_t cmp_b = (uint32_t)(Tb * htim1.Init.Period);
uint32_t cmp_c = (uint32_t)(Tc * htim1.Init.Period);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, cmp_a);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, cmp_b);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, cmp_c);
}
7. 完整FOC控制循环实现
7.1 主控制循环结构
在定时器中断中实现FOC控制循环:
c复制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim3) { // 10kHz控制频率
// 1. 读取电流采样值
float ia = ADC_Get_Current(0);
float ib = ADC_Get_Current(1);
// 2. Clarke变换
float ialpha, ibeta;
Clarke_Transform(ia, ib, -ia-ib, &ialpha, &ibeta);
// 3. 获取转子位置
float theta = Get_Rotor_Angle();
float sin_theta, cos_theta;
arm_sin_cos_f32(theta * RAD_TO_DEG, &sin_theta, &cos_theta);
// 4. Park变换
float id, iq;
Park_Transform(ialpha, ibeta, sin_theta, cos_theta, &id, &iq);
// 5. PI控制
float vd = PI_Update(&pi_id, id_ref - id, 0.0001f);
float vq = PI_Update(&pi_iq, iq_ref - iq, 0.0001f);
// 6. 逆Park变换
float valpha = vd * cos_theta - vq * sin_theta;
float vbeta = vd * sin_theta + vq * cos_theta;
// 7. SVPWM生成
float Ta, Tb, Tc;
SVPWM_Generate(valpha, vbeta, &Ta, &Tb, &Tc);
// 8. 更新PWM
Update_PWM_Duty(Ta, Tb, Tc);
}
}
7.2 参数整定技巧
FOC系统需要调整多个PI控制器参数,以下是一些实用技巧:
- 先调速度环,再调电流环
- 从较小的Kp开始,逐步增加
- Ki值通常设为Kp的1/10到1/100
- 观察阶跃响应,调整到无明显超调
- 实际测试中,可以先用手轻轻阻止电机旋转,观察电流响应
8. 调试技巧与常见问题解决
8.1 调试工具的使用
- 使用J-Scope实时观测变量
- 通过串口发送调试数据到PC
- 使用逻辑分析仪捕获PWM波形
- 用示波器观察电流波形
8.2 常见问题及解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 电机不转 | PWM输出未使能 | 检查TIM1的PWM输出配置 |
| 电机抖动 | 电流采样相位错误 | 检查电流采样电阻连接 |
| 过流保护触发 | PI参数过大 | 减小Kp和Ki值 |
| 速度不稳定 | 霍尔信号干扰 | 增加霍尔信号滤波电容 |
| 发热严重 | SVPWM计算错误 | 检查SVPWM扇区判断逻辑 |
9. 性能优化与进阶功能
9.1 使用ARM DSP库加速运算
STM32F407支持浮点运算和DSP指令集,可以显著提高FOC计算速度:
c复制#include "arm_math.h"
// 使用DSP库进行Park变换
void Park_Transform_DSP(float ialpha, float ibeta, float sin_theta, float cos_theta, float *id, float *iq)
{
*id = ialpha * cos_theta + ibeta * sin_theta;
*iq = -ialpha * sin_theta + ibeta * cos_theta;
}
9.2 无传感器FOC实现
对于没有位置传感器的应用,可以通过滑模观测器或高频注入法估算转子位置:
c复制void Sliding_Observer_Update(float ialpha, float ibeta, float Valpha, float Vbeta,
float *theta_est, float *speed_est)
{
// 实现滑模观测器算法
// ...
}
在实际项目中,我发现从零开始实现FOC代码虽然耗时,但对理解电机控制原理有极大帮助。建议初学者按照开环→电流闭环→速度闭环的顺序逐步实现,每完成一步都充分测试验证后再继续下一步。调试过程中,保存各个阶段的代码版本非常重要,这样当出现问题时可以快速回溯到上一个正常工作状态。
