PUYA TIM — TIM1 四路 PWM 输出 TIM1_PWM

PUYA PY32F403_Firmware V1.4.5 TIM1 PWM 示例教学

TIM — TIM1 四路 PWM 输出 TIM1_PWM

固件库: PY32F403_Firmware V1.4.5
芯片: PY32F403(Cortex-M4)
源文件: PY32F403-STK/Example/TIM/TIM1_PWM/Src/main.c
核心外设: TIM1(高级定时器,4 路 PWM,APB2)


1. 功能简介(大白话)

本例用 TIM1(高级定时器) 同时输出 4 路 PWM 波形,分别接在 PA8~PA11 四个引脚上:

通道 引脚 CCR 比较值 占空比
CH1 PA8 10 20%
CH2 PA9 20 40%
CH3 PA10 30 60%
CH4 PA11 40 80%

波形的周期固定(约 196Hz),只是每路的「高电平占比」不同。用逻辑分析仪或示波器看这四个引脚,就能直观看到 PWM 占空比的差别——这也是调光、调速最基础的玩法。

2. 硬件准备

物品 说明
PY32F403-STK 开发板 自带 TIM1 通道引脚
逻辑分析仪 / 示波器 看 PA8~PA11 波形
(可选)LED + 限流电阻 接某通道看亮度差异(或 4 颗 LED)

测量点:PA8(TIM1_CH1)、PA9(TIM1_CH2)、PA10(TIM1_CH3)、PA11(TIM1_CH4)。

若只想「肉眼看效果」,可把 4 个通道分别接 LED,占空比越高灯越亮(注意串限流电阻,或直接用 STK 上已接好的 LED 引脚——但 STK 的 LED 在 PA1,需飞线或改引脚)。

3. 完整代码(带中文注释)

 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
#include "main.h"

/* 周期与占空比相关宏(ARR=50,比较值 10/20/30/40)*/
#define PERIOD_VALUE   (uint32_t)(50 - 1)   /* 即 ARR=50 */
#define PULSE1_VALUE   10                    /* CH1 比较值 → 占空比 10/50=20% */
#define PULSE2_VALUE   20                    /* CH2 → 40% */
#define PULSE3_VALUE   30                    /* CH3 → 60% */
#define PULSE4_VALUE   40                    /* CH4 → 80% */

TIM_HandleTypeDef    TimHandle;
TIM_OC_InitTypeDef   sConfig;

int main(void)
{
  /* 1) HAL 初始化 */
  HAL_Init();
  /* 2) 系统时钟:HSI 8MHz;APB2 分频 2 → 4MHz;定时器时钟 = APB2×2 = 8MHz */
  APP_SystemClockConfig();

  /* 3) TIM1 时基配置 */
  TimHandle.Instance = TIM1;
  TimHandle.Init.Period            = 50;        /* ARR 自动重装载值 */
  TimHandle.Init.Prescaler         = 800 - 1;   /* 预分频 800(时钟先除 800)*/
  TimHandle.Init.ClockDivision     = TIM_CLOCKDIVISION_DIV1;
  TimHandle.Init.CounterMode       = TIM_COUNTERMODE_UP;   /* 向上计数 */
  TimHandle.Init.RepetitionCounter = 1 - 1;     /* 重复计数 0(高级定时器特有)*/
  TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_PWM_Init(&TimHandle) != HAL_OK) APP_ErrorHandler(); /* 内部调 MSP 配 PA8~PA11 */

  /* 4) 输出比较(PWM)公共参数 */
  sConfig.OCMode       = TIM_OCMODE_PWM1;     /* PWM 模式1 */
  sConfig.OCPolarity   = TIM_OCPOLARITY_HIGH;  /* 有效电平为高 */
  sConfig.OCFastMode   = TIM_OCFAST_DISABLE;
  sConfig.OCNPolarity  = TIM_OCNPOLARITY_HIGH; /* 互补通道极性(N 通道,本例未用)*/
  sConfig.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  sConfig.OCIdleState  = TIM_OCIDLESTATE_RESET;

  /* 5) 逐通道设置比较值(占空比)*/
  sConfig.Pulse = PULSE1_VALUE;   /* CCR1 = 10 */
  if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, TIM_CHANNEL_1) != HAL_OK) APP_ErrorHandler();
  sConfig.Pulse = PULSE2_VALUE;   /* CCR2 = 20 */
  if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, TIM_CHANNEL_2) != HAL_OK) APP_ErrorHandler();
  sConfig.Pulse = PULSE3_VALUE;   /* CCR3 = 30 */
  if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, TIM_CHANNEL_3) != HAL_OK) APP_ErrorHandler();
  sConfig.Pulse = PULSE4_VALUE;   /* CCR4 = 40 */
  if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, TIM_CHANNEL_4) != HAL_OK) APP_ErrorHandler();

  /* 6) 启动 4 路 PWM(引脚开始持续输出波形)*/
  HAL_TIM_PWM_Start(&TimHandle, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&TimHandle, TIM_CHANNEL_2);
  HAL_TIM_PWM_Start(&TimHandle, TIM_CHANNEL_3);
  HAL_TIM_PWM_Start(&TimHandle, TIM_CHANNEL_4);

  while (1) { }   /* 此后硬件自动翻转,CPU 不用管 */
}

