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_DeadTime、TIM1_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、脉宽 1
2ms 对应 0180°(改 ARR/PSC 得 50Hz,调 CCR 给脉宽);
- 直流电机调速:PWM 接 MOS 管/驱动芯片,占空比控转速;
- 蜂鸣器奏乐:不同频率 PWM 对应不同音高;
- 无刷电机/电源:参考
TIM1_ComplementarySignals_DeadTime、TIM1_6Step 用互补 PWM + 死区;
- 测频/测速:参考
TIM1_InputCapture_* 与 TIM1_EncoderTI2AndTI1,用输入捕获或编码器接口。
返回:定时器 TIM 总览