电源设计技术信息网站

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

下载中心

TECH INFOArduino入门指南

Arduino RF 探索者机器人 —第2部分—组装所有组件

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

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

arduino robot

在第1部分中,我们讨论了构建一个探索者机器人的几个重要步骤。我们通过Eagle设计并创建了我们自己的PCB。在第2部分中,我们将添加其他组件,并对程序进行测试,以确保RF机器人能够按照预期方式运行。这里我们所设计的探索者机器人能够自主行驶,感知周围环境并无线传输收集到的数据。该项目的目标是为探索者机器人制造原型,该原型将配备有一组传感器,如温度传感器和压力传感器,这些传感器能够借助数字无线电通信模块(RF)实时发送所收集的信息。

硬件

  • • Arduino Uno
  • • 2x 收发器 NRF24l01+
  • • 2x Pololu 发动机 100:1
  • • 4x 车轮
  • • 稳压器
  • • 水晶玻璃
  • • L298
  • • nRF24L01
  • • 气压传感器 BMP085
  • • 距离传感器 HC-SR04

 

软件

 

工具

  • Eagle CAD

 

arduino robot

图1:第1部分中的RF机器人设置

步骤 1:结构和机械组件

远程控制器将从笔记本电脑的USB端口获取信息,并通过第二个nRF24l01+模块进行重定向。远程控制器由Arduino开发板和带有NRF连接器和电源的板组成,这个板将被安装在Uno上方,以避免使用电线。

arduino robot

图2:远程控制器原理图

arduino robot

图3:远程控制器PCB

arduino robot

图4:安装有连接到Arduino Uno 的NRF24L01+模块的远程控制器

您还会看到我们安装的两个LED灯,用来指示电路的功能和上述的其他组件。我们将会使用一些用于稳压器和NPN晶体管过热保护的小冷却器。电路板将会被放置于电池上方,如图5所示。

arduino robot

图5:安装在电池上方的远程控制器

步骤 2: 模块

我们正在使用的是nRF24L01+无线收发器模块。nRF24L01+是一款超低功耗的无线射频收发器。对于此类应用程序,该模块是最佳选择。它以其出色的性能和低廉的价格成为最受欢迎的型号之一,同时,该模块通用的通信协议使其能够与全球广泛使用的软件兼容。

nRF24L01+ 资料表

规格:

  • • 工作频率 2.4GHz – 126 通道
  • • 速度: 250kbps, 1 和 2 Mbps
  • • 发射器: 输出功率为0dBm 时是11.3mA
  • • 最大功率: 100mW (由于前置放大器)
  • • 接口: 4 引脚 SPI – 有效负载: 32 字节

 

arduino robot

图6:NRF24L01+模块

为了获取压力和温度数据,我们将使用Sparkfun BMP085气压传感器。该传感器提供300至110 kPa的测量范围,误差为0.03 kPa。BMP085还可以提供0至65 °C范围内的温度测量功能。其承受的电压必须在1.8-3.6V范围内,并通过l2C直接与微控制器建立连接。

BMP085 资料表

规格:

  • • 接口(l2C)
  • • 宽气压范围
  • • 宽电压范围
  • • 极低的电流损耗
  • • 低噪环境测量
  • • 完全较准
  • • 含运动传感器

 

arduino robot

图7:BMP085气压传感器

现在我们将要安装HC-SR04超声波传感器。HC-SR04的检测范围为2-200厘米。微控制器向传感器发送激发声波的脉冲。当传感器感知到声波已返回时,将向微控制器发送回脉冲,计算发送脉冲的时间与接收到脉冲的时间之间的差值即可算出距离。

D=(t2−t1)×170

 

arduino robot

图8:位于车体正面的HC-SR04距离传感器

步骤 3: 数据路由

Arduino平台提供了预定义的功能,使用者无需对一些寄存器进行配置。可以通过一些库来实现平台与其他外围设备的接口。使用一根Arduino-PC电缆就可以以非常简单地完成编程。该开发板配有USB-UART转换器。

