这个基于STM32F103RCT6的智能车项目,是我去年带队参加大学生智能车竞赛时的实战作品。整个系统最核心的创新点在于将光电编码器与直流电机PID调速系统完美结合,配合多种传感器模块实现了高精度的运动控制。下面我就从硬件设计到软件实现,完整复盘这个项目的技术细节。
选择STM32F103RCT6作为主控芯片主要基于三点考虑:
实际调试中发现,RCT6的512KB Flash完全够用,但64KB RAM在运行卡尔曼滤波时略显紧张,后续可以考虑升级到STM32F407系列
采用TB6612FNG双H桥驱动芯片,相比传统的L298N具有明显优势:
电机参数:

关键传感器配置:
使用TIM定时器的编码器接口模式,配置要点:
c复制void Encoder_Config(TIM_TypeDef* TIMx){
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_Period = 65535; // 16位最大值
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0F; // 重要!抑制噪声
TIM_ICInit(TIMx, &TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIMx, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising,
TIM_ICPolarity_Rising);
TIM_Cmd(TIMx, ENABLE);
}
速度计算算法:
c复制float Get_Speed(uint32_t dt_ms){
static int32_t last_count = 0;
int32_t current_count = TIM_GetCounter(TIMx);
int32_t delta = current_count - last_count;
last_count = current_count;
// 转每秒脉冲数:delta/(dt_ms/1000) = delta*1000/dt_ms
float pulse_per_sec = (delta * 1000.0f) / dt_ms;
// 转换为cm/s: (脉冲数/52)*轮周长
return (pulse_per_sec/52.0f) * 21.0f; // 轮周长21cm
}
改进型增量式PID实现:
c复制typedef struct{
float Target; // 目标值
float Kp,Ki,Kd; // PID参数
float Error_Last; // 上次偏差
float Integral; // 积分项
float Output; // 输出值
uint16_t MaxOut; // 输出限幅
float Alpha; // 低通滤波系数(0~1)
}PID_TypeDef;
void PID_Calc(PID_TypeDef* pid){
float error = pid->Target - Get_Speed();
// 积分分离:偏差大时不积分
if(fabs(error) < 200) {
pid->Integral += error;
pid->Integral = fmaxf(fminf(pid->Integral, pid->MaxOut/pid->Ki), -pid->MaxOut/pid->Ki);
}
// 微分项低通滤波
static float last_dout = 0;
float dout = (error - pid->Error_Last) * pid->Kd;
dout = pid->Alpha * dout + (1-pid->Alpha) * last_dout;
last_dout = dout;
pid->Output = pid->Kp*error + pid->Ki*pid->Integral + dout;
pid->Output = fmaxf(fminf(pid->Output, pid->MaxOut), -pid->MaxOut);
pid->Error_Last = error;
}
参数整定经验:
MPU6050数据读取优化:
c复制void MPU6050_Read_Raw(int16_t* acc, int16_t* gyro){
uint8_t buf[14]; // 包含温度数据
I2C_Read_Reg(MPU6050_ADDR, ACCEL_XOUT_H, buf, 14);
// 数据拼接注意字节序(大端模式)
acc[0] = (int16_t)((buf[0]<<8) | buf[1]);
acc[1] = (int16_t)((buf[2]<<8) | buf[3]);
acc[2] = (int16_t)((buf[4]<<8) | buf[5]);
gyro[0] = (int16_t)((buf[8]<<8) | buf[9]);
gyro[1] = (int16_t)((buf[10]<<8) | buf[11]);
gyro[2] = (int16_t)((buf[12]<<8) | buf[13]);
// 校准补偿
acc[0] -= acc_bias[0];
gyro[0] -= gyro_bias[0];
// ...其他轴类似
}
互补滤波实现:
c复制float Complementary_Filter(float acc_angle, float gyro_rate, float dt){
static float angle = 0;
const float alpha = 0.98; // 陀螺仪权重
// 加速度计角度计算(注意限制分母不为零)
float acc_angle_rad = atan2f(acc_y, sqrtf(acc_x*acc_x + acc_z*acc_z));
// 互补滤波核心公式
angle = alpha * (angle + gyro_rate * dt)
+ (1-alpha) * acc_angle_rad;
return angle * 180.0f / M_PI; // 转为角度
}
CAN初始化关键配置:
c复制CAN_InitTypeDef CAN_InitStructure;
CAN_InitStructure.CAN_TTCM = DISABLE;
CAN_InitStructure.CAN_ABOM = ENABLE; // 自动离线恢复
CAN_InitStructure.CAN_AWUM = ENABLE; // 自动唤醒
CAN_InitStructure.CAN_NART = DISABLE; // 重传使能
CAN_InitStructure.CAN_RFLM = DISABLE;
CAN_InitStructure.CAN_TXFP = DISABLE;
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_9tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_4tq;
CAN_InitStructure.CAN_Prescaler = 6; // 1MHz速率
CAN_Init(CAN1, &CAN_InitStructure);
自定义通信协议帧格式:
| 字节偏移 | 内容 | 类型 | 说明 |
|---|---|---|---|
| 0 | 帧头(0xAA) | uint8_t | 帧起始标志 |
| 1 | 长度 | uint8_t | 数据域长度 |
| 2-3 | 速度 | float | 小端格式 |
| 4-5 | 角度 | float | 小端格式 |
| 6 | 校验和 | uint8_t | 前面所有字节的异或 |
| 7 | 帧尾(0x55) | uint8_t | 帧结束标志 |
采用三级电源设计:
关键器件选型:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 电机启动时单片机复位 | 电源瞬态干扰 | 增加储能电容,加装磁珠 |
| 编码器计数不准 | 信号线过长引入噪声 | 缩短走线,添加RC滤波 |
| MPU6050数据跳变 | 电源纹波过大 | 单独LDO供电,增加滤波电容 |
| CAN通信失败 | 终端电阻未接或阻抗不匹配 | 检查120Ω终端电阻 |
| PID输出振荡 | 微分项噪声放大 | 增加低通滤波,降低Kd参数 |
定时器中断优先级设置:
内存优化:
c复制#pragma pack(push, 1) // 单字节对齐
typedef struct{
uint8_t header;
float data[2];
uint16_t crc;
}Packet_TypeDef;
#pragma pack(pop)
实时性保障:
__attribute__((section(".ramfunc"))))这个项目从硬件设计到软件调试前后历时3个月,最深刻的体会是:在嵌入式系统中,硬件可靠性是基础,软件算法是灵魂,而抗干扰设计往往是决定成败的关键。后续计划增加激光雷达避障功能,正在研究将STM32与ESP32通过SPI通信的方案。