File Coverage

blib/lib/Device/BusPirate/Mode/I2C.pm
Criterion Covered Total %
statement 92 92 100.0
branch 8 14 57.1
condition 4 11 36.3
subroutine 21 21 100.0
pod 8 9 88.8
total 133 147 90.4


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, 2014-2021 -- leonerd@leonerd.org.uk
5              
6             package Device::BusPirate::Mode::I2C 0.22;
7              
8 7     7   27386 use v5.14;
  7         34  
9 7     7   39 use warnings;
  7         17  
  7         295  
10 7     7   52 use base qw( Device::BusPirate::Mode );
  7         15  
  7         3770  
11              
12 7     7   51 use Carp;
  7         15  
  7         374  
13              
14 7     7   43 use Future::AsyncAwait;
  7         14  
  7         34  
15              
16 7     7   267 use constant MODE => "I2C";
  7         15  
  7         422  
17              
18 7   50 7   42 use constant PIRATE_DEBUG => $ENV{PIRATE_DEBUG} // 0;
  7         13  
  7         10215  
19              
20             =head1 NAME
21              
22             C - use C in I2C mode
23              
24             =head1 SYNOPSIS
25              
26             use Device::BusPirate;
27              
28             my $pirate = Device::BusPirate->new;
29             my $i2c = $pirate->enter_mode( "I2C" )->get;
30              
31             my $addr = 0x20;
32              
33             my $count = 0;
34             while(1) {
35             $i2c->send( $addr, chr $count )->get;
36             my $in = ord $i2c->recv( $addr, 1 )->get;
37             printf "Read %02x\n", $in;
38              
39             $count++; $count %= 255;
40             }
41              
42             =head1 DESCRIPTION
43              
44             This object is returned by a L instance when switching it
45             into C mode. It provides methods to configure the hardware, and interact
46             with one or more I2C-attached chips.
47              
48             =cut
49              
50             =head1 METHODS
51              
52             The following methods documented with a trailing call to C<< ->get >> return
53             L instances.
54              
55             =cut
56              
57             # Not to be confused with start_bit
58             async sub start
59 2     2 0 6 {
60 2         4 my $self = shift;
61              
62 2         14 await $self->_start_mode_and_await( "\x02", "I2C" );
63              
64 2         142 ( $self->{version} ) = await $self->pirate->read( 1, "I2C start" );
65              
66 2         1176 print STDERR "PIRATE I2C STARTED\n" if PIRATE_DEBUG;
67 2         9 return $self;
68             }
69              
70             =head2 configure
71              
72             $i2c->configure( %args )->get
73              
74             Change configuration options. The following options exist:
75              
76             =over 4
77              
78             =item speed
79              
80             A string giving the clock speed to use for I2C. Must be one of the values:
81              
82             5k 50k 100k 400k
83              
84             =back
85              
86             =cut
87              
88             my %SPEEDS = (
89             '5k' => 0,
90             '50k' => 1,
91             '100k' => 2,
92             '400k' => 3,
93             );
94              
95             async sub configure
96 3     3 1 989 {
97 3         16 my $self = shift;
98 3         10 my %args = @_;
99              
100 3         8 my $bytes = "";
101              
102 3 100       11 if( defined $args{speed} ) {
103 2 50       11 defined( my $speed = $SPEEDS{$args{speed}} ) or
104             croak "Unrecognised speed '$args{speed}'";
105              
106 2         9 $bytes .= chr( 0x60 | $speed );
107             }
108              
109 3         10 $self->pirate->write( $bytes );
110              
111 3         25 my $response = await $self->pirate->read( length $bytes, "I2C configure" );
112 3 50       1047 $response eq "\x01" x length $bytes or
113             die "Expected ACK response to I2C configure";
114              
115 3         15 return;
116             }
117              
118             =head2 start_bit
119              
120             $i2c->start_bit->get
121              
122             Sends an I2C START bit transition
123              
124             =cut
125              
126             sub start_bit
127             {
128 15     15 1 399 my $self = shift;
129              
130 15         21 print STDERR "PIRATE I2C START-BIT\n" if PIRATE_DEBUG;
131              
132 15         40 $self->pirate->write_expect_ack( "\x02", "I2C start_bit" );
133             }
134              
135             =head2 stop_bit
136              
137             $i2c->stop_bit->get
138              
139             Sends an I2C STOP bit transition
140              
141             =cut
142              
143             sub stop_bit
144             {
145 10     10 1 91 my $self = shift;
146              
147 10         16 print STDERR "PIRATE I2C STOP-BIT\n" if PIRATE_DEBUG;
148              
149 10         31 $self->pirate->write_expect_ack( "\x03", "I2C stop_bit" );
150             }
151              
152             =head2 write
153              
154             $i2c->write( $bytes )->get
155              
156             Sends the given bytes over the I2C wire. This method does I send a
157             preceding start or a following stop; you must do that yourself, or see the
158             C and C methods.
159              
160             =cut
161              
162             async sub write
163 15     15 1 411 {
164 15         26 my $self = shift;
165 15         33 my ( $bytes ) = @_;
166              
167 15         26 printf STDERR "PIRATE I2C WRITE %v02X\n", $bytes if PIRATE_DEBUG;
168 15         81 my @chunks = $bytes =~ m/(.{1,16})/gs;
169              
170 15         38 foreach my $bytes ( @chunks ) {
171 15         29 my $len_1 = length( $bytes ) - 1;
172              
173 15         44 my $buf = await $self->pirate->write_expect_acked_data(
174             chr( 0x10 | $len_1 ) . $bytes, length $bytes, "I2C bulk transfer"
175             );
176              
177 15         1077 $buf =~ m/^\x00*/;
178 15 50       161 $+[0] == length $bytes or
179             die "Received NACK after $+[0] bytes";
180             }
181             }
182              
183             =head2 read
184              
185             $bytes = $i2c->read( $length )->get
186              
187             Receives the given number of bytes over the I2C wire, sending an ACK bit after
188             each one but the final, to which is sent a NACK.
189              
190             =cut
191              
192             async sub read
193 7     7 1 412 {
194 7         13 my $self = shift;
195 7         14 my ( $length ) = @_;
196              
197 7         15 my $ret = "";
198              
199 7         11 print STDERR "PIRATE I2C READING $length\n" if PIRATE_DEBUG;
200              
201 7         19 foreach my $ack ( (1)x($length-1), (0) ) {
202 11         293 $self->pirate->write( "\x04" );
203              
204 11         68 $ret .= await $self->pirate->read( 1, "I2C read data" );
205              
206 11         6848 await $self->pirate->write_expect_ack( $ack ? "\x06" : "\x07", "I2C read send ACK" );
207             }
208              
209 7         479 printf STDERR "PIRATE I2C READ %v02X\n", $ret if PIRATE_DEBUG;
210 7         28 return $ret;
211             }
212              
213             # TODO: Turn this into an `async sub` without ->then chaining; though currently the
214             # ->followed_by makes that trickier
215             sub _i2c_txn
216             {
217 3     3   6 my $self = shift;
218 3         7 my ( $code ) = @_;
219              
220             $self->pirate->enter_mutex( sub {
221             $self->start_bit
222             ->then( $code )
223             ->followed_by( sub {
224 3         447 my $f = shift;
225 3         8 $self->stop_bit->then( sub { $f } );
  3         168  
226 3     3   295 });
227 3         9 });
228             }
229              
230             =head2 send
231              
232             $i2c->send( $address, $bytes )->get
233              
234             A convenient wrapper around C, C and C. This
235             method sends a START bit, then an initial byte to address the slave in WRITE
236             mode, then the remaining bytes, followed finally by a STOP bit. This is
237             performed atomically by using the C method.
238              
239             C<$address> should be an integer, in the range 0 to 0x7f.
240              
241             =cut
242              
243             sub send
244             {
245 1     1 1 995 my $self = shift;
246 1         4 my ( $address, $bytes ) = @_;
247              
248 1 50 33     8 $address >= 0 and $address < 0x80 or
249             croak "Invalid I2C slave address";
250              
251             $self->_i2c_txn( sub {
252 1     1   66 $self->write( chr( $address << 1 | 0 ) . $bytes );
253 1         9 });
254             }
255              
256             =head2 recv
257              
258             $bytes = $i2c->recv( $address, $length )->get
259              
260             A convenient wrapper around C, C, C and C.
261             This method sends a START bit, then an initial byte to address the slave in
262             READ mode, then reads the given number of bytes, followed finally by a STOP
263             bit. This is performed atomically by using the C method.
264              
265             C<$address> should be an integer, in the range 0 to 0x7f.
266              
267             =cut
268              
269             sub recv
270             {
271 1     1 1 562 my $self = shift;
272 1         4 my ( $address, $length ) = @_;
273              
274 1 50 33     10 $address >= 0 and $address < 0x80 or
275             croak "Invalid I2C slave address";
276              
277 1     1   57 $self->_i2c_txn( async sub {
278 1         6 await $self->write( chr( $address << 1 | 1 ) );
279 1         71 await $self->read( $length );
280 1         8 });
281             }
282              
283             =head2 send_then_recv
284              
285             $bytes_in = $ic->send_then_recv( $address, $bytes_out, $read_len )->get
286              
287             A convenient wrapper around C, C, C and C.
288             This method combines a C and C operation, with a repeated START
289             condition inbetween (not a STOP). It is useful when reading values from I2C
290             slaves that implement numbered registers; sending the register number as a
291             write, before requesting the read.
292              
293             C<$address> should be an integer, in the range 0 to 0x7f.
294              
295             =cut
296              
297             sub send_then_recv
298             {
299 1     1 1 1086 my $self = shift;
300 1         4 my ( $address, $bytes_out, $read_len ) = @_;
301              
302 1 50 33     8 $address >= 0 and $address < 0x80 or
303             croak "Invalid I2C slave address";
304              
305 1     1   57 $self->_i2c_txn( async sub {
306 1         8 await $self->write( chr( $address << 1 | 0 ) . $bytes_out );
307 1         79 await $self->start_bit; # repeated START
308 1         69 await $self->write( chr( $address << 1 | 1 ) );
309 1         69 await $self->read( $read_len );
310 1         8 });
311             }
312              
313             =head1 AUTHOR
314              
315             Paul Evans
316              
317             =cut
318              
319             0x55AA;