From fa02ecc079f3c45c1a11491cbe4a1075f6b1125f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Jel=C3=ADnek?= <jelinva4@fel.cvut.cz> Date: Fri, 7 Mar 2025 22:24:55 +0100 Subject: [PATCH] Optimize sending data to the display --- lib/cube/sh1106.py | 92 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 80 insertions(+), 12 deletions(-) diff --git a/lib/cube/sh1106.py b/lib/cube/sh1106.py index 0f90463..1945dac 100644 --- a/lib/cube/sh1106.py +++ b/lib/cube/sh1106.py @@ -44,6 +44,7 @@ _SET_SEG_REMAP = const(0xa0) _LOW_COLUMN_ADDRESS = const(0x00) _HIGH_COLUMN_ADDRESS = const(0x10) _SET_PAGE_ADDRESS = const(0xB0) +_SET_OFFSET = const(0xD3) # Function to generate n equidistant points in range <a,b> def linspace(a, b, n): @@ -67,6 +68,8 @@ class SH1106(framebuf.FrameBuffer): self.pages = self.height // 8 self.bufsize = self.pages * self.width self.renderbuf = bytearray(self.bufsize) + self.pages_to_update = 0 + if self.rotate90: self.displaybuf = bytearray(self.bufsize) # HMSB is required to keep the bit order in the render buffer @@ -94,6 +97,7 @@ class SH1106(framebuf.FrameBuffer): def init_display(self): self.reset() self.fill(0) + self.show() self.poweron() # rotate90 requires a call to flip() for setting up. self.flip(self.flip_en) @@ -125,18 +129,37 @@ class SH1106(framebuf.FrameBuffer): def invert(self, invert): self.write_cmd(_SET_NORM_INV | (invert & 1)) - def show(self): + def show(self, full_update = False): # self.* lookups in loops take significant time (~4fps). (w, p, db, rb) = (self.width, self.pages, self.displaybuf, self.renderbuf) if self.rotate90: for i in range(self.bufsize): db[w * (i % p) + (i // p)] = rb[i] - for page in range(self.height // 8): - self.write_cmd(_SET_PAGE_ADDRESS | page) - self.write_cmd(_LOW_COLUMN_ADDRESS | 2) - self.write_cmd(_HIGH_COLUMN_ADDRESS | 0) - self.write_data(db[(w*page):(w*page+w)]) + if full_update: + pages_to_update = (1 << self.pages) - 1 + else: + pages_to_update = self.pages_to_update + #print("Updating pages: {:08b}".format(pages_to_update)) + for page in range(self.pages): + if (pages_to_update & (1 << page)): + self.write_cmd(_SET_PAGE_ADDRESS | page) + self.write_cmd(_LOW_COLUMN_ADDRESS | 2) + self.write_cmd(_HIGH_COLUMN_ADDRESS | 0) + self.write_data(db[(w*page):(w*page+w)]) + self.pages_to_update = 0 + + def register_updates(self, y0, y1=None): + # this function takes the top and optional bottom address of the changes made + # and updates the pages_to_change list with any changed pages + # that are not yet on the list + start_page = max(0, y0 // 8) + end_page = max(0, y1 // 8) if y1 is not None else start_page + # rearrange start_page and end_page if coordinates were given from bottom to top + if start_page > end_page: + start_page, end_page = end_page, start_page + for page in range(start_page, end_page+1): + self.pages_to_update |= 1 << page def reset(self, res): if res is not None: @@ -147,6 +170,51 @@ class SH1106(framebuf.FrameBuffer): res(1) time.sleep_ms(20) + def pixel(self, x, y, color=None): + if color is None: + return super().pixel(x, y) + else: + super().pixel(x, y , color) + page = y // 8 + self.pages_to_update |= 1 << page + + def text(self, text, x, y, color=1): + super().text(text, x, y, color) + self.register_updates(y, y+7) + + def line(self, x0, y0, x1, y1, color): + super().line(x0, y0, x1, y1, color) + self.register_updates(y0, y1) + + def hline(self, x, y, w, color): + super().hline(x, y, w, color) + self.register_updates(y) + + def vline(self, x, y, h, color): + super().vline(x, y, h, color) + self.register_updates(y, y+h-1) + + def fill(self, color): + super().fill(color) + self.pages_to_update = (1 << self.pages) - 1 + + def blit(self, fbuf, x, y, key=-1, palette=None): + super().blit(fbuf, x, y, key, palette) + self.register_updates(y, y+self.height) + + def scroll(self, x, y): + # my understanding is that scroll() does a full screen change + super().scroll(x, y) + self.pages_to_update = (1 << self.pages) - 1 + + def fill_rect(self, x, y, w, h, color): + super().fill_rect(x, y, w, h, color) + self.register_updates(y, y+h-1) + + def rect(self, x, y, w, h, color): + super().rect(x, y, w, h, color) + self.register_updates(y, y+h-1) + def draw_bar_chart_v(self, current_val, x, y, w, h, low_lim=0, high_lim=100, no_of_ticks=5, label= None, redraw=False): if(redraw): self.init_display() @@ -167,7 +235,7 @@ class SH1106(framebuf.FrameBuffer): self.fill_rect(x, y - h, w, h - level, 0); self.rect(x, y - h, w, h, 1); self.fill_rect(x, y - level, w, level, 1); - self.show() + self.show(True) return redraw def draw_bar_chart_h(self, current_val, x, y, w, h, low_lim=0, high_lim=100, no_of_ticks=5, label= None, redraw=False): @@ -190,7 +258,7 @@ class SH1106(framebuf.FrameBuffer): self.fill_rect(x + level, y - h, w - level, h, 0); self.rect(x, y - h, w, h, 1); self.fill_rect(x, y - h, level, h, 1); - self.show() + self.show(True) return redraw def draw_dial(self, curval, cx, cy, r, loval, hival, no_of_steps, sa, label, Redraw): @@ -267,7 +335,7 @@ class SH1106(framebuf.FrameBuffer): self.ply = ly self.prx = rx self.pry = ry - self.show() + self.show(True) return Redraw def continuous_graph(self, x, y, gx, gy, w, h, xlo, xhi, ylo, yhi, label, Redraw): @@ -314,7 +382,7 @@ class SH1106(framebuf.FrameBuffer): self.data_points = 0 # up until now print sends data to a video buffer NOT the screen # this call sends the data to the screen - self.show(); + self.show(True); return Redraw def fill_circle(self, x0, y0, radius, color): @@ -338,7 +406,7 @@ class SH1106(framebuf.FrameBuffer): self.vline(int(x0 + y), int(y0 - x), int(2*x + 1), color) self.vline(int(x0 - x), int(y0 - y), int(2*y + 1), color) self.vline(int(x0 - y), int(y0 - x), int(2*x + 1), color) - self.show() + self.show(True) def fill_triangle(self, x0, y0, x1, y1, x2, y2, color): # Filled triangle drawing function. Will draw a filled triangle around @@ -406,7 +474,7 @@ class SH1106(framebuf.FrameBuffer): a, b = b, a self.hline(a, y, b-a+1, color) y += 1 - self.show() + self.show(True) def draw_arrow(self, x: int, y: int, dir: int, c: int = 1): j_max = 2 -- GitLab