根据前面的各个模块的学习,我们现在就可以实现一个自己的蓝牙无线智能小车了;该小车具有以下基本功能:
避障功能-超声波测距实现;
黑白巡线-红外探测实现;
手机控制-蓝牙通信;
其他小车基本动作;
该小车仅实现了一个小车的基本功能,旨在学习各个模块的应用并将其整合到一个实际项目中,在此基础上完全可以加入更多的功能,让你的小车可玩性更高。小车主要运用超声测距方式进行避障功能,使用红外探测的方式进行黑白寻迹,使用蓝牙与手机APP进行通信,让小车可以远程控制。制作中可参考以下各个模块的应用:
零知智障小车系列1 - L298N模块使用(点击跳转)小车底盘+车轮(2个主轮和1个万向轮)
电池9V(干电池组)
蓝牙 JDY18串口透传模块
直流减速电机 TT马达 2个
电机驱动板 L298N 一个
超声避障模块 (包括支架)
舵机(包括云台)
红外寻迹模块 4路(包含控制板)
零知标准板 1个
螺丝、杜邦线等配件
组装的灵活度较大,可以根据自己的需求进行合理组装模块。这里的建议是边组装边调试,即组装一个模块就调试一个模块,该模块正常工作后再组装下一个,这样就更容易完成整个小车的组装。接线时注意电源的VCC电压大小,这里有三种:3.3v、5v、9v,要接正确。详细接线图如下:
软件设计保护两个部分:单片机程序和安卓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(点击下载)