基于STM32的智能小车设计

AI助手已提取文章相关产品:

基于STM32的智能循迹避障小车设计实践

在高校电子信息类课程中,总有一些项目能真正让学生把“纸上谈兵”变成“真枪实弹”——智能小车就是其中之一。它不像单纯的LED闪烁实验那样简单,也不像无人机飞控那样遥不可及,而是一个刚刚好的“中间态”:既有硬件搭建的乐趣,又有算法调试的挑战,还能看到自己的代码让机器动起来。

我们最近完成的这款基于STM32F103C8T6的智能小车,集成了红外循迹与超声波避障功能。从原理图绘制到代码烧录,从传感器校准到路径决策逻辑优化,整个过程几乎涵盖了嵌入式开发的所有核心环节。更重要的是,它的成本控制在200元以内,完全适合教学批量部署。


为什么选STM32F103C8T6?

说到主控芯片,很多人第一反应是51或Arduino,但当我们需要处理多任务、高实时性控制时,这些8位单片机就显得力不从心了。STM32F103C8T6虽然只是“蓝丸”开发板上的那颗不起眼的小芯片,但它基于ARM Cortex-M3内核,主频高达72MHz,自带64KB Flash和20KB RAM,还配备了丰富的定时器、ADC和通信接口资源。

实际使用中最让人安心的是它的外设成熟度。比如我们要用PWM驱动电机,直接调用TIM3输出即可;要读取多个红外传感器状态?PA0~PA3一组GPIO输入搞定;测距需要精确计时?TIM2配合输入捕获模式轻松实现微秒级精度。更别说Keil、STM32CubeIDE、PlatformIO等工具链的支持,让开发效率大幅提升。

下面这段初始化LED的代码,看似简单,却是所有调试工作的起点:

void LED_Init(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
}

PC13接了一个共阴极LED,程序运行时通过 GPIO_ResetBits(GPIOC, GPIO_Pin_13) 点亮作为运行指示。别小看这个灯,它能在系统卡死时帮你判断是否进入了死循环,或者中断有没有正常触发。


红外循迹:低成本路径识别的可靠方案

市面上做循迹小车最常用的还是红外传感器阵列。我们用了四路数字型红外模块(如TCRT5000),并排安装在车体底部,间距约1.8cm,刚好略小于黑线宽度(通常为2cm)。这种布局既能保证在直线上有足够覆盖范围,又能在转弯时提供明确的方向偏差信号。

工作原理其实很直观:红外发射管照向地面,黑色胶带吸光强、反射弱,白色地板则相反。接收端根据反射强度输出高低电平——压在线上为低,偏离为高(具体逻辑取决于比较器电路设计)。

关键在于如何快速准确地读取这四个传感器的状态,并做出转向决策。我们采用了一种紧凑的数据打包方式:

uint16_t Read_Track_Sensors(void) {
    uint16_t sensor_val = 0;
    sensor_val |= (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) << 0);
    sensor_val |= (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) << 1);
    sensor_val |= (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2) << 2);
    sensor_val |= (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) << 3);
    return sensor_val;
}

返回值是一个4位二进制数,代表当前各传感器的状态。例如:
- 0b0110 → 中间两个压线,说明处于直线段;
- 0b0011 → 右侧两个压线,说明路径向右偏,应左转纠正;
- 0b1100 → 左侧两个压线,需右转;
- 0b0000 → 所有传感器都在白区,可能已脱轨,进入搜索模式。

这里有个工程经验:不要一检测到状态变化就立即转向。现实中地面反光不均、车身震动都会导致误判。我们在软件中加入了简单的延时滤波机制——连续两次采样结果一致才执行动作,有效减少了“蛇形走位”。


超声波避障:不只是测个距离那么简单

HC-SR04几乎是每个初学者都会接触的超声波模块,便宜、易用、资料多。但真正在动态环境中稳定工作,远没有想象中那么简单。

其基本流程是:给Trig引脚一个≥10μs的高脉冲,模块自动发出40kHz超声波;遇到障碍物后回波被接收,Echo引脚拉高,持续时间即为往返时间。距离计算公式为:

$$
\text{Distance (cm)} = \frac{\text{Echo高电平时间 (μs)} \times 0.034}{2}
$$

我们最初用轮询方式实现测距:

float Get_Distance(void) {
    float distance = 0;
    GPIO_SetBits(GPIOB, GPIO_Pin_1);  // Trig
    Delay_us(10);
    GPIO_ResetBits(GPIOB, GPIO_Pin_1);

    while (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0));  // 等待上升沿
    TIM_Cmd(TIM2, ENABLE);
    while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0));   // 等待下降沿
    TIM_Cmd(TIM2, DISABLE);

    uint32_t time_us = TIM_GetCounter(TIM2);
    distance = (float)(time_us * 0.034 / 2);
    TIM_SetCounter(TIM2, 0);
    return distance;
}

这种方式简单直接,但在主循环中频繁调用会阻塞其他任务。后来我们改用定时器输入捕获+中断的方式,大大提升了系统的响应能力和测量精度。

