File Coverage

blib/lib/Device/Chip/NoritakeGU_D.pm
Criterion Covered Total %
statement 209 244 85.6
branch 7 22 31.8
condition 6 25 24.0
subroutine 45 54 83.3
pod 14 25 56.0
total 281 370 75.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, 2020-2021 -- leonerd@leonerd.org.uk
5              
6 8     8   668106 use v5.26; # signatures
  8         82  
7 8     8   475 use Object::Pad 0.57;
  8         7660  
  8         35  
8              
9             package Device::Chip::NoritakeGU_D 0.04;
10             class Device::Chip::NoritakeGU_D
11 1     1   473 :isa(Device::Chip);
  1         12192  
  1         26  
12              
13 8     8   2063 use Carp;
  8         13  
  8         460  
14              
15 8     8   46 use Future::AsyncAwait;
  8         20  
  8         51  
16 8     8   315 use List::Util qw( first );
  8         15  
  8         6799  
17              
18             =encoding UTF-8
19              
20             =head1 NAME
21              
22             C - chip driver for F F display modules
23              
24             =head1 SYNOPSIS
25              
26             use Device::Chip::NoritakeGU_D;
27             use Future::AsyncAwait;
28              
29             my $chip = Device::Chip::NoritakeGU_D->new( interface => "UART" );
30             await $chip->mount( Device::Chip::Adapter::...->new );
31              
32             await $chip->text( "Hello, world!" );
33              
34             =head1 DESCRIPTION
35              
36             This L subclass provides communication to a display module in
37             the F family by F.
38              
39             The reader is presumed to be familiar with the general operation of this chip;
40             the documentation here will not attempt to explain or define chip-specific
41             concepts or features, only the use of this module to access them.
42              
43             =cut
44              
45             =head1 CONSTRUCTOR
46              
47             =cut
48              
49             =head2 new
50              
51             $chip = Device::Chip::NoritakeGU_D->new(
52             interface => $iface,
53             ...
54             )
55              
56             Constructs a new driver instance for the given interface type. The type must
57             be one of C, C or C.
58              
59             =cut
60              
61             my %INTERFACES = (
62             UART => 1, I2C => 1, SPI => 1,
63             );
64              
65             has $_protocol :param(interface);
66             has $_interface;
67              
68             ADJUST
69             {
70             $INTERFACES{$_protocol} or
71             croak "Unrecognised interface type '$_protocol'";
72              
73             my $iface_class = __PACKAGE__."::_Iface::$_protocol";
74             $_interface = $iface_class->new;
75             }
76              
77 7     7 0 89 method PROTOCOL { $_protocol }
  7         17  
78              
79 7     7   1622 *UART_options = *I2C_options = *SPI_options = method { $_interface->options };
  7         24  
80              
81             # passthrough
82             method power
83 0     0 0 0 {
84 0 0       0 return $self->protocol->power( @_ ) if $self->protocol->can( "power" );
85 0         0 return Future->done;
86             }
87              
88 7         15 method mount ( $adapter, %params )
  7         14  
  7         14  
  7         11  
89 7     7 1 409 {
90 7         29 $_interface->mountopts( \%params );
91              
92 7         45 return $self->SUPER::mount( $adapter, %params );
93             }
94              
95 15     15 0 36 method write { $_interface->write( $self, @_ ) }
  15         51  
96 5     5 0 9425 method read { $_interface->read ( $self, @_ ) }
  5         27  
97              
98 6     6 0 13 method write_us { $self->write( pack "C*", 0x1F, @_ ) }
  6         32  
99              
100             =head1 METHODS
101              
102             The following methods documented in an C expression return L
103             instances.
104              
105             =cut
106              
107             =head2 text
108              
109             await $chip->text( $str );
110              
111             Draw text at the cursor position.
112              
113             =cut
114              
115 3         5 async method text ( $text )
  3         5  
  3         5  
116 3         7 {
117             # Don't allow C0 controls
118 3 50       16 $text =~ m/[\x00-\x1F]/ and
119             croak "Invalid characters for ->text";
120              
121 3         10 await $self->write( $text );
122 3     3 1 526 }
123              
124 16         31 sub BOOL_COMMAND ( $name, @bytes )
125 16     16 0 18 {
  16         22  
  16         168  
126 16         22 my $lastbyte = pop @bytes;
127              
128 8     8   58 no strict 'refs';
  8         14  
  8         1453  
129 1     1   166 *$name = method ( $on ) {
  1         2  
  1         1  
  1         2  
130 1         3 $self->write_us( @bytes, $lastbyte + !!$on );
131 16         66 };
132             }
133              
134 16         37 sub INT_COMMAND ( $name, $min, $max, @bytes )
  16         19  
  16         17  
