基于江科大(13, 14, 15, 16, 17, 19)、铁头山羊(40, 42, 44)三教程综合整理
STM32F10x 系列有 最多 8 个定时器,分为三种类型:
| 类型 | 定时器 | 位数 | 特点 |
|---|---|---|---|
| 高级定时器 | TIM1, TIM8 | 16 位 | 带死区互补输出、重复计数器、刹车功能,适合电机控制 |
| 通用定时器 | TIM2~TIM5 | 16 位 | 时基单元、输入捕获、输出比较、编码器接口、PWM 生成 |
| 基本定时器 | TIM6, TIM7 | 16 位 | 仅时基单元(定时中断 + DAC 触发),无输入输出通道 |
这三个笔记综合覆盖的是 通用定时器 TIM2~TIM5,这也是最常用、功能最丰富的定时器。
上图:STM32 定时器中断基本结构(含时基单元、预分频器、计数器、自动重装寄存器)(来源:STM32入门教程 PPT 第57页)
上图:预分频器工作时序(来源:STM32入门教程 PPT 第58页)
每个通用定时器都有一个 16 位的时基单元,由三个寄存器组成:
CK_PSC(定时器时钟,如 72 MHz)
│
▼
┌──────────┐ ┌────────────┐ ┌──────────┐
│ PSC │ ──→ │ CNT 计数器 │ ──→ │ ARR │
│预分频器 │ │ (16-bit) │ │自动重装 │
│16-bit │ │ 0~65535 │ │寄存器 │
└──────────┘ └────────────┘ └──────────┘
| 寄存器 | 全称 | 作用 |
|---|---|---|
| PSC | Prescaler(预分频器) | 对 CK_PSC 进行分频,得到 CK_CNT = CK_PSC / (PSC + 1) |
| CNT | Counter(计数器) | 在 CK_CNT 驱动下递增/递减,每来一个脉冲 ±1 |
| ARR | Auto-Reload Register(自动重装载寄存器) | 当 CNT 达到 ARR 时溢出归零(向上计数),产生更新事件 |
| 模式 | 说明 | 应用场景 |
|---|---|---|
| 向上计数 | CNT 从 0 递增到 ARR,溢出后归 0 | 最常用,定时中断、PWM |
| 向下计数 | CNT 从 ARR 递减到 0,溢出后回到 ARR | 偶尔使用 |
| 中央对齐 | CNT 从 0 到 ARR 来回摆动 | 需要对称 PWM 时 |
CK_CNT = CK_PSC / (PSC + 1)
T_溢出 = (ARR + 1) / CK_CNT
T_溢出 = (ARR + 1) × (PSC + 1) / CK_PSC
示例:CK_PSC = 72 MHz,PSC = 7199,ARR = 9999
CK_CNT = 72M / 7200 = 10000 Hz = 10 kHz
T_溢出 = (9999 + 1) / 10000 = 1 秒
void Timer_Init(void)
{
// 1. 开启 TIM2 时钟(TIM2 在 APB1 上)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 2. 选择内部时钟(默认就是内部时钟,此函数可选)
TIM_InternalClockConfig(TIM2);
// 3. 时基单元配置
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 滤波器分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; // ARR = 9999
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; // PSC = 7199
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 高级定时器用
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 4. 清除更新标志位(TIM_TimeBaseInit 末尾会手动产生更新事件)
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
// 5. 开启更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 6. NVIC 配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
// 7. 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
// 用户代码:如变量自增、设置标志位等
Num++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 必须清除标志位
}
}
// HAL库 — 定时中断
TIM_HandleTypeDef htim2;
void MX_TIM2_Init(void)
{
htim2.Instance = TIM2;
htim2.Init.Prescaler = 7200 - 1; // 72MHz/7200 = 10kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 10000 - 1; // 10kHz/10000 = 1Hz
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_Base_Init(&htim2);
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
HAL_TIM_Base_Start_IT(&htim2); // 启动中断模式
}
// MSP 初始化:HAL 自动调用,负责底层 RCC/GPIO/NVIC
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2) {
__HAL_RCC_TIM2_CLK_ENABLE();
}
}
// HAL 弱回调,需在用户文件中实现
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2) {
// 定时中断处理,如 Num++;
}
}
注意:
HAL_TIM_Base_Init() 内部会调用 HAL_TIM_Base_MspInit() 开启时钟等底层配置。AutoReloadPreload 使能后,ARR 在更新事件时更新,防止写入途中产生异常波形。HAL_TIM_PeriodElapsedCallback,在 HAL 库里有一个默认 __weak 实现,用户需在同文件或主文件中覆盖。输出比较(Output Compare)是定时器的输出功能,通过比较 CNT 和 CCR(捕获/比较寄存器)的值,在引脚上输出特定的电平变化。
STM32 通用定时器有 4 个独立通道(CH1~CH4),每个通道有一个 CCR 寄存器。
PWM(Pulse Width Modulation,脉冲宽度调制)是最常用的输出比较模式。
PWM 模式 1(向上计数):
CNT 溢出 → CNT 归零,重新从 0 开始
CCR ARR
CNT: 0───████████████████████████████████████████████████████████───0
输出:████████████████ 低电平 ████████████████████████████████████
↑ 占空比 = CCR/ARR ↑
| 参数 | 公式 | 说明 |
|---|---|---|
| 频率 | Freq = CK_PSC / (PSC+1) / (ARR+1) |
PWM 波的周期 |
| 占空比 | Duty = CCR / (ARR+1) |
高电平占整个周期的比例 |
| 分辨率 | Res = 1 / (ARR+1) |
占空比的调节步长 |
示例:72 MHz,PSC = 719,ARR = 99
Freq = 72M / 720 / 100 = 1000 Hz = 1 kHz
Duty(CCR=50) = 50 / 100 = 50%
Res = 1/100 = 1%
使用定时器 PWM 输出时,GPIO 必须设置为 复用推挽输出(GPIO_Mode_AF_PP),因为引脚由外设控制而非 GPIO 模块直接控制。
void PWM_Init(void)
{
// 开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// GPIO 复用推挽输出
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // TIM2_CH1 默认在 PA0
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 内部时钟
TIM_InternalClockConfig(TIM2);
// 时基单元:PSC=719, ARR=99 → Freq=1kHz
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 输出比较配置:PWM 模式 1
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure); // 初始化结构体默认值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; // 初始 CCR=0
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
// 设置占空比
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); // Compare: 0~100 对应占空比 0%~100%
}
主函数:呼吸灯效果
int main(void)
{
PWM_Init();
while (1)
{
// 渐亮
for (uint8_t i = 0; i <= 100; i++)
{
PWM_SetCompare1(i);
Delay_ms(10);
}
// 渐暗
for (uint8_t i = 0; i <= 100; i++)
{
PWM_SetCompare1(100 - i);
Delay_ms(10);
}
}
}
// HAL库 — PWM 驱动 LED 呼吸灯
TIM_HandleTypeDef htim2;
void MX_TIM2_Init(void)
{
TIM_OC_InitTypeDef sConfigOC = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 720 - 1; // 72MHz/720 = 100kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 100 - 1; // 100kHz/100 = 1kHz
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_PWM_Init(&htim2);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0; // 初始 CCR=0
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
}
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (htim->Instance == TIM2) {
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
// 设置占空比(宏写法,与 GPIO 无关)
void PWM_SetCompare1(uint16_t Compare)
{
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, Compare);
}
与标准库对照:
| 标准库 | HAL 库 |
|--------|--------|
| TIM_OC1Init(TIM2, &...) | HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) |
| TIM_SetCompare1(TIM2, val) | __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, val) |
| GPIO_Mode_AF_PP | GPIO_MODE_AF_PP |
| TIM_Cmd(TIM2, ENABLE) | HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1) |
引脚重映射:如果 PA0 被占用,可以将 TIM2_CH1 重映射到其他引脚:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); // 部分重映射
// 或完全重映射参考手册确定映射关系
输入捕获(Input Capture)是定时器的输入功能,用于 测量外部信号的频率、周期、占空比。
外部信号 → TIMx_CH1(PA6) → 输入滤波器 → 边沿检测器 → ICx → 捕获 CCR
↑
触发信号(上升/下降沿)
当检测到指定边沿时,当前 CNT 的值被立即锁存到 CCR 寄存器,同时可以触发中断。
| 方法 | 原理 | 适用频率 |
|---|---|---|
| 测频法 | 固定时间 Ts 内统计脉冲个数 N,f = N/Ts | 高频信号 |
| 测周法 | 测量两个上升沿之间的周期 T,f = 1/T | 低频信号 |
最常用的输入捕获方式:从模式复位,即 TI1 的上升沿触发 CNT 复位。
第一个上升沿 第二个上升沿
│ │
外部信号 ───────┴──────── ────────┴────────
CNT 0────████████████████████████
↑ ↑
捕获到 CCR1=0 捕获到 CCR1=N
随即 CNT 归零 f = CK_CNT / N
void IC_Init(void)
{
// 开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// GPIO 上拉输入(TIM3_CH1 默认在 PA6)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 内部时钟
TIM_InternalClockConfig(TIM3);
// 时基单元:PSC=71 → CK_CNT = 1MHz(测周法使用)
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; // ARR 最大
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; // CK_CNT = 1MHz
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
// 输入捕获初始化:通道 1,上升沿,直通
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xF; // 最大滤波
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 捕获不分频
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
// 从模式复位:TI1FP1 上升沿触发 CNT 归零
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
// 启动定时器
TIM_Cmd(TIM3, ENABLE);
}
// 获取频率:f = CK_CNT / (CCR1 + 1)
uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}
双通道同时捕获:通道 1 测频率(上升沿触发复位),通道 2 测占空比(下降沿捕获)。
上升沿 下降沿 上升沿
│ │ │
──┘─────────┴──────┘──
↑ ↑
CH1 捕获 CCR1 CH1 捕获 CCR1(从模式复位,CNT 归零)
↑
CH2 捕获占用比(下降沿触发)
频率 f = CK_CNT / CCR1
占空比 Duty = CCR2 / CCR1
配置差别:将通道 2 设置为相反的极性(下降沿),选择交叉输入(TIM_ICSelection_IndirectTI)。
// HAL库 — 输入捕获测频率
TIM_HandleTypeDef htim3;
void MX_TIM3_IC_Init(void)
{
TIM_IC_InitTypeDef sConfigIC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 72 - 1; // 72MHz/72 = 1MHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 65536 - 1; // ARR 最大
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_IC_Init(&htim3);
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0xF;
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);
// 从模式复位:TI1FP1 上升沿触发 CNT 归零(HAL 无封装,直接用寄存器)
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
}
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (htim->Instance == TIM3) {
__HAL_RCC_TIM3_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
// 捕获回调:发生捕获时自动调用
uint32_t CapturedValue = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM3) {
CapturedValue = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
// 频率 f = 1000000 / (CapturedValue + 1)
}
}
PWMI 模式(测频率+占空比) HAL 库配置:
// 通道 1:上升沿,直通
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);
// 通道 2:下降沿,交叉输入
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_2);
// 从模式复位
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);
// 回调中读取:
// uint32_t Period = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
// uint32_t DutyPulse = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_2);
// 频率 f = CK_CNT / Period
// 占空比 = DutyPulse / Period
STM32 定时器的 编码器接口模式 可以直接连接 增量式旋转编码器(如 EC11) 的两路正交信号,自动完成正反转判断和计数。
与 EXTI 方式(前面 Note 1 中的 5-2 示例)相比:
编码器接法:TI1(PA6)接 A 相,TI2(PA7)接 B 相。
| 计数方向 | TI1 电平(相对 TI2) | TI2 电平(相对 TI1) |
|---|---|---|
| 正转(递增) | 超前 90° | 滞后 90° |
| 反转(递减) | 滞后 90° | 超前 90° |
定时器根据两路信号的边沿顺序自动判断方向,对 CNT 进行 递增或递减。
| 模式 | TI1 边沿 | TI2 边沿 | 单圈脉冲数 |
|---|---|---|---|
| TI1 | 计数 | 忽略 | 1 倍 |
| TI2 | 忽略 | 计数 | 1 倍 |
| TI1+TI2(推荐) | 计数 | 计数 | 2 倍 / 4 倍 |
4 倍频:TI1 和 TI2 的上升沿+下降沿都计数的模式。效果:编码器一圈 20 个脉冲 → 计数 80。
void Encoder_Init(void)
{
// 开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// GPIO 上拉输入:PA6(TIM3_CH1), PA7(TIM3_CH2)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 时基单元:PSC=0, ARR=最大(65535),自由运行
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; // 不分频
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
// 输入捕获初始化(为编码器模式做准备)
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xF; // 滤波去抖动
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
// 编码器模式配置(必须在输入捕获之后!)
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
// 注意:这里的 Rising/Falling 表示是否反相,而非真正的边沿
// 启动定时器
TIM_Cmd(TIM3, ENABLE);
}
// 获取增量值并清零
int16_t Encoder_Get(void)
{
int16_t Temp;
Temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3, 0);
return Temp;
}
测速主函数(结合定时中断,每隔固定时间读取一次):
int16_t Speed;
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Speed = Encoder_Get(); // 每 1 秒(或定时间隔)读一次增量值
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
int main(void)
{
OLED_Init();
Timer_Init(); // TIM2 每隔 1 秒中断一次
Encoder_Init(); // TIM3 编码器模式
while (1)
{
OLED_ShowSignedNum(1, 7, Speed, 5);
}
}
// HAL库 — 编码器接口测速
TIM_HandleTypeDef htim3;
TIM_Encoder_InitTypeDef sEncoder = {0};
void MX_TIM3_Encoder_Init(void)
{
htim3.Instance = TIM3;
htim3.Init.Prescaler = 1 - 1; // 不分频
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 65536 - 1; // ARR 最大,自由运行
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
sEncoder.EncoderMode = TIM_ENCODERMODE_TI12; // TI1+TI2 双沿计数
sEncoder.IC1Polarity = TIM_ICPOLARITY_RISING;
sEncoder.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sEncoder.IC1Prescaler = TIM_ICPSC_DIV1;
sEncoder.IC1Filter = 0xF;
sEncoder.IC2Polarity = TIM_ICPOLARITY_RISING;
sEncoder.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sEncoder.IC2Prescaler = TIM_ICPSC_DIV1;
sEncoder.IC2Filter = 0xF;
HAL_TIM_Encoder_Init(&htim3, &sEncoder);
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
}
void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (htim->Instance == TIM3) {
__HAL_RCC_TIM3_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
// 获取增量值并清零
int16_t Encoder_Get(void)
{
int16_t Temp = (int16_t)__HAL_TIM_GET_COUNTER(&htim3);
__HAL_TIM_SET_COUNTER(&htim3, 0);
return Temp;
}
注意:
HAL_TIM_Encoder_Init() 内部同时完成了时基单元 + 编码器配置 + 通道滤波,无需额外调用 TIM_ICInit。AutoReloadPreload 通常设为 DISABLE,因为需要 CNT 对 ARR 立即响应。__HAL_TIM_GET_COUNTER / __HAL_TIM_SET_COUNTER 是宏,代替标准库的 TIM_GetCounter / TIM_SetCounter。| 标准库 | HAL 库(宏/函数) | 用途 |
|---|---|---|
TIM_Cmd(TIMx, ENABLE/DISABLE) |
HAL_TIM_Base_Start/Stop(&htim) |
启动/停止定时器 |
TIM_InternalClockConfig(TIMx) |
HAL 初始化默认内部时钟 | 内部时钟 |
TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE) |
HAL_TIM_Base_Start_IT(&htim) |
使能更新中断 |
TIM_SetCounter(TIMx, Val) |
__HAL_TIM_SET_COUNTER(&htim, Val) |
写 CNT |
TIM_GetCounter(TIMx) |
__HAL_TIM_GET_COUNTER(&htim) |
读 CNT |
TIM_ClearFlag(TIMx, flag) |
__HAL_TIM_CLEAR_FLAG(&htim, flag) |
清除标志 |
| 标准库 | HAL 库(宏/函数) | 用途 |
|---|---|---|
TIM_SetCompare1(TIMx, Val) |
__HAL_TIM_SET_COMPARE(&htim, CH, Val) |
设置 CCR |
TIM_GetCompare1(TIMx) |
__HAL_TIM_GET_COMPARE(&htim, CH) |
读取 CCR |
TIM_OC1PreloadConfig(TIMx, ENABLE) |
sConfigOC.OCFastMode + AutoReloadPreload |
预装载 |
TIM_OCStructInit(&s) |
直接给 TIM_OC_InitTypeDef 成员赋值 |
初始化结构体 |
| 标准库 | HAL 库(宏/函数) | 用途 |
|---|---|---|
TIM_GetCapture1(TIMx) |
HAL_TIM_ReadCapturedValue(&htim, CH) |
读取捕获值 |
TIM_SelectInputTrigger(TIMx, src) |
寄存器直接操作(HAL 无封装) | 选择触发源 |
TIM_SelectSlaveMode(TIMx, mode) |
寄存器直接操作(HAL 无封装) | 从模式配置 |
| 标准库 | HAL 库 | 用途 |
|---|---|---|
TIM_EncoderInterfaceConfig(...) |
HAL_TIM_Encoder_Init(&htim, &sEncoder) |
配置编码器模式 |
TIM_ICInit(TIMx, &s) → TIM_EncoderInterfaceConfig(...) |
HAL_TIM_Encoder_Init() 一次完成 |
一步到位 |
| 函数 | 用途 |
|---|---|
TIM_InternalClockConfig(TIMx) |
内部时钟(CK_INT) |
TIM_ITRxExternalClockConfig(TIMx, TIM_TS_ITR0~3) |
其他定时器输出作为时钟 |
TIM_TIxExternalClockConfig(TIMx, ...) |
外部引脚的时钟 |
TIM_ETRClockMode2Config(TIMx, ...) |
ETR 引脚外部时钟 |
T = (PSC+1)×(ARR+1) / CK_PSC症状:中断持续触发,主循环无法正常运行。
原因:中断标志位没有清除。
解决:
TIM_ClearITPendingBit(TIMx, TIM_IT_Update);
// 或 EXTI 中断:
EXTI_ClearITPendingBit(EXTI_LineX);
TIM_TimeBaseInit() 函数末尾会手动产生一次更新事件(UG 位)。如果在初始化之前开启了中断,CNT=0 时的 ARR=0 状态也会导致立即触发。
解决:
TIM_ClearFlag(TIMx, TIM_FLAG_Update); // 在 TIM_ITConfig 之前清除
必须先调用 TIM_ICInit() 初始化通道,再调用 TIM_EncoderInterfaceConfig() 配置编码器模式。反过来会使得输入捕获的配置覆盖编码器模式。
如果 TIM_OCInitStructure 中的某些成员(如 TIM_OCIdleState, TIM_OCNIdleState)没有显式赋值,在高级定时器上可能会得到意外结果。使用 TIM_OCStructInit() 可以给所有成员赋默认值,避免不确定行为。