麦特轮巡线避障小车开发
麦特轮巡线避障小车开发:从硬件到智能控制
大家好!今天我要分享一个基于麦特轮的智能巡线避障小车项目。这款小车不仅能沿着预设的黑线自主行驶,还能通过超声波传感器检测前方障碍物并自动避让,实现了"循迹+避障"的双重智能控制。本文将详细介绍系统设计框架和关键技术点。
项目概述
麦特轮(Mecanum Wheel)凭借其独特的全向移动能力,让小车能够灵活地前进、后退、横移和旋转,这为复杂环境下的巡线和避障提供了巨大便利。本项目实现的核心功能包括:
- 基础交互:LED灯光指示、蜂鸣器报警提示、按键模式控制
- 电源管理:实时电池电量检测与低电量报警
- 环境感知:超声波测距(HC-SR04)实现障碍物检测
- 运动控制:麦特轮全向运动驱动
- 智能行为:黑线识别跟踪+30cm内障碍物左转向避让
硬件系统设计
核心控制器选择
推荐使用STM32F103系列单片机作为主控制器,其丰富的GPIO接口、定时器资源和ADC模块能够完美支持多传感器数据采集和电机控制需求。
硬件组成清单
-
运动系统
- 4个65mm麦特轮
- 4个N20减速电机(带编码器可选)
- 2个TC1508A电机驱动模块(每模块控制2个电机)
-
感知系统
- 5路热敏电阻感知模块(用于黑线检测)
- HC-SR04超声波模块(测距范围2-400cm)
- 电压检测电路(电阻分压网络)
-
交互系统
- 3-5个LED指示灯(电源、运行、故障等)
- 有源蜂鸣器(提示音输出)
- 1个按键(启动/停止、模式切换、参数调整)
-
电源系统
- 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
调试与优化技巧
-
分模块调试
- 先单独测试每个模块:电机是否能正常转动、传感器是否能正确读数
- 使用串口打印传感器数据,验证数据准确性
-
巡线参数优化
- 调整传感器安装高度,确保能清晰区分黑线和白色背景
- 根据实际赛道宽度调整转向幅度和时间
- 复杂弯道可增加PID控制算法提高跟踪精度
-
避障逻辑优化
- 多次测量取平均值,减少超声波测距误差
- 根据小车速度动态调整避障反应距离
- 旋转角度和移动距离需要根据实际车体尺寸校准
-
系统稳定性提升
- 电机驱动部分增加续流二极管,减少对控制系统的干扰
- 传感器供电增加滤波电容,提高读数稳定性
- 关键代码添加超时处理,避免系统卡死
总结与扩展
本项目实现了麦特轮小车的基本巡线和避障功能,通过模块化设计使系统具有良好的可扩展性。未来可以在此基础上添加更多高级功能:
- 增加蓝牙模块实现手机APP远程控制
- 加入OLED显示屏显示实时状态和数据
- 实现多模式切换(如循迹模式、避障模式、遥控模式)
- 优化控制算法,采用PID控制实现更平稳的运动