从零开始:用STM32定时器与PWM打造智能小车电机控制系统

智能小车作为嵌入式领域的经典项目,其核心控制模块往往依赖于电机的精准调速与方向控制。对于初学者而言,如何利用STM32的定时器与PWM技术实现高效电机驱动,是迈向嵌入式开发实战的重要一步。本文将基于STM32F103C8T6(Cortex-M3内核)平台,通过实际项目案例,详细解析从硬件连接到软件配置的全流程,帮助读者构建完整的智能小车电机控制系统。

1. 硬件基础与电机驱动原理

在开始代码编写前,我们需要先了解智能小车电机控制的基本硬件架构。典型的直流电机驱动方案通常采用H桥电路,通过PWM信号控制电机的转速和方向。

核心硬件组件

  • STM32F103C8T6最小系统板(主控芯片)
  • L298N或TB6612FNG电机驱动模块
  • 直流减速电机(带编码器反馈可选)
  • 电源管理系统(锂电池+稳压模块)

电机驱动的关键参数包括工作电压、电流容量和PWM频率。一般来说,小型直流电机的PWM频率建议设置在1-20kHz之间,以避免可闻噪声并保证响应速度。对于STM32F103系列,定时器时钟最高可达72MHz,为生成精准PWM提供了充足精度。

实际项目中,我曾遇到电机响应迟滞的问题,最终发现是PWM频率设置过高导致驱动芯片切换损耗过大。将频率从20kHz降至5kHz后,不仅解决了发热问题,还提高了扭矩响应速度。

2. STM32定时器系统深度解析

STM32的定时器系统是其外设中的精华部分,理解其工作原理是实现精准电机控制的基础。STM32F103C8T6包含4个通用定时器(TIM2-TIM5)、2个高级定时器(TIM1、TIM8)和1个基本定时器(TIM6)。

定时器关键功能对比

定时器类型 分辨率 PWM输出 编码器接口 适用场景
高级定时器 16位 6通道+互补输出 支持 电机控制、电源转换
通用定时器 16位 4通道 支持 通用PWM生成、输入捕获
基本定时器 16位 不支持 时基生成、简单定时

对于电机控制,我们通常选择通用定时器或高级定时器。以TIM3为例,这是一个16位通用定时器,具有4个PWM输出通道,完全满足双电机控制需求。

定时器时钟配置步骤

  1. 使能TIM3时钟:RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE)
  2. 配置时基单元:设置预分频器(PSC)和自动重载值(ARR)
  3. 设置PWM模式:选择输出比较模式
  4. 使能定时器:TIM_Cmd(TIM3, ENABLE)
// TIM3 PWM初始化示例
void TIM3_PWM_Init(uint16_t arr, uint16_t psc)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // 时基单元配置
    TIM_TimeBaseStructure.TIM_Period = arr;        // 自动重载值
    TIM_TimeBaseStructure.TIM_Prescaler = psc;     // 预分频系数
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // PWM模式配置
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    
    // 通道1配置
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
    
    TIM_ARRPreloadConfig(TIM3, ENABLE);
    TIM_Cmd(TIM3, ENABLE);
}

3. PWM生成与电机调速实战

PWM(脉冲宽度调制)是电机调速的核心技术,通过调节占空比来控制平均电压,从而实现转速调节。STM32的定时器提供了灵活的PWM生成能力,支持边沿对齐和中心对齐模式。

PWM参数计算公式

  • PWM频率 = 定时器时钟 / [(PSC+1) * (ARR+1)]
  • 占空比 = CCR / (ARR+1) * 100%

假设我们需要生成频率为10kHz,占空比可调的PWM信号:

  • 定时器时钟:72MHz
  • 设置PSC=71,ARR=99
  • 频率 = 72,000,000 / (72*100) = 10,000Hz
  • 占空比调节范围:0-100%(通过修改CCR值实现)

电机方向控制实现: 对于典型的H桥驱动,需要两个GPIO控制方向,一个PWM通道控制速度:

// 电机控制结构体定义
typedef struct {
    GPIO_TypeDef* DIR1_Port;
    uint16_t DIR1_Pin;
    GPIO_TypeDef* DIR2_Port;
    uint16_t DIR2_Pin;
    TIM_TypeDef* TIMx;
    uint16_t PWM_Channel;
} Motor_TypeDef;