在本项目中,需要有两个程序:一个用于机器人的功能,另一个用于远程控制器。这两个程序通过nRF24101+模块以无线电的方式相互通信。数据流程图如图9所示。有两种方式。正向传递:首先,信息被应用程序发送到Arduino。然后由收发器接管的信息将进一步发送到第二个收发器,再返回到微控制器。这将用于控制机器人的机体运动。

arduino robot

图9:数据流程图

反向传递:控制器从传感器获取信息并进行处理,然后将其发送到OTA(空中下载技术),使其最终到达PC端。

步骤 4: 应用程序

该应用程序是通过Microsoft Visual Studio 2010使用Visual C#编程语言来完成的。Visual Studio在编辑部分融入视觉元素,具有非常简洁直观的界面。这有助于实现一些复杂的应用程序。该应用程序可以发送指令,接收数据并进行显示。机器人的控制由键盘上的箭头按键来实现。当您按下箭头按键的时候,运动按钮会亮起,表示指令已经被接收到。这些指令其实是串行传送的字母,最后会到达机器人控制器进行解码。例如,当您按下“向上”键时,应用程序通过USB在串行端口发送“U”,指令的解码表如下所示:

字母 动作
U 前进
R 右转
D 向后移动
L 左转
N 更新
B 打开灯光
O 发送PWM值

为了达到该效果,我们应用了以下算法:应用程序发送LED灯“打开”指令,然后远程控制器接收指令并将其发送给机器人。一旦机器人接收到指令,就会打开LED并向应用程序发送指令打开指示灯。我们引入该算法来避免产生不同步的问题(LED熄灭但指示灯亮起的情况)。

另一个重要问题是界面中信息的导入和显示。为了使应用程序能够区分信息并知道在哪里显示,我们实施了以下方法:一旦机器人从远程控制器接收到信息,它不仅仅只是进行简单的传送,还会对其进行处理并发送类似这样的指令:“CMD: TE =” + val.marime。应用程序就会在通过串行端口检索到以“CMD”开头的信息时删除掉“:”并读取之后的两个字母,这两个字母表示的是信息将会被写入的区域。“val. marime”会在“=”之后被读取,对于以上示例,程序将会把读取到的数据分配到温度区域中。

为了使应用程序能够识别同时按下的两个按键,我们已经为每个按键初始化了一个变量。当按下一个按键时,对应事件就会其变量的值增加到1;当释放按键时,对应事件就会将其变量减少到0。计时器将会持续计算这些变量的值。

arduino robot

图10:设计应用程序

步骤 5: 关于程序

机器人使用的是在Arduino IDE中编译的程序,比远程控制器的程序要复杂的多:

  • • 程序定义了向前/向后移动,转弯,开灯,计算距离、温度和压力的函数。
  • • 主程序是一个无限循环程序,将会持续判断无线电模块是否接收到信息,如果有,就会对输入的代码进行处理。
  • • 如果输入信息与程序中的一条预设指令匹配,那么将根据收到的字母执行相应功能。
  • • 当从传感器接收到传递信息的指令时,将会运行多个函数来对信息进行收集,并将其添加到将被传递的向量中。
  • • 如果我们要运行的是有关运动的函数,则当收到“U”,“D”,“R”或“L”时,程序将执行一个指令20ms。尽管这个时间很短,但通过持续按下笔记本电脑上的按键,就可以非常快速地发送一连串的指令。

 

步骤 6: 代码

