基于STM32的测障测角小车设计与Proteus仿真

基于STM32的测障测角小车设计与Proteus仿真

一、系统概述与核心功能

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. 硬件电路设计要点

三、软件设计与核心算法

1. 系统架构

采用前后台系统(无操作系统),以主循环为前台逻辑调度核心,以通用定时器中断为后台精准控制源:

  1. TIM2输入捕获中断:精准捕捉超声波回响脉冲宽度,换算为距离值(Distance = Pulse_Width / 58.0 cm)。

  2. TIM3 PWM输出:产生50Hz周期、占空比可调的方波,控制舵机转角(Angle = (Duty – 5) / 10 * 180)。

  3. 主循环逻辑

    • 扫描阶段:控制舵机从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. 仿真环境搭建

2. 仿真调试技巧

参考代码 测障测角小车+仿真电路 www.youwenfan.com/contentcst/124102.html

五、系统调试与扩展

1. 实物调试步骤

阶段 操作 工具
舵机调试 测试0°、90°、180°是否精准到位,有无抖动 肉眼观察、量角器
测距调试 放置障碍物在不同距离,对比测量值与真实值 卷尺、串口打印
扫描调试 观察舵机转动是否流畅,扫描点是否覆盖前方180° 肉眼观察、OLED显示
避障调试 在轨道放置障碍物,观察小车是否能顺利绕行 实际场地测试

2. 扩展功能

六、总结

基于STM32的测障测角小车是嵌入式系统入门与进阶的经典实战项目。通过SG90舵机与HC-SR04超声波的巧妙结合,打破了单一固定探头的视野局限,实现了前方180°的雷达式扫描。配合L298N电机驱动与精心设计的避障算法,小车能够智能应对复杂路况。利用Proteus仿真与Keil联合调试,开发者可以在硬件成型前完美验证逻辑漏洞,极大地提高了开发效率。

专注于matlab/simulink,电子电路,编程