嵌入式开发必掌握:定时器与PWM实战精讲(精确定时+电机控制+编码器应用)

嵌入式开发必掌握:定时器与PWM实战精讲(精确定时+电机控制+编码器应用)

标签:嵌入式开发、定时器、PWM、STM32、电机控制、编码器、输入捕获、精确定时、底层驱动

前言

摘要:定时器是嵌入式系统的心脏,PWM是电机控制的核心。本文从定时器工作原理、精确定时配置、PWM输出控制、输入捕获测速、编码器接口应用到电机闭环控制实战,提供全套可直接量产的驱动代码,同时总结定时器配置常见踩坑、PWM频率精度优化、编码器信号抗干扰方案,帮助开发者掌握高效可靠的定时器与PWM编程。

定时器是MCU最基础也是最重要的外设之一,广泛应用于精确定时、PWM控制、脉冲测量、编码器接口等场景。看似简单的定时器配置,实际开发中却容易遇到定时精度不够、PWM频率偏差、编码器计数错误等问题。

文章主要内容

  • 定时器工作原理与架构
  • 精确定时配置方法
  • PWM输出控制实战
  • 输入捕获测速应用
  • 编码器接口应用
  • 电机控制实战项目

一、定时器工作原理与架构

1.1 STM32定时器分类

STM32定时器分为三类:

STM32定时器

高级定时器
TIM1/TIM8

通用定时器
TIM2-TIM5

基本定时器
TIM6/TIM7

4通道PWM

死区控制

编码器接口

4通道PWM

输入捕获

编码器接口

简单定时

DAC触发

定时器对比表

类型计数器位宽通道数特殊功能典型应用
高级定时器16位4死区、刹车、编码器电机控制、PWM
通用定时器16/32位4输入捕获、编码器定时、PWM、测速
基本定时器16位0DAC触发简单定时

1.2 定时器核心寄存器

typedef struct
{
    volatile uint32_t CR1;
    volatile uint32_t CR2;
    volatile uint32_t SMCR;
    volatile uint32_t DIER;
    volatile uint32_t SR;
    volatile uint32_t EGR;
    volatile uint32_t CCMR1;
    volatile uint32_t CCMR2;
    volatile uint32_t CCER;
    volatile uint32_t CNT;
    volatile uint32_t PSC;
    volatile uint32_t ARR;
    volatile uint32_t RCR;
    volatile uint32_t CCR1;
    volatile uint32_t CCR2;
    volatile uint32_t CCR3;
    volatile uint32_t CCR4;
    volatile uint32_t BDTR;
    volatile uint32_t DCR;
    volatile uint32_t DMAR;
} TIM_TypeDef;

关键寄存器说明

寄存器作用说明
PSC预分频器时钟分频系数
ARR自动重装载值定时周期
CNT计数器当前值实时计数值
CCRx捕获/比较值PWM占空比
DIER中断使能配置中断源

1.3 定时器时钟源

时钟源选择

typedef enum {
    TIM_CLOCKSOURCE_INTERNAL = 0x00,
    TIM_CLOCKSOURCE_ITR0     = 0x01,
    TIM_CLOCKSOURCE_ITR1     = 0x02,
    TIM_CLOCKSOURCE_ITR2     = 0x03,
    TIM_CLOCKSOURCE_TI1ED    = 0x04,
    TIM_CLOCKSOURCE_TI1      = 0x05,
    TIM_CLOCKSOURCE_TI2      = 0x06,
    TIM_CLOCKSOURCE_ETRMODE1 = 0x07,
    TIM_CLOCKSOURCE_ETRMODE2 = 0x08
} tim_clocksource_t;

二、精确定时配置方法

2.1 定时周期计算

定时周期公式

T = (PSC + 1) × (ARR + 1) / Fclk

其中:

  • T:定时周期(秒)
  • PSC:预分频系数
  • ARR:自动重装载值
  • Fclk:定时器时钟频率

示例:1ms定时配置(系统时钟72MHz):

void tim2_1ms_init(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    
    TIM2->PSC = 72 - 1;
    
    TIM2->ARR = 1000 - 1;
    
    TIM2->DIER |= TIM_DIER_UIE;
    
    TIM2->CR1 |= TIM_CR1_CEN;
    
    NVIC_SetPriority(TIM2_IRQn, 5);
    NVIC_EnableIRQ(TIM2_IRQn);
}

2.2 微秒级精确定时

微秒延时函数

void delay_us(uint32_t us)
{
    uint32_t start = TIM2->CNT;
    uint32_t wait = us;
    
    while ((TIM2->CNT - start) < wait) {
    }
}

高精度定时器配置

void tim2_us_timer_init(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    
    TIM2->PSC = 72 - 1;
    
    TIM2->ARR = 0xFFFFFFFF;
    
    TIM2->CR1 |= TIM_CR1_CEN;
}

2.3 定时器中断处理

中断服务函数

volatile uint32_t timer_count = 0;

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF) {
        TIM2->SR = ~TIM_SR_UIF;
        
        timer_count++;
        
        if (timer_count % 1000 == 0) {
            led_toggle();
        }
    }
}

多定时任务管理

typedef struct {
    uint32_t period;
    uint32_t counter;
    void (*handler)(void);
    uint8_t enable;
} timer_task_t;

#define MAX_TIMER_TASKS  10

static timer_task_t timer_tasks[MAX_TIMER_TASKS];

int register_timer_task(uint32_t period, void (*handler)(void))
{
    for (int i = 0; i < MAX_TIMER_TASKS; i++) {
        if (!timer_tasks[i].enable) {
            timer_tasks[i].period = period;
            timer_tasks[i].counter = 0;
            timer_tasks[i].handler = handler;
            timer_tasks[i].enable = 1;
            return i;
        }
    }
    return -1;
}

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF) {
        TIM2->SR = ~TIM_SR_UIF;
        
        for (int i = 0; i < MAX_TIMER_TASKS; i++) {
            if (timer_tasks[i].enable) {
                timer_tasks[i].counter++;
                
                if (timer_tasks[i].counter >= timer_tasks[i].period) {
                    timer_tasks[i].counter = 0;
                    if (timer_tasks[i].handler) {
                        timer_tasks[i].handler();
                    }
                }
            }
        }
    }
}

三、PWM输出控制实战

3.1 PWM原理

PWM(脉冲宽度调制):通过改变脉冲宽度控制平均输出电压。

周期T

高电平时间
Ton

低电平时间
T-Ton

占空比

Duty = Ton/T × 100%

PWM频率与占空比

频率 = Fclk / ((PSC + 1) × (ARR + 1))
占空比 = CCRx / (ARR + 1) × 100%

3.2 PWM输出配置

基础PWM配置

void pwm_init(TIM_TypeDef *tim, uint32_t channel, 
              uint32_t frequency, uint32_t duty_cycle)
{
    uint32_t psc = 72 - 1;
    uint32_t arr = (72000000 / (psc + 1) / frequency) - 1;
    uint32_t ccr = (arr + 1) * duty_cycle / 100;
    
    if (tim == TIM1) RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
    else if (tim == TIM2) RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    else if (tim == TIM3) RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
    
    tim->PSC = psc;
    tim->ARR = arr;
    
    uint32_t shift = (channel - 1) * 8;
    if (channel <= 2) {
        tim->CCMR1 &= ~(0xFF << shift);
        tim->CCMR1 |= (0x68 << shift);
    } else {
        shift = (channel - 3) * 8;
        tim->CCMR2 &= ~(0xFF << shift);
        tim->CCMR2 |= (0x68 << shift);
    }
    
    tim->CCER |= (1 << ((channel - 1) * 4));
    
    if (tim == TIM1) {
        tim->BDTR |= TIM_BDTR_MOE;
    }
    
    tim->CCR1 = (channel == 1) ? ccr : tim->CCR1;
    tim->CCR2 = (channel == 2) ? ccr : tim->CCR2;
    tim->CCR3 = (channel == 3) ? ccr : tim->CCR3;
    tim->CCR4 = (channel == 4) ? ccr : tim->CCR4;
    
    tim->CR1 |= TIM_CR1_CEN;
}

3.3 PWM占空比调节

动态调节占空比

