File Coverage

blib/lib/Device/Chip/TCS3472x.pm
Criterion Covered Total %
statement 104 111 93.6
branch 6 10 60.0
condition 6 9 66.6
subroutine 19 20 95.0
pod 6 10 60.0
total 141 160 88.1


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