
歡迎閱讀教程系列的第五篇文章,其中我正在構建一個基于遙控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