File Coverage

blib/lib/Device/Chip/SSD1306.pm
Criterion Covered Total %
statement 115 150 76.6
branch 14 24 58.3
condition 3 5 60.0
subroutine 16 20 80.0
pod 13 13 100.0
total 161 212 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, 2015-2023 -- leonerd@leonerd.org.uk
5              
6 5     5   419155 use v5.26;
  5         27  
7 5     5   21 use warnings;
  5         8  
  5         141  
8 5     5   1547 use Object::Pad 0.800 ':experimental(adjust_params)';
  5         24185  
  5         245  
9              
10             package Device::Chip::SSD1306 0.14;
11             class Device::Chip::SSD1306
12             :isa(Device::Chip)
13 3     3   1590 :strict(params);
  3         16599  
  3         116  
14              
15 5     5   1059 use Carp;
  5         9  
  5         263  
16 5     5   36 use Future::AsyncAwait;
  5         8  
  5         20  
17              
18             =encoding UTF-8
19              
20             =head1 NAME
21              
22             C - chip driver for monochrome OLED modules
23              
24             =head1 DESCRIPTION
25              
26             This abstract L subclass provides communication to an
27             F, F or F chip attached by an adapter. To actually
28             use it, you should use one of the subclasses for the various interface types.
29              
30             =over 4
31              
32             =item *
33              
34             L - I²C
35              
36             =item *
37              
38             L - 4-wire SPI
39              
40             =back
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             =head1 DEVICE MODELS
49              
50             This module supports a variety of actual chip modules with different display
51             sizes. The specific display module is selected by the C argument to the
52             constructor, which should take one of the following values:
53              
54             =over 4
55              
56             =item C
57              
58             An F driving a display with 128 columns and 64 rows. The most common
59             of the display modules, often found with a 0.96 inch display.
60              
61             This setting also drives the F chip found in the larger 1.6 or 2.4
62             inch displays.
63              
64             =item C
65              
66             An F driving a display with 128 columns and 32 rows. This is the
67             usual "half-height" module, often found with a 0.91 inch display. This uses
68             only even-numbered rows.
69              
70             =item C
71              
72             An F driving a display with 64 columns and 32 rows. This is the usual
73             "quarter" size module, often found with a 0.49 inch display. This uses the
74             only the middle 64 columns.
75              
76             =item C
77              
78             An F driving a display with 128 columns and 64 rows. This is the chip
79             that's usually found in the larger display modules, such as 1.3 and 1.6 inch.
80              
81             =back
82              
83             The overall API shape of this module is similar to that of
84             L, supporting the same set of drawing methods. A
85             future version of both of these modules may extend the concept into providing
86             access via an interface helper instance, if some standard API shape is defined
87             for driving these kinds of 1bpp pixel displays.
88              
89             =cut
90              
91             my %MODELS = (
92             "SSD1306-128x64" => {
93             columns => 128,
94             rows => 64,
95              
96             set_com_pins_arg => 0x12,
97             column_offset => 0,
98             },
99             "SSD1306-128x32" => {
100             columns => 128,
101             rows => 32,
102              
103             set_com_pins_arg => 0x02,
104             column_offset => 0,
105             },
106             "SSD1306-64x32" => {
107             columns => 64,
108             rows => 32,
109              
110             set_com_pins_arg => 0x12,
111             # This module seems to use the middle 64 column pins
112             column_offset => 32,
113             },
114             "SH1106-128x64" => {
115             columns => 128,
116             rows => 64,
117              
118             set_com_pins_arg => 0x12,
119             # SH1106 has a 128 column physical display but its VRAM is 132 columns
120             # wide; with two blank columns either side of the display. It actually
121             # starts outputting from column 2;
122             column_offset => 2,
123             },
124             );
125              
126             =head1 CONSTRUCTOR
127              
128             =cut
129              
130             =head2 new
131              
132             $chip = Device::Chip::SSD1306->new(
133             model => $model,
134             ...
135             )
136              
137             Returns a new C driver instance for the given model
138             name, which must be one of the models listed in L. If no model
139             option is chosen, the default of C will apply.
140              
141             In addition to C, the following named options may also be passed:
142              
143             =over 4
144              
145             =item xflip => BOOL
146              
147             =item yflip => BOOL
148              
149             If true, the order of columns or rows respectively will be reversed by the
150             hardware. In particular, if both are true, this inverts the orientation of
151             the display, if it is mounted upside-down.
152              
153             =back
154              
155             =cut
156              
157 5     5 1 17 field $_rows :reader;
  5         19  
