Files
Bjorn/resources/waveshare_epd/epd2in13_V2.py

270 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# epd2in13_V2 — V2 alignée V4, zone utile 120px centrée dans 122px
# - Fenêtrage complet 122x250
# - Data entry: X++ puis Y++ (0x03) comme V3/V4
# - getbuffer() accepte une image 120x250 (ou 122x250) et la centre (offset=1)
# - Aucune rotation/mirroring côté driver (géré en amont si besoin)
# - Pas de décalage wrap-around d1 pixel (fini la ligne sombre)
import logging
from . import epdconfig
# Résolution physique du panneau (hardware)
EPD_WIDTH = 122
EPD_HEIGHT = 250
logger = logging.getLogger(__name__)
class EPD:
def __init__(self):
self.is_initialized = False
self.reset_pin = epdconfig.RST_PIN
self.dc_pin = epdconfig.DC_PIN
self.busy_pin = epdconfig.BUSY_PIN
self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
FULL_UPDATE = 0
PART_UPDATE = 1
# LUTs d'origine (Waveshare)
lut_full_update= [
0x80,0x60,0x40,0x00,0x00,0x00,0x00,
0x10,0x60,0x20,0x00,0x00,0x00,0x00,
0x80,0x60,0x40,0x00,0x00,0x00,0x00,
0x10,0x60,0x20,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x03,0x03,0x00,0x00,0x02,
0x09,0x09,0x00,0x00,0x02,
0x03,0x03,0x00,0x00,0x02,
0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,
0x15,0x41,0xA8,0x32,0x30,0x0A,
]
lut_partial_update = [
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0x00,0x00,0x00,0x00,0x00,0x00,
0x40,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x0A,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,
0x15,0x41,0xA8,0x32,0x30,0x0A,
]
# Hardware reset
def reset(self):
epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(5)
epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200)
def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0)
epdconfig.spi_writebyte([command])
def send_data(self, data):
epdconfig.digital_write(self.dc_pin, 1)
epdconfig.spi_writebyte([data])
def send_data2(self, data):
epdconfig.digital_write(self.dc_pin, 1)
epdconfig.spi_writebyte2(data)
def ReadBusy(self):
# 0: idle, 1: busy
while epdconfig.digital_read(self.busy_pin) == 1:
epdconfig.delay_ms(50)
def TurnOnDisplay(self):
self.send_command(0x22)
self.send_data(0xC7)
self.send_command(0x20)
self.ReadBusy()
def TurnOnDisplayPart(self):
self.send_command(0x22)
self.send_data(0x0c)
self.send_command(0x20)
self.ReadBusy()
def init(self, update):
"""
Init V2 alignée V4 :
- Data entry: 0x03 (X++ puis Y++)
- X-window: start=0x00, end=0x0F (16 octets = 128 bits => couvre nos 122 px)
- Y-window: start=0x0000, end=0x00F9 (250 lignes)
- Curseur: X=0x00, Y=0x0000
"""
if not self.is_initialized:
if epdconfig.module_init() != 0:
return -1
self.reset()
self.is_initialized = True
if update == self.FULL_UPDATE:
self.ReadBusy()
self.send_command(0x12) # soft reset
self.ReadBusy()
# Analog/Digital blocks
self.send_command(0x74); self.send_data(0x54)
self.send_command(0x7E); self.send_data(0x3B)
# Driver output control (height - 1) => 249
self.send_command(0x01)
self.send_data(0xF9) # 249
self.send_data(0x00)
self.send_data(0x00)
# Data entry mode X++ Y++
self.send_command(0x11)
self.send_data(0x03)
# Fenêtre RAM X (octets) 0..15 (16*8=128 bits -> couvre 122 px)
self.send_command(0x44)
self.send_data(0x00) # start
self.send_data(0x0F) # end
# Fenêtre RAM Y 0..249
self.send_command(0x45)
self.send_data(0x00) # Y-start L
self.send_data(0x00) # Y-start H
self.send_data(0xF9) # Y-end L
self.send_data(0x00) # Y-end H
# Border/VCOM/LUT timing
self.send_command(0x3C); self.send_data(0x03)
self.send_command(0x2C); self.send_data(0x55)
self.send_command(0x03); self.send_data(self.lut_full_update[70])
self.send_command(0x04)
self.send_data(self.lut_full_update[71])
self.send_data(self.lut_full_update[72])
self.send_data(self.lut_full_update[73])
self.send_command(0x3A); self.send_data(self.lut_full_update[74]) # Dummy line
self.send_command(0x3B); self.send_data(self.lut_full_update[75]) # Gate time
self.send_command(0x32) # LUT table
for i in range(70):
self.send_data(self.lut_full_update[i])
# Curseur X/Y
self.send_command(0x4E); self.send_data(0x00) # X-counter (byte)
self.send_command(0x4F); self.send_data(0x00); self.send_data(0x00) # Y-counter
self.ReadBusy()
else:
# PARTIAL init
self.send_command(0x2C); self.send_data(0x26) # VCOM
self.ReadBusy()
self.send_command(0x32)
for i in range(70):
self.send_data(self.lut_partial_update[i])
self.send_command(0x37)
self.send_data(0x00); self.send_data(0x00); self.send_data(0x00)
self.send_data(0x00); self.send_data(0x40); self.send_data(0x00); self.send_data(0x00)
self.send_command(0x22); self.send_data(0xC0)
self.send_command(0x20); self.ReadBusy()
self.send_command(0x3C); self.send_data(0x01)
# Même fenêtrage quen full
self.send_command(0x44); self.send_data(0x00); self.send_data(0x0F)
self.send_command(0x45); self.send_data(0x00); self.send_data(0x00); self.send_data(0xF9); self.send_data(0x00)
self.send_command(0x4E); self.send_data(0x00)
self.send_command(0x4F); self.send_data(0x00); self.send_data(0x00)
return 0
def getbuffer(self, image):
W, H = self.width, self.height # 122 x 250
bytes_per_line = (W + 7) // 8 # 16
buf = bytearray([0xFF] * (bytes_per_line * H))
img = image.convert('1')
imw, imh = img.size
work_w = min(imw, 120)
x_offset = (W - work_w) // 2 # =1 pour 120px
pixels = img.load()
for y in range(min(imh, H)):
base = y * bytes_per_line
for x in range(work_w):
src_x = x if imw == 120 else (x + (imw - work_w)//2)
if pixels[src_x, y] == 0:
xi = x + x_offset
if xi <= 0 or xi >= W-1:
continue # sécurité: ne jamais écrire col 0 ni 121
byte_index = base + (xi >> 3)
bit = 0x80 >> (xi & 7)
buf[byte_index] &= (~bit) & 0xFF
# force colonnes 0 et 121 en blanc
buf[base + (0 >> 3)] |= (0x80 >> (0 & 7))
buf[base + (121 >> 3)] |= (0x80 >> (121 & 7))
return buf
def display(self, image):
self.send_command(0x24)
self.send_data2(image)
self.TurnOnDisplay()
def displayPartial(self, image):
bytes_per_line = (self.width + 7) // 8
total = self.height * bytes_per_line
# Buffer inversé pour le second plan (comme dorigine)
buf_inv = bytearray(total)
for i in range(total):
buf_inv[i] = (~image[i]) & 0xFF
self.send_command(0x24)
self.send_data2(image)
self.send_command(0x26)
self.send_data2(buf_inv)
self.TurnOnDisplayPart()
def displayPartBaseImage(self, image):
self.send_command(0x24)
self.send_data2(image)
self.send_command(0x26)
self.send_data2(image)
self.TurnOnDisplay()
def Clear(self, color=0xFF):
bytes_per_line = (self.width + 7) // 8
buf = bytearray([color] * (self.height * bytes_per_line))
self.send_command(0x24)
self.send_data2(buf)
self.TurnOnDisplay()
def sleep(self):
self.send_command(0x10) # enter deep sleep
self.send_data(0x03)
epdconfig.delay_ms(2000)
epdconfig.module_exit()
# END OF FILE