
目录
[1. 简介](#1. 简介)
[2. 模式](#2. 模式)
[2.1 计数器](#2.1 计数器)
[2.2 捕获模式](#2.2 捕获模式)
[2.3 比较模式(边沿对齐PWM)](#2.3 比较模式(边沿对齐PWM))
[2.4 单次触发](#2.4 单次触发)
[2.5 中心模式(互补 PWM)](#2.5 中心模式(互补 PWM))
[3. 代码编写](#3. 代码编写)
[3.1 定时器初始化](#3.1 定时器初始化)
[3.2 中断服务函数](#3.2 中断服务函数)
[3.3 主函数](#3.3 主函数)
1. 简介
通用定时器 TIMER 主要包括 4 个独立的 Timer。分别可以独立配置运行计数时钟和滤波常数。每个 Timer 可以用于输出特定周期占空比的波形,也可以捕获外部波形进行周期占空比的检测。

定时器模块有以下特点:
- 独立工作,可工作在不同频率下
- Timer0/1 为 16bit 通用定时器
- Timer2/3 为 32bit 通用定时器
- 每个通用定时器处理 2 个外部输入信号(捕获模式),或者产生 2 个输出信号(比较模式)
- 对每个输入信号可以进行最大 2040 个系统主时钟的滤波,即,当芯片工作在 96MHz 时钟频率下时,可以滤除 21μs 宽度以下毛刺
2. 模式
2.1 计数器
Timer 中的计数器可以选择递增或递减方向计数,默认为递增计数。
- 若为递增计数,则计数器从 0 计数到 TH 值,再回到 0 重新开始计数,计数器回到 0 时,产生回零中断。
- 若为递减计数,则计数器从 TH 计数到 0 值,再回到 TH 重新开始计数,计数器回到 0 时,产生回零中断。
实际计数周期为(TH+1)/clk_freq,其中clk_freq是定时器的时钟频率。

2.2 捕获模式
捕获模式下,可以使用 Timer 来检测输入信号的上升 / 下降或者双沿,发生捕获事件(即输入信号电平变化)时,定时器计数值存入 TIMERx_CMP 寄存器,并产生捕获中断。计数器回零时,仍然会产生回零中断。


2.3 比较模式(边沿对齐PWM)
比较模式下,计数器计数到 TIMERx_CMP 值时,产生比较中断。比较模式可以驱动一个比较脉冲发生,在回零时,向 IO 口输出一个电平(极性可配置),在比较事件发生时,电平翻转,向 IO 口输出另一个电平。计数器回零时,仍然会产生回零中断。设置 TIMERx_CMP0=0,可使得 Timer X 通道 0 为恒 1,设置 TIMERx_CMP0=TIMERx_TH+1,可使得 Timer 通道 0 为恒 0。


2.4 单次触发
定时器配置在比较模式(边沿对齐 PWM),且 TIMERx_CFG.EN = 0(定时器处于空闲、未计数状态),软件向寄存器位 ONE_TRIG 写 1,硬件自动输出单个完整周期、指定占空比的 PWM 波形。

注意:如果定时器正在执行单次波形计数过程中,再次对 ONE_TRIG 写 1,本次触发命令直接忽略;定时器会继续跑完当前周期,不会重新启动。
也就是:周期未完成前,频繁软件触发无效。
2.5 中心模式(互补 PWM)
除递增计数和递减外,计数器还可以设置为中心计数模式,即 TIMERx_CFG.CENTER=1。此时,计数器从 0 递增计数至 TH 后再递减计数回 0,开始下一个周期的中心计数。实际计数周期为2TH/clk_freq,clk_freq 指定时器的时钟频率。
中心计数模式可以用于输出互补 PWM 波形,通过合理设置 TIMERx_CMP0/1 可以产生上下管导通死区,死区时间=TIMERx_CMP1-TIMERx_CMP0。此外,需要合理设置通道的初始值以产生互补波形。Timer 在递增计数和递减计数期间都会有TIMERx_CNT=TIMER_CMP0/1 的时刻,但只有递增计数阶段会产生中断事件。
3. 代码编写
这里只说明计数器的用法,也就是定时器中断,实现功能是每隔1s让LED1电平反转一次,代码沿用上一章用系统定时器做LED闪烁的代码:
3.1 定时器初始化
我们先来看一下定时器初始化结构体部分:
cpp
typedef struct
{
FuncState Timer_EN; /**< 定时器使能,ENABLE使能,DISABLE失能*/
uint32_t Timer_CAP1_CLR_EN; /**< 当发生CAP1捕获事件时,清零 Timer 计数器,高有效*/
uint32_t Timer_CAP0_CLR_EN; /**< 当发生CAP0捕获事件时,清零 Timer 计数器,高有效*/
uint32_t Timer_ONE_TRIG; /**< 单次发送模式,此位需要在Timer比较模式下使用,且对应定时器使能需设置为0*/
uint32_t Timer_CENTER; /**< 中心计数模式使能,0:Timer向上从0计数至TH,然后回0,或Timer向下
从TH计数至0,然后回到TH,1:Timer向上从0计数至TH,然后向下计数至0*/
uint32_t Timer_DIR; /**< 0:0->TH 递增计数,1:TH->递减计数*/
/**
* @brief 定时器分频 \n
* @see TIM_Clk_Div1 \n
* @see TIM_Clk_Div2 \n
* @see TIM_Clk_Div4 \n
* @see TIM_Clk_Div8 \n
* @see TIM_Clk_Div16 \n
* @see TIM_Clk_Div32 \n
* @see TIM_Clk_Div64 \n
* @see TIM_Clk_Div128
*/
uint32_t Timer_ClockDiv;
uint32_t Timer_ETON; /**< Timer计数器计数外部启动使能,0: 自动运行,1:外部事件触发计数*/
uint32_t Timer_GATE_EN; /**< Timer暂停使能,0:不暂停 1:当外部信号为低时,Timer暂停计数,
外部信号根据TIMER1_EVT.EVT_SRC 进行选择*/
uint32_t Timer_RL_EN; /**< Timer重装使能,0:禁用外部事件重装,1:使能外部事件重装*/
uint32_t Timer_XCLK_EN; /**< Timer时钟源,0: 芯片内部时钟,1:外部时钟*/
/**
* @brief 定时器分频 \n
* @see CAP_SRC_TCH0 \n
* @see CAP_SRC_TCH1 \n
* @see CAP_SRC_CLU0 \n
* @see CAP_SRC_CLU1 \n
* @see CAP_SRC_CLU2 \n
* @see CAP_SRC_CLU3 \n
* @see CAP_SRC_CMP0 \n
* @see CAP_SRC_CMP1 \n
* @see CAP_SRC_CMP2 \n
* @see CAP_SRC_CHXOR
*/
uint32_t Timer_SRC1;
uint32_t Timer_CH1Output; /**< Timer 通道1在比较模式下的输出极性控制,当计数器 CNT<CMP1时的输出值,0:低电平,1:高电平*/
uint32_t Timer_CH1_WorkMode; /**< Timer 通道1的工作模式选择,0: 比较模式 ,1: 捕获模式*/
uint32_t Timer_CH1_FE_CAP_EN; /**< Timer通道1下升沿捕获事件使能,1:使能,0:关闭*/
uint32_t Timer_CH1_RE_CAP_EN; /**< Timer通道1上升沿捕获事件使能,1:使能,0:关闭*/
/**
* @brief 定时器分频 \n
* @see CAP_SRC_TCH0 \n
* @see CAP_SRC_TCH1 \n
* @see CAP_SRC_CLU0 \n
* @see CAP_SRC_CLU1 \n
* @see CAP_SRC_CLU2 \n
* @see CAP_SRC_CLU3 \n
* @see CAP_SRC_CMP0 \n
* @see CAP_SRC_CMP1 \n
* @see CAP_SRC_CMP2 \n
* @see CAP_SRC_CHXOR
*/
uint32_t Timer_SRC0; /**< Timer 捕获模式通道0信号来源*/
uint32_t Timer_CH0Output; /**< Timer 通道0在比较模式下的输出极性控制,当计数器 CNT<CMP1时的输出值,0:低电平,1:高电平*/
uint32_t Timer_CH0_WorkMode; /**< Timer 通道0的工作模式选择,0: 比较模式 ,1: 捕获模式*/
uint32_t Timer_CH0_FE_CAP_EN; /**< Timer通道0下升沿捕获事件使能,1:使能,0:关闭*/
uint32_t Timer_CH0_RE_CAP_EN; /**< Timer通道0上升沿捕获事件使能,1:使能,0:关闭*/
uint32_t Timer_TH; /**< Timer计数器计数门限*/
uint32_t Timer_CNT; /**< Timer计数器计数门限*/
uint32_t Timer_CMP0; /**< 通道0比较捕获寄存器*/
uint32_t Timer_CMP1; /**< 通道1比较捕获寄存器*/
/**
* @brief 定时器分频 \n
* @see TIMER0_EVT_CH0 \n
* @see TIMER0_EVT_CH1 \n
* @see ...... \n
* @see TIMER_EVT_TADC2 \n
* @see TIMER_EVT_TADC3
*/
uint32_t Timer_EVT; /**< Timer外部事件选择*/
uint32_t Timer_FLT; /**< 通道0/1信号滤波宽度选择。取值范围0~255,滤波公式=Timer_FLT*8*定时器时钟*/
/**
* @brief 定时器中断使能 \n
* @see Timer_IRQEna_CH0 \n
* @see Timer_IRQEna_CH1 \n
* @see Timer_IRQEna_ZC \n
* @see Timer0_IRQEna_FAIL \n
* @see Timer_IRQEna_CH0_RE \n
* @see Timer_IRQEna_CH1_RE \n
* @see Timer_IRQEna_ZC_RE \n
* @see Timer0_IRQEna_FAIL_RE
*/
uint32_t Timer_IRQEna; /**< 中断使能*/
uint32_t Timer0_UPDATE; /**< 软件更新,1:将 TH/CMP0/CMP1 的预装载值加载到影子寄存器 STH/SCMP0/SCMP1
之中,0:无作用*/
uint32_t Timer0_SHADOW; /**< 0:软件写入TH/CMP0/CMP1 时直接更新对应的影子寄存器,1:启用影子寄存器,
软件写入 TH/CMP0/CMP1 时进仅仅更新预装载值,当Timer发生过零事件时才将预
装载值写入影子寄存器*/
uint32_t Timer0_CH1_DEFAULT; /**< 定时器0发生Fail事件MOE清零后,CH1通道输出值*/
uint32_t Timer0_CH0_DEFAULT; /**< 定时器0发生Fail事件MOE清零后,CH0通道输出值*/
uint32_t Timer0_HALT_PRT; /**< MCU 进入HALT状态,Timer输出值选择。1:正常输出;0:输出 CH1_DEFAULT 和 CH0_DEFAULT。*/
uint32_t Timer0_FAIL_SEL; /**< FAIL信号选择,0:TIMER0_FAIL,来自GPIO,1:CLU0输出*/
uint32_t Timer0_FAIL_POL; /**< FAIL信号极性,0:高电平FAIL,1:低电平FAIL*/
uint32_t Timer0_FAIL_EN; /**< FAIL 信号使能,0:禁用 FAIL,1:使能 FAIL*/
} TIM_TimerInitTypeDef;
可以看到有很多参数,不过在这里我们有些暂时都用不到,我们将这些参数按照功能维度分类:
| 功能域 | 涉及的字段(默认值=0) | 为什么不需要动 |
|---|---|---|
| 捕获模式 | Timer_CHx_WorkMode, Timer_SRCx, Timer_CHx_FE/RE_CAP_EN, Timer_CAPx_CLR_EN, Timer_FLT |
没开捕获模式,相关寄存器无论如何取值都不参与硬件行为 |
| PWM/比较输出 | Timer_CHxOutput, Timer_CMP0, Timer_CMP1 |
没使能 CH0/CH1 比较中断,无 PWM 输出需求 |
| 外部事件 | Timer_ETON, Timer_GATE_EN, Timer_RL_EN, Timer_XCLK_EN, Timer_EVT |
时钟源选内部(96MHz PLL),不需要外部触发/门控/重载 |
| Fail 安全 | Timer0_FAIL_EN/SEL/POL, Timer0_CHx_DEFAULT, Timer0_HALT_PRT |
电机控制的保护机制,与定时演示无关 |
| 影子寄存器 | Timer0_UPDATE, Timer0_SHADOW |
仅在运行时动态修改 TH/CMP 才需要,初始化阶段不用 |
这里我们真正起作用的参数:
Timer_ClockDiv:决定计数频率
Timer_TH :决定中断周期
Timer_IRQEna :决定触发哪种中断
Timer_EN :决定定时器开关
配置如下:
cpp
void Timer0_Init(void)
{
TIM_TimerInitTypeDef TIM_InitStruct;
TIM_TimerStrutInit(&TIM_InitStruct);
TIM_InitStruct.Timer_CH0_WorkMode = TIMER_OPMode_CMP;
TIM_InitStruct.Timer_CH1_WorkMode = TIMER_OPMode_CMP;
TIM_InitStruct.Timer_TH = 37499;
TIM_InitStruct.Timer_ClockDiv = TIMER_CLK_DIV128;
TIM_InitStruct.Timer_IRQEna = Timer_IRQEna_ZC;
TIM_InitStruct.Timer_EN = ENABLE;
TIM_TimerInit(UTIMER0, &TIM_InitStruct);
NVIC_EnableIRQ(TIMER0_IRQn);
}
这里还配置了CH0/CH1 的工作模式,二者只在触发比较中断或输出 PWM 时才生效,对仅用过零中断的场景,这两个寄存器位写什么都不会改变行为,至于为什么在这里重新声明一边,官方SDK给的直接沿用了,并且这样写万一 SDK 升级改了 StructInit 的默认值,显式赋值不受影响。
这里准备使用Timer0,我们知道Timer0是16位寄存器,最大数据为65535,又由于此时我们此时使用的是96MHz的主频,根据公式:
计数周期 = (TH+1)/(主频/分频系数)
举个例子:50ms周期
定时器时钟 = 96MHz / 128 = 750kHz
计数周期 = (37499 + 1) / 750000 = 0.05s = 50ms
| 分频系数 | 定时器时钟 | 需要的 TH 值 | 16位能装下 |
|---|---|---|---|
| DIV1 | 96 MHz | 4,799,999 | ❌ |
| DIV2 | 48 MHz | 2,399,999 | ❌ |
| DIV4 | 24 MHz | 1,199,999 | ❌ |
| DIV8 | 12 MHz | 599,999 | ❌ |
| DIV16 | 6 MHz | 299,999 | ❌ |
| DIV32 | 3 MHz | 149,999 | ❌ |
| DIV64 | 1.5 MHz | 74,999 | ❌ (超 65535) |
| DIV128 | 750 kHz | 37,499 | ✅ |
因此这里只能给到128分频,TH值为37499,中断选择过零中断:
cpp
/**
* @brief 中断使能配置定义
*/
typedef enum
{
Timer_IRQEna_None = 0, /**< 关闭所有中断*/
Timer_IRQEna_CH0 = BIT0, /**< 通道 0 中断*/
Timer_IRQEna_CH1 = BIT1, /**< 通道 1 中断*/
Timer_IRQEna_ZC = BIT2, /**< 计数器过 0 中断*/
Timer0_IRQEna_FAIL = BIT3, /**< 定时器0,FAIL事件中断*/
Timer_IRQEna_CH0_RE = BIT8, /**< 通道0比较/捕获DMA请求使能*/
Timer_IRQEna_CH1_RE = BIT9, /**< 通道1比较/捕获DMA请求使能*/
Timer_IRQEna_ZC_RE = BIT10, /**< 计数器过0 DMA请求使能*/
Timer0_IRQEna_FAIL_RE = BIT11, /**< 定时器0,FAIL事件DMA请求使能*/
Timer_IRQEna_All = 0xf /**< 开启所有中断*/
} Timer_IRQEnaDef;
3.2 中断服务函数
我们知道刚刚设置的是每隔50ms产生一次中断,然后这里通过 tick_cnt 进行累加计数,实现1s定时器的操作,实现每隔1s让LED1电平反转一次:
cpp
void TIMER0_IRQHandler(void)
{
static uint8_t tick_cnt = 0;
if (TIM_GetIRQFlag(UTIMER0, TIMER_IF_ZERO))
{
TIM_ClearIRQFlag(UTIMER0, Timer_IRQEna_ZC);
tick_cnt++;
if (tick_cnt >= 20)
{
tick_cnt = 0;
if (GPIO_ReadOutputDataBit(LED1_PORT, LED1_PIN))
{
GPIO_ResetBits(LED1_PORT, LED1_PIN);
}
else
{
GPIO_SetBits(LED1_PORT, LED1_PIN);
}
}
}
}
3.3 主函数
主函数将LED1部分删除即可:
cpp
int main(void)
{
Hardware_init(); /* 硬件初始化 */
while(1)
{
GPIO_ResetBits(LED2_PORT, LED2_PIN); /* LED2 灭*/
SoftDelay(0x68A000);
GPIO_SetBits(LED2_PORT, LED2_PIN); /* LED2 亮*/
SoftDelay(0x68A000);
}
}
然后就会发现 LED1 和 LED2 开始每隔1s同步闪烁,不过由于0x68A000是我们计算的定时,可能不准确,后续可能就不会同步了。
工程源码:

