Lego+BrickPi机器人课程学习(二):第一个机器人

从这个教程开始,就要开始介绍机器人的搭建和编程了,将混合参考Robotics 333和Dexster官网的教程进行。

机器人的搭建

Brick Pi端口图 上图展示了Brick Pi的端口图,正如EV3拥有4个传感器接口和4个动作输出单元接口,我们的BrickPi也有4个传感器接口:S1-S4(S for Sensor),以及四个输出接口M1-M4 (M for Motor)。传感器和动作机构的连接方式和EV3相同。

Brick Pi的外壳可以连接乐高Beam组件,连接过程中需要借助peg组件,注意为了组装的便捷,先一次一个将peg放置到位,再连接beam,如下图所示。

外壳连接Beam

对于第一个机器人,我们将参考Robotics 333第二堂习题课的课程要求,而那里面提到将使用Dexster官网的SimpleBot案例教学进行。我们接下来将进行SimpleBot的组装,这是一个最简单的双轮差速机器人。

我对SimpleBot进行了如下升级:第一,将控制器外壳比较舒服地放到了底座上,并在上面制作了简易的电池仓;第二,考虑到带电池整体的重量比较大,将转向轮进行了加大,转向轮的设计参考了The Lego Mindstorms EV3 Discovery Book一书。以下是部分大图。

电池支撑盒 控制器外壳支撑 底部加固 转向加固 原始转向轮设计 完成品

机器人编程

SimpleBot是BrickPi+的案例,这里面给出的simplebot_psp.pysimplebot_simple.pysimplebot_speed.py等三个案例都是用之前版本的代码编写的,和BrickPi 3的代码相差很多。BrickPi 3的文档比较分散不完整,我们参考BrickPi 3的API源代码重新编写这三个文件并进行测试。

