【S144】基于探索者套件的智能导盲小车
作品说明 |
作者:陈庆洋 陈朝阳 李浩毅
单位:泰山学院 机械与建筑工程学院
指导老师:郭鹏 任崇刚
1. 背景介绍
五彩缤纷的世界把美好展示给了世间的每一个人,但是有一类人却无法看见这光怪陆离的景致,而只能用心感受到生命的每一个诗意——盲人,我们在欣赏美景行万里路时,他们只能凭借想象去认知;当我们博览群书遨游知识海洋,他们只能‘摸’书艰难求索;就连我们最简单的衣食住行,在他们永恒长夜的眼里都是一种奢求。
众所周知,在导盲行业中应用范围最广、应用时间最长的无疑是导盲犬,导盲犬是一种工作犬,其主要工作是代替视障人士的双眼,为他们领路,但导盲犬的筛选与训练要求极为苛刻,按照国际惯例,导盲犬的一生要经过四个阶段:寄养期、培训期、服役期、退役期。对于“准导盲犬”来说,它们出生仅仅两个月后就要开始它们长达18个月的“求学之路”了,它们先被送往爱心志愿者(一般都是养犬高手)家寄养12-14个月,使幼犬适应社会环境、健康成长,然后被送去学校,经过这6个月训练的导盲犬,还要再和真正的盲人一起磨合3-4周,并通过一系列严格的测试才算真正毕业。培训合格的导盲犬将免费交付给适合的视障人士使用,服役期为8—10年,服役期满后的导盲犬将被送到爱心志愿者家庭或送回基地养老。从幼犬40天左右开始挑选、寄养到基地再到盲人适应训练,共有70项之多的国际标准,由于训练难度极大,只有极少的一部分能成为真正的导盲犬。另外导盲犬的“贵”在于珍贵,一只导盲犬的培训在国内需要1年半左右的时间,而且对犬要求比较严格,培训淘汰率高,所以培训出一只导盲犬很不容易,所需人力物力成本较高,培养一只导盲犬费用大概在12万到15万,目前国内导盲犬数量也比较稀少,所以比较珍贵,而这就导致了导盲犬的资源供给与盲人的导盲犬极不对等,通常只有符合一定要求的盲人才有机会得到导盲犬的帮助,更大数量的盲人依然在一片黑暗中艰难摸索本该五彩斑斓的世界。
技术的革新与智能化的高度发展让我们看到了希望,在高度发展的现代化世界,智能为我们展现了太多的奇迹与震撼,让我们看到了价格成本低廉的同时,具有可复制性的智能导盲产品实现的可能性,也终于为一生也许都无法感知色彩的盲人点亮了漆黑世界里的第一盏明灯。
2. 作品设计
本作品设计——基于探索者套件的智能导盲小车,主要使用模块有:Basra主控板、BigFish扩展板、两个触须传感器、触碰传感器、近红外传感器、三轴加速度传感器、高精度gps模块、RFID-RC522读卡器。
本作品的设计主要功能如下:导盲犬的活动性较大,盲人在日常生活中准备出门时需要寻找导盲犬的位置,导盲小车优点在于使用完成后位置较为固定,但缺点也较为明显,就在于导盲犬在需要执行任务时会与主人有交互性的互动,而导盲小车在供电之前无法接受接收执行任务的指令,我们第一版的想法使用了语音模块,但语音模块的不稳定与使用时关键词有可能被误触发直接导致断电或供电的情况,危险性过高,基于此,深思熟虑后我们最终放弃此方案,使用更简洁明了的方法,我们设计的功能最终确定为刷卡解锁供电,在使用完成后刷卡结束供电,以提高用户使用时的操纵感,在节省成本的前提下更加实用。在刷卡解锁供电后,可根据语音识别模块识别用户想要前往的地点自动进行gps导航与路线规划。
安全永远摆在第一位,要想保证特殊群体使用的安全性,首要在于保护车体本身运行的安全性,避免其陷入进退两难的境地,因而我们在设计时着重注重了车体本身运行的稳定性,尽量发挥探索者套件的可能性,以多重检测来实现此目的,具体实现方法为:在车前段设置有双重触须传感器,当小车前方进入狭窄易卡住区域时,触须传感器会先车轮进入区域一步被触发及时进行停止以防车体陷入在狭窄空间内进退两难的境地;同时在车体正前方设有由舵机云台承载的可控环绕超声波探测配合高灵敏度触碰传感器,以防潜在无法检测到的不规则物体阻碍小车前进路线;为进一步最大化合理接口资源分配,车体后方则是由近红外传感器触发判断条件来进行障碍的实时探测与规避,各个模块紧密连接相互配合,能够最大限度全方位地保护车体本身以至于使用者的安全。在大范围上开发应用上,我们选择运用能够与探索者主控板配合的gps定位模块,根据预设的地址,gps能够高精度能够自动带领盲人前往其想去的地方,行星越障轮的设计则是充分考虑到盲人所使用的实时场景,如楼梯及有坡度的复杂地形,仅凭探索者自带越野轮显然难以满足需求,于是我们便根据需求设计了九齿轮3层传动结构的行星越障轮以满足在各个使用场景下的要求。综合上述条件,本作品设计——基于探索者套件的智能导盲小车,就此完成。
作品说明
智能导盲小车
3. 作品设计思路与结构
3.1 设计思路
3.2 模型与工程图
4. 程序控制源代码
① RFID刷卡解锁卡信息读取写入(sketch_nov15a sketch_nov15a.ino)
#include <SPI.h>
#include <MFRC522.h>
#define RST_PIN 9 // 配置针脚
#define SS_PIN 10
MFRC522 mfrc522(SS_PIN, RST_PIN); // 创建新的RFID实例
MFRC522::MIFARE_Key key;
void setup() {
Serial.begin(9600); // 设置串口波特率为9600
while (!Serial); // 如果串口没有打开,则死循环下去不进行下面的操作
SPI.begin(); // SPI开始
mfrc522.PCD_Init(); // Init MFRC522 card
for (byte i = 0; i < 6; i++) {
key.keyByte = 0xFF;
}
Serial.println(F("扫描卡开始进行读或者写"));
Serial.print(F("使用A和B作为键"));
dump_byte_array(key.keyByte, MFRC522::MF_KEY_SIZE);
Serial.println();
Serial.println(F("注意,会把数据写入到卡在#1"));
}
void loop() {
// 寻找新卡
if ( ! mfrc522.PICC_IsNewCardPresent())
return;
// 选择一张卡
if ( ! mfrc522.PICC_ReadCardSerial())
return;
// 显示卡片的详细信息
Serial.print(F("卡片 UID:"));
dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size);
Serial.println();
Serial.print(F("卡片类型: "));
MFRC522:ICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
Serial.println(mfrc522.PICC_GetTypeName(piccType));
// 检查兼容性
if ( piccType != MFRC522:ICC_TYPE_MIFARE_MINI
&& piccType != MFRC522:ICC_TYPE_MIFARE_1K
&& piccType != MFRC522:ICC_TYPE_MIFARE_4K) {
Serial.println(F("仅仅适合Mifare Classic卡的读写"));
return;
}
// 我们只使用第二个扇区
// 覆盖扇区4
byte sector = 1;
byte blockAddr = 4;
byte dataBlock[] = {
0x01, 0x02, 0x03, 0x04, // 1, 2, 3, 4,
0x05, 0x06, 0x07, 0x08, // 5, 6, 7, 8,
0x00, 0x00, 0x00, 0x00, // 0,0,0,0
0x00, 0x00, 0x00, 0x00 // 0,0,0,0
};//写入的数据定义
byte trailerBlock = 7;
MFRC522::StatusCode status;
byte buffer[18];
byte size = sizeof(buffer);
// 原来的数据
Serial.println(F("显示原本的数据..."));
status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522:ICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
if (status != MFRC522::STATUS_OK) {
Serial.print(F("身份验证失败?或者是卡链接失败"));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
// 显示整个扇区
Serial.println(F("显示所有扇区的数据"));
mfrc522.PICC_DumpMifareClassicSectorToSerial(&(mfrc522.uid), &key, sector);
Serial.println();
// 从块儿读取数据
Serial.print(F("读取块儿的数据在:")); Serial.print(blockAddr);
Serial.println(F("块 ..."));
status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size);
if (status != MFRC522::STATUS_OK) {
Serial.print(F("读卡失败,没有连接上 "));
Serial.println(mfrc522.GetStatusCodeName(status));
}
Serial.print(F("数据内容在第 ")); Serial.print(blockAddr); Serial.println(F(" 块:"));
dump_byte_array(buffer, 16); Serial.println();
Serial.println();
//开始进行写入准备
Serial.println(F("开始进行写入的准备..."));
status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522:ICC_CMD_MF_AUTH_KEY_B, trailerBlock, &key, &(mfrc522.uid));
if (status != MFRC522::STATUS_OK) {
Serial.print(F("写入失败,没有连接上或者没有权限 "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
// Write data to the block
Serial.print(F("在第: ")); Serial.print(blockAddr);
Serial.println(F(" 块中写入数据..."));
dump_byte_array(dataBlock, 16); Serial.println();
status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(blockAddr, dataBlock, 16);
if (status != MFRC522::STATUS_OK) {
Serial.print(F("写入失败... "));
Serial.println(mfrc522.GetStatusCodeName(status));
}
Serial.println();
// 再次读取卡中数据,这次是写入之后的数据
Serial.print(F("读取写入后第")); Serial.print(blockAddr);
Serial.println(F(" 块的数据 ..."));
status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size);
if (status != MFRC522::STATUS_OK) {
Serial.print(F("读取失败... "));
Serial.println(mfrc522.GetStatusCodeName(status));
}
Serial.print(F("块 ")); Serial.print(blockAddr); Serial.println(F("数据为 :"));
dump_byte_array(buffer, 16); Serial.println();
// 验证一下数据,要保证写入前后数据是相等的
// 通过计算块中的字节数量
Serial.println(F("等待验证结果..."));
byte count = 0;
for (byte i = 0; i < 16; i++) {
// 比较一下缓存中的数据(我们读出来的数据) = (我们刚刚写的数据)
if (buffer == dataBlock)
count++;
}
Serial.print(F("匹配的字节数量 = ")); Serial.println(count);
if (count == 16) {
Serial.println(F("验证成功 :"));
} else {
Serial.println(F("失败,数据不匹配"));
Serial.println(F("也许写入的内容不恰当"));
}
Serial.println();
// 转储扇区数据
Serial.println(F("写入后的数据内容为::"));
mfrc522.PICC_DumpMifareClassicSectorToSerial(&(mfrc522.uid), &key, sector);
Serial.println();
// 停止 PICC
mfrc522.PICC_HaltA();
//停止加密PCD
mfrc522.PCD_StopCrypto1();
}
/**
* 将字节数组转储为串行的十六进制值
*/
void dump_byte_array(byte *buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer < 0x10 ? " 0" : " ");
Serial.print(buffer, HEX);
}
} |
#include<Servo.h> //舵机库 Servo myservo; //声明舵机 #define Servo_Pin 4 //定义舵机引脚号 #define Angle1 0 #define Angle2 270 #define Servo_Move_Delay 20 //舵机没动一次延时时间(单位:毫秒)
void setup() { Serial.begin(9600);//开启串口,并设置波特率为9600 myservo.attach(Servo_Pin);//设置舵机引脚 myservo.write(Angle1); //先让舵机快速转动到角度Angle1 }
void loop() { Servo_Move( Angle1, Angle2 ); Servo_Move( Angle2, Angle1 ); }
void Servo_Move(int start_angle, int end_angle){ int delta_angle = abs(start_angle - end_angle); if( delta_angle == 0 ){ delta_angle = 1; } // int servo_flags = 0; //定义舵机标志位 servo_flags = (start_angle - end_angle)>0 ? -1 : 1; for( int i=0;i<delta_angle;i++){ myservo.write(start_angle + i*servo_flags); delay(Servo_Move_Delay); } } |
#define ECHOPIN A0//这个口源程序里是A1的,得换口 #define TRIGPIN A1//echopin和trigpin都得换、
float distance; void setup() { Serial.begin(9600);//开启串口,并设置波特率为9600
pinMode( 9 , OUTPUT ); pinMode( 10 , OUTPUT ); pinMode( 5 , OUTPUT ); pinMode( 6 , OUTPUT );
pinMode( 3 , INPUT ); pinMode( A2 , INPUT ); pinMode( A4 , INPUT ); pinMode( A3, INPUT );//三轴加速度口 pinMode( 12, INPUT );//的口晚上看看对不对 pinMode( A5 , INPUT);//同上 Serial.begin(9600);
pinMode(ECHOPIN, INPUT); pinMode(TRIGPIN, OUTPUT); } void loop(){ chaosheng(); int chupeng = digitalRead(3); int chuxu1 = digitalRead(A2); int chuxu2 = digitalRead(12); int hongwai = digitalRead(A4); Serial.println(distance);
digitalWrite( 9 , HIGH );//正常转 digitalWrite( 10 , LOW ); digitalWrite( 5 , HIGH ); digitalWrite( 6 , LOW );
if ((chuxu1) == 0)//触须 { digitalWrite( 9 , HIGH ); digitalWrite( 10 , HIGH ); digitalWrite( 5 , HIGH ); digitalWrite( 6 , HIGH );
}
if ((chuxu2)==0) { digitalWrite( 9 , HIGH ); digitalWrite( 10 , HIGH ); digitalWrite( 5 , HIGH ); digitalWrite( 6 , HIGH ); }
while (( digitalRead(A4) )== 0 )//红外 { digitalWrite( 9 , HIGH ); digitalWrite( 10 , HIGH ); digitalWrite( 5 , HIGH ); digitalWrite( 6 , HIGH );
}
if (distance <= 10) {//超声条件判断 digitalWrite( 9 ,HIGH); digitalWrite( 10 , HIGH ); digitalWrite( 5 , HIGH ); digitalWrite( 6 , HIGH); }
if (( ( analogRead(A3) ) > ( 600 ) ))
{
digitalWrite( 9 ,HIGH ); digitalWrite( 10 , HIGH ); digitalWrite( 5 , HIGH ); digitalWrite( 6 , HIGH );
}
}
void chaosheng() { digitalWrite(TRIGPIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIGPIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIGPIN, LOW);
distance = pulseIn(ECHOPIN, HIGH);
distance= distance/58;
delay(500);} |
#include "SIM900.h"
#include <SoftwareSerial.h>
//#include "inetGSM.h"
#include "sms.h"
//#include "call.h"
#include <string.h>
#include <TinyGPS.h>
/* This sample code demonstrates the normal use of a TinyGPS object.
It requires the use of SoftwareSerial, and assumes that you have a
4800-baud serial GPS device hooked up on pins 3(rx) and 4(tx).
*/
TinyGPS gps;
#define ledpin 13
#define pwrkey 27
//**************************************************************************
char sms_rx[122]; //Received text SMS
byte type_sms=SMS_ALL; //Type of SMS
byte del_sms=1; //0: No deleting sms - 1: Deleting SMS
char number_incoming[20];
//**************************************************************************
SMSGSM sms;
int error;
boolean started= false ;
bool newData = false ;
char gps_year[8];
char gps_mon[3];
char gps_day[3];
char gps_hour[3];
char gps_min[3];
char gps_sec[3];
char gps_lon[20];
char gps_lat[20];
char gps_sms[100];
void setup()
{
//software power sim900 up
pinMode(pwrkey,OUTPUT);
digitalWrite(pwrkey,HIGH);
delay(600);
digitalWrite(pwrkey,LOW);
Serial.begin(115200);
Serial2.begin(9600);
if (gsm.begin(9600)) {
Serial.println( "\nstatus=READY" );
gsm.forceON(); //To ensure that SIM908 is not only in charge mode
started= true ;
} else Serial.println( "\nstatus=IDLE" );
if (started)
{
//delete all sms message
Serial.println( "Deleting SMS" );
char error = DeleteAllSMS();
if (error==1)Serial.println( "All SMS deleted" );
else Serial.println( "SMS not deleted" );
}
else
{Serial.println( "SIM900 NOT EXISTED" ); while (1);}
delay(10000);
}
void loop()
{
if (started)
{
check_gps();
Check_SMS();
}
}
void Check_SMS() //Check if there is an sms 'type_sms'
{
char pos_sms_rx; //Received SMS position
pos_sms_rx=sms.IsSMSPresent(type_sms);
if (pos_sms_rx!=0)
{
//Read text/number/position of sms
sms.GetSMS(pos_sms_rx,number_incoming,sms_rx,120);
Serial.print( "Received SMS from " );
Serial.print(number_incoming);
Serial.print( "(sim position: " );
Serial.print(word(pos_sms_rx));
Serial.println( ")" );
Serial.println(sms_rx);
if (del_sms==1) //If 'del_sms' is 1, i delete sms
{
error=sms.DeleteSMS(pos_sms_rx);
if (error==1)Serial.println( "SMS deleted" );
else Serial.println( "SMS not deleted" );
}
if (( strstr (sms_rx, "gps" )!=0)&&( strlen (sms_rx)==3))
{
Serial.println( "\nsending SMS" ); if (newData)
{
if (sms.SendSMS(number_incoming, gps_sms))
Serial.println( "\nSMS sent OK" );
else
Serial.println( "\nSMS sent error" );
}
else
{
if (sms.SendSMS(number_incoming, "gps not ready" ))
Serial.println( "\nSMS sent OK" );
else
Serial.println( "\nSMS sent error" );
}
}
Serial2.flush(); |
}
newData= false ;
return ;
}
char check_gps()
{
newData= false ;
unsigned long chars;
unsigned short sentences, failed;
// For one second we parse GPS data and report some key values
for (unsigned long start = millis(); millis() - start < 1000;)
{
while (Serial2.available())
{
char c = Serial2.read();
// Serial.write(c); // uncomment this line if you want to see the GPS data flowing
if (gps.encode(c)) // Did a new valid sentence come in?
newData = true ;
}
}
if (newData)
{
float flat, flon;
unsigned long age;
int _year;
byte _month, _day,_hour,_minute,_second,_hundredths;
gps.f_get_position(&flat, &flon, &age); gps.crack_datetime(&_year,&_month,&_day,&_hour,&_minute,&_second,&_hundredths,&age);
flat == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flat, 6;
flon == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flon, 6;
dtostrf(flat, 11, 6, gps_lat);
dtostrf(flon, 10, 6, gps_lon);
strcpy (gps_sms, "lat:" );
strcat (gps_sms,gps_lat);
strcat (gps_sms, "\n" );
strcat (gps_sms, "lon:" );
strcat (gps_sms,gps_lon);
strcat (gps_sms, "\n" );
strcat (gps_sms, "time:" );
itoa(_year,gps_year,10);
strcat (gps_sms,gps_year);
itoa(_month,gps_mon,10);
if ( strlen (gps_mon)==1)
strcat (gps_sms, "0" );
strcat (gps_sms,gps_mon);
itoa(_day,gps_day,10);
if ( strlen (gps_day)==1)
strcat (gps_sms, "0" );
strcat (gps_sms,gps_day);
itoa(_hour,gps_hour,10);
if ( strlen (gps_hour)==1)
strcat (gps_sms, "0" );
strcat (gps_sms,gps_hour);
itoa(_minute,gps_min,10);
if ( strlen (gps_min)==1)
strcat (gps_sms, "0" );
strcat (gps_sms,gps_min);
itoa(_second,gps_sec,10);
if ( strlen (gps_sec)==1)
strcat (gps_sms, "0" );
strcat (gps_sms,gps_sec);
Serial.println(gps_sms);
}
}
char DeleteAllSMS()
{
char ret_val = -1;
if (CLS_FREE != gsm.GetCommLineStatus()) return (ret_val);
gsm.SetCommLineStatus(CLS_ATCMD);
ret_val = 0; // still not present
gsm.SimpleWriteln(F( "AT+CMGDA=\"DEL ALL\"" ));
switch (gsm.WaitResp(8000, 50, "OK" )) {
case RX_TMOUT_ERR:
// response was not received in specific time
ret_val = -2;
break ;
case RX_FINISHED_STR_RECV:
// OK was received => SMS deleted
ret_val = 1;
break ;
case RX_FINISHED_STR_NOT_RECV:
// other response: e.g. ERROR => SMS was not deleted
ret_val = 0;
break ;
}
gsm.SetCommLineStatus(CLS_FREE);
return (ret_val);
} |
② Servo_Move_Slowly.ino
③ littecarwheelsstop.ino
④ gps.zip.ino
5. 社会价值
智能导盲行业在国内对比其他突飞猛进的智能行业来讲仍处于萌芽时期,智能导盲机器人的生产与投入使用也处于探索的阶段,智能小车的发展相对来说比较完备,但用于导盲行业的数量却寥寥无几,本项目智能导盲小车的想法构思也由此萌芽与进展,经过不懈的设计改版,最终呈现为如实物展示此类的智能导盲小车。同时大家的想法终究不够完善我们也会虚心接受领导、老师们的指导意见并进行后续的改进完善,因为我们团队一致认为且坚定相信,智能导盲小车将来有巨大的发展空间并且最终一定会造福社会,成就导盲行业的新曙光、新未来。
* 本项目未获得作者开源授权,无法提供资料下载。
|