void pwm_set_duty(TIM_TypeDef *tim, uint32_t channel, uint32_t duty_cycle)
{
    uint32_t arr = tim->ARR;
    uint32_t ccr = (arr + 1) * duty_cycle / 100;
    
    switch (channel) {
        case 1:
            tim->CCR1 = ccr;
            break;
        case 2:
            tim->CCR2 = ccr;
            break;
        case 3:
            tim->CCR3 = ccr;
            break;
        case 4:
            tim->CCR4 = ccr;
            break;
    }
}

呼吸灯效果

void breathing_led(void)
{
    static uint8_t brightness = 0;
    static uint8_t direction = 1;
    
    brightness += direction * 2;
    
    if (brightness >= 100) {
        direction = 0;
    } else if (brightness <= 0) {
        direction = 1;
    }
    
    pwm_set_duty(TIM2, 1, brightness);
}

3.4 多路PWM同步输出

四路PWM配置

void multi_pwm_init(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
    
    TIM3->PSC = 72 - 1;
    TIM3->ARR = 1000 - 1;
    
    TIM3->CCMR1 = (0x68 << 0) | (0x68 << 8);
    TIM3->CCMR2 = (0x68 << 0) | (0x68 << 8);
    
    TIM3->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E | 
                 TIM_CCER_CC3E | TIM_CCER_CC4E;
    
    TIM3->CCR1 = 250;
    TIM3->CCR2 = 500;
    TIM3->CCR3 = 750;
    TIM3->CCR4 = 1000;
    
    TIM3->CR1 |= TIM_CR1_CEN;
}

四、输入捕获测速应用

4.1 输入捕获原理

输入捕获:捕获外部信号边沿,记录定时器计数值。

外部信号

边沿检测

捕获计数器值

计算脉宽/频率

4.2 频率测量

频率测量配置

typedef struct {
    uint32_t last_count;
    uint32_t frequency;
    uint8_t capture_flag;
} frequency_meter_t;

static frequency_meter_t freq_meter = {0};

void input_capture_init(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    
    GPIOA->MODER &= ~(0x03 << 0);
    GPIOA->MODER |= (0x02 << 0);
    GPIOA->AFRL &= ~(0x0F << 0);
    GPIOA->AFRL |= (0x01 << 0);
    
    TIM2->PSC = 72 - 1;
    TIM2->ARR = 0xFFFFFFFF;
    
    TIM2->CCMR1 = 0x01;
    
    TIM2->CCER = TIM_CCER_CC1E | TIM_CCER_CC1P;
    
    TIM2->DIER = TIM_DIER_CC1IE;
    
    TIM2->CR1 = TIM_CR1_CEN;
    
    NVIC_SetPriority(TIM2_IRQn, 5);
    NVIC_EnableIRQ(TIM2_IRQn);
}

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_CC1IF) {
        TIM2->SR = ~TIM_SR_CC1IF;
        
        uint32_t current_count = TIM2->CCR1;
        
        if (freq_meter.capture_flag) {
            uint32_t period = current_count - freq_meter.last_count;
            freq_meter.frequency = 1000000 / period;
        }
        
        freq_meter.last_count = current_count;
        freq_meter.capture_flag = 1;
    }
}

4.3 脉宽测量

脉宽测量配置

typedef struct {
    uint32_t rise_count;
    uint32_t fall_count;
    uint32_t pulse_width;
    uint8_t state;
} pulse_meter_t;

static pulse_meter_t pulse_meter = {0};

void pulse_width_measure_init(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    
    TIM2->PSC = 72 - 1;
    TIM2->ARR = 0xFFFFFFFF;
    
    TIM2->CCMR1 = 0x01;
    
    TIM2->CCER = TIM_CCER_CC1E;
    
    TIM2->DIER = TIM_DIER_CC1IE;
    
    TIM2->CR1 = TIM_CR1_CEN;
    
    NVIC_SetPriority(TIM2_IRQn, 5);
    NVIC_EnableIRQ(TIM2_IRQn);
}

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_CC1IF) {
        TIM2->SR = ~TIM_SR_CC1IF;
        
        uint32_t current_count = TIM2->CCR1;
        
        if (GPIOA->IDR & 0x01) {
            pulse_meter.rise_count = current_count;
            TIM2->CCER |= TIM_CCER_CC1P;
        } else {
            pulse_meter.fall_count = current_count;
            pulse_meter.pulse_width = pulse_meter.fall_count - pulse_meter.rise_count;
            TIM2->CCER &= ~TIM_CCER_CC1P;
        }
    }
}

