File Coverage

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