AT32 TIM — PWM 输出(LED 呼吸灯 + 电机调速的基础)

AT32F403A 定时器 TMR3 输出 4 路不同占空比的 PWM 信号

TIM — PWM 输出

难度: ⭐⭐(需要了解定时器基本概念)
固件库: AT32F403A_407_Firmware_Library V2.2.2
源文件: project/at_start_f403a/examples/tmr/pwm_output_tmr3/src/main.c


这个实验是干嘛的?

PWM(Pulse Width Modulation,脉冲宽度调制)是一种用数字信号模拟模拟信号的技术。

简单说:MCU 只能输出 0V 或 3.3V,不能输出 1.5V。但如果你快速地在 0V 和 3.3V 之间切换,改变高电平的占比,就能模拟出不同的"平均电压":

1
2
3
占空比 10%:  ┃▄┃▄▄▄▄▄▄▄▄▄  → 平均电压 ≈ 0.33V → LED 很暗
占空比 50%:  ┃▄▄▄▄▄┃▄▄▄▄▄  → 平均电压 ≈ 1.65V → LED 中等亮度
占空比 90%:  ┃▄▄▄▄▄▄▄▄▄┃▄  → 平均电压 ≈ 2.97V → LED 很亮

本实验用 TMR3 的 4 个通道输出 4 路不同占空比的 PWM:

  • 通道 1:50% 占空比
  • 通道 2:37.5%
  • 通道 3:25%
  • 通道 4:12.5%

硬件准备

  • AT32F403A 开发板
  • 示波器(看波形)或 LED(看亮度变化)

PWM 输出引脚(TMR3 的 4 个通道):

通道 引脚 占空比
CH1 PA6 50%
CH2 PA7 37.5%
CH3 PB0 25%
CH4 PB1 12.5%

如果接 LED:PA6 的 LED 最亮,PB1 的 LED 最暗。


PWM 核心概念

关键参数

参数 含义 本例数值
ARR(重装载值) 定时器计数到多少归零 665
PSC(预分频器) 时钟分频系数 系统时钟/24MHz - 1
CCR(比较值) 计数到多少时翻转输出 通道不同值不同
占空比 CCR / (ARR+1) 50%, 37.5%, 25%, 12.5%
PWM 频率 时钟 / (PSC+1) / (ARR+1) 24MHz / 1 / 666 ≈ 36KHz

PWM 工作原理

1
2
3
4
计数器: 0 → 1 → 2 → ... → CCR → ... → ARR → 0 → 1 → ...
输出:   高电平 ──────────┐                ┌────────── 高电平
                         └── 低电平 ──────┘
                         ↑ CCR 时刻翻转
  • 计数器从 0 向上计数到 ARR,然后归零重新开始
  • 计数 < CCR 时,输出高电平
  • 计数 ≥ CCR 时,输出低电平
  • CCR 越大,高电平时间越长,占空比越大

完整代码

  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
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
#include "at32f403a_407_board.h"
#include "at32f403a_407_clock.h"

/* 4 个通道的比较值(决定占空比) */
uint16_t ccr1_val = 333;   // 333/666 = 50%
uint16_t ccr2_val = 249;   // 249/666 = 37.4%
uint16_t ccr3_val = 166;   // 166/666 = 25%
uint16_t ccr4_val = 83;    // 83/666  = 12.5%

uint16_t prescaler_value = 0;

/* GPIO 配置:把定时器通道映射到引脚 */
void gpio_configuration(void)
{
  gpio_init_type gpio_init_struct;
  gpio_default_para_init(&gpio_init_struct);

  /* PA6(CH1) + PA7(CH2) */
  gpio_init_struct.gpio_pins = GPIO_PINS_6 | GPIO_PINS_7;
  gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
  gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
  gpio_init_struct.gpio_mode = GPIO_MODE_MUX;        // 复用模式!
  gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
  gpio_init(GPIOA, &gpio_init_struct);

  /* PB0(CH3) + PB1(CH4) */
  gpio_init_struct.gpio_pins = GPIO_PINS_0 | GPIO_PINS_1;
  gpio_init(GPIOB, &gpio_init_struct);
}

/* 时钟使能 */
void crm_configuration(void)
{
  crm_periph_clock_enable(CRM_TMR3_PERIPH_CLOCK, TRUE);
  crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
  crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);
}