五、编码器接口应用

5.1 编码器原理

增量式编码器:输出A、B两路正交脉冲信号。

A相信号

正交解码

B相信号

计数器
自动增/减

正转

A超前B 90°

反转

B超前A 90°

5.2 编码器接口配置

STM32编码器模式配置

void encoder_interface_init(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    
    GPIOA->MODER &= ~((0x03 << 6) | (0x03 << 7));
    GPIOA->MODER |= ((0x02 << 6) | (0x02 << 7));
    GPIOA->AFRL &= ~((0x0F << 24) | (0x0F << 28));
    GPIOA->AFRL |= ((0x02 << 24) | (0x02 << 28));
    
    TIM3->SMCR = 0x03;
    
    TIM3->CCMR1 = (0x01 << 0) | (0x01 << 8);
    TIM3->CCER = 0;
    
    TIM3->ARR = 0xFFFF;
    TIM3->CNT = 0;
    
    TIM3->CR1 = TIM_CR1_CEN;
}

int32_t get_encoder_count(void)
{
    return (int16_t)TIM3->CNT;
}

int32_t get_encoder_speed(void)
{
    static int32_t last_count = 0;
    int32_t current_count = get_encoder_count();
    int32_t speed = current_count - last_count;
    last_count = current_count;
    return speed;
}

5.3 编码器方向检测

方向判断

typedef enum {
    ENCODER_DIR_CW = 0,
    ENCODER_DIR_CCW = 1
} encoder_dir_t;

encoder_dir_t get_encoder_direction(void)
{
    if (TIM3->CR1 & TIM_CR1_DIR) {
        return ENCODER_DIR_CCW;
    } else {
        return ENCODER_DIR_CW;
    }
}

六、电机控制实战项目

6.1 项目需求

实现直流电机闭环控制系统:

  • PWM调速控制
  • 编码器速度反馈
  • PID速度闭环
  • 串口参数调整

6.2 PID控制器实现

typedef struct {
    float kp;
    float ki;
    float kd;
    float target;
    float integral;
    float last_error;
    float output_limit;
} pid_controller_t;

void pid_init(pid_controller_t *pid, float kp, float ki, float kd, float limit)
{
    pid->kp = kp;
    pid->ki = ki;
    pid->kd = kd;
    pid->integral = 0;
    pid->last_error = 0;
    pid->output_limit = limit;
}

float pid_compute(pid_controller_t *pid, float feedback)
{
    float error = pid->target - feedback;
    
    pid->integral += error;
    
    if (pid->integral > pid->output_limit / pid->ki) {
        pid->integral = pid->output_limit / pid->ki;
    } else if (pid->integral < -pid->output_limit / pid->ki) {
        pid->integral = -pid->output_limit / pid->ki;
    }
    
    float derivative = error - pid->last_error;
    
    float output = pid->kp * error + pid->ki * pid->integral + pid->kd * derivative;
    
    if (output > pid->output_limit) {
        output = pid->output_limit;
    } else if (output < -pid->output_limit) {
        output = -pid->output_limit;
    }
    
    pid->last_error = error;
    
    return output;
}

6.3 电机控制主循环

static pid_controller_t speed_pid;

void motor_control_init(void)
{
    pwm_init(TIM2, 1, 10000, 0);
    encoder_interface_init();
    
    pid_init(&speed_pid, 2.0f, 0.1f, 0.01f, 100.0f);
}

void motor_set_speed(float target_rpm)
{
    speed_pid.target = target_rpm;
}

void motor_control_loop(void)
{
    static uint32_t last_time = 0;
    uint32_t current_time = get_tick();
    
    if (current_time - last_time >= 10) {
        last_time = current_time;
        
        int32_t encoder_count = get_encoder_count();
        float current_rpm = encoder_count * 60.0f / (10 * 1000);
        
        float pid_output = pid_compute(&speed_pid, current_rpm);
        
        uint32_t pwm_duty = (uint32_t)(50 + pid_output / 2);
        if (pwm_duty > 100) pwm_duty = 100;
        if (pwm_duty < 0) pwm_duty = 0;
        
        pwm_set_duty(TIM2, 1, pwm_duty);
    }
}

