V0.2 Adapted for improved framebuf module

pull/7/head
Peter Hinch 2016-12-17 12:14:50 +00:00
rodzic 4d5ce589a5
commit c56753fde0
5 zmienionych plików z 34 dodań i 192 usunięć

Wyświetl plik

@ -9,12 +9,18 @@ presented [here](https://github.com/peterhinch/micropython-font-to-py.git).
![Picture](ssd1306.JPG)
## Release notes
V0.2 17th Dec 2016 The ``Writer`` class now uses the framebuf pixel method. This
trades a 2:1 drop in performance for portability between devices with different
mappings. File ssd1306_drv.py is no longer provided as the framebuf scrolling
bug is now fixed.
# Files
1. ssd1306_test.py A simple test program.
2. ssd1306.py A snapshot of the current official driver.
3. ssd1306_drv.py A temporary Python fix for a bug in the above.
4. writer.py A generic Writer class. Keeps track of the text insertion point
3. writer.py A generic Writer class. Keeps track of the text insertion point
over multiple fonts, handles newline and vertical scrolling if required.
In addition several font files are provided as samples.
@ -27,7 +33,7 @@ to a Pyboard. It is untested on other platforms, but I'd expect it to be
portable to any device supporting the official driver. If in doubt, install and
test this first.
Copy files 1-4 and ``freesans20.py`` to the target and issue
Copy files 1-3 and ``freesans20.py`` to the target and issue
```python
import ssd1306_test
@ -78,32 +84,12 @@ default text overrunning the bottom of the display will cause text above to
scroll up to accommodate it. Setting ``row_clip`` will override this behaviour
causing text to be clipped.
A final classmethod is provided for possible future use.
``mapping`` The current official ssd1306 driver configures the hardware to use
an unorthodox mapping of bytes onto pixels. The original plan for the
``framebuf`` module was that it should support vertical and horizontal mappings
only. Should the official ssd1306 driver be changed to use vertical mapping
(which the device supports) this method may be used to accommodate it. This mode
has been tested.
# Use of font_to_py.py
To convert font files to Python for use with this driver the default (vertical)
mapping and bit order should be used. The only optional argument which may be
needed is ``-f`` if fixed-width rendering is desired.
# Note
The official SSD1306 driver is based on the framebuf module which is in a state
of active development. The code presented here extends the official driver and
consequently may be less than stable if there is significant change to the
underlying framebuffer class. Further, the official driver's scroll method is
buggy and at the time of writing there is a moratorium on PR's. I have coded a
Python workround in the file ssd1306_drv.py. Hopefully in time this file will
become redundant and the official driver will support vertical scrolling over
long distances.
# License
Any code placed here is released under the MIT License (MIT).

Wyświetl plik

@ -32,7 +32,7 @@ class SSD1306:
self.external_vcc = external_vcc
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
self.framebuf = framebuf.FrameBuffer1(self.buffer, self.width, self.height)
self.framebuf = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.MVLSB)
self.poweron()
self.init_display()

Wyświetl plik

@ -1,122 +0,0 @@
# ssd1306_drv.py An implementation of a Display class for SSD1306 based displays
# V0.1 Peter Hinch Nov 2016
# https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display
# https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html
# Version supports vertical and "horizontal" modes.
# The MIT License (MIT)
#
# Copyright (c) 2016 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# SSD1306 classes to fix the official one whose scroll method is broken
# This also demonstrates a way to do scrolling for devices with true vertical
# mapping (the official driver sets the device into its "horizontal" mode).
# This is a hybrid mode where bytes are aligned vertically but arranged
# horizontally
import ssd1306
class SSD1306(object):
def __init__(self, device):
self.width = device.width # In pixels
self.height = device.height
self.bpc = (self.height + 7) >> 3
self.device = device
self.vmap = False # Currently the official driver uses "horizontal"
@property
def buffer(self): # property emulates underlying device
return self.device.buffer
def show(self):
self.device.show()
# Return map-independent offset into buffer
def _offset(self, x, y):
if self.vmap:
return y + x * self.bpc
else:
return y * self.width + x
def scroll(self, x, y):
dy = abs(y)
if dy:
self._scrolly(dy, y > 0)
dx = abs(x)
if dx:
self._scrollx(dx, x > 0)
def _scrolly(self, ystep, down):
buf = self.device.buffer
bpc = self.bpc
ystep_bytes = (ystep >> 3) + 1
ystep_bits = ystep & 7
if down:
for x in range(self.width):
for ydest in range(bpc - 1, -1, -1):
ysource = ydest - ystep_bytes
data = 0
if ysource + 1 >= 0:
data = buf[self._offset(x, ysource + 1)] << ystep_bits
if ysource >= 0:
data |= buf[self._offset(x, ysource)] >> 8 - ystep_bits
buf[self._offset(x, ydest)] = data
else:
for x in range(self.width):
for ydest in range(bpc):
ysource = ydest + ystep_bytes
data = 0
if ysource < bpc:
data = buf[self._offset(x, ysource)] << (8-ystep_bits)
if ysource - 1 < bpc:
data |= buf[self._offset(x, ysource - 1)] >> ystep_bits
buf[self._offset(x, ydest)] = data
def _scrollx(self, xstep, right): # scroll x
buf = self.device.buffer
bpc = self.bpc
for y in range(bpc):
if right: # Scroll right
for xdest in range(self.width - 1, -1, -1):
data = 0
xsource = xdest - xstep
if xsource >= 0:
data = buf[self._offset(xsource, y)]
buf[self._offset(xdest, y)] = data
else:
for xdest in range(0, self.width):
data = 0
xsource = xdest + xstep
if xsource < self.width:
data = buf[self._offset(xsource, y)]
buf[self._offset(xdest, y)] = data
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
device = ssd1306.SSD1306_I2C(width, height, i2c, addr, external_vcc)
super().__init__(device)
class SSD1306_SPI(SSD1306):
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
device = ssd1306.SSD1306_SPI(width, height, spi, dc, res, cs, external_vcc)
super().__init__(device)

Wyświetl plik

@ -26,8 +26,10 @@
# https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display
# https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html
# V0.2 Dec 17th 2016 Now supports updated framebuf module.
import machine
from ssd1306_drv import SSD1306_SPI, SSD1306_I2C
from ssd1306 import SSD1306_SPI, SSD1306_I2C
from writer import Writer
# Fonts

Wyświetl plik

@ -1,5 +1,5 @@
# writer.py Implements the Writer class.
# V0.1 Peter Hinch Nov 2016
# V0.2 Peter Hinch Dec 2016: supports updated framebuf module.
# The MIT License (MIT)
#
@ -24,21 +24,15 @@
# THE SOFTWARE.
# A Writer supports rendering text to a Display instance in a given font.
# Currently supports vertical mapping and the SSD1306 "pseudo-horizontal" map
# only
# Multiple Writer instances may be created, each rendering a font to the
# same Display object.
VERT = 0
HORIZ = 1
WEIRD = 2
class Writer(object):
text_row = 0 # attributes common to all Writer instances
text_row = 0 # attributes common to all Writer instances
text_col = 0
row_clip = False # Clip or scroll when screen full
col_clip = False # Clip or new line when row is full
bmap = WEIRD # SSD1306 pseudo-horizontal
row_clip = False # Clip or scroll when screen full
col_clip = False # Clip or new line when row is full
@classmethod
def set_textpos(cls, row, col):
@ -50,13 +44,6 @@ class Writer(object):
cls.row_clip = row_clip
cls.col_clip = col_clip
@classmethod
def mapping(cls, bmap):
if bmap in (VERT, HORIZ):
cls.bmap = bmap
else:
raise ValueError('Unsupported mapping')
def __init__(self, device, font):
super().__init__()
self.device = device
@ -65,8 +52,6 @@ class Writer(object):
raise OSError('Font must be vertically mapped')
self.screenwidth = device.width # In pixels
self.screenheight = device.height
div, mod = divmod(device.height, 8)
self.bytes_per_col = div + 1 if mod else div
def _newline(self):
height = self.font.height()
@ -83,41 +68,32 @@ class Writer(object):
self._printchar(char)
def _printchar(self, char):
bmap = Writer.bmap # Buffer mapping
if char == '\n':
self._newline()
return
fbuff = self.device.buffer
glyph, char_height, char_width = self.font.get_ch(char)
if Writer.text_row+char_height > self.screenheight and Writer.row_clip:
return
if Writer.text_row + char_height > self.screenheight:
if Writer.row_clip:
return
self._newline()
if Writer.text_col + char_width > self.screenwidth:
if Writer.col_clip:
return
else:
self._newline()
div, mod = divmod(char_height, 8)
gbytes = div + 1 if mod else div # No. of bytes per column of glyph
start_row, align = divmod(Writer.text_row, 8)
for scol in range(0, char_width): # Source column
dcol = scol + Writer.text_col # Destination column
dest_row_byte = start_row
for gbyte in range(gbytes): # Each glyph byte in column
if bmap == VERT:
dest = dcol * self.bytes_per_col + dest_row_byte
elif bmap == WEIRD:
dest = dcol + dest_row_byte * self.screenwidth
source = scol * gbytes + gbyte
data = fbuff[dest] & (0xff >> (8 - align))
fbuff[dest] = data | glyph[source] << align
if align and (dest_row_byte + 1) < self.bytes_per_col:
if bmap == VERT:
dest += 1
elif bmap == WEIRD:
dest += self.screenwidth
data = fbuff[dest] & (0xff << align)
fbuff[dest] = data | glyph[source] >> (8 - align)
dest_row_byte += 1
if dest_row_byte >= self.bytes_per_col:
gbytes = div + 1 if mod else div # No. of bytes per column of glyph
device = self.device
for scol in range(char_width): # Source column
dcol = scol + Writer.text_col # Destination column
drow = Writer.text_row # Destination row
for srow in range(char_height): # Source row
gbyte, gbit = divmod(srow, 8)
if drow >= self.screenheight:
break
if gbit == 0: # Next glyph byte
data = glyph[scol * gbytes + gbyte]
device.pixel(dcol, drow, data & (1 << gbit))
drow += 1
Writer.text_col += char_width