电源设计技术信息网站

订阅电子杂志   English   繁體中文   日本語   한국어

下载中心

TECH INFOArduino入门指南

利用Parallax激光传感器制作Arduino激光机器人—第2部分

这篇文章来源于DevicePlus.com英语网站的翻译稿。

点击此处阅读本文的第1部分 >

arduino robot

 

在第1部分中,我们制作了一块定制的亚克力板底座,把NEMA 17步进电机安装到了底盘上。然后,我们将Arduino Uno 3和电机开发板也连到亚克力底座上,并安装了电机开发板库。在第2部分中,我们将添加机器人工作所需的其他系统部件,比如伺服器和激光测距仪(LRF),并编写一个程序,让机器人能够自主移动。

有关如何利用Arduino UNO R3从零开始搭建轮式机器人的信息,请参考我们在之前的文章《如何制作自己的机器人》 《如何制作自己的机器人(第2部分)》。在本文中,我们将进一步增加机器人的功能:让该机器人动起来并为其增加激光测距(LRF)功能,以使该设备能够检测物体并测量距离。

该机器人的设计目标如下:

  • 向前、向后移动;向左和向右旋转90度;向左和向右旋转45度。
  • 避开障碍物时根据最佳可用路径朝不同的方向移动。
  • 测量各个方向(前方、左右90度、左右45度)的距离。
  • 根据可用的最长距离,确定朝哪个方向前进(向前、向后、向左或向右)。

硬件

制作此机器人所需的硬件请参见以下硬件明细,这些零部件在许多电商处都可以买到(明细中给出了部分地址)。

 

软件

工具

  • 圆锉
  • Dremel 电动工具
  • 电烙铁
  • 迷你钢锯

安装并测试伺服器

下一步是安装用于平移的伺服器。首先,请用螺钉将两块小板拧到伺服基座上,然后用螺钉将其固定到亚克力底座上,如图1和图2所示。请用2个螺钉将铝制安装架安装到伺服器的顶部。

arduino robot

图1.用两小板将伺服器固定到亚克力底座上

 

arduino robot

图2.将铝制安装架安装到伺服器上

如图3所示,将伺服器的接头连至电机开发板。电机开发板上有2个伺服器接头,分别标记为“servo1”和“servo2”。本次连接请使用servo1接头(外侧的那个)。请注意,切勿接反。

arduino robot

图3.伺服与电机开发板的连接

现在,我们可以运行程序了,请将以下代码上传到Arduino。无需安装新库,系统所需的库(Servo.h)都已包含在Arduino IDE程序中。伺服器应使用Digital引脚10(servo 1),或者如果伺服器连接到电机开发板的servo 2,那么应使用引脚11。

//***********************************************************************************************

#include <Servo.h>

Servo panMotor;                      // servo for laser range finder (lrf) scanning

int pos = 0;    // variable to store the servo position

const int a = 1000;

void setup()

{ panMotor.attach(10); }             // Attach Servo for scanning to pin 10

void loop()

{

// *************************** Scan Left ***********************************    

   panMotor.write(180);       // 90 degree

   delay(a);       

   panMotor.write(135);       // 45 degree

   delay(a);

// ************************** Scan Right  **********************************      

   panMotor.write(45);        // 45 degree

   delay(a);     

   panMotor.write(0);         // 90 degree

   delay(a);

// ************************* Neutral position *****************************    

   panMotor.write(90);

   delay(a);  

}

//**************************************************************************************************

 

代码很简单,它负责让机器人从左向右进行扫描并返回原始位置。

伺服器的工作原理请参见以下视频:

安装激光测距仪(LRF)

激光测距仪产品文档– Parallax

Parallax激光测距仪(LRF)模块是一款利用激光技术计算仪器到目标物体的距离的测量仪器。该设备根据光学三角测量法(激光、摄像头和物体质心之间的简单三角函数)来计算仪器到目标物体的距离。其最佳测量范围为6–48英寸(15–122厘米),测量精度误差<5%(平均3%)。

硬件安装很简单。只需在亚克力板上钻两个与LRF位置相匹配的孔,然后用塑料垫片和螺钉将LRF固定到铝底盘上即可(请参见图5)。

