File Coverage

blib/lib/Device/Chip/TSL256x.pm
Criterion Covered Total %
statement 131 135 97.0
branch 20 30 66.6
condition 12 21 57.1
subroutine 24 24 100.0
pod 9 11 81.8
total 196 221 88.6


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, 2016-2023 -- leonerd@leonerd.org.uk
5              
6 8     8   2259297 use v5.26;
  8         94  
7 8     8   44 use warnings;
  8         19  
  8         306  
8 8     8   737 use Object::Pad 0.800;
  8         11288  
  8         408  
9              
10             package Device::Chip::TSL256x 0.09;
11             class Device::Chip::TSL256x
12 1     1   647 :isa(Device::Chip);
  1         17642  
  1         45  
13              
14 8     8   6629 use Device::Chip::Sensor -declare;
  8         25916  
  8         46  
15              
16 8     8   5033 use Data::Bitfield qw( bitfield enumfield );
  8         18300  
  8         704  
17 8     8   67 use Future;
  8         18  
  8         408  
18 8     8   56 use Future::AsyncAwait;
  8         22  
  8         46  
19              
20 8     8   476 use constant PROTOCOL => "I2C";
  8         15  
  8         1716  
21              
22             =encoding UTF-8
23              
24             =head1 NAME
25              
26             C - chip driver for F
27              
28             =head1 SYNOPSIS
29              
30             use Device::Chip::TSL256x;
31             use Future::AsyncAwait;
32              
33             my $chip = Device::Chip::TSL256x->new;
34             await $chip->mount( Device::Chip::Adapter::...->new );
35              
36             await $chip->power(1);
37              
38             sleep 1; # Wait for one integration cycle
39              
40             printf "Current ambient light level is %.2f lux\n",
41             scalar await $chip->read_lux;
42              
43             =head1 DESCRIPTION
44              
45             This L subclass provides specific communication to a F
46             F or F attached to a computer via an I²C adapter.
47              
48             The reader is presumed to be familiar with the general operation of this chip;
49             the documentation here will not attempt to explain or define chip-specific
50             concepts or features, only the use of this module to access them.
51              
52             =cut
53              
54             method I2C_options
55 7     7 0 3316 {
56             return (
57 7         57 addr => 0x39,
58             max_bitrate => 100E3,
59             );
60             }
61              
62             use constant {
63             # Mask bits for the command byte
64 8         1033 CMD_MASK => 1<<7,
65             CMD_CLEAR => 1<<6,
66             CMD_WORD => 1<<5,
67             CMD_BLOCK => 1<<4,
68 8     8   73 };
  8         19  
69              
70             use constant {
71 8         27987 REG_CONTROL => 0x00,
72             REG_TIMING => 0x01,
73             REG_THRESHLOW => 0x02, # 16bit
74             REG_THRESHHIGH => 0x04, # 16bit
75             REG_INTERRUPT => 0x06,
76             REG_ID => 0x0A,
77             REG_DATA0 => 0x0C, # 16bit
78             REG_DATA1 => 0x0E, # 16bit
79 8     8   73 };
  8         23  
80              
81             bitfield { format => "bytes-LE" }, CONFIG =>
82             POWER => enumfield( 0, qw( OFF . . ON )),
83             GAIN => enumfield( 1*8+4, qw( 1 16 )),
84             INTEG => enumfield( 1*8+0, qw( 13ms 101ms 402ms ));
85              
86 25         37 async method _read ( $addr, $len )
  25         47  
  25         37  
  25         47  
87 25         59 {
88 25         95 return await $self->protocol->write_then_read(
89             ( pack "C", CMD_MASK | ( $addr & 0x0f ) ), $len
90             );
91 25     25   28969 }
92              
93 5         34 async method _write ( $addr, $data )
  5         12  
  5         22  
  5         10  
