File Coverage

blib/lib/Device/Chip/MAX11200.pm
Criterion Covered Total %
statement 178 202 88.1
branch 10 18 55.5
condition 3 9 33.3
subroutine 35 39 89.7
pod 14 18 77.7
total 240 286 83.9


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, 2017-2022 -- leonerd@leonerd.org.uk
5              
6 2     2   111747 use v5.26;
  2         16  
7 2     2   10 use Object::Pad 0.57;
  2         22  
  2         10  
8              
9             package Device::Chip::MAX11200 0.13;
10             class Device::Chip::MAX11200
11             :isa(Device::Chip);
12              
13 2     2   558 use Carp;
  2         4  
  2         139  
14 2     2   13 use Future::AsyncAwait;
  2         4  
  2         10  
15 2     2   1004 use Future::IO;
  2         20243  
  2         160  
16 2     2   559 use Data::Bitfield 0.02 qw( bitfield boolfield enumfield );
  2         2045  
  2         139  
17 2     2   17 use List::Util qw( first );
  2         4  
  2         165  
18              
19 2     2   13 use constant PROTOCOL => "SPI";
  2         3  
  2         645  
20              
21             =head1 NAME
22              
23             C - chip driver for F
24              
25             =head1 SYNOPSIS
26              
27             use Device::Chip::MAX11200;
28             use Future::AsyncAwait;
29              
30             my $chip = Device::Chip::MAX11200->new;
31             await $chip->mount( Device::Chip::Adapter::...->new );
32              
33             await $chip->trigger;
34              
35             printf "The reading is %d\n", await $chip->read_adc;
36              
37             =head1 DESCRIPTION
38              
39             This L subclass provides specific communications to a F
40             F or F chip.
41              
42             The reader is presumed to be familiar with the general operation of this chip;
43             the documentation here will not attempt to explain or define chip-specific
44             concepts or features, only the use of this module to access them.
45              
46             =cut
47              
48             sub SPI_options
49             {
50             return (
51 1     1 0 305 mode => 0,
52             max_bitrate => 2E6,
53             );
54             }
55              
56             =head1 METHODS
57              
58             The following methods documented in an C expression return L
59             instances.
60              
61             =cut
62              
63             =head2 init
64              
65             await $chip->init;
66              
67             Performs startup self-calibration by setting C and C to zero
68             then requesting a calibration cycle.
69              
70             =cut
71              
72 0         0 async method init ()
  0         0  
73 0         0 {
74 0         0 await $self->change_config(
75             NOSCG => 0,
76             NOSCO => 0,
77             );
78              
79 0         0 await $self->selfcal;
80              
81 0         0 await Future::IO->sleep( 0.25 ); # selfcal takes 200msec (longer at LINEF=50Hz)
82 0     0 1 0 }
83              
84             use constant {
85 2         882 REG_STAT => 0,
86             REG_CTRL1 => 1,
87             REG_CTRL2 => 2,
88             REG_CTRL3 => 3,
89             REG_DATA => 4,
90             REG_SOC => 5,
91             REG_SGC => 6,
92             REG_SCOC => 7,
93             REG_SCGC => 8,
94 2     2   22 };
  2         4  
95              
96 11         20 async method read_register ( $reg, $len = 1 )
  11         20  
  11         19  
  11         18  
97 11         23 {
98 11         42 my $bytes = await $self->protocol->readwrite(
99             pack "C a*", 0xC1 | ( $reg << 1 ), "\0" x $len
100             );
101              
102 11         17863 return substr $bytes, 1;
103 11     11 0 1259 }
104              
105 6         9 async method write_register ( $reg, $val )
  6         10  
  6         13  
  6         10  
106 6         14 {
107 6         25 await $self->protocol->write(
108             pack "C a*", 0xC0 | ( $reg << 1 ), $val
109             );
110 6     6 0 1132 }
111              
112             my @RATES = qw( 1 2.5 5 10 15 30 60 120 );
113              
114             use constant {
115 2         4075 CMD_SELFCAL => 0x10,
116             CMD_SYSOCAL => 0x20,
117             CMD_SYSGCAL => 0x30,
118             CMD_POWERDOWN => 0x08,
119             CMD_CONV => 0,
120 2     2   13 };
  2         4  
121              
122 3         5 async method command ( $cmd )
  3         5  
  3         4  
