1. 平衡小车电机测速的核心原理
做平衡小车的朋友都知道,电机测速是整个系统的"眼睛"。就像我们骑自行车时需要知道车轮转得多快才能保持平衡一样,平衡小车也需要实时掌握电机转速。这里我们用STM32F1的编码器模式来实现这个功能,实测下来精度可以达到±2转/分钟,完全能满足平衡控制的需求。
直流减速电机自带霍尔编码器,每转会产生固定数量的脉冲。以常见的GM25-370电机为例,电机本身转速12000转/分钟,经过34:1的减速箱后输出转速约350转/分钟。编码器每转产生15个脉冲,经过减速箱后每输出轴转一圈实际产生510个脉冲(15×34)。STM32的编码器模式还能进行4倍频,最终每转能得到2040个脉冲信号。
测速原理很简单:定时采集脉冲数量,比如每0.1秒读取一次计数器值。假设读到的脉冲数是1020,那么转速就是1020÷2040÷0.1=5转/秒(即300转/分钟)。这个计算过程会在代码部分详细展开。
2. 硬件准备与安全接线
2.1 必备硬件清单
我用的核心硬件包括:
- STM32F103C8T6最小系统板(蓝色药丸板)
- DRV8848电机驱动模块(比TB6612电流更大)
- 带15线霍尔编码器的GM25-370直流减速电机
- 12V锂电池供电系统
- 0.96寸OLED显示屏(I2C接口)
- HC-08蓝牙模块(用于无线调试)
特别要注意电机选择。市面上常见的有光电编码器和霍尔编码器两种,霍尔编码器抗干扰能力更强,更适合电机应用。编码器线数也不是越多越好,15线在减速后已经能提供足够的分辨率。
2.2 生死攸关的接线技巧
这里我踩过坑:曾经因为接线错误烧了三块单片机。关键注意事项:
- 电机驱动板的电源GND和信号GND要分开处理。建议只将信号GND与单片机共地,大电流的电源GND单独走线。
- STM32绝对不能直接给电机驱动板供电!电机驱动需要单独电源。
- 编码器信号线建议加10K上拉电阻,避免信号抖动。
- 电机电源线上一定要加0.1μF的消噪电容。
具体接线图示:
- 编码器A相接TIM3_CH1(PA6)
- 编码器B相接TIM3_CH2(PA7)
- 电机PWM接TIM4_CH1(PB6)
- 电机方向控制接任意GPIO
3. CubeMX关键配置详解
3.1 时钟树配置
先配置HSE为8MHz外部晶振,PLL倍频到72MHz系统时钟。这是STM32F1的黄金频率,所有外设都基于这个时钟工作。特别注意APB1总线时钟要设为36MHz,因为定时器挂载在这个总线上。
3.2 PWM配置(TIM4)
- 选择TIM4,Clock Source选Internal Clock
- Channel1配置为PWM Generation CH1
- 参数设置:
- Prescaler: 71(得到1MHz计数频率)
- Counter Period: 199(生成10kHz PWM)
- Pulse: 初始值设为0
PWM频率建议设置在8-16kHz之间,超出这个范围要么会有可闻噪音,要么电机响应变慢。
3.3 编码器模式配置(TIM4)
这是最关键的配置:
- 选择TIM3,Combined Channels设为Encoder Mode
- 参数设置:
- Encoder Mode: TI1 and TI2(4倍频模式)
- Counter Period: 65535(16位计数器最大值)
- Polarity: Rising Edge(常规配置)
特别注意:Polarity设置不影响计数方向,只影响触发边沿。计数方向由编码器相位差决定。
3.4 定时器中断配置(TIM1)
配置TIM1产生100ms中断:
- Prescaler: 7199
- Counter Period: 999 这样得到的定时器频率是72MHz/(7199+1)/(999+1)=10Hz
4. 代码实现与优化技巧
4.1 编码器数据处理
// encoder.c void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim == &htim1) { short counter = (short)__HAL_TIM_GET_COUNTER(&htim3); __HAL_TIM_SET_COUNTER(&htim3, 0); // 计算转速(转/秒) float speed = (float)counter / 2040.0f / 0.1f; // OLED显示 OLED_ShowDecimal(0, 4, speed, 2, 2, 12, 0); } }这段代码有三个优化点:
- 使用short类型强制转换处理计数器溢出
- 2040是4倍频后的每转脉冲数(510×4)
- 0.1是定时器中断周期
4.2 电机控制实现
// motor.c void Motor_SetSpeed(uint8_t percentage) { uint16_t pulse = 7200 * percentage / 100; // ARR=7200 __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, pulse); }建议做成开环速度曲线,不同电压下电机特性不同,可以实测后建立速度-占空比对照表。
4.3 抗干扰处理
实际测试中发现编码器会受到电机干扰,加入以下滤波代码:
// 在定时器中断中加入移动平均滤波 #define FILTER_SIZE 5 static float speed_buffer[FILTER_SIZE] = {0}; static uint8_t filter_index = 0; speed_buffer[filter_index] = (float)counter / 2040.0f / 0.1f; filter_index = (filter_index + 1) % FILTER_SIZE; float filtered_speed = 0; for(int i=0; i<FILTER_SIZE; i++) { filtered_speed += speed_buffer[i]; } filtered_speed /= FILTER_SIZE;5. 实测数据与常见问题
5.1 性能测试结果
在不同PWM占空比下测得转速:
| 占空比 | 实测转速(rpm) | 理论值(rpm) | 误差 |
|---|---|---|---|
| 20% | 68 | 70 | -2.8% |
| 40% | 142 | 140 | +1.4% |
| 60% | 208 | 210 | -0.9% |
| 80% | 278 | 280 | -0.7% |
| 100% | 348 | 350 | -0.5% |
5.2 常见故障排查
计数器值不变化:
- 检查编码器供电(通常是5V)
- 用示波器看PA6/PA7是否有波形
- 确认CubeMX配置了编码器模式
转速显示跳动大:
- 增加软件滤波
- 检查编码器连接线是否太长
- 在编码器信号线加100Ω电阻和100pF电容滤波
电机转动但转速显示为零:
- 检查编码器方向是否接反
- 尝试交换A相B相接线
6. 进阶应用:为PID控制做准备
测速最终是为PID控制服务的,这里先打好基础。后续PID需要:
- 稳定的速度采样(本文已实现)
- 合适的控制周期(建议5-10ms)
- 速度-占空比转换关系
建议先建立电机特性表,记录不同PWM下的空载转速和带载转速,这对PID参数整定很有帮助。在我的平衡小车项目中,这套测速方案最终实现了±1rpm的稳定控制精度。