STM32 平衡车前置:直流电机正反转 PID 控制(编码器测速 + PWM 驱动)

平衡车的核心是通过精准控制左右电机的转速(正反转 + 速度闭环)来维持车身平衡,本文基于 STM32F103 平台,详解一套 "编码器测速 + PWM 驱动 + 正反转控制 + 自动测试" 的电机控制程序,为后续平衡车 PID 闭环控制打下基础。全程不修改代码,仅拆解核心逻辑,新手也能看懂。

一、程序核心功能与硬件基础

1. 核心功能

  • 基于定时器编码器模式采集电机转速(带方向,正负值区分正反转);
  • 定时器 PWM 输出驱动电机,GPIO 电平控制电机正反转;
  • 50ms 定时中断触发转速计算,实现速度实时监测;
  • 正反转自动测试状态机:自动遍历不同 PWM 值(正转 4000-7000、反转 4000-7000),采集稳定后的平均转速;
  • 串口调试:打印实时 PWM、方向、转速,支持 USMART 在线调试,兼容匿名上位机数据传输。

2. 硬件与定时器资源(核心重点)

外设 / 定时器 功能 关键参数
TIM1 PWM 输出(驱动电机) 72MHz 主频,预分频 1,ARR=7199(10kHz PWM)
TIM2 50ms 定时中断(触发转速计算) 预分频 7199,ARR=499(72M/7200=10kHz,10kHz*500=50ms)
TIM3 编码器模式(采集电机脉冲) 编码器模式 TI12,捕获 AB 相脉冲,溢出中断计数
USART1 串口调试 + 匿名上位机通信 115200 波特率,DMA / 中断接收
GPIOA 电机正反转控制(PA2/PA3) 推挽输出,PA2 = 高 / PA3 = 低→正转;反之反转
其他 I2C/AT24C02/OLED(辅助,非核心) 本文不重点讲解

二、主程序核心逻辑拆解

主程序是整个控制流程的 "大脑",我们逐段解析关键部分:

1. 全局变量与初始化(程序入口)

cs 复制代码
// 设备句柄(封装外设操作,便于模块化调用)
TMX_UART_DEV* uart_dev;                 //串口设备
TMX_I2C_MASTER_DEV * i2c_master_dev;    //I2C主设备
TMX_AT24C02_DEV * at24c02_dev;          //EEPROM设备
TMX_OLED_DEV * oled_dev;                //OLED设备
TMX_PH2_TIMER_DEV* ph2_timer_dev;       //电机控制设备

// 测试相关变量(正反转自动测试核心)
static int16_t pwm_test_values[] = {4000, 5000, 6000, 7000, -4000, -5000, -6000, -7000};
static uint8_t pwm_index = 0;            // 当前测试的PWM值索引
static uint8_t test_state = 0;           // 测试状态机:0-设置PWM 1-稳定等待 2-数据采集
static int16_t current_pwm_signed = 0;   // 带符号的当前PWM值(正负区分方向)

初始化流程

cs 复制代码
HAL_Init();                         // 初始化HAL库
sys_stm32_clock_init(RCC_PLL_MUL9); // 设置时钟为72MHz(STM32F103核心)
delay_init(72);                     // 延时初始化(对应72MHz主频)
usmart_init(72);                    // USMART在线调试初始化

// 外设初始化(核心电机控制相关)
uart_dev = tmx_uart_init(TMX_UART1,115200,UART_DMA_ON); // 串口1初始化
ph2_timer_dev = tmx_ph2_timer_init();      // 电机控制模块初始化(TIM1/2/3+GPIO)

tmx_ph2_timer_init()是电机控制的核心初始化函数,内部完成:

  • TIM2(50ms 中断)初始化并开启中断;
  • TIM3(编码器模式)初始化,开启溢出中断,启动编码器采集;
  • TIM1(PWM)初始化,配置 CH1 输出 PWM 并启动;
  • GPIOA2/A3(正反转)初始化,默认设置为正转。

2. 主循环:核心控制逻辑

主循环分为两大分支:定时器事件处理(50ms)串口接收处理,其中定时器事件是电机控制的核心。