int main(void)
{
  system_clock_config();

  /* 第一步:使能时钟 */
  crm_configuration();

  /* 第二步:配置 GPIO(复用模式) */
  gpio_configuration();

  /* 第三步:计算预分频值 */
  // 例:系统时钟 240MHz,要得到 24MHz 定时器时钟
  // prescaler_value = 240MHz / 24MHz - 1 = 9
  prescaler_value = (uint16_t)(system_core_clock / 24000000) - 1;

  /* 第四步:定时器基本配置 */
  // ARR = 665,PSC = 9
  // PWM 频率 = 24MHz / (9+1) / (665+1) ≈ 3.6KHz
  tmr_base_init(TMR3, 665, prescaler_value);
  tmr_cnt_dir_set(TMR3, TMR_COUNT_UP);              // 向上计数
  tmr_clock_source_div_set(TMR3, TMR_CLOCK_DIV1);   // 不分频

  /* 第五步:配置 4 个通道为 PWM 模式 */
  tmr_output_config_type tmr_oc_init_structure;
  tmr_output_default_para_init(&tmr_oc_init_structure);

  tmr_oc_init_structure.oc_mode = TMR_OUTPUT_CONTROL_PWM_MODE_A;  // PWM 模式 A
  tmr_oc_init_structure.oc_idle_state = FALSE;
  tmr_oc_init_structure.oc_polarity = TMR_OUTPUT_ACTIVE_HIGH;     // 高电平有效
  tmr_oc_init_structure.oc_output_state = TRUE;                    // 使能输出

  /* 配置通道 1,CCR = 333,占空比 = 333/666 ≈ 50% */
  tmr_output_channel_config(TMR3, TMR_SELECT_CHANNEL_1, &tmr_oc_init_structure);
  tmr_channel_value_set(TMR3, TMR_SELECT_CHANNEL_1, ccr1_val);
  tmr_output_channel_buffer_enable(TMR3, TMR_SELECT_CHANNEL_1, TRUE);

  /* 配置通道 2,CCR = 249,占空比 ≈ 37.4% */
  tmr_output_channel_config(TMR3, TMR_SELECT_CHANNEL_2, &tmr_oc_init_structure);
  tmr_channel_value_set(TMR3, TMR_SELECT_CHANNEL_2, ccr2_val);
  tmr_output_channel_buffer_enable(TMR3, TMR_SELECT_CHANNEL_2, TRUE);

  /* 配置通道 3,CCR = 166,占空比 ≈ 25% */
  tmr_output_channel_config(TMR3, TMR_SELECT_CHANNEL_3, &tmr_oc_init_structure);
  tmr_channel_value_set(TMR3, TMR_SELECT_CHANNEL_3, ccr3_val);
  tmr_output_channel_buffer_enable(TMR3, TMR_SELECT_CHANNEL_3, TRUE);

  /* 配置通道 4,CCR = 83,占空比 ≈ 12.5% */
  tmr_output_channel_config(TMR3, TMR_SELECT_CHANNEL_4, &tmr_oc_init_structure);
  tmr_channel_value_set(TMR3, TMR_SELECT_CHANNEL_4, ccr4_val);
  tmr_output_channel_buffer_enable(TMR3, TMR_SELECT_CHANNEL_4, TRUE);

  /* 使能 ARR 预装载 */
  tmr_period_buffer_enable(TMR3, TRUE);

  /* 第六步:启动定时器 */
  tmr_counter_enable(TMR3, TRUE);

  /* PWM 输出已启动,主循环可以做其他事 */
  while(1)
  {
    // 如果要动态改变占空比,在这里调用:
    // tmr_channel_value_set(TMR3, TMR_SELECT_CHANNEL_1, new_ccr_val);
  }
}

代码分段讲解

1. GPIO 复用模式

1
gpio_init_struct.gpio_mode = GPIO_MODE_MUX;  // 复用模式

关键点:PWM 输出的引脚必须配置为复用模式(MUX),不是普通输出模式(OUTPUT)。

为什么?因为引脚的控制权要交给定时器硬件,而不是 CPU。定时器自动翻转引脚电平,CPU 不用管。

2. 预分频器

1
prescaler_value = (uint16_t)(system_core_clock / 24000000) - 1;

定时器的时钟源是系统时钟(比如 240MHz),太快了。预分频器把它降频:

  • PSC = 9 → 定时器时钟 = 240MHz / 10 = 24MHz
  • 定时器每 41.67ns 计数一次

3. ARR 和 CCR

