3. PWM初始化代码实战:MX配置、生成代码、HAL_TIM_PWM_Init与HAL_TIM_PWM_ConfigChannel
好,咱们直接进入正题。上一章我们把PWM的理论基础捋了一遍,这一章就动手实操——用STM32CubeMX配置工程,生成代码,然后看HAL库到底帮我们干了什么。
我个人习惯,拿到一个新项目,第一步永远是打开CubeMX。别急着写代码,先把外设配明白。你想想看,如果时钟树都配错了,后面调PWM频率就是白费功夫。
3.1 用CubeMX配置PWM通道
我以STM32F103C8T6为例,选一个定时器通道输出PWM。假设我们要用TIM2的通道1,对应PA0引脚。
核心配置步骤:
- 选定时器时钟源:Internal Clock(内部时钟),别选外部触发,除非你有特殊需求。
- 配通道模式:Channel1选"PWM Generation CH1",就是PWM输出模式。
- 设预分频器与自动重装值:这两个参数决定PWM频率。
- 设脉冲宽度:就是占空比,单位是定时器计数周期。
频率怎么算?公式很简单:
PWM频率 = 定时器时钟 / (预分频器 + 1) / (自动重装值 + 1)
举个例子,TIM2挂载在APB1上,时钟36MHz。我想要20kHz的PWM,算一下:
36MHz / (36-1) / (50-1) ≈ 20.4kHz
预分频器设35,自动重装值设49,脉冲宽度设25,占空比就是50%。
我的小技巧:在CubeMX的"Parameter Settings"页面,你改完预分频器和重装值,下面会实时显示更新频率。我一般先设目标频率,再微调分频系数,让实际值尽量接近目标值。
3.2 生成代码与HAL库初始化流程
配置完点"Generate Code",STM32CubeMX会帮我们生成初始化代码。打开工程,你会看到main.c里多了几行关键调用。
HAL库的PWM初始化分两步走:
- HAL_TIM_PWM_Init():底层定时器初始化,配置时基单元。
- HAL_TIM_PWM_ConfigChannel():配置PWM通道参数,比如极性、对齐方式。
我截取一段典型的生成代码,你看看:
// 定时器句柄定义
TIM_HandleTypeDef htim2;
// 时基初始化结构体
TIM_OC_InitTypeDef sConfigOC = {0};
// HAL_TIM_PWM_Init 内部调用
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim)
{
if(htim->Instance == TIM2)
{
__HAL_RCC_TIM2_CLK_ENABLE(); // 使能定时器时钟
HAL_GPIO_Init(GPIOA, GPIO_PIN_0, &GPIO_InitStruct); // 配置PA0为复用推挽输出
}
}
// 主函数中的初始化序列
HAL_TIM_PWM_Init(&htim2); // 第一步:初始化定时器时基
sConfigOC.OCMode = TIM_OCMODE_PWM1; // PWM模式1
sConfigOC.Pulse = 25; // 占空比对应值
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; // 高电平有效
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; // 快速模式关闭
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1); // 第二步:配置通道
// 启动PWM输出
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
注意:HAL_TIM_PWM_Init()会调用MspInit回调函数,这里面完成GPIO和时钟的底层配置。如果你手动改了MspInit里的内容,重新生成代码时会被覆盖。我建议把用户代码写在/* USER CODE BEGIN */和/* USER CODE END */之间。
3.3 两个核心函数的内部机制
咱们深入看看这两个函数到底干了什么。
| 函数 | 主要工作 | 关键寄存器 |
|---|---|---|
| HAL_TIM_PWM_Init | 配置时基:预分频器、自动重装值、计数模式 | TIMx_PSC, TIMx_ARR, TIMx_CR1 |
| HAL_TIM_PWM_ConfigChannel | 配置通道:输出比较模式、极性、脉冲值 | TIMx_CCMR1, TIMx_CCER, TIMx_CCR1 |
HAL_TIM_PWM_Init内部会做这几件事:
- 调用MspInit使能时钟、配GPIO
- 写PSC和ARR寄存器
- 设置计数方向(向上/向下/中心对齐)
- 配置时钟分频因子(DMA相关)
HAL_TIM_PWM_ConfigChannel内部:
- 写CCMRx寄存器,设置OCxM位(PWM1或PWM2)
- 写CCER寄存器,设置极性、使能输出
- 写CCRx寄存器,设置脉冲宽度
我曾经在项目里遇到一个问题:PWM输出波形完全不对,频率和占空比都乱了。查了半天,发现是HAL_TIM_PWM_Init和HAL_TIM_PWM_ConfigChannel的调用顺序反了。HAL库要求先Init再ConfigChannel,顺序错了寄存器配置会互相覆盖。
避坑指南:如果你在运行时动态修改占空比,直接写CCR寄存器就行,不用重新调用ConfigChannel。比如:__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, newPulse); 这样效率高得多。
3.4 知识结构图:PWM初始化流程
下面这张图把整个初始化流程串起来了,从CubeMX配置到最终输出波形,每一步都清晰可见。