// 电机控制函数
void Motor_SetSpeed(Motor_TypeDef* motor, int16_t speed)
{
    // 限制速度范围
    speed = (speed > 100) ? 100 : (speed < -100) ? -100 : speed;
    
    // 设置方向
    if(speed >= 0) {
        GPIO_WriteBit(motor->DIR1_Port, motor->DIR1_Pin, Bit_SET);
        GPIO_WriteBit(motor->DIR2_Port, motor->DIR2_Pin, Bit_RESET);
    } else {
        GPIO_WriteBit(motor->DIR1_Port, motor->DIR1_Pin, Bit_RESET);
        GPIO_WriteBit(motor->DIR2_Port, motor->DIR2_Pin, Bit_SET);
        speed = -speed;
    }
    
    // 设置PWM占空比
    switch(motor->PWM_Channel) {
        case 1:
            TIM_SetCompare1(motor->TIMx, speed);
            break;
        case 2:
            TIM_SetCompare2(motor->TIMx, speed);
            break;
        // 其他通道类似
    }
}

在实际调试中,我发现电机在低速时存在死区问题,即占空比低于某个阈值时电机无法启动。通过添加最小启动占空比补偿,有效解决了这个问题:

// 添加死区补偿
#define MOTOR_DEAD_ZONE 15  // 实测死区占空比

void Motor_SetSpeed_WithCompensation(Motor_TypeDef* motor, int16_t speed)
{
    if(abs(speed) < MOTOR_DEAD_ZONE && speed != 0) {
        speed = (speed > 0) ? MOTOR_DEAD_ZONE : -MOTOR_DEAD_ZONE;
    }
    Motor_SetSpeed(motor, speed);
}

4. 编码器反馈与闭环控制

为实现精准的电机控制,集成编码器反馈是必不可少的一步。STM32的定时器编码器接口可以直接读取正交编码器信号,无需外部中断处理,大大简化了软件设计。

编码器接口配置要点

  • 使用TIMx的CH1和CH2通道接收编码器A、B相信号
  • 设置编码器模式为TI1和TI2边沿计数
  • 配置滤波器减少噪声影响
void Encoder_Init(TIM_TypeDef* TIMx)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;
    
    // 时基配置
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_Period = 65535;  // 16位最大值
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);
    
    // 编码器接口配置
    TIM_EncoderInterfaceConfig(TIMx, TIM_EncoderMode_TI12, 
                              TIM_ICPolarity_Rising, 
                              TIM_ICPolarity_Rising);
    
    // 输入捕获配置
    TIM_ICInitStructure.TIM_ICFilter = 6;  // 适当滤波
    TIM_ICInit(TIMx, &TIM_ICInitStructure);
    
    TIM_Cmd(TIMx, ENABLE);
}

速度计算算法: 通过定期读取编码器计数值变化,可以计算电机实际转速:

int32_t Get_Motor_Speed(TIM_TypeDef* TIMx, uint32_t sample_time_ms)
{
    static int16_t last_count = 0;
    int16_t current_count = TIM_GetCounter(TIMx);
    int16_t delta = current_count - last_count;
    last_count = current_count;
    
    // 考虑计数器溢出情况
    if(delta > 32767) delta -= 65536;
    else if(delta < -32768) delta += 65536;
    
    // 转换为转速值(RPM)
    // 假设编码器线数为13,减速比为30:1
    const float counts_per_rev = 13 * 30 * 4;  // 4倍频
    float rpm = (delta * 60000.0) / (counts_per_rev * sample_time_ms);
    
    return (int32_t)rpm;
}

PID控制器实现: 结合编码器反馈,我们可以实现完整的PID闭环控制:

typedef struct {
    float Kp, Ki, Kd;
    float integral;
    float prev_error;
    uint32_t last_time;
} PID_Controller;

float PID_Update(PID_Controller* pid, float setpoint, float measured)
{
    float error = setpoint - measured;
    uint32_t now = HAL_GetTick();
    float dt = (now - pid->last_time) / 1000.0f;
    pid->last_time = now;
    
    // 比例项
    float P = pid->Kp * error;
    
    // 积分项(抗积分饱和)
    pid->integral += error * dt;
    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->prev_error) / dt;
    pid->prev_error = error;
    
    return P + I + D;
}

