# MicroPython library for Waveshare 2.9" B/W/R e-paper display GDEW029Z10 from micropython import const from time import sleep_ms import ustruct # Display resolution EPD_WIDTH = const(128) EPD_HEIGHT = const(296) # Display commands PANEL_SETTING = const(0x00) POWER_SETTING = const(0x01) POWER_OFF = const(0x02) #POWER_OFF_SEQUENCE_SETTING = const(0x03) POWER_ON = const(0x04) #POWER_ON_MEASURE = const(0x05) BOOSTER_SOFT_START = const(0x06) #DEEP_SLEEP = const(0x07) DATA_START_TRANSMISSION_1 = const(0x10) #DATA_STOP = const(0x11) DISPLAY_REFRESH = const(0x12) DATA_START_TRANSMISSION_2 = const(0x13) #PLL_CONTROL = const(0x30) #TEMPERATURE_SENSOR_COMMAND = const(0x40) #TEMPERATURE_SENSOR_CALIBRATION = const(0x41) #TEMPERATURE_SENSOR_WRITE = const(0x42) #TEMPERATURE_SENSOR_READ = const(0x43) VCOM_AND_DATA_INTERVAL_SETTING = const(0x50) #LOW_POWER_DETECTION = const(0x51) #TCON_SETTING = const(0x60) TCON_RESOLUTION = const(0x61) #GET_STATUS = const(0x71) #AUTO_MEASURE_VCOM = const(0x80) #VCOM_VALUE = const(0x81) VCM_DC_SETTING_REGISTER = const(0x82) #PARTIAL_WINDOW = const(0x90) #PARTIAL_IN = const(0x91) #PARTIAL_OUT = const(0x92) #PROGRAM_MODE = const(0xA0) #ACTIVE_PROGRAM = const(0xA1) #READ_OTP_DATA = const(0xA2) #POWER_SAVING = const(0xE3) # Display orientation ROTATE_0 = const(0) ROTATE_90 = const(1) ROTATE_180 = const(2) ROTATE_270 = const(3) class EPD: def __init__(self, spi, cs, dc, rst, busy): self.spi = spi self.cs = cs self.dc = dc self.rst = rst self.busy = busy self.cs.init(self.cs.OUT, value=1) self.dc.init(self.dc.OUT, value=0) self.rst.init(self.rst.OUT, value=0) self.busy.init(self.busy.IN) self.width = EPD_WIDTH self.height = EPD_HEIGHT self.rotate = ROTATE_0 def _command(self, command, data=None): self.dc.low() self.cs.low() self.spi.write(bytearray([command])) self.cs.high() if data is not None: self._data(data) def _data(self, data): self.dc.high() self.cs.low() self.spi.write(data) self.cs.high() def init(self): self.reset() self._command(BOOSTER_SOFT_START, b'\x17\x17\x17') self._command(POWER_ON) self.wait_until_idle() self._command(PANEL_SETTING, b'\x8F') self._command(VCOM_AND_DATA_INTERVAL_SETTING, b'\x77') self._command(TCON_RESOLUTION, ustruct.pack(">BH", EPD_WIDTH, EPD_HEIGHT)) self._command(VCM_DC_SETTING_REGISTER, b'\x0A') def wait_until_idle(self): while self.busy.value() == 1: sleep_ms(100) def reset(self): self.rst.low() sleep_ms(200) self.rst.high() sleep_ms(200) def display_frame(self, frame_buffer_black, frame_buffer_red): if (frame_buffer_black != None): self._command(DATA_START_TRANSMISSION_1) sleep_ms(2) for i in range(0, self.width * self.height // 8): self._data(bytearray([frame_buffer_black[i]])) sleep_ms(2) if (frame_buffer_red != None): self._command(DATA_START_TRANSMISSION_2) sleep_ms(2) for i in range(0, self.width * self.height // 8): self._data(bytearray([frame_buffer_red[i]])) sleep_ms(2) self._command(DISPLAY_REFRESH) self.wait_until_idle() def set_rotate(self, rotate): if (rotate == ROTATE_0): self.rotate = ROTATE_0 self.width = epdif.EPD_WIDTH self.height = epdif.EPD_HEIGHT elif (rotate == ROTATE_90): self.rotate = ROTATE_90 self.width = epdif.EPD_HEIGHT self.height = epdif.EPD_WIDTH elif (rotate == ROTATE_180): self.rotate = ROTATE_180 self.width = epdif.EPD_WIDTH self.height = epdif.EPD_HEIGHT elif (rotate == ROTATE_270): self.rotate = ROTATE_270 self.width = epdif.EPD_HEIGHT self.height = epdif.EPD_WIDTH def set_pixel(self, frame_buffer, x, y, colored): if (x < 0 or x >= self.width or y < 0 or y >= self.height): return if (self.rotate == ROTATE_0): self.set_absolute_pixel(frame_buffer, x, y, colored) elif (self.rotate == ROTATE_90): point_temp = x x = epdif.EPD_WIDTH - y y = point_temp self.set_absolute_pixel(frame_buffer, x, y, colored) elif (self.rotate == ROTATE_180): x = epdif.EPD_WIDTH - x y = epdif.EPD_HEIGHT- y self.set_absolute_pixel(frame_buffer, x, y, colored) elif (self.rotate == ROTATE_270): point_temp = x x = y y = epdif.EPD_HEIGHT - point_temp self.set_absolute_pixel(frame_buffer, x, y, colored) def set_absolute_pixel(self, frame_buffer, x, y, colored): # To avoid display orientation effects # use EPD_WIDTH instead of self.width # use EPD_HEIGHT instead of self.height if (x < 0 or x >= EPD_WIDTH or y < 0 or y >= EPD_HEIGHT): return if (colored): frame_buffer[(x + y * EPD_WIDTH) // 8] &= ~(0x80 >> (x % 8)) else: frame_buffer[(x + y * EPD_WIDTH) // 8] |= 0x80 >> (x % 8) def draw_string_at(self, frame_buffer, x, y, text, font, colored): image = Image.new('1', (self.width, self.height)) draw = ImageDraw.Draw(image) draw.text((x, y), text, font = font, fill = 255) # Set buffer to value of Python Imaging Library image. # Image must be in mode 1. pixels = image.load() for y in range(self.height): for x in range(self.width): # Set the bits for the column of pixels at the current position. if pixels[x, y] != 0: self.set_pixel(frame_buffer, x, y, colored) def draw_line(self, frame_buffer, x0, y0, x1, y1, colored): # Bresenham algorithm dx = abs(x1 - x0) sx = 1 if x0 < x1 else -1 dy = -abs(y1 - y0) sy = 1 if y0 < y1 else -1 err = dx + dy while((x0 != x1) and (y0 != y1)): self.set_pixel(frame_buffer, x0, y0 , colored) if (2 * err >= dy): err += dy x0 += sx if (2 * err <= dx): err += dx y0 += sy def draw_horizontal_line(self, frame_buffer, x, y, width, colored): for i in range(x, x + width): self.set_pixel(frame_buffer, i, y, colored) def draw_vertical_line(self, frame_buffer, x, y, height, colored): for i in range(y, y + height): self.set_pixel(frame_buffer, x, i, colored) def draw_rectangle(self, frame_buffer, x0, y0, x1, y1, colored): min_x = x0 if x1 > x0 else x1 max_x = x1 if x1 > x0 else x0 min_y = y0 if y1 > y0 else y1 max_y = y1 if y1 > y0 else y0 self.draw_horizontal_line(frame_buffer, min_x, min_y, max_x - min_x + 1, colored) self.draw_horizontal_line(frame_buffer, min_x, max_y, max_x - min_x + 1, colored) self.draw_vertical_line(frame_buffer, min_x, min_y, max_y - min_y + 1, colored) self.draw_vertical_line(frame_buffer, max_x, min_y, max_y - min_y + 1, colored) def draw_filled_rectangle(self, frame_buffer, x0, y0, x1, y1, colored): min_x = x0 if x1 > x0 else x1 max_x = x1 if x1 > x0 else x0 min_y = y0 if y1 > y0 else y1 max_y = y1 if y1 > y0 else y0 for i in range(min_x, max_x + 1): self.draw_vertical_line(frame_buffer, i, min_y, max_y - min_y + 1, colored) def draw_circle(self, frame_buffer, x, y, radius, colored): # Bresenham algorithm x_pos = -radius y_pos = 0 err = 2 - 2 * radius if (x >= self.width or y >= self.height): return while True: self.set_pixel(frame_buffer, x - x_pos, y + y_pos, colored) self.set_pixel(frame_buffer, x + x_pos, y + y_pos, colored) self.set_pixel(frame_buffer, x + x_pos, y - y_pos, colored) self.set_pixel(frame_buffer, x - x_pos, y - y_pos, colored) e2 = err if (e2 <= y_pos): y_pos += 1 err += y_pos * 2 + 1 if(-x_pos == y_pos and e2 <= x_pos): e2 = 0 if (e2 > x_pos): x_pos += 1 err += x_pos * 2 + 1 if x_pos > 0: break def draw_filled_circle(self, frame_buffer, x, y, radius, colored): # Bresenham algorithm x_pos = -radius y_pos = 0 err = 2 - 2 * radius if (x >= self.width or y >= self.height): return while True: self.set_pixel(frame_buffer, x - x_pos, y + y_pos, colored) self.set_pixel(frame_buffer, x + x_pos, y + y_pos, colored) self.set_pixel(frame_buffer, x + x_pos, y - y_pos, colored) self.set_pixel(frame_buffer, x - x_pos, y - y_pos, colored) self.draw_horizontal_line(frame_buffer, x + x_pos, y + y_pos, 2 * (-x_pos) + 1, colored) self.draw_horizontal_line(frame_buffer, x + x_pos, y - y_pos, 2 * (-x_pos) + 1, colored) e2 = err if (e2 <= y_pos): y_pos += 1 err += y_pos * 2 + 1 if(-x_pos == y_pos and e2 <= x_pos): e2 = 0 if (e2 > x_pos): x_pos += 1 err += x_pos * 2 + 1 if x_pos > 0: break # to wake call reset() or init() def sleep(self): self._command(VCOM_AND_DATA_INTERVAL_SETTING, b'\x37') self._command(VCM_DC_SETTING_REGISTER, b'\x00') # to solve Vcom drop self._command(POWER_SETTING, b'\x02\x00\x00\x00') # gate switch to external self.wait_until_idle() self._command(POWER_OFF)