/**
  * @brief  系统时钟:HSI 8MHz;APB2 二分频
  *         注意:APB2 分频≠1,故 TIM1 时钟 = APB2×2 = 8MHz
  */
static void APP_SystemClockConfig(void)
{
  RCC_OscInitTypeDef  OscInitstruct = {0};
  RCC_ClkInitTypeDef  ClkInitstruct = {0};

  OscInitstruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  OscInitstruct.HSIState       = RCC_HSI_ON;
  OscInitstruct.HSI48MState    = RCC_HSI48M_OFF;
  OscInitstruct.LSEState       = RCC_LSE_OFF;
  OscInitstruct.LSIState       = RCC_LSI_OFF;
  OscInitstruct.PLL.PLLState   = RCC_PLL_OFF;
  if (HAL_RCC_OscConfig(&OscInitstruct) != HAL_OK) APP_ErrorHandler();

  ClkInitstruct.ClockType      = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
                                 RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  ClkInitstruct.SYSCLKSource   = RCC_SYSCLKSOURCE_HSI;  /* 8MHz */
  ClkInitstruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;       /* HCLK = 8MHz */
  ClkInitstruct.APB1CLKDivider = RCC_HCLK_DIV1;         /* APB1 = 8MHz */
  ClkInitstruct.APB2CLKDivider = RCC_HCLK_DIV2;         /* APB2 = 4MHz */
  if (HAL_RCC_ClockConfig(&ClkInitstruct, FLASH_LATENCY_0) != HAL_OK) APP_ErrorHandler();
}

void APP_ErrorHandler(void) { while (1) {} }

附:MSP 里的引脚配置(HAL_TIM_PWM_Init 自动调用)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  __HAL_RCC_TIM1_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;     /* 复用推挽 */
  GPIO_InitStruct.Pull      = GPIO_PULLUP;
  GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF4_TIM1;       /* PA8~PA11 复用为 TIM1 */

  GPIO_InitStruct.Pin = GPIO_PIN_8;  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  GPIO_InitStruct.Pin = GPIO_PIN_9;  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  GPIO_InitStruct.Pin = GPIO_PIN_10; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  GPIO_InitStruct.Pin = GPIO_PIN_11; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

4. 代码分段讲解

4.1 频率是怎么算出来的?

1
2
3
4
定时器时钟 = 8 MHz          (APB2=4MHz,因分频≠1 故 ×2)
计数时钟   = 8M / (PSC+1) = 8M / 800 = 10 kHz      (每 100µs 计一个数)
溢出周期   = (ARR+1) / 计数时钟 = 51 / 10kHz ≈ 5.1 ms
PWM 频率   = 1 / 5.1ms ≈ 196 Hz

