Skip to content

Commit a055617

Browse files
committed
feature/driver/sh1107: oled driver & 1 bit image file
1 parent dc2f275 commit a055617

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

m5stack/fs/user/res/img/m5stack.pbm

685 Bytes
Binary file not shown.

m5stack/libs/driver/sh1107.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# https://www.displayfuture.com/Display/datasheet/controller/SH1107.pdf
2+
3+
from micropython import const
4+
from framebuf import FrameBuffer, MONO_VLSB, MONO_HMSB, MONO_HLSB
5+
6+
SET_CONTRAST = const(0x81)
7+
SET_ENTIRE_ON = const(0xA4)
8+
SET_NORM_INV = const(0xA6)
9+
SET_DISP = const(0xAE)
10+
SET_DCDC_MODE = const(0xAD)
11+
SET_MEM_MODE = const(0x20)
12+
SET_PAGE_ADDR = const(0xB0)
13+
SET_COL_LO_ADDR = const(0x00)
14+
SET_COL_HI_ADDR = const(0x10)
15+
SET_DISP_START_LINE = const(0xDC)
16+
SET_SEG_REMAP = const(0xA0)
17+
SET_MUX_RATIO = const(0xA8)
18+
SET_COM_OUT_DIR = const(0xC0)
19+
SET_DISP_OFFSET = const(0xD3)
20+
SET_DISP_CLK_DIV = const(0xD5)
21+
SET_PRECHARGE = const(0xD9)
22+
SET_VCOM_DESEL = const(0xDB)
23+
24+
TEST_CHUNK = const(8)
25+
26+
27+
class SH1107(FrameBuffer):
28+
def __init__(self, width, height, external_vcc):
29+
self.external_vcc = external_vcc
30+
if width == 128 and height == 64:
31+
self.page_mode = False
32+
elif (width == 64 and height == 128) or (width == 128 and height == 128):
33+
self.page_mode = True
34+
else:
35+
raise ValueError
36+
self.width = width
37+
self.height = height
38+
self.pages = self.height // 8
39+
self.line_bytes = self.width // 8
40+
size = self.width * self.height // 8
41+
self.curr_buffer = bytearray(b'\x00' * size) # self.fill(0)
42+
self.prev_buffer = bytearray(b'\xff' * size) # force full refresh
43+
super().__init__(self.curr_buffer, self.width, self.height,
44+
MONO_VLSB if self.page_mode else MONO_HMSB)
45+
self.init_display()
46+
self.fill(0)
47+
self.show()
48+
try:
49+
self.image(27, 0, "/flash/img/m5stack.pbm")
50+
self.show()
51+
except:
52+
pass
53+
54+
def init_display(self):
55+
for cmd in (
56+
SET_DISP | 0x00, # off
57+
# address setting
58+
# 0x00 = page, 0x01 = vertical
59+
SET_MEM_MODE | (0x00 if self.page_mode else 0x01),
60+
# resolution and layout
61+
SET_DISP_START_LINE, 0x00,
62+
SET_SEG_REMAP | 0x00, # 0x01 rotate 180 deg
63+
# 0x08 rotate 180 deg
64+
SET_COM_OUT_DIR | (0x00 if self.page_mode else 0x08),
65+
SET_MUX_RATIO, 0x7f, # always this?
66+
# offseted for 64 x 128 (Aliexpress 0.96")
67+
SET_DISP_OFFSET, 0x60 if self.width != self.height else 0x00,
68+
# timing and driving scheme
69+
SET_DISP_CLK_DIV, 0x50,
70+
SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,
71+
SET_VCOM_DESEL, 0x35, # 0.77 * Vcc
72+
SET_DCDC_MODE, 0x81, # on, 0.6 * switch freq
73+
# display
74+
SET_CONTRAST, 0x80, # very low to avoid uneven background
75+
SET_ENTIRE_ON | 0x00, # output follows RAM contents, not entire on
76+
SET_NORM_INV | 0x00 # 0x00 = not inverted, 0x01 = inverted
77+
):
78+
self.write_cmd(cmd)
79+
# buffers are initialized as if self.fill(0) was called
80+
self.show()
81+
self.poweron()
82+
83+
def poweroff(self):
84+
self.write_cmd(SET_DISP | 0x00)
85+
86+
def poweron(self):
87+
self.write_cmd(SET_DISP | 0x01)
88+
89+
def contrast(self, contrast):
90+
self.write_cmd(SET_CONTRAST)
91+
self.write_cmd(contrast)
92+
93+
def invert(self, invert):
94+
self.write_cmd(SET_NORM_INV | (invert & 1))
95+
96+
def image(self, x, y, filename):
97+
with open(filename, 'rb') as f:
98+
f.readline()
99+
f.readline()
100+
width, height = [int(v) for v in f.readline().split()]
101+
data = bytearray(f.read())
102+
image = FrameBuffer(data, width, height, MONO_HLSB)
103+
self.blit(image, x, y)
104+
105+
def show(self):
106+
if self.page_mode:
107+
self.show_page_mode()
108+
else:
109+
self.show_vert_mode()
110+
self.prev_buffer[:] = self.curr_buffer
111+
112+
def show_page_mode(self):
113+
for page in range(self.pages):
114+
noffs = page * self.width
115+
for col1, col2 in self.test_modified(noffs, self.width):
116+
c = col1 - noffs
117+
self.write_cmd(SET_PAGE_ADDR | page)
118+
self.write_cmd(SET_COL_LO_ADDR | (c & 0x0f))
119+
self.write_cmd(SET_COL_HI_ADDR | ((c & 0x70) >> 4))
120+
self.write_data(self.curr_buffer[col1: col2])
121+
# print('Write offsets {} : {}, col: {}'.format(col1, col2, c))
122+
123+
def show_vert_mode(self):
124+
for col in range(self.height):
125+
noffs = col * self.line_bytes
126+
for page1, page2 in self.test_modified(noffs, self.line_bytes):
127+
self.write_cmd(SET_PAGE_ADDR | (page1 - noffs))
128+
self.write_cmd(SET_COL_LO_ADDR | (col & 0x0f))
129+
self.write_cmd(SET_COL_HI_ADDR | ((col & 0x70) >> 4))
130+
self.write_data(self.curr_buffer[page1: page2])
131+
# print('Write offsets {} : {}, page: {}'.format(page1, page2, page1 - noffs))
132+
133+
def test_modified(self, offs, width):
134+
ptr = offs
135+
width += offs
136+
while ptr < width:
137+
# skip unmodified chunks
138+
while ptr < width and self.curr_buffer[ptr: ptr + TEST_CHUNK] == self.prev_buffer[ptr: ptr + TEST_CHUNK]:
139+
ptr += TEST_CHUNK
140+
141+
if ptr < width:
142+
first = ptr
143+
ptr += TEST_CHUNK
144+
# find modified chunks
145+
while ptr < width and self.curr_buffer[ptr:ptr + TEST_CHUNK] != self.prev_buffer[ptr:ptr + TEST_CHUNK]:
146+
ptr += TEST_CHUNK
147+
148+
yield first, ptr
149+
ptr += TEST_CHUNK
150+
151+
152+
class SH1107_I2C(SH1107):
153+
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
154+
self.i2c = i2c
155+
self.i2c_addr = addr
156+
self.temp = bytearray(2)
157+
self.write_list = [b'\x40', None] # Co=0, D/C#=1
158+
super().__init__(width, height, external_vcc)
159+
160+
def write_cmd(self, cmd):
161+
self.temp[0] = 0x80 # Co=1, D/C#=0
162+
self.temp[1] = cmd
163+
self.i2c.writeto(self.i2c_addr, self.temp)
164+
165+
def write_data(self, buf):
166+
self.write_list[1] = buf
167+
self.i2c.writevto(self.i2c_addr, self.write_list)

0 commit comments

Comments
 (0)