File Coverage

blib/lib/Device/BusPirate/Mode/I2C.pm
Criterion Covered Total %
statement 110 110 100.0
branch 8 14 57.1
condition 4 11 36.3
subroutine 21 21 100.0
pod 8 9 88.8
total 151 165 91.5


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 7     7   24194 use v5.14;
  7         28  
7 7     7   40 use Object::Pad 0.45;
  7         111  
  7         56  
8              
9             package Device::BusPirate::Mode::I2C 0.23;
10 7     7   3778 class Device::BusPirate::Mode::I2C isa Device::BusPirate::Mode;
  7         18  
  7         276  
11              
12 7     7   1343 use Carp;
  7         14  
  7         417  
13              
14 7     7   42 use Future::AsyncAwait;
  7         17  
  7         27  
15              
16 7     7   314 use constant MODE => "I2C";
  7         13  
  7         498  
17              
18 7   50 7   52 use constant PIRATE_DEBUG => $ENV{PIRATE_DEBUG} // 0;
  7         12  
  7         16188  
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             has $_version;
58              
59             # Not to be confused with start_bit
60             async method start
61 2         6 {
62 2         14 await $self->_start_mode_and_await( "\x02", "I2C" );
63              
64 2         144 ( $_version ) = await $self->pirate->read( 1, "I2C start" );
65              
66 2         1130 print STDERR "PIRATE I2C STARTED\n" if PIRATE_DEBUG;
67 2         10 return $self;
68 2     2 0 6 }
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 3         5 async method configure ( %args )
  3         8  
  3         64  
96 3         12 {
97 3         7 my $bytes = "";
98              
99 3 100       13 if( defined $args{speed} ) {
100 2 50       12 defined( my $speed = $SPEEDS{$args{speed}} ) or
101             croak "Unrecognised speed '$args{speed}'";
102              
103 2         9 $bytes .= chr( 0x60 | $speed );
104             }
105              
106 3         54 $self->pirate->write( $bytes );
107              
108 3         25 my $response = await $self->pirate->read( length $bytes, "I2C configure" );
109 3 50       1092 $response eq "\x01" x length $bytes or
110             die "Expected ACK response to I2C configure";
111              
112 3         19 return;
113 3     3 1 1005 }
114              
115             =head2 start_bit
116              
117             $i2c->start_bit->get
118              
119             Sends an I2C START bit transition
120              
121             =cut
122              
123             method start_bit
124 15     15 1 415 {
125 15         24 print STDERR "PIRATE I2C START-BIT\n" if PIRATE_DEBUG;
126              
127 15         42 $self->pirate->write_expect_ack( "\x02", "I2C start_bit" );
128             }
129              
130             =head2 stop_bit
131              
132             $i2c->stop_bit->get
133              
134             Sends an I2C STOP bit transition
135              
136             =cut
137              
138             method stop_bit
139 10     10 1 106 {
140 10         16 print STDERR "PIRATE I2C STOP-BIT\n" if PIRATE_DEBUG;
141              
142 10         31 $self->pirate->write_expect_ack( "\x03", "I2C stop_bit" );
143             }
144              
145             =head2 write
146              
147             $i2c->write( $bytes )->get
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         23 async method write ( $bytes )
  15         29  
  15         26  
156 15         44 {
157 15         38 printf STDERR "PIRATE I2C WRITE %v02X\n", $bytes if PIRATE_DEBUG;
158 15         85 my @chunks = $bytes =~ m/(.{1,16})/gs;
159              
160 15         41 foreach my $bytes ( @chunks ) {
161 15         27 my $len_1 = length( $bytes ) - 1;
162              
163 15         46 my $buf = await $self->pirate->write_expect_acked_data(
164             chr( 0x10 | $len_1 ) . $bytes, length $bytes, "I2C bulk transfer"
165             );
166              
167 15         1096 $buf =~ m/^\x00*/;
168 15 50       128 $+[0] == length $bytes or
169             die "Received NACK after $+[0] bytes";
170             }
171 15     15 1 445 }
172              
173             =head2 read
174              
175             $bytes = $i2c->read( $length )->get
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         13 async method read ( $length )
  7         12  
  7         11  
183 7         21 {
184 7         13 my $ret = "";
185              
186 7         12 print STDERR "PIRATE I2C READING $length\n" if PIRATE_DEBUG;
187              
188 7         23 foreach my $ack ( (1)x($length-1), (0) ) {
189 11         303 $self->pirate->write( "\x04" );
190              
191 11         76 $ret .= await $self->pirate->read( 1, "I2C read data" );
192              
193 11         6564 await $self->pirate->write_expect_ack( $ack ? "\x06" : "\x07", "I2C read send ACK" );
194             }
195              
196 7         475 printf STDERR "PIRATE I2C READ %v02X\n", $ret if PIRATE_DEBUG;
197 7         26 return $ret;
198 7     7 1 428 }
199              
200             # TODO: Turn this into an `async sub` without ->then chaining; though currently the
201             # ->followed_by makes that trickier
202 3         7 method _i2c_txn ( $code )
  3         5  
  3         5  
203 3     3   11 {
204             $self->pirate->enter_mutex( sub {
205             $self->start_bit
206             ->then( $code )
207             ->followed_by( sub {
208 3         459 my $f = shift;
209 3         10 $self->stop_bit->then( sub { $f } );
  3         173  
210 3     3   308 });
211 3         10 });
212             }
213              
214             =head2 send
215              
216             $i2c->send( $address, $bytes )->get
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         3 method send ( $address, $bytes )
  1         3  
  1         2  
  1         2  
228 1     1 1 1109 {
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   70 $self->write( chr( $address << 1 | 0 ) . $bytes );
234 1         9 });
235             }
236              
237             =head2 recv
238              
239             $bytes = $i2c->recv( $address, $length )->get
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         3 method recv ( $address, $length )
  1         2  
  1         3  
  1         2  
251 1     1 1 593 {
252 1 50 33     14 $address >= 0 and $address < 0x80 or
253             croak "Invalid I2C slave address";
254              
255 1     1   58 $self->_i2c_txn( async sub {
256 1         7 await $self->write( chr( $address << 1 | 1 ) );
257 1         81 await $self->read( $length );
258 1         39 });
259             }
260              
261             =head2 send_then_recv
262              
263             $bytes_in = $ic->send_then_recv( $address, $bytes_out, $read_len )->get
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         2 method send_then_recv ( $address, $bytes_out, $read_len )
  1         2  
  1         3  
  1         3  
  1         1  
276 1     1 1 1319 {
277 1 50 33     10 $address >= 0 and $address < 0x80 or
278             croak "Invalid I2C slave address";
279              
280 1     1   60 $self->_i2c_txn( async sub {
281 1         7 await $self->write( chr( $address << 1 | 0 ) . $bytes_out );
282 1         71 await $self->start_bit; # repeated START
283 1         72 await $self->write( chr( $address << 1 | 1 ) );
284 1         163 await $self->read( $read_len );
285 1         9 });
286             }
287              
288             =head1 AUTHOR
289              
290             Paul Evans
291              
292             =cut
293              
294             0x55AA;