零知智能避障小车项目 - 人人都能实现的无线控制小车

根据前面的各个模块的学习,我们现在就可以实现一个自己的蓝牙无线智能小车了;该小车具有以下基本功能:

避障功能-超声波测距实现;
黑白巡线-红外探测实现;
手机控制-蓝牙通信;
其他小车基本动作;

步骤

1、知识预备

该小车仅实现了一个小车的基本功能,旨在学习各个模块的应用并将其整合到一个实际项目中,在此基础上完全可以加入更多的功能,让你的小车可玩性更高。小车主要运用超声测距方式进行避障功能,使用红外探测的方式进行黑白寻迹,使用蓝牙与手机APP进行通信,让小车可以远程控制。制作中可参考以下各个模块的应用:

零知智障小车系列1 - L298N模块使用(点击跳转)
零知智障小车系列2 - 超声波模块使用(点击跳转)
零知智障小车系列3 - 红外寻迹模块使用(点击跳转)
零知智障小车系列4 - 蓝牙模块与手机APP通信(点击跳转)

2、硬件准备

小车底盘+车轮(2个主轮和1个万向轮)
电池9V(干电池组)
蓝牙 JDY18串口透传模块
直流减速电机 TT马达 2个
电机驱动板 L298N 一个
超声避障模块 (包括支架)
舵机(包括云台)
红外寻迹模块 4路(包含控制板)
零知标准板 1个
螺丝、杜邦线等配件

3、组装

组装的灵活度较大,可以根据自己的需求进行合理组装模块。这里的建议是边组装边调试,即组装一个模块就调试一个模块,该模块正常工作后再组装下一个,这样就更容易完成整个小车的组装。接线时注意电源的VCC电压大小,这里有三种:3.3v、5v、9v,要接正确。详细接线图如下:

4、软件设计

软件设计保护两个部分:单片机程序和安卓APP。

(1)单片机程序

单品机程序使用函数封装的形式设计,每个模块都实现相应的功能函数,然后在主程序中调用。

首先需要进行引脚的设置,具体代码如下:

引脚定义:

												
	#include <Servo.h>//舵机控制库
	 
	//调试控制,注释掉下面两句取消调试
	#define DEBUG_PRINT                //调试串口打印输出控制
	#define DEBUG_REMOTE        //将获取到的数据发送到手机APP,便于调试
	 
	//所有引脚的设置
	//1、L298N电机驱动
	#define LEFT_MOTOR1         2
	#define LEFT_MOTOR2                3
	#define RIGHT_MOTOR1         4
	#define RIGHT_MOTOR2         5
	#define LEFT_ENA                 6        
	#define RIGHT_ENB                 9
	//2、超声避障SR04
	#define SR04_TRIG                10
	#define SR04_ECHO                11
	//3、舵机(辅助超声避障功能)
	#define SERVO_PWM                12
	//4、红外寻迹
	#define LINE1                        A0
	#define LINE2                        A1
	#define LINE3                        A2
	#define LINE4                        A3
	//5、蓝牙模块 JDY-18
	#define BLE_SERIAL                Serial1
	 
	//控制命令定义
	#define        STOP                 0
	#define        FORWARD                1
	#define        BACKWARD        2
	#define        TURNLEFT        3
	#define        TURNRIGHT        4
	 
	Servo myServo;//舵机												
						
引脚模式设置:

								
		void setup() {
		        //引脚模式设置
		        pinMode(LED_BUILTIN, OUTPUT);//板载LED
		        Serial.begin(9600);//用于调试
		         
		        pinMode(LEFT_MOTOR1, OUTPUT);
		        pinMode(LEFT_MOTOR2, OUTPUT);
		        pinMode(RIGHT_MOTOR1, OUTPUT);
		        pinMode(RIGHT_MOTOR2, OUTPUT);
		        pinMode(LEFT_ENA,OUTPUT);
		        pinMode(RIGHT_ENB,OUTPUT);
		         
		        pinMode(SR04_TRIG, OUTPUT);
		        pinMode(SR04_ECHO, INPUT);
		         
		        myServo.attach(SERVO_PWM);
		         
		        pinMode(LINE1,INPUT);
		        pinMode(LINE2,INPUT);
		        pinMode(LINE3,INPUT);
		        pinMode(LINE4,INPUT);
		         
		        BLE_SERIAL.begin(9600);
		        BLE_SERIAL.println("AT+NAMELingzhiBleCar");//设置蓝牙设备名称
		}
		
