全部例程

TIM - 通用定时器

W55MH32

更新于 2026年8月13日

本篇总结

使用 TIM3,输出 PWM 波形并用输入捕获测量信号脉宽。

🔑 KEIL MDK、UART调试、TIM

本示例演示 W55MH32L 通用定时器(TIM2-5)的两项典型功能:PWM 输出输入捕获。PWM 输出模式下,TIM3 在 PA7 产生固定频率与占空比的方波;输入捕获模式下,TIM3 通过通道 2 捕获外部信号的上升沿/下降沿,测量其高电平持续时间。

通用定时器简介

W55MH32 的通用定时器(TIM2-5)是一个由可编程预分频器驱动的 16 位自动装载计数器,每个定时器拥有 4 个独立通道,可分别配置为:输入捕获、输出比较、PWM 生成(边缘或中央对齐)、单脉冲模式输出。与基本定时器相比,通用定时器增加了通道逻辑;与高级定时器相比,它没有互补输出与死区插入。

通道模式用途本篇演示
输出比较 / PWM产生可调频率与占空比的方波步骤 1-3:PA7 输出 10kHz/50% PWM
输入捕获测量外部信号电平跳变时间步骤 4-5:测量 PA7 高电平脉宽

通用定时器的时基核心与基本定时器一致:更新周期 = (PSC+1) × (ARR+1) / TIMxCLK;PWM 输出在此基础上多了 TIMx_CCRx 比较寄存器来决定占空比:占空比 = CCR / (ARR+1)

前置工具准备

开始本例程前,请确认以下工具已安装:

  • WIZ UartTool V1.0:串口日志观察工具:点击下载
  • 示波器或逻辑分析仪(可选):观察 PA7 的 PWM 波形;输入捕获实验需一个可产生方波的外部信号源

硬件连接

PWM 输出与输入捕获共用 PA7(TIM3_CH2),但二者分属两个独立工程,互不冲突。串口日志通过 USART1(PA9/PA10)的板载 USB 转串口输出,仅需一根 Micro USB 线连接 DEBUG USB 口到 PC。

引脚功能方向模式所属演示
PA7TIM3_CH2芯片 → 外部复用推挽输出 (AF_PP)PWM 输出
PA7TIM3_CH2外部 → 芯片下拉输入 (IPD)输入捕获
PA9USART1_TX开发板 → PC复用推挽输出 (AF_PP)两者共用
PA10USART1_RXPC → 开发板浮空输入 (IN_FLOATING)两者共用

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\TIM\TIM_General\,核心文件如下:

TIM_General/
├── USER/
│   ├── main.c            ← TIM初始化 + PWM输出/输入捕获 + 中断服务 + printf 重定向
│   ├── w55mh32_conf.h    ← 外设头文件集中包含
│   ├── w55mh32_it.c/h    ← 中断服务函数(系统异常部分)
│   ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│   └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
    └── delay/
        └── delay.c/h      ← SysTick 延时函数

步骤1:TIM3 PWM 输出初始化

文件:USER/main.c | 函数:TIM_Configuration() — 配置 PA7 为复用推挽输出,TIM3 时基 1MHz、ARR=99(PWM 周期 100μs / 10kHz),CH2 输出 PWM2 模式、占空比 50%。


void TIM_Configuration(void)
{
    GPIO_InitTypeDef        GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef       TIM_OCInitStructure;

    /* 开启 TIM3 及 GPIOA/AFIO 时钟 */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

    /* PA7: TIM3_CH2 → 复用推挽输出 */
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 时基:PSC 使 CK_CNT=1MHz,ARR=99 → PWM 周期 100μs(10kHz) */
    TIM_TimeBaseStructure.TIM_Period        = 99;
    TIM_TimeBaseStructure.TIM_Prescaler     = clocks.PCLK1_Frequency / 1000000 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode   = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    /* 输出比较:PWM2 模式,CH2,Pulse=49 → 占空比 49%,低电平有效 */
    TIM_OCInitStructure.TIM_OCMode       = TIM_OCMode_PWM2;
    TIM_OCInitStructure.TIM_OutputState  = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
    TIM_OCInitStructure.TIM_Pulse        = 99 / 2;
    TIM_OCInitStructure.TIM_OCPolarity   = TIM_OCPolarity_Low;
    TIM_OCInitStructure.TIM_OCNPolarity  = TIM_OCNPolarity_Low;
    TIM_OCInitStructure.TIM_OCIdleState  = TIM_OCIdleState_Set;
    TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
    TIM_OC2Init(TIM3, &TIM_OCInitStructure);

    /* 使能预装载寄存器:运行中改 CCR/ARR 会在下个更新事件生效 */
    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM3, ENABLE);

    /* 启动定时器 */
    TIM_Cmd(TIM3, ENABLE);
    TIM_CtrlPWMOutputs(TIM3, ENABLE);
}