158 4     4 1 15 field $_columns :reader;
  4         21  
159              
160             field $_column_offset;
161             field $_set_com_pins_arg;
162             field $_xflip :param = 0;
163             field $_yflip :param = 0;
164              
165             ADJUST :params ( :$model = "SSD1306-128x64" )
166             {
167             my $modelargs = $MODELS{$model} or croak "Unrecognised model $model";
168              
169             ( $_rows, $_columns, $_column_offset, $_set_com_pins_arg ) =
170             @{$modelargs}{qw( rows columns column_offset set_com_pins_arg )};
171             }
172              
173             =head1 METHODS
174              
175             The following methods documented in an C expression return L
176             instances.
177              
178             =cut
179              
180             =head2 rows
181              
182             =head2 columns
183              
184             $n = $chip->rows
185              
186             $n = $chip->columns
187              
188             Simple accessors that return the number of rows or columns present on the
189             physical display.
190              
191             =cut
192              
193             use constant {
194 5         15177 CMD_SET_CONTRAST => 0x81, # , contrast
195             CMD_DISPLAY_LAMPTEST => 0xA4, # + all on
196             CMD_DISPLAY_INVERT => 0xA6, # + invert
197             CMD_DISPLAY_OFF => 0xAE, #
198             CMD_DISPLAY_ON => 0xAF, #
199              
200             CMD_SCROLL_RIGHT => 0x26, # , 0, start page, time, end page, 0, 0xff
201             CMD_SCROLL_LEFT => 0x27, # , 0, start page, time, end page, 0, 0xff
202             CMD_SCROLL_VERT_RIGHT => 0x29, # , 0, start page, time, end page, vertical
203             CMD_SCROLL_VERT_LEFT => 0x2A, # , 0, start page, time, end page, vertical
204             CMD_SCROLL_DEACTIVATE => 0x2E, #
205             CMD_SCROLL_ACTIVATE => 0x2F, #
206             CMD_SET_SCROLL_AREA => 0xA3, # , start row, scroll rows
207              
208             CMD_SET_LOW_COLUMN => 0x00, # + column
209             CMD_SET_HIGH_COLUMN => 0x10, # + column
210             CMD_SET_ADDR_MODE => 0x20, # , mode
211             MODE_HORIZONTAL => 0,
212             MODE_VERTICAL => 1,
213             MODE_PAGE => 2,
214             CMD_SET_COLUMN_ADDR => 0x21, # , start column, end column
215             CMD_SET_PAGE_ADDR => 0x22, # , start page, end page
216             CMD_SET_PAGE_START => 0xB0, # + page
217              
218             CMD_SET_DISPLAY_START => 0x40, # + line
219             CMD_SET_SEGMENT_REMAP => 0xA0, # + remap
220             CMD_SET_MUX_RATIO => 0xA8, # , mux
221             CMD_SET_COM_SCAN_DIR => 0xC0, # + direction(8)
222             CMD_SET_DISPLAY_OFFS => 0xD3, # , line offset
223             CMD_SET_COM_PINS => 0xDA, # , (config << 4) | 0x02
224              
225             CMD_SET_CLOCKDIV => 0xD5, # , (freq << 4) | (divratio)
226             CMD_SET_PRECHARGE => 0xD9, # , (ph1 << 4) | (ph2)
227             CMD_SET_VCOMH_LEVEL => 0xDB, # , (level << 4)
228             CMD_SET_CHARGEPUMP => 0x8D, # , 0x10 | (enable << 2)
229              
230             CMD_NOP => 0xE3,
231 5     5   2781 };
  5         16  