电机驱动函数接口

该函数接口实现根据不同命令进行电机的动作,包含:前进、后退、左转、右转和停止功能。具体代码如下:


									
	void motor_run(int cmd){
	        switch(cmd)
	        {
	                case FORWARD:
	                        #ifdef DEBUG_PRINT
	                        Serial.println("向前");
	                        #endif
	                        analogWrite(LEFT_ENA,highSpeed);
	                        digitalWrite(LEFT_MOTOR1, HIGH);
	                        digitalWrite(LEFT_MOTOR2, LOW);
	                        analogWrite(RIGHT_ENB,highSpeed);
	                        digitalWrite(RIGHT_MOTOR1, HIGH);
	                        digitalWrite(RIGHT_MOTOR2, LOW);
	                break;
	                case BACKWARD:
	                        #ifdef DEBUG_PRINT
	                        Serial.println("向后");
	                        #endif
	                        digitalWrite(LEFT_MOTOR1,  LOW);
	                        digitalWrite(LEFT_MOTOR2, HIGH);
	                        digitalWrite(RIGHT_MOTOR1, LOW);
	                        digitalWrite(RIGHT_MOTOR2, HIGH);
	                break;
	                case TURNLEFT:
	                        #ifdef DEBUG_PRINT
	                        Serial.println("左转");
	                        #endif
	                        analogWrite(LEFT_ENA,lowSpeed);
	                        analogWrite(RIGHT_ENB,highSpeed);
	                        digitalWrite(LEFT_MOTOR1, HIGH);
	                        digitalWrite(LEFT_MOTOR2, LOW);//C
	                        digitalWrite(RIGHT_MOTOR1, HIGH);
	                        digitalWrite(RIGHT_MOTOR2, LOW);
	                break;
	                case TURNRIGHT:
	                        #ifdef DEBUG_PRINT
	                        Serial.println("右转");
	                        #endif
	                        analogWrite(LEFT_ENA,highSpeed);
	                        analogWrite(RIGHT_ENB,lowSpeed);
	                        digitalWrite(LEFT_MOTOR1, HIGH);
	                        digitalWrite(LEFT_MOTOR2, LOW);
	                        digitalWrite(RIGHT_MOTOR1, HIGH);//C
	                        digitalWrite(RIGHT_MOTOR2, LOW);
	                break;
	                case STOP:
	                default:
	                        #ifdef DEBUG_PRINT
	                        Serial.println("停止");
	                        #endif
	                        digitalWrite(LEFT_ENA,1);
	                        digitalWrite(RIGHT_ENB,1);
	                        digitalWrite(LEFT_MOTOR1, LOW);
	                        digitalWrite(LEFT_MOTOR2, LOW);
	                        digitalWrite(RIGHT_MOTOR1, LOW);
	                        digitalWrite(RIGHT_MOTOR2, LOW);
	                break;
	        }
	}		
		
然后在loop()函数中调用,进行调试:

										
	void loop() {
	         
	        if(Serial.available()>0)
	        {
	                Serial.print("cmd:");
	                 
	                int cmd = Serial.read() - 48;
	                Serial.println(cmd);
	                 
	                motor_run(cmd);
	        }
	         
	}								
		

调试时根据小车实际运行的方向进行参数的调试,直到实际运行的方向与预定方向一致。

超声避障

