STM32F1定时器实战:从呼吸灯到电机控制,手把手教你玩转TIM模块

1. 初识STM32F1定时器:从理论到实践的跨越

在嵌入式开发领域,STM32F1系列微控制器因其出色的性能和丰富的外设资源广受欢迎。其中,定时器(TIM)模块堪称最强大的外设之一,它不仅能完成基本的定时功能,还能实现PWM输出、输入捕获、编码器接口等高级功能。但很多开发者在学习过程中常常陷入理论知识的泥潭,无法将手册上的寄存器描述转化为实际可用的代码。

定时器的本质是一个自动运行的计数器,它通过对时钟信号进行计数来实现各种时间相关的功能。STM32F1的定时器分为三种类型:

  • 基本定时器(TIM6/TIM7):最简单的定时器,仅支持向上计数和定时中断
  • 通用定时器(TIM2-TIM5):支持PWM输出、输入捕获、编码器接口等功能
  • 高级定时器(TIM1/TIM8):在通用定时器基础上增加了互补输出、死区控制等电机控制专用功能
// 定时器时钟源选择示例代码
void TIM_ClockConfig(void) {
    // 选择内部时钟源(默认)
    TIM_InternalClockConfig(TIM2);
    
    // 或者选择外部时钟模式1
    TIM_TIxExternalClockConfig(TIM2, TIM_TIxExternalCLKSource_TI1, 
                             TIM_ICPolarity_Rising, 0x00);
}

实际开发中,我们最常用的是通用定时器,因为它功能全面且资源丰富。以TIM2为例,它拥有:

  • 16位自动重装载计数器
  • 16位可编程预分频器(分频系数1-65536)
  • 4个独立通道,可用于输入捕获/输出比较
  • 支持向上、向下、中央对齐计数模式

提示:STM32F103C8T6有4个通用定时器(TIM2-TIM5)和1个高级定时器(TIM1),合理规划定时器资源对复杂项目至关重要。

2. 呼吸灯实战:PWM的艺术

呼吸灯是展示PWM技术最直观的案例,通过调节LED的亮度变化模拟呼吸效果。其核心原理是利用PWM的占空比控制LED的平均电流,从而实现亮度渐变。

**PWM(脉宽调制)**是一种通过调节脉冲宽度来控制模拟信号的技术。在STM32中,PWM由定时器的输出比较功能实现,关键参数包括:

参数 说明 计算公式
频率 PWM波形的周期倒数 Fpwm = Fclock / (PSC+1) / (ARR+1)
占空比 高电平时间占周期的比例 Duty = CCR / (ARR+1)
分辨率 占空比最小变化量 1 / (ARR+1)
// PWM初始化代码示例
void PWM_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
    
    // 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置GPIO为复用推挽输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; // TIM2_CH1
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 定时器基础配置
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
    TIM_TimeBaseInitStruct.TIM_Period = 999; // ARR = 999
    TIM_TimeBaseInitStruct.TIM_Prescaler = 71; // 72MHz/(71+1) = 1MHz
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
    
    // PWM模式配置
    TIM_OC_InitStruct.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OC_InitStruct.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OC_InitStruct.TIM_Pulse = 0; // 初始占空比0%
    TIM_OC_InitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM2, &TIM_OC_InitStruct);
    
    TIM_Cmd(TIM2, ENABLE);
}

实现呼吸灯效果的关键是动态调整CCR值,创建一个渐变循环:

void Breath_LED_Effect(void) {
    uint16_t i;
    while(1) {
        // 渐亮
        for(i=0; i<1000; i++) {
            TIM_SetCompare1(TIM2, i);
            Delay_ms(1);
        }
        // 渐暗
        for(i=1000; i>0; i--) {
            TIM_SetCompare1(TIM2, i);
            Delay_ms(1);
        }
    }
}

注意:PWM频率不宜过低,否则会出现闪烁现象。对于LED控制,通常选择200Hz-1kHz的频率范围。

