PUYA TIMER1 — PWM 输出(LL 库)

PUYA PY32F0xx_Firmware V1.5.0 TIMER1 PWM 输出(TIM1_PWM_Init)示例教学(LL 库版本)

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% 最亮)。
  • 示波器三路波形相位相同,仅高电平宽度不同。

常见问题

  1. 引脚没波形?

    • 八成是漏了 LL_TIM_EnableAllOutputs()(高级定时器必须使能主输出)。
    • 确认 GPIO 复用 Alternate = LL_GPIO_AF_2 且引脚确实是 PA8/9/10。
  2. 频率不对 / 占空比反了?

    • 频率由 Prescaler × (Autoreload+1) 与定时器时钟共同决定,先确认系统时钟是 24MHz。
    • PWM1 与 PWM2 模式高/低电平相反,想要“比较值越大越亮”可改用 LL_TIM_OCMODE_PWM1
  3. 想动态调占空比?

    • 运行中调用 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(通用定时器)重复本例,体会“无需主输出使能”的差异。
世界是你们
使用 Hugo 构建
主题 StackJimmy 设计