94 5         17 {
95 5         23 await $self->protocol->write(
96             pack "C a*", CMD_MASK | ( $addr & 0x0f ), $data
97             );
98 5     5   13 }
99              
100             =head1 ACCESSORS
101              
102             The following methods documented in an C expression return L
103             instances.
104              
105             =cut
106              
107             =head2 read_config
108              
109             $config = await $chip->read_config;
110              
111             Returns a C reference of the contents of control and timing registers,
112             using fields named from the data sheet.
113              
114             POWER => OFF | ON
115             GAIN => 1 | 16
116             INTEG => 13ms | 101ms | 402ms
117              
118             Additionally, the following keys are provided calculated from those, as a
119             convenience.
120              
121             integ_msec => 13.7 | 101 | 402
122              
123             =head2 change_config
124              
125             await $chip->change_config( %changes );
126              
127             Writes updates to the control and timing registers.
128              
129             Note that this method will ignore the C convenience value.
130              
131             Note that these two methods use a cache of configuration bytes to make
132             subsequent modifications more efficient. This cache will not respect the
133             "one-shot" nature of the C bit.
134              
135             =cut
136              
137             field $_CONTROLbyte;
138             field $_TIMINGbyte;
139              
140             my %INTEG_to_msec = (
141             '13ms' => 13.7,
142             '101ms' => 101,
143             '402ms' => 402,
144             );
145              
146 22         39 async method read_config ()
  22         30  
147 22         71 {
148 22   66     192 my %config = unpack_CONFIG( pack "a1 a1",
      66        
149             $_CONTROLbyte //= await $self->_read( REG_CONTROL, 1 ),
150             $_TIMINGbyte //= await $self->_read( REG_TIMING, 1 ),
151             );
152              
153 22         13327 $config{integ_msec} = $INTEG_to_msec{ $config{INTEG} };
154              
155 22         203 return \%config;
156 22     22 1 34756 }
157              
158 3         7 async method change_config ( %changes )
  3         9  
  3         4  
159 3         13 {
160 3         10 my $config = await $self->read_config;
161              
162 3         171 $config->{$_} = $changes{$_} for keys %changes;
163              
164 3         9 delete $config->{integ_msec};
165              
166 3         17 my ( $CONTROL, $TIMING ) = unpack "a1 a1", pack_CONFIG( %$config );
167              
168 3 50       344 if( $CONTROL ne $_CONTROLbyte ) {
169 0         0 await $self->_write( REG_CONTROL, $_CONTROLbyte = $CONTROL );
170             }
171 3 50       10 if( $TIMING ne $_TIMINGbyte ) {
172 3         14 await $self->_write( REG_TIMING, $_TIMINGbyte = $TIMING );
173             }
174 3     3 1 4672 }
175              
176 1         2 async method initialize_sensors ()
  1         2  
177 1         3 {
178 1         4 await $self->power( 1 );
179              
180 1         9568 $self->enable_agc( 1 );
181              
182             # Wait for one integration cycle
183 1         5 await $self->protocol->sleep( ( await $self->read_config )->{integ_msec} / 1000 );
184 1     1 0 406 }
185              
186             =head2 enable_agc
187              
188             $chip->enable_agc( $agc )
189              
190             Accessor for the internal gain-control algorithm. If enabled, the C
191             configuration will be automatically controlled to switch between high- and
192             low-level settings.
193              
194             =cut
195              
196             field $_agc_enabled;
197              
198 2         8 method enable_agc ( $agc )
  2         5  
  2         3  
199 2     2 1 245 {
200 2         5 $_agc_enabled = $agc;
201             }
202              
203             =head2 read_id
204              
205             $id = await $chip->read_id;
206              
207             Returns the chip's ID register value.
208              
209             =cut
210              
211 1         3 async method read_id ()
  1         2  
212 1         3 {
213 1         5 return unpack "C", await $self->_read( REG_ID, 1 );
214 1     1 1 404 }
215              
216             =head2 read_data0
217              
218             =head2 read_data1
219              
220             $data0 = await $chip->read_data0;
221              
222             $data1 = await $chip->read_data1;
223              
224             Reads the current values of the ADC channels.
225              
226             =cut
227              
228 1         2 async method read_data0 ()
  1         2  
229 1         3 {
230 1         6 return unpack "S<", await $self->_read( REG_DATA0, 2 );
231 1     1 1 377 }
232              
233 1         6 async method read_data1 ()
  1         2  
234 1         3 {
235 1         6 return unpack "S<", await $self->_read( REG_DATA1, 2 );
236 1     1 1 15780 }
237              
238             =head2 read_data
239              
240             ( $data0, $data1 ) = await $chip->read_data;
241              
242             Read the current values of both ADC channels in a single I²C transaction.
243              
244             =cut
245              
246 11         22 async method read_data ()
  11         17  
