文章目录
-
- 一、项目概述
- 二、硬件清单
- 三、硬件电路设计与接线
-
- 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最小系统板,实现一款具备循迹和避障功能的智能小车。核心功能包括:
本教程面向零基础小白,从硬件选型、电路接线、代码编写到实物调试,全程详细讲解,确保能落地实现。
二、硬件清单
| 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 详细接线表
| 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 环境准备
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 硬件组装步骤
- 所有GND是否共地;
- 电机供电是否为12V,传感器/STM32供电是否为5V;
- 引脚是否接错(尤其是IN1-IN4,接错会导致电机转向异常)。
6.2 代码下载与调试
- 首先测试电机:短接红外传感器,观察小车是否前进/转向正常;
- 测试循迹:在地面贴黑色轨迹线,小车应沿轨迹行驶;
- 测试避障:在小车前方20cm内放置障碍物,小车应停止→后退→左转绕行。