六步完成:开时钟 → GPIO 复用推挽 → 时基参数(PSC=71 使 CK_CNT=1MHz、ARR=99)→ 输出比较参数(PWM2、Pulse=49、低电平有效)→ 预装载使能 → 启动。PWM 周期 = (ARR+1)/CK_CNT = 100/1MHz = 100μs(10kHz),占空比 = Pulse/(ARR+1) = 49/100 = 49%

如果你想自定义

  • 改 PWM 频率 → 修改 TIM_Period(ARR)和 TIM_Prescaler(PSC)。如要 1kHz:PSC=71(1MHz)、ARR=999
  • 改占空比 → 修改 TIM_Pulse(CCR)。如要 30% 占空比:TIM_Pulse = 99 * 3 / 10(即 29)
  • 换用 CH1/CH3/CH4 → 同步修改 GPIO 引脚(CH1=PA6、CH3=PA8、CH4=PA9)和 TIM_OCxInit / TIM_OCxPreloadConfig 的通道号

步骤2:主函数逻辑

函数:main() — 初始化延时与 UART(115200) → 打印时钟频率 → 配置 TIM3 PWM → 进入空循环,波形由硬件持续输出。


RCC_ClocksTypeDef clocks;

int main(void)
{
    delay_init();
    UART_Configuration(115200);
    RCC_GetClocksFreq(&clocks);

    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, "
           "PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
           (float)clocks.SYSCLK_Frequency / 1000000,
           (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000,
           (float)clocks.PCLK2_Frequency / 1000000,
           (float)clocks.ADCCLK_Frequency / 1000000);
    printf("TIM General PWM Test.\n");

    TIM_Configuration();

    while (1);
}

PWM 一旦配置完成即由硬件持续输出,while(1) 中无需任何干预。clocks 为全局变量,供 TIM_Configuration() 计算 PSC 时引用 PCLK1 频率,保证换主频时无需改代码。

运行时观察占空比

while(1) 中加入以下代码可周期性打印当前占空比:

uint16_t v = TIM_GetCapture2(TIM3);
printf("Duty: %.1f%%\n", (float)v / 100 * 100);
delay_ms(1000);

步骤3:printf 重定向

重写 fputc() 将 printf 输出重定向到 USART1,并自动在 \n 前补 \r 确保串口工具正确换行。


int SER_PutChar(int ch)
{
    while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));
    USART_SendData(USART_TEST, (uint8_t)ch);
    return ch;
}

int fputc(int c, FILE *f)
{
    if (c == '\n')
        SER_PutChar('\r');         // 补回车,确保串口工具正确换行
    return (SER_PutChar(c));
}

步骤4:输入捕获初始化(扩展功能)

以下为独立工程 TIM_InputCapture 的核心代码(与 PWM 输出工程互斥,不可同时烧录)。函数:TIM_ICConfiguration() — 将 PA7 配为下拉输入,TIM3 时基 1MHz(计数分辨率 1μs),CH2 上升沿捕获,并使能溢出 + 捕获中断。