3. 舵机控制:精准的角度定位

舵机是一种位置伺服机构,通过PWM信号的脉冲宽度来控制输出轴的角度。标准舵机的控制信号要求:

  • 周期:20ms (50Hz)
  • 脉冲宽度:0.5ms-2.5ms 对应 0°-180°
// 舵机初始化
void Servo_Init(void) {
    // 定时器配置(产生50Hz PWM)
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    TIM_TimeBaseStruct.TIM_Period = 19999; // ARR = 19999
    TIM_TimeBaseStruct.TIM_Prescaler = 71; // 72MHz/72 = 1MHz
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
    
    // PWM配置
    TIM_OC_InitTypeDef TIM_OC_InitStruct;
    TIM_OC_InitStruct.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OC_InitStruct.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OC_InitStruct.TIM_Pulse = 1500; // 初始位置90°
    TIM_OC_InitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM2, &TIM_OC_InitStruct);
    
    TIM_Cmd(TIM2, ENABLE);
}

// 设置舵机角度(0-180°)
void Servo_SetAngle(uint8_t angle) {
    uint16_t pulse = 500 + angle * 2000 / 180; // 映射到500-2500
    TIM_SetCompare1(TIM2, pulse);
}

实际项目中,我们通常会将舵机控制封装成模块:

typedef struct {
    TIM_TypeDef* TIMx;
    uint16_t channel;
    uint16_t min_pulse;
    uint16_t max_pulse;
} Servo_HandleTypeDef;

void Servo_Init(Servo_HandleTypeDef *hservo) {
    // 初始化代码...
}

void Servo_Write(Servo_HandleTypeDef *hservo, float angle) {
    float pulse = hservo->min_pulse + angle * (hservo->max_pulse - hservo->min_pulse) / 180.0f;
    switch(hservo->channel) {
        case 1: TIM_SetCompare1(hservo->TIMx, (uint16_t)pulse); break;
        case 2: TIM_SetCompare2(hservo->TIMx, (uint16_t)pulse); break;
        // 其他通道...
    }
}

提示:不同品牌舵机的脉冲范围可能略有差异,实际使用前应查阅规格书进行调整。有些舵机支持更宽的脉冲范围(如500-2500μs),可提供更大的转动角度。

4. 直流电机控制:从调速到闭环

直流电机控制是定时器的高级应用,通过PWM调节电机转速,配合驱动电路实现正反转控制。常用的电机驱动芯片有L298N、TB6612等。

4.1 电机驱动基础

TB6612是双路H桥驱动芯片,主要特点:

  • 最大电流1.2A(持续)/3.2A(峰值)
  • 内置过热保护和低压检测
  • 支持PWM调速和方向控制

接线示意图

STM32 GPIO ---- TB6612
PA4          AIN1
PA5          AIN2
PA6          PWMA
// 电机驱动初始化
void Motor_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    
    // 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置控制引脚
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // PWM初始化(1kHz)
    PWM_Init(GPIO_Pin_6, TIM3, TIM_Channel_1, 1000);
}

// 设置电机速度和方向
void Motor_SetSpeed(int16_t speed) {
    if(speed >= 0) {
        // 正转
        GPIO_SetBits(GPIOA, GPIO_Pin_4);
        GPIO_ResetBits(GPIOA, GPIO_Pin_5);
    } else {
        // 反转
        GPIO_ResetBits(GPIOA, GPIO_Pin_4);
        GPIO_SetBits(GPIOA, GPIO_Pin_5);
        speed = -speed;
    }
    PWM_SetDuty(TIM3, TIM_Channel_1, speed);
}

4.2 编码器接口与速度测量

要实现闭环控制,需要实时获取电机转速。正交编码器是常用的速度检测装置,STM32的定时器内置编码器接口,可直接连接编码器。

