STM32红外循线小车


橘子不是唯一的水果
原创
发布时间: 2026-01-09 09:43:45 | 阅读数 0收藏数 0评论数 0
封面
本项目基于 STM32 微控制器,设计并实现了一款 红外循线智能小车。小车通过红外循迹传感器实时检测地面黑白线路信息,结合 STM32 的高速数据处理能力与电机驱动控制,实现了稳定、可靠的自动循线行驶功能。 系统以 STM32 作为核心控制单元,负责红外传感器数据采集、电机驱动信号输出以及系统逻辑控制。小车采用 红外反射原理 判断行进路线,通过对左右循迹信号的分析,动态调整电机转速与方向,使小车始终沿既定轨迹平稳前进。同时,系统预留了串口通信、SWD 调试、电源扩展等接口,便于功能扩展与参数调试。

准备工作:

材料:

  1. 芯片型号:STM32F103RCT6
  2. 红外模块:TCRT5000
  3. 电机驱动模块:L298N
  4. 11v充电电池
  5. 亚克力车架
  6. 直流电机
  7. 适用于F103RCT6的智能小车拓展板
  8. 杜邦线
  9. ST-Link烧录器
1

项目介绍

“本项目实现一款能够在无人工干预下自主行驶的巡线小车。小车基于红外传感阵列实时识别白底黑线赛道,结合闭环控制算法精确纠正行驶轨迹,实现高速度与高稳定性的动态平衡。在抵达终点后,小车将执行原地掉头指令,并利用路径规划算法完成精准倒车入库。”

场地如图所示

2

组装


一、前期硬件搭建方案

在系统搭建初期,整车采用 6 节 1.5V 干电池串联 作为主电源,为整车提供较高的输入电压。同时,小车配置 两路红外循迹模块 用于基础的路径识别。

由于电池串联后电压较高,无法直接为 STM32 单片机供电,因此电源首先接入 L298N 电机驱动板。L298N 驱动板板载稳压模块可将输入电压 降压至 5V,再由该 5V 电源为 STM32 单片机及其扩展板供电。整体供电结构如下图所示。


二、供电架构说明

系统的供电架构可分为两部分:

  1. 控制部分供电
  2. L298N 驱动板的一部分接口与 STM32 单片机(及其扩展板)相连,为单片机提供稳定的工作电源,并通过使能端口实现对电机驱动的控制。
  3. 执行部分供电
  4. L298N 驱动板的另一部分直接为四个直流电机供电,用于驱动车轮运动,实现小车的前进、后退及转向功能。

此外,红外循迹传感器通过信号线与 STM32 扩展板上对应的 GPIO 引脚相连,单片机通过读取传感器状态实现循迹控制。


三、前期组装与问题分析

在前期组装与调试过程中,系统能够完成基础循迹功能,但在复杂赛道或弯道场景下,两路循迹模块存在明显不足

  1. 对复杂路径的识别能力有限;
  2. 无法精确反映小车相对于轨道中心的偏航状态;
  3. 在急弯或交叉路段容易出现误判。


四、后期改进方案

针对前期存在的问题,系统在后期进行了如下优化改进:

  1. 电源升级
  2. 将原有的干电池供电方案更换为 11V 可充电锂电池,提高供电稳定性并减小维护成本。
  3. 循迹模块升级
  4. 将原有的 两路红外循迹模块升级为七路循迹模块
  5. 多通道循迹模块能够提供更丰富的路径信息,使单片机可以更准确地判断小车偏航程度,从而实现更加平滑、精准的循迹控制。


五、最终成品结构说明

最终完成的小车整体结构如图所示,各部分功能说明如下:

  1. [1] 四驱直流电机:为小车提供动力,实现灵活运动;
  2. [2] 可充电电池:为系统提供稳定电源;
  3. [3] 扩展板 + STM32 主控板:作为系统核心,负责数据处理与控制逻辑;
  4. [4] L298N 电机驱动板:实现电机驱动与电源分配;
  5. [5] 七路红外循迹模块:用于高精度路径检测与循迹控制。