该功能先调试在固定方向时的距离测量,具体代码如下:


									
	int getInstance()
	{
	        digitalWrite(SR04_TRIG, LOW);
	        delayMicroseconds(2);
	        digitalWrite(SR04_TRIG, HIGH);
	        delayMicroseconds(10);
	        digitalWrite(SR04_TRIG, LOW);
	         
	        int distance = pulseIn(SR04_ECHO, HIGH);
	        distance = distance/58.0;
	         
	        return distance;
	}						
		

现在我们再结合舵机,在前方、左边、右边方向上不断探测小车前面的障碍物距离,并且在障碍物距离为70cm时候按照当前方向进行调整,实现自动避障。具体实现如下:


								
	void avoidance(int avoidDistance)
	{
	        int i = 0;
	         
	        int time = 30;//舵机速度
	         
	        int disFor = 0;//探测的距离
	        int disLeft = 0;
	        int disRight = 0;
	         
	        int d = 70;
	         
	        //首先探测正前方
	        myServo.write(d);
	        for(i=0;i<5;i++)
	        {
	                disFor += getDistance();
	                delay(50);
	        }
	        disFor = disFor/5;
	        #ifdef DEBUG_PRINT
	                Serial.print(" 前方距离:");
	                Serial.println(disFor);
	        #endif
	         
	        if(disFor <= avoidDistance)//正前方有障碍物
	        {
	                //停车
	                motor_run(0);
	                 
	                int t = 0;
	                int total = 0;
	                 
	                //探测右边
	                for(i=d; i>0;i--)
	                {
	                        myServo.write(i);
	                        delay(time);
	                }
	                for(i=0;i<5;i++)
	                {
	                        disRight += getDistance();
	                        delay(50);
	                }
	                disRight = disRight/5;
	                 
	                #ifdef DEBUG_PRINT
	                        Serial.print(" 右侧距离:");
	                        Serial.println(disRight);
	                #endif
	                 
	                myServo.write(d);
	                 
	                //探测左边
	                for(i=d; i<170;i++)
	                {
	                        myServo.write(i);
	                        delay(time);
	                }
	                for(i=0;i<5;i++)
	                {
	                        disLeft += getDistance();
	                        delay(50);
	                }
	                disLeft = disLeft/5;
	                 
	                #ifdef DEBUG_PRINT
	                        Serial.print(" 左侧距离:");
	                        Serial.println(disLeft);
	                #endif
	                 
	                myServo.write(d);
	                 
	                int rollTime = 1000;
	                 
	                //两边都有障碍物,掉头
	                if((disLeft <= avoidDistance) && (disRight <= avoidDistance))
	                {
	                        motor_run(3);
	                        Serial.println("--掉头");
	                        delay(rollTime*2);
	                }
	                else if(disLeft >= disRight)
	                {
	                        motor_run(3);//左转
	                        delay(rollTime);
	                }
	                else if(disLeft < disRight)
	                {
	                        motor_run(4);//右转
	                        delay(rollTime);
	                }
	                motor_run(0);//停止
	                 
	        }else{
	                motor_run(1);//无障碍,前进
	        }
	         
	        //delay(300);        
	}
	

程序中的有些参数需根据自己实际调试时的情况进行调整。在loop中调用该函数,把小车放在一个有很多隔板的场地,然后就可以观察到小车会根据前方障碍物的情况自动避障。

自动巡线

自动巡线是根据红外探测黑白区域实现,这里是在黑色区域上沿着白线前进,遇到一方出白线时自动调整方向以便一直沿着白线前进,知道遇到两边检测到都是黑色,结束。代码如下:


								
	void lineTrace()
	{
	        int IR_1 = digitalRead(LINE1);
	        int IR_2 = digitalRead(LINE2);
	         
	        if(IR_1 == 0 && IR_2 == 0)//在白色线上
	        {
	                motor_run2(1);//前进
	        }
	        if(IR_1 == 1 && IR_2 == 0)//Line1 出白线
	        {
	                motor_run2(3);//左转
	        }
	        if(IR_1 == 0 && IR_2 == 1)//Line2 出白线
	        {
	                motor_run2(4);//右转
	        }
	}								
	