arduino robot

图4.将LRF电缆连至电机开发板

 

arduino robot

图5.安装在铝制安装架上的LRF

由于LRF的最佳测量值上限为122厘米,我们需要将铝制安装架稍微向前弯曲,以使该范围始终小于120厘米(图6)。

arduino robot

图6.向前弯曲铝制安装架,使激光到地的距离小于120厘米

请完全按照图7将电缆接头连至LRF。GND接地,VCC接5V,SOUT接引脚8,SIN接引脚9。

 

arduino robot

图7. LRF与电机开发板的连接

我们已经完成了LRF的安装和连接,现在就来上传代码吧!同样,无需安装库。我们要用的SoftwareSerial.h已经包含在Arduino IDE中。

下面的代码源自示例代码,我们只是进行了一些修改,将距离数据从字符串转换为整数。其作用是测量传感器到前方物体的距离并打印结果。我们用串行监视器显示结果。

// **************************************************************************************************************************************************

#include <SoftwareSerial.h>

#define rxPin     8                   // Serial input (connects to the LRF's SOUT pin)

#define txPin     9                   // Serial output (connects to the LRF's SIN pin)

#define ledPin   13                   // Most Arduino boards have an on-board LED on this pin

#define BUFSIZE  16                   // Size of buffer

int  lrfDataInt;

SoftwareSerial lrfSerial =  SoftwareSerial(rxPin, txPin);

void setup()                          // Set up code called once on start-up

{

 // *************************************** setup for LRF ***********************************************

 pinMode(ledPin, OUTPUT);

 pinMode(rxPin, INPUT);

 pinMode(txPin, OUTPUT);

 digitalWrite(ledPin, LOW);                // turn LED off

 Serial.begin(9600);

 while (!Serial);                          // Wait until ready

 Serial.println("\n\nParallax Laser Range Finder");

 lrfSerial.begin(9600);

 Serial.print("Waiting for the LRF...");

 delay(2000);                             // Delay to let LRF module start up

 lrfSerial.print('U');                    // Send character

 while (lrfSerial.read() != ':');   

 delay(10);                               // Short delay

 lrfSerial.flush();                       // Flush the receive buffer

 Serial.println("Ready!");

 Serial.flush();                          // Wait for all bytes to be transmitted to the Serial Monitor

}

 // ******************************************  main loop ************************************************

void loop()  // Main code, to run repeatedly

{

 lrf();

}

 // ****************************************** end main loop *********************************************

void lrf()

{

 lrfSerial.print('R');                    // Send command

 digitalWrite(ledPin, HIGH);              // Turn LED on while LRF is taking a measurement

 char lrfData[BUFSIZE];                   // Buffer for incoming data

 int  lrfDataInt1;

 int  lrfDataInt2;

 int  lrfDataInt3;

 int  lrfDataInt4;

 int offset = 0;                          // Offset into buffer

 lrfData[0] = 0;                          // Clear the buffer    

 

 while(1)

 {

   if (lrfSerial.available() > 0)         // If there are any bytes available to read, then the LRF must have responded

   {

     lrfData[offset] = lrfSerial.read();  // Get the byte and store it in our buffer

     if (lrfData[offset] == ':')          // If a ":" character is received, all data has been sent and the LRF is ready to accept the next command

      { lrfData[offset] = 0;              // Null terminate the string of bytes we just received

       break; }                           // Break out of the loop

     offset++;                            // Increment offset into array

     if (offset >= BUFSIZE) offset = 0;   // If the incoming data string is longer than our buffer, wrap around to avoid going out-of-bounds

   }

 }

 lrfDataInt1 = ( lrfData[5] -'0');

 lrfDataInt2 = ( lrfData[6] -'0');

 lrfDataInt3 = ( lrfData[7] -'0');

 lrfDataInt4 = ( lrfData[8] -'0');

 lrfDataInt = (1000*lrfDataInt1)+ (100*lrfDataInt2)+(10*lrfDataInt3) + lrfDataInt4;

 

 Serial.print("Distance = ");             // The lrfData string should now contain the data returned by the LRF, so display it on the Serial Monitor

 Serial.println(lrfDataInt);

 Serial.flush();                          // Wait for all bytes to be transmitted to the Serial Monitor

 digitalWrite(ledPin, LOW);               // Turn LED off

 delay(1000);

}