(1)50ms 定时事件处理(TIM2 中断触发)
cs 复制代码
if(ph2_timer_dev->event == 1){
    temp1++;
    ph2_timer_dev->event = 0;       // 清除TIM2中断标志
    
    // 每500ms(10次*50ms)计算一次转速并执行测试逻辑
    if(temp1 >10){
        // 1. 计算当前电机转速(参数500=计算周期500ms)
        int16_t speed = ph2_timer_dev->get_speed_left(500);
        // 2. 读取当前PWM寄存器值(绝对值)
        uint16_t current_pwm_abs = TIM1->CCR1;
        // 3. 还原带符号的PWM值(正负区分方向)
        int16_t actual_pwm = current_pwm_signed >= 0 ? current_pwm_abs : -current_pwm_abs;
        
        // 4. 打印实时状态(调试用)
        printf("方向: %s, 设定PWM: %d, 实际PWM: %d, 速度: %d RPM\r\n",
               (current_pwm_signed >= 0) ? "正转" : "反转",
               current_pwm_signed, actual_pwm, speed);
        
        // 5. 正反转自动测试状态机(核心中的核心)
        static uint8_t wait_counter = 0;
        static uint8_t sample_counter = 0;
        static int32_t speed_sum = 0;
        static uint16_t abs_pwm_value = 0;
        
        switch(test_state){
            case 0:  // 状态0:设置新的PWM值(切换正反转/不同PWM)
                current_pwm_signed = pwm_test_values[pwm_index];
                abs_pwm_value = (current_pwm_signed >= 0) ? current_pwm_signed : -current_pwm_signed;
                
                printf("\n=== 测试 %s, PWM=%d ===\r\n",
                       (current_pwm_signed >= 0) ? "正转" : "反转",
                       abs_pwm_value);
                
                // 设置PWM(内部自动处理方向:正→PA2=高,反→PA3=高)
                ph2_timer_dev->set_pwm_left(current_pwm_signed);
                
                test_state = 1;  // 切换到"稳定等待"状态
                wait_counter = 0;
                sample_counter = 0;
                speed_sum = 0;
                break;
                
            case 1:  // 状态1:等待电机稳定(2秒=4次*500ms)
                wait_counter++;
                if(wait_counter >= 4) {  
                    printf("电机已稳定,开始采集数据...\r\n");
                    test_state = 2;  // 切换到"数据采集"状态
                }
                break;
                
            case 2:  // 状态2:采集3秒数据(6次*500ms),计算平均转速
                sample_counter++;
                speed_sum += speed;  // 累加每次的转速值
                
                if(sample_counter >= 6) {  
                    int16_t avg_speed = speed_sum / 6;  // 计算平均转速
                    
                    // 打印测试结果
                    printf("测试结果: %s PWM=%d -> 平均速度=%d RPM\r\n",
                           (current_pwm_signed >= 0) ? "正转" : "反转",
                           abs_pwm_value, avg_speed);
                    
                    // 切换到下一个测试PWM值
                    pwm_index++;
                    if(pwm_index >= sizeof(pwm_test_values)/sizeof(pwm_test_values[0])) {
                        pwm_index = 0;  // 测试完一轮,从头再来
                        printf("\n=== 正反转完整测试完成 ===\r\n");
                    }
                    
                    test_state = 0;  // 回到"设置PWM"状态
                }
                break;
        }
        
        // 6. 发送转速到匿名上位机(可视化)
        send_speed_to_anonymous(speed);
        temp1 = 0;  // 重置计数器
    }
}

关键解析

  • ph2_timer_dev->event == 1:由 TIM2 的 50ms 中断回调函数置 1,是整个电机控制的 "心跳";
  • get_speed_left(500):核心测速函数,基于 TIM3 编码器的脉冲数 + 溢出数,计算 500ms 内的电机转速(RPM);
  • 状态机设计:避免电机刚切换 PWM 就采集数据(转速不稳定),先等 2 秒稳定,再采集 3 秒数据求平均,保证测试准确性;
  • set_pwm_left(current_pwm_signed):封装函数,内部根据 PWM 正负设置 GPIO 方向,再将绝对值写入 TIM1->CCR1(PWM 占空比)。
(2)串口接收处理(USMART 调试 + 数据打印)
cs 复制代码
if(uart_dev->finish == 1){
  // 识别USMART格式(u_xxx(yyy)\r\n),交给USMART处理
  if(uart_dev->length>=8 && 
     uart_dev->buffer[0] == 'u' && 
     uart_dev->buffer[1] == '_' && 
     uart_dev->buffer[uart_dev->length-2] == '\r' && 
     uart_dev->buffer[uart_dev->length-1] == '\n'){
    usmart_action((char *)(uart_dev->buffer));
  }
  else{
    // 非USMART格式,直接打印接收的数据(调试用)
    printfBuffer(uart_dev->buffer,uart_dev->length);
  } 
  // 重新开启串口接收(DMA/中断)
  uart_dev->start_receive();
}

USMART 作用 :无需重新烧录程序,直接通过串口调用自定义函数(如u_test1(1)),快速调试电机参数。

3. 辅助函数:上位机通信

send_speed_to_anonymous(int16_t speed):将转速数据封装成匿名上位机的通信协议(帧头 + 功能字 + 数据 + 校验和),通过串口发送,实现转速可视化(上位机可实时绘图)。

三、核心定时器工作原理(新手必懂)

1. TIM2:50ms 定时中断(心跳定时器)

