File Coverage

blib/lib/Device/Chip/TSL256x.pm
Criterion Covered Total %
statement 128 132 96.9
branch 20 30 66.6
condition 12 21 57.1
subroutine 23 23 100.0
pod 9 11 81.8
total 192 217 88.4


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