232              
233             =head2 init
234              
235             await $chip->init;
236              
237             Initialise the display after reset to some sensible defaults.
238              
239             =cut
240              
241             # This initialisation sequence is inspired by the Adafruit driver
242             # https://github.com/adafruit/Adafruit_SSD1306
243              
244 0         0 async method init ()
  0         0  
245 0         0 {
246 0         0 await $self->display( 0 );
247 0         0 await $self->send_cmd( CMD_SET_CLOCKDIV, ( 8 << 4 ) | 0x80 );
248 0         0 await $self->send_cmd( CMD_SET_MUX_RATIO, $_rows - 1 );
249 0         0 await $self->send_cmd( CMD_SET_DISPLAY_OFFS, 0 );
250 0         0 await $self->send_cmd( CMD_SET_DISPLAY_START | 0 );
251 0         0 await $self->send_cmd( CMD_SET_CHARGEPUMP, 0x14 );
252 0         0 await $self->send_cmd( CMD_SET_ADDR_MODE, MODE_HORIZONTAL );
253 0         0 await $self->send_cmd( CMD_SET_SEGMENT_REMAP | ( $_xflip ? 1 : 0 ) );
254 0         0 await $self->send_cmd( CMD_SET_COM_SCAN_DIR | ( $_yflip ? 1<<3 : 0 ) );
255 0         0 await $self->send_cmd( CMD_SET_COM_PINS, $_set_com_pins_arg );
256 0         0 await $self->send_cmd( CMD_SET_CONTRAST, 0x9F );
257 0         0 await $self->send_cmd( CMD_SET_PRECHARGE, ( 0x0f << 4 ) | ( 1 ) );
258 0         0 await $self->send_cmd( CMD_SET_VCOMH_LEVEL, ( 4 << 4 ) );
259 0     0 1 0 }
260              
261             =head2 display
262              
263             await $chip->display( $on );
264              
265             Turn on or off the display.
266              
267             =cut
268              
269 0         0 async method display ( $on )
  0         0  
  0         0  
270 0         0 {
271 0         0 await $self->send_cmd( $on ? CMD_DISPLAY_ON : CMD_DISPLAY_OFF );
272 0     0 1 0 }
273              
274             =head2 display_lamptest
275              
276             await $chip->display_lamptest( $enable );
277              
278             Turn on or off the all-pixels-lit lamptest mode.
279              
280             =cut
281              
282 0         0 async method display_lamptest ( $enable )
  0         0  
  0         0  
283 0         0 {
284 0         0 await $self->send_cmd( CMD_DISPLAY_LAMPTEST + !!$enable );
285 0     0 1 0 }
286              
287             =head2 display_invert
288              
289             await $chip->display_invert( $enable );
290              
291             Turn on or off the inverted output mode.
292              
293             =cut
294              
295 0         0 async method display_invert ( $enable )
  0         0  
  0         0  
296 0         0 {
297 0         0 await $self->send_cmd( CMD_DISPLAY_INVERT + !!$enable );
298 0     0 1 0 }
299              
300             =head2 send_display
301              
302             await $chip->send_display( $pixels );
303              
304             Sends an entire screen-worth of pixel data. The C<$pixels> should be in a
305             packed binary string containing one byte per 8 pixels.
306              
307             =cut
308              
309 4         5 async method send_display ( $pixels )
  4         6  
  4         6  
