4. STM32CubeMX配置PWM:新建工程、选择芯片、配置时钟与定时器

说实话,很多初学者拿到STM32,第一件事就是点灯。但点灯只是入门,真正让单片机“动起来”的,往往是PWM。我带的项目中,从舵机控制到电机调速,从LED呼吸灯到DC-DC电源,PWM几乎无处不在。这一章,我们就手把手把PWM的配置流程走一遍。

我会用STM32CubeMX这个工具。它最大的好处是——你不用手写那些繁琐的时钟配置代码。你想想看,以前用标准库的时候,光一个RCC配置就要翻半天手册。现在?图形化点几下,代码就生成了。

4.1 新建工程与芯片选型

打开CubeMX,点击“New Project”。

芯片选型这块,我个人习惯直接搜型号。比如我们这次用的STM32F103C8T6,在搜索框输入“STM32F103C8”就能找到。注意选对封装——LQFP48,别选成LQFP64,管脚数不一样。

我的小习惯: 选完芯片后,我会先看一眼Pinout视图。如果芯片引脚布局和你的板子对不上,赶紧换型号。我曾经在一个项目里选错了封装,板子都画好了才发现,那叫一个酸爽。

选好后,双击芯片型号,工程就创建好了。这时候你会看到一个空白芯片引脚图,所有引脚都是灰色的——还没配置呢。

4.2 配置时钟树(HSE/LSE/PLL)

时钟是单片机的“心跳”。心跳乱了,啥都跑不对。我见过不少新手,PWM频率死活调不对,最后发现是时钟树配错了。

切换到“Clock Configuration”选项卡。这里你会看到一张时钟树图。

我们的目标是:让系统时钟跑到72MHz(STM32F103的最高主频)。

  1. 选择HSE(高速外部时钟): 一般板子上有个8MHz晶振,接到OSC_IN/OSC_OUT引脚。在HSE输入框里填8。
  2. 配置PLL: PLL就是把低频倍频到高频。公式是:PLL输出 = HSE频率 × PLL倍频系数。我们要72MHz,所以倍频系数设9(8×9=72)。
  3. 选择系统时钟源: 在“System Clock Mux”下拉菜单里选PLLCLK。这样系统时钟就来自PLL输出。
  4. APB1/APB2分频: APB2(高速总线)我一般不分频,保持72MHz。APB1(低速总线)最大只能36MHz,所以设2分频。这个后面配定时器时会用到。
注意: 时钟树配置完,如果看到红色数字,说明某个时钟超限了。比如APB1如果设成72MHz,它会变红——因为APB1最高只能36MHz。这时候调一下分频系数就行。

配置完的效果,大概是这样:

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。

  1. 选择时钟源: 在“Clock Source”里勾选“Internal Clock”。这样TIM3的时钟来自APB1总线(36MHz)。
  2. 配置通道: TIM3有4个通道。我们只用CH1和CH2。在“Channel1”下拉菜单选“PWM Generation CH1”,CH2也一样。
  3. 设置参数: 切换到“Parameter Settings”选项卡。
    • Prescaler(预分频器): 设为71。这样定时器时钟变成 36MHz / (71+1) = 500kHz。
    • Counter Mode(计数模式): 选“Up”。向上计数,最常用。
    • Counter Period(自动重装值): 设为19999。这样PWM频率 = 500kHz / (19999+1) = 25Hz。
    • Auto-reload preload: 建议Enable。这样更新影子寄存器,避免PWM波形抖动。
  4. 设置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初始化代码。

避坑指南: 生成代码后,别急着编译。先打开main.c,找到MX_TIM3_Init()函数,看一眼里面的参数是不是和你配的一致。我曾经遇到过CubeMX版本bug,Prescaler值被自动改了,害我查了半天。

4.5 知识结构总览

下面这张图,把整个配置流程串起来了。你可以把它当成一个检查清单:

STM32CubeMX PWM配置流程 STM32CubeMX PWM配置流程 1. 新建工程 选择芯片型号 2. 配置时钟树 HSE → PLL → 72MHz 3. 配置定时器 TIM3 → PWM模式 4. 生成 时钟树关键参数 • HSE: 8MHz • PLL倍频: x9 • SYSCLK: 72MHz • APB1: 36MHz (2分频) 定时器PWM参数 • Prescaler: 71 • Counter Period: 19999 • PWM频率: 25Hz • 舵机用50Hz需改Period 引脚分配 • TIM3_CH1: PA6 • TIM3_CH2: PA7 • 舵机信号线 → PA6 • LED → PA7 (呼吸灯) 生成代码后,在main.c中调用 MX_TIM3_Init() 即可启动PWM输出

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就输出了。就这么简单。

重要提醒: 别忘了在main.c里把HAL_Init()和SystemClock_Config()加上。CubeMX生成的代码里默认就有,但如果你自己新建文件,容易漏掉。没有时钟初始化,定时器不会工作。