返回博客
STM32 定时器全解:输入捕获、PWM 输出与编码器模式

STM32 定时器全解:输入捕获、PWM 输出与编码器模式

2025年4月1日STM32, 定时器, PWM

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);