123 3         8 {
124 3         16 await $self->protocol->write( pack "C", 0x80 | $cmd );
125 3     3 0 6 }
126              
127             =head2 read_status
128              
129             $status = await $chip->read_status;
130              
131             Returns a C reference containing the chip's current status.
132              
133             RDY => 0 | 1
134             MSTAT => 0 | 1
135             UR => 0 | 1
136             OR => 0 | 1
137             RATE => 1 | 2.5 | 5 | 10 | 15 | 30 | 60 | 120
138             SYSOR => 0 | 1
139              
140             =cut
141              
142             bitfield { format => "bytes-LE" }, STAT =>
143             RDY => boolfield(0),
144             MSTAT => boolfield(1),
145             UR => boolfield(2),
146             OR => boolfield(3),
147             RATE => enumfield(4, @RATES),
148             SYSOR => boolfield(7);
149              
150 1         2 async method read_status ()
  1         1  
151 1         3 {
152 1         4 my $bytes = await $self->read_register( REG_STAT );
153              
154 1         57 return unpack_STAT( $bytes );
155 1     1 1 228 }
156              
157             =head2 read_config
158              
159             $config = await $chip->read_config;
160              
161             Returns a C reference containing the chip's current configuration.
162              
163             SCYCLE => 0 | 1
164             FORMAT => "TWOS_COMP" | "OFFSET"
165             SIGBUF => 0 | 1
166             REFBUF => 0 | 1
167             EXTCLK => 0 | 1
168             UB => "UNIPOLAR" | "BIPOLAR"
169             LINEF => "60Hz" | "50Hz"
170             NOSCO => 0 | 1
171             NOSCG => 0 | 1
172             NOSYSO => 0 | 1
173             NOSYSG => 0 | 1
174             DGAIN => 1 2 4 8 16 # only valid for the MAX11210
175              
176             =cut
177              
178             bitfield { format => "bytes-LE" }, CONFIG =>
179             # CTRL1
180             SCYCLE => boolfield(1),
181             FORMAT => enumfield(2, qw( TWOS_COMP OFFSET )),
182             SIGBUF => boolfield(3),
183             REFBUF => boolfield(4),
184             EXTCLK => boolfield(5),
185             UB => enumfield(6, qw( BIPOLAR UNIPOLAR )),
186             LINEF => enumfield(7, qw( 60Hz 50Hz )),
187             # CTRL2 is all GPIO control; we'll do that elsewhere
188             # CTRL3
189             NOSCO => boolfield(8+1),
190             NOSCG => boolfield(8+2),
191             NOSYSO => boolfield(8+3),
192             NOSYSG => boolfield(8+4),
193             DGAIN => enumfield(8+5, qw( 1 2 4 8 16 ));
194              
195 1         2 async method read_config ()
  1         2  
196 1         6 {
197 1         4 my ( $ctrl1, $ctrl3 ) = await Future->needs_all(
198             $self->read_register( REG_CTRL1 ), $self->read_register( REG_CTRL3 )
199             );
200              
201 1         133 return $self->{config} = { unpack_CONFIG( $ctrl1 . $ctrl3 ) };
202 1     1 1 3326 }
203              
204             =head2 change_config
205              
206             await $chip->change_config( %changes );
207              
208             Changes the configuration. Any field names not mentioned will be preserved at
209             their existing values.
210              
211             =cut
212              
213 1         3 async method change_config ( %changes )
  1         3  
  1         2  
214 1         4 {
215 1   33     4 my $config = $self->{config} // await $self->read_config;
216              
217 1         13 $self->{config} = { %$config, %changes };
218 1         2 my $ctrlb = pack_CONFIG( %{ $self->{config} } );
  1         9  
219              
220 1         228 await Future->needs_all(
221             $self->write_register( REG_CTRL1, substr $ctrlb, 0, 1 ),
222             $self->write_register( REG_CTRL3, substr $ctrlb, 1, 1 ),
223             );
224 1     1 1 3537 }
225              
226             =head2 selfcal
227              
228             await $chip->selfcal;
229              
230             Requests the chip perform a self-calibration.
231              
232             =cut
233              
234 1         2 async method selfcal ()
  1         44  
235 1         6 {
236 1         6 await $self->command( CMD_SELFCAL );
237 1     1 1 3248 }
238              
239             =head2 syscal_offset
240              
241             await $chip->syscal_offset;
242              
243             Requests the chip perform the offset part of system calibration.
244              
245             =cut
246              
247 0         0 async method syscal_offset ()
  0         0  
