1. 项目概述:基于STM32的SimpleFOC开环电机控制
在嵌入式电机控制领域,无刷直流电机(BLDC)因其高效率、高扭矩密度和长寿命等优势,正逐步取代传统有刷电机。但对于初学者而言,磁场定向控制(FOC)算法的复杂性往往成为入门障碍。本文将详细介绍基于STM32F10x系列微控制器和SimpleFOC框架的开环控制实现方案。
这个项目特别适合刚接触电机控制的开发者,它通过开环控制方式降低了入门门槛,同时保留了向闭环控制升级的扩展性。系统采用模块化设计,包含硬件驱动层、算法核心层和用户接口层,完整实现了速度/位置开环控制功能。我在实际测试中使用了一款7对极的BLDC电机,在12V供电条件下,开环速度控制范围可达0-20rad/s(约0-3.2转/秒),位置控制精度在无负载情况下约为±5°。
注意:开环控制虽简单易实现,但存在丢步风险,不适合高精度或大负载场景。建议仅作为学习闭环控制前的过渡方案。
2. 硬件架构与核心配置
2.1 STM32最小系统设计
项目采用STM32F103C8T6作为主控芯片,其72MHz主频和丰富的外设资源完全满足需求。关键硬件配置包括:
- 电源电路:12V直流输入经LM2596降压至5V,再通过AMS1117-3.3转换为MCU工作电压
- 电机驱动:使用IR2104S半桥驱动芯片配合6个N沟道MOSFET(如IRLZ44N)组成三相逆变桥
- 信号调理:在PWM输出端加入100Ω电阻和10nF电容组成低通滤波器,减少高频干扰
- 调试接口:USB转TTL模块通过PA9/PA10实现串口通信,波特率设置为115200
2.2 定时器关键参数计算
电机控制的核心是精确的PWM信号生成。我们使用TIM2的CH1-CH3产生三路中心对齐PWM,配置步骤如下:
- 定时器时钟频率:72MHz(APB1总线)
- 目标PWM频率:25kHz(高于可听频率范围)
- 计数周期 = 时钟频率 / PWM频率 = 72MHz / 25kHz = 2880
- 采用中心对齐模式,实际ARR值 = 2880/2 -1 = 1439
- 死区时间:根据MOSFET规格,设置为500ns
- 死区时钟=72MHz,所需计数=72MHz×500ns=36
- 写入TIM2->BDTR寄存器的DTG[7:0]=36
c复制// PWM周期计算验证
#define PWM_FREQ 25000 // 25kHz
#define CPU_CLK 72000000 // 72MHz
uint32_t arr_value = (CPU_CLK / PWM_FREQ) / 2 - 1; // 结果应为1439
3. 软件架构解析
3.1 主控制流程(main.c)
主程序采用经典的前后台架构:
c复制int main(void) {
// 硬件初始化
GPIO_Config(); // 配置LED和电机使能引脚
uart_init(115200); // 串口初始化
TIM2_PWM_Init(); // PWM定时器初始化
TIM4_1ms_Init(); // 系统定时器初始化
// 电机参数配置
voltage_power_supply = 12.0; // 电源电压
voltage_limit = 2.5; // 电压限制(需根据电机调整)
pole_pairs = 7; // 极对数
// 主循环
while(1) {
if(time1_cntr >= 200) { // 200ms周期任务
LED_blink(); // 状态指示
time1_cntr = 0;
}
move(target); // 电机控制
commander_run(); // 串口指令处理
}
}
关键参数设置经验:
voltage_limit初始值建议设为电源电压的20%-30%- 小功率电机(<50W)通常1-3V即可启动
- 若电机抖动但无法启动,可逐步增加电压至5-6V
- 电机过热时应立即降低电压限制
3.2 开环控制算法实现
开环控制的核心是通过电压-时间积分估算转子位置,关键函数在BLDCMotor.c中实现:
速度开环控制
c复制float velocityOpenloop(float target_velocity) {
unsigned long now_us = SysTick->VAL;
float Ts = (now_us < open_loop_timestamp) ?
(open_loop_timestamp - now_us) :
(0xFFFFFF - now_us + open_loop_timestamp);
Ts /= 9.0; // 转换为微秒
// 角度积分:θ = θ + ω*Δt
shaft_angle = _normalizeAngle(shaft_angle + target_velocity * Ts * 1e-6);
// 施加固定电压
setPhaseVoltage(voltage_limit, 0, _electricalAngle(shaft_angle, pole_pairs));
return voltage_limit;
}
位置开环控制
c复制float angleOpenloop(float target_angle) {
float Ts = ...; // 时间间隔计算同速度模式
// 限制单步最大变化量
float step = velocity_limit * Ts * 1e-6;
if(fabs(target_angle - shaft_angle) > step) {
shaft_angle += _sign(target_angle - shaft_angle) * step;
} else {
shaft_angle = target_angle;
}
setPhaseVoltage(voltage_limit, 0, _electricalAngle(shaft_angle, pole_pairs));
return voltage_limit;
}
实测发现:开环位置控制时,
velocity_limit建议设为电机最大机械转速的1.2倍。例如电机额定300RPM(≈31.4rad/s),可设velocity_limit=35。
4. SVPWM实现细节
空间矢量脉宽调制(SVPWM)是将d/q轴电压转换为三相PWM的核心算法,其实现包含以下关键步骤:
-
电压归一化:
c复制Uout = Uq / voltage_power_supply; // 归一化到[0,1] -
扇区判断:
c复制sector = (angle_el / _PI_3) + 1; // 每60°为一个扇区 -
矢量作用时间计算:
c复制T1 = _SQRT3 * _sin(sector*_PI_3 - angle_el) * Uout; T2 = _SQRT3 * _sin(angle_el - (sector-1)*_PI_3) * Uout; T0 = 1 - T1 - T2; // 零矢量时间 -
占空比分配(以扇区1为例):
c复制Ta = T1 + T2 + T0/2; // A相占空比 Tb = T2 + T0/2; // B相占空比 Tc = T0/2; // C相占空比
我在实际调试中发现,当电源电压波动超过±10%时,会出现明显的转矩脉动。解决方法是在setPhaseVoltage()函数中加入电压补偿:
c复制// 电压补偿(需在main.c中定义battery_voltage变量)
float compensation = battery_voltage / voltage_power_supply;
Uq *= compensation;
5. 性能优化技巧
5.1 快速三角函数计算
传统库函数的sin/cos计算耗时约50-100us,我们采用查表法优化:
c复制const int sine_array[200] = {0,79,158,...}; // 预计算正弦值(放大10000倍)
float _sin(float a) {
if(a < _PI_2) { // 第一象限
return 0.0001 * sine_array[_round(126.6873 * a)];
}
// 其他象限处理...
}
优化后:
- 计算时间缩短至5-10us
- 精度损失约±0.005(对开环控制足够)
5.2 平方根近似算法
使用快速平方根倒数算法(Quake III经典算法):
c复制float _sqrtApprox(float number) {
long i;
float y = number;
i = * (long *) &y;
i = 0x5f375a86 - (i >> 1);
y = * (float *) &i;
return number * y;
}
实测在STM32F103上:
- 标准库
sqrt():约56us - 近似算法:仅12us,精度损失约2%
6. 常见问题与解决方法
6.1 电机抖动不转
现象:上电后电机发出高频噪声但转子不转动
可能原因:
- 电压限制过低(
voltage_limit) - 极对数设置错误(
pole_pairs) - PWM频率过高导致MOSFET开关损耗大
解决方案:
- 通过串口逐步增加电压限制(如"T3.0\r\n")
- 确认电机铭牌上的极对数(或通过手动旋转测量)
- 将PWM频率降至10-15kHz测试
6.2 转速不稳定
现象:设定固定速度但实际转速波动明显
可能原因:
- 电源功率不足
- 机械负载变化大
- 时间间隔计算不准确
解决方案:
- 在电源端并联大容量电解电容(如4700μF)
- 检查联轴器是否安装牢固
- 改用硬件定时器(如TIM3)精确计算Δt
6.3 串口指令无响应
现象:发送"T6.28\r\n"等指令后电机不动作
排查步骤:
- 用示波器检查USART_TX引脚是否有数据发出
- 确认串口终端设置为"回车换行(CR+LF)"模式
- 检查
USART_RX_BUF是否正常接收数据
7. 进阶开发建议
虽然本文聚焦开环控制,但系统已预留闭环控制接口:
- 电流采样:在MOSFET下管加入0.1Ω采样电阻,通过运放放大后接入ADC
- 位置反馈:连接AS5600等磁编码器到I2C接口
- PID调节:使用预置的PID模块实现闭环控制
c复制// 切换到闭环速度控制示例
controller = Type_velocity; // 改为速度闭环
PID_velocity.P = 0.2; // 设置PID参数
PID_velocity.I = 10.0;
PID_velocity.D = 0.001;
在实际项目中,我建议先通过开环控制验证硬件和基础驱动,再逐步引入闭环控制。这种渐进式开发方法能有效降低调试难度。