:制作这个项目的原因是大一第二学期,我开始通过学校图书馆的无线电杂志联系Raspberry Pi卡片计算机和Arduino其中,微控制器Raspberry Pi给当初什么都不懂的我留下了深刻的印象:。
在强烈的探索欲望的驱使下,我从网上买了两块Element14的Raspberry Pi一代Model B(现在已经绝版了)板材等相关配件已经开始在Raspbian系统上自学Python以及各种传感器的使用方法,后来为了测试自己的学习成绩,我花了几周时间做了这个简单的轮式机器人。虽然它涉及的原理并不复杂,但是对于那会儿刚开始接触嵌入式的我来说,能成功搭建一个完整的机器人系统还是挺有挑战性的。
概述
简单轮式机器人实际上是一更传统的入门级智能汽车。它具有蓝牙远程遥控、超声波避障、红外边缘检测和红外巡逻(未完成)功能,可以完成一些有趣的小型实验。此外,简单轮机器人的软件是开源的,具体代码可以来自我GitHub仓库上获得。
原理
硬件
以下是简单轮式机器人的硬件系统连接图:
核心主控
系统的硬件核心控制分别是Arduino和Raspberry Pi。其中Arduino主要负责接收红外光电测距模块的数据,控制装有超声波模块的单轴舵机云台的旋转;Raspberry Pi四个直流减速电机的转向和转速控制可电机的转向和转速,此外,它还可以接收超声波模块和从Arduino串口发送的红外光电测距模块的数据可以判断机器人的前部和两侧是否有障碍物。如果机器人与障碍物之间的距离小于特定阈值,则Arduino和Raspberry Pi会分别改变LED的颜色并启动蜂鸣器来发出警报。
当然,有人会问:为什么我不能只用它Raspberry Pi作为机器人的核心主控,必须使用另一个Arduino呢?其实根据这个项目的实际需要,只用一个Raspberry Pi够了,但是对我来说就够了。Arduino考虑以下三个原因:
Raspberry Pi一代Model B板子的GPIO只有26个引脚,即使重复使用一些具有特殊功能的引脚,引脚资源仍然相对紧张。
Raspberry Pi官方提供的GPIO库虽然含有PWM函数,但实际控制舵机可能是由于软件模拟PWM方波不够稳定,导致舵机抖动严重。
可以学习Python串口编程。
所以我选择了以上三个方面。Arduino和Raspberry Pi系统架构系统架构。
外部电源
因为时间紧,我没有在电源管理上花太多时间。对于Arduino,我用的是能装6个1.5V对于干电池的电池盒,Raspberry Pi我从大学室友那里借了一个充电宝来解决耗电量大的问题。但是,虽然供电还可以,但由于充电宝重量大,四轮压力过大,影响了遥控的准确性。
电机驱动
机器人的电机驱动部分是传统的L293D芯片是一种集成高电压、高电流、四通道电机驱动芯片的单片集成,包含两个双极H桥电路,可设置IN1和IN2输入引脚的逻辑电平来控制电机的旋转方向,EN能引脚可以一路连接到PWM上,通过调整PWM方波占空比可调节电机转速。
数据感知
为了实现最基本的避障功能,我们需要为机器人配备一些传感器。这里我使用的传感器为HC-SR04超声波测距模块和红外光电避障模块,其中红外光电避障模块有一对红外发射和接收管,发射管在运行过程中会发射一定频率的红外线。当检测方向遇到障碍物时,红外线会反射回接收管接收较电路处理后,信号输出接口会输出低电平信号。这样,只要在程序中判断接口的电平,就可以知道机器人是否靠近障碍物。
类似于红外光电避障模块的工作原理,超声波模块可以向空中发射超声波信号。当检测到反射信号时,只需将超声波从发射到接收所需的时间乘以声速,然后将其除以二,即可获得机器人与障碍物之间的距离,从而为以后的机器人避障做好准备。
软件
Raspberry Pi库代码
实现了库代码GPIO引脚初始化函数,LED灯具设置函数、蜂鸣器设置函数、电机控制函数、超声波测距函数和LCD1602显示函数,其中LCD1602显示函数调用Python的SMBUS库来完成IIC数据通信可以在屏幕上显示字符串(注:SMBUS和IIC协议之间存在差异,但在Raspberry Pi两者的概念基本相同)。
importtime importsmbus importRPi.GPIOasgpio motor_run_left=17 motor_run_right=10 motor_direction_left=4 motor_direction_right=25 led_left=7 led_right=8 ultrasonic_trig=23 ultrasonic_echo=24 servo=11 buzzer=18 lcd_address=0x27 data_bus=smbus.SMBus(1) classSimpleWheeledRobot: def__init__(self): gpio.setmode(gpio.BCM) gpio.setup(motor_run_left,gpio.OUT) gpio.setup(motor_run_right,gpio.OUT) gpio.setup(motor_direction_left,gpio.OUT) gpio.setup(motor_direction_right,gpio.OUT) gpio.setup(led_left,gpio.OUT) gpio.setup(led_right,gpio.OUT) defset_motors(self,run_left,direction_left,run_right,direction_right): gpio.output(motor_run_left,run_left) gpio.output(motor_run_right,run_right) gpio.output(motor_direction_left,direction_left) gpio.output(motor_direction_right,direction_right) defset_led_left(self,state): gpio.output(led_left,state) defset_led_right(self,state): gpio.output(led_right,&nbp;state)
def go_forward(self, seconds):
if seconds == 0:
self.set_motors(1, 1, 1, 1)
self.set_led_left(1)
self.set_led_right(1)
else:
self.set_motors(1, 1, 1, 1)
time.sleep(seconds)
gpio.cleanup()
def go_reverse(self, seconds):
if seconds == 0:
self.set_motors(1, 0, 1, 0)
self.set_led_left(0)
self.set_led_right(0)
else:
self.set_motors(1, 0, 1, 0)
time.sleep(seconds)
gpio.cleanup()
def go_left(self, seconds):
if seconds == 0:
self.set_motors(0, 0, 1, 1)
self.set_led_left(1)
self.set_led_right(0)
else:
self.set_motors(0, 0, 1, 1)
time.sleep(seconds)
gpio.cleanup()
def go_right(self, seconds):
if seconds == 0:
self.set_motors(1, 1, 0, 0)
self.set_led_left(0)
self.set_led_right(1)
else:
self.set_motors(1, 1, 0, 0)
time.sleep(seconds)
gpio.cleanup()
def go_pivot_left(self, seconds):
if seconds == 0:
self.set_motors(1, 0, 1, 1)
self.set_led_left(1)
self.set_led_right(0)
else:
self.set_motors(1, 0, 1, 1)
time.sleep(seconds)
gpio.cleanup()
def go_pivot_right(self, seconds):
if seconds == 0:
self.set_motors(1, 1, 1, 0)
self.set_led_left(0)
self.set_led_right(1)
else:
self.set_motors(1, 1, 1, 0)
time.sleep(seconds)
gpio.cleanup()
def stop(self):
self.set_motors(0, 0, 0, 0)
self.set_led_left(0)
self.set_led_right(0)
def buzzing(self):
gpio.setup(buzzer, gpio.OUT)
gpio.output(buzzer, True)
for x in range(5):
gpio.output(buzzer, False)
time.sleep(0.1)
gpio.output(buzzer, True)
time.sleep(0.1)
def get_distance(self):
gpio.setmode(gpio.BCM)
gpio.setup(ultrasonic_trig, gpio.OUT)
gpio.setup(ultrasonic_echo, gpio.IN)
gpio.output(ultrasonic_trig, False)
while gpio.input(ultrasonic_echo) == 0:
start_time = time.time()
while gpio.input(ultrasonic_echo) == 1:
stop_time = time.time()
duration = stop_time - start_time
distance = (duration * 34300) / 2
gpio.cleanup()
return distance
def send_command(self, command):
buf = command & 0xF0
buf |= 0x04
data_bus.write_byte(lcd_address, buf)
time.sleep(0.002)
buf &= 0xFB
data_bus.write_byte(lcd_address, buf)
buf = (command & 0x0F) << 4
buf |= 0x04
data_bus.write_byte(lcd_address, buf)
time.sleep(0.002)
buf &= 0xFB
data_bus.write_byte(lcd_address, buf)
def send_data(self, data):
buf = data & 0xF0
buf |= 0x05
data_bus.write_byte(lcd_address, buf)
time.sleep(0.002)
buf &= 0xFB
data_bus.write_byte(lcd_address, buf)
buf = (data & 0x0F) << 4
buf |= 0x05
data_bus.write_byte(lcd_address, buf)
time.sleep(0.002)
buf &= 0xFB
data_bus.write_byte(lcd_address, buf)
def initialize_lcd(self):
try:
self.send_command(0x33)
time.sleep(0.005)
self.send_command(0x32)
time.sleep(0.005)
self.send_command(0x28)
time.sleep(0.005)
self.send_command(0x0C)
time.sleep(0.005)
self.send_command(0x01)
except:
return False
else:
return True
def clear_lcd(self):
self.send_command(0x01)
def print_lcd(self, x, y, lcd_string):
if x < 0:
x = 0
if x > 15:
x = 15
if y < 0:
y = 0
if y > 1:
y = 1
address = 0x80 + 0x40 * y + x
self.send_command(address)
for lcd_char in lcd_string:
self.send_data(ord(lcd_char))
def move_servo_left(self):
servo_range = 13
gpio.setmode(gpio.BCM)
gpio.setup(servo, gpio.OUT)
pwm = gpio.PWM(servo, 100)
pwm.start(0)
while servo_range <= 23:
pwm.ChangeDutyCycle(servo_range)
servo_range += 1
time.sleep(0.5)
pwm.stop()
def move_servo_right(self):
servo_range = 13
gpio.setmode(gpio.BCM)
gpio.setup(servo, gpio.OUT)
pwm = gpio.PWM(servo, 100)
pwm.start(0)
while servo_range >= 0:
pwm.ChangeDutyCycle(servo_range)
servo_range -= 1
time.sleep(0.5)
pwm.stop()
Raspberry Pi测试代码1
该代码调用了上面自己编写的机器人代码库,主要实现了超声波距离检测函数和键盘遥控函数,其中键盘遥控函数里面又根据所按按键的不同调用并组合上面代码库中的不同函数来完成某些特定的功能(比如机器人遇到障碍物后首先会发出警报,然后再进行相应的规避运动等)。
import time
import serial
import random
import Tkinter as tk
import RPi.gpio as gpio
from simple_wheeled_robot_lib import SimpleWheeledRobot
simple_wheeled_robot = SimpleWheeledRobot()
simple_wheeled_robot.initialize_lcd()
port = "/dev/ttyUSB0"
serial_to_arduino = serial.Serial(port, 9600)
serial_from_arduino = serial.Serial(port, 9600)
serial_from_arduino.flushInput()
serial_to_arduino.write('n')
def detecte_distance():
distance = simple_wheeled_robot.get_distance()
if distance >= 20:
# Light up the green led.
serial_to_arduino.write('g')
elif distance >= 10:
# Light up the yellow led.
serial_to_arduino.write('y')
elif distance < 10:
# Light up the red led.
serial_to_arduino.write('r')
simple_wheeled_robot.buzzing()
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_reverse(2)
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_pivot_right(2)
# Check the distance between wall and car's both sides.
serial_to_arduino.write('k')
data_from_arduino = serial_from_arduino.readline()
data_from_arduino_int = int(data_from_arduino)
# Car is too close to the left wall.
if data_from_arduino_int == 01:
simple_wheeled_robot.buzzing()
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_right(2)
# Car is too close to the right wall.
elif data_from_arduino_int == 10:
simple_wheeled_robot.buzzing()
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_left(2)
def input_key(event):
simple_wheeled_robot.__init__()
print 'Key', event.char
key_press = event.char
seconds = 0.05
if key_press.lower() == 'w':
simple_wheeled_robot.go_forward(seconds)
elif key_press.lower() == 's':
simple_wheeled_robot.go_reverse(seconds)
elif key_press.lower() == 'a':
simple_wheeled_robot.go_left(seconds)
elif key_press.lower() == 'd':
simple_wheeled_robot.go_right(seconds)
elif key_press.lower() == 'q':
simple_wheeled_robot.go_pivot_left(seconds)
elif key_press.lower() == 'e':
simple_wheeled_robot.go_pivot_right(seconds)
elif key_press.lower() == 'o':
gpio.cleanup()
# Move the servo in forward directory.
serial_to_arduino.write('o')
time.sleep(0.05)
elif key_press.lower() == 'h':
gpio.cleanup()
# Light up the logo.
serial_to_arduino.write('h')
elif key_press.lower() == 'j':
gpio.cleanup()
# Turn off the logo.
serial_to_arduino.write('j')
elif key_press.lower() == 'p':
gpio.cleanup()
# Move the servo in reverse directory.
serial_to_arduino.write('p')
time.sleep(0.05)
elif key_press.lower() == 'i':
gpio.cleanup()
serial_to_arduino.write('m')
# Enter the random run mode.
serial_to_arduino.write('i')
for z in range(20):
x = random.randrange(0, 5)
if x == 0:
for y in range(30):
detecte_distance()
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_forward(0.05)
elif x == 1:
for y in range(30):
detecte_distance()
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_left(0.05)
elif x == 2:
for y in range(30):
detecte_distance()
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_right(0.05)
elif x == 3:
for y in range(30):
detecte_distance()
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_pivot_left(0.05)
elif x == 4:
for y in range(30):
detecte_distance()
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_pivot_right(0.05)
data_from_arduino = serial_from_arduino.readline()
data_from_arduino_int = int(data_from_arduino)
if data_from_arduino_int == 1111:
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_forward(0.05)
if data_from_arduino_int == 1111:
simple_wheeled_robot.__init__()
simple_wheeled_robot.stop()
elif data_from_arduino_int == 0000:
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_forward(0.05)
elif data_from_arduino_int == 0100:
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_left(0.05)
elif data_from_arduino_int == 1000 or \
data_from_arduino_int == 1100:
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_left(0.08)
elif data_from_arduino_int == 0010:
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_right(0.05)
elif data_from_arduino_int == 0001 or \
data_from_arduino_int == 0011:
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_right(0.08)
serial_to_arduino.write('l')
elif key_press.lower() == 'u':
gpio.cleanup()
simple_wheeled_robot.print_lcd(1, 0, 'UltrasonicWare')
simple_wheeled_robot.print_lcd(1, 1, 'Distance:%.lf CM' %
simple_wheeled_robot.get_distance())
else:
pass
distance = simple_wheeled_robot.get_distance()
if distance >= 20:
serial_to_arduino.write('g')
elif distance >= 10:
serial_to_arduino.write('y')
elif distance < 10:
serial_to_arduino.write('r')
simple_wheeled_robot.buzzing()
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_reverse(2)
serial_to_arduino.write('k')
data_from_arduino = serial_from_arduino.readline()
data_from_arduino_int = int(data_from_arduino)
if data_from_arduino_int == 1:
simple_wheeled_robot.buzzing()
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_right(2)
elif data_from_arduino_int == 10:
simple_wheeled_robot.buzzing()
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_left(2)
try:
command = tk.Tk()
command.bind('<KeyPress>', input_key)
command.mainloop()
except KeyboardInterrupt:
gpio.cleanup()
Raspberry Pi测试代码2
该代码实现的功能比较简单,仅测试了机器人的电机遥控和超声波避障两个功能。
import Tkinter as tk
import RPi.GPIO as gpio
from simple_wheeled_robot_lib import SimpleWheeledRobot
simple_wheeled_robot = SimpleWheeledRobot()
simple_wheeled_robot.initialize_lcd()
def input_key(event):
print 'Key', event.char
key_press = event.char
seconds = 0.05
if key_press.lower() == 'w':
simple_wheeled_robot.go_forward(seconds)
elif key_press.lower() == 's':
simple_wheeled_robot.go_reverse(seconds)
elif key_press.lower() == 'a':
simple_wheeled_robot.go_left(seconds)
elif key_press.lower() == 'd':
simple_wheeled_robot.go_right(seconds)
elif key_press.lower() == 'q':
simple_wheeled_robot.go_pivot_left(seconds)
elif key_press.lower() == 'e':
simple_wheeled_robot.go_pivot_right(seconds)
elif key_press.lower() == 'o':
simple_wheeled_robot.move_servo_left()
elif key_press.lower() == 'p':
simple_wheeled_robot.move_servo_right()
else:
pass
distance = simple_wheeled_robot.get_distance()
simple_wheeled_robot.print_lcd(1, 0, 'UltrasonicWare')
simple_wheeled_robot.print_lcd(1, 1, 'Distance:%.lf CM' % distance)
print "Distance: %.1f CM" % distance
if distance < 10:
simple_wheeled_robot.__init__()
simple_wheeled_robot.go_reverse(2)
try:
command = tk.Tk()
command.bind('<KeyPress>', input_key)
command.mainloop()
except KeyboardInterrupt:
gpio.cleanup()
Raspberry Pi测试代码3
simple_wheeled_robot_run_3.py
该代码实现的功能与上面的测试代码2类似,只不过图形界面本代码里使用的是Pygame而不是之前的Tkinter。
import sys
import pygame
from pygame.locals import *
import RPi.GPIO as gpio
from simple_wheeled_robot_lib import SimpleWheeledRobot
simple_wheeled_robot = SimpleWheeledRobot()
pygame.init()
screen = pygame.display.set_mode((800, 800))
font = pygame.font.SysFont("arial", 64)
pygame.display.set_caption('SimpleWheeledRobot')
pygame.mouse.set_visible(0)
while True:
gpio.cleanup()
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
if event.type == KEYDOWN:
time = 0.03
if event.key == K_UP or event.key == ord('w'):
simple_wheeled_robot.go_forward(time)
elif event.key == K_DOWN or event.key == ord('s'):
simple_wheeled_robot.go_reverse(time)
elif event.key == K_LEFT or event.key == ord('a'):
simple_wheeled_robot.go_left(time)
elif event.key == K_RIGHT or event.key == ord('d'):
simple_wheeled_robot.go_right(time)
elif event.key == ord('q'):
simple_wheeled_robot.go_pivot_left(time)
elif event.key == ord('e'):
simple_wheeled_robot.go_pivot_right(time)
Arduino测试代码
该代码从逻辑上比较好理解,在setup()函数初始化引脚的模式和串口波特率后,主函数loop()会不断地从串口中读取字符数据,并根据字符的不同进行不同的处理工作。
int distance;
int distance_left;
int distance_right;
int motor_value;
int motor_value_a;
int motor_value_b;
int motor_value_c;
int motor_value_d;
int motor_a = 6;
int motor_b = 7;
int motor_c = 8;
int motor_d = 9;
int servo = 5;
int led = 2;
int led_red = 13;
int led_yellow = 12;
int led_green = 11;
int distance_sensor_left = 3;
int distance_sensor_right = 4;
char data;
uint16_t angle = 1500;
void setup()
{
// Set serial's baud rate.
Serial.begin(9600);
pinMode(motor_a, INPUT);
pinMode(motor_b, INPUT);
pinMode(motor_c, INPUT);
pinMode(motor_d, INPUT);
pinMode(servo, OUTPUT);
pinMode(led , OUTPUT);
pinMode(led_red, OUTPUT);
pinMode(led_yellow, OUTPUT);
pinMode(led_green, OUTPUT);
pinMode(distance_sensor_left, INPUT);
pinMode(distance_sensor_right, INPUT);
pinMode(A0, OUTPUT);
pinMode(A1, OUTPUT);
pinMode(A2, OUTPUT);
pinMode(A3, OUTPUT);
pinMode(A4, OUTPUT);
pinMode(A5, OUTPUT);
}
void loop()
{
if (Serial.available()) {
switch(Serial.read()) {
// Light up the logo.
case 'h': {
digitalWrite(A0, HIGH);
digitalWrite(A1, HIGH);
digitalWrite(A2, HIGH);
digitalWrite(A3, HIGH);
digitalWrite(A4, HIGH);
digitalWrite(A5, HIGH);
break;
}
// Turn off the logo.
case 'j': {
digitalWrite(A0, LOW);
digitalWrite(A1, LOW);
digitalWrite(A2, LOW);
digitalWrite(A3, LOW);
digitalWrite(A4, LOW);
digitalWrite(A5, LOW);
break;
}
// Move the servo in forward directory.
case 'o' : {
angle += 50;
if (angle > 2500) {
angle = 2500;
}
break;
}
// Move the servo in reverse directory.
case 'p' : {
angle -= 50;
if (angle < 500) {
angle = 500;
}
break;
}
case 'n': {
digitalWrite(led, HIGH);
break;
}
case 'm': {
digitalWrite(led, LOW);
break;
}
case 'g': {
// When the distance between objects and car is far enough,
// light the green led.
digitalWrite(led_green, HIGH);
digitalWrite(led_yellow, LOW);
digitalWrite(led_red, LOW);
break;
}
case 'y': {
// When the distance between objects and car is near enough,
// light the yellow led.
digitalWrite(led_yellow, HIGH);
digitalWrite(led_green, LOW);
digitalWrite(led_red, LOW);
break;
}
case 'r': {
// When the distance between objects and car is very near,
// light the red led.
digitalWrite(led_red, HIGH);
digitalWrite(led_yellow, LOW);
digitalWrite(led_green, LOW);
break;
}
case 'k': {
// Return distance sensor's state between objects and car.
distance_left = digitalRead(distance_sensor_left);
distance_right = digitalRead(distance_sensor_right);
distance = distance_left * 10 + distance_right ;
Serial.println(distance, DEC);
break;
}
case 'i': {
while (1) {
// Return motor's state to raspberry pi.
motor_value_a = digitalRead(motor_a);
motor_value_b = digitalRead(motor_b);
motor_value_c = digitalRead(motor_c);
motor_value_d = digitalRead(motor_d);
motor_value = motor_value_a * 1000 + motor_value_b * 100 +
motor_value_c * 10 + motor_value_d;
Serial.println(motor_value, DEC);
delay(1000);
data = Serial.read();
if (data == 'l') {
break;
}
}
}
default:
break;
}
}
// Delay enough time for servo to move position.
digitalWrite(servo, HIGH);
delayMicroseconds(angle);
digitalWrite(servo, LOW);
delay(15);
}
成果
以下是制作完成后的成果图和测试视频:
总结
这个简单轮式机器人是大一那会儿我对自己课外所学知识的一个应用。虽然现在回过头再来看自己当初做的项目,感觉技术原理非常简单,远没有我之后研究的ROS和MoveIt!那么复杂,但是通过整个制作过程,我学会了如何根据项目需求去网上购买相关的材料,如何将主控等硬件设备安装在机器人机械结构最合理的位置上,如何使用IDE编写Arduino程序,如何在Raspberry Pi上使用Python语言来读取硬件数据并控制硬件,如何实现简单的串口通信等等。我一直认为兴趣是最好的老师,当你开始接触一个全新的领域时,兴趣可以成为你克服各种困难并鼓舞你前进的强大动力。当然,除了兴趣,更重要的是亲自动手实践,书上的东西讲得再好也是别人的,不是你的,就算重复造轮子也有着其无法替代的重要意义,因为并不是每个人都能造得出轮子,通过学习并实践前人所学习过的知识,你会收获别人不会有的宝贵经验!
最后,。总之,对于我来说,通过本次项目我开始真正喜欢上了嵌入式开发并逐渐走上了专业化的道路,每个人都应该有自己的梦想,那这个简单轮式机器人就是我梦想的起点,激励着我不断向前!当然,也希望大家能在制作机器人的道路上玩得开心并有所收获!