cs 复制代码
htim2.Init.Prescaler = 7200-1;  // 预分频=7199 → 72MHz/7200=10kHz
htim2.Init.Period = 500-1;      // 自动重装值=499 → 10kHz/(499+1)=20Hz → 周期50ms

中断回调:HAL_TIM_PeriodElapsedCallback中检测到 TIM2 中断,置位_ph2_timer_dev.event = 1,触发主循环的转速计算。

2. TIM3:编码器模式(测速核心)

编码器模式原理:电机带动编码器旋转,输出 AB 相脉冲,TIM3 通过捕获 AB 相脉冲的上升沿 / 下降沿,自动计数(正转加、反转减),溢出时触发中断,记录溢出次数(n_left)。

测速公式(get_speed_left内部):

cs 复制代码
int32_t P = (int32_t)n * 65535 + (int32_t)x;  // 总脉冲数(溢出数*最大值 + 当前计数值)
int32_t rpm = (P * 60000) / (4 * XIANSHU * JIANSUBI * d);  // 转成RPM
  • 4:编码器 AB 相四倍频(TIM3 编码器模式默认);
  • XIANSHU=11:编码器每圈 11 个脉冲;
  • JIANSUBI=20:电机减速比 20(编码器转 20 圈 = 轮子转 1 圈);
  • d=500:计算周期(ms),60000=60*1000(转成每分钟转速)。

3. TIM1:PWM 输出(驱动电机)

初始化参数:

cs 复制代码
htim1.Init.Prescaler = 1-1;     // 预分频=0 → 72MHz主频
htim1.Init.Period = 7200-1;     // ARR=7199 → PWM频率=72MHz/7200=10kHz(电机驱动常用频率)

PWM 占空比:TIM1->CCR1的值决定占空比,例如CCR1=3600 → 占空比 = 3600/7200=50%,值越大转速越快。

四、测试效果与后续扩展

1. 测试效果

串口打印示例:

cs 复制代码
=== 测试 正转, PWM=4000 ===
方向: 正转, 设定PWM: 4000, 实际PWM: 4000, 速度: 120 RPM
方向: 正转, 设定PWM: 4000, 实际PWM: 4000, 速度: 119 RPM
电机已稳定,开始采集数据...
测试结果: 正转 PWM=4000 -> 平均速度=120 RPM

=== 测试 正转, PWM=5000 ===
...
=== 测试 反转, PWM=4000 ===
方向: 反转, 设定PWM: -4000, 实际PWM: -4000, 速度: -118 RPM
...
=== 正反转完整测试完成 ===
测试序列: 正转4000-7000, 反转4000-7000

2. 后续扩展(平衡车方向)

  • 加入 PID 闭环控制:根据 "目标转速 - 实际转速" 的误差,自动调整 PWM 值,实现转速精准跟随;
  • 双轮控制:复制左轮逻辑到右轮,同步控制左右电机;
  • 加入姿态传感器(MPU6050):采集车身倾角,结合电机转速 PID,实现平衡车自平衡;
  • 优化测速:加入卡尔曼滤波,抑制转速数据抖动。

总结

本文详解的程序是平衡车开发的核心前置模块,核心逻辑是 "TIM2 定时触发→TIM3 编码器测速→状态机测试→TIM1 PWM 驱动 + GPIO 方向控制"。程序采用模块化封装(设备句柄),便于后续扩展;状态机设计保证测试准确性,USMART 和上位机通信降低调试难度。

掌握这套程序后,只需加入姿态传感器和 PID 闭环算法,就能实现平衡车的基础自平衡控制。核心是理解三个定时器的分工:TIM2 做定时、TIM3 做测速、TIM1 做驱动,这也是 STM32 电机控制的经典搭配。

相关推荐
学工科的皮皮志^_^2 小时前
光模块学习
经验分享·笔记·嵌入式硬件·学习
少年、潜行2 小时前
F1C100/200S学习笔记(3)-- 裸机开发
linux·笔记·学习·驱动·裸机·f1c200s
老王熬夜敲代码2 小时前
网路编程--协议
linux·网络·笔记
芥子沫2 小时前
Docker安装Blossom笔记
笔记·docker·容器
UVM_ERROR2 小时前
Git仓库损坏(Segmentation fault)修复实战:虚拟机环境下UVM项目救援指南
笔记·git·vscode·github·芯片
Jerry丶Li2 小时前
四十、STM32的外设SPI
stm32·单片机·嵌入式硬件
龚子亦2 小时前
【Unity开发】热更新学习——HybridCLR框架
学习·unity·游戏引擎·热更新
苦 涩2 小时前
考研408笔记之计算机组成原理(一)——计算机系统概述
笔记·计算机组成原理·考研408
Chunyyyen2 小时前
【第二十七周】OCR学习02
学习·ocr