def draw_text8x8(self, x, y, text, color, landscape=False): """Draw text using built-in MicroPython 8x8 bit font. Args: x (int): Starting X position. y (int): Starting Y position. text (string): Text to draw. color (int): RGB565 color value. landscape (bool): Orientation (default: False = portrait) """ text_length = len(text) * 8 # Confirm coordinates in boundary if self.is_off_grid(x, y, x + 7, y + 7): return # Rearrange color r = (color & 0xF800) >> 8 g = (color & 0x07E0) >> 3 b = (color & 0x1F) << 3 buf = bytearray(text_length * 16) fbuf = FrameBuffer(buf, text_length, 8, RGB565) fbuf.text(text, 0, 0, color565(b, r, g)) if landscape: self.write_cmd(self.SET_REMAP, 0x77) # Vertical address reverse self.block(self.width - (x + 8), y, (self.width - (x + 8)) + 7, y + text_length - 1, buf) self.write_cmd(self.SET_REMAP, 0x74) # Switch back to horizontal else: self.block(x, y, x + text_length - 1, y + 7, buf)
def draw_icon(oled_display_obj: framebuf.FrameBuffer, icon_bytes: bytes, position_x: int, position_y: int, show: bool = False): width = int.from_bytes(icon_bytes[:2], 'big') height = int.from_bytes(icon_bytes[2:4], 'big') y = 0 x = 0 for m_byte in icon_bytes[4:]: for byte_index in range(8): pixel_value = m_byte & (1 << byte_index) oled_display_obj.pixel(position_x + x, position_y + y, pixel_value) x = x + 1 if x >= width: x = 0 y = y + 1 if y >= height: if show: oled_display_obj.show() return
def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False): self.i2c = i2c self.addr = addr self.temp = bytearray(2) self.width = width self.height = height self.external_vcc = external_vcc self.pages = self.height // 8 self.buffer = bytearray(self.pages * self.width) from framebuf import FrameBuffer, MVLSB self.framebuf = FrameBuffer(self.buffer, self.width, self.height, MVLSB) self.init_display()
def scroll(self, s, c=0xFFFF, delay_ms=100): """ Make a text scrolling on the screen with the c color. delay_ms is the time between 2 successive frames """ _w = (len(s) + 2) * 8 _buf = bytearray( (len(s) + 2) * 8 ) # 8 columns * 1 byte (8 rows) needed to display a char (each char = 8x8 pixels) _fb = FrameBuffer(_buf, _w, 8, MONO_VLSB) _fb.text(" %s " % s, 0, 0, 1) for cols in range(_w - 8): for x in range(8): for y in range(8): self.pixel(x, y, c if _buf[cols + x] & (0x1 << y) else 0x0) self.update() time.sleep_ms(delay_ms) del (_fb) del (_buf)
def _pbm_decode(self, img_arrays): next_value = bytearray() pnm_header = [] stat = True index = 3 while stat: next_byte = bytes([img_arrays[index]]) if next_byte == b"#": while bytes([img_arrays[index]]) not in [b"", b"\n"]: index += 1 if not next_byte.isdigit(): if next_value: pnm_header.append( int("".join(["%c" % char for char in next_value]))) next_value = bytearray() else: next_value += next_byte if len(pnm_header) == 2: stat = False index += 1 pixel_arrays = img_arrays[index:] if self.invert == 1: for i in range(len(pixel_arrays)): pixel_arrays[i] = (~pixel_arrays[i]) & 0xff return FrameBuffer(pixel_arrays, pnm_header[0], pnm_header[1], 3)
def __init__(self, filename, display): with MagickWand() as wand: wand.read_image(filename) # make the image fit img_w, img_h = wand.image_width, wand.image_height disp_w, disp_h = display._screen.width, display._screen.height if img_w < disp_w and img_h < disp_h: # if the image is smaller than the screen, extend it with a # white border # TODO: if the screen is >= 2x the image, double the image size # instead of adding a border x = -(disp_w - img_w) // 2 y = -(disp_h - img_h) // 2 wand.extent_image(disp_w, disp_h, x, y) elif img_w > disp_w and img_h > disp_h: # FIXME: figure out which resize method is best and shrink the # image pass # convert to native format (1bpp) # TODO: if source image is color, we should dither or make dither # an optional parameter wand.image_format = 'GRAY' wand.image_depth = 1 # then convert to micropython framebuf so we can blit data = wand.image_blob self._framebuf = FrameBuffer(data, wand.image_width, wand.image_height, MONO_HLSB)
def init_fb(self): if self.buffer is None: self.buffer = bytearray(self.width * self.height * 2) if self.fb is None: self.fb = FrameBuffer(self.buffer, self.width, self.height, framebuf.RGB565) # noqa: E501 return self.fb
def __init__(self, spi, cs=14, dc=27, rst=33, bl=32): self.buffer = bytearray(32) self.letter = FrameBuffer(bytearray(8), 8, 8, MONO_HLSB) self.spi = spi self.cs = Pin(cs, Pin.OUT) self.dc = Pin(dc, Pin.OUT) self.rst = Pin(rst, Pin.OUT) self.bl = Pin(bl, Pin.OUT) self.width = 320 self.height = 240 self.char_width = 16 self.char_height = 16 self.offset = 0 self.background = color565(0, 0, 0) self._reset() self._setup()
def large_centered_text(fb, s, avail_width, y): x = (avail_width - font.str_width(s)) // 2 for c in s: ch, width = font.get_ch(c) chfb = FrameBuffer(bytearray(ch), width, font.height, MONO_VMSB) fb.blit(chfb, x, y) x += width
def _bmp_decode(self, img_arrays): file_size = int.from_bytes(img_arrays[2:6], 'little') offset = int.from_bytes(img_arrays[10:14], 'little') width = int.from_bytes(img_arrays[18:22], 'little') height = int.from_bytes(img_arrays[22:26], 'little') bpp = int.from_bytes(img_arrays[28:30], 'little') if bpp != 1: raise TypeError("Only support 1 bit color bmp") line_bytes_size = (bpp * width + 31) // 32 * 4 array_size = width * abs(height) // 8 pixel_arrays = bytearray(array_size) if width % 8: array_row = width // 8 + 1 else: array_row = width // 8 array_col = height # print("fileSize:{}, offset: {} ".format(file_size, offset)) # print("width:{}, height: {},bit_count:{},line_bytes_size:{},array_size:{},".format( # width, height, bpp, line_bytes_size, array_size)) # print('array_col:{},array_row:{}'.format(array_col, array_row)) for i in range(array_col): for j in range(array_row): index = -(array_row * (i + 1) - j) _offset = offset + i * line_bytes_size + j if self.invert == 0: pixel_byte = (~img_arrays[_offset]) & 0xff else: pixel_byte = img_arrays[_offset] pixel_arrays[index] = pixel_byte return FrameBuffer(pixel_arrays, width, height, 3)
def main(): screen = LCD12864(("192.168.31.203", 10086)) image_file = os.path.join(CURRENT_PATH, "test.pbm") with open(image_file, "rb") as f: w, h, type, data, comment = read_image(f) image = FrameBuffer(data, w, h, MONO_HLSB) screen.blit(image, (128 - w) // 2, 0) screen.show()
def __init__(self, gap_size): # 创建鸟和管道的framebuffer self.bird_fb = FrameBuffer(BIRD, bird_size[0], bird_size[1], framebuf.MONO_HLSB) self.pipe_top_fb = FrameBuffer(PIPE_TOP, pipe_size[0], pipe_size[1], framebuf.MONO_HLSB) self.pipe_down_fb = FrameBuffer(PIPE_DOWN, pipe_size[0], pipe_size[1], framebuf.MONO_HLSB) self.gap_size = gap_size self.high_score = 0 self.pressed = False self.game_state = 0 self.flappy_bird = None self.obstacle_1 = None self.obstacle_2 = None
def __init__(self, width, height, channels, brightness=1): self.height = height self.width = width self.channels = channels self.pixels = neopixel.NeoPixel(board.D18, width*height, bpp=channels, brightness=brightness, auto_write=False) self.frameBuffers = ( FrameBuffer(bytearray(width * height * channels), width, height, channels), FrameBuffer(bytearray(width * height * channels), width, height, channels) ) self.activeLock = threading.Lock() self.active = 0 self.inactive = 1 self.worker = threading.Thread(target=self.__write, daemon=True) self.worker.start()
def framebuffer(self): """Creates a new framebuffer for the screen returns a framebuf.FrameBuffer object used for drawing and a bytearray object to be passed to self.update() """ data = bytearray(self._fix_info.line_length * self.height) if self._fix_info.visual in (_FB_VISUAL_MONO01, _FB_VISUAL_MONO10): format = MONO_HMSB fbuf = FrameBuffer(data, self.width, self.height, format, self._fix_info.line_length // self.bpp * 8) return fbuf, data
def __init__(self): self.EN_DYN = Pin(16) self.EN_DYN.on() self.SPEED_PULSE = Pin(15) self.isAP = False self.oled = FrameBuffer(bytearray(128 * 64 // 8), 128, 64, MONO_HLSB, True) self.ina = Ina219(None) self.btns = Btns([1, 2, 3, 4])
def framebuffer(self): """Creates a new frame buffer for the display Returns: A ``framebuf.FrameBuffer`` object used for drawing and a bytearray object to be passed to :py:meth:`update` """ data = bytearray(self._fix_info.line_length * self.height) if self._fix_info.visual in (_FB_VISUAL_MONO01, _FB_VISUAL_MONO10): fmt = MONO_HMSB fbuf = FrameBuffer(data, self.width, self.height, fmt, self._fix_info.line_length // self.bpp * 8) return fbuf, data
def __init__(self): self.textbuffer = TextBuffer(cols, rows) # make the framebuffer we draw into the size of one line of text as that's all we need self.buf = bytearray(screen_width * font_height // 8) # the screen defaults to portrait and we want to use it in landscape so we have to rotate as we go, unfortunately. that's why dimensions look swapped around self.fb = FrameBuffer(self.buf, font_height, screen_width, MONO_HLSB) sck = Pin(18, Pin.OUT) mosi = Pin(23, Pin.OUT) miso = Pin(19, Pin.IN) spi = SPI(2, baudrate=80000000, polarity=0, phase=0, sck=sck, mosi=mosi, miso=miso) cs = Pin(5, Pin.OUT) dc = Pin(17, Pin.OUT) rst = Pin(27, Pin.OUT) busy = Pin(35, Pin.IN) self.epd = EPD(spi, cs, dc, rst, busy) self.epd.init() self.clear_screen() # we were in slow mode for the initial clear on startup, we're now going to fast mode as that's the most likely one we'll need next self.mode = 'slow' self.set_fast() self.running = False self.last_change = None
def __init__(self): """ Driver for Eink display. Provides helper functionality for displaying data. Assumes a hardware SPI connection on GPIO 14 (HSCLK) and GPIO 13 (HMOSI)/ """ self.spi = SPI(SPI_ID) self.frame_byte_array = bytearray(EPD_WIDTH * EPD_HEIGHT // NUMBER_OF_BITS) self.frame_buffer = FrameBuffer( self.frame_byte_array, EPD_WIDTH, EPD_HEIGHT, MONO_HLSB, ) self.driver = EPD(self.spi, EINK_CS, EINK_DC, EINK_RESET, EINK_BUSY)
def chars(self, str, x, y): str_w = self._font.get_width(str) div, rem = divmod(self._font.height(), 8) nbytes = div + 1 if rem else div buf = bytearray(str_w * nbytes) pos = 0 for ch in str: glyph, char_w = self._font.get_ch(ch) for row in range(nbytes): index = row * str_w + pos for i in range(char_w): buf[index + i] = glyph[nbytes * i + row] pos += char_w fb = FrameBuffer(buf, str_w, self._font.height(), MONO_VLSB) self.blit(fb, x, y, str_w, self._font.height()) return x + str_w
def draw_bitmap_raw(self, path, x, y, w, h, invert=False, rotate=0): """Load raw bitmap from disc and draw to screen. Args: path (string): Image file path. x (int): x-coord of image. y (int): y-coord of image. w (int): Width of image. h (int): Height of image. invert (bool): True = invert image, False (Default) = normal image. rotate(int): 0, 90, 180, 270 Notes: w x h cannot exceed 2048 """ if rotate == 90 or rotate == 270: w, h = h, w # Swap width & height if landscape buf_size = w * h with open(path, "rb") as f: if rotate == 0: buf = bytearray(f.read(buf_size)) elif rotate == 90: buf = bytearray(buf_size) for x1 in range(w - 1, -1, -1): for y1 in range(h): index = (w * y1) + x1 buf[index] = f.read(1)[0] elif rotate == 180: buf = bytearray(buf_size) for index in range(buf_size - 1, -1, -1): buf[index] = f.read(1)[0] elif rotate == 270: buf = bytearray(buf_size) for x1 in range(1, w + 1): for y1 in range(h - 1, -1, -1): index = (w * y1) + x1 - 1 buf[index] = f.read(1)[0] if invert: for i, _ in enumerate(buf): buf[i] ^= 0xFF fbuf = FrameBuffer(buf, w, h, GS8) self.monoFB.blit(fbuf, x, y)
def get_matrix(self): if self.data_cache is None: self.make() if not self.border: return self.modules width = len(self.modules) + self.border * 2 buf = bytearray(width * (width + 7) // 8) fb = FrameBuffer(buf, width, width, MONO_HLSB) fb.fill(0) y = self.border for module in self.modules: x = self.border for p in module: fb.pixel(x, y, p) x += 1 y += 1 return (fb, width)
send(str(user_info)) # send user info to server config = receiveJSON(recv_msg(client).decode(FORMAT)) inPins = config["in"] outPins = config["out"] while True: reads = [] for pin in inPins: val = Pin(pin, Pin.IN, Pin.PULL_UP).value() reads.append(val) send(str(reads)) resp = recv_msg(client) for i in (0, len(outPins) - 1): pinNumber = outPins[i] pinValue = resp[i] Pin(pinNumber, Pin.OUT).value(not pinValue) screen = resp[i + 1:] fbuf = FrameBuffer(screen, 128, 64, MONO_VLSB) oled.blit(fbuf, 0, 0) oled.rotate(True) oled.show() sleep(0.01) send(DISCONNECT_MESSAGE)
def load_sprite(self, path, w, h, invert=False, rotate=0): """Load MONO_HMSB bitmap from disc to sprite. Args: path (string): Image file path. w (int): Width of image. h (int): Height of image. invert (bool): True = invert image, False (Default) = normal image. rotate(int): 0, 90, 180, 270 Notes: w x h cannot exceed 2048 """ array_size = w * h with open(path, "rb") as f: buf = bytearray(f.read(array_size)) fb = FrameBuffer(buf, w, h, MONO_HMSB) if rotate == 0 and invert is True: # 0 degrees fb2 = FrameBuffer(bytearray(array_size), w, h, MONO_HMSB) for y1 in range(h): for x1 in range(w): fb2.pixel(x1, y1, fb.pixel(x1, y1) ^ 0x01) fb = fb2 elif rotate == 90: # 90 degrees byte_width = (w - 1) // 8 + 1 adj_size = h * byte_width fb2 = FrameBuffer(bytearray(adj_size), h, w, MONO_HMSB) for y1 in range(h): for x1 in range(w): if invert is True: fb2.pixel(y1, x1, fb.pixel(x1, (h - 1) - y1) ^ 0x01) else: fb2.pixel(y1, x1, fb.pixel(x1, (h - 1) - y1)) fb = fb2 elif rotate == 180: # 180 degrees fb2 = FrameBuffer(bytearray(array_size), w, h, MONO_HMSB) for y1 in range(h): for x1 in range(w): if invert is True: fb2.pixel( x1, y1, fb.pixel((w - 1) - x1, (h - 1) - y1) ^ 0x01) else: fb2.pixel(x1, y1, fb.pixel((w - 1) - x1, (h - 1) - y1)) fb = fb2 elif rotate == 270: # 270 degrees byte_width = (w - 1) // 8 + 1 adj_size = h * byte_width fb2 = FrameBuffer(bytearray(adj_size), h, w, MONO_HMSB) for y1 in range(h): for x1 in range(w): if invert is True: fb2.pixel(y1, x1, fb.pixel((w - 1) - x1, y1) ^ 0x01) else: fb2.pixel(y1, x1, fb.pixel((w - 1) - x1, y1)) fb = fb2 return fb
import machine from framebuf import FrameBuffer, MONO_HLSB, RGB565 from time import sleep_ms scl = machine.Pin('X9') sda = machine.Pin('X10') i2c = machine.I2C(scl=scl, sda=sda) frame_size = [64, 32] text = "Test" # Up to 8 characters in a 64px wide screen text_hsize = len(text) * 8 centered_text_start = [int((frame_size[0] / 2) - (text_hsize / 2)), int((frame_size[1] / 2) - (8 / 2))] # Frame buffers main_frame = FrameBuffer(bytearray(frame_size[0] * frame_size[1] // 8), frame_size[0], frame_size[1], MONO_HLSB) text_frame = FrameBuffer(bytearray(text_hsize * 8 // 8), text_hsize, 8, MONO_HLSB) pixel_frame = FrameBuffer(bytearray(1), 1, 1, MONO_HLSB) pixel_frame_black = FrameBuffer(bytearray(1), 1, 1, MONO_HLSB) # Text text_frame.fill(0) text_frame.text(text, 0, 0, 1) # Single-Pixels pixel_frame.fill(0) pixel_frame.pixel(0, 0, 1) pixel_frame_black.pixel(0, 0, 0) # Drawing text_frame on top of main_frame
def get_letter(self, letter, invert=False, rotate=0): """Convert letter byte data to pixels. Args: letter (string): Letter to return (must exist within font). invert (bool): True = white text, False (Default) black text. rotate (int): rotation (default: 0) Returns: (FrameBuffer): Pixel data in MONO_VLSB. (int, int): Letter width and height. """ # Get index of letter letter_ord = ord(letter) - self.start_letter # Confirm font contains letter if letter_ord >= self.letter_count: print('Font does not contain character: ' + letter) return b'', 0, 0 bytes_per_letter = self.bytes_per_letter offset = letter_ord * bytes_per_letter mv = memoryview(self.letters[offset:offset + bytes_per_letter]) # Get width of letter (specified by first byte) width = mv[0] height = self.height byte_height = (height - 1) // 8 + 1 # Support fonts up to 5 bytes high if byte_height > 6: print("Error: maximum font byte height equals 6.") return b'', 0, 0 array_size = width * byte_height ba = bytearray(mv[1:array_size + 1]) # Set inversion and re-order bytes if height > 1 byte pos = 0 ba2 = bytearray(array_size) if invert is True: # 0 bit is black/red so inverted is default for i in range(0, array_size, byte_height): ba2[pos] = ba[i] if byte_height > 1: ba2[pos + width] = ba[i + 1] if byte_height > 2: ba2[pos + width * 2] = ba[i + 2] if byte_height > 3: ba2[pos + width * 3] = ba[i + 3] if byte_height > 4: ba2[pos + width * 4] = ba[i + 4] if byte_height > 5: ba2[pos + width * 5] = ba[i + 5] pos += 1 else: # Use XOR to negate inversion for i in range(0, array_size, byte_height): ba2[pos] = ba[i] ^ 0xFF if byte_height > 1: ba2[pos + width] = ba[i + 1] ^ 0xFF if byte_height > 2: ba2[pos + width * 2] = ba[i + 2] ^ 0xFF if byte_height > 3: ba2[pos + width * 3] = ba[i + 3] ^ 0xFF if byte_height > 4: ba2[pos + width * 4] = ba[i + 4] ^ 0xFF if byte_height > 5: ba2[pos + width * 5] = ba[i + 5] ^ 0xFF pos += 1 fb = FrameBuffer(ba2, width, height, MONO_VLSB) if rotate == 0: # 0 degrees return fb, width, height elif rotate == 90: # 90 degrees byte_width = (width - 1) // 8 + 1 adj_size = height * byte_width fb2 = FrameBuffer(bytearray(adj_size), height, width, MONO_VLSB) for y in range(height): for x in range(width): fb2.pixel(y, x, fb.pixel(x, (height - 1) - y)) return fb2, height, width elif rotate == 180: # 180 degrees fb2 = FrameBuffer(bytearray(array_size), width, height, MONO_VLSB) for y in range(height): for x in range(width): fb2.pixel(x, y, fb.pixel((width - 1) - x, (height - 1) - y)) return fb2, width, height elif rotate == 270: # 270 degrees byte_width = (width - 1) // 8 + 1 adj_size = height * byte_width fb2 = FrameBuffer(bytearray(adj_size), height, width, MONO_VLSB) for y in range(height): for x in range(width): fb2.pixel(y, x, fb.pixel((width - 1) - x, y)) return fb2, height, width
def bitmap(self, bitmap, x, y, w, h): fb = FrameBuffer(bytearray(bitmap), w, h, MONO_VLSB) self.blit(fb, x, y, w, h) return x + w
class ILI9341: def __init__(self, spi, cs=14, dc=27, rst=33, bl=32): self.buffer = bytearray(32) self.letter = FrameBuffer(bytearray(8), 8, 8, MONO_HLSB) self.spi = spi self.cs = Pin(cs, Pin.OUT) self.dc = Pin(dc, Pin.OUT) self.rst = Pin(rst, Pin.OUT) self.bl = Pin(bl, Pin.OUT) self.width = 320 self.height = 240 self.char_width = 16 self.char_height = 16 self.offset = 0 self.background = color565(0, 0, 0) self._reset() self._setup() def _reset(self): self.cs.value(1) self.dc.value(0) self.rst.value(0) utime.sleep_ms(50) self.rst.value(1) utime.sleep_ms(50) def _setup(self): for command, arguments in ( (0xef, b'\x03\x80\x02'), (0xcf, b'\x00\xc1\x30'), (0xed, b'\x64\x03\x12\x81'), (0xe8, b'\x85\x00\x78'), (0xcb, b'\x39\x2c\x00\x34\x02'), (0xf7, b'\x20'), (0xea, b'\x00\x00'), (0xc0, b'\x23'), # Power Control 1, VRH[5:0] (0xc1, b'\x10'), # Power Control 2, SAP[2:0], BT[3:0] (0xc5, b'\x3e\x28'), # VCM Control 1 (0xc7, b'\x86'), # VCM Control 2 (0x36, b'\x48'), # Memory Access Control (0x3a, b'\x55'), # Pixel Format (0xb1, b'\x00\x18'), # FRMCTR1 (0xb6, b'\x08\x82\x27'), # Display Function Control (0xf2, b'\x00'), # Gamma Function Disable (0x26, b'\x01'), # Gamma Curve Selected (0xe0, b'\x0f\x31\x2b\x0c\x0e\x08\x4e\xf1\x37\x07\x10\x03\x0e\x09\x00' ), # Set Gamma (0xe1, b'\x00\x0e\x14\x03\x11\x07\x31\xc1\x48\x08\x0f\x0c\x31\x36\x0f')): self._write_command(command, arguments) self._write_command(_CMD_WAKE) utime.sleep_ms(120) def on(self): self._write_command(_CMD_DISPLAY_ON) self.bl.value(1) def off(self): self.bl.value(0) self._write_command(_CMD_DISPLAY_OFF) def set_inversion(self, inverse): if inverse: self._write_command(_CMD_DISPLAY_INVERSION_ON) else: self._write_command(_CMD_DISPLAY_INVERSION_OFF) def to_color(self, r, g, b): return color565(r, g, b) def set_background(self, background=color565(0, 0, 0)): self.fill_rectangle(0, 0, self.width - 1, self.height - 1, background) self.background = background def set_pixel(self, x, y, color=color565(255, 255, 255)): if x >= 0 and x < self.width and y >= 0 and y < self.height: self.fill_rectangle(x, y, x, y, color) def draw_line(self, x0, y0, x1, y1, color=color565(255, 255, 255)): dx = abs(x1 - x0) dy = -abs(y1 - y0) sx = 1 if x0 < x1 else -1 sy = 1 if y0 < y1 else -1 e = dx + dy while x0 != x1 or y0 != y1: self.set_pixel(x0, y0, color) e2 = e << 1 if e2 > dy: e += dy x0 += sx if e2 < dx: e += dx y0 += sy def draw_polyline(self, x, y, points, color=color565(255, 255, 255)): last_point = None for point in points: if last_point is not None: self.draw_line(x + last_point[0], y + last_point[1], x + point[0], y + point[1], color) last_point = point def draw_string(self, x_origin, y_origin, chars, color=color565(255, 255, 255)): for char in chars: self.letter.fill(0) self.letter.text(char, 0, 0) for x in range(0, 8): for y in range(0, 8): x0 = x_origin + (x << 1) y0 = y_origin + (y << 1) x1 = x0 + 2 y1 = y0 + 2 self.fill_rectangle( x0, y0, x1, y1, color if self.letter.pixel(x, y) > 0 else self.background) x_origin += 16 def rotate_up(self, delta=1): self.offset = (self.offset + delta) % self.height self._write_command(_CMD_LINE_SET, ustruct.pack('>H', self.offset)) def scroll_up(self, delta): self.fill_rectangle(0, 0, self.width - 1, delta, color=self.background) self.rotate_up(delta) def fill_rectangle(self, x0, y0, x1, y1, color): x0, x1 = self.width - x1 - 1, self.width - x0 - 1 if x0 < 0 and x1 < 0: return if x0 >= self.width and x1 >= self.width: return if y0 < 0 and y1 < 0: return if y0 >= self.height and y1 >= self.height: return if x0 < 0: x0 = 0 if x0 >= self.width: x0 = self.width - 1 if y0 < 0: y0 = 0 if y0 >= self.height: y0 = self.height - 1 if x1 < 0: x1 = 0 if x1 >= self.width: x1 = self.width - 1 if y1 < 0: y1 = 0 if y1 >= self.height: y1 = self.height - 1 w = x1 - x0 + 1 h = y1 - y0 + 1 pixel_count = min(16, w * h) color_msb = color >> 8 color_lsb = color & 255 position = 0 for index in range(0, pixel_count): self.buffer[position] = color_msb position += 1 self.buffer[position] = color_lsb position += 1 if pixel_count == 16: self._fill_large_rectangle(x0, y0, x1, y1) else: self._fill_small_rectangle(x0, y0, x1, y1, position) def _fill_large_rectangle(self, x0, y0, x1, y1): y = y0 while y <= y1: x = x0 while x <= x1: x_right = min(x1, x + 15) self._fill_small_rectangle(x, y, x_right, y, (x_right - x + 1) << 1) x = x_right + 1 y += 1 def _fill_small_rectangle(self, x0, y0, x1, y1, position): y0 += self.offset y1 += self.offset y0 %= self.height y1 %= self.height self._write_command(_CMD_COLUMN_SET, ustruct.pack(">HH", x0, x1)) self._write_command(_CMD_PAGE_SET, ustruct.pack(">HH", y0, y1)) self._write_command(_CMD_RAM_WRITE, memoryview(self.buffer)[0:position]) def _write_command(self, command, arguments=None): self.dc.value(0) self.cs.value(0) self.spi.write(bytearray([command])) self.cs.value(1) if arguments is not None: self.dc.value(1) self.cs.value(0) self.spi.write(arguments) self.cs.value(1)
class EPD: def __init__(self, spi, cs_pin, reset_pin, busy_pin, adt): self.resolution = RESOLUTION[0] self.width, self.height = RESOLUTION[0] self.cols, self.rows = RESOLUTION[1] self.buf = FrameBuffer(bytearray(self.width * self.height // 4), self.width, self.height, GS2_HMSB) self.border_colour = 0 self._reset_pin = reset_pin self._busy_pin = busy_pin self._cs_pin = cs_pin self._spi = spi self._adt = adt self._dirty = False self._luts = { 'default': [ # Phase 0 Phase 1 Phase 2 Phase 3 Phase 4 Phase 5 Phase 6 # A B C D A B C D A B C D A B C D A B C D A B C D A B C D 0b01001000, 0b10100000, 0b00010000, 0b00010000, 0b00010011, 0b00000000, 0b00000000, # LUT0 - Black 0b01001000, 0b10100000, 0b10000000, 0b00000000, 0b00000011, 0b00000000, 0b00000000, # LUTT1 - White 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # IGNORE 0b01001000, 0b10100101, 0b00000000, 0b10111011, 0b00000000, 0b00000000, 0b00000000, # LUT3 - Red 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # LUT4 - VCOM # Duration | Repeat # A B C D | 64, 12, 32, 12, 6, # 0 Flash 16, 8, 4, 4, 6, # 1 clear 4, 8, 8, 16, 16, # 2 bring in the black 2, 2, 2, 64, 32, # 3 time for red 2, 2, 2, 2, 2, # 4 final black sharpen phase 0, 0, 0, 0, 0, # 5 0, 0, 0, 0, 0 # 6 ] } def setup(self): self._reset_pin.off() time.sleep(0.1) self._reset_pin.on() time.sleep(0.1) self._send_command(0x12) # Soft Reset self._busy_wait() def _busy_wait(self): return while self._busy_pin.value(): time.sleep(0.01) def _update(self, buf_a, buf_b): self.setup() packed_height = list(struct.pack('<H', self.rows)) self._send_command(0x74, 0x54) # Set Analog Block Control self._send_command(0x7e, 0x3b) # Set Digital Block Control self._send_command(0x01, packed_height + [0x00]) # Gate setting self._send_command(0x03, [0b10000, 0b0001]) # Gate Driving Voltage self._send_command(0x3a, 0x07) # Dummy line period self._send_command(0x3b, 0x04) # Gate line width self._send_command( 0x11, 0x03) # Data entry mode setting 0x03 = X/Y increment self._send_command(0x04) # Power On self._send_command(0x2c, 0x3c) # VCOM Register, 0x3c = -1.5v? self._send_command(0x3c, 0x00) self._send_command(0x3c, 0xFF) self._send_command(0x32, self._luts['default']) # Set LUTs self._send_command(0x44, [0x00, (self.cols // 8) - 1]) # Set RAM X Start/End self._send_command(0x45, [0x00, 0x00] + packed_height) # Set RAM Y Start/End # 0x24 == RAM B/W, 0x26 == RAM Red for data in ((0x24, buf_a), (0x26, buf_b)): cmd, buf = data self._send_command(0x4e, 0x00) # Set RAM X Pointer Start self._send_command(0x4f, [0x00, 0x00]) # Set RAM Y Pointer Start self._send_command(cmd, buf) temp = self._adt.read_temp() temp_b = struct.pack(">h", int(temp * 16)) temp0 = (temp_b[0] & 0xF) << 4 temp1 = temp_b[1] self._send_command(0x1b, [temp1, temp0]) self._send_command(0x22, 0xc7) # Display Update Sequence self._send_command(0x20) # Trigger Display Update time.sleep(0.05) self._busy_wait() self._send_command(0x10, 0x01) # Enter Deep Sleep self._dirty = False def set_pixel(self, x, y, v): if v in (WHITE, BLACK, RED): self.buf.pixel(x, y, v) self._dirty = True def hline(self, x, y, w, v): if v in (WHITE, BLACK, RED): self.buf.hline(x, y, w, v) self._dirty = True def vline(self, x, y, h, v): if v in (WHITE, BLACK, RED): self.buf.hline(x, y, h, v) self._dirty = True @property def dirty(self): return self._dirty def show(self): def gen_buf_a(): for row in range(self.rows): for col in range(0, self.cols, 8): out = 0 for i in range(8): if self.buf.pixel(col + i, row) == BLACK: out += 1 << (7 - i) yield out def gen_buf_b(): for row in range(self.rows): for col in range(0, self.cols, 8): out = 0 for i in range(8): if self.buf.pixel(col + i, row) == RED: out += 1 << (7 - i) yield out self._update(FrameGen(self.cols, self.rows, BLACK, self.buf.pixel), FrameGen(self.cols, self.rows, RED, self.buf.pixel)) def _spi_write(self, dc, values): self._spi.write(values, dc) def _send_command(self, command, data=None): self._spi_write(_SPI_COMMAND, [command]) if data is not None: self._send_data(data) def _send_data(self, data): if isinstance(data, int): data = [data] self._spi_write(_SPI_DATA, data)
class Screen: def __init__(self): self.textbuffer = TextBuffer(cols, rows) # make the framebuffer we draw into the size of one line of text as that's all we need self.buf = bytearray(screen_width * font_height // 8) # the screen defaults to portrait and we want to use it in landscape so we have to rotate as we go, unfortunately. that's why dimensions look swapped around self.fb = FrameBuffer(self.buf, font_height, screen_width, MONO_HLSB) sck = Pin(18, Pin.OUT) mosi = Pin(23, Pin.OUT) miso = Pin(19, Pin.IN) spi = SPI(2, baudrate=80000000, polarity=0, phase=0, sck=sck, mosi=mosi, miso=miso) cs = Pin(5, Pin.OUT) dc = Pin(17, Pin.OUT) rst = Pin(27, Pin.OUT) busy = Pin(35, Pin.IN) self.epd = EPD(spi, cs, dc, rst, busy) self.epd.init() self.clear_screen() # we were in slow mode for the initial clear on startup, we're now going to fast mode as that's the most likely one we'll need next self.mode = 'slow' self.set_fast() self.running = False self.last_change = None def write(self, byteslike): self.textbuffer.write(byteslike) self.debounce_update() def set_slow(self): if self.mode == 'slow': return self.mode = 'slow' self.epd.set_slow() def set_fast(self): if self.mode == 'fast': return self.mode = 'fast' self.epd.set_fast() def plot(self, x, y): # rotate 90 degrees on the fly self.fb.pixel(font_height - 1 - y, x, 0) def plot_inverse(self, x, y): # rotate 90 degrees self.fb.pixel(font_height - 1 - y, x, 1) def debounce_update(self): self.last_change = time.ticks_ms() def update_screen(self, tmr=None): self.last_change = None if not self.running: return # TODO: keep some performance stats somewhere # the changed lines. keys are row indexes, values are line strings lines_dict = self.textbuffer.pop() # slow update if the entire screen changed (gives it a chance to remove the ghosting), otherwise fast for partial updates if len(lines_dict) == self.textbuffer.rows: self.set_slow() else: self.set_fast() cursor_x = self.textbuffer.x() cursor_y = self.textbuffer.y() # keep both buffers in the display's controller in sync for i in range(2): self._update_buffer(lines_dict, cursor_x, cursor_y) # display only one of the buffers if i == 0: self.epd.display_frame() def _update_buffer(self, lines_dict, cursor_x, cursor_y): for row_index, line in lines_dict.items(): # clear the framebuffer because it now represents this row self.fb.fill(1) # print the text font.draw_line(line.encode('ascii'), self.plot) if row_index == cursor_y: # draw the cursor (rotated 90 degrees) self.fb.fill_rect(0, cursor_x * font_width, font_height, font_width, 0) # if the cursor is on a character, also draw that character inverted if cursor_x < len(line): font.draw_line(line[cursor_x].encode('ascii'), self.plot_inverse, cursor_x * font_width, 1) # copy this row to the screen's buffer (rotated 90 degrees) self.epd.set_frame_memory( self.buf, screen_height - ((row_index + 1) * font_height), 0, font_height, screen_width) def clear_screen(self): self.fb.fill(1) # clear both buffers for i in range(2): for row_index in range(self.textbuffer.rows): self.epd.set_frame_memory(self.buf, row_index * font_height, 0, font_height, screen_width) # but only clear the screen once if i == 0: self.epd.display_frame() def clear(self): self.textbuffer.clear() self.debounce_update()
def __init__(self, spi, cs_pin, reset_pin, busy_pin, adt): self.resolution = RESOLUTION[0] self.width, self.height = RESOLUTION[0] self.cols, self.rows = RESOLUTION[1] self.buf = FrameBuffer(bytearray(self.width * self.height // 4), self.width, self.height, GS2_HMSB) self.border_colour = 0 self._reset_pin = reset_pin self._busy_pin = busy_pin self._cs_pin = cs_pin self._spi = spi self._adt = adt self._dirty = False self._luts = { 'default': [ # Phase 0 Phase 1 Phase 2 Phase 3 Phase 4 Phase 5 Phase 6 # A B C D A B C D A B C D A B C D A B C D A B C D A B C D 0b01001000, 0b10100000, 0b00010000, 0b00010000, 0b00010011, 0b00000000, 0b00000000, # LUT0 - Black 0b01001000, 0b10100000, 0b10000000, 0b00000000, 0b00000011, 0b00000000, 0b00000000, # LUTT1 - White 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # IGNORE 0b01001000, 0b10100101, 0b00000000, 0b10111011, 0b00000000, 0b00000000, 0b00000000, # LUT3 - Red 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # LUT4 - VCOM # Duration | Repeat # A B C D | 64, 12, 32, 12, 6, # 0 Flash 16, 8, 4, 4, 6, # 1 clear 4, 8, 8, 16, 16, # 2 bring in the black 2, 2, 2, 64, 32, # 3 time for red 2, 2, 2, 2, 2, # 4 final black sharpen phase 0, 0, 0, 0, 0, # 5 0, 0, 0, 0, 0 # 6 ] }