当前位置: 首页 > news >正文

麦特轮巡线避障小车开发

麦特轮巡线避障小车开发:从硬件到智能控制

大家好!今天我要分享一个基于麦特轮的智能巡线避障小车项目。这款小车不仅能沿着预设的黑线自主行驶,还能通过超声波传感器检测前方障碍物并自动避让,实现了"循迹+避障"的双重智能控制。本文将详细介绍系统设计框架和关键技术点。

项目概述

麦特轮(Mecanum Wheel)凭借其独特的全向移动能力,让小车能够灵活地前进、后退、横移和旋转,这为复杂环境下的巡线和避障提供了巨大便利。本项目实现的核心功能包括:

  • 基础交互:LED灯光指示、蜂鸣器报警提示、按键模式控制
  • 电源管理:实时电池电量检测与低电量报警
  • 环境感知:超声波测距(HC-SR04)实现障碍物检测
  • 运动控制:麦特轮全向运动驱动
  • 智能行为:黑线识别跟踪+30cm内障碍物左转向避让

硬件系统设计

核心控制器选择

推荐使用STM32F103系列单片机作为主控制器,其丰富的GPIO接口、定时器资源和ADC模块能够完美支持多传感器数据采集和电机控制需求。

硬件组成清单

  1. 运动系统

    • 4个65mm麦特轮
    • 4个N20减速电机(带编码器可选)
    • 2个TC1508A电机驱动模块(每模块控制2个电机)
  2. 感知系统

    • 5路热敏电阻感知模块(用于黑线检测)
    • HC-SR04超声波模块(测距范围2-400cm)
    • 电压检测电路(电阻分压网络)
  3. 交互系统

    • 3-5个LED指示灯(电源、运行、故障等)
    • 有源蜂鸣器(提示音输出)
    • 1个按键(启动/停止、模式切换、参数调整)
  4. 电源系统

    • 7.4V锂电池(提供电机动力)
    • 3.3V稳压模块(为单片机和传感器供电)

硬件连接拓扑

[STM32]├── [电机驱动A] ── 左前轮电机│               └─ 右后轮电机│├── [电机驱动B] ── 右前轮电机│               └─ 左后轮电机│├── [5路热敏电阻模块](PA0-PA4)│├── [HC-SR04]│     ├─ Trig ─ PB0│     └─ Echo ─ PB1│├── [按键](PB2)│├── [LED](PC0-PC2)│├── [蜂鸣器](PC3)│└── [电压检测](PA5-ADC)

软件架构设计

采用分层设计思想,将系统分为硬件抽象层(BSP)、算法层和应用层,提高代码复用性和可维护性。

模块划分

main.c                 // 主程序入口
├── bsp/               // 硬件抽象层
│   ├── Light.c      // LED控制
│   ├── Buzzer.c   // 蜂鸣器控制
│   ├── Key.c      // 按键处理
│   ├── Battery.c    // 电源检测
│   ├── Ultrasonic.c // 超声波测距
│   ├── Motors.c    // 电机驱动
│   └── Track.c       // 热敏电阻巡线
│
├── algorithm/         // 算法层
│   ├── Motors.c   // 巡线算法(写在热敏电阻巡线)
│   └── avoid.c        // 避障逻辑└── app/               // 应用层├── app_control.c  // 主控制逻辑└── app_mode.c     // 工作模式管理

主程序流程