频率 = 定时器时钟 / (PSC+1) / (ARR+1) = 8000000 / 800 / 51 ≈ 196Hz(与 readme 一致)。

想改频率:改 Period(ARR)或 Prescaler(PSC)。想改占空比:改各通道 Pulse(CCR)。

4.2 占空比怎么定?

PWM1 模式下,计数器 0→ARR 循环;当 Count < CCR 时输出有效(高),否则无效(低)。所以:

1
占空比 = CCR / (ARR + 1) = 10 / 51 ≈ 19.6%  (代码取 50 作分母近似 20%)

4 个通道 CCR 分别为 10/20/30/40,对应占空比 20%/40%/60%/80%。

4.3 为什么 TIM1 是「高级定时器」?

TIM1 比通用定时器多了**互补输出(CHxN)、死区插入、刹车(Break)**等能力,专为电机/电源半桥驱动设计。本例只用其普通 PWM 通道,没动用互补/死区;想做无刷电机、逆变器可参考 TIM1_ComplementarySignals_DeadTimeTIM1_6Step

4.4 启动后 CPU 干嘛去了?

HAL_TIM_PWM_Start 之后,波形由硬件定时器自动生成,主循环空转即可。这正是 PWM 相比「软件延时翻转 IO」的优势:不占 CPU、精度高、多路完全同步。

5. 实验现象

  • 用示波器/逻辑分析仪看 PA8~PA11:
    • 四路频率相同(约 196Hz)
    • 占空比依次为 20% / 40% / 60% / 80%
    • 同频不同占空,直观对比明显。
  • 若接 LED,低占空比暗、高占空比亮。

6. 常见问题

现象 原因 解决
示波器看不到波形 引脚不对 / 没 Start 确认 PA8~PA11,且 4 路都调了 Start
频率算出来是 98Hz 忘了「APB 分频≠1 时定时器×2」 TIM1 时钟其实是 8MHz,不是 4MHz
占空比反了(高变低) 极性设反 OCPolarity 改成 TIM_OCPOLARITY_LOW
改了 ARR 没生效 开了自动预装载却没触发更新 TIM_AUTORELOAD_PRELOAD_ENABLE 时需等更新事件
想输出更高频 ARR/PSC 太大 减小 PSC 和 ARR;提高系统时钟
CH1N/CH2N 互补脚没动 没配 N 通道/没开死区 参考互补输出示例

7. 小结

  • PWM = ARR 定周期 + CCR 定占空比 + 定时器硬件自动翻转
  • 本例 4 路共用同一时基(同频),仅 CCR 不同(不同占空比);
  • TIM1 时钟 = APB2 × 2 = 8MHz(APB2 分频≠1 的关键规则);
  • 频率 ≈ 196Hz,占空比 20/40/60/80%;
  • 启动后波形全自动,CPU 不参与,多路精准同步。

8. 拓展应用(学了能做什么项目)

  • LED 呼吸灯 / 调光:让 CCR 缓慢变化做出渐变效果;
  • 舵机控制:50Hz、脉宽 12ms 对应 0180°(改 ARR/PSC 得 50Hz,调 CCR 给脉宽);
  • 直流电机调速:PWM 接 MOS 管/驱动芯片,占空比控转速;
  • 蜂鸣器奏乐:不同频率 PWM 对应不同音高;
  • 无刷电机/电源:参考 TIM1_ComplementarySignals_DeadTimeTIM1_6Step 用互补 PWM + 死区;
  • 测频/测速:参考 TIM1_InputCapture_*TIM1_EncoderTI2AndTI1,用输入捕获或编码器接口。

返回:定时器 TIM 总览

世界是你们
使用 Hugo 构建
主题 StackJimmy 设计