// 编码器接口初始化
void Encoder_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    
    // 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    // 配置编码器输入引脚(PB6-PB7)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
    
    // 定时器基础配置
    TIM_TimeBaseStruct.TIM_Period = 65535;
    TIM_TimeBaseStruct.TIM_Prescaler = 0;
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStruct);
    
    // 编码器接口配置
    TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, 
                             TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    
    TIM_Cmd(TIM4, ENABLE);
}

// 获取速度值(每秒钟的脉冲数)
int16_t Encoder_GetSpeed(void) {
    static int16_t last_count = 0;
    int16_t current_count = TIM_GetCounter(TIM4);
    int16_t speed = current_count - last_count;
    last_count = current_count;
    return speed;
}

4.3 PID速度控制

结合编码器反馈和PWM输出,可以实现电机的闭环速度控制。PID是工业控制中最常用的算法:

typedef struct {
    float Kp, Ki, Kd;
    float integral;
    float last_error;
} PID_HandleTypeDef;

float PID_Calculate(PID_HandleTypeDef *pid, float setpoint, float input) {
    float error = setpoint - input;
    
    // 比例项
    float P = pid->Kp * error;
    
    // 积分项(抗积分饱和)
    pid->integral += error;
    if(pid->integral > 1000) pid->integral = 1000;
    else if(pid->integral < -1000) pid->integral = -1000;
    float I = pid->Ki * pid->integral;
    
    // 微分项
    float D = pid->Kd * (error - pid->last_error);
    pid->last_error = error;
    
    return P + I + D;
}

void Motor_Control_Loop(void) {
    PID_HandleTypeDef pid = {0.5, 0.01, 0.1, 0, 0};
    float target_speed = 1000; // 目标速度(脉冲/秒)
    
    while(1) {
        float current_speed = Encoder_GetSpeed();
        float output = PID_Calculate(&pid, target_speed, current_speed);
        
        // 限制输出范围
        if(output > 1000) output = 1000;
        else if(output < -1000) output = -1000;
        
        Motor_SetSpeed((int16_t)output);
        Delay_ms(10);
    }
}

注意:PID参数需要根据具体电机和负载进行调整。实际项目中,通常会实现参数自整定功能或提供上位机调试接口。

5. 高级应用技巧与性能优化

5.1 定时器级联

对于需要超长定时的应用,可以通过主从模式将多个定时器级联:

void Timer_Cascade(void) {
    // 主定时器(TIM2)配置
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    TIM_TimeBaseStruct.TIM_Period = 999;
    TIM_TimeBaseStruct.TIM_Prescaler = 7199; // 72MHz/7200 = 10kHz
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
    
    // 配置主定时器TRGO输出
    TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
    
    // 从定时器(TIM3)配置
    TIM_TimeBaseStruct.TIM_Period = 65535;
    TIM_TimeBaseStruct.TIM_Prescaler = 0;
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct);
    
    // 配置从模式
    TIM_SelectInputTrigger(TIM3, TIM_TS_ITR1); // TIM2作为TIM3的触发源
    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Gated);
    
    TIM_Cmd(TIM2, ENABLE);
    TIM_Cmd(TIM3, ENABLE);
}

5.2 输入捕获测量频率

定时器的输入捕获功能可以精确测量外部信号的频率和占空比:

void InputCapture_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_ICInitTypeDef TIM_ICInitStruct;
    
    // 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置输入引脚(PA0)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 定时器基础配置
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    TIM_TimeBaseStruct.TIM_Period = 0xFFFF;
    TIM_TimeBaseStruct.TIM_Prescaler = 71; // 1MHz计数频率
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStruct);
    
    // 输入捕获配置(PWMI模式)
    TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStruct.TIM_ICFilter = 0x0F;
    TIM_PWMIConfig(TIM5, &TIM_ICInitStruct);
    
    // 从模式配置
    TIM_SelectInputTrigger(TIM5, TIM_TS_TI1FP1);
    TIM_SelectSlaveMode(TIM5, TIM_SlaveMode_Reset);
    
    TIM_Cmd(TIM5, ENABLE);
}