3

原理图

原理图说明

由于项目时间较为紧张,前期并未自行设计 PCB 电路板,而是直接采用了常规的智能小车 STM32 拓展板作为控制核心。该拓展板在设计时预留了大量功能接口,因此板上包含了部分本项目中未实际使用的排针接口。

在本系统中,循迹模块采用的是七路红外循迹传感器,需要至少七路 GPIO 输入引脚用于采集传感器状态。由于拓展板原有的循迹接口数量不足,设计中复用了拓展板上其他功能模块对应的排针引脚,以满足七路循迹信号的接入需求。


拓展板原理图说明

拓展板整体电路基于 STM32 最小系统进行设计,并在此基础上集成了电机驱动、电源分配以及多种外设接口。由于使用的是成熟的成品拓展板,其原理图中包含了多种功能模块的接口定义,但本项目仅使用其中与电机驱动、循迹传感器及调试接口相关的部分。


引脚复用及调试问题分析

在小车连接 ST-Link 进行在线调试(未连接 USB 通信接口)时,发现系统运行存在异常,部分功能会受到影响。经过分析,推测该问题与 STM32 引脚复用冲突 有关。

ST-Link 在调试过程中需要连接 SWDIO 引脚,而该引脚与 PA13 引脚复用。当 ST-Link 连接到开发板时,其内部调试芯片可能会对 PA13 引脚的电平状态产生影响,从而干扰系统中基于该引脚的正常功能。

为避免该问题,在程序烧录完成后,断开 ST-Link 与开发板的连接,再对小车进行功能测试,可有效消除调试接口对系统运行的影响。


4

电机编码

定时器 PWM 初始化函数

/**
* @brief TIM4 PWM 初始化
* @param autoReload: 自动重装载值(ARR)
* @param prescaler : 预分频系数(PSC)
* @note PWM 用于控制左右电机转速
*/
void TIM4_PWM_Init(uint16_t autoReload, uint16_t prescaler)
{
TIM_TimeBaseInitTypeDef timBaseConfig;
TIM_OCInitTypeDef timOcConfig;
GPIO_InitTypeDef gpioConfig;

/* 使能外设时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);

/* ========== 电机方向控制引脚(普通 GPIO 输出) ========== */

// 左电机方向 IN1(PB10)
gpioConfig.GPIO_Pin = GPIO_Pin_10;
gpioConfig.GPIO_Mode = GPIO_Mode_Out_PP;
gpioConfig.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpioConfig);

// 左电机方向 IN2(PC2)
gpioConfig.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOC, &gpioConfig);

// 右电机方向 IN3(PB12)
gpioConfig.GPIO_Pin = GPIO_Pin_12;
GPIO_Init(GPIOB, &gpioConfig);

// 右电机方向 IN4(PB13)
gpioConfig.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOB, &gpioConfig);

/* ========== PWM 输出引脚(复用推挽输出) ========== */

// 左电机 PWM ENA(PB6 -> TIM4_CH1)
gpioConfig.GPIO_Pin = GPIO_Pin_6;
gpioConfig.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &gpioConfig);

// 右电机 PWM ENB(PB9 -> TIM4_CH4)
gpioConfig.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &gpioConfig);

/* ========== 定时器时基配置 ========== */

timBaseConfig.TIM_Period = autoReload;
timBaseConfig.TIM_Prescaler = prescaler;
timBaseConfig.TIM_ClockDivision = TIM_CKD_DIV1;
timBaseConfig.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &timBaseConfig);

/* ========== PWM 输出比较配置 ========== */

timOcConfig.TIM_OCMode = TIM_OCMode_PWM1;
timOcConfig.TIM_OutputState = TIM_OutputState_Enable;
timOcConfig.TIM_Pulse = 0; // 初始占空比为 0
timOcConfig.TIM_OCPolarity = TIM_OCPolarity_High;

TIM_OC1Init(TIM4, &timOcConfig); // 左电机
TIM_OC4Init(TIM4, &timOcConfig); // 右电机

TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);