注意,Raspbian for Robots已经将BrickPi3安装好了,直接在Python中就可以`import brickpi3。如果用了其他的操作系统,则需要如下进行安装:

sudo curl https://raw.githubusercontent.com/DexterInd/Raspbian_For_Robots/master/upd_script/fetch_brickpi3.sh | bash

实际编程的流程是这样的:(1)在Mac上进行编程工作;(2)通过scp命令将程序传到Raspberry Pi的指定位置;(3)通过ssh或vnc运行程序,实现机器人的程序控制。

在实际运行的时候,我发现了Brick Pi面临的一个电压问题。一开始我使用了Battery Pack,将8节1.2V的AA电池串联进行供电,但是发现这样比较费电,1900毫安的电池1-2个小时就跪了,后来我自作聪明尝试用5V的充电宝进行供电,发现也是可以带动的,就这么用了。然而,在跑程序的时候,我发现电机根本没有响应,但是另一方面却能读到电机手动转动的位置数据。经过一番寻觅,最终发现就是充电宝的锅:根据这份说明,Brick Pi上闪烁的黄灯表明了电压的水平:

  1. 每秒1次说明输入电压在7.2V以上;
  2. 每秒2次说明输入电压在7.2V以下;
  3. 每秒4次说明输入电压低于6.5V或Raspberry的电压低于4.85V;
  4. 快速闪烁说明Brick Pi和Raspberry Pi的连接有问题。

设备要求的名义电压是9V,推荐电压是7.2-10V,虽然5V的电压可以驱动Raspberry,但是乐高的电机要求输入电压要达到9V。使用充电宝就是3.的情形,这时电机被自动disabled。所以尽管耗电快,还是必须使用之前的Battery Pack。

用键盘输入控制的SimpleBot

直接给出代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
##################################################                                                              
# Program Name: simple_first_awsd
# ================================
# This code is for moving the simplebot with awsd
# created to test the first robot ever built
# History
# ------------------------------------------------
# Author Date Comments
# Siyu 20/01/04 Initial Authoring
#
#
#
##################################################

'''
Hardware:
Left Wheel - Connect EV3 Large Motor to MC
Right Wheel - Connect EV3 Large Motor to MD

Commands:
w - Move forward
a - Move left
d - Move right
s - Move back
x - Stop
'''
import brickpi3 as BP
import time

bp = BP.BrickPi3() # initialize a BrickPi3 object

LEGO_MOTOR_LEFT = bp.PORT_C # left EV3 motor connected to MC
LEGO_MOTOR_RIGHT = bp.PORT_D # right EV3 motor connected to MD

DRIVE_SPEED = 200 # the motor speed, specified as dps (degree per second)

#func for moving forward
def fwd():
bp.set_motor_dps( LEGO_MOTOR_LEFT, DRIVE_SPEED )
bp.set_motor_dps( LEGO_MOTOR_RIGHT, DRIVE_SPEED )

#func for moving left
def left():
bp.set_motor_dps( LEGO_MOTOR_RIGHT, DRIVE_SPEED )
bp.set_motor_dps( LEGO_MOTOR_LEFT, -DRIVE_SPEED )

#func for moving right
def right():
bp.set_motor_dps( LEGO_MOTOR_RIGHT, -DRIVE_SPEED)
bp.set_motor_dps( LEGO_MOTOR_LEFT, DRIVE_SPEED)

#func for moving backward
def back():
bp.set_motor_dps( LEGO_MOTOR_LEFT, -DRIVE_SPEED)
bp.set_motor_dps( LEGO_MOTOR_RIGHT, -DRIVE_SPEED)

#func for stop
def stop():
bp.set_motor_dps( LEGO_MOTOR_LEFT, 0)
bp.set_motor_dps( LEGO_MOTOR_RIGHT, 0)


while True:
print('请输入动作:')
inp = str(input())
if inp == 'w':
bp.set_motor_dps( LEGO_MOTOR_LEFT, DRIVE_SPEED )
bp.set_motor_dps( LEGO_MOTOR_RIGHT, DRIVE_SPEED )
elif inp == 'a':
left()
elif inp == 'd':
right()
elif inp == 's':
back()
elif inp == 'x':
stop()
elif inp == 'q':
stop()
break
time.sleep(0.01)

bp.reset_all() # Unconfigure the sensors, disable the motors, and restore the LED to the control of the BrickPi3 firmware.

目前代码只是实现基本的功能,没有考虑完备性和各种情形,只能作为对于基础功能的验证。效果如下:

键盘控制机器人行进

连续控制+加减速的SimpleBot

上面的例子中输入必须要回车确认,且速度无法调整,在这个例子里,我们进行改进,另外将机器人封装到一个类中。程序的主体是这个样子。重点看Tribot类。

1
2
3
4
5
6
7
8
9
10
import brickpi3 as BP
import time
import pygame
import sys

class Tribot(object):
...
if __name__ == '__main__':
simple = Tribot(left = 'C',right='D',speed = 200)
simple.run()

Tribot类长这个样子,需要关注几点。一、我使用了pygame来处理键盘输入,只是实现了基础功能但是会出现一个pygame窗口,暂时先不管。二、使用了一个logging函数将操作和速度以定长的方式打印,且使用了\033[1A\r字符以确保在同一行不断刷新。三、关于加减速,EV3的large motor能达到的最大DPS大概是1000左右,所以将最大速度设成了1000,另外在加减速函数中,机器人将保持行进方向,为此在类中需要记录当前行进方向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
class Tribot(object):

def __init__(self,left = None, right = None, speed = 0):

self.bp = BP.BrickPi3() #i nitialize a BrickPi 3 object
self.left = None # left motor of the robot
self.right = None # right motor of the robot
self.current_move = 'x' #initially the robot is not moving
self.speed = speed

#setup left motor
if left in [1,'A','a']:
self.left = self.bp.PORT_A
elif left in [2,'B','b']:
self.left = self.bp.PORT_B
elif left in [3,'C','c']:
self.left = self.bp.PORT_C
elif left in [4,'D','d']:
self.left = self.bp.PORT_D

#setup right motor
if right in [1,'A','a']:
self.right = self.bp.PORT_A
elif right in [2,'B','b']:
self.right = self.bp.PORT_B
elif right in [3,'C','c']:
self.right = self.bp.PORT_C
elif right in [4,'D','d']:
self.right = self.bp.PORT_D

print('Tribot Initialized')

def logging(self,d,s):

if d == 'w':
Dir = 'forward'

elif d == 's':
Dir = 'backward'

elif d == 'a':
Dir = 'left'

elif d == 'd':
Dir = 'right'

elif d == 'x':
Dir = 'stop'

'''
use ASCII escape sequence to move cursor up one
line (\033[1A). Use \r to move to the beginning of the line.
'''
print('Direction: %-10s Speed: %-4d \033[1A\r' %(Dir,s))

def move(self,val):
#if input value is to move/stop, update
#current move status
if val in ['a','w','s','d','x']:
self.current_move = val

if val == 'w': # move forward
self.bp.set_motor_dps(self.left,self.speed)
self.bp.set_motor_dps(self.right,self.speed)
self.logging(val,self.speed)
elif val == 's': # move backward
self.bp.set_motor_dps(self.left, -self.speed)
self.bp.set_motor_dps(self.right, -self.speed)
self.logging(val,self.speed)
elif val == 'a': # move left
self.bp.set_motor_dps(self.left,-self.speed)
self.bp.set_motor_dps(self.right,+self.speed)
self.logging(val,self.speed)
elif val == 'd': # move right
self.bp.set_motor_dps(self.left, self.speed)
self.bp.set_motor_dps(self.right,-self.speed)
self.logging(val,self.speed)
elif val == 'x': # stop
self.bp.set_motor_dps(self.left, 0)
self.bp.set_motor_dps(self.right,0)
self.logging(val,0)

def speed_up(self):
if self.speed > 990:
self.speed = 1000
else:
self.speed += 10
self.move(self.current_move)

def speed_down(self):
if self.speed < 10:
self.speed = 0
else:
self.speed -= 10
self.move(self.current_move)

'''
use pygame event handler to capture the input from
keyboard. No need to press enter anymore

'''

def run(self):
pygame.init()
#this is just to make pygame works,
#nothing is going to be printed on the screen
screen = pygame.display.set_mode((512,384))
while True:

# get events and (somewhere in background) update list
for event in pygame.event.get():
if event.type == pygame.QUIT:
break

# get current list.
pressed = pygame.key.get_pressed()

if pressed[pygame.K_w]:
self.move('w')
elif pressed[pygame.K_s]:
self.move('s')
elif pressed[pygame.K_a]:
self.move('a')
elif pressed[pygame.K_d]:
self.move('d')
elif pressed[pygame.K_x]:
self.move('x')
elif pressed[pygame.K_i]:
self.speed_up()
elif pressed[pygame.K_k]:
self.speed_down()
elif pressed[pygame.K_q]:
self.move('x')
break
time.sleep(0.1)
self.quit()

def quit(self):
self.bp.reset_all()# Unconfigure the sensors, disable the motors,
#and restore the LED to the control of the BrickPi3 firmware.
print('\nTribot Quit')
pygame.quit()
sys.exit()

控制效果如图:

simplebot_psp.py使用了MINDSENSORS PSP Controller来控制机器人,这部分涉及另外的硬件和I2C端口的使用,先暂时不去碰。

总结

这一次我们参照SimpleBot设计了第一个双轮差速机器人,并编写了两个简单的控制程序。