6.4 串口参数调整

void uart_command_handler(uint8_t *data, uint32_t length)
{
    if (strncmp((char *)data, "SPEED:", 6) == 0) {
        float speed = atof((char *)(data + 6));
        motor_set_speed(speed);
        printf("Set speed: %.2f RPM\n", speed);
    }
    else if (strncmp((char *)data, "PID:", 4) == 0) {
        float kp, ki, kd;
        sscanf((char *)(data + 4), "%f,%f,%f", &kp, &ki, &kd);
        pid_init(&speed_pid, kp, ki, kd, 100.0f);
        printf("Set PID: Kp=%.2f, Ki=%.2f, Kd=%.2f\n", kp, ki, kd);
    }
    else if (strncmp((char *)data, "INFO", 4) == 0) {
        printf("Speed: %.2f RPM\n", speed_pid.target);
        printf("PID: Kp=%.2f, Ki=%.2f, Kd=%.2f\n", 
               speed_pid.kp, speed_pid.ki, speed_pid.kd);
    }
}

七、定时器踩坑总结

7.1 常见踩坑

踩坑1:定时精度不够

问题现象

  • 定时周期偏差大
  • 定时不稳定

问题分析

  • 预分频和ARR配置不合理
  • 时钟源选择错误
  • 中断响应延迟

解决方案

void precise_timer_config(uint32_t period_us)
{
    uint32_t psc = 72 - 1;
    uint32_t arr = (period_us * (72000000 / (psc + 1)) / 1000000) - 1;
    
    TIM2->PSC = psc;
    TIM2->ARR = arr;
}

踩坑2:PWM频率偏差

问题现象

  • PWM频率与预期不符
  • 占空比不准确

问题分析

  • 计算公式错误
  • 时钟频率配置错误

解决方案

void pwm_frequency_verify(uint32_t target_freq)
{
    uint32_t actual_freq = 72000000 / ((TIM2->PSC + 1) * (TIM2->ARR + 1));
    
    if (abs(actual_freq - target_freq) > target_freq * 0.01) {
        printf("PWM频率偏差: 目标=%d, 实际=%d\n", target_freq, actual_freq);
    }
}

踩坑3:编码器计数错误

问题现象

  • 编码器计数跳变
  • 方向判断错误

问题分析

  • 信号干扰
  • 编码器模式配置错误

解决方案

void encoder_filter_config(void)
{
    TIM3->SMCR |= (0x03 << 0);
    
    TIM3->CCMR1 |= (0x03 << 2) | (0x03 << 10);
}

7.2 本章小结

定时器和PWM是嵌入式开发的核心外设,掌握其配置方法和应用技巧至关重要。

关键要点

  1. 定时精度:合理配置PSC和ARR
  2. PWM控制:理解频率和占空比计算
  3. 输入捕获:精确测量外部信号
  4. 编码器接口:自动解码正交信号

八、延伸阅读

本文是"嵌入式开发必掌握"系列第七篇,相关文章:

  1. ARM架构与启动流程:理解从上电到main()的过程
  2. GPIO与外部中断:掌握基础外设配置
  3. 本文:定时器与PWM:精确定时和电机控制
  4. 通信接口开发:UART、SPI、I2C、CAN

下一篇文章将讲解"通信接口开发",深入理解串口、SPI、I2C、CAN通信。


相关文章推荐

  • 嵌入式开发必掌握:ARM架构与启动流程全解析
  • 嵌入式开发必掌握:GPIO与外部中断实战
  • 嵌入式开发必掌握:数据结构与算法的实战应用

【投票】你开发中最常用的定时器功能是?

  • 精确定时(任务调度)
  • PWM输出(电机/LED控制)
  • 输入捕获(频率/脉宽测量)
  • 编码器接口(电机测速)

如果觉得文章有帮助,欢迎点赞、收藏、关注!

互动交流

  • 你在定时器配置时遇到过哪些问题?
  • PWM控制有什么优化技巧?
  • 欢迎评论区分享经验,我会逐条回复。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值