line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Device::Dynamixel; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
43129
|
use strict; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
40
|
|
4
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
39
|
|
5
|
1
|
|
|
1
|
|
6
|
use List::Util qw(sum); |
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
122
|
|
6
|
1
|
|
|
1
|
|
6
|
use feature qw(say); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
105
|
|
7
|
1
|
|
|
1
|
|
2333
|
use Const::Fast; |
|
1
|
|
|
|
|
4483
|
|
|
1
|
|
|
|
|
12
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 NAME |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
Device::Dynamixel - Simple control of Robotis Dynamixel servo motors |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=cut |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
our $VERSION = '0.027'; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 SYNOPSIS |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
use Device::Dynamixel; |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
open my $pipe, '+<', '/dev/ttyUSB0' or die "Couldn't open pipe for reading and writing"; |
23
|
|
|
|
|
|
|
my $motorbus = Device::Dynamixel->new($pipe); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# set motion speed of ALL motors to 200 |
26
|
|
|
|
|
|
|
$motorbus->writeMotor($Device::Dynamixel::BROADCAST_ID, |
27
|
|
|
|
|
|
|
$Device::Dynamixel::addresses{Moving_Speed_L}, [200, 0]); |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# move motor 5 to 10 degrees off-center |
30
|
|
|
|
|
|
|
$motorbus->moveMotorTo_deg(5, 10); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# read the position of motor 5 |
33
|
|
|
|
|
|
|
my $status = $motorbus->readMotor(5, |
34
|
|
|
|
|
|
|
$Device::Dynamixel::addresses{Present_Position_L}, 2); |
35
|
|
|
|
|
|
|
my @params = @{$status->{params}}; |
36
|
|
|
|
|
|
|
my $position = $params[1]*255 + $params[0]; |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
=head1 DESCRIPTION |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
This is a simple module to communicate with Robotis Dynamixel servo motors. The |
41
|
|
|
|
|
|
|
Dynamixel AX-12 motors have been tested to work with this module, but the others |
42
|
|
|
|
|
|
|
should work also. |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
A daisy-chained series string of motors is connected to the host via a simple |
45
|
|
|
|
|
|
|
serial connection. Each motor in the series has an 8-bit ID. This ID is present |
46
|
|
|
|
|
|
|
in every command to address specific motors. One Device::Dynamixel object should |
47
|
|
|
|
|
|
|
be created for a single string of motors connected to one motor port. |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
These motors communicate using a particular protocol, which is implemented by |
50
|
|
|
|
|
|
|
this module. Commands are sent to the motor. A status reply is sent back after |
51
|
|
|
|
|
|
|
each command. This module handles construction and parsing of Dynamixel packets, |
52
|
|
|
|
|
|
|
as well as the sending and receiving data when needed. |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
=head2 EXPORTED VARIABLES |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
To communicate with all motor at once, send commands to the broadcast ID: |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
$Device::Dynamixel::BROADCAST_ID |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
All the motor control addresses described in the Dynamixel docs are defined in this module, |
61
|
|
|
|
|
|
|
available as |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
$Device::Dynamixel::addresses{$value} |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
Defined values are: |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
ModelNumber_L |
68
|
|
|
|
|
|
|
ModelNumber_H |
69
|
|
|
|
|
|
|
Version_of_Firmware |
70
|
|
|
|
|
|
|
ID |
71
|
|
|
|
|
|
|
Baud_Rate |
72
|
|
|
|
|
|
|
Return_Delay_Time |
73
|
|
|
|
|
|
|
CW_Angle_Limit_L |
74
|
|
|
|
|
|
|
CW_Angle_Limit_H |
75
|
|
|
|
|
|
|
CCW_Angle_Limit_L |
76
|
|
|
|
|
|
|
CCW_Angle_Limit_H |
77
|
|
|
|
|
|
|
Highest_Limit_Temperature |
78
|
|
|
|
|
|
|
Lowest_Limit_Voltage |
79
|
|
|
|
|
|
|
Highest_Limit_Voltage |
80
|
|
|
|
|
|
|
Max_Torque_L |
81
|
|
|
|
|
|
|
Max_Torque_H |
82
|
|
|
|
|
|
|
Status_Return_Level |
83
|
|
|
|
|
|
|
Alarm_LED |
84
|
|
|
|
|
|
|
Alarm_Shutdown |
85
|
|
|
|
|
|
|
Down_Calibration_L |
86
|
|
|
|
|
|
|
Down_Calibration_H |
87
|
|
|
|
|
|
|
Up_Calibration_L |
88
|
|
|
|
|
|
|
Up_Calibration_H |
89
|
|
|
|
|
|
|
Torque_Enable |
90
|
|
|
|
|
|
|
LED |
91
|
|
|
|
|
|
|
CW_Compliance_Margin |
92
|
|
|
|
|
|
|
CCW_Compliance_Margin |
93
|
|
|
|
|
|
|
CW_Compliance_Slope |
94
|
|
|
|
|
|
|
CCW_Compliance_Slope |
95
|
|
|
|
|
|
|
Goal_Position_L |
96
|
|
|
|
|
|
|
Goal_Position_H |
97
|
|
|
|
|
|
|
Moving_Speed_L |
98
|
|
|
|
|
|
|
Moving_Speed_H |
99
|
|
|
|
|
|
|
Torque_Limit_L |
100
|
|
|
|
|
|
|
Torque_Limit_H |
101
|
|
|
|
|
|
|
Present_Position_L |
102
|
|
|
|
|
|
|
Present_Position_H |
103
|
|
|
|
|
|
|
Present_Speed_L |
104
|
|
|
|
|
|
|
Present_Speed_H |
105
|
|
|
|
|
|
|
Present_Load_L |
106
|
|
|
|
|
|
|
Present_Load_H |
107
|
|
|
|
|
|
|
Present_Voltage |
108
|
|
|
|
|
|
|
Present_Temperature |
109
|
|
|
|
|
|
|
Registered_Instruction |
110
|
|
|
|
|
|
|
Reserved |
111
|
|
|
|
|
|
|
Moving |
112
|
|
|
|
|
|
|
Lock |
113
|
|
|
|
|
|
|
Punch_L |
114
|
|
|
|
|
|
|
Punch_H |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
To change the baud rate of the motor, the B address must be written |
117
|
|
|
|
|
|
|
with a value |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
$Device::Dynamixel::baudrateValues{$baud} |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
The available baud rates are |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
1000000 |
124
|
|
|
|
|
|
|
500000 |
125
|
|
|
|
|
|
|
400000 |
126
|
|
|
|
|
|
|
250000 |
127
|
|
|
|
|
|
|
200000 |
128
|
|
|
|
|
|
|
115200 |
129
|
|
|
|
|
|
|
57600 |
130
|
|
|
|
|
|
|
19200 |
131
|
|
|
|
|
|
|
9600 |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
Note that the baud rate generally is cached from the last time the motor was |
134
|
|
|
|
|
|
|
used, defaulting to 1Mbaud at the start |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=head2 STATUS RETURN |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
Most of the functions return a status hash that describes the status of the motors and/or returns |
139
|
|
|
|
|
|
|
queried data. This hash is defined as |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
{ from => $motorID, |
142
|
|
|
|
|
|
|
error => $error, |
143
|
|
|
|
|
|
|
params => \@parameters } |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
If no valid reply was received, undef is returned. Look at the Dynamixel hardware documentation for |
146
|
|
|
|
|
|
|
the exact meaning of each hash element. |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=cut |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# Constants defined in the dynamixel docs |
152
|
|
|
|
|
|
|
const our $BROADCAST_ID => 0xFE; |
153
|
|
|
|
|
|
|
const my %instructions => |
154
|
|
|
|
|
|
|
(PING => 0x01, |
155
|
|
|
|
|
|
|
READ_DATA => 0x02, |
156
|
|
|
|
|
|
|
WRITE_DATA => 0x03, |
157
|
|
|
|
|
|
|
REG_WRITE => 0x04, |
158
|
|
|
|
|
|
|
ACTION => 0x05, |
159
|
|
|
|
|
|
|
RESET => 0x06, |
160
|
|
|
|
|
|
|
SYNC_WRITE => 0x83); |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
const our %addresses => |
163
|
|
|
|
|
|
|
(ModelNumber_L => 0, |
164
|
|
|
|
|
|
|
ModelNumber_H => 1, |
165
|
|
|
|
|
|
|
Version_of_Firmware => 2, |
166
|
|
|
|
|
|
|
ID => 3, |
167
|
|
|
|
|
|
|
Baud_Rate => 4, |
168
|
|
|
|
|
|
|
Return_Delay_Time => 5, |
169
|
|
|
|
|
|
|
CW_Angle_Limit_L => 6, |
170
|
|
|
|
|
|
|
CW_Angle_Limit_H => 7, |
171
|
|
|
|
|
|
|
CCW_Angle_Limit_L => 8, |
172
|
|
|
|
|
|
|
CCW_Angle_Limit_H => 9, |
173
|
|
|
|
|
|
|
Highest_Limit_Temperature => 11, |
174
|
|
|
|
|
|
|
Lowest_Limit_Voltage => 12, |
175
|
|
|
|
|
|
|
Highest_Limit_Voltage => 13, |
176
|
|
|
|
|
|
|
Max_Torque_L => 14, |
177
|
|
|
|
|
|
|
Max_Torque_H => 15, |
178
|
|
|
|
|
|
|
Status_Return_Level => 16, |
179
|
|
|
|
|
|
|
Alarm_LED => 17, |
180
|
|
|
|
|
|
|
Alarm_Shutdown => 18, |
181
|
|
|
|
|
|
|
Down_Calibration_L => 20, |
182
|
|
|
|
|
|
|
Down_Calibration_H => 21, |
183
|
|
|
|
|
|
|
Up_Calibration_L => 22, |
184
|
|
|
|
|
|
|
Up_Calibration_H => 23, |
185
|
|
|
|
|
|
|
Torque_Enable => 24, |
186
|
|
|
|
|
|
|
LED => 25, |
187
|
|
|
|
|
|
|
CW_Compliance_Margin => 26, |
188
|
|
|
|
|
|
|
CCW_Compliance_Margin => 27, |
189
|
|
|
|
|
|
|
CW_Compliance_Slope => 28, |
190
|
|
|
|
|
|
|
CCW_Compliance_Slope => 29, |
191
|
|
|
|
|
|
|
Goal_Position_L => 30, |
192
|
|
|
|
|
|
|
Goal_Position_H => 31, |
193
|
|
|
|
|
|
|
Moving_Speed_L => 32, |
194
|
|
|
|
|
|
|
Moving_Speed_H => 33, |
195
|
|
|
|
|
|
|
Torque_Limit_L => 34, |
196
|
|
|
|
|
|
|
Torque_Limit_H => 35, |
197
|
|
|
|
|
|
|
Present_Position_L => 36, |
198
|
|
|
|
|
|
|
Present_Position_H => 37, |
199
|
|
|
|
|
|
|
Present_Speed_L => 38, |
200
|
|
|
|
|
|
|
Present_Speed_H => 39, |
201
|
|
|
|
|
|
|
Present_Load_L => 40, |
202
|
|
|
|
|
|
|
Present_Load_H => 41, |
203
|
|
|
|
|
|
|
Present_Voltage => 42, |
204
|
|
|
|
|
|
|
Present_Temperature => 43, |
205
|
|
|
|
|
|
|
Registered_Instruction => 44, |
206
|
|
|
|
|
|
|
Reserved => 45, |
207
|
|
|
|
|
|
|
Moving => 46, |
208
|
|
|
|
|
|
|
Lock => 47, |
209
|
|
|
|
|
|
|
Punch_L => 48, |
210
|
|
|
|
|
|
|
Punch_H => 49); |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
const our %baudrateValues => |
213
|
|
|
|
|
|
|
(1000000 => 0x01, |
214
|
|
|
|
|
|
|
500000 => 0x03, |
215
|
|
|
|
|
|
|
400000 => 0x04, |
216
|
|
|
|
|
|
|
250000 => 0x07, |
217
|
|
|
|
|
|
|
200000 => 0x09, |
218
|
|
|
|
|
|
|
115200 => 0x10, |
219
|
|
|
|
|
|
|
57600 => 0x22, |
220
|
|
|
|
|
|
|
19200 => 0x67, |
221
|
|
|
|
|
|
|
9600 => 0xCF); |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
# a received packet is deemed complete if no data was received in this much time |
224
|
|
|
|
|
|
|
const my $timeDelimiter_s => 0.1; |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
# motor range in command coordinates and in degrees |
227
|
|
|
|
|
|
|
const my $motorRange_coords => 0x400; |
228
|
|
|
|
|
|
|
const my $motorRange_deg => 300; |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=head2 new( PIPE ) |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
Creates a new object to talk to a Dynamixel motor. The file handle has to be opened and set-up |
235
|
|
|
|
|
|
|
prior to constructing the object. |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=cut |
238
|
|
|
|
|
|
|
sub new |
239
|
|
|
|
|
|
|
{ |
240
|
0
|
|
|
0
|
1
|
|
my ($classname, $pipe) = @_; |
241
|
|
|
|
|
|
|
|
242
|
0
|
|
|
|
|
|
my $this = {}; |
243
|
0
|
|
|
|
|
|
bless($this, $classname); |
244
|
|
|
|
|
|
|
|
245
|
0
|
|
|
|
|
|
return $this->_init($pipe); |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub _init |
249
|
|
|
|
|
|
|
{ |
250
|
0
|
|
|
0
|
|
|
my $this = shift; |
251
|
0
|
|
|
|
|
|
my $pipe = shift; |
252
|
|
|
|
|
|
|
|
253
|
0
|
|
|
|
|
|
$this->{pipe} = $pipe; |
254
|
0
|
|
|
|
|
|
return $this; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
# Constructs a binary dynamixel packet with a given command |
258
|
|
|
|
|
|
|
sub _makeInstructionPacket |
259
|
|
|
|
|
|
|
{ |
260
|
0
|
|
|
0
|
|
|
my ($motorID, $instruction, $parameters) = @_; |
261
|
0
|
|
|
|
|
|
my $body = pack( 'C3C' . scalar @$parameters, |
262
|
|
|
|
|
|
|
$motorID, 2 + @$parameters, $instruction, |
263
|
|
|
|
|
|
|
@$parameters ); |
264
|
|
|
|
|
|
|
|
265
|
0
|
|
|
|
|
|
my $checksum = ( ~sum(unpack('C*', $body)) & 0xFF ); |
266
|
0
|
|
|
|
|
|
return pack('CC', 0xFF, 0xFF) . $body . chr $checksum; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=head1 METHODS |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=head2 pingMotor( motorID ) |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
Sends a ping. Status reply is returned |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=cut |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
sub pingMotor |
278
|
|
|
|
|
|
|
{ |
279
|
0
|
|
|
0
|
1
|
|
my $this = shift; |
280
|
0
|
|
|
|
|
|
my ($motorID) = @_; |
281
|
|
|
|
|
|
|
|
282
|
0
|
|
|
|
|
|
my $pipe = $this->{pipe}; |
283
|
0
|
|
|
|
|
|
print $pipe _makeInstructionPacket($motorID, $instructions{PING}, []); |
284
|
0
|
|
|
|
|
|
return _pullMotorReply($this->{pipe}); |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=head2 writeMotor( motorID, startingAddress, data ) |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
Sends a command to the motor. Status reply is returned. |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
=cut |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
sub writeMotor |
294
|
|
|
|
|
|
|
{ |
295
|
0
|
|
|
0
|
1
|
|
my $this = shift; |
296
|
0
|
|
|
|
|
|
my ($motorID, $where, $what) = @_; |
297
|
|
|
|
|
|
|
|
298
|
0
|
|
|
|
|
|
my $pipe = $this->{pipe}; |
299
|
0
|
|
|
|
|
|
print $pipe _makeInstructionPacket($motorID, $instructions{WRITE_DATA}, [$where, @$what]); |
300
|
0
|
|
|
|
|
|
return _pullMotorReply($this->{pipe}); |
301
|
|
|
|
|
|
|
} |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=head2 readMotor( motorID, startingAddress, howManyBytes ) |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
Reads data from the motor. Status reply is returned. |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
=cut |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
sub readMotor |
310
|
|
|
|
|
|
|
{ |
311
|
0
|
|
|
0
|
1
|
|
my $this = shift; |
312
|
0
|
|
|
|
|
|
my ($motorID, $where, $howmany) = @_; |
313
|
|
|
|
|
|
|
|
314
|
0
|
|
|
|
|
|
my $pipe = $this->{pipe}; |
315
|
0
|
|
|
|
|
|
print $pipe _makeInstructionPacket($motorID, $instructions{READ_DATA}, [$where, $howmany]); |
316
|
0
|
|
|
|
|
|
return _pullMotorReply($this->{pipe}); |
317
|
|
|
|
|
|
|
} |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
=head2 writeMotor_queue( motorID, startingAddress, data ) |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
Queues a particular command to the motor and returns the received reply. Does |
322
|
|
|
|
|
|
|
not actually execute the command until triggered with triggerMotorQueue( ) |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
=cut |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
sub writeMotor_queue |
327
|
|
|
|
|
|
|
{ |
328
|
0
|
|
|
0
|
1
|
|
my $this = shift; |
329
|
0
|
|
|
|
|
|
my ($motorID, $where, $what) = @_; |
330
|
|
|
|
|
|
|
|
331
|
0
|
|
|
|
|
|
my $pipe = $this->{pipe}; |
332
|
0
|
|
|
|
|
|
print $pipe _makeInstructionPacket($motorID, $instructions{REG_WRITE}, [$where, @$what]); |
333
|
0
|
|
|
|
|
|
return _pullMotorReply($this->{pipe}); |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
=head2 triggerMotorQueue( motorID ) |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
Sends a trigger for the queued commands. Status reply is returned. |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=cut |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
sub triggerMotorQueue |
343
|
|
|
|
|
|
|
{ |
344
|
0
|
|
|
0
|
1
|
|
my $this = shift; |
345
|
0
|
|
|
|
|
|
my ($motorID) = @_; |
346
|
|
|
|
|
|
|
|
347
|
0
|
|
|
|
|
|
my $pipe = $this->{pipe}; |
348
|
0
|
|
|
|
|
|
print $pipe _makeInstructionPacket($motorID, $instructions{ACTION}, []); |
349
|
0
|
|
|
|
|
|
return _pullMotorReply($this->{pipe}); |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
=head2 resetMotor( motorID ) |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
Sends a motor reset. Status reply is returned. |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
=cut |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
sub resetMotor |
359
|
|
|
|
|
|
|
{ |
360
|
0
|
|
|
0
|
1
|
|
my $this = shift; |
361
|
0
|
|
|
|
|
|
my ($motorID) = @_; |
362
|
|
|
|
|
|
|
|
363
|
0
|
|
|
|
|
|
my $pipe = $this->{pipe}; |
364
|
0
|
|
|
|
|
|
print $pipe _makeInstructionPacket($motorID, $instructions{RESET}, []); |
365
|
0
|
|
|
|
|
|
return _pullMotorReply($this->{pipe}); |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
=head2 syncWriteMotor( motorID, startingAddress, data ) |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
Sends a synced-write command to the motor. Status reply is returned. |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
=cut |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
sub syncWriteMotor |
375
|
|
|
|
|
|
|
{ |
376
|
0
|
|
|
0
|
1
|
|
my $this = shift; |
377
|
0
|
|
|
|
|
|
my ($motorID, $writes, $where) = @_; |
378
|
|
|
|
|
|
|
|
379
|
0
|
|
|
|
|
|
my @parms = map { ($_->{motorID}, @{$_->{what}}) } @$writes; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
380
|
0
|
|
|
|
|
|
my $lenchunk = scalar @{$writes->[0]{what}}; |
|
0
|
|
|
|
|
|
|
381
|
0
|
|
|
|
|
|
@parms = ($where, $lenchunk, @parms); |
382
|
|
|
|
|
|
|
|
383
|
0
|
0
|
|
|
|
|
if( ($lenchunk + 1) * @$writes + 2 != @parms ) |
384
|
|
|
|
|
|
|
{ |
385
|
0
|
|
|
|
|
|
die "syncWriteMotor: size mismatch!"; |
386
|
|
|
|
|
|
|
} |
387
|
|
|
|
|
|
|
|
388
|
0
|
|
|
|
|
|
my $pipe = $this->{pipe}; |
389
|
0
|
|
|
|
|
|
print $pipe _makeInstructionPacket($BROADCAST_ID, $instructions{SYNC_WRITE}, \@parms); |
390
|
0
|
|
|
|
|
|
return _pullMotorReply($this->{pipe}); |
391
|
|
|
|
|
|
|
} |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
sub _pullMotorReply |
394
|
|
|
|
|
|
|
{ |
395
|
0
|
|
|
0
|
|
|
my $pipe = shift; |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
# read data until there's a lull of $timeDelimiter_s seconds |
398
|
0
|
|
|
|
|
|
my $packet = ''; |
399
|
0
|
|
|
|
|
|
select(undef, undef, undef, $timeDelimiter_s); # sleep for a bit to wait for data |
400
|
0
|
|
|
|
|
|
while(1) |
401
|
|
|
|
|
|
|
{ |
402
|
0
|
|
|
|
|
|
my $rin = ''; |
403
|
0
|
|
|
|
|
|
vec($rin,fileno($pipe),1) = 1; |
404
|
0
|
|
|
|
|
|
my ($nfound, $timeleft) = select($rin, undef, undef, $timeDelimiter_s); |
405
|
0
|
0
|
|
|
|
|
last if($nfound == 0); |
406
|
|
|
|
|
|
|
|
407
|
0
|
|
|
|
|
|
my $bytes; |
408
|
0
|
|
|
|
|
|
sysread($pipe, $bytes, $nfound); |
409
|
0
|
|
|
|
|
|
$packet .= $bytes; |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
|
412
|
0
|
|
|
|
|
|
return _parseStatusPacket($packet); |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
# parses a given binary string as a dynamixel status packet |
416
|
|
|
|
|
|
|
sub _parseStatusPacket |
417
|
|
|
|
|
|
|
{ |
418
|
0
|
|
|
0
|
|
|
my $str = shift; |
419
|
|
|
|
|
|
|
|
420
|
0
|
0
|
|
|
|
|
my ($key) = unpack('n', substr($str, 0, 2, '')) or return; |
421
|
|
|
|
|
|
|
|
422
|
0
|
0
|
|
|
|
|
return if($key != 0xFFFF); |
423
|
|
|
|
|
|
|
|
424
|
0
|
0
|
|
|
|
|
my ($motorID, $length, $error) = unpack('C3', substr($str, 0, 3, '')) or return; |
425
|
0
|
|
|
|
|
|
my $numParameters = $length - 2; |
426
|
0
|
0
|
|
|
|
|
return if($numParameters < 0); |
427
|
|
|
|
|
|
|
|
428
|
0
|
|
|
|
|
|
my @parameters = (); |
429
|
0
|
|
|
|
|
|
my $sumParameters = 0; |
430
|
0
|
0
|
|
|
|
|
if ($numParameters) |
431
|
|
|
|
|
|
|
{ |
432
|
0
|
0
|
|
|
|
|
@parameters = unpack("C$numParameters", substr($str, 0, $numParameters, '')) or return; |
433
|
0
|
|
|
|
|
|
$sumParameters = sum(@parameters); |
434
|
|
|
|
|
|
|
} |
435
|
0
|
|
0
|
|
|
|
my $checksum = unpack('C1', substr($str, 0, 1, '')) // return; |
436
|
0
|
|
|
|
|
|
my $checksumShouldbe = ~($motorID + $length + $error + $sumParameters) & 0xFF; |
437
|
0
|
0
|
|
|
|
|
return if($checksum != $checksumShouldbe); |
438
|
|
|
|
|
|
|
|
439
|
0
|
|
|
|
|
|
return {from => $motorID, |
440
|
|
|
|
|
|
|
error => $error, |
441
|
|
|
|
|
|
|
params => \@parameters}; |
442
|
|
|
|
|
|
|
} |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=head2 moveMotorTo_deg( motorID, position_degrees ) |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
Convenience function that uses the lower-level routines to move a motor to a |
449
|
|
|
|
|
|
|
particular position |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
=cut |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
sub moveMotorTo_deg |
454
|
|
|
|
|
|
|
{ |
455
|
0
|
|
|
0
|
1
|
|
my $this = shift; |
456
|
0
|
|
|
|
|
|
my ($motorID, $position_deg) = @_; |
457
|
|
|
|
|
|
|
|
458
|
0
|
|
|
|
|
|
my $position = int( 0.5 + ($position_deg * $motorRange_coords/$motorRange_deg + 0x1ff) ); |
459
|
0
|
0
|
|
|
|
|
$position = 0 if $position < 0; |
460
|
0
|
0
|
|
|
|
|
$position = $motorRange_coords-1 if $position >= $motorRange_coords; |
461
|
0
|
|
|
|
|
|
return $this->writeMotor($motorID, $addresses{Goal_Position_L}, [unpack('C2', pack('v', $position))] ); |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
1; |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
__END__ |