在实际调试PID参数时,我建议采用如下步骤:

  1. 先将Ki和Kd设为0,逐渐增大Kp直到系统出现轻微振荡
  2. 将Kp设为当前值的50%,然后逐渐增加Ki直到静差消除
  3. 最后加入Kd抑制超调,但不宜过大以免引入噪声

5. 系统集成与实战调试技巧

将各个模块整合成完整的电机控制系统需要考虑多个实际因素,包括电源管理、噪声抑制和实时性保证。

电源系统设计注意事项

  • 电机电源与MCU电源完全隔离,使用不同的稳压模块
  • 在电机电源输入端添加大容量电解电容(100-1000μF)缓冲电流突变
  • 信号线路上使用磁珠或小电感滤除高频噪声

软件架构优化: 建议采用模块化设计,将电机控制相关功能封装成独立模块:

// 电机控制模块接口
typedef struct {
    Motor_TypeDef hardware;
    PID_Controller speed_pid;
    int32_t target_speed;
    int32_t current_speed;
    uint32_t last_update;
} Motor_Control_Module;

void Motor_Module_Init(Motor_Control_Module* module, 
                      GPIO_TypeDef* dir1_port, uint16_t dir1_pin,
                      GPIO_TypeDef* dir2_port, uint16_t dir2_pin,
                      TIM_TypeDef* pwm_tim, uint16_t pwm_ch,
                      TIM_TypeDef* enc_tim)
{
    // 硬件初始化
    module->hardware.DIR1_Port = dir1_port;
    module->hardware.DIR1_Pin = dir1_pin;
    // ... 其他硬件初始化
    
    // PID参数初始化
    module->speed_pid.Kp = 0.5f;
    module->speed_pid.Ki = 0.1f;
    module->speed_pid.Kd = 0.01f;
    module->speed_pid.integral = 0;
    module->speed_pid.prev_error = 0;
}

void Motor_Module_Update(Motor_Control_Module* module)
{
    // 每10ms更新一次
    if(HAL_GetTick() - module->last_update < 10) return;
    
    // 读取实际速度
    module->current_speed = Get_Motor_Speed(module->hardware.enc_tim, 10);
    
    // PID计算
    float control = PID_Update(&module->speed_pid, 
                              module->target_speed, 
                              module->current_speed);
    
    // 设置电机速度
    Motor_SetSpeed_WithCompensation(&module->hardware, (int16_t)control);
    
    module->last_update = HAL_GetTick();
}

常见调试问题与解决方案

  1. 电机抖动或噪声过大

    • 检查PWM频率是否合适(建议5-10kHz)
    • 增加编码器滤波器设置
    • 检查电源电压是否稳定
  2. 响应速度慢或有明显超调

    • 调整PID参数,增加Kp或Kd
    • 检查编码器采样周期是否过长
    • 确认电机机械负载是否过重
  3. 低速控制精度差

    • 启用死区补偿
    • 提高PWM分辨率(增加ARR值)
    • 考虑使用FOC(磁场定向控制)算法

在实际项目中,我还发现温度对电机性能影响显著。特别是在长时间运行后,电机电阻变化会导致特性改变。为此,可以添加温度补偿算法,或者定期自动校准PID参数。

通过串口调试工具实时监控电机状态也是提高调试效率的有效方法:

void Motor_Debug_Print(Motor_Control_Module* module)
{
    printf("Target: %d, Actual: %d, PWM: %d\r\n",
          module->target_speed,
          module->current_speed,
          TIM_GetCompare1(module->hardware.TIMx));
}

最后提醒一点,在进行任何参数调整时,务必记录每次修改的效果。我习惯使用如下格式的记录表格:

调试时间 Kp Ki Kd 超调量 调节时间 静差 备注
09:30 0.5 0.0 0.0 45% 2.1s 12RPM 首次调试
09:45 0.3 0.05 0.01 15% 1.5s 3RPM 增加积分

这种系统化的调试方法能够显著提高开发效率,避免盲目调整参数。

Logo

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

更多推荐