//*************************************************************************************************************************************************

 

串行监视器显示的结果如下所示。所有尺寸的单位均为毫米。

arduino robot

安装OLED显示屏

首先,我们将OLED塑料盒安装到亚克力底座中(请参见图9),然后再将OLED显示屏附带的线缆一端连至显示屏。要将显示屏连至电机开发板,请将线缆另外一端jst接头上的线缆剪下,然后将红导线焊至5V,将黑导线焊至Ground,将黄导线焊至SDA引脚,将绿导线焊至SCL引脚。请确保OLED显示屏的背面朝外。

 

arduino robot

图8 . OLED与电机开发板之间的连接

将OLED固定在底座上并连接电缆后,我们就可以运行部分软件了。

首先,请确保已经安装了SeeedOLED.h。然后,将以下代码上传到Arduino。该代码使用了名为oled1的函数,稍后的最终编码也会使用该函数。其基本功能就是显示100到109的数字。

//*****************************************************************************************************************************************

#include <Wire.h>

#include <SeeedOLED.h>

int distanceFwd;

void setup()

{ Wire.begin();}

void loop()

{

 int i = 0;

for (i; (i < 10); i ++)

{ distanceFwd = 100 + i;

oled1();

delay(1000); }

}

  void oled1()

 {

 SeeedOled.clearDisplay();           //clear the screen and set start position to top left corner

 SeeedOled.setNormalDisplay();       //Set display to Normal mode

 SeeedOled.setPageMode();            //Set addressing mode to Page Mode

 SeeedOled.setTextXY(3,3);          

 SeeedOled.putString("Forward :");

 SeeedOled.setTextXY(5,9);

 SeeedOled.putNumber(distanceFwd);

 }  

//*****************************************************************************************************************************************

 

程序正常运行时,显示屏应该会显示以下视频中的内容:

安装最终代码

现在,我们已经完成了所有硬件的安装并测试了各个设备。让我们把所有软硬件结合起来,构建一个可以自主移动的智能激光机器人吧。最终程序会执行以下功能:

  • 测量前方距离
    • 如果距离超过70厘米,机器人将向前移动500步(大约50厘米);
    • 如果距离大于40厘米小于70cm,机器人将向前移动200步(20厘米);
    • 如果距离小于40厘米,机器人会向左扫描90度,向左扫描45度,向右扫描45度,向右扫描90度;
  • 测量每个方向的距离,然后计算哪个方向的测量距离最长;
  • 转向距离最长的那个方向;
  • 返回第一步。

请复制以下代码并将其上传到Arduino:

//*********************************************************************************************************************

#include <Wire.h>

#include <Adafruit_MotorShield.h>

#include "utility/Adafruit_MS_PWMServoDriver.h"

#include <SoftwareSerial.h>

#include <Servo.h>

#include <SeeedOLED.h>

#define ledPin   13                                         

#define BUFSIZE  16   

#define rxPin    8                                          // Serial input (connects to the LRF's SOUT pin)

#define txPin    9                                          // Serial output(connects to the LRF's SIN pin)

SoftwareSerial lrfSerial =  SoftwareSerial(rxPin, txPin);   // Size of buffer (in bytes) for incoming data

// Create the motor shield object with the default I2C address

  Adafruit_MotorShield AFMS = Adafruit_MotorShield();

// Connect a stepper motor with 200 steps per revolution (1.8 degree)

  Adafruit_StepperMotor *myMotor1 = AFMS.getStepper(200, 1);  // motor port #1 (M1 and M2)

  Adafruit_StepperMotor *myMotor2 = AFMS.getStepper(200, 2);  // motor port #2 (M3 and M4)

  Servo panMotor;                                             // servo for laser range finder (lrf) scanning

  int leftDistance1;

  int leftDistance2;

  int rightDistance1;

  int rightDistance2;

  int maxDistance;

  int angleTurn;

  int directions;

  int distanceFwd;  

  const int a = 30;                  

//*********************************************************************************start set up **************************