不过更大的问题是 误检 。比如斜面墙壁会导致回波偏移,无法返回;软质材料(如布料沙发)吸音严重,测不出有效距离;甚至有时地面反射也会造成干扰。为此我们做了几点改进:

  1. 设置最小有效距离阈值(如15cm) ,低于该值才认为是真实障碍;
  2. 多次测量取平均 ,剔除异常值;
  3. 增加探测频率 ,每100ms测一次,避免因单次误差导致急停;
  4. 结合运动状态判断 ,若小车静止时距离突变,优先怀疑传感器异常而非环境变化。

电机驱动:L298N真的够用吗?

L298N作为一款经典双H桥驱动芯片,虽然封装老旧、发热较大,但在教学场景下依然表现出色。它支持最高35V供电,每通道持续电流2A,足以带动常见的TT马达减速电机。

连接方式也很清晰:
- IN1/IN2 控制左电机正反转
- IN3/IN4 控制右电机
- ENA/ENB 接PWM信号实现调速

我们使用TIM3_CH1(PB6)输出PWM来调节速度:

void PWM_Motor_Init(void) {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_TimeBaseStructure.TIM_Period = 99;      // 100步周期
    TIM_TimeBaseStructure.TIM_Prescaler = 71;   // 72MHz → 1MHz
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 50;         // 初始占空比50%
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);

    TIM_Cmd(TIM3, ENABLE);
}

通过修改 TIM_Pulse 值即可动态调整占空比,实现无级调速。但我们发现一个问题:电机启动瞬间电流冲击大,容易打滑。于是加入了渐变加速策略——每次增加5%占空比,间隔50ms,直到达到目标速度。同理,停车时也逐步降速,显著提升了行驶平稳性。

电源方面,采用两节18650串联提供7.4V,经LM2596降压至5V供给STM32和传感器。特别注意的是,L298N的逻辑供电必须独立稳定,否则可能导致控制信号紊乱。


系统整合:从模块拼接到闭环控制

当各个模块单独测试都正常后,真正的挑战才开始:如何让它们协同工作?

我们的主控逻辑采用状态机结构,默认进入“循迹优先”模式。每50ms执行一次传感器采集:

while (1) {
    uint16_t track_state = Read_Track_Sensors();
    float dist = Get_Distance();

    if (dist < 20.0f) {
        // 进入避障模式
        Stop();
        Backward(800);  // 后退800ms
        TurnLeft(500);  // 尝试左转
        delay_ms(100);
    } else {
        // 继续循迹
        Track_Control(track_state);
    }
}

Track_Control() 函数根据传感器组合决定动作:
- 0110 :直行
- 0011 :左转(右轮快)
- 1100 :右转(左轮快)
- 0000 :原地旋转查找路径

为了防止在十字路口或断线处无限打转,我们加入了超时机制:若连续10次未能恢复轨迹,则自动切换至随机探索模式,尝试重新定位路线。

调试过程中最有效的手段是串口输出。我们将传感器原始数据、当前状态、电机指令等信息通过USART发送到PC端,用串口助手实时监控,极大加快了问题排查速度。比如有一次发现小车总是往右偏,查看日志才发现右侧红外传感器灵敏度偏低,重新调节电位器后恢复正常。


遇到的问题与实战经验

任何项目都不可能一帆风顺,以下是几个典型问题及解决方案:

问题现象 根本原因 解决方案
循迹时左右晃动严重 传感器响应延迟 + 控制过于激进 加入状态滤波,限制转向角度
超声波偶尔返回0或超大量程 回波未接收到或中断丢失 增加重试机制,三次无效则取上次有效值
电池电压下降后传感器误判 供电波动影响比较器阈值 使用独立稳压模块,确保传感器电压稳定
电机启停抖动大 PWM跳变更剧烈 实现软启动/停止,逐步增减占空比

还有一些细节值得注意:
- 机械结构上,红外传感器尽量靠近前轮轴线,减少转向滞后;
- 超声波模块居中安装,避免车身遮挡形成盲区;
- 所有引脚连线做好标记,避免插错烧芯片;
- 关键变量添加注释,方便后期维护。


教学之外的价值:不止是一次课程设计

这套系统看似简单,却完整体现了嵌入式系统的三大核心环节: 感知—决策—执行 。学生不仅能掌握GPIO、ADC、PWM、定时器等底层操作,更能理解如何将多个模块整合成一个协同工作的整体。

更重要的是,它具备极强的可扩展性:
- 换上蓝牙模块(HC-05),就能实现手机遥控;
- 加装编码器,结合PID算法,实现恒速巡航;
- 替换为OpenMV摄像头,迈入图像识别领域;
- 多台小车联网,探索群体协作行为。

甚至可以作为毕业设计的基础平台,进一步引入惯性导航、SLAM建图、ROS通信等功能。

这种高度集成且成本可控的设计思路,正体现了现代嵌入式教育的一个趋势:不再追求“炫技”,而是强调“可用、可靠、可演进”。让学生从动手实践中建立系统思维,才是这类项目真正的价值所在。


如今,这辆小车已经跑过了实验室的每一条预设轨道,也经历过无数次撞墙、脱轨、重启。但它每一次平稳地沿着黑线前进,或是灵巧地绕开前方障碍,都在无声诉说着一段关于代码、电路与控制逻辑的故事——而这,正是嵌入式世界的魅力所在。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

您可能感兴趣的与本文相关内容

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值