#include "RTX51TNY.H"
#include "UART.h"
#include "NVIC.h"
#include "Switch.h"
#include "GPIO.h"
#include "Light.h"
#include "Key.h"
#include "Battery.h"
#include "Buzzer.h"
#include "Ultrasonic.h"
#include "STC8H_PWM.h"
#include "Motors.h"void GPIO_config(void) {GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义//  uart1: P30  P31  准双向口GPIO_InitStructure.Pin  = GPIO_Pin_0 | GPIO_Pin_1;		//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}void UART_config(void) {// >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<COMx_InitDefine		COMx_InitStructure;					//结构定义COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTxCOMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLECOMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLEUART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4NVIC_UART1_Init(ENABLE,Priority_1);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}
void sys_init() {EAXSFR();		/* 扩展寄存器访问使能 */GPIO_config();UART_config();Light_init();  // 灯初始化Key_init();    // 按键
//    Battery_init(); // 电池Buzzer_init();  // 蜂鸣器Ultrasonic_init(); // 超声波Motors_init();  // 电机EA = 1; // 中断使能
}
#define MAIN_TASK	0
#define TASK_KEY	1
#define TASK_DISTANCE	2void main_task()  _task_ MAIN_TASK {sys_init();// 任务1:扫描按键os_create_task(TASK_KEY);// 任务2:超声波测距os_create_task(TASK_DISTANCE);// 删除任务0os_delete_task(MAIN_TASK);
}float distance = 0.0; // 距离
u8 is_stop = 0; // 1 停止 0启动
u8 is_key_down = 0; // 按下按键
int speed = 20; // 车速// 按下的回调函数
void Key_on_keydown() {printf("按下\n");// 全亮Light_on(ALL);printf("前进\n");Motors_forward(speed, MID_M);is_stop = 0; // 车不停止is_key_down = 1; // 按下按键}
// 抬起回调
void Key_on_keyup() {printf("抬起\n");Light_off(ALL);
}
void task_key()  _task_ TASK_KEY {while(1) {// 扫描按键Key_scan();os_wait2(K_TMO, 20);  // 5 * 20 = 100ms}
}
void task_distance()  _task_ TASK_DISTANCE {char res;while(1) {distance = 0.0;res = Ultrasonic_get_distance(&distance);if (res == 0) {// printf("distance = %.2f cm\n", distance);// 距离低于30cmif (distance < 30.0) {Buzzer_alarm();if (is_stop == 0) {   // 如果没有停止,才停止Motors_stop();is_stop = 1; // 车停止}} else {if (is_stop == 1 && is_key_down == 1) { // 如果按下按键启动,同时车停止了,继续启动Motors_forward(speed, MID_M);is_stop = 0; // 不停止}}} else {// printf("dis err: res = %d\n", (int)res);}os_wait2(K_TMO, 5);  // 5ms * 5}
}

核心模块实现详解

1. 电机驱动模块(Motors.c)

麦特轮的全向运动需要精确控制四个轮子的转向和转速,通过组合不同的运动方向可以实现复杂移动。

#include "Motors.h"void Motors_init() {EAXSFR();		/* 扩展寄存器访问使能 *///准双向口P1_MODE_IO_PU(GPIO_Pin_4 | GPIO_Pin_5| GPIO_Pin_6 | GPIO_Pin_7);P2_MODE_IO_PU(GPIO_Pin_0 | GPIO_Pin_1| GPIO_Pin_2 | GPIO_Pin_3);
}
// -100 --------- 0 --------- 100		速度
//后退最大速度	  0			前进最大速度
// 0 ----------  50 --------- 100		PWM占空比
static int speed2duty(int speed) {// speed > 0 前进// speed < 0 后退// [-100, 100]// speed / 2  	    =>  [-50,  50]// speed / 2 + 50   =>  [0, 100]return speed / 2 + 50;
}// PWM配置
#define PERIOD (MAIN_Fosc / 1000)
static void	PWM_config(MotorSpeed ms)   // 100 ~ 0     -1
{   // speed 写0, 停止// 类型                  变量PWMx_InitDefine		PWMx_InitStructure = {0};// ==================== 左前轮 PWM3_SW_P14_P15 模式1PWMx_InitStructure.PWM_Mode    		= CCMRn_PWM_MODE1;	//模式,		CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2PWMx_InitStructure.PWM_Duty    		= speed2duty(ms.LF_Speed) * PERIOD / 100.0;	//PWM占空比时间, 0~PeriodPWMx_InitStructure.PWM_EnoSelect    = (ms.LF_Speed == 0) ? 0 :  (ENO3P | ENO3N);	//输出通道选择,	ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8PPWM_Configuration(PWM3, &PWMx_InitStructure);// ==================== 右前轮 PWM4_SW_P16_P17 模式2PWMx_InitStructure.PWM_Mode    		= CCMRn_PWM_MODE2;	//模式,		CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2PWMx_InitStructure.PWM_Duty    		= speed2duty(ms.RF_Speed) * PERIOD / 100.0;	//PWM占空比时间, 0~PeriodPWMx_InitStructure.PWM_EnoSelect    = (ms.RF_Speed == 0) ? 0 :  (ENO4P | ENO4N);	//输出通道选择,	ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8PPWM_Configuration(PWM4, &PWMx_InitStructure);// ==================== 左后轮 PWM1_SW_P20_P21PWMx_InitStructure.PWM_Mode    		= CCMRn_PWM_MODE1;	//模式,		CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2PWMx_InitStructure.PWM_Duty    		= speed2duty(ms.LB_Speed) * PERIOD / 100.0;	//PWM占空比时间, 0~PeriodPWMx_InitStructure.PWM_EnoSelect    = (ms.LB_Speed == 0) ? 0 :  (ENO1P | ENO1N);	//输出通道选择,	ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8PPWM_Configuration(PWM1, &PWMx_InitStructure);// ==================== 右后轮 PWM2_SW_P22_P23  模式2PWMx_InitStructure.PWM_Mode    		= CCMRn_PWM_MODE2;	//模式,		CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2PWMx_InitStructure.PWM_Duty    		= speed2duty(ms.RB_Speed) * PERIOD / 100.0;	//PWM占空比时间, 0~PeriodPWMx_InitStructure.PWM_EnoSelect    = (ms.RB_Speed == 0) ? 0 :  (ENO2P | ENO2N);	//输出通道选择,	ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8PPWM_Configuration(PWM2, &PWMx_InitStructure);// 配置PWMAPWMx_InitStructure.PWM_Period   = PERIOD - 1;			//周期时间,   0~65535PWMx_InitStructure.PWM_DeadTime = 0;					//死区发生器设置, 0~255PWMx_InitStructure.PWM_MainOutEnable= ENABLE;			//主输出使能, ENABLE,DISABLEPWMx_InitStructure.PWM_CEN_Enable   = ENABLE;			//使能计数器, ENABLE,DISABLEPWM_Configuration(PWMA, &PWMx_InitStructure);			//初始化PWM通用寄存器,  PWMA,PWMB// 切换PWM通道PWM1_SW(PWM1_SW_P20_P21);PWM2_SW(PWM2_SW_P22_P23);PWM3_SW(PWM3_SW_P14_P15);PWM4_SW(PWM4_SW_P16_P17);// 初始化PWMA的中断NVIC_PWM_Init(PWMA,DISABLE,Priority_0);
}// speed:速度 0~100  mode: LEFT_M左前 , MID_M前进 , RIGHT_M右前
void Motors_forward(int speed, MotorsMode mode) {MotorSpeed ms = {0};if (mode == LEFT_M) {ms.LF_Speed = 0;ms.LB_Speed = speed;ms.RF_Speed = speed;ms.RB_Speed = 0;} else if (mode == MID_M) {ms.LF_Speed = speed;ms.LB_Speed = speed;ms.RF_Speed = speed;ms.RB_Speed = speed;} else if (mode == RIGHT_M) {ms.LF_Speed = speed;ms.LB_Speed = 0;ms.RF_Speed = 0;ms.RB_Speed = speed;}PWM_config(ms);}// speed:速度 0~100  mode: LEFT_M左后 , MID_M后退 , RIGHT_M右后
void Motors_backward(int speed, MotorsMode mode) {MotorSpeed ms = {0};if (mode == LEFT_M) {ms.LF_Speed = -speed;ms.LB_Speed = 0;ms.RF_Speed = 0;ms.RB_Speed = -speed;} else if (mode == MID_M) {ms.LF_Speed = -speed;ms.LB_Speed = -speed;ms.RF_Speed = -speed;ms.RB_Speed = -speed;} else if (mode == RIGHT_M) {ms.LF_Speed = 0;ms.LB_Speed = -speed;ms.RF_Speed = -speed;ms.RB_Speed = 0;}PWM_config(ms);
}// speed:速度 0~100  mode: LEFT_M左平移 ,RIGHT_M右平移
void Motors_translate(int speed, MotorsMode mode) {MotorSpeed ms = {0};if (mode == LEFT_M) {ms.LF_Speed = -speed;ms.LB_Speed = speed;ms.RF_Speed = speed;ms.RB_Speed = -speed;}  else if (mode == RIGHT_M) {ms.LF_Speed = speed;ms.LB_Speed = -speed;ms.RF_Speed = -speed;ms.RB_Speed = speed;}PWM_config(ms);
}// speed:速度 0~100  mode: LEFT_M向左旋转 , RIGHT_M向右旋转
void Motors_around(int speed, MotorsMode mode) {MotorSpeed ms = {0};if (mode == LEFT_M) { // 逆时针ms.LF_Speed = -speed;ms.LB_Speed = -speed;ms.RF_Speed = speed;ms.RB_Speed = speed;}  else if (mode == RIGHT_M) { // 顺时针ms.LF_Speed = speed;ms.LB_Speed = speed;ms.RF_Speed = -speed;ms.RB_Speed = -speed;}PWM_config(ms);
}// speed:速度 0~100  mode: LEFT_M左转 , RIGHT_M右转
void Motors_turn(int speed,  MotorsMode mode) {MotorSpeed ms = {0};if (mode == LEFT_M) {ms.LF_Speed = speed * 0.5;ms.LB_Speed = speed * 0.5;ms.RF_Speed = speed;ms.RB_Speed = speed;}  else if (mode == RIGHT_M) {ms.LF_Speed = speed;ms.LB_Speed = speed;ms.RF_Speed = speed * 0.5;ms.RB_Speed = speed * 0.5;}PWM_config(ms);
}// 停止
void Motors_stop() {MotorSpeed ms = {0};PWM_config(ms);
}

Motors.h

#ifndef ___MOTORS_H__
#define ___MOTORS_H__#include "GPIO.h"
#include "NVIC.h"
#include "Switch.h"
#include "STC8H_PWM.h"// 左前轮
#define 	LF_P		P14
#define 	LF_N		P15// 右前轮
#define 	RF_P		P16
#define 	RF_N		P17// 左后轮
#define 	LB_P		P20
#define 	LB_N		P21// 右后轮
#define 	RB_P		P22
#define 	RB_N		P23typedef struct{int LF_Speed;	// 左前轮速度int LB_Speed;	// 左后轮速度int RF_Speed;	// 右前轮速度int RB_Speed;	// 右后轮速度
}MotorSpeed;typedef enum{LEFT_M, MID_M , RIGHT_M
}MotorsMode;// 初始化
void Motors_init();// speed:速度 0~100  mode: LEFT_M左前 , MID_M前进 , RIGHT_M右前
void Motors_forward(int speed, MotorsMode mode);// speed:速度 0~100  mode: LEFT_M左后 , MID_M后退 , RIGHT_M右后
void Motors_backward(int speed , MotorsMode mode);// speed:速度 0~100  mode: LEFT_M左平移 ,RIGHT_M右平移
void Motors_translate(int speed , MotorsMode mode);// speed:速度 0~100  mode: LEFT_M向左旋转 , RIGHT_M向右旋转
void Motors_around(int speed , MotorsMode mode);// speed:速度 0~100  mode: LEFT_M左转 , RIGHT_M右转
void Motors_turn(int speed ,  MotorsMode mode);// 停止
void Motors_stop();#endif

2. 热敏电阻巡线模块( Track.c)

巡线模块通过检测黑线与白色背景的反射光差异来判断小车位置,通常使用5路传感器实现更精确的轨迹跟踪。

#include "Track.h"// 初始化
void Track_init() {//准双向口	P0_MODE_IO_PU(GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4);}static int last_pos = 0; // 上一次状态 
// 获取寻迹坐标: 高电平-不亮-压到黑线  低电平-亮起-正常反射面
// 压到黑线  没有压到(返回上一次)  压到多根黑线(多个坐标的平均值)
int Track_get_position() { int cur_pos = 0;  // 当前坐标 u8 cnt = 0; // 为了统计压到多少根黑线if (LED1 == 1) { // 左1	-64cur_pos += -64;cnt++;}if (LED2 == 1) { // 左2	-32cur_pos += -32;cnt++;}if (LED3 == 1) { // 中	0cur_pos += 0;cnt++;}if (LED4 == 1) { // 右1	32cur_pos += 32;cnt++;}if (LED5 == 1) { // 右2	64cur_pos += 64;cnt++;}if (cnt == 0) { // 没有压到黑线return last_pos; // 返回上一次状态}// 能执行到这,说明有压到黑线// 保存上一次状态last_pos = cur_pos;return cur_pos / cnt;
}

Track.h

#ifndef __TRACK_H__
#define __TRACK_H__#include "GPIO.h"#define LED1 P00 // 左1	-64
#define LED2 P01 // 左2	-32
#define LED3 P02 // 中	0
#define LED4 P03 // 右1	32
#define LED5 P04 // 右2	64// 初始化
void Track_init();
// 获取寻迹坐标: 高电平-不亮-压到黑线  低电平-亮起-正常反射面
int Track_get_position();#endif

3. 巡线实现

基本巡线算法通过检测传感器状态来判断小车偏离情况,并控制电机进行方向调整。

#include 	"RTX51TNY.H"#include	"GPIO.h"	// 库函数
#include 	"UART.h"
#include	"NVIC.h"
#include 	"Switch.h"#include 	"Light.h"	// 驱动,外设
#include 	"Key.h"
#include 	"Battery.h"
#include 	"Buzzer.h"
#include 	"Ultrasonic.h"
#include 	"Motors.h"
#include 	"Track.h"void GPIO_config(void) {GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义GPIO_InitStructure.Pin  = GPIO_Pin_0 | GPIO_Pin_1;		//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}void UART_config(void) {// >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<COMx_InitDefine		COMx_InitStructure;					//结构定义COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTxCOMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLECOMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLEUART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4NVIC_UART1_Init(ENABLE,Priority_1);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}void sys_init() {EA = 1; 		// 全局中断使能EAXSFR();		// 扩展寄存器访问使能// ================== 库函数GPIO_config(); 	// GPIO配置UART_config(); 	// 串口配置// ================== 驱动,外设Light_init(); // 灯Key_init();   // 按键Battery_init(); // 电池电压Buzzer_init();	// 蜂鸣器Ultrasonic_init();	// 超声波Motors_init();		// 电机Track_init();		// 巡线printf("====sys_init====\n");
}#define		KEY_TASK_ID		2
#define		TRACK_TASK_ID	3void main_task() _task_ 0 {sys_init(); // 配置os_create_task(KEY_TASK_ID);  // 按键扫描任务// 销毁任务0os_delete_task(0);
}u8 flag = 1;
void Key_on_keydown() {switch(flag){case 1: printf("===创建巡线任务====\n");os_create_task(TRACK_TASK_ID); break; case 2: printf("===删除巡线任务====\n");os_delete_task(TRACK_TASK_ID); Motors_stop(); // 删除任务,不会停止电机break; default:  break; }flag++;if (flag > 2) flag = 1;
}void track_task() _task_ TRACK_TASK_ID {int pos;char speed = 30;while(1) {pos = Track_get_position();// 调试完,一定要注释printf,会影响灵敏度// printf("pos = %d\n", pos);if (pos == 0) { // 前进Motors_forward(speed, MID_M);} else if (pos < 0) { // 左转Motors_turn(speed, LEFT_M);} else if (pos > 0) { // 右转Motors_turn(speed, RIGHT_M);}os_wait2(K_TMO, 2); // 5ms * 2}
}void Key_on_keydown_bk() {float vol;char res;float distance;vol = Battery_get_voltage(); // 获取电池电压printf("vol = %.2f\n", vol);res = Ultrasonic_get_distance(&distance);printf("res = %d\n", (int)res);if (res == 0) {printf("distance = %.2f cm", distance);}
}void Key_on_keyup() {printf("===Key_on_keyup===\n");
}void key_task() _task_ KEY_TASK_ID {while(1) {Key_scan2(Key_on_keydown, NULL); // 扫描按键os_wait2(K_TMO, 4); // 5ms * 4}
}

4. 超声波测距与避障(Ultrasonic.c)

超声波模块用于检测前方障碍物距离,当距离小于30cm时触发左转向或者停止避障逻辑。

#include "Ultrasonic.h"void Ultrasonic_init() {// 准双向口	P47P4_MODE_IO_PU(GPIO_Pin_7);// 高阻输入	P33P3_MODE_IN_HIZ(GPIO_Pin_3);
}static void Delay10us(void)	//@24.000MHz
{unsigned char data i;i = 78;while (--i);
}// 返回值为char,因为有负数,代表不同的状态,返回0,才代表成功获取距离
char Ultrasonic_get_distance(float *distance) {int cnt = 0;// 1. 至少给trig 10us(给个2个10us)高电平时间,再拉低TRIG = 1;Delay10us();Delay10us();TRIG = 0;// 2. 计算echo低电平的时间,(当echo变高电平时,退出循环)#if MS2// Delay 2msdelay_ms(2);#endif// 假如超过 30 * 10 = 300us 都没有变为高电平(防止没有插上超声波模块,导致程序卡死),也要退出循环cnt = 0;do {Delay10us();cnt ++;} while(ECHO == 0 && cnt < 30);if (cnt >= 30) {return -1;}// 3. 计算echo高电平时间(从上升沿到下降沿的间隔时间)cnt = 0;do {Delay10us();cnt ++;} while(ECHO == 1);// 1ms = 1000us     10us = 0.01ms// printf("echo -> %.2f ms\n", (cnt * 0.01));// 4. 计算距离:测试距离= (高电平时间*声速(340M/S))/2 要除以2,因为声音有来回// cnt 是高电平的时间  1个cnt 10 us// dis = ((cnt * 0.01)ms * 340m/s) / 2// dis = ((cnt * 0.01)ms * 34000cm/1000ms) / 2// dis = ((cnt * 0.01)ms * 34cm/ms) / 2*distance = ((cnt * 0.01) * 34) * 0.5;if(*distance  < 2.0){ // 距离太短,数值不保证return -2;}else if(*distance  > 400.0){ // 距离太远,数值也不保证return -3;}return 0;
}

Ultrasonic.h

#ifndef __ULTRISONIC_H__
#define __ULTRISONIC_H__#include 	"GPIO.h"
#include	"Delay.h"// trig:单片机发送信号,高速超声波的模组去测距
// echo:高速超声波的模组响应信号
#define 	TRIG	P47
#define 	ECHO	P33// 新版超声波需要
#define  MS2	1void Ultrasonic_init();// 返回值为char,因为有负数,代表不同的状态,返回0,才代表成功获取距离
char Ultrasonic_get_distance(float *distance);#endif

5. 电源检测模块(Battery.c)

通过ADC检测电池电压,实现低电量报警功能。

#include    "Battery.h"
#include	"ADC.h"
#include	"NVIC.h"
static void GPIO_config(void) {GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义GPIO_InitStructure.Pin  = GPIO_PIN;		//指定要初始化的IO,// 高阻输入GPIO_InitStructure.Mode = GPIO_HighZ;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_PORT, &GPIO_InitStructure);//初始化
}
/******************* AD配置函数 *******************/
static void	ADC_config(void)
{ADC_InitTypeDef		ADC_InitStructure;		//结构定义ADC_InitStructure.ADC_SMPduty   = 31;		//ADC 模拟信号采样时间控制, 0~31(注意: SMPDUTY 一定不能设置小于 10)ADC_InitStructure.ADC_CsSetup   = 0;		//ADC 通道选择时间控制 0(默认),1ADC_InitStructure.ADC_CsHold    = 1;		//ADC 通道选择保持时间控制 0,1(默认),2,3ADC_InitStructure.ADC_Speed     = ADC_SPEED_2X1T;		//设置 ADC 工作时钟频率	ADC_SPEED_2X1T~ADC_SPEED_2X16TADC_InitStructure.ADC_AdjResult = ADC_RIGHT_JUSTIFIED;	//ADC结果调整,	ADC_LEFT_JUSTIFIED,ADC_RIGHT_JUSTIFIEDADC_Inilize(&ADC_InitStructure);		//初始化ADC_PowerControl(ENABLE);				//ADC电源开关, ENABLE或DISABLENVIC_ADC_Init(DISABLE,Priority_0);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
}
// 初始化
void Battery_init() {GPIO_config();ADC_config();
}
// 获取电池电压
float Battery_get_voltage() {u16 adc_value;float vol;// 读采样值adc_value = Get_ADCResult(BATTERY_ADC_CH);// 转换为电压vol =  adc_value * 2.5 / 4095;  // 返回 return vol * 6.1;
}

Battery.h

#ifndef __BATTERY_H__
#define __BATTERY_H__#include "GPIO.h"#define BATTERY_ADC_CH	ADC_CH3
#define GPIO_PORT		GPIO_P1
#define GPIO_PIN		GPIO_Pin_3// 初始化
void Battery_init();
// 获取电池电压
float Battery_get_voltage();#endif

调试与优化技巧

  1. 分模块调试

    • 先单独测试每个模块:电机是否能正常转动、传感器是否能正确读数
    • 使用串口打印传感器数据,验证数据准确性
  2. 巡线参数优化

    • 调整传感器安装高度,确保能清晰区分黑线和白色背景
    • 根据实际赛道宽度调整转向幅度和时间
    • 复杂弯道可增加PID控制算法提高跟踪精度
  3. 避障逻辑优化

    • 多次测量取平均值,减少超声波测距误差
    • 根据小车速度动态调整避障反应距离
    • 旋转角度和移动距离需要根据实际车体尺寸校准
  4. 系统稳定性提升

    • 电机驱动部分增加续流二极管,减少对控制系统的干扰
    • 传感器供电增加滤波电容,提高读数稳定性
    • 关键代码添加超时处理,避免系统卡死

总结与扩展

本项目实现了麦特轮小车的基本巡线和避障功能,通过模块化设计使系统具有良好的可扩展性。未来可以在此基础上添加更多高级功能:

  • 增加蓝牙模块实现手机APP远程控制
  • 加入OLED显示屏显示实时状态和数据
  • 实现多模式切换(如循迹模式、避障模式、遥控模式)
  • 优化控制算法,采用PID控制实现更平稳的运动
http://www.dtcms.com/a/350511.html

相关文章:

  • IEEE子刊 | 注意缺陷多动障碍的功能连接模式:近红外机器学习研究
  • QML中的QtObject
  • QT新建文件或者项目解释:那些模板分别是什么意思?
  • 前端部署终极详细指南
  • 容器日志收集配置在云服务器环境中的集成方案
  • JWT用户认证后微服务间如何认证?(双向TLS(mTLS)、API网关、Refresh Token刷新Token)微服务间不传递用户认证Token
  • C-JSON接口的使用
  • 【什么是端到端模型】
  • 益莱储@PCIe技术大会
  • Bright Data 代理 + MCP :解决 Google 搜索反爬的完整方案
  • WPF 参数设置界面按模型字段自动生成设置界面
  • Docker:网络连接
  • python面试题目100个(更新中预计10天更完)
  • 深度学习(二):数据集定义、PyTorch 数据集定义与使用(分板块解析)
  • 决策树原理与 Sklearn 实战
  • 【动手学深度学习】7.1. 深度卷积神经网络(AlexNet)
  • 0825 http梳理作业
  • 【慕伏白】CTFHub 技能树学习笔记 -- Web 之信息泄露
  • Linux多线程[生产者消费者模型]
  • python项目中pyproject.toml是做什么用的
  • 【Canvas与标牌】维兰德汤谷公司logo
  • Hadoop MapReduce Task 设计源码分析
  • java-代码随想录第十七天| 700.二叉搜索树中的搜索、617.合并二叉树、98.验证二叉搜索树
  • C++ STL 专家容器:关联式、哈希与适配器
  • 《微服务架构下API网关流量控制Bug复盘:从熔断失效到全链路防护》
  • 精准测试的密码:解密等价类划分,让Bug无处可逃
  • 【C语言16天强化训练】从基础入门到进阶:Day 11
  • 朴素贝叶斯算法总结
  • 互联网大厂Java面试实录:Spring Boot与微服务架构解析
  • cmd命令行删除文件夹