由于这里在拐弯时需要尽量短时间完成,因此把电机驱动改为如下motor_run2();如下:


													
	//拐弯时候一个轮子动,一个不懂,更快拐弯
	void motor_run2(int cmd)
	{
	        int low = 90;
	        int high = 120;
	        switch(cmd)
	        {
	                case FORWARD:
	                        #ifdef DEBUG_PRINT
	                        Serial.println("向前");
	                        #endif
	                        analogWrite(LEFT_ENA,low);
	                        analogWrite(RIGHT_ENB,low);
	                        digitalWrite(LEFT_MOTOR1, HIGH);
	                        digitalWrite(LEFT_MOTOR2, LOW);
	                        digitalWrite(RIGHT_MOTOR1, HIGH);
	                        digitalWrite(RIGHT_MOTOR2, LOW);
	                break;
	                case BACKWARD:
	                        #ifdef DEBUG_PRINT
	                        Serial.println("向后");
	                        #endif
	                        digitalWrite(LEFT_MOTOR1,  LOW);
	                        digitalWrite(LEFT_MOTOR2, HIGH);
	                        digitalWrite(RIGHT_MOTOR1, LOW);
	                        digitalWrite(RIGHT_MOTOR2, HIGH);
	                break;
	                case TURNLEFT:
	                        #ifdef DEBUG_PRINT
	                        Serial.println("左转");
	                        #endif
	                        analogWrite(LEFT_ENA,low);
	                        analogWrite(RIGHT_ENB,low);
	                        digitalWrite(LEFT_MOTOR1, LOW);
	                        digitalWrite(LEFT_MOTOR2, LOW);//C
	                        digitalWrite(RIGHT_MOTOR1, HIGH);
	                        digitalWrite(RIGHT_MOTOR2, LOW);
	                break;
	                case TURNRIGHT:
	                        #ifdef DEBUG_PRINT
	                        Serial.println("右转");
	                        #endif
	                        analogWrite(LEFT_ENA,low);
	                        analogWrite(RIGHT_ENB,low);
	                        digitalWrite(LEFT_MOTOR1, HIGH);
	                        digitalWrite(LEFT_MOTOR2, LOW);
	                        digitalWrite(RIGHT_MOTOR1, LOW);
	                        digitalWrite(RIGHT_MOTOR2, LOW);
	                break;
	                case STOP:
	                default:
	                        #ifdef DEBUG_PRINT
	                        Serial.println("停止");
	                        #endif
	                        digitalWrite(LEFT_ENA,1);
	                        digitalWrite(RIGHT_ENB,1);
	                        digitalWrite(LEFT_MOTOR1, LOW);
	                        digitalWrite(LEFT_MOTOR2, LOW);
	                        digitalWrite(RIGHT_MOTOR1, LOW);
	                        digitalWrite(RIGHT_MOTOR2, LOW);
	                break;
	        }
	}					
		
蓝牙控制

蓝牙模块与手机APP进行通信,接收到APP发来的信息后根据命令进行动作:


											
	void bleControl()
	{
	        if(Serial1.available()>0){
	                char x = Serial1.read();
	                 
	                int cmd = (int)(x);//字符转数字
	                 
	                Serial.print("recv cmd:");
	                Serial.println((int)(x));
	                 
	                motor_run(cmd);
	                 
	                //current_dir = cmd;
	        }
	        delay(500);
	}			
				

(2)手机APP程序设计

手机APP程序基于android平台,使用FastBle库进行编写的。有一定基础的可以直接在改程序上进行修改,操作界面如下:

扫描设备 连接设备 操作界面

手机APP程序:零知智障小车APP.apk(点击下载)

完整项目零知平台软件代码:零知智障小车-单片机程序.7z(点击下载)