STM32F103RC+L298N+五路红外循迹小车实战指南
引言
循迹小车作为嵌入式开发的经典入门项目,不仅能帮助初学者快速掌握硬件连接、传感器应用和电机控制等核心技能,还能通过实际调试过程培养解决问题的能力。本文将基于STM32F103RC微控制器和L298N电机驱动模块,结合五路红外传感器,带你从零开始打造一台能稳定跑直线的智能小车。
不同于简单的代码演示,我们会重点关注整个项目的完整实现流程——从元器件选型、电路设计到软件调试的每个环节都会详细展开。特别针对初学者容易遇到的电源干扰、接线错误、PID参数调节等痛点问题,提供经过实战验证的解决方案。
1. 硬件选型与电路设计
1.1 核心元器件清单
一个可靠的循迹小车系统需要精心选择每个组件。以下是经过多次验证的硬件配置方案:
| 组件名称 | 型号/参数 | 数量 | 备注说明 |
|---|---|---|---|
| 主控芯片 | STM32F103RC | 1 | 具备足够GPIO和PWM资源 |
| 电机驱动模块 | L298N | 1 | 支持双路直流电机驱动 |
| 红外传感器 | TCRT5000 | 5 | 建议购买带比较器输出的模块 |
| 直流减速电机 | TT马达(6V/200RPM) | 2 | 带编码器版本更佳 |
| 电源模块 | LM2596降压模块 | 1 | 输入7-12V,输出5V/3A |
| 电池 | 18650锂电池(7.4V) | 2 | 需配套电池盒 |
提示:红外传感器建议选择已经集成比较器的模块,可以省去外部电路设计,降低入门难度。
1.2 电源系统设计
电源稳定性是影响小车性能的关键因素,常见问题包括:
- 电机启动时导致MCU复位
- 传感器供电不稳造成误检测
- L298N驱动电压不足影响电机扭矩
推荐采用双路独立供电方案:
- 动力电源:7.4V锂电池直接供给L298N的电机驱动端
- 控制电源:通过LM2596降压到5V,为STM32和传感器供电
// 电源连接示意图 电池正极 → L298N(12V输入) 电池正极 → LM2596(输入) → 5V输出 → STM32_VDD1.3 传感器布局优化
五路红外传感器的安装位置直接影响循迹效果。经过实测,推荐以下布局参数:
- 传感器间距:1.5-2cm(适应常见黑色电工胶带轨迹)
- 离地高度:0.5-1cm(需根据实际场地调整)
- 安装角度:略微向前倾斜10-15度(提高前瞻性)
[传感器排列示意图] 左2 ─ 左1 ─ 中 ─ 右1 ─ 右22. 硬件连接与调试
2.1 STM32与L298N连接
L298N模块的正确连接是控制电机的关键。以下是经过优化的接线方案:
| L298N引脚 | STM32连接 | 功能说明 |
|---|---|---|
| ENA | PA8 (TIM1_CH1) | 左电机PWM控制 |
| IN1 | PB12 | 左电机方向控制1 |
| IN2 | PB13 | 左电机方向控制2 |
| ENB | PA9 (TIM1_CH2) | 右电机PWM控制 |
| IN3 | PB14 | 右电机方向控制1 |
| IN4 | PB15 | 右电机方向控制2 |
注意:务必先确认电机转向是否正确,再固定小车结构。错误的转向会导致控制逻辑完全相反。
2.2 红外传感器接口配置
五路红外传感器建议采用以下GPIO分配方案:
#define IR_LEFT2 PC0 #define IR_LEFT1 PC1 #define IR_MIDDLE PC2 #define IR_RIGHT1 PC3 #define IR_RIGHT2 PC4初始化代码示例:
void IR_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOC_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); }2.3 常见硬件问题排查
在初期调试阶段,可能会遇到以下典型问题:
电机不转:
- 检查L298N使能跳线帽是否已移除
- 确认PWM信号是否正常输出(可用示波器或LED测试)
- 测量电机两端电压是否达到预期
传感器误触发:
- 调整传感器电位器,改变检测灵敏度
- 检查供电电压是否稳定(应在4.5-5.5V之间)
- 避免环境光干扰(可在传感器上方加遮光罩)
电源不稳定:
- 在STM32的VDD与GND之间并联100μF电容
- 电机电源与控制电源地线在一点共地
3. 软件算法实现
3.1 基础循迹逻辑设计
五路传感器可以提供丰富的路径信息,我们采用状态机方式处理:
typedef enum { ON_TRACK, // 00000 SLIGHT_LEFT, // 00100 SLIGHT_RIGHT, // 00010 SHARP_LEFT, // 11000 SHARP_RIGHT, // 00011 LOST_TRACK // 11111 } TrackState; TrackState GetTrackState(void) { uint8_t sensors = (HAL_GPIO_ReadPin(GPIOC, IR_LEFT2) << 4) | (HAL_GPIO_ReadPin(GPIOC, IR_LEFT1) << 3) | (HAL_GPIO_ReadPin(GPIOC, IR_MIDDLE) << 2) | (HAL_GPIO_ReadPin(GPIOC, IR_RIGHT1) << 1) | HAL_GPIO_ReadPin(GPIOC, IR_RIGHT2); switch(sensors) { case 0b00100: return ON_TRACK; case 0b01100: return SLIGHT_LEFT; // 其他状态判断... default: return LOST_TRACK; } }3.2 PID控制算法实现
为了让小车跑得更直更稳,我们引入增量式PID算法:
typedef struct { float Kp, Ki, Kd; float last_error, prev_error; float integral; } PID_Controller; float PID_Update(PID_Controller* pid, float error) { float derivative = error - pid->last_error; pid->integral += error; float output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative; pid->prev_error = pid->last_error; pid->last_error = error; return output; }参数整定建议:
- 先调P(比例),让小车能基本跟随但不振荡
- 再调D(微分),抑制过冲和振荡
- 最后调I(积分),消除静态误差
3.3 电机控制策略
基于PID输出,我们需要动态调整左右电机PWM:
void Motor_Control(float pid_output) { // 基础速度(可根据需要调整) uint16_t base_speed = 500; // 计算左右电机速度 uint16_t left_speed = base_speed - pid_output; uint16_t right_speed = base_speed + pid_output; // 限制PWM范围 left_speed = (left_speed > 999) ? 999 : left_speed; right_speed = (right_speed > 999) ? 999 : right_speed; // 更新PWM __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, left_speed); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, right_speed); }4. 系统调试与优化
4.1 分阶段调试方法
建议按照以下顺序逐步调试:
传感器测试:
- 单独测试每个传感器的开关状态
- 确认检测距离和灵敏度一致
电机测试:
- 验证正反转控制逻辑
- 测试PWM调速是否线性
开环测试:
- 固定PWM值,观察小车直线行驶偏差
闭环调试:
- 先调P参数,再引入D和I
- 记录不同参数下的运行效果
4.2 性能优化技巧
经过多次实测,总结出以下优化经验:
机械对称性:
- 确保车轮直径一致(可用卡尺测量)
- 检查车体左右重量分布平衡
软件滤波:
- 对传感器信号进行软件消抖
- 采用移动平均滤波处理PWM输出
// 简单的移动平均滤波实现 #define FILTER_SIZE 5 float speed_filter[FILTER_SIZE] = {0}; uint8_t filter_index = 0; float ApplyFilter(float new_value) { speed_filter[filter_index] = new_value; filter_index = (filter_index + 1) % FILTER_SIZE; float sum = 0; for(int i=0; i<FILTER_SIZE; i++) { sum += speed_filter[i]; } return sum / FILTER_SIZE; }- 轨迹适应性:
- 针对不同宽度轨迹调整传感器阈值
- 根据转弯半径动态调整PID参数
4.3 常见问题解决方案
在实际项目中,我们遇到过这些问题及解决方法:
小车走S形路线:
- 检查传感器安装是否水平
- 降低P参数,增加D参数
- 确认电机转速是否匹配
过弯时冲出轨迹:
- 提高转弯时的内侧电机减速幅度
- 增加前瞻性(如将中间传感器前移)
直线行驶有偏差:
- 校准电机基准PWM值
- 检查车轮是否打滑
- 在平整度高的场地上测试
5. 完整代码架构
5.1 工程文件结构
一个组织良好的项目结构能提高开发效率:
├── Core/ │ ├── Src/ │ │ ├── main.c # 主循环和初始化 │ │ ├── motor.c # 电机控制接口 │ │ ├── sensor.c # 红外传感器处理 │ │ └── pid.c # PID算法实现 │ └── Inc/ # 对应头文件 ├── Drivers/ # HAL库文件 └── STM32F103C8T6_FLASH.ld # 链接脚本5.2 主控制逻辑实现
main.c中的核心处理流程:
int main(void) { // 硬件初始化 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM1_Init(); IR_Init(); // PID控制器初始化 PID_Controller pid = {.Kp = 0.5, .Ki = 0.01, .Kd = 0.2}; while (1) { TrackState state = GetTrackState(); float error = CalculateError(state); float output = PID_Update(&pid, error); Motor_Control(output); HAL_Delay(10); // 10ms控制周期 } }5.3 关键函数详解
CalculateError()根据传感器状态计算偏差:
float CalculateError(TrackState state) { switch(state) { case ON_TRACK: return 0.0f; case SLIGHT_LEFT: return -0.3f; case SLIGHT_RIGHT: return 0.3f; case SHARP_LEFT: return -1.0f; case SHARP_RIGHT: return 1.0f; default: return 0.0f; // 丢失轨迹保持直行 } }Motor_Control()增强版带死区补偿:
void Motor_Control(float output) { static float left_comp = 1.0, right_comp = 1.0; // 校准模式:记录左右电机补偿系数 if(calibration_mode) { // ...校准逻辑... return; } uint16_t base = 500; uint16_t left = base * left_comp - output * 200; uint16_t right = base * right_comp + output * 200; // 应用PWM __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, left); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, right); }6. 进阶功能扩展
6.1 无线调试接口
添加蓝牙或2.4G模块实现实时参数调整:
// 通过串口接收PID参数 void USART1_IRQHandler(void) { if(USART1->SR & USART_SR_RXNE) { char cmd = USART1->DR; if(cmd == 'P') { // 接收并更新P参数 sscanf(rx_buffer, "P%f", &pid.Kp); } // 其他命令处理... } }6.2 速度闭环控制
利用编码器实现更精确的速度控制:
- 配置TIM2/TIM3为编码器模式
- 定期读取计数器值计算实际转速
- 增加速度PID环
// 编码器初始化 TIM_Encoder_InitTypeDef encoder = {0}; encoder.EncoderMode = TIM_ENCODERMODE_TI12; encoder.IC1Polarity = TIM_ICPOLARITY_RISING; encoder.IC2Polarity = TIM_ICPOLARITY_RISING; HAL_TIM_Encoder_Init(&htim2, &encoder); HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);6.3 多模式切换
通过按键实现不同运行模式:
typedef enum { MODE_MANUAL, // 手动控制 MODE_LINE_TRACK,// 循迹模式 MODE_CALIBRATE // 传感器校准 } OperationMode; void HandleModeSwitch(void) { if(HAL_GPIO_ReadPin(BUTTON_GPIO_Port, BUTTON_Pin) == GPIO_PIN_RESET) { HAL_Delay(50); // 消抖 current_mode = (current_mode + 1) % 3; UpdateIndicatorLED(); } }7. 项目经验分享
在实际开发这个小车的过程中,有几个关键点值得特别注意:
电源布线:最初我们将所有电源线简单地拧在一起,结果电机启动时经常导致单片机复位。后来改用星型接地和增加去耦电容后,系统稳定性大幅提升。
传感器校准:五路红外传感器的灵敏度实际上存在差异,我们增加了上电自动校准功能,让小车在启动时自动记录每路传感器的基准值,显著提高了不同光照条件下的适应性。
机械结构:3D打印的车架虽然美观,但发现长时间运行后会有轻微变形影响行驶直线度。改用2mm铝板切割后,配合橡胶减震垫,解决了这个问题。
参数调节:PID参数不是一成不变的,我们最终实现了一套根据转弯半径动态调整参数的算法,当检测到急弯时自动提高微分系数,使小车能更平稳过弯。