File Coverage

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