forked from FRC4564/Maestro
-
Notifications
You must be signed in to change notification settings - Fork 16
/
uart_device.py
executable file
·402 lines (369 loc) · 18.5 KB
/
uart_device.py
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
#!/usr/bin/python
##!/usr/bin/python3 en el original
###########################################################################################
# Filename:
# Device5.py
###########################################################################################
# Project Authors:
# Juhapekka Piiroinen
# Brian Wu
#https://raw.githubusercontent.com/AustralianSynchrotron/Australian-Synchrotron-Surveyor-Tunnel-Exploration-And-Fault-Detection-Robot/master/dev/Device.py
#
# Changes:
# February 8, 2015 by Dipto Pratyaksa
# - Port code from Python2 to Python3
# - added set_angle function to enable servo positioning between 0 and 180 degrees
# September 24, 2012 by Cameron Rodda
# - added minimaestro 12+ multiple servo writes, .set_targets function
# June 14, 2010 by Juhapekka Piiroinen - changes committed to svn
# - added comments for the device commands according to the manual from Pololu
# - added latest draft code for rotating base servo (Parallax Continuous Rotating Servo)
# - note! you should be able to clear error flags with .get_errors function according to the manual
# - renamed CameraDriver to LegacyCameraDriver as Brian Wu has done better one
# - integrated batch of changes provided by Brian Wu
#
# June 11, 2010 by Brian Wu - Changes committed thru email
# - Decoupling the implementation from the program
#
# April 19, 2010 by Juhapekka Piiroinen
# - Initial Release
#
# Email:
#
# License:
# GNU/GPLv3
#
# Description:
# A python-wrapper for Pololu Micro Maestro 6-Channel USB Servo Controller
#
############################################################################################
# /!\ Notes /!\
# You will have to enable _USB Dual Port_ mode from the _Pololu Maestro Control Center_.
#
############################################################################################
# Device Documentation is available @ http://www.pololu.com/docs/pdf/0J40/maestro.pdf
############################################################################################
# (C) 2010 Juhapekka Piiroinen
# Brian Wu
############################################################################################
import serial
import time
def log(*msgline):
for msg in msgline:
print (msg),
print
class Device(object):
def __init__(self,con_port="/dev/ttyACM1",ser_port="/dev/ttyACM0",timeout=1): #/dev/ttyACM0 and /dev/ttyACM1 for Linux
############################
# lets introduce and init the main variables
self.con = None
self.ser = None
self.isInitialized = False
############################
# lets connect the TTL Port
try:
self.con = serial.Serial(con_port,timeout=timeout)
self.con.baudrate = 9600
self.con.close()
self.con.open()
log("Link to Command Port -", con_port, "- successful")
except serial.serialutil.SerialException as e:
print (e)
log("Link to Command Port -", con_port, "- failed")
if self.con:
#####################
#If your Maestro's serial mode is "UART, detect baud rate", you must first send it the baud rate indication byte 0xAA on
#the RX line before sending any commands. The 0xAA baud rate indication byte can be the first byte of a Pololu protocol
#command.
#http://www.pololu.com/docs/pdf/0J40/maestro.pdf - page 35
#self.con.write(bytes(chr(0xAA),'utf-8'))
#self.con.flush()
#log("Baud rate indication byte 0xAA sent!")
pass
###################################
# lets connect the TTL Port
try:
self.ser = serial.Serial(ser_port,timeout=timeout)
#self.ser.baudrate = 9600
self.ser.close()
self.ser.open()
log("Link to TTL Port -", ser_port, "- successful")
except serial.serialutil.SerialException as e:
print (e)
log("Link to TTL Port -", ser_port, "- failed!")
self.isInitialized = (self.con!=None and self.ser!=None)
if (self.isInitialized):
err_flags = self.get_errors()
log("Device error flags read (",err_flags,") and cleared")
log("Device initialized:",self.isInitialized)
###########################################################################################################################
## common write function for handling all write related tasks
def write(self,*data):
if not self.isInitialized: log("Not initialized"); return
if not self.ser.writable():
log("Device not writable")
return
for d in data:
if type(d) is list:
# Handling for writing to multiple servos at same time
for li in d:
self.ser.write(bytes([li]))
else:
self.ser.write(bytes([d]))
self.ser.flush()
###########################################################################################################################
## Go Home
# Compact protocol: 0xA2
# --
# This command sends all servos and outputs to their home positions, just as if an error had occurred. For servos and
# outputs set to "Ignore", the position will be unchanged.
# --
# Source: http://www.pololu.com/docs/pdf/0J40/maestro.pdf
def go_home(self):
if not self.isInitialized: log("Not initialized"); return
self.write(0xA2)
###########################################################################################################################
## Set Target
# Compact protocol: 0x84, channel number, target low bits, target high bits
# --
# The lower 7 bits of the third data byte represent bits 0-6 of the target (the lower 7 bits), while the lower 7 bits of the
# fourth data byte represent bits 7-13 of the target. The target is a non-negative integer.
# --
# Source: http://www.pololu.com/docs/pdf/0J40/maestro.pdf
def set_target(self, servo, value):
if not self.isInitialized: log("Not initialized"); return
if not self.ser.writable():
log("Device not writable")
return
#time.sleep(0.0001)
value = int(value)*4
log("servo: {} value: {}".format(servo, value))
commandByte = 0x84
commandByte2 = 0xaa + 0x0c + 0x04
channelByte = servo
lowTargetByte = value & 0x7F
highTargetByte = (value >> 7) & 0x7F
self.write(commandByte, channelByte, lowTargetByte, highTargetByte)
def setAngle(self, servo, angle):
if angle > 180 or angle <0:
angle=90
byteone=int(254*angle/180)
self.write(0xFF, servo, byteone)
def setRotation(self, servo, angle):
if angle > 254 or angle <0:
angle=127
self.write(0xFF, servo, angle)
##########################################################################################################################
## Set Targets
# Compact protocol: 0x9F, number of targets, first channel number, first target low bits, first target high bits, second
# target low bits, second target high bits, ...
# --
# This command simultaneously sets the targets for a contiguous block of channels. The first byte specifies how many
# channels are in the contiguous block; this is the number of target values you will need to send. The second byte specifies
# the lowest channel number in the block. The subsequent bytes contain the target values for each of the channels, in order
# by channel number, in the same format as the Set Target command above.
# --
# Source: http://www.pololu.com/docs/pdf/0J40/maestro.pdf
def set_targets(self,num_targets,start_channel,values):
if not self.isInitialized: log("Not initialized"); return
result = []
for k in range(num_targets):
print ("K=",values[k])
highbits,lowbits = divmod(values[k],32)
#lowbits = values[k] & 0x7F
#highbits = (values[k] >> 7) & 0x7F
result.append(lowbits)
result.append(highbits)
if type(start_channel) is list:
start_channel = min(start_channel)
self.write(0x9F,num_targets,start_channel,lowbits, highbits, lowbits,highbits)
###########################################################################################################################
## Set Speed
# Compact protocol: 0x87, channel number, speed low bits, speed high bits
# --
# This command limits the speed at which a servo channel's output value changes. The speed limit is given in units of (0.25 us)/(10 ms)
# --
# For example, the command 0x87, 0x05, 0x0C, 0x01 sets
# the speed of servo channel 5 to a value of 140, which corresponds to a speed of 3.5 us/ms. What this means is that if
# you send a Set Target command to adjust the target from, say, 1000 us to 1350 us, it will take 100 ms to make that
# adjustment. A speed of 0 makes the speed unlimited, so that setting the target will immediately affect the position. Note
# that the actual speed at which your servo moves is also limited by the design of the servo itself, the supply voltage, and
# mechanical loads; this parameter will not help your servo go faster than what it is physically capable of.
# --
# At the minimum speed setting of 1, the servo output takes 40 seconds to move from 1 to 2 ms.
# The speed setting has no effect on channels configured as inputs or digital outputs.
# --
# Source: http://www.pololu.com/docs/pdf/0J40/maestro.pdf
def set_speed(self,servo,speed):
if not self.isInitialized: log("Not initialized"); return
highbits,lowbits = divmod(speed,32)
self.write(0x87,servo,lowbits << 2,highbits)
time.sleep(0.1)
def set_speeds(self,servos,speeds):
if not self.isInitialized: log("Not initialized"); return
index = 0
for s in servos:
if type(speeds) is list:
highbits,lowbits = divmod(speeds[index],32)
self.write(0x87,s,lowbits << 2,highbits)
#log("MULTI: channel %s; speed %s"%(s,speeds[index]))
index += 1
elif type(speeds) is int:
highbits,lowbits = divmod(speeds,32)
self.write(0x87,s,lowbits << 2,highbits)
#log("SINGLE: channel %s; speed %s"%(s,speeds))
else:
log("Set Speed: <Type> Error"); return
###########################################################################################################################
## Set Acceleration
# Compact protocol: 0x89, channel number, acceleration low bits, acceleration high bits
# --
# This command limits the acceleration of a servo channel's output. The acceleration limit is a value from 0 to 255 in units of (0.25 us)/(10 ms)/(80 ms),
# --
# A value of 0 corresponds to no acceleration limit. An acceleration limit causes the speed of a servo to slowly ramp up until it reaches the maximum speed, then
# to ramp down again as position approaches target, resulting in a relatively smooth motion from one point to another.
# With acceleration and speed limits, only a few target settings are required to make natural-looking motions that would
# otherwise be quite complicated to produce.
# --
# At the minimum acceleration setting of 1, the servo output takes about 3 seconds to move smoothly from a target of 1 ms to a target of 2 ms.
# The acceleration setting has no effect on channels configured as inputs or digital outputs.
# --
# Source: http://www.pololu.com/docs/pdf/0J40/maestro.pdf
def set_acceleration(self,servo,acceleration):
if not self.isInitialized: log("Not initialized"); return
highbits,lowbits = divmod(acceleration,32)
self.write(0x89,servo,lowbits << 2,highbits)
###########################################################################################################################
## Set PWM (Mini Maestro 12, 18, and 24 only)
# Compact protocol: 0x8A, on time low bits, on time high bits, period low bits, period high bits
# --
# This command sets the PWM output to the specified on time and period, in units of 1/48 us. The on time and period
# are both encoded with 7 bits per byte in the same way as the target in command 0x84, above.
# --
# Source: http://www.pololu.com/docs/pdf/0J40/maestro.pdf
def set_pwm(self):
pass
###########################################################################################################################
## Get Position
# Compact protocol: 0x90, channel number
#Pololu protocol: 0xAA, device number, 0x10, channel number
# Response: position low 8 bits, position high 8 bits
# --
# This command allows the device communicating with the Maestro to get the position value of a channel. The position
# is sent as a two-byte response immediately after the command is received.
# --
# If the specified channel is configured as a servo, this position value represents the current pulse width that the Maestro
# is transmitting on the channel, reflecting the effects of any previous commands, speed and acceleration limits, or scripts
# running on the Maestro.
# --
# If the channel is configured as a digital output, a position value less than 6000 means the Maestro is driving the line low,
# while a position value of 6000 or greater means the Maestro is driving the line high.
# --
# If the channel is configured as an input, the position represents the voltage measured on the channel. The inputs on
# channels 0-11 are analog: their values range from 0 to 1023, representing voltages from 0 to 5 V. The inputs on channels
# 12-23 are digital: their values are either exactly 0 or exactly 1023.
# --
# Note that the formatting of the position in this command differs from the target/speed/acceleration formatting in the
# other commands. Since there is no restriction on the high bit, the position is formatted as a standard little-endian two-
# byte unsigned integer. For example, a position of 2567 corresponds to a response 0x07, 0x0A.
# --
# Note that the position value returned by this command is equal to four times the number displayed in the Position box
# in the Status tab of the Maestro Control Center.
# --
# Source: http://www.pololu.com/docs/pdf/0J40/maestro.pdf
def get_position(self,servo):
if not self.isInitialized: log("Not initialized"); return None
command = chr(0x90) + chr(servo)
#command = chr(0xaa) + chr(0x0c)+chr(0x10)+chr(servo)
self.write(0x90,servo)
#self.write(0xAA, 0x0C, 0x10, servo)
data = self.ser.read(2)
if data:
return (int(data[0])+int(data[1]<<8))/4
else:
return None
def get_positions(self,servos):
if not self.isInitialized: log("Not initialized"); return None
result = []
for s in servos:
self.write(0x90,servo)
data = self.ser.read(2)
if data:
result.append( (int(data[0])+(int(data[1])<<8))/4 )
else:
result.append( None )
return result
###########################################################################################################################
## Get Moving State
# Compact protocol: 0x93
# Response: 0x00 if no servos are moving, 0x01 if servos are moving
# --
# This command is used to determine whether the servo outputs have reached their targets or are still changing, limited
# by speed or acceleration settings. Using this command together with the Set Target command, you can initiate several
# servo movements and wait for all the movements to finish before moving on to the next step of your program.
# --
# Source: http://www.pololu.com/docs/pdf/0J40/maestro.pdf
# WARNING: BUGGY! 0x01 always returned althoug it has stopped
def get_moving_state(self):
if not self.isInitialized: log("Not initialized"); return None
self.write(0x93)
data = self.ser.read(1)
if data:
return data[0]
else:
return None
###########################################################################################################################
## Get Errors
# Compact protocol: 0xA1
# --
# Response: error bits 0-7, error bits 8-15
# --
# Use this command to examine the errors that the Maestro has detected.
# --
# The error register is sent as a two-byte response immediately after the command is received,
# then all the error bits are cleared. For most applications using serial control, it is a good idea to check errors continuously
# and take appropriate action if errors occur.
# --
# Source: http://www.pololu.com/docs/pdf/0J40/maestro.pdf
def get_errors(self):
if not self.isInitialized: log("Not initialized"); return None
self.write(0xA1)
#self.ser.write(bytes(chr(0xA1),'UTF-8'))
data = self.ser.read(2)
if data:
return int(data[0])+int(data[1])<<8
else:
return None
###########################################################################################################################
## a helper function for Set Target
def wait_until_at_target(self):
while (self.get_moving_state()):
time.sleep(0.01)
###########################################################################################################################
## Lets close and clean when we are done
def __del__(self):
try:
if (self.ser):
self.ser.close()
del(self.ser)
except Exception as e:
print(e)
try:
if (self.con):
self.con.close()
del(self.conf)
except Exception as e:
print(e)
#convert angle 0-180 degrees to servo pos
#=(degree*(max-min)/180)+min
def set_angle(self, servo, min, max, degree):
val=(float(degree)*(max-min)/180)+min
self.set_target(servo,val)
print("Degree = " , degree)
def up(self, servo, min, max):
self.set_angle(servo,min,max,180)
def mid(self, servo,min,max):
self.set_angle(servo,min,max,90)
def down(self, servo,min,max):
self.set_angle(servo,min,max,0)