File Coverage

blib/lib/Device/Chip/NoritakeGU_D.pm
Criterion Covered Total %
statement 212 247 85.8
branch 7 22 31.8
condition 6 25 24.0
subroutine 46 55 83.6
pod 14 25 56.0
total 285 374 76.2


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