STM32F1定时器实战:从呼吸灯到电机控制,手把手教你玩转TIM模块
本文详细介绍了STM32F1定时器(TIM)模块的实战应用,从基础的呼吸灯PWM控制到高级的电机闭环控制。通过代码示例和原理分析,手把手教你实现舵机角度控制、直流电机调速及PID算法应用,帮助开发者全面掌握STM32F1定时器的强大功能。
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);
}
控制逻辑流程图:
- 初始化所有外设
- 读取传感器数据(编码器、超声波等)
- 根据控制算法计算目标速度
- PID控制调整电机输出
- 返回步骤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中保存校准值,可以有效解决这个问题。
更多推荐



所有评论(0)