欢迎光临
我们一直在努力

STM32项目实战:基于STM32F103的智能循迹避障小车

文章目录

    • 一、项目概述
    • 二、硬件清单
    • 三、硬件电路设计与接线
      • 3.1 核心电路原理
      • 3.2 详细接线表
    • 四、软件开发环境搭建
      • 4.1 环境准备
      • 4.2 工程文件结构
    • 五、代码编写(详细版)
      • 5.1 电机驱动模块
        • 文件名:motor.h
        • 文件名:motor.c
      • 5.2 红外循迹模块
        • 文件名:track.h
        • 文件名:track.c
      • 5.3 超声波避障模块
        • 文件名:ultrasonic.h
        • 文件名:ultrasonic.c
      • 5.4 主函数(核心逻辑)
        • 文件名:main.c
    • 六、硬件组装与调试
      • 6.1 硬件组装步骤
      • 6.2 代码下载与调试
      • 6.3 常见问题排查
    • 七、功能扩展建议
      • 总结

一、项目概述

本项目基于STM32F103C8T6最小系统板,实现一款具备循迹和避障功能的智能小车。核心功能包括:

  • 循迹:通过红外循迹模块识别地面黑色轨迹线,自动沿轨迹行驶;
  • 避障:通过超声波模块检测前方障碍物,当距离小于设定阈值时自动停车/绕行;
  • 驱动控制:使用L298N电机驱动模块控制直流减速电机,实现小车前进、后退、转向。
  • 本教程面向零基础小白,从硬件选型、电路接线、代码编写到实物调试,全程详细讲解,确保能落地实现。

    二、硬件清单

    器件名称数量作用
    STM32F103C8T6最小系统板 1 主控核心
    L298N电机驱动模块 1 驱动直流电机
    直流减速电机 4 小车动力来源
    智能小车底盘 1 承载所有硬件
    红外循迹模块 3 识别循迹线(左/中/右)
    HC-SR04超声波模块 1 检测前方障碍物距离
    12V锂电池 1 供电(电机)
    5V稳压模块(LM1117) 1 给STM32和传感器供电
    杜邦线(公对公/公对母) 若干 电路连接
    面包板 1 临时接线

    三、硬件电路设计与接线

    3.1 核心电路原理

    以下是系统整体硬件连接逻辑流程图,清晰展示各模块与STM32的交互关系:

    #mermaid-svg-eg6KmjU3JfOSrBPO{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eg6KmjU3JfOSrBPO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eg6KmjU3JfOSrBPO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eg6KmjU3JfOSrBPO .error-icon{fill:#552222;}#mermaid-svg-eg6KmjU3JfOSrBPO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eg6KmjU3JfOSrBPO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eg6KmjU3JfOSrBPO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eg6KmjU3JfOSrBPO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eg6KmjU3JfOSrBPO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eg6KmjU3JfOSrBPO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eg6KmjU3JfOSrBPO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eg6KmjU3JfOSrBPO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eg6KmjU3JfOSrBPO .marker.cross{stroke:#333333;}#mermaid-svg-eg6KmjU3JfOSrBPO svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eg6KmjU3JfOSrBPO p{margin:0;}#mermaid-svg-eg6KmjU3JfOSrBPO .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-eg6KmjU3JfOSrBPO .cluster-label text{fill:#333;}#mermaid-svg-eg6KmjU3JfOSrBPO .cluster-label span{color:#333;}#mermaid-svg-eg6KmjU3JfOSrBPO .cluster-label span p{background-color:transparent;}#mermaid-svg-eg6KmjU3JfOSrBPO .label text,#mermaid-svg-eg6KmjU3JfOSrBPO span{fill:#333;color:#333;}#mermaid-svg-eg6KmjU3JfOSrBPO .node rect,#mermaid-svg-eg6KmjU3JfOSrBPO .node circle,#mermaid-svg-eg6KmjU3JfOSrBPO .node ellipse,#mermaid-svg-eg6KmjU3JfOSrBPO .node polygon,#mermaid-svg-eg6KmjU3JfOSrBPO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eg6KmjU3JfOSrBPO .rough-node .label text,#mermaid-svg-eg6KmjU3JfOSrBPO .node .label text,#mermaid-svg-eg6KmjU3JfOSrBPO .image-shape .label,#mermaid-svg-eg6KmjU3JfOSrBPO .icon-shape .label{text-anchor:middle;}#mermaid-svg-eg6KmjU3JfOSrBPO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eg6KmjU3JfOSrBPO .rough-node .label,#mermaid-svg-eg6KmjU3JfOSrBPO .node .label,#mermaid-svg-eg6KmjU3JfOSrBPO .image-shape .label,#mermaid-svg-eg6KmjU3JfOSrBPO .icon-shape .label{text-align:center;}#mermaid-svg-eg6KmjU3JfOSrBPO .node.clickable{cursor:pointer;}#mermaid-svg-eg6KmjU3JfOSrBPO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eg6KmjU3JfOSrBPO .arrowheadPath{fill:#333333;}#mermaid-svg-eg6KmjU3JfOSrBPO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eg6KmjU3JfOSrBPO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eg6KmjU3JfOSrBPO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eg6KmjU3JfOSrBPO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eg6KmjU3JfOSrBPO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eg6KmjU3JfOSrBPO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eg6KmjU3JfOSrBPO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eg6KmjU3JfOSrBPO .cluster text{fill:#333;}#mermaid-svg-eg6KmjU3JfOSrBPO .cluster span{color:#333;}#mermaid-svg-eg6KmjU3JfOSrBPO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eg6KmjU3JfOSrBPO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eg6KmjU3JfOSrBPO rect.text{fill:none;stroke-width:0;}#mermaid-svg-eg6KmjU3JfOSrBPO .icon-shape,#mermaid-svg-eg6KmjU3JfOSrBPO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eg6KmjU3JfOSrBPO .icon-shape p,#mermaid-svg-eg6KmjU3JfOSrBPO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eg6KmjU3JfOSrBPO .icon-shape rect,#mermaid-svg-eg6KmjU3JfOSrBPO .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eg6KmjU3JfOSrBPO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eg6KmjU3JfOSrBPO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eg6KmjU3JfOSrBPO :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    供电

    降压至5V

    5V供电

    5V供电

    5V供电

    GPIO控制

    GPIO输入

    Trig/Echo GPIO

    驱动

    12V锂电池

    L298N电机驱动模块

    LM1117稳压模块

    STM32F103C8T6

    红外循迹模块

    HC-SR04超声波模块

    直流减速电机

    3.2 详细接线表

    STM32F103引脚外接模块模块引脚备注
    PA0 红外循迹模块(左) OUT 低电平表示检测到黑线
    PA1 红外循迹模块(中) OUT 低电平表示检测到黑线
    PA2 红外循迹模块(右) OUT 低电平表示检测到黑线
    PA3 HC-SR04 Trig 超声波触发引脚
    PA4 HC-SR04 Echo 超声波回响引脚
    PB0 L298N IN1 左电机正转控制
    PB1 L298N IN2 左电机反转控制
    PB2 L298N IN3 右电机正转控制
    PB3 L298N IN4 右电机反转控制
    5V 红外循迹模块 VCC 传感器供电
    5V HC-SR04 VCC 传感器供电
    GND 所有模块 GND 共地,必须接在一起
    12V L298N 12V 电机驱动供电
    L298N输出端 直流电机 电机接线 左电机接OUT1/OUT2,右电机接OUT3/OUT4

    四、软件开发环境搭建

    4.1 环境准备

  • 下载并安装Keil MDK-ARM V5(推荐版本:5.38),注册激活(用于STM32代码编译);
  • 下载STM32F103C8T6的器件库(Keil中安装Device Pack:STM32F1xx_DFP);
  • 准备ST-Link V2下载器,安装对应的驱动程序;
  • 配置Keil工程,选择STM32F103C8T6芯片,启用微库(MicroLIB)。
  • 4.2 工程文件结构

    STM32_Car_Project/
    ├── Core/
    │ ├── Inc/
    │ │ ├── main.h
    │ │ ├── stm32f1xx_hal_conf.h
    │ │ ├── motor.h // 电机驱动头文件
    │ │ ├── track.h // 循迹模块头文件
    │ │ ├── ultrasonic.h // 超声波模块头文件
    │ ├── Src/
    │ │ ├── main.c
    │ │ ├── stm32f1xx_hal_msp.c
    │ │ ├── stm32f1xx_it.c
    │ │ ├── motor.c // 电机驱动源码
    │ │ ├── track.c // 循迹模块源码
    │ │ ├── ultrasonic.c // 超声波模块源码
    ├── Drivers/ // STM32官方驱动库
    └── MDK-ARM/ // Keil工程文件

    五、代码编写(详细版)

    5.1 电机驱动模块

    文件名:motor.h

    #ifndef __MOTOR_H
    #define __MOTOR_H

    #include "stm32f1xx_hal.h"

    // 电机控制引脚定义
    #define IN1_PIN GPIO_PIN_0
    #define IN1_PORT GPIOB
    #define IN2_PIN GPIO_PIN_1
    #define IN2_PORT GPIOB
    #define IN3_PIN GPIO_PIN_2
    #define IN3_PORT GPIOB
    #define IN4_PIN GPIO_PIN_3
    #define IN4_PORT GPIOB

    // 电机动作枚举
    typedef enum {
    MOTOR_STOP = 0, // 停止
    MOTOR_FORWARD, // 前进
    MOTOR_BACKWARD, // 后退
    MOTOR_LEFT, // 左转
    MOTOR_RIGHT // 右转
    } Motor_State;

    // 函数声明
    void Motor_GPIO_Init(void); // 电机引脚初始化
    void Motor_Control(Motor_State state); // 电机动作控制
    void Motor_Set_Speed(uint8_t left_speed, uint8_t right_speed); // 速度控制(PWM版,可选)

    #endif

    文件名:motor.c

    #include "motor.h"

    /**
    * @brief 电机控制GPIO初始化
    * @note PB0-PB3设置为推挽输出,速度50MHz
    * @param 无
    * @retval 无
    */

    void Motor_GPIO_Init(void)
    {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 使能GPIOB时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();

    // IN1 (PB0) 配置
    GPIO_InitStruct.Pin = IN1_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(IN1_PORT, &GPIO_InitStruct);

    // IN2 (PB1) 配置
    GPIO_InitStruct.Pin = IN2_PIN;
    HAL_GPIO_Init(IN2_PORT, &GPIO_InitStruct);

    // IN3 (PB2) 配置
    GPIO_InitStruct.Pin = IN3_PIN;
    HAL_GPIO_Init(IN3_PORT, &GPIO_InitStruct);

    // IN4 (PB3) 配置
    GPIO_InitStruct.Pin = IN4_PIN;
    HAL_GPIO_Init(IN4_PORT, &GPIO_InitStruct);

    // 初始状态:电机停止
    HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN3_PORT, IN3_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN4_PORT, IN4_PIN, GPIO_PIN_RESET);
    }

    /**
    * @brief 电机动作控制
    * @note 控制小车前进、后退、左转、右转、停止
    * @param state: 电机动作状态(MOTOR_STOP/MOTOR_FORWARD等)
    * @retval 无
    */

    void Motor_Control(Motor_State state)
    {
    switch(state)
    {
    case MOTOR_STOP: // 停止
    HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN3_PORT, IN3_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN4_PORT, IN4_PIN, GPIO_PIN_RESET);
    break;

    case MOTOR_FORWARD: // 前进
    // 左电机正转:IN1=1, IN2=0
    HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, GPIO_PIN_RESET);
    // 右电机正转:IN3=1, IN4=0
    HAL_GPIO_WritePin(IN3_PORT, IN3_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(IN4_PORT, IN4_PIN, GPIO_PIN_RESET);
    break;

    case MOTOR_BACKWARD: // 后退
    HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(IN3_PORT, IN3_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN4_PORT, IN4_PIN, GPIO_PIN_SET);
    break;

    case MOTOR_LEFT: // 左转(左电机停,右电机转)
    HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN3_PORT, IN3_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(IN4_PORT, IN4_PIN, GPIO_PIN_RESET);
    break;

    case MOTOR_RIGHT: // 右转(右电机停,左电机转)
    HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN3_PORT, IN3_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(IN4_PORT, IN4_PIN, GPIO_PIN_RESET);
    break;

    default: // 默认停止
    Motor_Control(MOTOR_STOP);
    break;
    }
    }

    /**
    * @brief 电机速度控制(PWM版,可选)
    * @note 如需精准调速,可将PB0-PB3配置为TIM3 PWM输出,此处为预留接口
    * @param left_speed: 左电机速度(0-100)
    * @param right_speed: 右电机速度(0-100)
    * @retval 无
    */

    void Motor_Set_Speed(uint8_t left_speed, uint8_t right_speed)
    {
    // 基础版教程暂不实现PWM调速,如需实现可配置TIM3_CH3-CH6输出PWM
    // 此处仅做占位,实际使用时需补充TIM初始化和PWM输出代码
    }

    5.2 红外循迹模块

    文件名:track.h

    #ifndef __TRACK_H
    #define __TRACK_H

    #include "stm32f1xx_hal.h"

    // 循迹模块引脚定义
    #define TRACK_LEFT_PIN GPIO_PIN_0
    #define TRACK_LEFT_PORT GPIOA
    #define TRACK_MID_PIN GPIO_PIN_1
    #define TRACK_MID_PORT GPIOA
    #define TRACK_RIGHT_PIN GPIO_PIN_2
    #define TRACK_RIGHT_PORT GPIOA

    // 循迹状态枚举
    typedef enum {
    TRACK_LEFT = 0, // 左传感器检测到黑线
    TRACK_MID, // 中传感器检测到黑线
    TRACK_RIGHT, // 右传感器检测到黑线
    TRACK_NONE, // 无传感器检测到黑线
    TRACK_ALL // 所有传感器检测到黑线
    } Track_State;

    // 函数声明
    void Track_GPIO_Init(void); // 循迹引脚初始化
    Track_State Track_Get_State(void); // 获取循迹状态

    #endif

    文件名:track.c

    #include "track.h"

    /**
    * @brief 循迹模块GPIO初始化
    * @note PA0-PA2设置为上拉输入(红外模块OUT为低电平有效)
    * @param 无
    * @retval 无
    */

    void Track_GPIO_Init(void)
    {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 使能GPIOA时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();

    // 左循迹传感器(PA0)
    GPIO_InitStruct.Pin = TRACK_LEFT_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉输入,无黑线时为高电平
    HAL_GPIO_Init(TRACK_LEFT_PORT, &GPIO_InitStruct);

    // 中循迹传感器(PA1)
    GPIO_InitStruct.Pin = TRACK_MID_PIN;
    HAL_GPIO_Init(TRACK_MID_PORT, &GPIO_InitStruct);

    // 右循迹传感器(PA2)
    GPIO_InitStruct.Pin = TRACK_RIGHT_PIN;
    HAL_GPIO_Init(TRACK_RIGHT_PORT, &GPIO_InitStruct);
    }

    /**
    * @brief 获取循迹状态
    * @note 检测左/中/右传感器是否检测到黑线(低电平=检测到)
    * @param 无
    * @retval Track_State: 循迹状态
    */

    Track_State Track_Get_State(void)
    {
    uint8_t left_state = HAL_GPIO_ReadPin(TRACK_LEFT_PORT, TRACK_LEFT_PIN);
    uint8_t mid_state = HAL_GPIO_ReadPin(TRACK_MID_PORT, TRACK_MID_PIN);
    uint8_t right_state = HAL_GPIO_ReadPin(TRACK_RIGHT_PORT, TRACK_RIGHT_PIN);

    // 左传感器检测到黑线
    if(left_state == GPIO_PIN_RESET && mid_state == GPIO_PIN_SET && right_state == GPIO_PIN_SET)
    {
    return TRACK_LEFT;
    }
    // 中传感器检测到黑线
    else if(left_state == GPIO_PIN_SET && mid_state == GPIO_PIN_RESET && right_state == GPIO_PIN_SET)
    {
    return TRACK_MID;
    }
    // 右传感器检测到黑线
    else if(left_state == GPIO_PIN_SET && mid_state == GPIO_PIN_SET && right_state == GPIO_PIN_RESET)
    {
    return TRACK_RIGHT;
    }
    // 无传感器检测到黑线
    else if(left_state == GPIO_PIN_SET && mid_state == GPIO_PIN_SET && right_state == GPIO_PIN_SET)
    {
    return TRACK_NONE;
    }
    // 所有传感器检测到黑线
    else if(left_state == GPIO_PIN_RESET && mid_state == GPIO_PIN_RESET && right_state == GPIO_PIN_RESET)
    {
    return TRACK_ALL;
    }
    // 默认状态
    else
    {
    return TRACK_NONE;
    }
    }

    5.3 超声波避障模块

    文件名:ultrasonic.h

    #ifndef __ULTRASONIC_H
    #define __ULTRASONIC_H

    #include "stm32f1xx_hal.h"

    // 超声波模块引脚定义
    #define TRIG_PIN GPIO_PIN_3
    #define TRIG_PORT GPIOA
    #define ECHO_PIN GPIO_PIN_4
    #define ECHO_PORT GPIOA

    // 避障阈值(单位:cm)
    #define OBSTACLE_THRESHOLD 20

    // 函数声明
    void Ultrasonic_GPIO_Init(void); // 超声波引脚初始化
    float Ultrasonic_Get_Distance(void); // 获取检测距离
    uint8_t Ultrasonic_Check_Obstacle(void); // 检测是否有障碍物

    #endif

    文件名:ultrasonic.c

    #include "ultrasonic.h"
    #include "delay.h" // 需自行实现微秒级延时(HAL库默认只有毫秒级)

    /**
    * @brief 超声波模块GPIO初始化
    * @note Trig(PA3)输出,Echo(PA4)输入
    * @param 无
    * @retval 无
    */

    void Ultrasonic_GPIO_Init(void)
    {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 使能GPIOA时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();

    // Trig引脚(PA3)- 输出
    GPIO_InitStruct.Pin = TRIG_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(TRIG_PORT, &GPIO_InitStruct);

    // Echo引脚(PA4)- 输入
    GPIO_InitStruct.Pin = ECHO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(ECHO_PORT, &GPIO_InitStruct);

    // 初始状态:Trig置低
    HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET);
    }

    /**
    * @brief 微秒级延时函数(HAL库无原生us延时,需自行实现)
    * @note 基于系统时钟,适用于72MHz主频
    * @param us: 延时微秒数
    * @retval 无
    */

    void Delay_us(uint32_t us)
    {
    uint32_t delay = (HAL_RCC_GetHCLKFreq() / 1000000) * us / 5;
    while(delay);
    }

    /**
    * @brief 获取超声波检测距离
    * @note 原理:Trig发送10us高电平,检测Echo高电平持续时间,计算距离
    * 距离 = 时间(us) * 声速(340m/s) / 2 / 10000
    * @param 无
    * @retval float: 检测距离(单位:cm)
    */

    float Ultrasonic_Get_Distance(void)
    {
    uint32_t time = 0;
    float distance = 0.0f;

    // 发送触发信号:10us高电平
    HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET);
    Delay_us(10);
    HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET);

    // 等待Echo引脚变为高电平
    while(HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) == GPIO_PIN_RESET);

    // 记录Echo高电平持续时间
    while(HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) == GPIO_PIN_SET)
    {
    time++;
    Delay_us(1);
    // 超时保护:避免死循环(最大检测距离约400cm)
    if(time > 40000)
    {
    break;
    }
    }

    // 计算距离
    distance = (float)time * 0.017f; // 0.017 = 340/2/10000

    return distance;
    }

    /**
    * @brief 检测是否有障碍物
    * @note 距离小于阈值则判定为有障碍物
    * @param 无
    * @retval uint8_t: 1=有障碍物,0=无障碍物
    */

    uint8_t Ultrasonic_Check_Obstacle(void)
    {
    float distance = Ultrasonic_Get_Distance();
    if(distance < OBSTACLE_THRESHOLD && distance > 0)
    {
    return 1;
    }
    else
    {
    return 0;
    }
    }

    5.4 主函数(核心逻辑)

    文件名:main.c

    #include "main.h"
    #include "motor.h"
    #include "track.h"
    #include "ultrasonic.h"
    #include "stm32f1xx_hal.h"

    /* 函数声明 */
    void SystemClock_Config(void);
    static void MX_GPIO_Init(void);

    /**
    * @brief 主函数:智能小车核心逻辑
    * @note 流程:初始化 → 循环检测避障 → 循迹控制
    * @param 无
    * @retval int
    */

    int main(void)
    {
    // 1. HAL库初始化
    HAL_Init();

    // 2. 系统时钟配置(72MHz)
    SystemClock_Config();

    // 3. GPIO初始化
    MX_GPIO_Init();

    // 4. 模块初始化
    Motor_GPIO_Init(); // 电机初始化
    Track_GPIO_Init(); // 循迹模块初始化
    Ultrasonic_GPIO_Init(); // 超声波模块初始化

    // 5. 初始状态:电机停止
    Motor_Control(MOTOR_STOP);
    HAL_Delay(1000); // 上电延时1秒

    // 6. 主循环
    while (1)
    {
    // 第一步:检测障碍物
    if(Ultrasonic_Check_Obstacle() == 1)
    {
    // 有障碍物:停止 → 后退 → 左转(绕行)
    Motor_Control(MOTOR_STOP);
    HAL_Delay(500);
    Motor_Control(MOTOR_BACKWARD);
    HAL_Delay(1000);
    Motor_Control(MOTOR_LEFT);
    HAL_Delay(800);
    }
    else
    {
    // 无障碍物:循迹控制
    Track_State track_state = Track_Get_State();
    switch(track_state)
    {
    case TRACK_MID: // 中间检测到黑线:前进
    Motor_Control(MOTOR_FORWARD);
    break;

    case TRACK_LEFT: // 左边检测到黑线:左转
    Motor_Control(MOTOR_LEFT);
    HAL_Delay(200); // 左转200ms后回到前进
    Motor_Control(MOTOR_FORWARD);
    break;

    case TRACK_RIGHT: // 右边检测到黑线:右转
    Motor_Control(MOTOR_RIGHT);
    HAL_Delay(200); // 右转200ms后回到前进
    Motor_Control(MOTOR_FORWARD);
    break;

    case TRACK_NONE: // 无黑线:停止
    Motor_Control(MOTOR_STOP);
    break;

    case TRACK_ALL: // 全黑线:停止
    Motor_Control(MOTOR_STOP);
    break;

    default:
    Motor_Control(MOTOR_STOP);
    break;
    }
    }

    // 小延时,避免频繁检测
    HAL_Delay(50);
    }
    }

    /**
    * @brief 系统时钟配置(72MHz)
    * @note STM32F103C8T6默认外部8MHz晶振,倍频至72MHz
    * @param 无
    * @retval 无
    */

    void SystemClock_Config(void)
    {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    // 配置外部高速晶振(HSE)
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 8MHz *9 =72MHz
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
    Error_Handler();
    }

    // 配置系统时钟总线
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
    |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // APB1最大36MHz
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    {
    Error_Handler();
    }
    }

    /**
    * @brief GPIO初始化(全局)
    * @note 此处为HAL库默认GPIO初始化入口,模块专属GPIO在各自文件中初始化
    * @param 无
    * @retval 无
    */

    static void MX_GPIO_Init(void)
    {
    // 如需全局GPIO初始化,可在此添加
    }

    /**
    * @brief 错误处理函数
    * @note 死循环,方便调试
    * @param 无
    * @retval 无
    */

    void Error_Handler(void)
    {
    __disable_irq();
    while (1)
    {
    // 可添加LED闪烁等错误提示
    }
    }

    #ifdef USE_FULL_ASSERT
    /**
    * @brief 断言失败处理函数
    * @param file: 文件名
    * @param line: 行号
    * @retval 无
    */

    void assert_failed(uint8_t *file, uint32_t line)
    {
    // 可添加调试信息输出
    }
    #endif

    六、硬件组装与调试

    6.1 硬件组装步骤

  • 将4个直流减速电机安装到小车底盘,连接L298N驱动模块的OUT1-OUT4;
  • 固定STM32最小系统板、L298N模块、锂电池到底盘,注意布局紧凑且不影响车轮转动;
  • 将红外循迹模块安装在小车前部下方(距离地面1-2cm),左/中/右排列,对准地面轨迹线;
  • 将HC-SR04超声波模块安装在小车前部居中位置,高度适中(避免地面反射);
  • 按照接线表连接所有杜邦线,重点检查:
    • 所有GND是否共地;
    • 电机供电是否为12V,传感器/STM32供电是否为5V;
    • 引脚是否接错(尤其是IN1-IN4,接错会导致电机转向异常)。
  • 6.2 代码下载与调试

  • 将ST-Link V2连接到STM32最小系统板(SWD接口:SWCLK/SWDIO/GND/3.3V);
  • 在Keil中打开工程,编译无错误后,点击“Download”下载代码到STM32;
  • 上电测试:
    • 首先测试电机:短接红外传感器,观察小车是否前进/转向正常;
    • 测试循迹:在地面贴黑色轨迹线,小车应沿轨迹行驶;
    • 测试避障:在小车前方20cm内放置障碍物,小车应停止→后退→左转绕行。
  • 6.3 常见问题排查

  • 电机不转:检查L298N供电、IN1-IN4引脚是否接错、代码中电机控制逻辑;
  • 循迹不灵敏:调整红外模块高度、检查上拉电阻是否启用、传感器引脚是否接反;
  • 超声波检测不准:检查Trig/Echo引脚、微秒延时函数是否准确、模块安装角度;
  • 小车跑偏:调整左右电机速度(可通过PWM实现)、检查车轮是否卡滞。
  • 七、功能扩展建议

  • 添加PWM调速:将电机控制引脚改为TIM3 PWM输出,精准控制左右电机速度,解决跑偏问题;
  • 添加蓝牙遥控:增加HC-05蓝牙模块,通过手机APP控制小车;
  • 添加OLED显示:实时显示检测距离、循迹状态;
  • 优化避障逻辑:增加多个超声波模块,实现360°避障。
  • 总结

  • 本项目核心是STM32F103通过GPIO控制L298N驱动电机,结合红外循迹模块和超声波模块实现循迹避障功能,硬件接线需严格遵循引脚定义,避免接错导致器件损坏;
  • 代码层面分为电机驱动、循迹检测、超声波避障三大模块,主函数通过循环检测障碍物和循迹状态,控制小车动作,零基础小白可按文件逐一编写代码,确保每个模块独立调试通过后再整合;
  • 调试阶段需重点检查供电、引脚连接、传感器安装高度,这些是影响小车功能落地的关键因素,遇到问题可逐一模块排查,确保每个功能点单独验证通过。
  • 赞(0)
    未经允许不得转载:171主机测评 » STM32项目实战:基于STM32F103的智能循迹避障小车
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址