File Coverage

blib/lib/Device/MegaSquirt/Serial.pm
Criterion Covered Total %
statement 15 116 12.9
branch 0 24 0.0
condition 0 3 0.0
subroutine 5 11 45.4
pod 6 6 100.0
total 26 160 16.2


line stmt bran cond sub pod time code
1             package Device::MegaSquirt::Serial;
2              
3 1     1   6 use strict;
  1         3  
  1         35  
4 1     1   5 use warnings;
  1         3  
  1         35  
5 1     1   6 use Carp;
  1         2  
  1         59  
6              
7 1     1   2085 use Time::HiRes qw(usleep);
  1         6038  
  1         88  
8 1     1   2588 use Device::SerialPort;
  1         49937  
  1         905  
9              
10             =head1 NAME
11              
12             Device::MegaSquirt::Serial - Low level serial commands for MegaSquirt.
13              
14             =head1 SYNOPSIS
15              
16             use Device::MegaSquirt::Serial;
17             $mss = new Device::MegaSquirt::Serial('/dev/ttyUSB0');
18              
19             @data = $mss->read_r(9, 0, 0, 256);
20              
21             =head1 DESCRIPTION
22              
23             This library is used for the low level serial commands needed
24             to communicate with a MegaSquirt [2] controller.
25              
26             The only concern at this level is how the data is read and written.
27             The specific details of what the data actually represents is not
28             defined here. That should be defined at a higher level where
29             defintions are made relevant to the Megasquirt version number.
30              
31             =cut
32              
33             =head1 OPERATIONS
34              
35             =cut
36              
37             # {{{ new()
38              
39             =head2 Device::MegaSquirt::Serial->new($serial)
40              
41             Returns: defined object on success, FALSE otherwise
42              
43             The I constructor takes a single argument specifying which
44             serial device to use (e.g. '/dev/ttyUSB0').
45             If initiation of the device is successful the returned objected
46             can be used to call other functions.
47              
48             =cut
49              
50             sub new {
51 0     0 1   my $class = shift;
52 0           my $port_name = shift;
53              
54 0 0         unless (-e $port_name) {
55 0           carp "ERROR: port '$port_name' does not exist.";
56 0           return;
57             }
58              
59 0           my $serial = Device::SerialPort->new($port_name, undef, undef);
60 0 0         if (!$serial) {
61 0           carp "Can't open $port_name: $!";
62 0           return;
63             }
64              
65 0           $serial->baudrate(115200);
66 0           $serial->parity("none");
67 0           $serial->databits(8);
68 0           $serial->stopbits(1);
69 0           $serial->handshake("none");
70              
71 0           $serial->user_msg('ON');
72 0           $serial->error_msg('ON');
73              
74 0           $serial->read_const_time(200);
75              
76              
77 0           bless {serial => $serial}, $class;
78             }
79             # }}}
80              
81             # {{{ read_Q()
82              
83             =head2 $mss->read_Q()
84              
85             Returns: the Megasquirt version number, FALSE on error
86              
87             Executes the I command [1] to read the version number.
88              
89             $version = $mss->read_Q();
90              
91              
92             Q' and 'S' commands (both caps) are used to retrieve two indicators of the MS-II
93             code version. The first is the 20-byte ASCII string for the Rev Number of the
94             code version, the second is for a 32-byte Signature string. The latter is changed
95             in the code whenever a new feature is added, the first is changed in the code
96             whenever there has been an input parameter or output variable added. [1]
97              
98             =cut
99              
100             sub read_Q {
101 0     0 1   my $self = shift;
102 0           my $serial = $self->{serial};
103              
104 0           my $num_bytes = 20;
105              
106 0           my $num_write = $serial->write("Q");
107              
108 0           my ($num_read, $read) = $serial->read($num_bytes);
109              
110 0 0         if ($num_read != $num_bytes) {
111 0           carp "ERROR: $num_read bytes read but was expecting $num_bytes.";
112 0           return undef;
113             }
114              
115 0           return $read;
116             }
117              
118             # }}}
119              
120             # {{{ read_S()
121              
122             =head2 $mss->read_S()
123              
124             Returns: the Megasquirt signature, FALSE on error
125              
126             Executes the I command [1] to read the signature.
127              
128             $version = $mss->read_S();
129              
130             See also documentation of read_Q();
131              
132             =cut
133              
134             sub read_S {
135 0     0 1   my $self = shift;
136 0           my $serial = $self->{serial};
137              
138 0           my $num_bytes = 60;
139              
140 0           my $num_write = $serial->write("S");
141              
142 0           my ($num_read, $read) = $serial->read($num_bytes);
143              
144 0 0         if ($num_read != $num_bytes) {
145 0           carp "ERROR: $num_read bytes read but was expecting $num_bytes.";
146 0           return undef;
147             }
148              
149 0           return $read;
150             }
151              
152             # }}}
153              
154             # {{{ read_A() (burst mode)
155              
156             =head2 $mss->read_A($num_bytes)
157              
158             Returns: hash reference of bytes on success, FALSE on error
159              
160             Executes the I command [1] (Burst Mode) to read a frame of live variables.
161              
162             $dat = $mss->read_A($num_bytes);
163             # process $dat elsewhere
164              
165             The values of the data that is returned is dependent on the Megasquirt
166             version so the data is returned in its raw form and processing
167             must be done at a higher level.
168              
169             The size of data is also version dependent so this is given as an argument
170             so that it can it can be tested here to see if the correct amount of
171             data was read.
172              
173             =cut
174              
175             sub read_A {
176 0     0 1   my $self = shift;
177 0           my $num_bytes = shift;
178 0           my $serial = $self->{serial};
179              
180 0           my $num_write = $serial->write("A");
181              
182 0           my ($num_read, $read) = $serial->read($num_bytes);
183              
184 0 0         if ($num_read != $num_bytes) {
185 0           carp "ERROR: $num_read bytes read but was expecting $num_bytes.";
186 0           return undef;
187             }
188              
189 0           return $read;
190             }
191              
192             # }}}
193              
194             # {{{ read_r()
195              
196             =head2 $mss->read_r($tbl_idx, $offset, $num_bytes)
197              
198             Returns: array of bytes on success, FALSE on error
199              
200             Executes the I command [1] to read bytes from the controller.
201              
202             $tbl_idx - table index/offset; also called 'page'
203             $offset - offset
204             $num_bytes - the number of bytes to request
205              
206             =cut
207              
208             sub read_r {
209 0     0 1   my $self = shift;
210 0           my ($tbl_idx, $offset, $num_bytes) = @_;
211 0           my $serial = $self->{serial};
212              
213             # If the amount read/written is 0 this may be fixed by
214             # waiting and trying again.
215             # Partial reads do not appear to occure so they are
216             # not handled.
217              
218 0           my $n = 0;
219 0           my $read;
220 0           my $num_read_err = 0;
221 0           my $num_write_err = 0;
222 0           my $success = 0; # default false
223              
224 0   0       while ($num_read_err < 5 and $num_write_err < 5) {
225              
226             # 114 -> 'r'
227 0           my $to_write = pack("CCCnn", 114, 0, $tbl_idx, $offset, $num_bytes);
228 0           my $num_out = $serial->write($to_write);
229 0 0         if ($num_out != 7) {
230 0           $num_write_err++;
231 0           usleep(100000); # sleep 100 ms (mili seconds)
232 0           next;
233             }
234              
235             # 200 ms delay required when switching pages (or anytime)
236 0           usleep(200000); # sleep 200 ms (mili seconds)
237             # 200 ms -> 200000 us (micro seconds)
238              
239 0           my $num_read;
240 0           ($num_read, $read) = $serial->read($num_bytes);
241              
242 0 0         if ($num_read != $num_bytes) {
243 0           $num_read_err++;
244 0           next;
245             #return;
246             } else {
247 0           $success = 1;
248 0           last;
249             }
250             }
251              
252 0 0         if (! $success) {
253 0           carp "ERROR: unrecoverable error when using read_r (errors read/write = $num_read_err/$num_write_err)";
254 0           return;
255             }
256              
257 0           return $read;
258             }
259              
260             # }}}
261              
262             # {{{ write_w()
263              
264             =head2 $mss->write_w($tbl_idx, $offset, @bytes)
265              
266             Returns: TRUE on success, FALSE on error
267              
268             Executes the I command [1] to write bytes to the controller.
269              
270             $tbl_idx - table index/offset; also called 'page'
271             $offset - offset
272             @bytes - data bytes to be written
273              
274             It is expected that @bytes are only 1 byte chunks.
275             For example to write a two byte integer it must be broken
276             down in to 2 bytes.
277              
278             $pack = pack("n", $integer);
279             @bytes = unpack("CC", $pack);
280              
281             =cut
282              
283             sub write_w {
284 0     0 1   my $self = shift;
285 0           my ($tbl_idx, $offset, @data) = @_;
286 0           my $serial = $self->{serial};
287              
288 0           my $num_bytes = @data;
289              
290 0 0         unless ($num_bytes > 0) {
291 0           carp "no bytes to write";
292 0           return;
293             }
294              
295             # If the amount read/written is 0 this may be fixed by
296             # waiting and trying again.
297             # Partial writes are not accounted for.
298              
299 0           my $n = 0;
300 0           my $read;
301 0           my $num_write_err = 0;
302 0           my $success = 0; # default false
303              
304             # Check that the values are not too large to be packed correctly.
305 0           for (my $i = 0; $i < @data; $i++) {
306 0 0         if ($data[$i] > 255) {
307 0           carp "Data at offset $i is too large to pack, it must be less than or equal to 255.";
308 0           return;
309             }
310             }
311              
312 0           while ($num_write_err < 3) {
313              
314             # 119 -> 'w'
315 0           my $to_write = pack("CCCnnC*", 119, 0, $tbl_idx, $offset, $num_bytes, @data);
316             # If the @data values are larger than a char (C) the following error will occur
317             # Character in 'C' format wrapped in pack at line 33.
318              
319 0           my $num_out = $serial->write($to_write);
320 0 0         if ($num_out != (7 + $num_bytes)) {
321 0           carp "num_out = $num_out";
322 0           $num_write_err++;
323 0           usleep(100000); # sleep 100 ms (mili seconds)
324 0           next;
325             }
326              
327 0           $success = 1;
328 0           last;
329             }
330              
331 0 0         if (! $success) {
332 0           carp "ERROR: unrecoverable error when using write_w (errors write = $num_write_err)";
333 0           return;
334             }
335              
336 0           return 1; # success
337             }
338              
339             # }}}
340              
341             #
342             # (src)/ms2extra/is2_sci.s
343             # documents commands
344             #
345              
346              
347             =head1 PREREQUISITES
348              
349             Module Version
350             ------ -------
351             Device::SerialPort 1.04
352            
353             The version number given has been tested and shown to work.
354             Other version may also work.
355              
356             =head1 REFERENCES
357              
358             [1] RS232 communication with Megasquirt 2-Extra
359             http://home.comcast.net/~whaussmann/RS232_MS2E/RS232_MS2_E.htm
360              
361             [2] MegaSquirt Engine Management System
362             http://www.msextra.com/
363              
364             =head1 AUTHOR
365              
366             Jeremiah Mahler
367             CPAN ID: JERI
368             mailto:jmmahler@gmail.com
369             http://www.google.com/profiles/jmmahler#about
370              
371             =head1 COPYRIGHT
372              
373             Copyright (c) 2010, Jeremiah Mahler. All Rights Reserved.
374             This module is free software. It may be used, redistributed
375             and/or modified under the same terms as Perl itself.
376              
377             =head1 SEE ALSO
378              
379             Device::SerialPort
380              
381             =cut
382              
383             # vim:foldmethod=marker
384              
385             1;