File Coverage

blib/lib/HiPi/Interface/Common/MCP23X17.pm
Criterion Covered Total %
statement 15 204 7.3
branch 0 56 0.0
condition 0 5 0.0
subroutine 5 34 14.7
pod 0 25 0.0
total 20 324 6.1


line stmt bran cond sub pod time code
1             #########################################################################################
2             # Package HiPi::Interface::Common::MCP23X17
3             # Description : Base module for MCP23S17 & MCP23X17
4             # Copyright : Copyright (c) 2013-2017 Mark Dootson
5             # License : This is free software; you can redistribute it and/or modify it under
6             # the same terms as the Perl 5 programming language system itself.
7             #########################################################################################
8              
9             package HiPi::Interface::Common::MCP23X17;
10              
11             #########################################################################################
12              
13 1     1   511 use strict;
  1         6  
  1         30  
14 1     1   5 use warnings;
  1         2  
  1         25  
15 1     1   6 use parent qw( HiPi::Interface );
  1         4  
  1         6  
16 1     1   75 use HiPi qw( :rpi );
  1         2  
  1         319  
17 1     1   8 use Carp;
  1         2  
  1         2576  
18              
19             __PACKAGE__->create_accessors( qw( address devicename backend ) );
20              
21             our $VERSION ='0.81';
22              
23             our %_r_addr_map;
24              
25             sub set_address_bank {
26 0     0 0   my( $selforclass, $bank) = @_;
27 0 0         if( $bank == 1 ) {
28 0           $_r_addr_map{IODIRA} = 0x00;
29 0           $_r_addr_map{IPOLA} = 0x01;
30 0           $_r_addr_map{GPINTENA} = 0x02;
31 0           $_r_addr_map{DEFVALA} = 0x03;
32 0           $_r_addr_map{INTCONA} = 0x04;
33 0           $_r_addr_map{IOCON} = 0x05;
34 0           $_r_addr_map{GPPUA} = 0x06;
35 0           $_r_addr_map{INTFA} = 0x07;
36 0           $_r_addr_map{INTCAPA} = 0x08;
37 0           $_r_addr_map{GPIOA} = 0x09;
38 0           $_r_addr_map{OLATA} = 0x0A;
39 0           $_r_addr_map{IODIRB} = 0x10;
40 0           $_r_addr_map{IPOLB} = 0x11;
41 0           $_r_addr_map{GPINTENB} = 0x12;
42 0           $_r_addr_map{DEFVALB} = 0x13;
43 0           $_r_addr_map{INTCONB} = 0x14;
44 0           $_r_addr_map{GPPUB} = 0x16;
45 0           $_r_addr_map{INTFB} = 0x17;
46 0           $_r_addr_map{INTCAPB} = 0x18;
47 0           $_r_addr_map{GPIOB} = 0x19;
48 0           $_r_addr_map{OLATB} = 0x1A;
49             } else {
50 0           $_r_addr_map{IODIRA} = 0x00;
51 0           $_r_addr_map{IODIRB} = 0x01;
52 0           $_r_addr_map{IPOLA} = 0x02;
53 0           $_r_addr_map{IPOLB} = 0x03;
54 0           $_r_addr_map{GPINTENA} = 0x04;
55 0           $_r_addr_map{GPINTENB} = 0x05;
56 0           $_r_addr_map{DEFVALA} = 0x06;
57 0           $_r_addr_map{DEFVALB} = 0x07;
58 0           $_r_addr_map{INTCONA} = 0x08;
59 0           $_r_addr_map{INTCONB} = 0x09;
60 0           $_r_addr_map{IOCON} = 0x0A;
61 0           $_r_addr_map{GPPUA} = 0x0C;
62 0           $_r_addr_map{GPPUB} = 0x0D;
63 0           $_r_addr_map{INTFA} = 0x0E;
64 0           $_r_addr_map{INTFB} = 0x0F;
65 0           $_r_addr_map{INTCAPA} = 0x10;
66 0           $_r_addr_map{INTCAPB} = 0x11;
67 0           $_r_addr_map{GPIOA} = 0x12;
68 0           $_r_addr_map{GPIOB} = 0x13;
69 0           $_r_addr_map{OLATA} = 0x14;
70 0           $_r_addr_map{OLATB} = 0x15;
71             }
72             }
73              
74             sub new {
75 0     0 0   my ($class, %params) = @_;
76 0           my $self = $class->SUPER::new(%params);
77 0 0         my $bank = ( $params{bank} ) ? 1 : 0;
78 0           $self->set_address_bank($bank);
79 0           return $self;
80             }
81              
82             sub get_register_address {
83 0     0 0   my($self, $register) = @_;
84 0 0         croak(qq(Register $register is not recognised)) unless( exists($_r_addr_map{$register}) );
85 0           my $raddr = $_r_addr_map{$register};
86 0           return $raddr;
87             }
88              
89             sub read_register_bits {
90 0     0 0   my($self, $register, $numbytes) = @_;
91 0           my @bytes = $self->read_register_bytes($register, $numbytes);
92 0           my @bits;
93 0           while( defined(my $byte = shift @bytes )) {
94 0           my $checkbits = 0b00000001;
95 0           for( my $i = 0; $i < 8; $i++ ) {
96 0 0         my $val = ( $byte & $checkbits ) ? 1 : 0;
97 0           push( @bits, $val );
98 0           $checkbits *= 2;
99             }
100             }
101 0           return @bits;
102             }
103              
104             sub read_register_bytes {
105 0     0 0   my($self, $registername, $numbytes) = @_;
106 0   0       $numbytes ||= 1;
107 0           my $raddr = $self->get_register_address( $registername );
108 0           my @vals = $self->do_read_register_bytes($raddr, $numbytes);
109             # Check if address bank changed
110 0 0         if( $registername eq 'IOCON' ) {
111 0 0         my $bank = ( $vals[0] & 0b10000000 ) ? 1 : 0;
112 0           $self->set_address_bank($bank);
113             }
114 0           return @vals;
115             }
116              
117             sub write_register_bits {
118 0     0 0   my($self, $registername, @bits) = @_;
119 0           my $bitcount = @bits;
120 0           my $bytecount = $bitcount / 8;
121 0 0         if( $bitcount % 8 ) {
122 0           croak(qq(The number of bits $bitcount cannot be ordered into bytes));
123             }
124 0           my @bytes;
125 0           while( $bytecount ) {
126 0           my $byte = 0;
127 0           for(my $i = 0; $i < 8; $i++ ) {
128 0           my $bit = shift @bits;
129 0           $byte += ( $bit << $i );
130             }
131 0           push(@bytes, $byte);
132 0           $bytecount --;
133             }
134 0           $self->write_register_bytes($registername,@bytes);
135             }
136              
137             sub write_register_bytes {
138 0     0 0   my($self, $registername, @bytes) = @_;
139 0           my $raddr = $self->get_register_address( $registername );
140 0           my $rval = $self->do_write_register_bytes($raddr, @bytes);
141             # Check if address bank changed
142 0 0         if( $registername eq 'IOCON' ) {
143 0 0         my $bank = ( $bytes[0] & 0b10000000 ) ? 1 : 0;
144 0           $self->set_address_bank($bank);
145             }
146 0           return $rval;
147             }
148              
149             sub do_read_register_bytes {
150 0     0 0   croak 'do_read_register_bytes must be overidden in a derived class';
151             }
152              
153             sub do_write_register_bytes {
154 0     0 0   croak 'do_write_register_bytes must be overidden in a derived class';
155             }
156              
157             sub set_register_bit {
158 0     0 0   my($self, $register, $bit, $val) = @_;
159 0 0         croak qq(invalid bit or pin number $bit) unless $bit =~ /^[0-7]$/;
160 0           my ( $byte ) = $self->read_register_bytes($register, 1);
161 0           my $mask = 1 << $bit;
162 0 0         $val = ( $val ) ? 1 << $bit : 0;
163 0           $byte = ($byte & ~$mask) | $val;
164 0           $self->write_register_bytes($register, $byte );
165 0           return;
166             }
167              
168             sub get_register_bit {
169 0     0 0   my($self, $register, $bit) = @_;
170 0 0         croak qq(invalid bit or pin number $bit) unless $bit =~ /^[0-7]$/;
171 0           my ( $byte ) = $self->read_register_bytes($register, 1);
172 0           my $mask = 1 << $bit;
173 0 0         return ( $byte & $mask ) ? 1 : 0;
174             }
175              
176             sub iocon_bank {
177 0     0 0   my($self, $val) = @_;
178 0 0         if (defined($val)) {
179 0           $self->set_register_bit('IOCON', 7, $val);
180             } else {
181 0           $val = $self->get_register_bit('IOCON', 7);
182             }
183 0           return $val;
184             }
185              
186             sub iocon_mirror {
187 0     0 0   my($self, $val) = @_;
188 0 0         if (defined($val)) {
189 0           $self->set_register_bit('IOCON', 6, $val);
190             } else {
191 0           $val = $self->get_register_bit('IOCON', 6);
192             }
193 0           return $val;
194             }
195              
196             sub iocon_seqop {
197 0     0 0   my($self, $val) = @_;
198 0 0         if (defined($val)) {
199 0           $self->set_register_bit('IOCON', 5, $val);
200             } else {
201 0           $val = $self->get_register_bit('IOCON', 5);
202             }
203 0           return $val;
204             }
205              
206             sub iocon_disslw {
207 0     0 0   my($self, $val) = @_;
208 0 0         if (defined($val)) {
209 0           $self->set_register_bit('IOCON', 4, $val);
210             } else {
211 0           $val = $self->get_register_bit('IOCON', 4);
212             }
213 0           return $val;
214             }
215              
216             sub iocon_haen {
217 0     0 0   my($self, $val) = @_;
218 0 0         if (defined($val)) {
219 0           $self->set_register_bit('IOCON', 3, $val);
220             } else {
221 0           $val = $self->get_register_bit('IOCON', 3);
222             }
223 0           return $val;
224             }
225              
226             sub iocon_odr {
227 0     0 0   my($self, $val) = @_;
228 0 0         if (defined($val)) {
229 0           $self->set_register_bit('IOCON', 2, $val);
230             } else {
231 0           $val = $self->get_register_bit('IOCON', 2);
232             }
233 0           return $val;
234             }
235              
236             sub iocon_intpol {
237 0     0 0   my($self, $val) = @_;
238 0 0         if (defined($val)) {
239 0           $self->set_register_bit('IOCON', 1, $val);
240             } else {
241 0           $val = $self->get_register_bit('IOCON', 1);
242             }
243 0           return $val;
244             }
245              
246             sub _set_any_register_bit {
247 0     0     my($self, $portprefix, $port, $bit, $val) = @_;
248 0 0         croak q(invalid GPIO port $port) if $port !~ /^[a-b]$/i;
249 0           my $register = $portprefix . uc($port);
250 0           $self->set_register_bit($register, $bit, $val);
251 0           return;
252             }
253              
254             sub _get_any_register_bit {
255 0     0     my($self, $portprefix, $port, $bit) = @_;
256 0 0         croak q(invalid GPIO port $port) if $port !~ /^[a-b]$/i;
257 0           my $register = $portprefix . uc($port);
258 0           return $self->get_register_bit($register, $bit);
259             }
260              
261             sub _convert_portpin {
262 0     0     my($self, $portpin) = @_;
263 0           $portpin = uc($portpin);
264 0           my( $port, $pin ) = ( $portpin =~ /^([AB])([0-7])$/ );
265 0 0 0       if ($port && defined($pin)) {
266 0           return ( $port, $pin);
267             } else {
268 0           croak qq(invalid pin value $portpin);
269             }
270             }
271              
272             sub _standard_bit_handler {
273 0     0     my($self, $regbase, $portpin, $val) = @_;
274 0           my( $port, $pin ) = $self->_convert_portpin( $portpin );
275 0 0         if (defined($val)) {
276 0           $self->_set_any_register_bit( $regbase, $port, $pin, $val );
277             } else {
278 0           $val = $self->_get_any_register_bit( $regbase, $port, $pin );
279             }
280 0           return $val;
281             }
282              
283             # pin value has to read from GPIO but write to OLAT
284             # so do that all here
285              
286             sub pin_value {
287 0     0 0   my( $self, $portpin, $val) = @_;
288 0           my( $port, $bit ) = $self->_convert_portpin( $portpin );
289            
290 0           my $readregister = 'GPIO' . $port;
291 0           my $writeregister = 'OLAT' . $port;
292            
293 0           my ( $byte ) = $self->read_register_bytes($readregister, 1);
294 0           my $mask = 1 << $bit;
295 0 0         my $returnval = ( $byte & $mask ) ? 1 : 0;
296            
297 0 0         if (defined($val)) {
298 0 0         $val = ( $val ) ? 1 : 0;
299 0 0         if ( $val != $returnval ) {
300 0           $returnval = $val;
301 0           $byte = ($byte & ~$mask) | ( $val << $bit );
302 0           $self->write_register_bytes($writeregister, $byte );
303             }
304             }
305 0           return $returnval;
306             }
307              
308             sub pin_mode {
309 0     0 0   my( $self, $portpin, $val) = @_;
310 0           return $self->_standard_bit_handler('IODIR', $portpin, $val );
311             }
312              
313             sub pin_polarity {
314 0     0 0   my( $self, $portpin, $val) = @_;
315 0           return $self->_standard_bit_handler('IPOL', $portpin, $val );
316             }
317              
318             sub pin_interrupt_enable {
319 0     0 0   my( $self, $portpin, $val) = @_;
320 0           return $self->_standard_bit_handler('GPINTEN', $portpin, $val );
321             }
322              
323             sub pin_interrupt_default {
324 0     0 0   my( $self, $portpin, $val) = @_;
325 0           return $self->_standard_bit_handler('DEFVAL', $portpin, $val );
326             }
327              
328             sub pin_interrupt_control {
329 0     0 0   my( $self, $portpin, $val) = @_;
330 0           return $self->_standard_bit_handler('INTCON', $portpin, $val );
331             }
332              
333             sub pin_pull_up {
334 0     0 0   my( $self, $portpin, $val) = @_;
335 0           return $self->_standard_bit_handler('GPPU', $portpin, $val );
336             }
337              
338             #sub pin_interrupt_flag {
339             # my( $self, $portpin) = @_;
340             # return $self->_standard_bit_handler('INTF', $portpin );
341             #}
342             #
343             #sub pin_interrupt_capture {
344             # my( $self, $portpin) = @_;
345             # return $self->_standard_bit_handler('INTCAP', $portpin );
346             #}
347              
348              
349             1;
350              
351             __END__