TIMER1 — PWM 输出(LL 库)
HAL vs LL:PUYA 固件库提供 HAL 和 LL 两套 API。
- HAL 库:封装度高,可移植性强,适合快速开发
- LL 库:接近寄存器操作,代码精简效率高,适合性能敏感场景
本文使用 LL 库版本。
固件库: PY32F0xx_Firmware V1.5.0
芯片: PY32F030(PY32F002A / F003 同库可参考)
源文件: PUYA/PY32F0xx_Firmware_V1.5.0/Projects/PY32F030-STK/Example_LL/TIM/TIM1_PWM_Init/Src/main.c
功能简介
本示例用高级定时器 TIM1 在 PA8 / PA9 / PA10 上输出 3 路 PWM(PWM2 模式),比较值分别设为 250 / 500 / 750,对应占空比 25% / 50% / 75%。接上 LED 即可看到三档亮度,接示波器能看到三路相位相同的 PWM 波。
与 HAL 版(用 HAL_TIM_PWM_Start() 一行启动)相比,LL 版的“启动”被拆成几个显式步骤:配置 GPIO 复用 → 配置输出比较通道 LL_TIM_OC_Init() → 配时基 LL_TIM_Init() → LL_TIM_EnableAllOutputs()(高级定时器特有)→ LL_TIM_EnableCounter()。你会对“PWM 到底怎么出来的”理解得更透。
硬件准备
- PY32F030 开发板
- 可选:3 颗 LED(分别接 PA8 / PA9 / PA10,串 1kΩ 电阻到 GND),或示波器/逻辑分析仪 3 通道
- 注意:TIM1 是高级定时器,输出前要额外使能主输出(
MOE 位)
完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
#include "main.h"
#include "py32f030xx_ll_Start_Kit.h"
static void APP_SystemClockConfig(void);
static void APP_ConfigTIM1Base(void);
static void APP_ConfigPWMChannel(void);
int main(void)
{
APP_SystemClockConfig(); /* 系统时钟配到 24MHz(本例需要) */
LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_TIM1); /* 开 TIM1 时钟 */
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA); /* 开 GPIOA 时钟 */
APP_ConfigPWMChannel(); /* 先配 GPIO 复用 + 输出比较参数 */
APP_ConfigTIM1Base(); /* 再配时基并使能计数器 */
while (1) {}
}
/**
* @brief 配置 PWM 引脚复用 + 3 路输出比较
*/
static void APP_ConfigPWMChannel(void)
{
LL_GPIO_InitTypeDef TIM1CH1MapInit = {0};
LL_TIM_OC_InitTypeDef TIM_OC_Initstruct = {0};
/* PA8/PA9/PA10 复用为 TIM1_CH1/CH2/CH3,AF2 */
TIM1CH1MapInit.Pin = LL_GPIO_PIN_8 | LL_GPIO_PIN_9 | LL_GPIO_PIN_10;
TIM1CH1MapInit.Mode = LL_GPIO_MODE_ALTERNATE;
TIM1CH1MapInit.Alternate = LL_GPIO_AF_2;
TIM1CH1MapInit.Speed = LL_GPIO_SPEED_FREQ_HIGH;
TIM1CH1MapInit.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
TIM1CH1MapInit.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(GPIOA, &TIM1CH1MapInit);
/* 三路都用 PWM2 模式、输出使能、高有效 */
TIM_OC_Initstruct.OCMode = LL_TIM_OCMODE_PWM2;
TIM_OC_Initstruct.OCState = LL_TIM_OCSTATE_ENABLE;
TIM_OC_Initstruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH;
TIM_OC_Initstruct.OCIdleState = LL_TIM_OCIDLESTATE_LOW;
TIM_OC_Initstruct.CompareValue = 250; /* CH1 比较值 250 → 占空比 25% */
LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH1, &TIM_OC_Initstruct);
TIM_OC_Initstruct.CompareValue = 500; /* CH2 比较值 500 → 占空比 50% */
LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH2, &TIM_OC_Initstruct);
TIM_OC_Initstruct.CompareValue = 750; /* CH3 比较值 750 → 占空比 75% */
LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH3, &TIM_OC_Initstruct);
}
/**
* @brief 配置 TIM1 时基并使能
*/
static void APP_ConfigTIM1Base(void)
{
LL_TIM_InitTypeDef TIM1CountInit = {0};
TIM1CountInit.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; /* 不分频 */
TIM1CountInit.CounterMode = LL_TIM_COUNTERMODE_UP; /* 向上计数 */
TIM1CountInit.Prescaler = 2400 - 1; /* 预分频 2400 */
TIM1CountInit.Autoreload = 1000 - 1; /* 自动重装载 1000 */
TIM1CountInit.RepetitionCounter = 0;
LL_TIM_Init(TIM1, &TIM1CountInit);
LL_TIM_EnableAllOutputs(TIM1); /* 高级定时器:使能主输出 MOE */
LL_TIM_EnableCounter(TIM1); /* 启动计数器 */
}
/**
* @brief 系统时钟配置到 24MHz
*/
static void APP_SystemClockConfig(void)
{
LL_RCC_HSI_Enable();
LL_RCC_HSI_SetCalibFreq(LL_RCC_HSICALIBRATION_24MHz);
while (LL_RCC_HSI_IsReady() != 1) {}
LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSISYS);
while (LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSISYS) {}
LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
LL_Init1msTick(24000000);
LL_SetSystemCoreClock(24000000);
}
void APP_ErrorHandler(void)
{
while (1) {}
}
|
代码分段讲解
1. 时基与 PWM 频率
- 定时器时钟 = 系统时钟 24MHz。
Prescaler = 2400-1 → 计数时钟 = 24MHz / 2400 = 10kHz。
Autoreload = 1000-1 → 一个 PWM 周期 = 1000 个计数 = 100ms,即 PWM 频率 10Hz。
- 占空比 =
CompareValue / (Autoreload+1):250→25%、500→50%、750→75%(PWM2 模式下高/低电平关系与 PWM1 相反,但占空比数值算法相同)。
2. GPIO 复用
PA8/9/10 要设为 LL_GPIO_MODE_ALTERNATE 并指定 Alternate = LL_GPIO_AF_2 才能把引脚内部连到 TIM1。这一步和 USART 的 AF 配置思路完全一致。
3. 输出比较:LL 的“逐通道 Init”
HAL 里 HAL_TIM_PWM_ConfigChannel() 一次配一个通道;LL 里把“通道参数”封装成 LL_TIM_OC_InitTypeDef,逐个通道 LL_TIM_OC_Init()。三路只有 CompareValue 不同,其余(PWM2、使能、极性)共用一套。
4. 高级定时器特有的“主输出使能”
LL_TIM_EnableAllOutputs(TIM1) 设置的是 TIM1 的 BDTR.MOE 位。这是高级定时器(TIM1/TIM16/17)独有的:不使能主输出,PWM 引脚不会有波形。通用定时器(TIM3 等)则不需要这步——这是初学者最容易踩的坑。
5. 启动
最后 LL_TIM_EnableCounter(TIM1) 启动计数器,PWM 立即从引脚输出。LL 没有 HAL_TIM_PWM_Start() 这种“一键启动”,而是“配好 → 使能输出 → 使能计数”三步走。
实验现象
- PA8 / PA9 / PA10 输出频率约 10Hz 的 PWM,占空比分别约 25% / 50% / 75%。
- 接 LED:三颗亮度明显不同(25% 最暗,75% 最亮)。
- 示波器三路波形相位相同,仅高电平宽度不同。
常见问题
-
引脚没波形?
- 八成是漏了
LL_TIM_EnableAllOutputs()(高级定时器必须使能主输出)。
- 确认 GPIO 复用
Alternate = LL_GPIO_AF_2 且引脚确实是 PA8/9/10。
-
频率不对 / 占空比反了?
- 频率由
Prescaler × (Autoreload+1) 与定时器时钟共同决定,先确认系统时钟是 24MHz。
- PWM1 与 PWM2 模式高/低电平相反,想要“比较值越大越亮”可改用
LL_TIM_OCMODE_PWM1。
-
想动态调占空比?
- 运行中调用
LL_TIM_OC_SetCompareCHx(TIM1, 新值) 即可实时改占空比,无需重新 Init。
小结
- LL 配置 PWM 四步:开时钟 → 配 GPIO 复用 → 配 OC 通道参数 → 配时基并使能(高级定时器别忘了主输出)。
- 相比 HAL 的
HAL_TIM_PWM_Start(),LL 把“使能主输出”“使能计数器”都摊开给你,理解更深、控制更细。
- 调占空比用
LL_TIM_OC_SetCompareCHx(),调频率改 Prescaler / Autoreload。
拓展应用
- 在定时器更新中断里逐步改变
CompareValue,做“呼吸灯”。
- 多路 PWM 组合驱动 RGB LED 调色。
- 用 PWM 触发 ADC(
LL_TIM_SetTriggerOutput)实现同步采样。
- 改用 TIM3(通用定时器)重复本例,体会“无需主输出使能”的差异。