File Coverage

blib/lib/Device/Chip/Adapter/BusPirate.pm
Criterion Covered Total %
statement 135 250 54.0
branch 11 36 30.5
condition 6 11 54.5
subroutine 32 58 55.1
pod 0 6 0.0
total 184 361 50.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2015-2021 -- leonerd@leonerd.org.uk
5              
6 2     2   3322 use v5.14;
  2         8  
7 2     2   14 use Object::Pad 0.45;
  2         25  
  2         12  
8              
9             package Device::Chip::Adapter::BusPirate 0.23;
10             class Device::Chip::Adapter::BusPirate;
11              
12             # Can't isa Device::Chip::Adapter because it doesn't have a 'new'
13 2     2   1714 use Device::Chip::Adapter;
  2         8147  
  2         114  
14             *make_protocol = \&Device::Chip::Adapter::make_protocol;
15              
16 2     2   16 use Carp;
  2         4  
  2         118  
17              
18 2     2   12 use Future::AsyncAwait;
  2         5  
  2         9  
19 2     2   99 use Future::Mutex;
  2         4  
  2         55  
20              
21 2     2   10 use Device::BusPirate;
  2         5  
  2         2570  
22              
23             =head1 NAME
24              
25             C - a C implementation
26              
27             =head1 DESCRIPTION
28              
29             This class implements the L interface for the
30             I, allowing an instance of a L driver to communicate
31             with the actual chip hardware by using the I as a hardware
32             adapter.
33              
34             =cut
35              
36             =head1 CONSTRUCTOR
37              
38             =cut
39              
40             =head2 new
41              
42             $adapter = Device::Chip::Adapter::BusPirate->new( %args )
43              
44             Returns a new instance of a C. Takes the
45             same named arguments as L.
46              
47             =cut
48              
49             has $_bp;
50             has $_mode = undef;
51              
52             BUILD ( %args )
53             {
54             $_bp = Device::BusPirate->new( %args );
55             }
56              
57 0         0 sub new_from_description ( $class, %args )
58 0     0 0 0 {
  0         0  
  0         0  
59             # Whitelist known-OK constructor args
60 0         0 $class->new( map { $_ => $args{$_} } qw( serial baud ) );
  0         0  
61             }
62              
63             =head1 METHODS
64              
65             This module provides no new methods beyond the basic API documented in
66             L at version 0.01.
67              
68             Since version I<0.16> this module now supports multiple instances of the I2C
69             protocol, allowing multiple chips to be shared on the same bus.
70              
71             =cut
72              
73 1     1   4 sub _modename ( $mode ) { return ( ref($mode) =~ m/.*::(.*?)$/ )[0] }
  1         2  
  1         4  
  1         21  
