File Coverage

blib/lib/Device/Chip/Adapter/BusPirate.pm
Criterion Covered Total %
statement 157 229 68.5
branch 16 36 44.4
condition 7 11 63.6
subroutine 37 52 71.1
pod 0 5 0.0
total 217 333 65.1


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