1. 远程控制器

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
char cmd[3];
RF24 radio(9,10);
const uint64_t pipes[2] ={ 0xE8E8F0F0E1LL, 0xDEDEDEDEE7LL} ;
float bar[7];
char arc[3];
int cifra[3];
int val;
int a;
void setup(void){
Serial.begin(9600);
radio.begin();
radio.openWritingPipe(pipes[0]);
radio.openReadingPipe(1,pipes[1]);
radio.setDataRate(RF24_250KBPS);
pinMode(6, OUTPUT);
pinMode(5, OUTPUT);
}
void loop(void){
if(Serial) {
digitalWrite(6, HIGH);
digitalWrite(5, LOW);
}
else {
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
}
cmd[0]=Serial.read();

if(cmd[0]=='O') {
delay(20);
arc[0]=Serial.read();
delay(20);
arc[1]=Serial.read();
delay(20);
arc[2]=Serial.read();

//Storing the values
cifra[0]=arc[0]-'0';
cifra[1]=arc[1]-'0';
cifra[2]=arc[2]-'0';
val=10*cifra[0]+cifra[1];
Serial.println(val);
Serial.println(sizeof(cmd));
cmd[0]='X';
cmd[1]=val;
cmd[2]=cifra[2];
radio.write(cmd, sizeof(cmd));
}
a=1;

if(cmd[0]=='N') {
radio.write(cmd, sizeof(cmd));
radio.startListening();

delay(1);
while(a==1) {

if (radio.available()){
a=2;
bool done = false;
while (!done){
done = radio.read(bar, 28);
Serial.print("CMD:X1=");
Serial.println(bar[0]);
delay(5);

Serial.print("CMD:D1=");
Serial.println(bar[1]);
delay(5);
Serial.print("CMD:TE=");
Serial.println(bar[2]);
delay(5);
Serial.print("CMD:PR=");
Serial.println(bar[3]);
delay(5);
Serial.print("CMD:AT=");
Serial.println(bar[4]);
delay(5);
Serial.print("CMD:AL=");
Serial.println(bar[5]);
Serial.print("CMD:LE=");
Serial.println(bar[6]);

}
}
}
}
else {
radio.write(cmd, sizeof(cmd));
}
}

 

2. 机器人

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
#include
#define BMP085_ADDRESS 0x77 // I2C address of BMP085
#define trigPin 2
#define echoPin 4

const unsigned char OSS = 0;
int pwm, test1, test2;
char cmd[3];
RF24 radio(9,10);
const uint64_t pipes[2] ={ 0xE8E8F0F0E1LL, 0xDEDEDEDEE7LL} ;
float bar[7];
int ac1;
int ac2;
int ac3;
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1;
int b2;
int mb;
int mc;
int md;
float temperature, pressure, atm, altitude,leduri;
long b5;

void bmp085Calibration()
{
ac1 = bmp085ReadInt(0xAA);
ac2 = bmp085ReadInt(0xAC);
ac3 = bmp085ReadInt(0xAE);
ac4 = bmp085ReadInt(0xB0);
ac5 = bmp085ReadInt(0xB2);
ac6 = bmp085ReadInt(0xB4);
b1 = bmp085ReadInt(0xB6);
b2 = bmp085ReadInt(0xB8);
mb = bmp085ReadInt(0xBA);
mc = bmp085ReadInt(0xBC);
md = bmp085ReadInt(0xBE);
}

// Calculate temperature in deg C
float bmp085GetTemperature(unsigned int ut){
long x1, x2;

x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
x2 = ((long)mc << 11)/(x1 + md); b5 = x1 + x2; float temp = ((b5 + 8)>>4);
temp = temp /10;

return temp;
}

// Calculate pressure given up
// calibration values must be known
// b5 is also required so bmp085GetTemperature(...) must be called first.
// Value returned will be pressure in units of Pa.
long bmp085GetPressure(unsigned long up){
long x1, x2, x3, b3, b6, p;
unsigned long b4, b7;

b6 = b5 - 4000;
// Calculate B3
x1 = (b2 * (b6 * b6)>>12)>>11;
x2 = (ac2 * b6)>>11;
x3 = x1 + x2;
b3 = (((((long)ac1)*4 + x3)<<OSS) + 2)>>2;

// Calculate B4
x1 = (ac3 * b6)>>13;
x2 = (b1 * ((b6 * b6)>>12))>>16;
x3 = ((x1 + x2) + 2)>>2;
b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;

b7 = ((unsigned long)(up - b3) * (50000>>OSS));
if (b7 < 0x80000000)
p = (b7<<1)/b4;
else
p = (b7/b4)<<1; x1 = (p>>8) * (p>>8);
x1 = (x1 * 3038)>>16;
x2 = (-7357 * p)>>16;
p += (x1 + x2 + 3791)>>4;

long temp = p;
return temp;
}