74              
75             async method make_protocol_GPIO
76 0         0 {
77 0 0       0 $_mode and
78             croak "Cannot enter GPIO protocol when " . _modename( $_mode ) . " already active";
79              
80 0         0 $_mode = await $_bp->enter_mode( "BB" );
81              
82 0         0 await $_mode->configure( open_drain => 0 );
83              
84 0         0 return Device::Chip::Adapter::BusPirate::_GPIO->new( mode => $_mode );
85 0     0 0 0 }
86              
87             async method make_protocol_SPI
88 0         0 {
89 0 0       0 $_mode and
90             croak "Cannot enter SPI protocol when " . _modename( $_mode ) . " already active";
91              
92 0         0 $_mode = await $_bp->enter_mode( "SPI" );
93              
94 0         0 await $_mode->configure( open_drain => 0 );
95              
96 0         0 return Device::Chip::Adapter::BusPirate::_SPI->new( mode => $_mode );
97 0     0 0 0 }
98              
99             async method _enter_mode_I2C
100 2         5 {
101 2 100 66     21 return $_mode if
102             $_mode and _modename( $_mode ) eq "I2C";
103              
104 1 50       3 $_mode and
105             croak "Cannot enter I2C protocol when " . _modename( $_mode ) . " already active";
106              
107 1         6 $_mode = await $_bp->enter_mode( "I2C" );
108              
109 1         137 await $_mode->configure( open_drain => 1 );
110              
111 1         33 return $_mode;
112 2     2   4 }
113              
114             has $_mutex;
115              
116             async method make_protocol_I2C
117 2         7 {
118 2         9 my $mode = await $self->_enter_mode_I2C;
119              
120 2   66     126 $_mutex //= Future::Mutex->new;
121              
122 2         45 return Device::Chip::Adapter::BusPirate::_I2C->new( mode => $mode, mutex => $_mutex );
123 2     2 0 1250 }
124              
125             async method make_protocol_UART
126 0         0 {
127 0 0       0 $_mode and
128             croak "Cannot enter UART protocol when " . _modename( $_mode ) . " already active";
129              
130 0         0 $_mode = await $_bp->enter_mode( "UART" );
131              
132 0         0 await $_mode->configure( open_drain => 0 );
133              
134 0         0 return Device::Chip::Adapter::BusPirate::_UART->new( mode => $_mode );
135 0     0 0 0 }
136              
137             method shutdown
138 0     0 0 0 {
139 0         0 $_mode->power( 0 )->get;
140 0         0 $_bp->stop;
141             }
142              
143             class
144             Device::Chip::Adapter::BusPirate::_base {
145              
146 2     2   337 use Carp;
  2         5  
  2         171  
147 2     2   24 use List::Util qw( first );
  2         5  
  2         2703  
148              
149 15     15   38 has $_mode :reader :param;
  15         85  
150 6     6   13 has $_mutex :reader :param = undef; # only required for I2C
  6         36  
151              
152 0         0 method sleep ( $timeout )
  0         0  
  0         0  
153 0     0   0 {
154 0         0 $_mode->pirate->sleep( $timeout );
155             }
156              
157 0         0 method power ( $on )
  0         0  
  0         0  
158 0     0   0 {
159 0         0 $_mode->power( $on );
160             }
161              
162 1         3 method _find_speed ( $max_bitrate, @speeds )
  1         2  
  1         4  
  1         1  
163 1     1   3 {
164             return first {
165 2     2   5 my $rate = $_;
166 2 50       19 $rate =~ m/(.*)k$/ and $rate = 1E3 * $1;
167 2 50       8 $rate =~ m/(.*)M$/ and $rate = 1E6 * $1;
168              
169 2         10 $rate <= $max_bitrate
170 1         9 } @speeds;
171             }
172              
173             # Most modes only have access to the AUX GPIO pin
174 0     0   0 method list_gpios { return qw( AUX ) }
  0         0  
175              
176             method meta_gpios
177 0     0   0 {
178 0         0 return map { Device::Chip::Adapter::GPIODefinition( $_, "rw", 0 ) }
  0         0  
179             $self->list_gpios;
180             }
181              
182 0         0 method write_gpios ( $gpios )
  0         0  
  0         0  
183 0     0   0 {
184 0         0 foreach my $pin ( keys %$gpios ) {
185 0 0       0 $pin eq "AUX" or
186             croak "Unrecognised GPIO pin name $pin";
187              
188 0         0 return $_mode->aux( $gpios->{$pin} );
189             }
190              
191 0         0 Future->done;
192             }
193              
194 0         0 method read_gpios ( $gpios )
  0         0  
  0         0  
195 0     0   0 {
196 0         0 my @f;
197 0         0 foreach my $pin ( @$gpios ) {
198 0 0       0 $pin eq "AUX" or
199             croak "Unrecognised GPIO pin name $pin";
200              
201             return $_mode->read_aux
202 0     0   0 ->transform( done => sub { { AUX => $_[0] } } );
  0         0  
203             }
204              
205 0         0 Future->done( {} );
206             }
207              
208             # there's no more efficient way to tris_gpios than just read and ignore the result
209             async sub tris_gpios
210 0     0   0 {
211 0         0 my $self = shift;
212 0         0 await $self->read_gpios;
213 0         0 return;
214             }
215             }
216              
217             class
218             Device::Chip::Adapter::BusPirate::_GPIO isa Device::Chip::Adapter::BusPirate::_base;
219              
220 2     2   282 use List::Util 1.29 qw( pairmap );
  2         33  
  2         1212  
221              
222 0     0   0 method list_gpios { return qw( MISO CS MOSI CLK AUX ) }
  0         0  
223              
224 0         0 method write_gpios ( $gpios )
  0         0  
  0         0  
225 0     0   0 {
226             # TODO: validity checking
227             $self->mode->write(
228 0     0   0 pairmap { lc $a => $b } %$gpios
  0         0  
229             )
230             }
231              
232 0         0 async method read_gpios ( $gpios )
  0         0  
  0         0  
233 0         0 {
234 0         0 my $vals = await $self->mode->read( map { lc $_ } @$gpios );
235              
236 0     0   0 return { pairmap { uc $a => $b } %$vals };
  0         0  
237 0     0   0 }
238              
239             class
240             Device::Chip::Adapter::BusPirate::_SPI isa Device::Chip::Adapter::BusPirate::_base;
241              
242 2     2   226 use Carp;
  2         6  
  2         1281  
243              
244             my @SPI_SPEEDS = (qw( 8M 4M 2.6M 2M 1M 250k 125k 30k ));
245              
246 0         0 method configure ( %args )
  0         0  
  0         0  