void setup() {

 Serial.begin(9600);                                       // set up Serial library at 9600 bps

 panMotor.attach(10);                                      // Attach Servo for scanning to pin 10

 

 AFMS.begin();                                             // create with the default frequency 1.6KHz

 myMotor1->setSpeed(100);                                  // Set stepmotor1 speed at 100 rpm   

 myMotor2->setSpeed(100);                                  // Set stepmotor2 speed at 100 rpm   

 pinMode(ledPin, OUTPUT);

 

 pinMode(rxPin, INPUT);                                    // Input pin for LRF

 pinMode(txPin, OUTPUT);                                   // Output pin for LRF

 digitalWrite(ledPin, LOW);                                // turn LED off

 Serial.begin(9600);

 while (!Serial);                                          // Wait until ready

 lrfSerial.begin(9600);

 Serial.print("Waiting for the LRF...");

 delay(2000);                                              // Delay to let LRF module start up

 lrfSerial.print('U');                                     // Send character

 while (lrfSerial.read() != ':');                        

 delay(10);                                                // Short delay

 lrfSerial.flush();                                        // Flush the receive buffer

 Serial.println("Ready!");

 Serial.flush();                                           // Wait for all bytes to be transmitted to the Serial Monitor

 panMotor.write(90);

 delay(a);

}

//******************************************************************* start Loop *****************************************

void loop()

{

 distanceFwd = lrf();

 maxDistance = distanceFwd;

 oled1();

 if (distanceFwd > 700)

 { Motor(500,1);}

 else

 if (distanceFwd > 400)

 { Motor(200,1);}

 else                                                           // if path is blocked

 { checkTurn();

  turn();}     

}

//***************************************************************** check turn  function ********************************

void checkTurn()

 {

    digitalWrite(ledPin, HIGH);     

// ************************** Scan Left ***********************************   

   panMotor.write(180);

   delay(a);

   leftDistance1 = lrf();       

   

   panMotor.write(135);

   delay(a);

   leftDistance2 = lrf();

   oled();  

    

// *************************** Scan Right  *********************************       

   panMotor.write(45);

   delay(a);

   rightDistance2 = lrf();  

      

   panMotor.write(0);

   delay(a);

   rightDistance1 = lrf();

   oled();

   panMotor.write(90);

    

  digitalWrite(ledPin, LOW);

// ************************************ Turn Left ************************

     maxDistance = leftDistance1;

     angleTurn = 100;

     directions = 0;          

if (maxDistance <= leftDistance2)

    {angleTurn = 50;

     maxDistance = leftDistance2;

     directions = 0;

    }           

//*********************************** Turn Right ***********************            

if (maxDistance <= rightDistance2)

      {angleTurn = 50;

      maxDistance = rightDistance2;

      directions = 1;  

      }

      

if (maxDistance <= rightDistance1)

      {angleTurn = 100;

      maxDistance = rightDistance1;

      directions = 1;

      }                 

// ******************************* Turn Back******************************

 if ((leftDistance1 < 300) && (rightDistance1 <300) && (distanceFwd <300))

   {angleTurn = 200;

   directions = 3;

   }                 

 }

   

//************************************************ turn function *********************************************************

void turn()  

{

   rightDistance1 = 0;

   rightDistance2 = 0;

   leftDistance1  = 0;

   leftDistance2  = 0;

   

   if (directions == 0) // turn left  

    { Motor(angleTurn,3);}

   if (directions == 1) // turn right

    { Motor(angleTurn,4);}

   if (directions == 3) // turn back

    { Motor(angleTurn,4);}

 }

//***************************************  Stepper Motor function    ****************************************************

void Motor(int x,int y)

{

  int i = 0;

 for ( i; (i < x); i ++)

 {

  if (y == 1)   // move forward

 {myMotor1->step(1, FORWARD, SINGLE);

 myMotor2->step(1, BACKWARD, SINGLE);}

 

  if (y == 2)   // move backward

 {myMotor1->step(1, BACKWARD, SINGLE);

 myMotor2->step(1, FORWARD, SINGLE);}

 

 if (y == 3)    // move left

  { myMotor1->step(1, FORWARD, SINGLE);

 myMotor2->step(1, FORWARD, SINGLE);}

 

 if (y == 4)    // move right

  { myMotor1->step(1, BACKWARD, SINGLE);

 myMotor2->step(1, BACKWARD, SINGLE);}

 }

}

