summaryrefslogtreecommitdiff
path: root/drivers/display
diff options
context:
space:
mode:
authorScott Shawcroft <scott.shawcroft@gmail.com>2017-06-20 10:56:05 -0700
committerScott Shawcroft <scott.shawcroft@gmail.com>2017-06-20 10:56:05 -0700
commit30ee7019ca651bce1fe966c1089fe977d51dc92b (patch)
tree0114b6f744dd175b7722dd8edcbe24f4ee84bb45 /drivers/display
parent97fcdfbd08c922efd7470d6482d7c7c703ffc0ae (diff)
parent869cdcfdfc860b1177baf9b0f8818915ba54f3f5 (diff)
Merge tag 'v1.9.1'2.0.0-alpha.0
Fixes for stmhal USB mass storage, lwIP bindings and VFS regressions This release provides an important fix for the USB mass storage device in the stmhal port by implementing the SCSI SYNCHRONIZE_CACHE command, which is now require by some Operating Systems. There are also fixes for the lwIP bindings to improve non-blocking sockets and error codes. The VFS has some regressions fixed including the ability to statvfs the root. All changes are listed below. py core: - modbuiltins: add core-provided version of input() function - objstr: catch case of negative "maxsplit" arg to str.rsplit() - persistentcode: allow to compile with complex numbers disabled - objstr: allow to compile with obj-repr D, and unicode disabled - modsys: allow to compile with obj-repr D and PY_ATTRTUPLE disabled - provide mp_decode_uint_skip() to help reduce stack usage - makeqstrdefs.py: make script run correctly with Python 2.6 - objstringio: if created from immutable object, follow copy on write policy extmod: - modlwip: connect: for non-blocking mode, return EINPROGRESS - modlwip: fix error codes for duplicate calls to connect() - modlwip: accept: fix error code for non-blocking mode - vfs: allow to statvfs the root directory - vfs: allow "buffering" and "encoding" args to VFS's open() - modframebuf: fix signed/unsigned comparison pendantic warning lib: - libm: use isfinite instead of finitef, for C99 compatibility - utils/interrupt_char: remove support for KBD_EXCEPTION disabled tests: - basics/string_rsplit: add tests for negative "maxsplit" argument - float: convert "sys.exit()" to "raise SystemExit" - float/builtin_float_minmax: PEP8 fixes - basics: convert "sys.exit()" to "raise SystemExit" - convert remaining "sys.exit()" to "raise SystemExit" unix port: - convert to use core-provided version of built-in import() - Makefile: replace references to make with $(MAKE) windows port: - convert to use core-provided version of built-in import() qemu-arm port: - Makefile: adjust object-file lists to get correct dependencies - enable micropython.mem_*() functions to allow more tests stmhal port: - boards: enable DAC for NUCLEO_F767ZI board - add support for NUCLEO_F446RE board - pass USB handler as parameter to allow more than one USB handler - usb: use local USB handler variable in Start-of-Frame handler - usb: make state for USB device private to top-level USB driver - usbdev: for MSC implement SCSI SYNCHRONIZE_CACHE command - convert from using stmhal's input() to core provided version cc3200 port: - convert from using stmhal's input() to core provided version teensy port: - convert from using stmhal's input() to core provided version esp8266 port: - Makefile: replace references to make with $(MAKE) - Makefile: add clean-modules target - convert from using stmhal's input() to core provided version zephyr port: - modusocket: getaddrinfo: Fix mp_obj_len() usage - define MICROPY_PY_SYS_PLATFORM (to "zephyr") - machine_pin: use native Zephyr types for Zephyr API calls docs: - machine.Pin: remove out_value() method - machine.Pin: add on() and off() methods - esp8266: consistently replace Pin.high/low methods with .on/off - esp8266/quickref: polish Pin.on()/off() examples - network: move confusingly-named cc3200 Server class to its reference - uos: deconditionalize, remove minor port-specific details - uos: move cc3200 port legacy VFS mounting functions to its ref doc - machine: sort machine classes in logical order, not alphabetically - network: first step to describe standard network class interface examples: - embedding: use core-provided KeyboardInterrupt object
Diffstat (limited to 'drivers/display')
-rw-r--r--drivers/display/lcd160cr.py474
-rw-r--r--drivers/display/lcd160cr_test.py171
-rw-r--r--drivers/display/ssd1306.py161
3 files changed, 806 insertions, 0 deletions
diff --git a/drivers/display/lcd160cr.py b/drivers/display/lcd160cr.py
new file mode 100644
index 000000000..dd9ab9985
--- /dev/null
+++ b/drivers/display/lcd160cr.py
@@ -0,0 +1,474 @@
+# Driver for official MicroPython LCD160CR display
+# MIT license; Copyright (c) 2017 Damien P. George
+
+from micropython import const
+from utime import sleep_ms
+from ustruct import calcsize, pack_into
+import uerrno, machine
+
+# for set_orient
+PORTRAIT = const(0)
+LANDSCAPE = const(1)
+PORTRAIT_UPSIDEDOWN = const(2)
+LANDSCAPE_UPSIDEDOWN = const(3)
+
+# for set_startup_deco; can be or'd
+STARTUP_DECO_NONE = const(0)
+STARTUP_DECO_MLOGO = const(1)
+STARTUP_DECO_INFO = const(2)
+
+_uart_baud_table = {
+ 2400: 0,
+ 4800: 1,
+ 9600: 2,
+ 19200: 3,
+ 38400: 4,
+ 57600: 5,
+ 115200: 6,
+ 230400: 7,
+ 460800: 8,
+}
+
+class LCD160CR:
+ def __init__(self, connect=None, *, pwr=None, i2c=None, spi=None, i2c_addr=98):
+ if connect in ('X', 'Y', 'XY', 'YX'):
+ i = connect[-1]
+ j = connect[0]
+ y = j + '4'
+ elif connect == 'C':
+ i = 2
+ j = 2
+ y = 'A7'
+ else:
+ if pwr is None or i2c is None or spi is None:
+ raise ValueError('must specify valid "connect" or all of "pwr", "i2c" and "spi"')
+
+ if pwr is None:
+ pwr = machine.Pin(y, machine.Pin.OUT)
+ if i2c is None:
+ i2c = machine.I2C(i, freq=1000000)
+ if spi is None:
+ spi = machine.SPI(j, baudrate=13500000, polarity=0, phase=0)
+
+ if not pwr.value():
+ pwr(1)
+ sleep_ms(10)
+ # else:
+ # alread have power
+ # lets be optimistic...
+
+ # set connections
+ self.pwr = pwr
+ self.i2c = i2c
+ self.spi = spi
+ self.i2c_addr = i2c_addr
+
+ # create temp buffers and memoryviews
+ self.buf16 = bytearray(16)
+ self.buf19 = bytearray(19)
+ self.buf = [None] * 10
+ for i in range(1, 10):
+ self.buf[i] = memoryview(self.buf16)[0:i]
+ self.buf1 = self.buf[1]
+ self.array4 = [0, 0, 0, 0]
+
+ # set default orientation and window
+ self.set_orient(PORTRAIT)
+ self._fcmd2b('<BBBBBB', 0x76, 0, 0, self.w, self.h) # viewport 'v'
+ self._fcmd2b('<BBBBBB', 0x79, 0, 0, self.w, self.h) # window 'y'
+
+ def _send(self, cmd):
+ i = self.i2c.writeto(self.i2c_addr, cmd)
+ if i == len(cmd):
+ return
+ cmd = memoryview(cmd)
+ n = len(cmd)
+ while True:
+ i += self.i2c.writeto(self.i2c_addr, cmd[i:])
+ if i == n:
+ return
+ sleep_ms(10)
+
+ def _fcmd2(self, fmt, a0, a1=0, a2=0):
+ buf = self.buf[calcsize(fmt)]
+ pack_into(fmt, buf, 0, 2, a0, a1, a2)
+ self._send(buf)
+
+ def _fcmd2b(self, fmt, a0, a1, a2, a3, a4=0):
+ buf = self.buf[calcsize(fmt)]
+ pack_into(fmt, buf, 0, 2, a0, a1, a2, a3, a4)
+ self._send(buf)
+
+ def _waitfor(self, n, buf):
+ t = 5000
+ while t:
+ self.i2c.readfrom_into(self.i2c_addr, self.buf1)
+ if self.buf1[0] >= n:
+ self.i2c.readfrom_into(self.i2c_addr, buf)
+ return
+ t -= 1
+ sleep_ms(1)
+ raise OSError(uerrno.ETIMEDOUT)
+
+ def oflush(self, n=255):
+ t = 5000
+ while t:
+ self.i2c.readfrom_into(self.i2c_addr + 1, self.buf1)
+ r = self.buf1[0]
+ if r >= n:
+ return
+ t -= 1
+ machine.idle()
+ raise OSError(uerrno.ETIMEDOUT)
+
+ def iflush(self):
+ t = 5000
+ while t:
+ self.i2c.readfrom_into(self.i2c_addr, self.buf16)
+ if self.buf16[0] == 0:
+ return
+ t -= 1
+ sleep_ms(1)
+ raise OSError(uerrno.ETIMEDOUT)
+
+ #### MISC METHODS ####
+
+ @staticmethod
+ def rgb(r, g, b):
+ return ((b & 0xf8) << 8) | ((g & 0xfc) << 3) | (r >> 3)
+
+ @staticmethod
+ def clip_line(c, w, h):
+ while True:
+ ca = ce = 0
+ if c[1] < 0:
+ ca |= 8
+ elif c[1] > h:
+ ca |= 4
+ if c[0] < 0:
+ ca |= 1
+ elif c[0] > w:
+ ca |= 2
+ if c[3] < 0:
+ ce |= 8
+ elif c[3] > h:
+ ce |= 4
+ if c[2] < 0:
+ ce |= 1
+ elif c[2] > w:
+ ce |= 2
+ if ca & ce:
+ return False
+ elif ca | ce:
+ ca |= ce
+ if ca & 1:
+ if c[2] < c[0]:
+ c[0], c[2] = c[2], c[0]
+ c[1], c[3] = c[3], c[1]
+ c[1] += ((-c[0]) * (c[3] - c[1])) // (c[2] - c[0])
+ c[0] = 0
+ elif ca & 2:
+ if c[2] < c[0]:
+ c[0], c[2] = c[2], c[0]
+ c[1], c[3] = c[3], c[1]
+ c[3] += ((w - 1 - c[2]) * (c[3] - c[1])) // (c[2] - c[0])
+ c[2] = w - 1
+ elif ca & 4:
+ if c[0] == c[2]:
+ if c[1] >= h:
+ c[1] = h - 1
+ if c[3] >= h:
+ c[3] = h - 1
+ else:
+ if c[3] < c[1]:
+ c[0], c[2] = c[2], c[0]
+ c[1], c[3] = c[3], c[1]
+ c[2] += ((h - 1 - c[3]) * (c[2] - c[0])) // (c[3] - c[1])
+ c[3] = h - 1
+ else:
+ if c[0] == c[2]:
+ if c[1] < 0:
+ c[1] = 0
+ if c[3] < 0:
+ c[3] = 0
+ else:
+ if c[3] < c[1]:
+ c[0], c[2] = c[2], c[0]
+ c[1], c[3] = c[3], c[1]
+ c[0] += ((-c[1]) * (c[2] - c[0])) // (c[3] - c[1])
+ c[1] = 0
+ else:
+ return True
+
+ #### SETUP COMMANDS ####
+
+ def set_power(self, on):
+ self.pwr(on)
+ sleep_ms(15)
+
+ def set_orient(self, orient):
+ self._fcmd2('<BBB', 0x14, (orient & 3) + 4)
+ # update width and height variables
+ self.iflush()
+ self._send(b'\x02g0')
+ self._waitfor(4, self.buf[5])
+ self.w = self.buf[5][1]
+ self.h = self.buf[5][2]
+
+ def set_brightness(self, value):
+ self._fcmd2('<BBB', 0x16, value)
+
+ def set_i2c_addr(self, addr):
+ # 0x0e set i2c addr
+ if addr & 3:
+ raise ValueError('must specify mod 4 aligned address')
+ self._fcmd2('<BBW', 0x0e, 0x433249 | (addr << 24))
+
+ def set_uart_baudrate(self, baudrate):
+ try:
+ baudrate = _uart_baud_table[baudrate]
+ except KeyError:
+ raise ValueError('invalid baudrate')
+ self._fcmd2('<BBB', 0x18, baudrate)
+
+ def set_startup_deco(self, value):
+ self._fcmd2('<BBB', 0x19, value)
+
+ def save_to_flash(self):
+ self._send(b'\x02fn')
+
+ #### PIXEL ACCESS ####
+
+ def set_pixel(self, x, y, c):
+ self._fcmd2b('<BBBBH', 0x41, x, y, c)
+
+ def get_pixel(self, x, y):
+ self._fcmd2('<BBBB', 0x61, x, y)
+ t = 1000
+ while t:
+ self.i2c.readfrom_into(self.i2c_addr, self.buf1)
+ if self.buf1[0] >= 2:
+ self.i2c.readfrom_into(self.i2c_addr, self.buf[3])
+ return self.buf[3][1] | self.buf[3][2] << 8
+ t -= 1
+ sleep_ms(1)
+ raise OSError(uerrno.ETIMEDOUT)
+
+ def get_line(self, x, y, buf):
+ l = len(buf) // 2
+ self._fcmd2b('<BBBBB', 0x10, l, x, y)
+ l *= 2
+ t = 1000
+ while t:
+ self.i2c.readfrom_into(self.i2c_addr, self.buf1)
+ if self.buf1[0] >= l:
+ self.i2c.readfrom_into(self.i2c_addr, buf)
+ return
+ t -= 1
+ sleep_ms(1)
+ raise OSError(uerrno.ETIMEDOUT)
+
+ def screen_dump(self, buf, x=0, y=0, w=None, h=None):
+ if w is None:
+ w = self.w - x
+ if h is None:
+ h = self.h - y
+ if w <= 127:
+ line = bytearray(2 * w + 1)
+ line2 = None
+ else:
+ # split line if more than 254 bytes needed
+ buflen = (w + 1) // 2
+ line = bytearray(2 * buflen + 1)
+ line2 = memoryview(line)[:2 * (w - buflen) + 1]
+ for i in range(min(len(buf) // (2 * w), h)):
+ ix = i * w * 2
+ self.get_line(x, y + i, line)
+ buf[ix:ix + len(line) - 1] = memoryview(line)[1:]
+ ix += len(line) - 1
+ if line2:
+ self.get_line(x + buflen, y + i, line2)
+ buf[ix:ix + len(line2) - 1] = memoryview(line2)[1:]
+ ix += len(line2) - 1
+
+ def screen_load(self, buf):
+ l = self.w * self.h * 2+2
+ self._fcmd2b('<BBHBBB', 0x70, l, 16, self.w, self.h)
+ n = 0
+ ar = memoryview(buf)
+ while n < len(buf):
+ if len(buf) - n >= 0x200:
+ self._send(ar[n:n + 0x200])
+ n += 0x200
+ else:
+ self._send(ar[n:])
+ while n < self.w * self.h * 2:
+ self._send(b'\x00')
+ n += 1
+
+ #### TEXT COMMANDS ####
+
+ def set_pos(self, x, y):
+ self._fcmd2('<BBBB', 0x58, x, y)
+
+ def set_text_color(self, fg, bg):
+ self._fcmd2('<BBHH', 0x63, fg, bg)
+
+ def set_font(self, font, scale=0, bold=0, trans=0, scroll=0):
+ self._fcmd2('<BBBB', 0x46, (scroll << 7) | (trans << 6) | ((font & 3) << 4) | (bold & 0xf), scale & 0xff)
+
+ def write(self, s):
+ # TODO: eventually check for room in LCD input queue
+ self._send(s)
+
+ #### PRIMITIVE DRAWING COMMANDS ####
+
+ def set_pen(self, line, fill):
+ self._fcmd2('<BBHH', 0x50, line, fill)
+
+ def erase(self):
+ self._send(b'\x02\x45')
+
+ def dot(self, x, y):
+ if 0 <= x < self.w and 0 <= y < self.h:
+ self._fcmd2('<BBBB', 0x4b, x, y)
+
+ def rect(self, x, y, w, h, cmd=0x72):
+ if x + w <= 0 or y + h <= 0 or x >= self.w or y >= self.h:
+ return
+ elif x < 0 or y < 0:
+ left = top = True
+ if x < 0:
+ left = False
+ w += x
+ x = 0
+ if y < 0:
+ top = False
+ h += y
+ y = 0
+ if cmd == 0x51 or cmd == 0x72:
+ # draw interior
+ self._fcmd2b('<BBBBBB', 0x51, x, y, min(w, 255), min(h, 255))
+ if cmd == 0x57 or cmd == 0x72:
+ # draw outline
+ if left:
+ self._fcmd2b('<BBBBBB', 0x57, x, y, 1, min(h, 255))
+ if top:
+ self._fcmd2b('<BBBBBB', 0x57, x, y, min(w, 255), 1)
+ if x + w < self.w:
+ self._fcmd2b('<BBBBBB', 0x57, x + w, y, 1, min(h, 255))
+ if y + h < self.h:
+ self._fcmd2b('<BBBBBB', 0x57, x, y + h, min(w, 255), 1)
+ else:
+ self._fcmd2b('<BBBBBB', cmd, x, y, min(w, 255), min(h, 255))
+
+ def rect_outline(self, x, y, w, h):
+ self.rect(x, y, w, h, 0x57)
+
+ def rect_interior(self, x, y, w, h):
+ self.rect(x, y, w, h, 0x51)
+
+ def line(self, x1, y1, x2, y2):
+ ar4 = self.array4
+ ar4[0] = x1
+ ar4[1] = y1
+ ar4[2] = x2
+ ar4[3] = y2
+ if self.clip_line(ar4, self.w, self.h):
+ self._fcmd2b('<BBBBBB', 0x4c, ar4[0], ar4[1], ar4[2], ar4[3])
+
+ def dot_no_clip(self, x, y):
+ self._fcmd2('<BBBB', 0x4b, x, y)
+
+ def rect_no_clip(self, x, y, w, h):
+ self._fcmd2b('<BBBBBB', 0x72, x, y, w, h)
+
+ def rect_outline_no_clip(self, x, y, w, h):
+ self._fcmd2b('<BBBBBB', 0x57, x, y, w, h)
+
+ def rect_interior_no_clip(self, x, y, w, h):
+ self._fcmd2b('<BBBBBB', 0x51, x, y, w, h)
+
+ def line_no_clip(self, x1, y1, x2, y2):
+ self._fcmd2b('<BBBBBB', 0x4c, x1, y1, x2, y2)
+
+ def poly_dot(self, data):
+ if len(data) & 1:
+ raise ValueError('must specify even number of bytes')
+ self._fcmd2('<BBB', 0x71, len(data) // 2)
+ self._send(data)
+
+ def poly_line(self, data):
+ if len(data) & 1:
+ raise ValueError('must specify even number of bytes')
+ self._fcmd2('<BBB', 0x78, len(data) // 2)
+ self._send(data)
+
+ #### TOUCH COMMANDS ####
+
+ def touch_config(self, calib=False, save=False, irq=None):
+ self._fcmd2('<BBBB', 0x7a, (irq is not None) << 2 | save << 1 | calib, bool(irq) << 7)
+
+ def is_touched(self):
+ self._send(b'\x02T')
+ b = self.buf[4]
+ self._waitfor(3, b)
+ return b[1] >> 7 != 0
+
+ def get_touch(self):
+ self._send(b'\x02T') # implicit LCD output flush
+ b = self.buf[4]
+ self._waitfor(3, b)
+ return b[1] >> 7, b[2], b[3]
+
+ #### ADVANCED COMMANDS ####
+
+ def set_spi_win(self, x, y, w, h):
+ pack_into('<BBBHHHHHHHH', self.buf19, 0, 2, 0x55, 10, x, y, x + w - 1, y + h - 1, 0, 0, 0, 0xffff)
+ self._send(self.buf19)
+
+ def fast_spi(self, flush=True):
+ if flush:
+ self.oflush()
+ self._send(b'\x02\x12')
+ return self.spi
+
+ def show_framebuf(self, buf):
+ self.fast_spi().write(buf)
+
+ def set_scroll(self, on):
+ self._fcmd2('<BBB', 0x15, on)
+
+ def set_scroll_win(self, win, x=-1, y=0, w=0, h=0, vec=0, pat=0, fill=0x07e0, color=0):
+ pack_into('<BBBHHHHHHHH', self.buf19, 0, 2, 0x55, win, x, y, w, h, vec, pat, fill, color)
+ self._send(self.buf19)
+
+ def set_scroll_win_param(self, win, param, value):
+ self._fcmd2b('<BBBBH', 0x75, win, param, value)
+
+ def set_scroll_buf(self, s):
+ l = len(s)
+ if l > 32:
+ raise ValueError('length must be 32 or less')
+ self._fcmd2('<BBB', 0x11, l)
+ self._send(s)
+
+ def jpeg_start(self, l):
+ if l > 0xffff:
+ raise ValueError('length must be 65535 or less')
+ self.oflush()
+ self._fcmd2('<BBH', 0x6a, l)
+
+ def jpeg_data(self, buf):
+ self._send(buf)
+
+ def jpeg(self, buf):
+ self.jpeg_start(len(buf))
+ self.jpeg_data(buf)
+
+ def feed_wdt(self):
+ self._send(b'\x02\x17')
+
+ def reset(self):
+ self._send(b'\x02Y\xef\xbe\xad\xde')
+ sleep_ms(15)
diff --git a/drivers/display/lcd160cr_test.py b/drivers/display/lcd160cr_test.py
new file mode 100644
index 000000000..8b7ef4555
--- /dev/null
+++ b/drivers/display/lcd160cr_test.py
@@ -0,0 +1,171 @@
+# Driver test for official MicroPython LCD160CR display
+# MIT license; Copyright (c) 2017 Damien P. George
+
+import time, math, framebuf, lcd160cr
+
+def get_lcd(lcd):
+ if type(lcd) is str:
+ lcd = lcd160cr.LCD160CR(lcd)
+ return lcd
+
+def show_adc(lcd, adc):
+ data = [adc.read_core_temp(), adc.read_core_vbat(), 3.3]
+ try:
+ data[2] = adc.read_vref()
+ except:
+ pass
+ for i in range(3):
+ lcd.set_text_color((825, 1625, 1600)[i], 0)
+ if lcd.h == 160:
+ lcd.set_font(2)
+ lcd.set_pos(0, 100 + i * 16)
+ else:
+ lcd.set_font(2, trans=1)
+ lcd.set_pos(0, lcd.h-60 + i * 16)
+ lcd.write('%4s: ' % ('TEMP', 'VBAT', 'VREF')[i])
+ if i > 0:
+ s = '%6.3fV' % data[i]
+ else:
+ s = '%5.1f°C' % data[i]
+ if lcd.h == 160:
+ lcd.set_font(1, bold=0, scale=1)
+ else:
+ lcd.set_font(1, bold=0, scale=1, trans=1)
+ lcd.set_pos(45, lcd.h-60 + i * 16)
+ lcd.write(s)
+
+def test_features(lcd, orient=lcd160cr.PORTRAIT):
+ # if we run on pyboard then use ADC and RTC features
+ try:
+ import pyb
+ adc = pyb.ADCAll(12, 0xf0000)
+ rtc = pyb.RTC()
+ except:
+ adc = None
+ rtc = None
+
+ # set orientation and clear screen
+ lcd = get_lcd(lcd)
+ lcd.set_orient(orient)
+ lcd.set_pen(0, 0)
+ lcd.erase()
+
+ # create M-logo
+ mlogo = framebuf.FrameBuffer(bytearray(17 * 17 * 2), 17, 17, framebuf.RGB565)
+ mlogo.fill(0)
+ mlogo.fill_rect(1, 1, 15, 15, 0xffffff)
+ mlogo.vline(4, 4, 12, 0)
+ mlogo.vline(8, 1, 12, 0)
+ mlogo.vline(12, 4, 12, 0)
+ mlogo.vline(14, 13, 2, 0)
+
+ # create inline framebuf
+ offx = 14
+ offy = 19
+ w = 100
+ h = 75
+ fbuf = framebuf.FrameBuffer(bytearray(w * h * 2), w, h, framebuf.RGB565)
+ lcd.set_spi_win(offx, offy, w, h)
+
+ # initialise loop parameters
+ tx = ty = 0
+ t0 = time.ticks_us()
+
+ for i in range(300):
+ # update position of cross-hair
+ t, tx2, ty2 = lcd.get_touch()
+ if t:
+ tx2 -= offx
+ ty2 -= offy
+ if tx2 >= 0 and ty2 >= 0 and tx2 < w and ty2 < h:
+ tx, ty = tx2, ty2
+ else:
+ tx = (tx + 1) % w
+ ty = (ty + 1) % h
+
+ # create and show the inline framebuf
+ fbuf.fill(lcd.rgb(128 + int(64 * math.cos(0.1 * i)), 128, 192))
+ fbuf.line(w // 2, h // 2,
+ w // 2 + int(40 * math.cos(0.2 * i)),
+ h // 2 + int(40 * math.sin(0.2 * i)),
+ lcd.rgb(128, 255, 64))
+ fbuf.hline(0, ty, w, lcd.rgb(64, 64, 64))
+ fbuf.vline(tx, 0, h, lcd.rgb(64, 64, 64))
+ fbuf.rect(tx - 3, ty - 3, 7, 7, lcd.rgb(64, 64, 64))
+ for phase in (-0.2, 0, 0.2):
+ x = w // 2 - 8 + int(50 * math.cos(0.05 * i + phase))
+ y = h // 2 - 8 + int(32 * math.sin(0.05 * i + phase))
+ fbuf.blit(mlogo, x, y)
+ for j in range(-3, 3):
+ fbuf.text('MicroPython',
+ 5, h // 2 + 9 * j + int(20 * math.sin(0.1 * (i + j))),
+ lcd.rgb(128 + 10 * j, 0, 128 - 10 * j))
+ lcd.show_framebuf(fbuf)
+
+ # show results from the ADC
+ if adc:
+ show_adc(lcd, adc)
+
+ # show the time
+ if rtc:
+ lcd.set_pos(2, 0)
+ lcd.set_font(1)
+ t = rtc.datetime()
+ lcd.write('%4d-%02d-%02d %2d:%02d:%02d.%01d' % (t[0], t[1], t[2], t[4], t[5], t[6], t[7] // 100000))
+
+ # compute the frame rate
+ t1 = time.ticks_us()
+ dt = time.ticks_diff(t1, t0)
+ t0 = t1
+
+ # show the frame rate
+ lcd.set_pos(2, 9)
+ lcd.write('%.2f fps' % (1000000 / dt))
+
+def test_mandel(lcd, orient=lcd160cr.PORTRAIT):
+ # set orientation and clear screen
+ lcd = get_lcd(lcd)
+ lcd.set_orient(orient)
+ lcd.set_pen(0, 0xffff)
+ lcd.erase()
+
+ # function to compute Mandelbrot pixels
+ def in_set(c):
+ z = 0
+ for i in range(32):
+ z = z * z + c
+ if abs(z) > 100:
+ return i
+ return 0
+
+ # cache width and height of LCD
+ w = lcd.w
+ h = lcd.h
+
+ # create the buffer for each line and set SPI parameters
+ line = bytearray(w * 2)
+ lcd.set_spi_win(0, 0, w, h)
+ spi = lcd.fast_spi()
+
+ # draw the Mandelbrot set line-by-line
+ hh = ((h - 1) / 3.2)
+ ww = ((w - 1) / 2.4)
+ for v in range(h):
+ for u in range(w):
+ c = in_set((v / hh - 2.3) + (u / ww - 1.2) * 1j)
+ if c < 16:
+ rgb = c << 12 | c << 6
+ else:
+ rgb = 0xf800 | c << 6
+ line[2 * u] = rgb
+ line[2 * u + 1] = rgb >> 8
+ spi.write(line)
+
+def test_all(lcd, orient=lcd160cr.PORTRAIT):
+ lcd = get_lcd(lcd)
+ test_features(lcd, orient)
+ test_mandel(lcd, orient)
+
+print('To run all tests: test_all(<lcd>)')
+print('Individual tests are: test_features, test_mandel')
+print('<lcd> argument should be a connection, eg "X", or an LCD160CR object')
diff --git a/drivers/display/ssd1306.py b/drivers/display/ssd1306.py
new file mode 100644
index 000000000..53bcb0d2d
--- /dev/null
+++ b/drivers/display/ssd1306.py
@@ -0,0 +1,161 @@
+# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
+
+from micropython import const
+import time
+import framebuf
+
+
+# register definitions
+SET_CONTRAST = const(0x81)
+SET_ENTIRE_ON = const(0xa4)
+SET_NORM_INV = const(0xa6)
+SET_DISP = const(0xae)
+SET_MEM_ADDR = const(0x20)
+SET_COL_ADDR = const(0x21)
+SET_PAGE_ADDR = const(0x22)
+SET_DISP_START_LINE = const(0x40)
+SET_SEG_REMAP = const(0xa0)
+SET_MUX_RATIO = const(0xa8)
+SET_COM_OUT_DIR = const(0xc0)
+SET_DISP_OFFSET = const(0xd3)
+SET_COM_PIN_CFG = const(0xda)
+SET_DISP_CLK_DIV = const(0xd5)
+SET_PRECHARGE = const(0xd9)
+SET_VCOM_DESEL = const(0xdb)
+SET_CHARGE_PUMP = const(0x8d)
+
+
+class SSD1306:
+ def __init__(self, width, height, external_vcc):
+ self.width = width
+ self.height = height
+ self.external_vcc = external_vcc
+ self.pages = self.height // 8
+ self.buffer = bytearray(self.pages * self.width)
+ self.framebuf = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.MVLSB)
+ self.poweron()
+ self.init_display()
+
+ def init_display(self):
+ for cmd in (
+ SET_DISP | 0x00, # off
+ # address setting
+ SET_MEM_ADDR, 0x00, # horizontal
+ # resolution and layout
+ SET_DISP_START_LINE | 0x00,
+ SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
+ SET_MUX_RATIO, self.height - 1,
+ SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
+ SET_DISP_OFFSET, 0x00,
+ SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,
+ # timing and driving scheme
+ SET_DISP_CLK_DIV, 0x80,
+ SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,
+ SET_VCOM_DESEL, 0x30, # 0.83*Vcc
+ # display
+ SET_CONTRAST, 0xff, # maximum
+ SET_ENTIRE_ON, # output follows RAM contents
+ SET_NORM_INV, # not inverted
+ # charge pump
+ SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
+ SET_DISP | 0x01): # on
+ self.write_cmd(cmd)
+ self.fill(0)
+ self.show()
+
+ def poweroff(self):
+ self.write_cmd(SET_DISP | 0x00)
+
+ def contrast(self, contrast):
+ self.write_cmd(SET_CONTRAST)
+ self.write_cmd(contrast)
+
+ def invert(self, invert):
+ self.write_cmd(SET_NORM_INV | (invert & 1))
+
+ def show(self):
+ x0 = 0
+ x1 = self.width - 1
+ if self.width == 64:
+ # displays with width of 64 pixels are shifted by 32
+ x0 += 32
+ x1 += 32
+ self.write_cmd(SET_COL_ADDR)
+ self.write_cmd(x0)
+ self.write_cmd(x1)
+ self.write_cmd(SET_PAGE_ADDR)
+ self.write_cmd(0)
+ self.write_cmd(self.pages - 1)
+ self.write_data(self.buffer)
+
+ def fill(self, col):
+ self.framebuf.fill(col)
+
+ def pixel(self, x, y, col):
+ self.framebuf.pixel(x, y, col)
+
+ def scroll(self, dx, dy):
+ self.framebuf.scroll(dx, dy)
+
+ def text(self, string, x, y, col=1):
+ self.framebuf.text(string, x, y, col)
+
+
+class SSD1306_I2C(SSD1306):
+ def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
+ self.i2c = i2c
+ self.addr = addr
+ self.temp = bytearray(2)
+ super().__init__(width, height, external_vcc)
+
+ def write_cmd(self, cmd):
+ self.temp[0] = 0x80 # Co=1, D/C#=0
+ self.temp[1] = cmd
+ self.i2c.writeto(self.addr, self.temp)
+
+ def write_data(self, buf):
+ self.temp[0] = self.addr << 1
+ self.temp[1] = 0x40 # Co=0, D/C#=1
+ self.i2c.start()
+ self.i2c.write(self.temp)
+ self.i2c.write(buf)
+ self.i2c.stop()
+
+ def poweron(self):
+ pass
+
+
+class SSD1306_SPI(SSD1306):
+ def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
+ self.rate = 10 * 1024 * 1024
+ dc.init(dc.OUT, value=0)
+ res.init(res.OUT, value=0)
+ cs.init(cs.OUT, value=1)
+ self.spi = spi
+ self.dc = dc
+ self.res = res
+ self.cs = cs
+ super().__init__(width, height, external_vcc)
+
+ def write_cmd(self, cmd):
+ self.spi.init(baudrate=self.rate, polarity=0, phase=0)
+ self.cs(1)
+ self.dc(0)
+ self.cs(0)
+ self.spi.write(bytearray([cmd]))
+ self.cs(1)
+
+ def write_data(self, buf):
+ self.spi.init(baudrate=self.rate, polarity=0, phase=0)
+ self.cs(1)
+ self.dc(1)
+ self.cs(0)
+ self.spi.write(buf)
+ self.cs(1)
+
+ def poweron(self):
+ self.res(1)
+ time.sleep_ms(1)
+ self.res(0)
+ time.sleep_ms(10)
+ self.res(1)