STM32与TB6612智能小车开发实战:从编码器数据采集到串级PID调参完整指南
1. 项目概述与硬件选型
智能小车作为嵌入式学习和机器人开发的经典项目,涉及电机控制、传感器数据处理和自动控制算法等多个技术领域。本项目基于STM32微控制器和TB6612电机驱动模块,实现带编码器反馈的直流电机精准控制。
核心硬件组件:
- 主控芯片:STM32F103C8T6(72MHz主频,足够处理双电机PID运算)
- 驱动模块:TB6612FNG(相比L298N具有更低发热量和更高效率)
- 电机类型:6V直流减速电机(减速比1:48,自带AB相编码器)
- 编码器分辨率:11线/转(通过4倍频后为44脉冲/转)
实测数据:电机空载转速约160RPM,带载后降至120RPM左右,编码器总分辨率=44×48=2112脉冲/转
2. 编码器数据采集与速度计算
2.1 编码器接口配置
使用STM32的定时器编码器模式,可自动识别转向并计数:
// TIM1配置为编码器接口模式(电机1) TIM_Encoder_InitTypeDef encoder_config = { .EncoderMode = TIM_ENCODERMODE_TI12, .IC1Polarity = TIM_ICPOLARITY_RISING, .IC1Selection = TIM_ICSELECTION_DIRECTTI, .IC1Prescaler = TIM_ICPSC_DIV1, .IC1Filter = 6, .IC2Polarity = TIM_ICPOLARITY_RISING, .IC2Selection = TIM_ICSELECTION_DIRECTTI, .IC2Prescaler = TIM_ICPSC_DIV1, .IC2Filter = 6 }; HAL_TIM_Encoder_Init(&htim1, &encoder_config); HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);2.2 速度测量实现
采用定时中断法测量转速,在20ms定时中断中读取脉冲差值:
// 在定时器中断回调函数中 int16_t pulse_diff = __HAL_TIM_GET_COUNTER(&htim1); __HAL_TIM_SET_COUNTER(&htim1, 0); float rpm = (pulse_diff * 60.0f) / (2112 * 0.02); // 转换为RPM常见问题排查:
- 脉冲计数异常:检查编码器接线,确保A/B相没有接反
- 速度波动大:适当增加滤波器参数(ICxFilter)
- 方向判断错误:交换A/B相接线测试
3. 电机驱动与PWM控制
3.1 TB6612驱动配置
| 控制信号 | IN1 | IN2 | PWM | 电机状态 |
|---|---|---|---|---|
| 正转 | 1 | 0 | PWM | 正向旋转 |
| 反转 | 0 | 1 | PWM | 反向旋转 |
| 刹车 | 1 | 1 | 0 | 快速停止 |
| 待机 | 0 | 0 | X | 高阻状态 |
PWM配置建议:
- 频率:10-20kHz(超出人耳范围减少噪音)
- 分辨率:16位定时器可获得更平滑控制
// 电机控制函数示例 void Motor_SetSpeed(int motor, float speed) { uint16_t pwm = fabs(speed) * PWM_MAX; if(speed > 0) { HAL_GPIO_WritePin(IN1_GPIO, IN1_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(IN2_GPIO, IN2_PIN, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(IN1_GPIO, IN1_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(IN2_GPIO, IN2_PIN, GPIO_PIN_SET); } __HAL_TIM_SET_COMPARE(&htim3, motor==MOTOR_LEFT?TIM_CHANNEL_1:TIM_CHANNEL_2, pwm); }4. 串级PID控制实现
4.1 控制架构设计
位置环(外环) ↓ 速度环(内环) ↓ PWM输出4.2 PID算法实现
typedef struct { float Kp, Ki, Kd; float target, actual; float err, last_err; float integral, integral_limit; } PID_TypeDef; float PID_Calculate(PID_TypeDef *pid, float actual) { pid->err = pid->target - actual; // 抗积分饱和 if(fabs(pid->err) > 10) pid->integral = 0; else { pid->integral += pid->err; pid->integral = constrain(pid->integral, -pid->integral_limit, pid->integral_limit); } float output = pid->Kp * pid->err + pid->Ki * pid->integral + pid->Kd * (pid->err - pid->last_err); pid->last_err = pid->err; return constrain(output, -OUTPUT_MAX, OUTPUT_MAX); }4.3 参数整定步骤
先调速度环:
- 设Kp=0.5,Ki=0,Kd=0
- 逐步增加Kp直到出现轻微振荡
- 加入Ki消除静差(从Kp/10开始)
- 最后加入Kd抑制超调
再调位置环:
- 采用更小的比例系数(约为速度环的1/5)
- 通常不需要积分项
典型参数范围:
- 速度环:Kp=0.8-1.5, Ki=0.5-1.0, Kd=0.05-0.2
- 位置环:Kp=0.2-0.5, Kd=0.5-1.0
5. 上位机调试与性能优化
5.1 匿名助手配置
通过串口发送数据到上位机:
// 发送实际速度和目标速度到上位机 void SendToAssistant(float target, float actual) { uint8_t buf[8]; memcpy(buf, &target, 4); memcpy(buf+4, &actual, 4); HAL_UART_Transmit(&huart1, buf, 8, 100); }调试技巧:
- 先测试阶跃响应(突然改变目标值)
- 观察上升时间、超调量和稳定时间
- 调整参数使响应曲线达到临界阻尼状态
5.2 常见问题解决方案
问题1:电机启动抖动
- 原因:PID输出突变
- 解决:增加启动斜坡
// 渐进式设定目标值 void SetTargetGradually(float *target, float new_target, float step) { if(fabs(*target - new_target) > step) { *target += (*target < new_target) ? step : -step; } }问题2:直线行驶偏移
- 原因:两轮机械差异
- 解决:加入差速补偿
// 根据偏移量动态调整两轮速度 float left_speed = base_speed + offset_compensation; float right_speed = base_speed - offset_compensation;问题3:转向不精确
- 原因:惯性导致过冲
- 解决:加入转向减速区
if(fabs(target_angle - current_angle) < 10) { // 进入减速区 speed = BASE_SPEED * (fabs(target_angle - current_angle)/10); }6. 进阶优化方向
- 自适应PID:根据误差大小动态调整参数
- 运动规划:S曲线速度规划减少机械冲击
- 参数自整定:Ziegler-Nichols法等自动整定方法
- 卡尔曼滤波:对编码器数据进行滤波处理
实测性能指标:
- 直线行驶误差:<±2cm/米
- 转向精度:±3度
- 速度控制精度:±5RPM
项目完整代码已托管至GitHub(示例链接),包含:
- 完整的Keil工程文件
- 上位机通信协议
- 参数调试工具
- 3D打印小车结构设计图