File Coverage

blib/lib/Device/Chip/TCS3472x.pm
Criterion Covered Total %
statement 101 108 93.5
branch 6 10 60.0
condition 6 9 66.6
subroutine 18 19 94.7
pod 6 10 60.0
total 137 156 87.8


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, 2020-2021 -- leonerd@leonerd.org.uk
5              
6 4     4   278074 use v5.26;
  4         48  
7 4     4   448 use Object::Pad 0.57;
  4         7481  
  4         15  
8              
9             package Device::Chip::TCS3472x 0.03;
10             class Device::Chip::TCS3472x
11 1     1   445 :isa(Device::Chip);
  1         11931  
  1         30  
12              
13 4     4   928 use Carp;
  4         7  
  4         209  
14              
15 4     4   19 use Future;
  4         6  
  4         76  
16 4     4   24 use Future::AsyncAwait;
  4         8  
  4         30  
17              
18 4     4   1687 use Data::Bitfield 0.03 qw( bitfield boolfield intfield enumfield );
  4         6620  
  4         269  
19              
20 4     4   27 use constant PROTOCOL => "I2C";
  4         7  
  4         960  
21              
22             =encoding UTF-8
23              
24             =head1 NAME
25              
26             C - chip driver for F-family
27              
28             =head1 SYNOPSIS
29              
30             use Device::Chip::TCS3472x;
31             use Future::AsyncAwait;
32              
33             my $chip = Device::Chip::TCS3472x->new;
34             await $chip->mount( Device::Chip::Adapter::...->new );
35              
36             # Power on and enable ADCs
37             await $chip->change_config(
38             PON => 1,
39             AEN => 1,
40             );
41              
42             # At default config, first sensor reading is available after
43             # 620 msec
44             sleep 0.620;
45              
46             my ( $clear, $red, $green, $blue ) = await $chip->read_crgb;
47             print "Red=$red Green=$green Blue=$blue\n";
48              
49             =head1 DESCRIPTION
50              
51             This L subclass provides specific communications to a
52             F F-family RGB light sensor chip.
53              
54             The reader is presumed to be familiar with the general operation of this chip;
55             the documentation here will not attempt to explain or define chip-specific
56             concepts or features, only the use of this module to access them.
57              
58             =cut
59              
60             =head1 MOUNT PARAMETERS
61              
62             =head2 led
63              
64             Optional name of the GPIO line attached to the LED control pin common to many
65             breakout boards. This is used by the L method.
66              
67             =cut
68              
69             has $_led_pin;
70              
71 3         6 method mount ( $adapter, %params )
  3         7  
  3         4  
  3         5  
72 3     3 1 203 {
73 3 50       11 $_led_pin = delete $params{led} if exists $params{led};
74              
75 3         21 return $self->SUPER::mount( $adapter, %params );
76             }
77              
78             sub I2C_options
79             {
80             return (
81 3     3 0 767 addr => 0x29,
82             max_bitrate => 400E3,
83             );
84             }
85              
86             use constant {
87 4         6622 COMMAND => 0x80,
88             COMMAND_AUTOINC => (1 << 5),
89              
90             REG_ENABLE => 0x00,
91             REG_ATIME => 0x01,
92             REG_WTIME => 0x03,
93             REG_AILT => 0x04, # 16bit LE
94             REG_AIHT => 0x06, # 16bit LE
95             REG_PERS => 0x0C,
96             REG_CONFIG => 0x0D,
97             REG_CONTROL => 0x0F,
98             REG_ID => 0x12,
99              
100             REG_CDATA => 0x14, # 16bit LE
101 4     4   25 };
  4         7  