247 11         26 {
248 11         36 return unpack "S< S<", await $self->_read( REG_DATA0, 4 );
249 11     11 1 5358 }
250              
251             =head1 METHODS
252              
253             =cut
254              
255             =head2 power
256              
257             await $chip->power( $on );
258              
259             Enables or disables the main power control bits in the C register.
260              
261             =cut
262              
263 2         4 async method power ( $on )
  2         5  
  2         4  
264 2         6 {
265 2         12 await $self->_write( REG_CONTROL, $_CONTROLbyte = ( $on ? "\x03" : "\x00" ) );
266 2     2 1 289 }
267              
268             declare_sensor light =>
269             method => "read_lux",
270             units => "lux",
271             precision => 2;
272              
273             =head2 read_lux
274              
275             $lux = await $chip->read_lux;
276              
277             ( $lux, $data0, $data1 ) = await $chip->read_lux;
278              
279             Reads the two data registers then performs the appropriate scaling
280             calculations to return a floating-point number that approximates the light
281             level in Lux.
282              
283             Currently this conversion code presumes the contants for the T, FN and CL
284             chip types.
285              
286             In list context, also returns the raw C<$data0> and C<$data1> channel values.
287             The controlling code may wish to use these to adjust the gain if required.
288              
289             =cut
290              
291             field $_smallcount;
292              
293 10         23 async method read_lux ()
  10         14  
294 10         33 {
295 10         28 my ( $data0, $data1, $config ) = await Future->needs_all(
296             $self->read_data,
297             $self->read_config,
298             );
299              
300 10         4934 my $gain = $config->{GAIN};
301 10         22 my $msec = $config->{integ_msec};
302              
303 10         42 my $ch0 = $data0 * ( 16 / $gain ) * ( 402 / $msec );
304 10         36 my $ch1 = $data1 * ( 16 / $gain ) * ( 402 / $msec );
305              
306 10         20 my $lux = 0;
307              
308 10 50       31 if( $ch0 != 0 ) {
309 10         21 my $ratio = $ch1 / $ch0;
310              
311             # TODO: take account of differing package types.
312              
313 10 100       39 if( $ratio <= 0.52 ) {
    50          
    50          
    50          
314 6         40 $lux = 0.0304 * $ch0 - 0.062 * $ch0 * ( $ratio ** 1.4 );
315             }
316             elsif( $ratio <= 0.65 ) {
317 0         0 $lux = 0.0224 * $ch0 - 0.031 * $ch1;
318             }
319             elsif( $ratio <= 0.80 ) {
320 0         0 $lux = 0.0128 * $ch0 - 0.0153 * $ch1;
321             }
322             elsif( $ratio <= 1.30 ) {
323 4         9 $lux = 0.00146 * $ch0 - 0.00112 * $ch1;
324             }
325              
326 10 50       27 my $saturation = ( $msec == 402 ) ? 0xFFFF :
    100          
327             ( $msec == 101 ) ? 0x9139 : 0x13B7;
328              
329             # Detect sensor saturation
330 10 50 33     61 if( $data0 == $saturation or $data1 == $saturation ) {
331             # The sensor saturates at well under 50klux
332 0         0 $lux = 50_000;
333             }
334             }
335              
336 10 100       29 if( $_agc_enabled ) {
337 6 100 66     34 if( $gain == 1 and $data0 < 0x0800 and $data1 < 0x0800 ) {
      66        
338 5         125 $_smallcount++;
339 5 100       23 await $self->change_config( GAIN => 16 ) if $_smallcount >= 4;
340             }
341             else {
342 1         4 $_smallcount = 0;
343             }
344              
345 6 50 33     1706 if( $gain == 16 and ( $data0 > 0x8000 or $data1 > 0x8000 ) ) {
      66        
346 1         13 await $self->change_config( GAIN => 1 );
347             }
348             }
349              
350 10 50       1621 return $lux if !wantarray;
351 10         65 return $lux, $data0, $data1;
352 10     10 1 16536 }
353              
354             =head1 AUTHOR
355              
356             Paul Evans
357              
358             =cut
359              
360             0x55AA;