*这篇文章是作者的本科毕业设计,属于作者的原创文章,禁止随意转载,仅供大家学习参考。*
基于Arduino的避障小车现在已经有了很多的帖子和教程,但本篇文章为避障小车添加了更多的实用的功能,比如:拍照、人体感应、蜂鸣器响应、蓝牙控制、自动模式和手动模式之间的切换。这篇文章的避障小车使用的是超声波避障模块。
下面介绍一下这篇文章使用到的一些模块:
下面介绍这篇文章中各个模块之间的接线关系
MEGA2560传感器扩展板直接插接在2560开发板上。2560的扩展板的每个数字接口和模拟接口旁边都有独立的VCC和GND接口,这也是为什么需要用到扩展板的原因之一。有关2560扩展板的详情请参考MEGA2560传感器扩展板
超声波传感器:我一共用了三个超声波传感器,分别放在小车前面的舵机云台上、小车左方、小车右方,舵机云台由舵机控制,可以让云台上的超声波传感器探测更大的角度。HC-SR04超声波传感器一共有4个引脚,分别是VCC、GND、Trig、Echo。HC-SR04超声波传感器的具体用法请参考SR04超声波模块,SR04超声波传感器在Arduino中有官方的库文件,只需要调用即可,不需要自己定义函数去计时换算成距离。超声波传感器的GND连Arduino的GND,VCC连VCC就不多说了(随便哪一个口的VCC、GND都可以接,推荐接用到的那个口的GND、VCC)。
SR04 | Arduino |
---|---|
前Trig | D4 |
前Echo | D5 |
左Trig | D9 |
左Echo | D8 |
右Trig | D16 |
右Echo | D17 |
D是数字量接口,A是模拟量接口。大部分传感器或者模块都使用数字量接口,也有一部分传感器使用模拟量接口,这篇文章中模拟量接口只用到了一个,就是人体红外传感器使用的模拟量接口。
舵机:舵机控制的是小车前方的云台,云台上放了一个超声波传感器,通过开发板写角度,改变舵机的角度。SG90跟大部分舵机一样是三个接口,分别是S、VCC、GND,S是控制接口也称信号接口,大部分的传感器或者模块都是这三个接口。舵机使用方法非常简单,同样的拥有官方库文件,使用时只需要调用库函数就行了。例如:myservo.write(val);定义一个整型变量“val”通过改变val的值,再用这个函数,就可以改变舵机的角度。角度当然也可以一度一度地改变,只需要用一个循环语句,让舵机每变一度再延时几毫秒就可以了,这种一度一度地改变的方法更符合使用实际。
舵机 | Arduino |
---|---|
S | D2 |
舵机的三根线通常是是连在一起的杜邦线,一般认线的颜色就可以了,黄色的是信号线,红色的是VCC,黑色的是GND,竖着一排插在数字量接口那里,线的排列顺序刚好也和扩展板上接口的排列顺序一一对应。
人体红外传感器:AM412人体红外传感器是普通HC-SR501人体红外传感器的升级版,模块体积更小,但少了两个可调节电位器,正常使用来说,那两个可调节的电位器可以去掉。AM412人体红外传感器一共有三个引脚,OUT、VCC、GND。人体红外传感器的具体使用方法请参考人体红外传感器
人体红外传感器 | Arduino |
---|---|
OUT | A0 |
人体红外传感器是模拟信号,所以要连扩展板的模拟量接口。
蜂鸣器:蜂鸣器可以和人体红外传感器一起使用,当有人经过人体红外传感器时,蜂鸣器就会响应。蜂鸣器有三个引脚,S、VCC、GND。这里的蜂鸣器是低电平响应。
蜂鸣器 | Arduino |
---|---|
S | D14 |
蓝牙模块:蓝牙模块是与手机相连接的,手机上下载好相应的APP后(我使用的APP是“蓝牙串口”),可以发送数据给蓝牙模块,开发板接收到蓝牙模块的串口数据后执行相应的命令。蓝牙模块买到手后需要经过调试才能正常使用,一般使用USB转串口模块,将蓝牙模块插在USB转串口上,或者用开发板直接连蓝牙调试,这里调试一般为AT指令调试(使用开发板直连蓝牙模块调试时,不能将蓝牙接在D0和D1引脚上,其它数字引脚都可以,只需在程序中声明就行,因为D0、D1是开发板默认的与电脑USB的串口通讯接口,与电脑连接时不能被占用,否则电脑不能通过USB与开发板之间进行读写)。调试的主要内容是:①更改蓝牙模块的蓝牙名称和连接密码。②将蓝牙设置成从机模式(蓝牙分主机模式和从机模式,主机模式蓝牙开机自动扫描连接上次连接成功的手机或其他设备,从机模式开机后等待被连接)。③设置蓝牙波特率为9600(我习惯波特率都用9600)。
蓝牙模块具体的操作办法请参考HC-05蓝牙模块
在这篇文章中,我将手机发送给蓝牙的数据定义为整型变量,也就是数字的形式,比如改变手动自动模式或者执行前进命令都是不同的数字,这样在编程的时候比较方便。
HC-05蓝牙模块有很多个引脚,但是一般用到的就4个,分别是:RXD、TXD、VCC、GND,一个接收数据,一个发送数据,一个接电源、一个接地。当蓝牙模块调试好后,与开发板之间的连接方法如下表所示,但为了不占用与电脑之间的硬串口,每次开发板与电脑相连时,需要把蓝牙RXD、TXD的两根线拔掉(或者你直接定义另外两个数字量引脚也可以,这里用的是通用的蓝牙引脚D0、D1)。
HC-05蓝牙 | Arduino |
---|---|
TXD | D0 |
RXD | D1 |
串口相机:这篇文章使用的是VC0706串口相机,之所以使用这个相机是因为网络上有这个相机的第三方库文件,使用起来比较方便。但有个问题是,网络上下载的库文件中的例程,不能直接使用,直接使用的话会出现拍的照片只有一半或一少半显示出来,能完全显示出照片的教程在VC0706使用教程
这个教程中是将VC0706弄成了一个延时摄影(延时摄影是自动每隔几秒拍照一张,再后期通过电脑合成视频,视频看上去就是加快了速度)的工具,如果只需要VC0706执行拍一张照,可以将程序中的循环语句删掉,或者使用我后面文章中的程序。串口相机买来后基本不需要调试可以直接使用,网络上找到的相关资料都是收费的,所以我也就没调试。VC0706串口相机也有很多的引脚,但是有用的也只有TX、RX、VCC、GND。
VC0706 | Arduino |
---|---|
TX | D10 |
RX | D13 |
SD卡模块:SD卡模块也就是TF读写储存模块是用来储存串口相机拍摄的照片的。SD卡模块上插入一个SD卡,接在开发板上就可以直接使用了。
SD卡模块使用到的引脚有:CS(有的也称SS,这是SD卡的片选引脚,也就是开发板选择把数据存在这张卡里)、MOSI、MISO、SCK、VCC、GND。SD卡模块比较特殊在UNO和MEGA2560开发板上都有固定的引脚(且两个板子默认的引脚都不同),当然也可以自己定义引脚,这里我比较倾向于使用默认的引脚。SD卡模块在UNO上使用的教程请参考SD卡模块UNO
下表是SD卡模块在MEGA2560开发板上的引脚接线。
SD卡模块 | Arduino |
---|---|
CS/SS | D53 |
MOSI | D51 |
MISO | D50 |
SCK | D52 |
驱动模块:驱动模块我选用的是L298N双桥驱动板,带动直流电机。
左边轮子的直流电机连接L298N的OUT1、OUT2。
右边轮子的直流电机连接L298N的OUT3、OUT4。
其中L298N和开发板相连的几个引脚当中,ENA和ENB必须要连有PWM功能的数字量引脚,因为直流电机调速要用到PWM脉宽调制。
在驱动板内部,OUT1是与IN1相连的、OUT2与IN2相连,以此类推。ENA是左边电机的调速引脚,ENB是右边电机的调速引脚。
L298N | Arduino |
---|---|
ENA | D12(PWM) |
IN1 | D32 |
IN2 | D33 |
IN3 | D34 |
IN4 | D35 |
ENB | D11(PWM) |
另外驱动板因为电流很大,需要独立提供电源,我用了12V的电源供电,且驱动板的电源不能和开发板的电源引脚相连,但可以共地。
我将蓝牙的串口数据定义为整型变量,当手机发送0时,小车停止;当手机发送1时,小车前进;当手机发送2时,小车倒退;当手机发送3时,小车拍照;当小车发送6时,小车的蜂鸣器响应;当手机发送4时,小车切换成手动模式(小车开机默认为手动模式);当手机发送5时,小车切换成自动模式。自动模式下,小车将执行自动避障程序,直到接收到蓝牙的停止命令。
下图是小车完整的程序框图:
下面是这篇文章的完整程序,程序段中已经注明了注释,若还有不懂的也可以回复问我,我随缘解答。
#include //调用软串口库函数
#include //调用VC0706相机库函数
#include //调用SD卡库函数
#include //调用舵机库函数
#include //调用超声波传感器库函数(函数调用完成)
#include
SoftwareSerial BT(0, 1);//定义蓝牙的TX和RX引脚(蓝牙模块)
#define chipSelect 53//定义SD卡的片选引脚(摄像头、SD卡模块)
SoftwareSerial cameraconnection = SoftwareSerial(10,13);
Adafruit_VC0706 cam = Adafruit_VC0706(&cameraconnection);//(摄像头、SD卡模块初始化前的操作)
#define Buzzerpin 14//定义蜂鸣器的引脚
#define PIR_sensor A0//定义人体红外传感器的引脚(红外、蜂鸣器模块)
#define TRIG_PIN 5//定义前方超声波传感器控制端
#define ECHO_PIN 4//定义前方超声波传感器接收端
#define TRIGright_PIN 16//定义右方超声波传感器控制端
#define ECHOright_PIN 17//定义右方超声波传感器接收端
#define TRIGleft_PIN 9//定义左方超声波传感器控制端
#define ECHOleft_PIN 8//定义左方超声波传感器接收端
#define motor1pin1 32//定义IN1引脚
#define motor1pin2 33//定义IN2引脚
#define motor1pwm 12//定义ENA引脚
#define motor2pin1 34//定义IN3引脚
#define motor2pin2 35//定义IN4引脚
#define motor2pwm 11//定义ENB引脚(避障、驱动模块)(各模块引脚定义完成)
Servo myservo;//创建一个舵机控制对象
int djangle=90;//用该变量储存舵机角度位置
SR04 sr04=SR04(ECHO_PIN,TRIG_PIN);//定义前方超声波传感器
SR04 sr04r=SR04(ECHOright_PIN,TRIGright_PIN);//定义右方超声波传感器
SR04 sr04l=SR04(ECHOleft_PIN,TRIGleft_PIN);//定义左方超声波传感器
long forward;//储存舵机在90°时障碍物的距离
long leftfw;//储存舵机在135°(左前方)时障碍物的距离
long rightfw;//储存舵机在45°(右前方)时障碍物的距离
long left;//储存左方超声波传感器数值
long right;//储存右方超声波传感器数值
int l;//定义左电机速度变量
int r;//定义右电机速度变量
int n;//计数
int m;//计数
void motor(int motorpin1,int motorpin2,int motorpwm,int val)//定义一个电机转动函数
{
pinMode(motorpin1,OUTPUT);//输出第一个引脚
pinMode(motorpin2,OUTPUT);//输出第二个引脚
digitalWrite(motorpin1,1);//将第一个引脚抬高
digitalWrite(motorpin2,0);//将第二个引脚置低
analogWrite(motorpwm,val);//给EN引脚设定模拟值,设定转速
}//(避障、驱动模块初始化前的操作)
int rentiv=0;//定义人体红外传感器获取到的电位值(红外、蜂鸣器模块初始化前的操作)(初始化前的操作完成)
int opmode;//储存小车运行模式(手动和自动):4手动、5自动
int cmd;//储存小车在手动模式下的运行指令:0停止、1前进、2倒退、3拍照、6响铃
int blcmd;//暂存蓝牙指令
void setup()
{
opmode=4;//设置为手动模式
Serial.begin(9600);//设置串口通讯波特率(软串口)
BT.begin(9600);//设置蓝牙串口波特率(蓝牙模块)
myservo.attach(2);//定义舵机的信号引脚
myservo.write(djangle);//初始化舵机角度(避障、驱动模块)
pinMode(PIR_sensor,INPUT);//将A0引脚置为输入
pinMode(Buzzerpin,OUTPUT);//将14引脚置为输出(红外、蜂鸣器模块)
digitalWrite(Buzzerpin,HIGH);
delay(10000);//延迟10秒等待蓝牙启动并连上手机
Serial.println("已成功连接上小车");//连上蓝牙后通知手机
pinMode(53, OUTPUT);
if (!SD.begin(chipSelect))
{
Serial.println("SD卡异常");
return;
}
if (cam.begin())
{
Serial.println("相机已准备好");
}
else
{
Serial.println("相机异常");
return;
}
cam.setImageSize(VC0706_640x480);
Serial.println("照片尺寸为640x480");
Serial.println("现在是手动模式");//向手机输出“现在是手动模式”
}//(各模块初始化完成)
void loop()
{
while(Serial.available())
{
blcmd=Serial.read();
if(blcmd=='0')
{
Serial.println("接收到停止命令");
cmd=blcmd;
}
if(blcmd=='1')
{
Serial.println("接收到前进命令");
cmd=blcmd;
}
if(blcmd=='2')
{
Serial.println("接收到倒退命令");
cmd=blcmd;
}
if(blcmd=='3')
{
Serial.println("接收到拍照命令");
cmd=blcmd;
}
if(blcmd=='4')
{
Serial.println("切换成手动模式");
opmode=blcmd;
}
if(blcmd=='5')
{
Serial.println("切换成自动模式");
opmode=blcmd;
cmd=10;
}
if(blcmd=='6')
{
Serial.println("接收到响铃命令");
cmd=blcmd;
}
}
if((cmd=='0')&&(opmode=='4'))
{
l=0;
r=0;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);
Serial.println("小车已停止");
}
if((cmd=='0')&&(opmode=='5'))
{
l=0;
r=0;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);
Serial.println("小车已停止");
}
if((cmd=='1')&&(opmode=='4'))
{
l=120;
r=120;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);
Serial.println("小车正在前进");
}
if((cmd=='2')&&(opmode=='4'))
{
l=100;
r=100;
motor(motor1pin2,motor1pin1,motor1pwm,l);
motor(motor2pin2,motor2pin1,motor2pwm,r);
Serial.println("小车正在倒退");
}
if((cmd=='3')&&(opmode=='4'))
{
l=0;
r=0;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);
cmd=10;
Serial.println("小车正在拍照");
delay(20);
if (! cam.takePicture())
Serial.println("拍照失败!");
else
Serial.println("拍照完成!");
char filename[13];
strcpy(filename, "IMAGE000.JPG");
for (int i = 0; i < 1000; i++)
{
filename[5] = '0' + i/100;
filename[6] = '0' + (i%100)/10;
filename[7] = '0' + (i%100)%10;
if (! SD.exists(filename))
{
break;
}
}
File imgFile = SD.open(filename, FILE_WRITE);
uint16_t jpglen = cam.frameLength();
Serial.print(jpglen, DEC);
Serial.println(" byte image");
Serial.print("储存照片到");
Serial.print(filename);
while (jpglen > 0)
{
uint8_t *buffer;
uint8_t bytesToRead = min(32, jpglen);
buffer = cam.readPicture(bytesToRead);
imgFile.write(buffer, bytesToRead);
jpglen -= bytesToRead;
}
imgFile.close();
Serial.println("...完成!");
cam.begin();
}
if((cmd=='6')&&(opmode=='4'))
{
l=0;
r=0;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);
Serial.println("小车正在响铃");
digitalWrite(Buzzerpin,LOW);//发声音
delay(1000);
digitalWrite(Buzzerpin,HIGH);//不发声音
delay(1000);
}
if(opmode=='5')
{
rentiv=analogRead(PIR_sensor);//读取A0口的电压值并赋值到rentiv
Serial.println(rentiv);//串口发送rentiv的值
if(rentiv>600)
{
Serial.println("there is somebody!");
digitalWrite(Buzzerpin,LOW);//发声音
delay(1000);
digitalWrite(Buzzerpin,HIGH);//不发声音
delay(1000);
Serial.println("正在拍照...");
delay(20);
if (! cam.takePicture())
Serial.println("拍照失败!");
else
Serial.println("拍照完成!");
char filename[13];
strcpy(filename, "IMAGE000.JPG");
for (int i = 0; i < 1000; i++)
{
filename[5] = '0' + i/100;
filename[6] = '0' + (i%100)/10;
filename[7] = '0' + (i%100)%10;
if (! SD.exists(filename))
{
break;
}
}
File imgFile = SD.open(filename, FILE_WRITE);
uint16_t jpglen = cam.frameLength();
Serial.print(jpglen, DEC);
Serial.println(" byte image");
Serial.print("储存照片到");
Serial.print(filename);
while (jpglen > 0)
{
uint8_t *buffer;
uint8_t bytesToRead = min(32, jpglen);
buffer = cam.readPicture(bytesToRead);
imgFile.write(buffer, bytesToRead);
jpglen -= bytesToRead;
}
imgFile.close();
Serial.println("...储存完成!");
cam.begin();
rentiv=0;
}
forward=sr04.Distance();//获取前方障碍物距离
left=sr04l.Distance();//获取左方障碍物距离
right=sr04r.Distance();//获取右方障碍物距离
if(forward>30)//当前方障碍物距离大于30cm时
{
if((left>25)&&(right>25))//当左、右方障碍物距离大于25cm时
{
l=120;
r=120;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);//直行
delay(800);
}
if((left>25)&&(right<25))//左边比右边宽敞
{
l=90;
r=120;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);//左转
delay(800);
}
if((left<25)&&(right>25))//右边比左边宽敞
{
l=120;
r=90;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);//右转
delay(800);
}
if((left<25)&&(right<25))//当左、右障碍物距离小于25cm时
{
if(left>right)//如果左边比右边宽敞
{
l=70;
r=80;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);//左转
delay(800);
}
else//如果右边比左边宽敞
{
l=80;
r=70;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);//右转
delay(800);
}
}
}
if((forward<30)&&(forward>15))//当前方障碍物距离在15cm到30cm
{
l=0;
r=0;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);//让小车停下来
djangle=135;
myservo.write(djangle);//让舵机转到135°
delay(250);
for(n=0;n<2;n++)
{
leftfw=sr04.Distance();//左前方获取数据
delay(100);
}
djangle=45;
myservo.write(djangle);//让舵机转到45°
delay(400);
for(n=0;n<2;n++)
{
right=sr04.Distance();//右前方获取数据
delay(100);
}
djangle=90;
myservo.write(djangle);//舵机归中
delay(250);
if(leftfw>rightfw)//如果左前方比右前方宽敞
{
l=0;
r=80;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);//左转
delay(500);
}
else//右边比左边宽敞
{
l=80;
r=0;
motor(motor1pin1,motor1pin2,motor1pwm,l);
motor(motor2pin1,motor2pin2,motor2pwm,r);//右转
delay(500);
}
}
if(forward<15)
{
l=100;
r=100;
motor(motor1pin2,motor1pin1,motor1pwm,l);
motor(motor2pin2,motor2pin1,motor2pwm,r);//倒车
delay(500);
}
}
}