102              
103             bitfield { format => "bytes-LE" }, CONFIG =>
104             # REG_ENABLE
105             AIEN => boolfield( 0*8 + 4 ),
106             WEN => boolfield( 0*8 + 3 ),
107             AEN => boolfield( 0*8 + 1 ),
108             PON => boolfield( 0*8 + 0 ),
109             # REG_ATIME
110             ATIME => intfield( 1*8, 8 ),
111             # REG_WTIME
112             WTIME => intfield( 2*8, 8 ),
113             # REG_AILT 3,4 + REG_AIHT 5,6 TODO
114             # REG_PERS
115             APERS => enumfield( 7*8 + 0,
116             qw( EVERY 1 2 3 5 10 15 20 25 30 35 40 45 50 55 60 ) ),
117             # REG_CONFIG
118             WLONG => boolfield( 8*8 + 1 ),
119             # REG_CONTROL
120             AGAIN => enumfield( 9*8 + 0, qw( 1 4 16 60 ) ),
121             ;
122              
123             =head1 METHODS
124              
125             The following methods documented in an C expression return L
126             instances.
127              
128             =cut
129              
130 6         9 async method read_reg ( $addr, $len = 1 )
  6         9  
  6         8  
  6         6  
131 6         10 {
132 6         22 return await $self->protocol->write_then_read(
133             pack( "C", COMMAND | COMMAND_AUTOINC | ( $addr & 0x1F ) ), $len
134             );
135 6     6 0 10 }
136              
137             has @_regcache;
138              
139 8         12 async method cached_read_reg ( $addr, $len = 1 )
  8         11  
  8         9  
  8         10  
140 8         26 {
141 8         9 my $ret = "";
142 8         15 my $end = $addr + $len;
143              
144 8         17 while( $addr < $end ) {
145 14 100       33 if( defined $_regcache[$addr] ) {
146 10         16 $ret .= $_regcache[$addr++];
147 10         14 next;
148             }
149              
150 4         8 $len = 1;
151 4   66     26 $len++ while $addr+$len < $end and !defined $_regcache[$addr + $len];
152              
153 4         10 my $val = await $self->read_reg( $addr, $len );
154              
155 4         1728 $ret .= $val;
156 4         28 $_regcache[$addr++] = substr( $val, 0, 1, "" ) while length $val;
157             }
158              
159 8         52 return $ret;
160 8     8 0 8941 }
161              
162 4         6 async method cached_update_reg ( $addr, $val )
  4         4  
  4         6  
  4         3  
163 4         12 {
164 4         11 while( length $val ) {
165 10 100 66     69 $addr++, substr( $val, 0, 1, "" ), next if
166             defined $_regcache[$addr] and $_regcache[$addr] eq substr( $val, 0, 1 );
167              
168 2         4 my $len = 1;
169             # TODO: CoƤless longer writes
170              
171 2         11 await $self->protocol->write(
172             pack( "C a*", COMMAND | COMMAND_AUTOINC | ( $addr & 0x1F ),
173             substr( $val, 0, $len )
174             )
175             );
176              
177 2         1211 $_regcache[$addr++] = substr( $val, 0, 1, "" ), $len--
178             while $len;
179             }
180 4     4 0 1269 }
181              
182             =head2 read_id
183              
184             $id = await $chip->read_id;
185              
186             Returns a 2-character string from the ID register. The expected value will
187             depend on the type of chip
188              
189             "44" # TCS34721 or TCS34725
190             "4D" # TCS34723 or TCS34727
191              
192             =cut
193              
194 1         1 async method read_id ()
  1         1  
195 1         3 {
196 1         4 return sprintf "%02X", unpack "C", await $self->read_reg( REG_ID );
197 1     1 1 188 }
198              
199             =head2 read_config
200              
201             $config = await $chip->read_config;
202              
203             Returns a hash reference containing the current chip configuration.
204              
205             AEN => bool
206             AIEN => bool
207             AGAIN => 1 | 4 | 16 | 60
208             APERS => "EVERY" | int
209             ATIME => int
210             PON => bool
211             WEN => bool
212             WLONG => bool
213             WTIME => int
214              
215             The returned value also contains some lowercase-named synthesized fields,
216             containing helper values derived from the chip config. These keys are not
217             supported by L.
218              
219             atime_cycles => int # number of integration cycles implied by ATIME
220             atime_msec => num # total integration time implied by ATIME
221              
222             wtime_cycles => int # number of wait cycles implied by WTIME
223             wtime_msec => num # total wait time implied by WTIME and WLONG
224              
225             =cut
226              
227 2         4 async method read_config ()
  2         2  