248 0         0 {
249 0         0 await $self->command( CMD_SYSOCAL );
250 0     0 1 0 }
251              
252             =head2 syscal_gain
253              
254             await $chip->syscal_gain;
255              
256             Requests the chip perform the gain part of system calibration.
257              
258             =cut
259              
260 0         0 async method syscal_gain ()
  0         0  
261 0         0 {
262 0         0 await $self->command( CMD_SYSGCAL );
263 0     0 1 0 }
264              
265             =head2 trigger
266              
267             await $chip->trigger( $rate );
268              
269             Requests the chip perform a conversion of the input level, at the given
270             rate (which must be one of the values specified for the C configuration
271             option); defaulting to the value of L if not defined.
272             Once the conversion is complete it can be read using the C method.
273              
274             =head2 default_trigger_rate
275              
276             $rate = $chip->default_trigger_rate
277             $chip->default_trigger_rate = $new_rate
278              
279             Lvalue accessor for the default trigger rate if L is invoked without
280             one. Initialised to 120.
281              
282             =cut
283              
284             has $_default_trigger_rate = 120;
285 1     1 1 3311 method default_trigger_rate :lvalue { $_default_trigger_rate }
  1         3  
286              
287 2         5 async method trigger ( $rate = $_default_trigger_rate )
  2         4  
  2         4  