// 获取输入信号频率(Hz)
float IC_GetFrequency(void) {
    uint32_t ic1value = TIM_GetCapture1(TIM5);
    if(ic1value == 0) return 0;
    return 1000000.0f / ic1value; // 1MHz时钟
}

// 获取输入信号占空比(%)
float IC_GetDutyCycle(void) {
    uint32_t ic1value = TIM_GetCapture1(TIM5);
    uint32_t ic2value = TIM_GetCapture2(TIM5);
    if(ic1value == 0) return 0;
    return (ic2value * 100.0f) / ic1value;
}

5.3 定时器中断优化

合理使用定时器中断可以提高系统响应效率:

// 定时器中断优先级配置
void TIM_IRQ_Config(void) {
    NVIC_InitTypeDef NVIC_InitStruct;
    
    // 设置优先级分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    // 配置TIM2中断
    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
    
    // 使能定时器中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
}

// 定时器中断服务函数
void TIM2_IRQHandler(void) {
    if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        // 处理定时任务...
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

提示:对于时间要求严格的任务,应使用更高优先级的定时器(如TIM1)。同时,中断服务函数应尽可能简短,避免影响系统实时性。

6. 实战项目:智能小车控制系统

综合运用前述技术,我们可以构建一个完整的智能小车控制系统:

系统架构

  • 主控制器:STM32F103C8T6
  • 电机驱动:TB6612 ×2
  • 传感器:编码器 ×2,超声波模块,红外循迹模块
  • 通信:蓝牙模块(HC-05)
// 小车控制核心代码
typedef struct {
    int16_t left_speed;
    int16_t right_speed;
    PID_HandleTypeDef left_pid;
    PID_HandleTypeDef right_pid;
} Car_HandleTypeDef;

void Car_Init(Car_HandleTypeDef *hcar) {
    // 初始化各外设
    Motor_Init();
    Encoder_Init();
    PWM_Init();
    
    // 初始化PID参数
    hcar->left_pid = (PID_HandleTypeDef){0.5, 0.02, 0.1, 0, 0};
    hcar->right_pid = (PID_HandleTypeDef){0.5, 0.02, 0.1, 0, 0};
}

void Car_SetSpeed(Car_HandleTypeDef *hcar, int16_t linear, int16_t angular) {
    // 转换为左右轮速
    hcar->left_speed = linear - angular;
    hcar->right_speed = linear + angular;
}

void Car_Update(Car_HandleTypeDef *hcar) {
    // 获取当前编码器值
    int16_t left_actual = Encoder_GetSpeed(LEFT);
    int16_t right_actual = Encoder_GetSpeed(RIGHT);
    
    // PID计算
    float left_output = PID_Calculate(&hcar->left_pid, hcar->left_speed, left_actual);
    float right_output = PID_Calculate(&hcar->right_pid, hcar->right_speed, right_actual);
    
    // 设置电机输出
    Motor_SetSpeed(LEFT, (int16_t)left_output);
    Motor_SetSpeed(RIGHT, (int16_t)right_output);
}

控制逻辑流程图

  1. 初始化所有外设
  2. 读取传感器数据(编码器、超声波等)
  3. 根据控制算法计算目标速度
  4. PID控制调整电机输出
  5. 返回步骤2
// 主控制循环
void Main_Control_Loop(void) {
    Car_HandleTypeDef my_car;
    Car_Init(&my_car);
    
    while(1) {
        // 读取遥控指令或自主决策
        int16_t speed = Get_Remote_Speed();
        int16_t steer = Get_Remote_Steer();
        
        // 设置小车速度
        Car_SetSpeed(&my_car, speed, steer);
        
        // 更新控制
        Car_Update(&my_car);
        
        // 系统延时
        Delay_ms(10);
    }
}

在实际调试中发现,电机参数不对称会导致小车跑偏。通过分别调整左右电机的PID参数,并在EEPROM中保存校准值,可以有效解决这个问题。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