3. STM32生态系统:选型、CubeMX与HAL库、定时器与PWM输出
做FOC控制,说白了就是跟STM32的定时器、ADC、PWM这些外设打交道。我见过不少新手,一上来就对着数据手册硬啃,结果越看越懵。其实不用这样,咱们先把生态摸清楚,后面写代码就顺了。
3.1 STM32系列选型:FOC控制该用哪颗芯片?
STM32家族很大,但不是所有型号都适合做FOC。我个人习惯从三个维度来筛选:算力、定时器资源、ADC性能。
| 需求维度 | 最低要求 | 推荐型号举例 | 说明 |
|---|---|---|---|
| 算力 | 72MHz Cortex-M3以上 | STM32F103, F302, F4系列 | FOC电流环需要10-20kHz,算力不够会卡顿 |
| 定时器 | 至少1个高级定时器 | STM32F302, F4, G4系列 | 高级定时器才有互补PWM和刹车功能 |
| ADC | 双通道同步采样 | STM32F302, G431 | 同时采集两相电流,减少相位误差 |
| 成本敏感 | 够用就好 | STM32G0, G4系列 | G4自带Cordic硬件加速,算FOC很香 |
我的建议:如果刚入门,直接选STM32F302C8T6或STM32G431CBT6。这两颗芯片在FOC圈子里是“标配”,网上资料多,踩坑了也好找人问。
为什么会这样推荐?F302有双ADC,可以同时采样两相电流;G431更狠,内置了Cordic协处理器,算三角函数比纯软件快好几倍。我在项目里用G431做过一个无人机电调,电流环跑到20kHz,CPU负载才30%出头。
3.2 CubeMX与HAL库:从图形化配置到代码生成
以前做STM32开发,得手动翻寄存器手册,一行一行写初始化代码。现在有了CubeMX,这事儿就简单多了。你想想看,点几下鼠标,时钟树、引脚分配、外设参数就全配好了。
3.2.1 CubeMX的核心操作流程
- 选芯片:输入型号,比如STM32F302C8,双击打开。
- 配时钟:在Clock Configuration页面,把HCLK拉到72MHz(或芯片最高主频)。
- 使能外设:在Pinout页面,勾选TIM1、TIM2、ADC1、ADC2等。
- 设参数:比如TIM1的PWM频率设为20kHz,死区时间设500ns。
- 生成代码:选好IDE(MDK或IAR),点Generate Code。
小技巧:生成代码后,不要手动修改CubeMX生成的.ioc文件以外的初始化代码。如果你改了,下次重新生成时会被覆盖。我习惯把用户代码写在/* USER CODE BEGIN */和/* USER CODE END */之间,这样CubeMX会保留它们。
3.2.2 HAL库的层次结构
HAL库把寄存器操作封装成了函数。比如你想让定时器开始计数,直接调HAL_TIM_PWM_Start()就行。底层怎么配寄存器,你不用管。
但这里有个坑——HAL库的实时性不够好。为什么?因为它里面有很多错误检查和超时等待。在FOC电流环里,你需要在几微秒内完成ADC读取和PWM更新,HAL库的延迟可能会让你错过控制周期。
我曾经踩过的坑:第一次做FOC时,我用HAL库的HAL_ADC_Start_DMA()来触发ADC转换。结果发现电流环频率一超过10kHz,DMA回调就丢数据。后来查了手册才知道,HAL库在中断里做了太多状态检查。解决方案是:在电流环中断里直接操作寄存器,比如用TIM1->CCR1 = duty;来更新占空比,而不是调HAL函数。
3.3 定时器与PWM输出:FOC的“执行器”
FOC控制最终要输出6路PWM信号给逆变器。这6路PWM必须成对互补,还要带死区时间。STM32的高级定时器(TIM1、TIM8)就是干这个的。
3.3.1 高级定时器的核心功能
- 互补PWM输出:CH1和CH1N自动互补,你只需设一个占空比。
- 死区插入:防止上下桥臂直通,死区时间可编程(通常100ns-1μs)。
- 刹车功能:一旦检测到过流信号,硬件自动封锁PWM输出。
- 重复计数:可以设置每N次溢出才触发一次中断,减少CPU负担。
3.3.2 PWM频率与死区时间的计算
PWM频率一般设在10kHz-20kHz。频率太低,电机噪音大;频率太高,开关损耗大。我一般取16kHz,人耳听不到,效率也还行。
死区时间怎么算?公式很简单:
死区时间 = 死区寄存器值 × T_clk
其中T_clk是定时器的时钟周期。比如定时器时钟72MHz,T_clk≈13.9ns。如果你想设500ns死区,寄存器值≈500/13.9≈36。
关键参数速查表(定时器时钟72MHz):
| PWM频率 | 自动重装值(ARR) | 死区时间 | 死区寄存器值 |
|---|---|---|---|
| 10kHz | 7200-1 | 500ns | 36 |
| 16kHz | 4500-1 | 300ns | 22 |
| 20kHz | 3600-1 | 200ns | 14 |
3.3.3 代码示例:用HAL库配置TIM1输出6路互补PWM
// 在CubeMX中配置好TIM1后,生成的初始化代码大致如下:
// 注意:这里只展示核心配置,实际代码由CubeMX生成
TIM_HandleTypeDef htim1;
void MX_TIM1_Init(void)
{
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0; // 不分频,72MHz
htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1; // 中央对齐模式
htim1.Init.Period = 4500 - 1; // 16kHz PWM
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_PWM_Init(&htim1);
// 配置通道1(U相上桥)
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0; // 初始占空比0
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
// 配置死区时间
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_ENABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 36; // 500ns死区
sBreakDeadTimeConfig.BreakState = TIM_BREAK_ENABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.BreakFilter = 0;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE;
HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);
// 启动PWM输出
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); // 启动互补通道
// 通道2和通道3同理...
}
注意:高级定时器有个“坑”——你必须先调用HAL_TIMEx_ConfigBreakDeadTime()配置刹车和死区,然后再启动PWM。顺序反了,PWM可能不输出。我刚开始就因为这个折腾了半天。
3.4 本章知识结构图
下面这张图把STM32生态、CubeMX配置、定时器PWM这三块串起来了。你可以把它当作一个“路线图”,后面写代码时随时回来对照。
3.5 本章小结
嗯,这一章内容不少,但都是后面实战的基础。总结几个关键点:
- 选型:F302或G431,别纠结,直接上。
- CubeMX:用它生成初始代码,但电流环里别依赖HAL库。
- 定时器:高级定时器的互补PWM、死区、刹车,这三个功能必须吃透。