1
2
tmr_base_init(TMR3, 665, prescaler_value);  // ARR = 665
tmr_channel_value_set(TMR3, TMR_SELECT_CHANNEL_1, 333);  // CCR = 333
  • ARR = 665:计数器从 0 计到 665 就归零,一个周期 = 666 个计数
  • CCR = 333:计数到 333 时输出翻转
  • 占空比 = 333 / 666 ≈ 50%

4. PWM 模式 A vs B

1
tmr_oc_init_structure.oc_mode = TMR_OUTPUT_CONTROL_PWM_MODE_A;
  • PWM 模式 A:计数 < CCR 时输出高电平(最常用)
  • PWM 模式 B:计数 < CCR 时输出低电平(反相)

实验现象

用示波器观察 4 个引脚:

  • PA6:50% 占空比的方波
  • PA7:37.5% 占空比
  • PB0:25% 占空比
  • PB1:12.5% 占空比

如果接 LED:PA6 最亮,PB1 最暗。


如何动态改变占空比?

1
2
// 在主循环或中断中调用
tmr_channel_value_set(TMR3, TMR_SELECT_CHANNEL_1, new_ccr);

new_ccr 范围:0 ~ ARR(0 ~ 665)

  • new_ccr = 0 → 占空比 0% → LED 灭
  • new_ccr = 333 → 占空比 50% → LED 半亮
  • new_ccr = 665 → 占空比 100% → LED 全亮

常见问题

Q: 引脚没有 PWM 输出?

  • GPIO 是否配置为复用模式(MUX)?普通输出模式不行
  • 定时器时钟是否使能?
  • tmr_counter_enable() 是否调用?

Q: PWM 频率不对?

  • 检查 PSC 和 ARR 的值
  • PWM 频率 = 定时器时钟 / (PSC+1) / (ARR+1)
  • 确认系统时钟是否正确(system_core_clock

Q: 想生成 50Hz PWM(控制舵机)?

  • 舵机需要 50Hz(20ms 周期)
  • 假设定时器时钟 24MHz:ARR = 24000000 / 50 / (PSC+1) - 1
  • PSC = 11 → ARR = 39999 → 50Hz
  • CCR 范围:10005000 对应 0.5ms2.5ms → 0°~180°

小结

概念 说明
PWM 用数字信号模拟模拟信号
ARR 重装载值,决定 PWM 周期
CCR 比较值,决定占空比
占空比 CCR / (ARR+1),高电平占比
复用模式 GPIO 必须配置为 MUX 模式
预装载 缓冲写入,避免毛刺

学了 PWM 能做什么?

PWM 是嵌入式中最实用的"万能模拟器":

LED 控制

项目 原理 难度
LED 呼吸灯 CCR 从 0 渐增到 ARR 再渐减 ⭐⭐
RGB 调色 3 路 PWM 控制红绿蓝,混合出任意颜色 ⭐⭐
背光调节 LCD/键盘背光亮度无级调节

电机控制

项目 原理 难度
直流电机调速 PWM + MOS 管 → 改变占空比 = 改变转速 ⭐⭐
舵机控制 50Hz PWM,脉宽 0.5~2.5ms → 0°~180° ⭐⭐
步进电机驱动 PWM 控制细分驱动器 ⭐⭐⭐
无刷电机(BLDC) 6 路互补 PWM + 死区控制 ⭐⭐⭐⭐

音频与信号

项目 原理 难度
蜂鸣器播放音乐 不同频率 PWM = 不同音调 ⭐⭐
简易信号发生器 改变频率和占空比输出方波 ⭐⭐
DAC 替代 PWM + RC 滤波 → 模拟电压输出 ⭐⭐⭐
红外遥控发射 38KHz PWM 载波 + 数据调制 ⭐⭐⭐

电源与通信

项目 原理 难度
开关电源 PWM 控制 MOS 管 → DC-DC 降压/升压 ⭐⭐⭐⭐
充电控制 PWM 控制充电电流(恒流充电) ⭐⭐⭐
可控硅调光 PWM 控制双向可控硅导通角 ⭐⭐⭐

核心思路:PWM 的本质是"用时间换精度"。只要控制高电平的时间占比,就能模拟出任意电压、任意频率的信号。LED 亮度、电机转速、声音音调、电源电压……所有需要"连续调节"的场景,PWM 都能搞定。


下一步:学会了 PWM,可以学 DMA,了解如何用 DMA 配合定时器实现更高级的波形生成。

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