// Read 1 byte from the BMP085 at 'address'
char bmp085Read(unsigned char address)
{
unsigned char data;

Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(address);
Wire.endTransmission();

Wire.requestFrom(BMP085_ADDRESS, 1);
while(!Wire.available())
;

return Wire.read();
}

// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int bmp085ReadInt(unsigned char address)
{
unsigned char msb, lsb;

Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(address);
Wire.endTransmission();

Wire.requestFrom(BMP085_ADDRESS, 2);
while(Wire.available()<2)
;
msb = Wire.read();
lsb = Wire.read();

return (int) msb< }

// Read the uncompensated temperature value
unsigned int bmp085ReadUT(){
unsigned int ut;

// Write 0x2E into Register 0xF4
// This requests a temperature reading
Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(0xF4);
Wire.write(0x2E);
Wire.endTransmission();

// Wait at least 4.5ms
delay(5);

// Read two bytes from registers 0xF6 and 0xF7
ut = bmp085ReadInt(0xF6);
return ut;
}

// Read the uncompensated pressure value
unsigned long bmp085ReadUP(){

unsigned char msb, lsb, xlsb;
unsigned long up = 0;

// Write 0x34+(OSS<<6) into register 0xF4
// Request a pressure reading w/ oversampling setting
Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(0xF4);
Wire.write(0x34 + (OSS<<6));
Wire.endTransmission();

// Wait for conversion, delay time dependent on OSS
delay(2 + (3<<OSS));

// Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
msb = bmp085Read(0xF6);
lsb = bmp085Read(0xF7);
xlsb = bmp085Read(0xF8);

up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);

return up;
}

void writeRegister(int deviceAddress, byte address, byte val) {
Wire.beginTransmission(deviceAddress); // start transmission to device
Wire.write(address); // send register address
Wire.write(val); // send value to write
Wire.endTransmission(); // end transmission
}

int readRegister(int deviceAddress, byte address){

int v;
Wire.beginTransmission(deviceAddress);
Wire.write(address); // register to read
Wire.endTransmission();

Wire.requestFrom(deviceAddress, 1); // read a byte

while(!Wire.available()) {
// waiting
}

v = Wire.read();
return v;
}

float calcAltitude(float pressure){

float A = pressure/101325;
float B = 1/5.25588;
float C = pow(A,B);
C = 1 - C;
C = C /0.0000225577;

return C;
}

int distance2() {

long duration, distance;
digitalWrite(trigPin, LOW); // Added this line
delayMicroseconds(2); // Added this line
digitalWrite(trigPin, HIGH);

delayMicroseconds(10); // Added this line
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = (duration/2) / 29.1;
if (distance < 4) { // This is where the LED On/Off happens } else { } if (distance >= 200 || distance <= 0){

}
else {

}
return distance;
}

