4. STM32CubeMX配置PWM:新建工程、选择芯片、配置时钟与定时器
说实话,很多初学者拿到STM32,第一件事就是点灯。但点灯只是入门,真正让单片机“动起来”的,往往是PWM。我带的项目中,从舵机控制到电机调速,从LED呼吸灯到DC-DC电源,PWM几乎无处不在。这一章,我们就手把手把PWM的配置流程走一遍。
我会用STM32CubeMX这个工具。它最大的好处是——你不用手写那些繁琐的时钟配置代码。你想想看,以前用标准库的时候,光一个RCC配置就要翻半天手册。现在?图形化点几下,代码就生成了。
4.1 新建工程与芯片选型
打开CubeMX,点击“New Project”。
芯片选型这块,我个人习惯直接搜型号。比如我们这次用的STM32F103C8T6,在搜索框输入“STM32F103C8”就能找到。注意选对封装——LQFP48,别选成LQFP64,管脚数不一样。
选好后,双击芯片型号,工程就创建好了。这时候你会看到一个空白芯片引脚图,所有引脚都是灰色的——还没配置呢。
4.2 配置时钟树(HSE/LSE/PLL)
时钟是单片机的“心跳”。心跳乱了,啥都跑不对。我见过不少新手,PWM频率死活调不对,最后发现是时钟树配错了。
切换到“Clock Configuration”选项卡。这里你会看到一张时钟树图。
我们的目标是:让系统时钟跑到72MHz(STM32F103的最高主频)。
- 选择HSE(高速外部时钟): 一般板子上有个8MHz晶振,接到OSC_IN/OSC_OUT引脚。在HSE输入框里填8。
- 配置PLL: PLL就是把低频倍频到高频。公式是:PLL输出 = HSE频率 × PLL倍频系数。我们要72MHz,所以倍频系数设9(8×9=72)。
- 选择系统时钟源: 在“System Clock Mux”下拉菜单里选PLLCLK。这样系统时钟就来自PLL输出。
- APB1/APB2分频: APB2(高速总线)我一般不分频,保持72MHz。APB1(低速总线)最大只能36MHz,所以设2分频。这个后面配定时器时会用到。
配置完的效果,大概是这样:
HSE (8MHz) → PLL (x9) → SYSCLK (72MHz)
→ AHB (72MHz)
→ APB2 (72MHz) → TIM1, TIM8等
→ APB1 (36MHz) → TIM2, TIM3, TIM4等
4.3 配置定时器为PWM模式
时钟搞定,接下来配定时器。我们以TIM3为例,用它输出两路PWM,分别控制舵机和LED呼吸灯。
回到“Pinout & Configuration”选项卡,在左侧“Timers”里找到TIM3。
- 选择时钟源: 在“Clock Source”里勾选“Internal Clock”。这样TIM3的时钟来自APB1总线(36MHz)。
- 配置通道: TIM3有4个通道。我们只用CH1和CH2。在“Channel1”下拉菜单选“PWM Generation CH1”,CH2也一样。
- 设置参数: 切换到“Parameter Settings”选项卡。
- Prescaler(预分频器): 设为71。这样定时器时钟变成 36MHz / (71+1) = 500kHz。
- Counter Mode(计数模式): 选“Up”。向上计数,最常用。
- Counter Period(自动重装值): 设为19999。这样PWM频率 = 500kHz / (19999+1) = 25Hz。
- Auto-reload preload: 建议Enable。这样更新影子寄存器,避免PWM波形抖动。
- 设置PWM参数: 在下面“PWM Generation Channel1/2”里:
- Mode: 选“PWM mode 1”。意思是:计数小于比较值时输出有效电平,大于时无效。
- Pulse(比较值): 先填1500。这是舵机中位对应的脉宽(1.5ms)。
- Output compare preload: Enable。
- Fast Mode: 不勾。普通模式够用。
频率计算小公式:
PWM频率 = 定时器时钟 / (Prescaler+1) / (Counter Period+1)
我们这里:36,000,000 / 72 / 20000 = 25Hz
舵机控制一般用50Hz(20ms周期),所以把Counter Period改成19999就对了?等等,我算一下——500kHz / 50Hz = 10000,所以Counter Period应该设9999。嗯,上面那个25Hz是举例,实际舵机用50Hz。
4.4 生成初始化代码
所有配置完成后,点击右上角的“GENERATE CODE”。
在弹出的对话框里,注意几个选项:
- Project Name: 取个有意义的名字,比如“PWM_Servo_Control”。
- Project Location: 选个你找得到的路径。
- Toolchain / IDE: 我用MDK-ARM(Keil),你就按自己的习惯选。
- Firmware Package: 选你之前下载好的版本。我建议用1.8.0或更新版,bug少一些。
点“Generate”,等几秒钟。CubeMX会帮你生成一个完整的Keil工程,里面已经包含了时钟初始化、GPIO配置、定时器PWM初始化代码。
4.5 知识结构总览
下面这张图,把整个配置流程串起来了。你可以把它当成一个检查清单:
4.6 生成后的代码结构
生成完成后,打开工程,你会看到这样的文件结构:
| 文件/文件夹 | 说明 |
|---|---|
| Core/Src/main.c | 主函数,包含MX_TIM3_Init()调用 |
| Core/Src/tim.c | 定时器初始化函数实现 |
| Core/Inc/tim.h | 定时器相关头文件 |
| Core/Src/gpio.c | GPIO初始化(PWM引脚自动配置为复用推挽输出) |
| Drivers/STM32F1xx_HAL_Driver | HAL库驱动文件 |
打开tim.c,找到MX_TIM3_Init()函数。你会看到类似这样的代码:
void MX_TIM3_Init(void)
{
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 19999;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_PWM_Init(&htim3);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 1500;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2);
}
看到没?所有参数都自动填好了。你只需要在main()里加一句:
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
PWM就输出了。就这么简单。