平衡车的核心是通过精准控制左右电机的转速(正反转 + 速度闭环)来维持车身平衡,本文基于 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 电机控制的经典搭配。