228 2         5 {
229 2         10 my $config = join "", await Future->needs_all(
230             $self->cached_read_reg( REG_ENABLE, 2 ), # + REG_ATIME
231             $self->cached_read_reg( REG_WTIME, 5 ), # + REG_AILT + REG_AIHT
232             $self->cached_read_reg( REG_PERS, 2 ), # + REG_CONFIG
233             $self->cached_read_reg( REG_CONTROL, 1 ),
234             );
235              
236 2         266 my %config = unpack_CONFIG( $config );
237              
238             # Some derived helper fields
239 2         247 $config{atime_cycles} = 256 - $config{ATIME};
240 2         9 $config{atime_msec} = $config{atime_cycles} * 2.4;
241              
242 2         5 $config{wtime_cycles} = 256 - $config{WTIME};
243 2         5 $config{wtime_msec} = $config{wtime_cycles} * 2.4;
244 2 50       9 $config{wtime_msec} *= 12 if $config{WLONG};
245              
246 2         13 return \%config;
247 2     2 1 247 }
248              
249             =head2 change_config
250              
251             await $chip->change_config( %changes )
252              
253             Changes the configuration. Any field names not mentioned will be preserved at
254             their existing values.
255              
256             =cut
257              
258 1         2 async method change_config ( %changes )
  1         4  
  1         2  
259 1         7 {
260 1         5 my %config = (
261             ( await $self->read_config )->%*,
262             %changes,
263             );
264              
265             # TODO: Accept changes in derived fields
266 1   66     103 m/^[a-z]/ and delete $config{$_} for keys %config;
267              
268 1         7 my $val = pack_CONFIG( %config );
269              
270 1         158 await Future->needs_all(
271             $self->cached_update_reg( REG_ENABLE, substr( $val, 0, 2, "" ) ), # + REG_ATIME
272             $self->cached_update_reg( REG_WTIME, substr( $val, 0, 5, "" ) ), # + REG_AILT + REG_AIHT
273             $self->cached_update_reg( REG_PERS, substr( $val, 0, 2, "" ) ), # + REG_CONFIG
274             $self->cached_update_reg( REG_CONTROL, substr( $val, 0, 1, "" ) ),
275             );
276 1     1 1 4276 }
277              
278             =head2 read_crgb
279              
280             ( $clear, $red, $green, $blue ) = await $chip->read_crgb
281              
282             Returns the result of the most recent colour acquisition.
283              
284             =cut
285              
286 1         2 async method read_crgb ()
  1         2  
287 1         2 {
288 1         4 return unpack "S< S< S< S<", await $self->read_reg( REG_CDATA, 8 );
289 1     1 1 194 }
290              
291             =head2 set_led
292              
293             await $chip->set_led( $on );
294              
295             If the C mount parameter was specified, this method acts as a proxy for
296             the named GPIO line, setting it high or low to control the LED.
297              
298             While not a feature of the F sensor chip itself, this is common to
299             many breakout boards, so is provided here as a convenience.
300              
301             =cut
302              
303 0           async method set_led ( $on )
  0            
  0            
304 0           {
305 0 0         defined $_led_pin or
306             croak "Cannot ->set_led unless 'led' mount parameter is defined";
307              
308 0           await $self->protocol->write_gpios( { $_led_pin => $on } );
309 0     0 1   }
310              
311             =head1 AUTHOR
312              
313             Paul Evans
314              
315             =cut
316              
317             0x55AA;