310 4         13 {
311             # This output method isn't quite what most of the SSD1306 drivers use, but
312             # it happens to work on both the SSD1306 and the SH1106, whereas other code
313             # based on SET_COLUMN_ADDR + SET_PAGE_ADDR do not
314              
315 4         4 my $pagewidth = $_columns;
316              
317 4         6 my $column = $_column_offset;
318              
319 4         15 foreach my $page ( 0 .. ( $_rows / 8 ) - 1 ) {
320 24         614 await $self->send_cmd( CMD_SET_PAGE_START + $page );
321 24         801 await $self->send_cmd( CMD_SET_LOW_COLUMN | $column & 0x0f );
322 24         704 await $self->send_cmd( CMD_SET_HIGH_COLUMN | $column >> 4 );
323 24         719 await $self->send_data( substr $pixels, $page * $pagewidth, $pagewidth );
324             }
325 4     4 1 8 }
326              
327             =head1 DRAWING METHODS
328              
329             The following methods operate on an internal framebuffer stored by the
330             instance. The user must invoke the L method to update the actual
331             chip after calling them.
332              
333             =cut
334              
335             # The internal framebuffer
336             field @_display;
337             field $_display_dirty;
338             field $_display_dirty_xlo;
339             field $_display_dirty_xhi;
340              
341             =head2 clear
342              
343             $chip->clear
344              
345             Resets the stored framebuffer to blank.
346              
347             =cut
348              
349 1         2 method clear ()
  1         1  
350 1     1 1 7 {
351             @_display = (
352 1         4 map { [ ( 0 ) x $_columns ] } 1 .. $_rows
  64         576  
353             );
354 1         6 $_display_dirty = ( 1 << $_rows/8 ) - 1;
355 1         3 $_display_dirty_xlo = 0;
356 1         3 $_display_dirty_xhi = $_columns-1;
357             }
358              
359             =head2 draw_pixel
360              
361             $chip->draw_pixel( $x, $y, $val = 1 )
362              
363             Draw the given pixel. If the third argument is false, the pixel will be
364             cleared instead of set.
365              
366             =cut
367              
368 1         2 method draw_pixel ( $x, $y, $val = 1 )
  1         2  
  1         2  
  1         2  
  1         2  
369 1     1 1 7 {
370 1         3 $_display[$y][$x] = $val;
371 1         3 $_display_dirty |= ( 1 << int( $y / 8 ) );
372 1 50       3 $_display_dirty_xlo = $x if $_display_dirty_xlo > $x;
373 1 50       3 $_display_dirty_xhi = $x if $_display_dirty_xhi < $x;
374             }
375              
376             =head2 draw_hline
377              
378             $chip->draw_hline( $x1, $x2, $y, $val = 1 )
379              
380             Draw a horizontal line in the given I<$y> row, between the columns I<$x1> and
381             I<$x2> (inclusive). If the fourth argument is false, the pixels will be
382             cleared instead of set.
383              
384             =cut
385              
386 1         3 method draw_hline ( $x1, $x2, $y, $val = 1 )
  1         2  
  1         3  
  1         2  
  1         2  
  1         3  
387 1     1 1 17119 {
388 1         10 $_display[$y][$_] = $val for $x1 .. $x2;
389 1         4 $_display_dirty |= ( 1 << int( $y / 8 ) );
390 1 50       5 $_display_dirty_xlo = $x1 if $_display_dirty_xlo > $x1;
391 1 50       4 $_display_dirty_xhi = $x2 if $_display_dirty_xhi < $x2;
392             }
393              
394             =head2 draw_vline
395              
396             $chip->draw_vline( $x, $y1, $y2, $val = 1 )
397              
398             Draw a vertical line in the given I<$x> column, between the rows I<$y1> and
399             I<$y2> (inclusive). If the fourth argument is false, the pixels will be
400             cleared instead of set.
401              
402             =cut
403              
404 1         2 method draw_vline ( $x, $y1, $y2, $val = 1 )
  1         2  
  1         1  
  1         25  
  1         3  
  1         1  