//***********************************************************************  LRF function *******************************

long lrf()

{

 lrfSerial.print('R');         // Send command

 digitalWrite(ledPin, HIGH);   // Turn LED on while LRF is taking a measurement

 char lrfData[BUFSIZE];        // Buffer for incoming data

 int  lrfDataInt1;

 int  lrfDataInt2;

 int  lrfDataInt3;

 int  lrfDataInt4;

 int  lrfDataInt;

 int offset = 0;              // Offset into buffer

 lrfData[0] = 0;              // Clear the buffer    

 

 while(1)

 {

   if (lrfSerial.available() > 0)

   {

     lrfData[offset] = lrfSerial.read();  

     if (lrfData[offset] == ':')        

      { lrfData[offset] = 0;

        break;}               

     offset++;

     if (offset >= BUFSIZE) offset = 0;

   }

 }

 lrfDataInt1 = ( lrfData[5] -'0');

 lrfDataInt2 = ( lrfData[6] -'0');

 lrfDataInt3 = ( lrfData[7] -'0');

 lrfDataInt4 = ( lrfData[8] -'0');

 lrfDataInt = (1000*lrfDataInt1)+ (100*lrfDataInt2)+(10*lrfDataInt3) + lrfDataInt4;     

 Serial.flush();            

 digitalWrite(ledPin, LOW);  

 return lrfDataInt;

}

//********************************************************* Oled function ************************************************

 void oled()

 {

 SeeedOled.clearDisplay();           //clear the screen and set start position to top left corner

 SeeedOled.setNormalDisplay();       //Set display to Normal mode

 SeeedOled.setPageMode();            //Set addressing mode to Page Mode

 SeeedOled.setTextXY(0,0);          

 SeeedOled.putString("Left 1:");

 SeeedOled.setTextXY(0,12);

 SeeedOled.putNumber(leftDistance1);   

 SeeedOled.setTextXY(2,0);          

 SeeedOled.putString("Left 2:");

 SeeedOled.setTextXY(2,12);

 SeeedOled.putNumber(leftDistance2);   

 SeeedOled.setTextXY(4,0);          

 SeeedOled.putString("Right 1:");

 SeeedOled.setTextXY(4,12);

 SeeedOled.putNumber(rightDistance1);

 SeeedOled.setTextXY(6,0);          

 SeeedOled.putString("Right 2:");

 SeeedOled.setTextXY(6,12);

 SeeedOled.putNumber(rightDistance2);       

 }

   void oled1()

 {

 SeeedOled.clearDisplay();           //clear the screen and set start position to top left corner

 SeeedOled.setNormalDisplay();       //Set display to Normal mode

 SeeedOled.setPageMode();            //Set addressing mode to Page Mode

 SeeedOled.setTextXY(3,3);          

 SeeedOled.putString("Forward :");

 SeeedOled.setTextXY(5,9);

 SeeedOled.putNumber(distanceFwd);

 }  

//***********************************************************************************************************************************

 

 

arduino robot

图9. OLED显示屏安装在亚克力底座上的最终效果

结论

在之前的文章《如何制作自己的机器人》《如何制作自己的机器人(第2部分)》中,我们用步进电机制作了一款简单的轮式机器人。这次,我们对其进行了功能改进:为机器人增加了激光测距仪(LRF)功能,并且安装了轮子,让机器人能够自由移动。我一直想制作一款能够进行测量的设备。在本例中,凭借激光传感器,我们的机器人不仅能够检测并避开物体,同时还能获取更准确的距离数据。激光机器人还有许多其他应用场景。您也可以利用该激光传感器设计自己喜欢的有趣项目。

接下来,我们会做一些更炫酷的事情,敬请期待!

Purnomo Nuhalim
Purnomo Nuhalim

来自墨尔本的Purnomo是一名退休人员,也是电子发烧友。目前,他正使用Arduino和Raspberry Pi从事各种开放式硬件项目的研发。除了电子学,他还对航空建模和天文学充满热情。

分享到社交媒体