一、系统概述与核心功能
1. 系统定位
本设计基于STM32F103C8T6核心板,结合超声波测距、SG90舵机测角与L298N直流电机驱动,构建一款具备“前方180°扇形区域障碍扫描与自动避障”功能的智能小车。通过舵机带动超声波探头左右旋转,小车能在行进中实时构建前方环境地图,计算障碍物距离与偏离角度,从而做出最优的绕行决策。结合Proteus仿真电路,可在无实物情况下验证控制逻辑与避障算法。
2. 核心功能模块
| 模块 | 功能描述 |
|---|---|
| 运动底盘 | 4WD/2WD差速驱动,支持前进、后退、原地左/右旋转 |
| 测障测角单元 | SG90舵机(0°-180°旋转)搭载HC-SR04超声波,实现前方扇形区域扫描与距离测定 |
| 避障决策 | 基于扫描数据(距离+角度),判断左侧/右侧哪边空间更大,执行绕障逻辑 |
| 人机交互 | OLED显示最近障碍距离与角度,指示灯反馈当前运行状态 |
| 仿真调试 | Proteus环境下搭建虚拟电路,联合Keil进行软硬件协同仿真调试 |
二、硬件设计方案
1. 核心硬件选型
| 模块 | 型号 | 关键参数 | 接口方式 |
|---|---|---|---|
| 主控MCU | STM32F103C8T6 | 72MHz Cortex-M3,20KB RAM,3个通用定时器(PWM输出) | 核心控制器 |
| 测距传感器 | HC-SR04 | 探测距离2cm-400cm,精度±3mm,触发信号>10μs,回响脉冲宽度对应距离 | TIM2输入捕获(PA0-Echo, PA1-Trig) |
| 角度控制 | SG90舵机 | 转动角度0°-180°,PWM控制,工作频率50Hz(周期20ms),脉冲范围0.5ms-2.5ms | TIM3_CH1(PA6) |
| 电机驱动 | L298N | 双H桥,支持7-35V电机电压,逻辑电压5V,连续电流2A/路,带续流二极管保护 | IN1-4(PB0-PB3),ENA/B(PA8-PA9) |
| 直流电机 | TT直流减速电机×4 | 额定电压6V,空载转速200rpm,减速比1:48 | L298N输出端 |
| 电源模块 | 18650锂电池×2 | 7.4V/2200mAh,带充放电保护板,续航>2小时 | 7.4V直供电机,LM2596降压至5V/3.3V |
2. 硬件电路设计要点
- 舵机与超声波挂载:将HC-SR04牢固粘贴在SG90舵机的摇臂延长杆上,确保二者重心平衡。
- 电机驱动接线:L298N的ENA和ENB引脚必须接到STM32的PWM引脚(如PA8/PA9),以实现精确的调速控制;IN1-IN4接到普通GPIO(PB0-PB3)控制方向。
- 电源去耦:在L298N的12V/5V输入端并联1000μF电解电容与0.1μF瓷片电容,防止电机启停时的瞬间大电流拉低逻辑电压。
- 共地处理:STM32逻辑地与L298N逻辑地必须可靠连接,避免电位差导致控制信号紊乱。
三、软件设计与核心算法
1. 系统架构
采用前后台系统(无操作系统),以主循环为前台逻辑调度核心,以通用定时器中断为后台精准控制源:
- TIM2输入捕获中断:精准捕捉超声波回响脉冲宽度,换算为距离值(Distance = Pulse_Width / 58.0 cm)。
- TIM3 PWM输出:产生50Hz周期、占空比可调的方波,控制舵机转角(Angle = (Duty - 5) / 10 * 180)。
- 主循环逻辑:
- 扫描阶段:控制舵机从0°扫到180°,每间隔15°停留并触发测距。
- 决策阶段:分析扫描数据,找出最近障碍物的距离和角度。若距离小于安全阈值(如20cm),则判定左侧或右侧是否有通行空间。
- 执行阶段:根据决策结果,控制电机驱动小车前进、左转绕行或右转绕行。
2. 核心代码实现(基于标准库)
2.1 舵机角度控制(TIM3 PWM)
#include "stm32f10x.h"
#include "timer.h"
// 舵机初始化(50Hz,即20ms周期)
void SG90_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 1. 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// 2. 配置PA6为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 定时器时基配置:72MHz / (7199+1) / (199+1) = 50Hz
TIM_TimeBaseStructure.TIM_Period = 199; // ARR
TIM_TimeBaseStructure.TIM_Prescaler = 7199; // PSC
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// 4. PWM通道配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 15; // 初始占空比7.5% (1.5ms,对应90°)
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);
TIM_Cmd(TIM3, ENABLE);
}
// 设置舵机角度(0-180度)
void SG90_SetAngle(uint8_t angle) {
// 0度对应0.5ms (CCR=5),180度对应2.5ms (CCR=25)
// 公式:CCR = 5 + (angle / 180.0) * 20
uint16_t ccr_value = 5 + (angle * 20 / 180);
TIM_SetCompare1(TIM3, ccr_value);
}
2.2 超声波测距(TIM2输入捕获)
#include "stm32f10x.h"
#include "ultrasonic.h"
volatile uint8_t is_rising = 0; // 上升沿/下降沿标志
volatile uint32_t rising_time = 0; // 上升沿捕获值
volatile uint32_t falling_time = 0; // 下降沿捕获值
volatile uint8_t distance_cm = 0; // 计算出的距离(cm)
// 超声波初始化
void HC_SR04_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 2. Trig引脚(PA1)配置为推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. Echo引脚(PA0)配置为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 4. 定时器配置 (1us计数一次,72MHz/72=1MHz)
TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseStructure.TIM_Prescaler = 71;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 5. 输入捕获配置
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 先捕获上升沿
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
// 6. NVIC配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}
// 发送10us以上触发脉冲
void HC_SR04_Trig(void) {
GPIO_SetBits(GPIOA, GPIO_Pin_1);
Delay_us(15); // 延时15us
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
// TIM2中断服务程序
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) {
if (is_rising == 0) {
// 捕获到上升沿
rising_time = TIM_GetCapture1(TIM2);
is_rising = 1;
// 切换为下降沿捕获
TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Falling);
} else {
// 捕获到下降沿
falling_time = TIM_GetCapture1(TIM2);
is_rising = 0;
// 计算高电平持续时间(us)
uint32_t pulse_width;
if (falling_time >= rising_time) {
pulse_width = falling_time - rising_time;
} else {
pulse_width = (0xFFFF - rising_time + 1) + falling_time;
}
// 转换为厘米 (公式:距离cm = 高电平时间us / 58)
distance_cm = pulse_width / 58;
// 切换回上升沿捕获
TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Rising);
}
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
}
}
2.3 避障决策算法(核心逻辑)
#include "avoid_obstacle.h"
// 定义扫描数据存储结构
typedef struct {
uint8_t angle; // 舵机角度 (0-180)
uint8_t dist; // 该角度测得的距离 (cm)
} ScanPoint_t;
ScanPoint_t scan_map[13]; // 扫描13个点 (0°, 15°, 30°... 180°)
// 前方扇形扫描函数
void Radar_Scan(void) {
uint8_t idx = 0;
for (uint8_t angle = 0; angle <= 180; angle += 15) {
SG90_SetAngle(angle);
Delay_ms(200); // 等待舵机转动到位
HC_SR04_Trig();
Delay_ms(50); // 等待超声波测量完成
scan_map[idx].angle = angle;
scan_map[idx].dist = distance_cm;
idx++;
}
}
// 避障决策函数
// 返回值:0=前进,1=左转,2=右转,3=后退
uint8_t Avoid_Obstacle_Decision(void) {
uint8_t min_dist = 255;
uint8_t min_angle = 90; // 默认指向正前方
// 1. 寻找最近障碍物
for (uint8_t i = 0; i < 13; i++) {
if (scan_map[i].dist < min_dist) {
min_dist = scan_map[i].dist;
min_angle = scan_map[i].angle;
}
}
// 2. 判断是否需要避障(安全距离20cm)
if (min_dist > 20) {
return 0; // 安全,继续前进
}
// 3. 判断障碍物出现在左侧还是右侧
if (min_angle < 90) {
// 障碍物在左侧,向右转绕行
return 2;
} else {
// 障碍物在右侧或正前方,向左转绕行
return 1;
}
}
四、Proteus仿真电路设计
在没有实物的情况下,可以利用Proteus强大的仿真功能来验证上述代码。
1. 仿真环境搭建
- 主控MCU:在Proteus库中搜索
STM32F103C8(若找不到,可用STM32F103RB或STM32F103C6代替,引脚差异需微调)。 - 电机驱动:搜索
L298(注意不是L298N,Proteus里通常只画出芯片内部逻辑),将其输入输出引脚连接好。 - 直流电机:搜索
MOTOR-DC,连接在L298的输出端。 - 舵机与超声波:Proteus中没有现成的HC-SR04和SG90模型。为了仿真,我们可以:
- 逻辑替代:用几个滑动变阻器(Potentiometer)模拟距离输入,连接到ADC引脚。
- 物理替代:用
DC MOTOR模拟舵机转动(虽然不准确,但能看到角度变化的趋势)。 - 手动模拟:在仿真运行时,手动点击Proteus里的按钮,模拟舵机到位和超声波回响信号。
2. 仿真调试技巧
- 联合调试:在Keil中编译生成
.hex文件,加载到Proteus的STM32芯片属性中。 - 虚拟终端(Virtual Terminal):在Proteus中添加虚拟串口,连接到STM32的USART1引脚(PA9/PA10),可以在电脑上看到小车实时发送的扫描数据和决策结果。
- 激励源:若想模拟超声波回响脉冲,可以使用
PULSE GENERATOR(脉冲发生器),设置一个宽度随距离变化的脉冲,连接到PA0(Echo引脚)。
参考代码 测障测角小车+仿真电路 www.youwenfan.com/contentali/124102.html
五、系统调试与扩展
1. 实物调试步骤
| 阶段 | 操作 | 工具 |
|---|---|---|
| 舵机调试 | 测试0°、90°、180°是否精准到位,有无抖动 | 肉眼观察、量角器 |
| 测距调试 | 放置障碍物在不同距离,对比测量值与真实值 | 卷尺、串口打印 |
| 扫描调试 | 观察舵机转动是否流畅,扫描点是否覆盖前方180° | 肉眼观察、OLED显示 |
| 避障调试 | 在轨道放置障碍物,观察小车是否能顺利绕行 | 实际场地测试 |
2. 扩展功能
- PID速度闭环:添加编码器,实现小车定速行驶,避免电池电压下降导致速度变化。
- 视觉避障:将超声波升级为OpenMV摄像头,实现颜色识别和更复杂的道路循迹。
- 手机遥控:添加HC-05蓝牙模块,通过手机APP实时查看雷达扫描图并遥控小车。
六、总结
基于STM32的测障测角小车是嵌入式系统入门与进阶的经典实战项目。通过SG90舵机与HC-SR04超声波的巧妙结合,打破了单一固定探头的视野局限,实现了前方180°的雷达式扫描。配合L298N电机驱动与精心设计的避障算法,小车能够智能应对复杂路况。利用Proteus仿真与Keil联合调试,开发者可以在硬件成型前完美验证逻辑漏洞,极大地提高了开发效率。