405 1     1 1 7 {
406 1   50     4 $val //= 1;
407              
408 1         20 $_display[$_][$x] = $val for $y1 .. $y2;
409 1         10 $_display_dirty |= ( 1 << int( $_ / 8 ) ) for $y1 .. $y2;
410 1 50       7 $_display_dirty_xlo = $x if $_display_dirty_xlo > $x;
411 1 50       4 $_display_dirty_xhi = $x if $_display_dirty_xhi < $x;
412             }
413              
414             =head2 draw_blit
415              
416             $chip->draw_blit( $x, $y, @lines )
417              
418             Draws a bitmap pattern by copying the data given in lines, starting at the
419             given position.
420              
421             Each value in C<@lines> should be a string giving a horizontal line of bitmap
422             data, each character corresponding to a single pixel of the display. Pixels
423             corresponding to a spaces will be left alone, a hyphen will be cleared, and
424             any other character (for example a C<#>) will be set.
425              
426             For example, to draw an rightward-pointing arrow:
427              
428             $chip->draw_blit( 20, 40,
429             " # ",
430             " ## ",
431             "######",
432             "######",
433             " ## ",
434             " # " );
435              
436             =cut
437              
438 1         2 method draw_blit ( $x0, $y, @lines )
  1         2  
  1         2  
  1         3  
  1         1  
439 1     1 1 8 {
440 1         4 for( ; @lines; $y++ ) {
441 8         18 my @pixels = split m//, shift @lines;
442 8 50       12 @pixels or next;
443              
444 8         11 my $x = $x0;
445 8         14 for( ; @pixels; $x++ ) {
446 32         36 my $p = shift @pixels;
447              
448 32 50       62 $p eq " " ? next :
    100          
449             $p eq "-" ? ( $_display[$y][$x] = 0 ) :
450             ( $_display[$y][$x] = 1 );
451             }
452 8         9 $x--;
453              
454 8         13 $_display_dirty |= ( 1 << int( $y / 8 ) );
455 8 50       21 $_display_dirty_xlo = $x0 if $_display_dirty_xlo > $x0;
456 8 50       19 $_display_dirty_xhi = $x if $_display_dirty_xhi < $x;
457             }
458             }
459              
460             =head2 refresh
461              
462             await $chip->refresh;
463              
464             Sends the framebuffer to the display chip.
465              
466             =cut
467              
468 2         3 async method refresh ()
  2         3  
469 2         7 {
470 2         5 my $maxcol = $_columns - 1;
471 2         3 my $column = $_column_offset + $_display_dirty_xlo;
472              
473 2         8 foreach my $page ( 0 .. ( $_rows / 8 ) - 1 ) {
474 16 100       31 next unless $_display_dirty & ( 1 << $page );
475 12         17 my $row = $page * 8;
476              
477 12         16 my $data = "";
478 12         25 foreach my $col ( $_display_dirty_xlo .. $_display_dirty_xhi ) {
479 1152         1180 my $v = 0;
480 1152   66     5803 $v <<= 1, $_display[$row+$_][$col] && ( $v |= 1 ) for reverse 0 .. 7;
481 1152         1465 $data .= chr $v;
482             }
483              
484 12         32 await $self->send_cmd( CMD_SET_PAGE_START + $page );
485 12         498 await $self->send_cmd( CMD_SET_LOW_COLUMN | $column & 0x0f );
486 12         379 await $self->send_cmd( CMD_SET_HIGH_COLUMN | $column >> 4 );
487 12         349 await $self->send_data( $data );
488              
489 12         372 $_display_dirty &= ~( 1 << $page );
490             }
491              
492 2         3 $_display_dirty_xlo = $_columns;
493 2         18 $_display_dirty_xhi = -1;
494 2     2 1 19 }
495              
496             =head1 TODO
497              
498             =over 4
499              
500             =item *
501              
502             More interfaces - 3-wire SPI
503              
504             =back
505              
506             =cut
507              
508             =head1 AUTHOR
509              
510             Paul Evans
511              
512             =cut
513              
514             0x55AA;