void TIM_ICConfiguration(void)
{
    GPIO_InitTypeDef        GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_ICInitTypeDef       TIM_ICInitStructure;
    NVIC_InitTypeDef        NVIC_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

    /* PA7: TIM3_CH2 → 下拉输入(捕获外部信号) */
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 时基:PSC=71(72MHz/72=1MHz,计数分辨率 1μs),ARR=0xFFFF */
    TIM_TimeBaseStructure.TIM_Period        = 0xFFFF;
    TIM_TimeBaseStructure.TIM_Prescaler     = 72 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode   = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    /* 通道2 上升沿捕获 */
    TIM_ICInitStructure.TIM_Channel     = TIM_Channel_2;
    TIM_ICInitStructure.TIM_ICPolarity  = TIM_ICPolarity_Rising;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_Div1;
    TIM_ICInitStructure.TIM_ICFilter    = 0x0;
    TIM_ICInit(TIM3, &TIM_ICInitStructure);

    /* NVIC:溢出 + 捕获中断 */
    NVIC_InitStructure.NVIC_IRQChannel                   = TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    TIM_ITConfig(TIM3, TIM_IT_Update | TIM_IT_CC2, ENABLE);

    TIM_Cmd(TIM3, ENABLE);
}

关键三点:① PSC=71 使计数器每 1μs 加 1,捕获值单位即 μs;② ARR=0xFFFF,单次最多计 65535μs,超出则靠溢出中断累计;③ 先上升沿捕获清零计数器,再切换为下降沿捕获读出高电平时长。全局变量 TIM3_CAPTURE_STA(位7完成 / 位6已捕获上升沿 / 位0-5溢出次数)与 TIM3_CAPTURE_VAL(捕获值)配合主循环输出结果。

如果你想自定义

  • 改捕获精度 → 修改 TIM_Prescaler。如要 0.1ms 精度:PSC=7199(10kHz)
  • 改捕获通道 → 同步修改 TIM_Channel、GPIO 引脚和 TIM_IT_CCx
  • 测低电平脉宽 → 将首次捕获极性改为 TIM_ICPolarity_Falling,第二次切换为 Rising

步骤5:输入捕获中断服务函数

函数:TIM3_IRQHandler() — 上升沿清零计数器并切为下降沿捕获;下降沿读出计数值标记完成;溢出时累计溢出次数。主循环检测到完成标志后计算总时长。


void TIM3_IRQHandler(void)
{
    if (!(TIM3_CAPTURE_STA & 0x80))   /* 未完成捕获 */
    {
        /* 处理溢出中断 */
        if (TIM_GetITStatus(TIM3, TIM_IT_Update))
        {
            if (TIM3_CAPTURE_STA & 0x40)   /* 已捕获上升沿,统计溢出 */
            {
                if ((TIM3_CAPTURE_STA & 0x3F) == 0x3F)
                    TIM3_CAPTURE_STA |= 0x80;   /* 溢出 32 次,标记失败 */
                else
                    TIM3_CAPTURE_STA++;          /* 溢出次数 +1 */
            }
        }

        /* 处理捕获中断(CH2) */
        if (TIM_GetITStatus(TIM3, TIM_IT_CC2))
        {
            if (TIM3_CAPTURE_STA & 0x40)   /* 已捕获上升沿,本次为下降沿 */
            {
                TIM3_CAPTURE_VAL = TIM_GetCapture2(TIM3);   /* 读取计数值 */
                TIM3_CAPTURE_STA |= 0x80;                    /* 标记完成 */
                TIM_OC2PolarityConfig(TIM3, TIM_ICPolarity_Rising);  /* 切回上升沿 */
            }
            else                            /* 首次捕获上升沿 */
            {
                TIM3_CAPTURE_STA = 0x40;                    /* 标记已捕获上升沿 */
                TIM_SetCounter(TIM3, 0);                     /* 计数器清零 */
                TIM_OC2PolarityConfig(TIM3, TIM_ICPolarity_Falling); /* 切为下降沿 */
            }
        }
    }
    TIM_ClearITPendingBit(TIM3, TIM_IT_CC2 | TIM_IT_Update);   /* 清除中断标志 */
}

主循环中:总时长 = (溢出次数 × 65536 + 捕获值) μs。通过两次捕获(上升沿→下降沿)配合极性动态切换,用单通道实现了高电平脉宽测量。必须先清除中断标志,否则会反复进中断。

注意

TIM_OC2PolarityConfig 虽名为 OC(输出比较),但在输入捕获下用于动态切换 CC2 通道的捕获极性,这是标准库的惯用写法。若用 标准库则对应 HAL_TIM_IC_ConfigChannel 重新配置极性。

