TIM 时基单元结构
STM32F103 的通用定时器(TIM2-TIM5)和高级定时器(TIM1/TIM8)包含时基单元、输入捕获、输出比较、编码器接口等功能。时基单元由预分频器(PSC, Prescaler)、自动重载寄存器(ARR, Auto-Reload Register)、计数器(CNT, Counter)组成。
时基单元工作原理:
- 时钟源经 PSC 分频后驱动 CNT 计数
- CNT 计数到 ARR 值时产生更新事件(UEV)
- 更新事件可触发中断、DMA 请求或重载计数器
定时器频率计算:
定时器时钟 = APB1/APB2 时钟 × 倍频系数(若 APB 预分频≠1)
计数器时钟 = 定时器时钟 / (PSC + 1)
定时周期 = (ARR + 1) / 计数器时钟
STM32F103C8T6 系统时钟 72MHz,APB1 时钟 36MHz(APB1 预分频 2),TIM2-5 时钟 72MHz(倍频)。
计数模式
STM32 定时器支持三种计数模式:向上计数、向下计数、中央对齐计数。
向上计数模式
CNT 从 0 计数到 ARR,到达 ARR 时产生更新事件并重置为 0。
TIM_HandleTypeDef htim2;
void MX_TIM2_Init(void) {
htim2.Instance = TIM2;
htim2.Init.Prescaler = 7199; // 72MHz / 7200 = 10kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 9999; // 10kHz / 10000 = 1Hz
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htim2);
HAL_TIM_Base_Start(&htim2);
}
向下计数模式
CNT 从 ARR 计数到 0,到达 0 时产生更新事件并重载为 ARR。
htim2.Init.CounterMode = TIM_COUNTERMODE_DOWN;
中央对齐计数模式
CNT 从 0 计数到 ARR,再从 ARR 计数到 0,完成一个周期。适用于 PWM 生成,减少谐波。
htim2.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;
// CENTERALIGNED1/2/3 区别在于更新事件触发时机
PWM 输出模式
PWM(Pulse Width Modulation,脉冲宽度调制)通过改变占空比控制输出电压。STM32 定时器支持 PWM1 和 PWM2 两种模式。
PWM1 模式
CNT < CCRx 时输出有效电平,CNT ≥ CCRx 时输出无效电平。
TIM_OC_InitTypeDef sConfigOC = {0};
void TIM2_PWM_Init(void) {
MX_TIM2_Init();
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0; // 初始占空比 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);
}
// 设置占空比(0-100%)
void TIM2_Set_Duty(uint16_t duty_percent) {
uint16_t pulse = (htim2.Init.Period + 1) * duty_percent / 100;
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse);
}
PWM2 模式
CNT ≤ CCRx 时输出无效电平,CNT > CCRx 时输出有效电平。
sConfigOC.OCMode = TIM_OCMODE_PWM2;
互补输出与死区
高级定时器(TIM1/TIM8)支持互补输出,用于驱动半桥/全桥电路。死区时间防止上下桥臂直通。
TIM_HandleTypeDef htim1;
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
void TIM1_Complementary_PWM_Init(void) {
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 719; // 72MHz / 720 = 100kHz PWM
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
HAL_TIM_PWM_Init(&htim1);
// 配置通道 1 互补输出
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 360; // 50% 占空比
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
// 配置死区时间
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_1;
sBreakDeadTimeConfig.DeadTime = 10; // 死区时间
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);
}
死区时间计算:
死区时间 = DTG[7:0] × Tdtg
Tdtg = 系统时钟 / 死区时钟分频
占空比计算
占空比 = CCRx / (ARR + 1) × 100%
// 根据目标电压计算占空比(3.3V 系统)
float voltage = 1.65f; // 目标电压 1.65V
uint16_t duty = (voltage / 3.3f) * 100;
TIM2_Set_Duty(duty);
输入捕获模式
输入捕获用于测量外部信号频率、周期、脉宽。TIMx_CHx 引脚检测边沿时,CNT 值锁存到 CCRx 寄存器。
测量频率
TIM_HandleTypeDef htim3;
volatile uint32_t capture_value = 0;
volatile uint32_t last_capture_value = 0;
volatile uint32_t frequency = 0;
void TIM3_IC_Init(void) {
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71; // 72MHz / 72 = 1MHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFFFFFF; // 最大计数值
HAL_TIM_IC_Init(&htim3);
TIM_IC_InitTypeDef sConfigIC = {0};
sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM3) {
capture_value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
if (capture_value > last_capture_value) {
uint32_t period = capture_value - last_capture_value;
frequency = 1000000 / period; // 1MHz 时钟
}
last_capture_value = capture_value;
}
}
测量脉宽
使用双边沿捕获测量脉宽:
volatile uint32_t pulse_width = 0;
volatile uint8_t capture_state = 0;
void TIM3_Pulse_Init(void) {
TIM3_IC_Init();
// 配置通道 1 为上升沿捕获
TIM3->CCER &= ~TIM_CCER_CC1P;
capture_state = 0;
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM3) {
if (capture_state == 0) {
// 上升沿,记录起始时间
last_capture_value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
capture_state = 1;
// 切换为下降沿捕获
TIM3->CCER |= TIM_CCER_CC1P;
} else {
// 下降沿,计算脉宽
capture_value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
pulse_width = capture_value - last_capture_value;
capture_state = 0;
// 切换为上升沿捕获
TIM3->CCER &= ~TIM_CCER_CC1P;
}
}
}
外部时钟模式
外部时钟模式允许外部信号驱动定时器计数,用于脉冲计数、频率测量。
ETR 模式(外部触发)
ETR(External Trigger)引脚(TIMx_ETR)作为外部时钟源。
TIM_HandleTypeDef htim4;
void TIM4_ETR_Init(void) {
htim4.Instance = TIM4;
htim4.Init.Prescaler = 0;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 0xFFFFFFFF;
HAL_TIM_Base_Init(&htim4);
// 配置外部触发模式
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
sSlaveConfig.InputTrigger = TIM_TS_ETRF;
sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
sSlaveConfig.TriggerFilter = 0;
HAL_TIM_SlaveConfigSynchro(&htim4, &sSlaveConfig);
HAL_TIM_Base_Start(&htim4);
}
// 读取计数值
uint32_t TIM4_Get_Count(void) {
return __HAL_TIM_GET_COUNTER(&htim4);
}
TI1 模式(通道 1 作为外部时钟)
void TIM4_TI1_Init(void) {
TIM4_ETR_Init();
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
sSlaveConfig.TriggerFilter = 0;
HAL_TIM_SlaveConfigSynchro(&htim4, &sSlaveConfig);
}
编码器模式
编码器接口用于连接增量式光电编码器,测量位置、速度、方向。支持 TI1、TI2、TI12 三种模式。
TI12 模式(双相检测)
TIM_HandleTypeDef htim2;
void TIM2_Encoder_Init(void) {
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFF; // 16 位计数器
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_Encoder_Init(&htim2);
TIM_Encoder_InitTypeDef sConfig = {0};
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Filter = 0;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 0;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
HAL_TIM_Encoder_Config(&htim2, &sConfig);
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
}
// 读取编码器计数值
int16_t TIM2_Get_Encoder_Count(void) {
return (int16_t)__HAL_TIM_GET_COUNTER(&htim2);
}
// 读取方向
uint8_t TIM2_Get_Direction(void) {
return __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2);
}
编码器参数计算
编码器线数(PPR,Pulses Per Revolution)与计数值关系:
#define ENCODER_PPR 1000 // 编码器每转 1000 脉冲
// 计算转速(RPM)
float Calculate_RPM(int16_t delta_count, uint32_t time_ms) {
float rpm = (delta_count / (float)ENCODER_PPR) * 60000.0f / time_ms;
return rpm;
}
// 计算角度
float Calculate_Angle(int16_t count) {
float angle = (count % ENCODER_PPR) * 360.0f / ENCODER_PPR;
return angle;
}
完整应用示例
以下代码实现 PWM 输出和编码器读取的完整应用:
#include "stm32f1xx_hal.h"
TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;
volatile int16_t encoder_count = 0;
volatile uint32_t last_encoder_count = 0;
volatile float motor_speed = 0.0f;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM1_Init(void);
static void MX_TIM2_Init(void);
static void MX_TIM3_Init(void);
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
MX_TIM2_Init();
MX_TIM3_Init();
// 启动 PWM
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
// 启动编码器
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
// 启动定时器 3 用于周期计算速度
HAL_TIM_Base_Start_IT(&htim3);
while (1) {
// PID 控制电机速度
float target_speed = 1000.0f; // 目标转速 RPM
float error = target_speed - motor_speed;
float pwm_output = error * 0.5f; // 简单 P 控制
if (pwm_output > 100) pwm_output = 100;
if (pwm_output < 0) pwm_output = 0;
TIM1_Set_Duty((uint16_t)pwm_output);
HAL_Delay(10);
}
}
void TIM3_IRQHandler(void) {
HAL_TIM_IRQHandler(&htim3);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM3) {
// 每 100ms 计算一次速度
int16_t delta = encoder_count - last_encoder_count;
motor_speed = (delta / 1000.0f) * 600.0f; // PPR=1000, 100ms 周期
last_encoder_count = encoder_count;
}
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
encoder_count = (int16_t)__HAL_TIM_GET_COUNTER(htim);
}
}
static void MX_TIM1_Init(void) {
TIM_OC_InitTypeDef sConfigOC = {0};
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 719;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
HAL_TIM_PWM_Init(&htim1);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
}
static void MX_TIM2_Init(void) {
TIM_Encoder_InitTypeDef sConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFF;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_Encoder_Init(&htim2);
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Filter = 6;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 6;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
HAL_TIM_Encoder_Config(&htim2, &sConfig);
}
static void MX_TIM3_Init(void) {
htim3.Instance = TIM3;
htim3.Init.Prescaler = 7199;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 99;
HAL_TIM_Base_Init(&htim3);
}
void TIM1_Set_Duty(uint16_t duty_percent) {
uint16_t pulse = (htim1.Init.Period + 1) * duty_percent / 100;
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pulse);
}
常见问题
PWM 输出异常
原因:GPIO 配置错误或定时器时钟未使能。
解决:检查 GPIO 复用功能配置,确保 TIMx 时钟使能。
// GPIO 复用功能配置
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_8; // TIM1_CH1
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
编码器计数不准确
原因:滤波设置不当或信号质量差。
解决:调整 ICFilter 参数(0-15),值越大滤波越强但延迟增加。
sConfig.IC1Filter = 6; // 适中滤波
定时器中断不触发
原因:中断优先级冲突或 NVIC 未使能。
解决:检查 NVIC 配置,确保中断优先级合理。
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);