135 16     16 0 22 {
  16         27  
  16         18  
136 16         45 my $shortname = ( split m/_/, $name )[-1];
137              
138 16         26 my $lastbyte = pop @bytes;
139              
140 8     8   45 no strict 'refs';
  8         13  
  8         1742  
141 1     1   8571 *$name = method ( $value ) {
  1         2  
  1         1  
  1         2  
142 1 50 33     5 $value >= $min and $value <= $max or
143             croak "Invalid $shortname for ->$name";
144              
145 1         3 $self->write_us( @bytes, $lastbyte + $value );
146 16         91 };
147             }
148              
149 24         31 sub ENUM_COMMAND ( $name, $values, @bytes )
  24         27  
150 24     24 0 28 {
  24         31  
  24         24  
151 24         45 my @values = @$values;
152              
153 24         46 my $shortname = ( split m/_/, $name )[-1];
154              
155 24         38 my $lastbyte = pop @bytes;
156              
157 8     8   47 no strict 'refs';
  8         13  
  8         15662  
158 1     1   2537 *$name = method ( $value ) {
  1         2  
  1         2  
  1         1  
159 1 50   2   6 defined( my $index = first { $values[$_] eq $value } 0 .. $#values ) or
  2         6  
160             croak "Invalid $shortname for ->$name";
161              
162 1         4 $self->write_us( @bytes, $lastbyte + $index );
163 24         91 };
164             }
165              
166             =head2 cursor_left
167              
168             =head2 cursor_right
169              
170             =head2 cursor_home
171              
172             await $chip->cursor_left;
173             await $chip->cursor_right;
174              
175             await $chip->cursor_linehome;
176              
177             await $chip->cursor_home;
178              
179             Move the cursor left or right one character position, to the beginning of the
180             line, or to the home position (top left corner).
181              
182             =cut
183              
184 0     0 1 0 method cursor_left { $self->write( "\x08" ) }
  0         0  
185 0     0 1 0 method cursor_right { $self->write( "\x09" ) }
  0         0  
186 0     0 0 0 method cursor_linehome { $self->write( "\x0D" ) }
  0         0  
187 0     0 1 0 method cursor_home { $self->write( "\x0B" ) }
  0         0  
188              
189             =head2 cursor_goto
190              
191             await $chip->cursor_goto( $x, $y );
192              
193             Moves the cursor to the C<$x>'th column of the C<$y>'th line (zero-indexed).
194              
195             =cut
196              
197 1         2 method cursor_goto ( $x, $y )
  1         36  
  1         4  
  1         2  
198 1     1 1 2566 {
199             # TODO: Bounds-check $x, $y
200              
201 1         6 $self->write( pack "C C S< S<", 0x1F, 0x24, $x, $y );
202             }
203              
204             =head2 linefeed
205              
206             await $chip->linefeed;
207              
208             Move the cursor down to the next line.
209              
210             =cut
211              
212 0     0 1 0 method linefeed { $self->write( "\x0A" ) }
  0         0  
213              
214             =head2 clear
215              
216             $chip->clear
217              
218             Clear the display.
219              
220             =cut
221              
222 3     3 1 28877 method clear { $self->write( "\x0C" ) }
  3         13  
223              
224             =head2 select_window
225              
226             await $chip->select_window( $win );
227              
228             Select the main window (when C<$win> is 0), or one of the four numbered
229             sub-windows.
230              
231             =cut
232              
233             INT_COMMAND select_window => 0, 4,
234             0x10;
235              
236             =head2 initialise
237              
238             $chip->initialise
239              
240             Reset all settings to their default values.
241              
242             =cut
243              
244 0     0 1 0 method initialise { $self->write( "\x1B\x40" ) }
  0         0  
245              
246             =head2 set_cursor_visible
247              
248             await $chip->set_cursor_visible( $bool );
249              
250             Set whether the cursor is visible.
251              
252             =cut
253              
254             BOOL_COMMAND set_cursor_visible =>
255             0x43, 0x00;
256              
257             =head2 set_brightness
258              
259             await $chip->set_brightness( $val );
260              
261             Set the display brightness, from 1 to 8.
262              
263             =cut
264              
265             INT_COMMAND set_brightness => 1, 8,
266             0x58, 0x00;
267              
268             =head2 set_reverse
269              
270             await $chip->set_reverse( $bool );
271              
272             Sets whether subsequent text will be rendered in "reverse video" (clear pixels
273             on a set background) effect.
274              
275             =cut
276              
277             BOOL_COMMAND set_reverse =>
278             0x72, 0x00;
279              
280             =head2 set_write_mixture_display_mode
281              
282             await $chip->set_write_mixture_display_mode( $mode );
283              
284             Set the combining mode for newly-added display content. C<$mode> must be one
285             of
286              
287             set or and xor
288              
289             =cut
290              
291             ENUM_COMMAND set_write_mixture_display_mode => [qw( set or and xor )],
292             0x77, 0x00;
293              
294             =head2 set_font_size
295              
296             await $chip->set_font_size( $size );
297              
298             Set the font size. C<$size> must be one of
299              
300             5x7 8x16
301              
302             =cut
303              
304             ENUM_COMMAND set_font_size => [qw( 5x7 8x16 )],
305             0x28, 0x67, 0x01, 0x01;
306              
307             =head2 set_font_width
308              
309             await $chip->set_font_width( $width );
310              
311             Set the font width. C<$width> must be one of
312              
313             fixed fixed2 prop prop2
314              
315             =cut
316              
317             ENUM_COMMAND set_font_width => [qw( fixed fixed2 prop prop2 )],
318             0x28, 0x67, 0x03, 0x00;
319              
320             =head2 set_font_magnification
321              
322             await $chip->set_font_magnification( $xscale, $yscale );
323              
324             Set the font scaling factor. C<$xscale> must be between 1 to 4, and
325             C<$yscale> must be 1 or 2.
326              
327             =cut
328              
329 0         0 method set_font_magnification ( $x, $y )
  0         0  
  0         0  
  0         0  
330 0     0 1 0 {
331 0 0 0     0 $x >= 1 and $x <= 4 or croak "Invalid x scale";
332 0 0 0     0 $y >= 1 and $y <= 2 or croak "Invalid y scale";
333              
334 0         0 $self->write_us( 0x28, 0x67, 0x40, $x, $y );
335             }
336              
337 1         2 method _realtime_image_display ( $width, $height, $bytes )
  1         1  
  1         2  
  1         1  
  1         1  
338 1     1   2 {
339 1         16 $self->write( "\x1F\x28\x66\x11" . pack "S< S< C a*",
340             $width, $height, 1, $bytes,
341             );
342             }
343              
344             =head2 realtime_image_display_columns
345              
346             await $chip->realtime_image_display_columns( @columns );
347              
348             Sends a bitmapped image to the display, at the cursor position. The cursor is
349             not moved.
350              
351             C<@columns> should be a list of strings of equal length, containing bytes of
352             pixel data to represent each vertical column of the image content.
353              
354             =cut
355              
356 1         1 method realtime_image_display_columns ( @columns )
  1         3  
  1         1  
357 1     1 1 166 {
358 1 50       3 @columns or croak "Expected at least 1 column";
359 1         2 my $height = length $columns[0];
360 1   33     6 $height == length $_ or croak "Expected all columns of equal length" for @columns[1..$#columns];
361              
362 1         2 my $bytes = join "", @columns;
363              
364 1         3 $self->_realtime_image_display( scalar @columns, $height, $bytes );
365             }
366              
367 0         0 method realtime_image_display_lines ( @lines )
  0         0  
  0         0  
368 0     0 0 0 {
369 0 0       0 @lines or croak "Expected at least 1 line";
370 0         0 my $width = length $lines[0];
371 0   0     0 $width == length $_ or croak "Expected all lines of equal length" for @lines[1..$#lines];
372              
373             # Restripe the data in vertical strips
374             my $bytes = join "", map {
375 0         0 my $col = $_;
  0         0  
376 0         0 map { substr( $lines[$_], $col, 1 ) } 0 .. $#lines
  0         0  
377             } 0 .. $width-1;
378              
379 0         0 $self->_realtime_image_display( $width, scalar @lines, $bytes );
380             }
381              
382             =head2 set_gpio_direction
383              
384             await $chip->set_gpio_direction( $dir );
385              
386             Configure the GPIO pins for input or output. C<$dir> is bitmask of four bits.
387             Low bits correspond to input, high bits to output.
388              
389             =cut
390              
391 1         2 async method set_gpio_direction ( $dir )
  1         2  
  1         1  
392 1         3 {
393 1         4 await $self->write_us( 0x28, 0x70, 0x01, 0x00, $dir & 0x0F );
394 1     1 1 180 }
395              
396             =head2 set_gpio_output
397              
398             await $chip->set_gpio_output( $value );
399              
400             Write the value to the GPIO pins.
401              
402             =cut
403              
404 1         2 async method write_gpio ( $value )
  1         2  
  1         2  
405 1         5 {
406 1         4 await $self->write_us( 0x28, 0x70, 0x10, 0x00, $value & 0x0F );
407 1     1 0 9090 }
408              
409             =head2 read_gpio
410              
411             $value = await $chip->read_gpio;
412              
413             Returns the current state of the GPIO pins.
414              
415             =cut
416              
417             async method read_gpio
418 1         3 {
419 1         4 await $self->write_us( 0x28, 0x70, 0x20, 0x00 );
420 1         914 my ( $header, $id1, $id2, $value ) = unpack "C4", await $self->read( 4 );
421              
422 1 50 33     935 croak "Expected 0x28 0x70 0x20" unless $header == 0x28 and
      33        
423             $id1 == 0x70 and $id2 == 0x20;
424              
425 1         4 return $value;
426 1     1 1 4024 }
427              
428             =head2 read_touchswitches
429              
430             $switches = await $chip->read_touchswitches;
431              
432             Reads the status of the panel touch switches. Returns a hash reference whose
433             keys are the names of the touch areas (C, C, ...) and values are
434             booleans indicating whether that area currently detects a touch.
435              
436             =cut
437              
438             async method read_touchswitches
439 1         2 {
440 1         4 await $self->write( "\x1F\x4B\x10" );
441              
442 1         9009 my ( $header, $len, $switches ) = unpack "C C S>", await $self->read( 4 );
443 1 50       1094 croak sprintf "Expected header = 0x10; got 0x%02X", $header if $header != 0x10;
444 1 50       3 croak "Expected length=2, got $len" if $len != 2;
445              
446             return {
447 1         31 map +("SW$_", $switches & ( 2 ** ( $_-1 ) )), 1 .. 16
448             };
449 1     1 1 211 }
450              
451             # Interface helpers
452              
453             class Device::Chip::NoritakeGU_D::_Iface::UART {
454 8     8   1076 use constant DEFAULT_BAUDRATE => 38400;
  8         14  
  8         3326  
455              
456             has $_baudrate;
457              
458 5         7 method mountopts ( $params )
  5         9  
  5         6  
459 5     5   11 {
460 5   50     25 $_baudrate = delete $params->{baudrate} // DEFAULT_BAUDRATE;
461             }
462              
463             method options
464 5     5   12 {
465             return (
466 5         23 baudrate => $_baudrate,
467             );
468             }
469              
470 11         17 async method write ( $chip, $bytes )
  11         13  
  11         13  
  11         15  
471 11         20 {
472 11         29 await $chip->protocol->write( $bytes );
473 11     11   16 }
474              
475 3         48 async method read ( $chip, $len )
  3         9  
  3         5  
  3         4  
476 3         8 {
477 3         15 return await $chip->protocol->read( $len );
478 3     3   6 }
479             }
480              
481             class Device::Chip::NoritakeGU_D::_Iface::I2C {
482 8     8   735 use constant DEFAULT_ADDR => 0x50;
  8         15  
  8         3270  
483              
484             has $_addr;
485              
486 1         2 method mountopts ( $params )
  1         3  
  1         1  
487 1     1   2 {
488 1   50     7 $_addr = delete $params->{addr} // DEFAULT_ADDR;
489             }
490              
491             method options
492 1     1   3 {
493             return (
494 1         5 addr => $_addr,
495             );
496             }
497              
498 2         4 async method write ( $chip, $bytes )
  2         4  
  2         4  
  2         4  
499 2         5 {
500 2         9 await $chip->protocol->write( $bytes );
501 2     2   5 }
502              
503 1         2 async method read ( $chip, $len )
  1         3  
  1         2  
  1         2  
504 1         4 {
505 1         6 return await $chip->protocol->read( $len );
506 1     1   2 }
507             }
508              
509             class Device::Chip::NoritakeGU_D::_Iface::SPI {
510 1     1   3 method mountopts ( $ ) {}
  1         1  
511              
512             method options
513 1     1   3 {
514             return (
515 1         5 mode => 0,
516             # max_bitrate => 2E6, # min clock period 500ns
517             # Need to slow the bitrate down in order to generate inter-word gaps
518             max_bitrate => 500E3,
519             );
520             }
521              
522 2         5 async method write ( $chip, $bytes )
  2         4  
  2         5  
  2         3  
523 2         5 {
524 2         9 await $chip->protocol->write( "\x44" . $bytes );
525 2     2   4 }
526              
527 1         3 async method read ( $chip, $len )
  1         2  
  1         1  
  1         2  
528 1         2 {
529             # TODO:
530             # The datasheet says that after you write a 0x58 byte, the very next byte
531             # you get back will be the status. Experimental testing shows you get an
532             # echo of the 0x58 first, then status.
533              
534 1         7 my $status = unpack "x C", await $chip->protocol->write_then_read( "\x58", 2 );
535              
536             # The datasheet says that after you write a 0x54 byte, you'll immediately
537             # get 0x00 then the data. Experimental testing suggests that you get an
538             # echo of the 0x54 byte first, then 0x00, then the data.
539              
540 1         1282 my $bytes = await $chip->protocol->write_then_read( "\x54", ( $status & 0x1F ) + 2 );
541              
542 1         867 return substr $bytes, 2;
543 1     1   3 }
544             }
545              
546             =head1 AUTHOR
547              
548             Paul Evans
549              
550             =cut
551              
552             0x55AA;