File Coverage

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