1. 功能说明
寻迹机器人是一种能够跟踪特定物体或线路的机器人。它们通常具有以下功能和特点:
① 传感器:寻迹机器人配备了用于感知环境的传感器,如摄像头、灰度传感器等。这些传感器可以探测地面上的标记、颜色、纹理或其他特定特征,以确定要跟踪的目标。
② 自主导航:寻迹机器人通常具备自主导航能力,可以根据目标物体的位置和运动轨迹进行移动和调整。它们可能使用轮式、履带或其他移动机构来在地面上移动。
③ 跟踪精度:寻迹机器人通常被设计为能够实时跟踪目标物体,并尽可能准确地保持距离和方向。一些高级寻迹机器人还可以通过预测目标物体的运动来提高跟踪的精度。
④ 应用场景:寻迹机器人可以应用于多种场景,如工业生产线上的零部件跟踪、物流仓库中的货物识别与追踪、安防领域中的行人监控等。它们在自动化、智能化和效率提升方面具有广泛的应用前景。
本文示例将实现R023样机小型双轮差速底盘机器人沿直线寻迹行走的一个功能。
2. 结构装配
按照下图所示方式进行安装:
3. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | Basra主控板(兼容Arduino Uno) |
扩展板 | Bigfish2.1扩展板 |
传感器 | 灰度传感器 |
电池 | 7.4V锂电池 |
按照下图所示方式进行电路连接:
4. 功能实现
编程环境:Arduino 1.8.19
① 下面提供一个控制轮子转动方向和速度的参考例程(Test1.ino):
/*------------------------------------------------------------------------------------
版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved.
Distributed under MIT license.See file LICENSE for detail or copy at
https://opensource.org/licenses/MIT
by 机器谱 2023-07-14 https://www.robotway.com/
------------------------------*/
#include <Servo.h>//调用舵机库
Servo myservo;//声明一个舵机类
void setup()//Arduino的设置函数
{
myservo.attach(4);//绑定控制舵机的引脚
}
void loop()//Arduino的循环函数
{
for(int i=0; i<180; i++){//通过调节i值控制舵机的运行参数
myservo.write(i);//输出控制舵机的运行参数
delay(500);//延时
}
}
② 下面提供一个控制轮子前进、停止、左转、右转、左微调、右微调的参考例程(Test2.ino):
/*------------------------------------------------------------------------------------
版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved.
Distributed under MIT license.See file LICENSE for detail or copy at
https://opensource.org/licenses/MIT
by 机器谱 2023-07-14 https://www.robotway.com/
------------------------------*/
#include <Servo.h>
#define middle1 88//定义对应舵机的中间值,即停止转动的值
#define middle2 88//此值需要测量,各个舵机不一定相同
Servo myservo[2];//定义一个舵机类数组
void setup()
{
myservo[0].attach(4);
myservo[1].attach(3);
}
void loop()
{
Left();//调用左转函数
delay(1000);
Right();
delay(1000);
Forwards();
delay(1000);
stop();
delay(1000);
}
void Left()//左转函数
{
myservo[0].write(middle1);
myservo[1].write(middle2 + 20);
}
void Right()//右转函数
{
myservo[0].write(middle1 - 20);
myservo[1].write(middle2);
}
void Forwards()//前进函数
{
myservo[0].write(middle1 - 20);
myservo[1].write(middle2 + 20);
}
void stop()//后退函数
{
myservo[0].write(middle1);
myservo[1].write(middle2);
}
③ 下面提供一个将灰度传感器数据显示到LED点阵上的参考例程(Test3.ino):
/*------------------------------------------------------------------------------------
版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved.
Distributed under MIT license.See file LICENSE for detail or copy at
https://opensource.org/licenses/MIT
by 机器谱 2023-07-14 https://www.robotway.com/
------------------------------*/
#include <LedControl.h>//调用点阵库函数
#include <Servo.h>
LedControl lc=LedControl(12,11,13,1);//声明点阵类,并设置对应的引脚
int pin[3] = {A0, A4, A3};//设置传感器的对应的三个引脚
byte value;//声明传感器值变量
void setup()
{
LedInit();//初始化点阵
}
/************************************************************************************
此程序用到了for与switch的配合框架,可用于多传感器的实时处理,请细细体会!
具体解析:for循环中使用了位处理,这样的结果就是value的一个数据位对应一个传感器的状态,
此程序value的类型为byte,则可支持8个传感器,如果要使用更多传感器可定义int等。
传感器触发时返回值为0,因此value值与传感器触发的状态对应关系以A0触发为例:
A0传感器触发-->二进制:00000110-->十六进制:0x06-->对应case 0x06;
所以这样做的好处就是当传感器的状态发生改变时程序可以快速的到达指定的处理方式
**************************************************************************************/
void loop()
{
value = 0;
for(int i=0; i<3; i++){//通过循环检测,读取传感器的状态值
value |= (digitalRead(pin[i]) << i);//通过位处理得到结果值,digitalRead()用于读取数字值
}
switch (value) {//根据结果值进行相应的事件处理
case 0x00://全部触发
LedOn(0);//点亮相应的点阵
LedOn(1);
LedOn(2);
break;
case 0x01://触发右边两个
LedOn(1);
LedOn(2);
break;
case 0x03://触发右边一个
LedOn(2);
break;
case 0x04://触发左边两个
LedOn(0);
LedOn(1);
break;
case 0x05://触发中间一个
LedOn(1);
break;
case 0x06://触发左边一个
LedOn(0);
break;
default:
;
}
}
void LedOn(int key)//根据参数点亮相应的点阵LED
{
lc.clearDisplay(0);
for(int i=0; i<2; i++){
for(int j=3*key; j<3*key+2; j++){
lc.setLed(0, i, j, true);
}
}
}
void LedInit() //点阵初始化函数
{
lc.shutdown(0,false);
lc.setIntensity(0,8);
lc.clearDisplay(0);
}
④ 下面提供一个小车行走直线的参考例程(Test4.ino):
/*------------------------------------------------------------------------------------
版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved.
Distributed under MIT license.See file LICENSE for detail or copy at
https://opensource.org/licenses/MIT
by 机器谱 2023-07-14 https://www.robotway.com/
------------------------------*/
#include <Servo.h>
#define middle1 88
#define middle2 88
Servo myservo[2];
int pin[3] = {A0, A4, A3};
byte value;
byte value_his = 0;//记录上一次的传感器值
void setup()
{
myservo[0].attach(4);
myservo[1].attach(3);
}
void loop()
{
value = 0;
for(int i=0; i<3; i++){
value |= (digitalRead(pin[i]) << i);
}
if(value == 0x07){//当传感器都没有触发时默认为上一次的值
value = value_his;
}
switch (value) {
case 0x00://全部触发
Forwards();
break;
case 0x01://触发右边两个
while(digitalRead(pin[1])){//通过while循环使小车回到跑道中间
Right();
}
break;
case 0x03://触发右边一个
while(digitalRead(pin[1])){
Right();
}
break;
case 0x04://触发左边两个
while(digitalRead(pin[1])){
Left();
}
break;
case 0x05://触发中间一个
Forwards();
break;
case 0x06://触发左边一个
while(digitalRead(pin[1])){
Left();
}
break;
default:
stop();
}
value_his = value;
}
void Left()
{
myservo[0].write(middle1);
myservo[1].write(middle2 + 20);
}
void Right()
{
myservo[0].write(middle1 - 20);
myservo[1].write(middle2);
}
void Forwards()
{
myservo[0].write(middle1 - 20);
myservo[1].write(middle2 + 20);
}
void stop()
{
myservo[0].write(middle1);
myservo[1].write(middle2);
}
⑤ 下面提供一个小车识别十字路口的参考例程(Test5.ino):
/*------------------------------------------------------------------------------------
版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved.
Distributed under MIT license.See file LICENSE for detail or copy at
https://opensource.org/licenses/MIT
by 机器谱 2023-07-14 https://www.robotway.com/
------------------------------*/
#include <Servo.h>
#include <LedControl.h>
#define middle1 88
#define middle2 88
Servo myservo[2];
LedControl lc=LedControl(12,11,13,1);
int pin[3] = {A0, A4, A3};
byte value;
byte value_his = 0;
int time[3];//用于记录传感器的触发时间
void setup()
{
LedInit();
myservo[0].attach(4);
myservo[1].attach(3);
}
void loop()
{
value = 0;
for(int i=0; i<3; i++){
value |= (digitalRead(pin[i]) << i);
if(!digitalRead(pin[i])){
time[i] = millis();//调用mills函数可以得到此时单片机的运行时间
}
}
if(TimeDeal())
{
if(millis() > 1000){//用于排除刚开机时的误判
LedDis();//十字路口显示
}
}
if(value == 0x07){//当传感器都没有触发时默认为上一次的值
value = value_his;
}
switch (value) {
case 0x00://全部触发
Forwards();
break;
case 0x01://触发右边两个
while(digitalRead(pin[1])){
Right();
}
break;
case 0x03://触发右边一个
while(digitalRead(pin[1])){
Right();
}
break;
case 0x04://触发左边两个
while(digitalRead(pin[1])){
Left();
}
break;
case 0x05://触发中间一个
Forwards();
break;
case 0x06://触发左边一个
while(digitalRead(pin[1])){
Left();
}
break;
default:
stop();
}
value_his = value;
lc.clearDisplay(0);
}
void Left()
{
myservo[0].write(middle1);
myservo[1].write(middle2 + 20);
}
void Right()
{
myservo[0].write(middle1 - 20);
myservo[1].write(middle2);
}
void Forwards()
{
myservo[0].write(middle1 - 20);
myservo[1].write(middle2 + 20);
}
void stop()
{
myservo[0].write(middle1);
myservo[1].write(middle2);
}
bool TimeDeal()//十字路口识别函数
{
if(millis() > 500){
if((abs(time[1] - time[0]) < 100) && (abs(time[1] - time[2]) < 100)){//当中间传感器与另外两个传感器触发的时间小于100毫秒时判定为十字路口
return true;
}
else
return false;
}
}
void LedDis()//十字路口显示函数
{
for(int i=3; i<5; i++){
for(int j=0; j<8; j++){
lc.setLed(0, i, j, true);
}
}
for(int i=3; i<5; i++){
for(int j=0; j<8; j++){
lc.setLed(0, j, i, true);
}
}
}
void LedInit()
{
lc.shutdown(0,false); //start the 8*8 led
lc.setIntensity(0,8);
lc.clearDisplay(0);
}
⑥ 下面提供一个小车实现寻迹的完整程序(TrackingCar.ino):
/*------------------------------------------------------------------------------------
版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved.
Distributed under MIT license.See file LICENSE for detail or copy at
https://opensource.org/licenses/MIT
by 机器谱 2023-07-14 https://www.robotway.com/
------------------------------*/
#include <LedControl.h>
#include <Servo.h>
#define middle1 88
#define middle2 88
LedControl lc=LedControl(12,11,13,1);
Servo myservo[2];
int pin[3] = {A0, A4, A3};
int time[3];
byte value;
byte value_his = 0;
int flag = 0;
int times = 0;
void setup()
{
LedInit();
Serial.begin(9600);//串口,用于调试
myservo[0].attach(4);
myservo[1].attach(3);
}
void loop()
{
value = 0;
for(int i=0; i<3; i++){
value |= (digitalRead(pin[i]) << i);
if(!digitalRead(pin[i])){
time[i] = millis();
}
}
if(TimeDeal())
{
times++;
Serial.print(times);
}
else
{
if(times > 1){
Serial.println();
flag += 1;
Serial.println(flag);
}
times = 0;
}
if(flag == 3){
while(1){
stop();
}
}
if(value == 0x07){
value = value_his;
}
switch (value) {
case 0x00://全部触发
LedOn(0);
LedOn(1);
LedOn(2);
Forwards();
//delay(500);
break;
case 0x01://触发右边两个
LedOn(1);
LedOn(2);
while(digitalRead(pin[1])){
Right();
}
break;
case 0x03://触发右边一个
LedOn(2);
while(digitalRead(pin[1])){
Right();
}
break;
case 0x04://触发左边两个
LedOn(0);
LedOn(1);
while(digitalRead(pin[1])){
Left();
}
break;
case 0x05://触发中间一个
LedOn(1);
Forwards();
break;
case 0x06://触发左边一个
LedOn(0);
while(digitalRead(pin[1])){
Left();
}
break;
default:
stop();
}
value_his = value;
lc.clearDisplay(0);
}
bool TimeDeal()
{
if(millis() > 500){
if((abs(time[1] - time[0]) < 100) && (abs(time[1] - time[2]) < 100)){
return true;
}
else
return false;
}
}
void Left()
{
myservo[0].write(middle1);
myservo[1].write(middle2 + 20);
}
void Right()
{
myservo[0].write(middle1 - 20);
myservo[1].write(middle2);
}
void Forwards()
{
myservo[0].write(middle1 - 20);
myservo[1].write(middle2 + 20);
}
void stop()
{
myservo[0].write(middle1);
myservo[1].write(middle2);
}
void LedOn(int key)
{
for(int i=0; i<2; i++){
for(int j=3*key; j<3*key+2; j++){
lc.setLed(0, i, j, true);
}
}
}
void LedInit()
{
lc.shutdown(0,false); //start the 8*8 led
lc.setIntensity(0,8);
lc.clearDisplay(0);
}
程序源代码资料内容详见 小型双轮差速底盘-寻迹与路口