288 2         7 {
289 14 50   14   30 defined( my $rateidx = first { $RATES[$_] == $rate } 0 .. $#RATES )
  2         18  
290             or croak "Unrecognised conversion rate $rate";
291              
292 2         14 await $self->command( CMD_CONV | $rateidx );
293 2     2 1 4107 }
294              
295             =head2 read_adc
296              
297             $value = await $chip->read_adc;
298              
299             Reads the most recent reading from the result register on the tip. This method
300             should be called after a suitable delay after the L method when in
301             single cycle mode, or at any time when in continuous mode.
302              
303             The reading is returned directly from the chip as a plain 24-bit integer,
304             either signed or unsigned as per the C configuration.
305              
306             =cut
307              
308 4         9 async method read_adc ()
  4         4  
309 4         13 {
310 4         15 my $bytes = await $self->read_register( REG_DATA, 3 );
311              
312 4         219 return unpack "L>", "\0$bytes";
313 4     4 1 6900 }
314              
315             =head2 read_adc_ratio
316              
317             $ratio = await $chip->read_adc_ratio;
318              
319             Converts a reading obtained by L into a ratio between -1 and 1,
320             taking into account the current mode setting of the chip.
321              
322             =cut
323              
324 1         2 async method read_adc_ratio ()
  1         2  
325 1         5 {
326             my ( $value, $config ) = await Future->needs_all(
327             $self->read_adc,
328 1         4 ( $self->{config} ? Future->done( $self->{config} ) : $self->read_config )
329             );
330              
331 1 50       98 if( $config->{UB} eq "UNIPOLAR" ) {
332             # Raw 24bit integer
333 0         0 return $value / 2**24;
334             }
335             else {
336 1 50       4 if( $config->{FORMAT} eq "TWOS_COMP" ) {
337             # Signed integer in twos-complement form
338 1 50       5 $value -= 2**24 if $value >= 2**23;
339             }
340             else {
341             # Signed-integer in offset form
342 0         0 $value -= 2**23;
343             }
344 1         6 return $value / 2**23;
345             }
346 1     1 1 3205 }
347              
348             =head2 write_gpios
349              
350             await $chip->write_gpios( $values, $direction );
351              
352             =head2 read_gpios
353              
354             $values = await $chip->read_gpios;
355              
356             Sets or reads the values of the GPIO pins as a 4-bit integer. Bits in the
357             C<$direction> should be high to put the corresponding pin into output mode, or
358             low to put it into input mode.
359              
360             As an alternative to these methods, see instead L.
361              
362             =cut
363              
364 3         7 async method write_gpios ( $values, $dir )
  3         5  
  3         5  
  3         6  
365 3         10 {
366 3         23 await $self->write_register( REG_CTRL2, pack "C", ( $dir << 4 ) | $values );
367 3     3 1 2846 }
368              
369 3         6 async method read_gpios ()
  3         6  
370 3         11 {
371 3         10 my $bytes = await $self->read_register( REG_CTRL2 );
372              
373 3         158 return 0x0F & unpack "C", $bytes;
374 3     3 1 2507 }
375              
376             =head2 Calibration Registers
377              
378             $value = await $chip->read_selfcal_offset;
379             $value = await $chip->read_selfcal_gain;
380             $value = await $chip->read_syscal_offset;
381             $value = await $chip->read_syscal_gain;
382              
383             await $chip->write_selfcal_offset( $value );
384             await $chip->write_selfcal_gain( $value );
385             await $chip->write_syscal_offset( $value );
386             await $chip->write_syscal_gain( $value );
387              
388             Reads or writes the values of the calibration registers, as plain 24-bit
389             integers.
390              
391             =cut
392              
393             foreach (
394             [ "selfcal_offset", REG_SCOC ],
395             [ "selfcal_gain", REG_SCGC ],
396             [ "syscal_offset", REG_SOC ],
397             [ "syscal_gain", REG_SGC ],
398             ) {
399             my ( $name, $reg ) = @$_;
400              
401 2     2   17 no strict 'refs';
  2         5  
  2         855  
402              
403 1         7 *{"read_$name"} = async method () {
  1         2  
  1         2  
404 1         5 my $bytes = await $self->read_register( $reg, 3 );
405 1         56 return unpack "I>", "\0" . $bytes;
406 1     1   1388 };
407              
408 1         3 *{"write_$name"} = async method ( $value ) {
  1         2  
  1         2  
  1         2  
409 1         8 await $self->write_register( $reg,
410             substr( pack( "I>", $value ), 1 )
411             );
412 1     1   734 };
413             }
414              
415             =head2 as_gpio_adapter
416              
417             $adapter = $chip->as_gpio_adapter
418              
419             Returns an instance implementing the L interface,
420             allowing access to the four GPIO pins via the standard adapter API.
421              
422             =cut
423              
424             method as_gpio_adapter
425 1     1 1 721 {
426 1         51 return Device::Chip::MAX11200::_GPIOAdapter->new( chip => $self );
427             }
428              
429             class Device::Chip::MAX11200::_GPIOAdapter {
430 2     2   260 use Carp;
  2         4  
  2         2436  
431              
432             has $_chip :param;
433              
434 1         2 async method make_protocol ( $pname )
  1         2  
  1         2  
435 1         2 {
436 1 50       5 $pname eq "GPIO" or
437             croak "Unrecognised protocol name $pname";
438              
439 1         12 return $self;
440 1     1   3 }
441              
442 1     1   37 method list_gpios { qw( GPIO1 GPIO2 GPIO3 GPIO4 ) }
  1         7  
443              
444             method meta_gpios
445 0     0   0 {
446             return map {
447 0         0 Device::Chip::Adapter::GPIODefinition( $_, "rw", 0 )
  0         0  
448             } $self->list_gpios;
449             }
450              
451             has $_dir = "";
452             has $_val = "";
453              
454 2         4 async method write_gpios ( $values )
  2         4  
  2         4  
455 2         7 {
456 2         5 foreach my $n ( 1 .. 4 ) {
457 8 100       26 defined( my $v = $values->{"GPIO$n"} ) or next;
458              
459 2         9 vec( $_dir, $n-1, 1 ) = 1;
460 2         7 vec( $_val, $n-1, 1 ) = $v;
461             }
462              
463 2         9 await $_chip->write_gpios( ord $_val, ord $_dir );
464 2     2   4363 }
465              
466 1         2 async method tris_gpios ( $pins )
  1         2  
  1         2  
467 1         2 {
468 1         2 my $newdir = $_dir;
469 1         3 foreach my $pin ( @$pins ) {
470 1 50 33     16 $pin =~ m/^GPIO(\d)/ and $1 >= 1 and $1 <= 4 or
      33        
471             croak "Unrecognised GPIO pin name $pin";
472 1         3 my $n = $1;
473              
474 1         5 vec( $newdir, $n-1, 1 ) = 0;
475             }
476              
477 1 50       17 if( $newdir ne $_dir ) {
478 0         0 $_dir = $newdir;
479 0         0 await $_chip->write_gpios( ord $_val, ord $_dir );
480             }
481 1     1   3 }
482              
483 1         3 async method read_gpios ( $pins )
  1         1  
  1         2  
484 1         4 {
485 1         4 await $self->tris_gpios( $pins );
486              
487 1         48 my $read = chr await $_chip->read_gpios;
488              
489 1         52 my %ret;
490 1         3 foreach my $pin ( @$pins ) {
491 1 50       8 $pin =~ m/^GPIO(\d)/ and my $n = $1;
492 1         6 $ret{$pin} = vec( $read, $n-1, 1 );
493             }
494              
495 1         5 return \%ret;
496 1     1   3627 }
497             }
498              
499             =head1 AUTHOR
500              
501             Paul Evans
502              
503             =cut
504              
505             0x55AA;