void setup(void){
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
bar[0] ='A';
Serial.begin(9600);
radio.begin();
radio.openReadingPipe(1,pipes[0]);
radio.openWritingPipe(pipes[1]);
radio.startListening();
radio.setDataRate(RF24_250KBPS);

pwm=255;
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(2, OUTPUT);
pinMode(4, INPUT);
pinMode(3, OUTPUT);
Wire.begin();
bmp085Calibration();
}
void mersF(int timp, int pwm) {
analogWrite(5, pwm);
digitalWrite(6, LOW);
digitalWrite(8, LOW);
digitalWrite(7, LOW);
delay(timp);
digitalWrite(5, LOW);
}
void mersS(int timp, int pwm) {
analogWrite(6, pwm);
digitalWrite(5, LOW);
digitalWrite(8, LOW);
digitalWrite(7, LOW);
delay(timp);
digitalWrite(6, LOW);
}
void VirareD(int timp, int pwm) {

analogWrite(5, pwm);
digitalWrite(6, LOW);
digitalWrite(8, HIGH);
digitalWrite(7, LOW);
delay(timp);
digitalWrite(5, LOW);
digitalWrite(8, LOW);
}
void VirareS(int timp, int pwm) {
analogWrite(5, pwm);
digitalWrite(6, LOW);
digitalWrite(7, HIGH);
digitalWrite(8, LOW);
delay(timp);
digitalWrite(7, LOW);
digitalWrite(5, LOW);

}
void VirareDspate(int timp, int pwm) {
analogWrite(6, pwm);
digitalWrite(5, LOW);
digitalWrite(8, HIGH);
digitalWrite(7, LOW);
delay(timp);
digitalWrite(6, LOW);
digitalWrite(8, LOW);
}
void VirareSspate(int timp, int pwm) {
analogWrite(6, pwm);
digitalWrite(5, LOW);
digitalWrite(7, HIGH);
digitalWrite(8, LOW);
delay(timp);
digitalWrite(7, LOW);
digitalWrite(6, LOW);
}

void stop(){
digitalWrite(5, LOW);
digitalWrite(6, LOW);
digitalWrite(7, LOW);
digitalWrite(8, LOW);
}
void trimitere() {
temperature = bmp085GetTemperature(bmp085ReadUT()); //MUST be called first
pressure = bmp085GetPressure(bmp085ReadUP());
atm = pressure / 101325; // "standard atmosphere"
altitude = calcAltitude(pressure); //Uncompensated caculation - in Meters
leduri=digitalRead(3);

radio.stopListening();
bar[0]=analogRead(A2);
bar[1]=distance2();
bar[2]=temperature;
bar[3]=pressure;
bar[4]=atm;
bar[5]=altitude;
bar[6]=leduri;
radio.write(bar, sizeof(bar));
delay(200);
radio.startListening();
}

void aprindere() {
if(digitalRead(3)) digitalWrite(3,LOW);
else digitalWrite(3,HIGH);
delay(30);
}
void loop(void){
if (radio.available()){
bool done = false;
while (!done){
done = radio.read(cmd, 3);
if (cmd[0] == 'U'){
Serial.println("Exec cmd: Up");
mersF(30,pwm);
}
else if (cmd[0] == 'E'){
Serial.println("Exec cmd: Right");
VirareD(30,pwm);
}
else if (cmd[0] == 'C'){
Serial.println("Exec cmd: Right");
VirareDspate(30, pwm);
}
else if (cmd[0] == 'Z'){
Serial.println("Exec cmd: Right");
VirareSspate(30,pwm);
}
else if (cmd[0] == 'D'){
Serial.println("Exec cmd: Down");
mersS(30,pwm);
}
else if (cmd[0] == 'Q'){
Serial.println("Exec cmd: Left");
VirareS(30, pwm);
}
else if (cmd[0] == 'N'){
delay(30);
trimitere();
}
else if (cmd[0] == 'B'){
delay(30);
aprindere();
}
else if (cmd[0] == 'J'){
stop();
delay(30);
}
 
else if (cmd[0] == 'X'){
 
test1=cmd[1];
test2=cmd[2];
pwm=test1*10+test2;
delay(30);
}
else {
}
}
}
}

 

arduino robot

图11:完成的RF机器人

arduino robot

图12:组装好的RF机器人(侧视图)

现在,我们已经构建了一个自主arduino机器人,该机器人能够自主导航并从周围环境中收集数据!这是一个非常具有挑战性又很有意义的项目。这个原型还可以进行进一步的改善,例如添加用于在崎岖地形/环境中提供保护的外壳。如果您有任何改进的建议,请随时与我分享!

 

Tiberia Todeila
Tiberia Todeila

Tiberia目前是罗马尼亚布加勒斯特理工大学电气工程学院的大四学生。她非常热衷于设计和开发让日常生活更轻松的智能居家设备。

分享到社交媒体