欢迎阅读教程系列的第五篇文章,其中我正在构建一个基于遥控Arduino的车辆机器人。
这是我到目前为止发表的文章列表:
- 第一部分:硬件组件
- 第二部分:Arduino编程
- 第三部分:组装机器人
- 第四部分:A(不是那样)基本机器人固件
- 第五部分:避免障碍(本文)
- 第六部分:远程控制
在上一篇文章中,我写了我的Michelino机器人固件的第一个版本,其中包括一个电机控制器驱动程序。今天我正在增加对距离传感器的支持。
距离传感器驱动器
正如我在上一篇文章中所示,只要为其编写了设备驱动程序,我的固件就可以处理任何电机控制器板。此上下文中的设备驱动程序只不过是一个实现我为此类设备定义的通用接口的类。
距离传感器没有什么不同,可以应用相同的想法。首先,我将定义所有距离传感器设备通用的接口。这将进入文件distance_sensor.h:
/** * @file distance_sensor.h * @brief distance sensor driver definition for the Michelino robot. * @author Miguel Grinberg */namespace Michelino{ class DistanceSensorDriver { public: /** * @brief Class constructor. * @param distance The maximum distance in centimeters that needs to be tracked. */ DistanceSensorDriver(unsigned int distance) : maxDistance(distance) {} /** * @brief Return the distance to the nearest obstacle in centimeters. * @return the distance to the closest object in centimeters * or maxDistance if no object was detected */ virtual unsigned int getDistance() = 0; protected: unsigned int maxDistance; };};
我的距离传感器驱动器非常简单,驾驶员只需提供一种方法,以厘米为单位报告距离最近物体的距离。这就是我现在所需要的,我总是可以回来并在以后为驱动程序定义添加更多功能。
驱动程序的构造函数占用最大距离。这是在getDistance()没有检测到障碍物时将返回的值。
请注意,maxDistance成员变量位于protected块中,而不是private像我在其他类中使用的块。protected对于外部仍然是私有的变量,但子类可以访问它们。我希望这个驱动程序的实现可以访问这个值,这就是我创建它的原因protected。
正如我在上一篇文章中提到的,我发现我正在使用的HC-SR04距离传感器与arduino-new-ping开源库非常相配,所以我现在将为它实现上面的驱动程序接口档案newping_distance_sensor.h:
/** * @file newping_distance_sensor.h * @brief distance sensor driver for distance sensors supported by the NewPing library. * @author Miguel Grinberg */#include "distance_sensor.h"namespace Michelino{ class DistanceSensor : public DistanceSensorDriver { public: DistanceSensor(int triggerPin, int echoPin, int maxDistance) : DistanceSensorDriver(maxDistance), sensor(triggerPin, echoPin, maxDistance) { } virtual unsigned int getDistance() { int distance = sensor.ping_cm(); if (distance <= 0) return maxDistance; return distance; } private: NewPing sensor; };};
请注意getDistance()实现如何maxDistance访问父类中定义的成员变量。如果我在private块中定义了此变量,则此代码将无法编译。
与固件集成
作为第一次集成测试,让机器人前进并在发现障碍物时停止。
我将从我在前一篇文章中编写的固件开始,并将替换initialize()和run()方法,它们是我面向Arduino setup()和loop()函数的面向对象。如果您不熟悉,可能需要返回该文章以查看完整草图的结构。
在草图的顶部,我使用与电机控制器相同的结构包括驱动器:
#define ENABLE_NEWPING_DISTANCE_SENSOR_DRIVER#ifdef ENABLE_NEWPING_DISTANCE_SENSOR_DRIVER#include #include "newping_distance_sensor.h"#define DISTANCE_SENSOR_INIT 14,15,MAX_DISTANCE#endif
在类的private部分中创建Robot一个DistanceSensor对象:
private: DistanceSensor distanceSensor;
在Robot类构造函数中初始化对象:
Robot() : leftMotor(LEFT_MOTOR_INIT), rightMotor(RIGHT_MOTOR_INIT), distanceSensor(DISTANCE_SENSOR_INIT){ initialize();}
在initialize()方法中,我启动电机并将状态设置为运行:
void initialize(){ leftMotor.setSpeed(255); rightMotor.setSpeed(255); state = stateRunning;}
并且在该run()方法中,当距离传感器发现近距离障碍时,我停止电机:
void run(){ if (state == stateRunning) { if (distanceSensor.getDistance() <= TOO_CLOSE) { state = stateStopped; leftMotor.setSpeed(0); rightMotor.setSpeed(0); } }}
我在上面的所有更改中使用了两个常量,MAX_DISTANCE并且TOO_CLOSE。这些常量在草图的顶部定义,以便在需要修改时很容易找到它们:
#define TOO_CLOSE 10#define MAX_DISTANCE (TOO_CLOSE * 20)
我将使用10厘米的距离来考虑障碍物太近。我需要跟踪的最大距离是该距离的20倍或2米。这些是我凭空掏出的数字,我真的不知道它们是否会起作用,但在我有机会测试之后我总能回来调整它们。
草图现在处于可用状态。以下是完整的michelino.ino草图源代码:
#define ENABLE_ADAFRUIT_MOTOR_DRIVER#define ENABLE_NEWPING_DISTANCE_SENSOR_DRIVER#define TOO_CLOSE 10#define MAX_DISTANCE (TOO_CLOSE * 20)#ifdef ENABLE_ADAFRUIT_MOTOR_DRIVER#include #include "adafruit_motor_driver.h"#define LEFT_MOTOR_INIT 1#define RIGHT_MOTOR_INIT 3#endif#ifdef ENABLE_NEWPING_DISTANCE_SENSOR_DRIVER#include #include "newping_distance_sensor.h"#define DISTANCE_SENSOR_INIT 14,15,MAX_DISTANCE#endifnamespace Michelino{ class Robot { public: /* * @brief Class constructor. */ Robot() : leftMotor(LEFT_MOTOR_INIT), rightMotor(RIGHT_MOTOR_INIT), distanceSensor(DISTANCE_SENSOR_INIT) { initialize(); } /* * @brief Initialize the robot state. */ void initialize() { leftMotor.setSpeed(255); rightMotor.setSpeed(255); state = stateRunning; } /* * @brief Update the state of the robot based on input from sensor and remote control. * Must be called repeatedly while the robot is in operation. */ void run() { if (state == stateRunning) { if (distanceSensor.getDistance() <= TOO_CLOSE) { state = stateStopped; leftMotor.setSpeed(0); rightMotor.setSpeed(0); } } } private: Motor leftMotor; Motor rightMotor; DistanceSensor distanceSensor; enum state_t { stateStopped, stateRunning }; state_t state; };};Michelino::Robot robot;void setup(){ robot.initialize();}void loop(){ robot.run();}
在这个草图的控制下,机器人将一直运行,直到距离墙壁或其他障碍物10厘米或更小,然后停止。
如果您正在构建一个像我一样的机器人并尝试草图,您可能会发现机器人大部分时间都按预期运行,但并非总是如此。偶尔它可能会停止,即使没有近距离障碍,或者有时它可能会不停地撞到墙上。继续阅读以了解我如何找出问题以及如何解决问题。
记录
运行上面的草图几次就可以清楚地发现意外情况正在发生,但我怎样才能知道它是什么?我真的需要看到草图正在接收的所有数据以及它如何对它做出反应以理解为什么事情不会像我预期的那样发展。换句话说,我需要调试我的草图。
调试这样的嵌入式系统非常有用的技术是将通过应用程序传递的信息写入可以查看的日志中。
为了能够从我的草图中记录,我将编写一个记录函数,该printf()函数与C标准库中的旧函数具有相同的参数,除了因为我的机器人没有屏幕,输出将转到串口,我可以从我的PC上运行的Arduino软件中捕获,或者通过蓝牙从我的手机中捕获。我打算叫这个功能log()。以下是一个示例用法:
unsigned int distance = distanceSensor.getDistance();log("distance: %u