编译与下载

Keil MDK 中打开 TIM_General.uvprojxF7 编译,确认 0 Error(s)。连接 DEBUG USB 口到 PC,F8 下载。下载成功后按 RESET 键启动新固件。PWM 输出工程与输入捕获工程分别编译烧录,不可同时运行。

运行验证

PWM 输出工程:打开 WIZ UartTool V1.0 ,选 择开发板对应 COM 口,波特率 115200 / 8N1。按 RESET 启动后,串口打印时钟信息与 "TIM General PWM Test.";用示波器探头接 PA7,可观察到 10kHz、50% 占空比的方波。


SYSCLK: 72.0Mhz, HCLK: 72.0Mhz, PCLK1: 36.0Mhz, PCLK2: 72.0Mhz, ADCCLK: 36.0Mhz
TIM General PWM Test.

输入捕获工程 :在 PA7 上输入一个方波信号(可由另一块开发板或信号发生器产生),串口将持续打印该信号的高电平时间,单位 μs。

HIGH:500 us
HIGH:501 us
HIGH:500 us
...

常见问题

Q: PA7 没有 PWM 波形输出

按顺序检查:① GPIO 是否配为 GPIO_Mode_AF_PP(复用推挽,不是普通推挽);② TIM_OC2InitTIM_OutputState 是否为 Enable;③ TIM_Cmd(TIM3, ENABLE) 是否调用;④ 示波器探头地线是否接到开发板 GND。

Q: PWM 占空比与预期相反(该高的时候低)

PWM 模式与极性配合问题。本例用 PWM2 + OCPolarity_Low:CNT>CCR 时输出有效(低电平),所以 Pulse 越小、低电平时间越长。若想要"常规"高电平占空比,可改为 TIM_OCMode_PWM1 + TIM_OCPolarity_High,此时 Pulse/(ARR+1) 即为高电平占比。

Q: 输入捕获打印的脉宽值一直是 0 或乱跳

① 确认外部信号确实加到了 PA7 且共地;② 检查 GPIO 模式:无信号时应为 IPD(下拉),若信号是高电平有效则改为 IPU(上拉);③ 中断函数中极性切换是否正确(上升沿→下降沿→读值);④ TIM_ClearITPendingBit 是否放在中断函数末尾。

Q: 换用 CH1/CH3/CH4 后编译报错或无输出

通道号必须三处一致:① GPIO 引脚(CH1=PA6、CH2=PA7、CH3=PA8、CH4=PA9,注意 CH4 与 USART1_TX 冲突);② TIM_OCxInit / TIM_ICInitTIM_Channel_x;③ 预装载 TIM_OCxPreloadConfig 与捕获读 TIM_GetCapturex。三处通道号不一致会导致编译通过但运行异常。

Q: 编译报错 "cannot open source file w55mh32.h"

检查 Project → Options → C/C++ → Include Paths,确保 SDK 的 Libraries 目录已添加。若 TIM_OCInitTypeDef 结构体报未知成员,确认包含的 w55mh32_tim.h 已启用对应定时器宏。

总结

本文基于 W55MH32L 的 TIM3 通用定时器演示了两大核心功能:

  • PWM 输出:TIM3_CH2(PA7)配置为 PWM2 模式,PSC 使 CK_CNT=1MHz、ARR=99,输出 10kHz / 50% 占空比方波;TIM_OC2PreloadConfig 使能预装载,运行中可安全修改 CCR
  • 输入捕获:TIM3_CH2(PA7)配置为上升沿捕获,PSC=71 使计数分辨率 1μs,通过上升沿清零→下降沿读值的双沿切换测量高电平脉宽,溢出中断累计超长信号
  • 核心公式:PWM 频率 = TIMxCLK / (PSC+1) / (ARR+1)占空比 = CCR / (ARR+1)捕获脉宽 = (溢出次数 × 65536 + 捕获值) × 计数周期
  • 通用定时器的 4 个通道完全独立,可同时输出不同占空比的 PWM 或分别捕获多路信号,是电机控制与信号测量的基础

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家