TIM_ARRPreloadConfig(TIM4, ENABLE);
TIM_Cmd(TIM4, ENABLE);
}


左右电机速度控制函数

说明:
本函数只负责 速度大小
电机方向由方向控制函数决定


/**
* @brief 设置左电机转速
* @param speed: 0~100(仅表示速度大小)
*/
void Motor_Left_SetSpeed(int8_t speed)
{
uint16_t pwmValue;

if (speed > 100)
speed = 100;
if (speed < 0)
speed = 0;

pwmValue = speed * 72; // 1007200
TIM_SetCompare1(TIM4, pwmValue);
}

/**
* @brief 设置右电机转速
* @param speed: 0~100(仅表示速度大小)
*/
void Motor_Right_SetSpeed(int8_t speed)
{
uint16_t pwmValue;

if (speed > 100)
speed = 100;
if (speed < 0)
speed = 0;

pwmValue = speed * 72;
TIM_SetCompare4(TIM4, pwmValue);
}


基础运动控制函数


1. 前进


void Car_MoveForward(int8_t speed, uint16_t duration)
{
// 左轮正转
L298N_IN1 = 0;
L298N_IN2 = 1;

// 右轮正转
L298N_IN3 = 1;
L298N_IN4 = 0;

Motor_Left_SetSpeed(speed);
Motor_Right_SetSpeed(speed);

delay_ms(duration);
}

2. 停止


void Car_Stop(uint16_t duration)
{
L298N_IN1 = 0;
L298N_IN2 = 0;
L298N_IN3 = 0;
L298N_IN4 = 0;

Motor_Left_SetSpeed(0);
Motor_Right_SetSpeed(0);

delay_ms(duration);
}

3. 原地旋转


void Car_SpinLeft(int8_t speed, uint16_t duration)
{
L298N_IN1 = 0;
L298N_IN2 = 1;
L298N_IN3 = 0;
L298N_IN4 = 1;

Motor_Left_SetSpeed(speed);
Motor_Right_SetSpeed(speed);

delay_ms(duration);
}

void Car_SpinRight(int8_t speed, uint16_t duration)
{
L298N_IN1 = 1;
L298N_IN2 = 0;
L298N_IN3 = 1;
L298N_IN4 = 0;

Motor_Left_SetSpeed(speed);
Motor_Right_SetSpeed(speed);

delay_ms(duration);
}


循迹传感器数据处理

1. 全局变量定义


uint8_t sensorData[7]; // 七路红外传感器
uint8_t offsetLeft; // 左偏移权值
uint8_t offsetRight; // 右偏移权值
uint8_t offsetCenter; // 中间传感器

2. 黑线偏移计算函数


void LineSensor_Read(void)
{
sensorData[0] = l3;
sensorData[1] = l2;
sensorData[2] = l1;
sensorData[3] = m;
sensorData[4] = r1;
sensorData[5] = r2;
sensorData[6] = r3;

offsetLeft = sensorData[0] * 3 + sensorData[1] * 2 + sensorData[2];
offsetRight = sensorData[4] + sensorData[5] * 2 + sensorData[6] * 3;
offsetCenter = sensorData[3];
}


循迹控制主逻辑


void LineTracking_Task(void)
{
LineSensor_Read();

if (offsetRight > offsetLeft)
{
if (offsetRight > 3)
Car_SpinRight(75, 100);
else if (offsetRight >= 2)
Car_SpinRight(65, 2);
else
Car_MoveForward(80, 5);
}
else if (offsetLeft > offsetRight)
{
if (offsetLeft > 3)
Car_SpinLeft(75, 100);
else if (offsetLeft >= 2)
Car_SpinLeft(65, 2);
else
Car_MoveForward(80, 5);
}
else if (offsetCenter)
{
Car_MoveForward(85, 5);
}
}

5

完成


阅读记录0
点赞0
收藏0
禁止 本文未经作者允许授权,禁止转载
猜你喜欢
评论/提问(已发布 0 条)
评论 评论
收藏 收藏
分享 分享
pdf下载 下载
pdf下载 举报