TIMER — TIMER1 PWM 输出
固件库: GD32F30x_Firmware_Library V3.0.3
芯片: GD32F30x(以 GD32F307 评估板为例)
源文件:GD32F30x_Firmware_Library_V3.0.3/Examples/TIMER/TIMER1_pwmout/main.c
功能简介
本示例用 TIMER1 的 3 个通道(CH0/CH1/CH2)同时输出 3 路 PWM(脉冲宽度调制) 波形,每路的频率相同、占空比不同:
- CH0(PA0):占空比 25%
- CH1(PA1):占空比 50%
- CH2(PA2):占空比 75%
PWM 是嵌入式里最常用、也最好玩的功能之一——LED 呼吸灯、电机调速、舵机转角、蜂鸣器音调,本质上都是"调占空比 / 调频率"。本例不依赖任何中断,配置好定时器后它就在后台自动、持续地翻转引脚,CPU 可以干别的(这里只是 while(1) 空转)。
PWM 是什么直观感受? 想象一个电灯以很快的频率(比如 60Hz 以上,人眼看不见闪烁)在"亮—灭—亮—灭"。亮的时间占比就是"占空比"。占空比 25% 看起来暗,75% 看起来亮——这就是 LED 调光的原理。
硬件准备
1. 开发板
- GD32F30x 评估板(本例无 LED/串口,纯定时器输出)。
2. 观察 PWM 的两种方式
- 示波器 / 逻辑分析仪:直接测量 PA0 / PA1 / PA2 三路波形,最直观。
- 用 LED + 限流电阻:把 PA0~PA2 各串一个 220Ω 电阻接 LED 到地。占空比 25% 的灯明显比 75% 的暗。注意 TIMER1 是 3.3V 电平,LED 别直接接大电流。
- 万用表测电压:直流档测 PA0,读数 ≈ 3.3V × 占空比(25% → 约 0.83V,50% → 约 1.65V,75% → 约 2.48V)。这也是"PWM 当 DAC 用"的思路。
3. 引脚
| 通道 | 引脚 | 复用功能 |
|---|---|---|
| CH0 | PA0 | TIMER1_CH0 |
| CH1 | PA1 | TIMER1_CH1 |
| CH2 | PA2 | TIMER1_CH2 |
完整代码(带中文注释)
|
|
代码分段讲解
1. GPIO:PWM 引脚是"复用功能"
|
|
PWM 波形不是软件一位位翻转出来的,而是定时器硬件直接驱动引脚产生的。所以引脚必须配成 GPIO_MODE_AF_PP(复用推挽),并且必须开 RCU_AF 时钟。如果忘了开 AF,引脚不会输出 PWM。
2. 定时器的"三件套":PSC、ARR、CCR
PWM 的两个关键量——频率和占空比——由这三者决定:
| 参数 | 代码 | 含义 | 决定什么 |
|---|---|---|---|
| 预分频 PSC | prescaler = 119 |
计数时钟 = 120MHz/120 = 1MHz | 时间基准 |
| 重装载 ARR | period = 15999 |
一个周期计 16000 次 → 16ms | 频率(约 62.5Hz) |
| 比较值 CCR | pulse = 3999/7999/11999 |
计到该值时翻转 | 占空比 |
占空比公式(PWM 模式 0、高极性):
|
|
(代码注释里写 4000/16000 是取整近似,实际 CCR=3999,细节不重要,理解"比例"即可。)
为什么 TIMER1CLK 要除以 120 而不是直接用 120MHz? 直接用 120MHz 计数,16000 次只有 133 微秒,频率高达 7.5kHz,对 LED 调光没问题,但本例想演示"慢一点、好测"。分频到 1MHz 后频率约 62.5Hz,既方便示波器观察,也接近 LED 调光的常用频段。想得到别的频率,改 PSC 或 ARR 即可。
3. 通道输出配置:使能 + 极性
|
|
outputstate = ENABLE:告诉定时器"这个通道要往引脚上输出波形"。ocpolarity = HIGH:把"有效电平"定义成高电平。配合 PWM0 模式,效果是"计数 < CCR 时输出低,≥ CCR 时输出高"——也就是高电平占 CCR/ARR,正弦地,占空比 = CCR/ARR。- 互补通道(
outputnstate/ocnpolarity)只在高级定时器 TIMER0 驱动半桥电机时才用,本例关掉。
4. 每通道:比较值 + 模式 + 影子
|
|
三行缺一不可:
- 设比较值:决定占空比的核心数字。
- 设模式为 PWM0:让通道进入"比较值到就翻转"的 PWM 行为。
- 影子寄存器:这里
DISABLE表示 CCR 立即生效。若开启影子(ENABLE),修改 CCR 会在下次更新事件才生效,可避免中途改值导致波形断裂。本例静态输出,关不关都行。
5. 启动:影子 ARR + 使能
|
|
timer_auto_reload_shadow_enable:让 ARR 也在"更新事件"时才更新,避免在计数中途改周期导致波形毛刺(本例虽不改 ARR,但这是好习惯)。timer_enable才是真正的"发令枪"——调用之后,硬件计数器开始跑,引脚上立刻出现 PWM 波形。配置再多,不enable就没输出。
实验现象
- 下载程序并复位后,PA0 / PA1 / PA2 立即开始输出 PWM(无需任何触发)。
- 用示波器看:
- 三路频率相同(约 62.5Hz,周期约 16ms)。
- 占空比分别为 25% / 50% / 75%,高电平宽度依次翻倍。
- 若用 LED 接这三路:亮度明显呈 暗 → 中 → 亮 三档。
- 若用万用表直流档:电压约为 0.83V / 1.65V / 2.48V(3.3V × 占空比)。
常见问题
Q1:下载后引脚没有任何波形?
按顺序排查:① 是否 timer_enable(TIMER1) 被调用(没启动当然没输出);② 引脚是否配成 GPIO_MODE_AF_PP 且开了 RCU_AF;③ 是否开了 RCU_TIMER1 时钟;④ 通道 outputstate 是否为 TIMER_CCX_ENABLE。
Q2:频率/占空比和预期不符?
先确认系统时钟:GD32F30x 评估板通常是 120MHz,例程按此算 1MHz 计数时钟。若你的板子系统时钟不同(如 108MHz),TIMER1CLK = SystemCoreClock / 120 的分母就要相应改。频率 = 1MHz / (ARR+1),占空比 = CCR/(ARR+1),用公式反推即可。
Q3:想改频率怎么办?
频率 = 计数时钟 / (ARR+1)。改 period(ARR)最直接;要同时保住占空比比例,就按同比例改三路的 pulse 值。注意 ARR 越大频率越低。
Q4:想调占空比怎么办?
改对应通道的 timer_channel_output_pulse_value_config(..., CCR)。CCR 越大占空比越高。运行时也可以反复调用这个函数动态调(比如做呼吸灯)。
Q5:PWM 模式 0 和模式 1 有什么区别?
PWM0:计数 < CCR 输出无效(低),≥ CCR 输出有效(高);PWM1 反过来。再叠加 ocpolarity 的极性翻转,最终波形可上可下。本例用 PWM0+高极性,结果是"高电平占比 = CCR/ARR"。记不住就实际用示波器看一眼。
Q6:为什么 PA0~PA2 能同时输出?一个定时器不是只有一个计数器吗?
对!TIMER1 只有一个计数器在跑,但它有多个比较通道(CH0~CH3),每个通道有独立的 CCR。计数器是大家共用的,每个通道拿同一个计数值和自己的 CCR 比,于是同一频率下能输出不同占空比的多路 PWM。这正是"用一个定时器驱动多个舵机/多个 LED"的原理。
小结
本示例把 PWM 的"三件套"讲得干干净净:
- 开时钟:
RCU_GPIOA+RCU_AF(复用必须)+RCU_TIMER1。 - 配 GPIO:PWM 引脚设
GPIO_MODE_AF_PP。 - 配定时器:
PSC定时间基准、ARR定周期(频率)、计数方向=向上。 - 配通道:使能输出、设极性、设
PWM0模式、设CCR(占空比)。 - 启动:
timer_auto_reload_shadow_enable+timer_enable,硬件自动输出。
记住公式 频率 = 计数时钟/(ARR+1),占空比 = CCR/(ARR+1),你就能随心所欲地产生任意 PWM。
拓展应用
掌握 PWM 后,可以做一堆好玩的项目:
- LED 呼吸灯:在
while(1)里用循环不断改pulse值(从 0 渐增到 ARR 再渐减),占空比平滑变化,LED 就会"呼吸"。 - 电机调速:把 PWM 接到直流电机驱动模块(如 L298N、TB6612)的使能端,占空比越大转速越高。
- 舵机控制:舵机靠 50Hz(周期 20ms)PWM 的脉宽定位,1.5ms 居中、0.5
2.5ms 对应 0180°。改 ARR/PSC 得到 50Hz,再用 CCR 调脉宽即可。 - 蜂鸣器/音频:无源蜂鸣器用不同频率的 PWM 发出不同音调,可演奏简单音乐。
- 伪 DAC:低通滤波 PWM 得到平滑电压,用占空比当"模拟量输出"(精度有限但省 DAC 外设)。
- 多路独立 PWM:TIMER1 的 4 个通道可各自不同占空比,同时驱动 4 个舵机或 4 路 LED 调光。
- 进阶:需要精确测频率/脉宽时,看同目录
TIMER2_inputcapture;需要电机死区/互补输出看TIMER0系列。
想理解"比较值怎么自动改出呼吸灯/波形",建议基于本例把
while(1)改成动态调整pulse的小循环试试。