247 0     0   0 {
248 0         0 my $mode = delete $args{mode};
249 0         0 my $max_bitrate = delete $args{max_bitrate};
250              
251             croak "Cannot support SPI wordsize other than 8"
252 0 0 0     0 if ( $args{wordsize} // 8 ) != 8;
253              
254 0 0       0 croak "Unrecognised configuration options: " . join( ", ", keys %args )
255             if %args;
256              
257 0 0       0 $self->mode->configure(
    0          
258             ( defined $mode ?
259             ( mode => $mode ) : () ),
260             ( defined $max_bitrate ?
261             ( speed => $self->_find_speed( $max_bitrate, @SPI_SPEEDS ) ) : () ),
262             );
263             }
264              
265 0         0 method readwrite ( $data )
  0         0  
  0         0  
266 0     0   0 {
267 0         0 $self->mode->writeread_cs( $data );
268             }
269              
270 0         0 method readwrite_no_ss ( $data )
  0         0  
  0         0  
271 0     0   0 {
272 0         0 $self->mode->writeread( $data );
273             }
274              
275             method assert_ss
276 0     0   0 {
277 0         0 $self->mode->chip_select( 0 );
278             }
279              
280             method release_ss
281 0     0   0 {
282 0         0 $self->mode->chip_select( 1 );
283             }
284              
285             class
286             Device::Chip::Adapter::BusPirate::_I2C isa Device::Chip::Adapter::BusPirate::_base;
287              
288 2     2   248 use Carp;
  2         5  
  2         2447  
289              
290             my @I2C_SPEEDS = (qw( 400k 100k 50k 5k ));
291              
292             has $_addr;
293              
294             # TODO - addr ought to be a mount option somehow
295 2         8 method configure ( %args )
  2         8  
  2         5  
296 2     2   1061 {
297 2         7 my $addr = delete $args{addr};
298 2         8 my $max_bitrate = delete $args{max_bitrate};
299              
300 2 50       7 croak "Unrecognised configuration options: " . join( ", ", keys %args )
301             if %args;
302              
303 2 50       7 $_addr = $addr if defined $addr;
304              
305 2         5 my @f;
306              
307 2 100       11 push @f, $self->mode->configure(
308             speed => $self->_find_speed( $max_bitrate, @I2C_SPEEDS )
309             ) if defined $max_bitrate;
310              
311             # It's highly likely the user will want the pullups enabled here
312 2         272 push @f, $self->mode->pullup( 1 );
313              
314 2         630 Future->needs_all( @f );
315             }
316              
317             sub DESTROY
318             {
319 2     2   1572 my $self = shift;
320 2 50       9 $self->mode->pullup( 0 )->get if $self->mode;
321             }
322              
323 3         7 async method write ( $bytes )
  3         6  
  3         6  
324 3         9 {
325 3     3   11 await $self->txn(sub { shift->write( $bytes ) });
  3         17  
326 3     3   1375 }
327              
328 1         3 async method read ( $len )
  1         2  
  1         2  
329 1         4 {
330 1     1   6 return await $self->txn(sub { shift->read( $len ) });
  1         7  
331 1     1   633 }
332              
333 1         3 async method write_then_read ( $write_bytes, $read_len )
  1         2  
  1         3  
  1         2  
334 1         4 {
335 1     1   6 return await $self->txn(async sub ( $helper ){
  1         2  
  1         3  
  1         2  
336 1         4 await $helper->write( $write_bytes );
337 1         142 return await $helper->read( $read_len );
338 1         6 });
339 1     1   1362 }
340              
341             has $_txn_helper;
342              
343 6         12 method txn ( $code )
  6         10  
  6         7  
344 6     6   1294 {
345 6 50       19 defined $_addr or
346             croak "Cannot ->txn without a defined addr";
347              
348 6   66     25 my $helper = $_txn_helper //= Device::Chip::Adapter::BusPirate::_I2C::Txn->new( mode => $self->mode, addr => $_addr );
349              
350             return $self->mutex->enter(sub {
351 6         10 return $code->( $helper )->followed_by(sub ( $f ) {
352 6         19 return $self->mode->stop_bit->then( sub { $f } );
  6         343  
353 6     6   653 });
354 6         52 });
355             }
356              
357             class
358             Device::Chip::Adapter::BusPirate::_I2C::Txn {
359              
360             has $_mode :param;
361             has $_addr :param;
362              
363 6         12 async method write ( $bytes )
  6         11  
  6         8  
364 6         20 {
365 6         22 await $_mode->start_bit;
366 6         473 await $_mode->write( chr( $_addr << 1 | 0 ) . $bytes );
367 6     6   153 }
368              
369 4         7 async method read ( $len )
  4         6  
  4         7  
370 4         13 {
371 4         12 await $_mode->start_bit;
372 4         277 await $_mode->write( chr( $_addr << 1 | 1 ) );
373 4         283 return await $_mode->read( $len );
374 4     4   280 }
375             }
376              
377             class
378             Device::Chip::Adapter::BusPirate::_UART isa Device::Chip::Adapter::BusPirate::_base;
379              
380 2     2   1274 use Carp;
  2         7  
  2         907  
381              
382 0           method configure ( %args )
  0            
  0            
383 0     0     {
384             return $self->mode->configure(
385             baud => $args{baudrate},
386             bits => $args{bits},
387             parity => $args{parity},
388             stop => $args{stop},
389 0           );
390             }
391              
392 0           method write ( $bytes )
  0            
  0            
393 0     0     {
394 0           return $self->mode->write( $bytes );
395             }
396              
397 0     0     method read { croak "Device::BusPirate does not support read on UART" }
  0            
398              
399             =head1 AUTHOR
400              
401             Paul Evans
402              
403             =cut
404              
405             0x55AA;