Skip to content

Commit f17e62d

Browse files
authored
Merge pull request #40 from mikeysklar/fpc7519-support
Auto-detect FPC-7519rev.b panel so old and new MagTags work without manual configuration
2 parents 292f58b + d391233 commit f17e62d

2 files changed

Lines changed: 129 additions & 46 deletions

File tree

adafruit_ssd1680.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
2929
"""
3030

31+
import time
32+
33+
import digitalio
3134
from epaperdisplay import EPaperDisplay
3235

3336
try:
@@ -58,6 +61,101 @@
5861
_STOP_SEQUENCE = b"\x10\x80\x01\x01\x64" # Deep Sleep
5962

6063

64+
# LUT for original MagTag panel: FPC-A005 (SSD1680, User ID first byte 0x00)
65+
FPC_A005_LUT = (
66+
b"\x2a\x60\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L0
67+
b"\x20\x60\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L1
68+
b"\x28\x60\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L2
69+
b"\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L3
70+
b"\x00\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L4
71+
b"\x00\x02\x00\x05\x14\x00\x00" # TP, SR, RP Group0
72+
b"\x1e\x1e\x00\x00\x00\x00\x01" # TP, SR, RP Group1
73+
b"\x00\x02\x00\x05\x14\x00\x00" # TP, SR, RP Group2
74+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group3
75+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group4
76+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group5
77+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group6
78+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group7
79+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group8
80+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group9
81+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group10
82+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group11
83+
b"\x24\x22\x22\x22\x23\x32\x00\x00\x00" # FR, XON
84+
)
85+
86+
# LUT for FPC-7519rev.b and newer panels (User ID first byte != 0x00).
87+
# GxEPD2_4G (GDEM029T94) waveform with L0/L3 swapped to match CircuitPython's luma mapping
88+
# (luma 0→L0=black, luma 255→L3=white) and 0x48 flag for DC-balanced drive.
89+
FPC7519_LUT = (
90+
b"\x20\x48\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L0 (black)
91+
b"\x08\x48\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L1 (dark gray)
92+
b"\x02\x48\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L2 (light gray)
93+
b"\x40\x48\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L3 (white)
94+
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L4 (VCOM)
95+
b"\x0a\x19\x00\x03\x08\x00\x00" # TP, SR, RP Group0
96+
b"\x14\x01\x00\x14\x01\x00\x03" # TP, SR, RP Group1
97+
b"\x0a\x03\x00\x08\x19\x00\x00" # TP, SR, RP Group2
98+
b"\x01\x00\x00\x00\x00\x00\x01" # TP, SR, RP Group3
99+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group4
100+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group5
101+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group6
102+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group7
103+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group8
104+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group9
105+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group10
106+
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP Group11
107+
b"\x22\x22\x22\x22\x22\x22\x00\x00\x00" # FR, XON
108+
)
109+
110+
111+
def detect_ssd1680_panel(data_pin, clk_pin, cs_pin, dc_pin, rst_pin):
112+
"""Read SSD1680 User ID (register 0x2E) via half-duplex bitbang SPI.
113+
114+
Must be called before the SPI bus is initialized (FourWire claims the pins).
115+
The SSD1680 responds on the MOSI/DATA line in half-duplex mode, not MISO.
116+
117+
Returns first byte of User ID:
118+
0x00 -> FPC-A005 (original/legacy MagTag panel)
119+
anything else -> FPC-7519rev.b or newer panel; use FPC7519_LUT
120+
"""
121+
data = digitalio.DigitalInOut(data_pin)
122+
clk = digitalio.DigitalInOut(clk_pin)
123+
cs = digitalio.DigitalInOut(cs_pin)
124+
dc = digitalio.DigitalInOut(dc_pin)
125+
rst = digitalio.DigitalInOut(rst_pin)
126+
127+
for pin in (data, clk, cs, dc, rst):
128+
pin.direction = digitalio.Direction.OUTPUT
129+
pin.value = False
130+
131+
rst.value = False
132+
time.sleep(0.001)
133+
rst.value = True
134+
time.sleep(0.010)
135+
136+
cmd = 0x2E
137+
for i in range(8):
138+
data.value = bool(cmd & (1 << (7 - i)))
139+
clk.value = True
140+
clk.value = False
141+
142+
data.switch_to_input(pull=digitalio.Pull.UP)
143+
dc.value = True
144+
145+
result = 0
146+
for _ in range(8):
147+
result = (result << 1) | (1 if data.value else 0)
148+
clk.value = True
149+
clk.value = False
150+
151+
cs.value = True
152+
153+
for pin in (data, clk, cs, dc, rst):
154+
pin.deinit()
155+
156+
return result
157+
158+
61159
# pylint: disable=too-few-public-methods
62160
class SSD1680(EPaperDisplay):
63161
r"""SSD1680 driver
Lines changed: 31 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
# SPDX-FileCopyrightText: 2026 Mikey Sklar, written for Adafruit Industries
12
# SPDX-FileCopyrightText: 2025 Scott Shawcroft, written for Adafruit Industries
23
# SPDX-FileCopyrightText: Copyright (c) 2021 Melissa LeBlanc-Williams for Adafruit Industries
34
#
45
# SPDX-License-Identifier: Unlicense
56

6-
"""Simple test script for 2.9" 296x128 display. This example runs it in 2bit grayscale mode."""
7+
"""Simple test script for 2.9" 296x128 display. This example runs it in 2bit grayscale mode.
8+
9+
Automatically detects whether the MagTag has the original FPC-A005 panel or the
10+
newer FPC-7519rev.b panel by reading the SSD1680 User ID register before the SPI
11+
bus is initialized.
12+
"""
713

814
import time
915

@@ -16,74 +22,53 @@
1622

1723
displayio.release_displays()
1824

19-
# This pinout works on a MagTag with the newer screen and may need to be altered for other boards.
20-
spi = busio.SPI(board.EPD_SCK, board.EPD_MOSI) # Uses SCK and MOSI
21-
epd_cs = board.EPD_CS
22-
epd_dc = board.EPD_DC
23-
epd_reset = board.EPD_RESET
24-
epd_busy = board.EPD_BUSY
25-
26-
display_bus = FourWire(spi, command=epd_dc, chip_select=epd_cs, reset=epd_reset, baudrate=1000000)
27-
time.sleep(1)
28-
29-
ti_290mfgn_gray4_lut_code = (
30-
b"\x2a\x60\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L0
31-
b"\x20\x60\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L1
32-
b"\x28\x60\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L2
33-
b"\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L3
34-
b"\x00\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # VS L4
35-
b"\x00\x02\x00\x05\x14\x00\x00" # TP, SR, RP of Group0
36-
b"\x1e\x1e\x00\x00\x00\x00\x01" # TP, SR, RP of Group1
37-
b"\x00\x02\x00\x05\x14\x00\x00" # TP, SR, RP of Group2
38-
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP of Group3
39-
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP of Group4
40-
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP of Group5
41-
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP of Group6
42-
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP of Group7
43-
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP of Group8
44-
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP of Group9
45-
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP of Group10
46-
b"\x00\x00\x00\x00\x00\x00\x00" # TP, SR, RP of Group11
47-
b"\x24\x22\x22\x22\x23\x32\x00\x00\x00" # FR, XON
25+
panel_id = adafruit_ssd1680.detect_ssd1680_panel(
26+
board.EPD_MOSI, board.EPD_SCK, board.EPD_CS, board.EPD_DC, board.EPD_RESET
4827
)
28+
print(f"SSD1680 User ID first byte: 0x{panel_id:02x}")
29+
30+
if panel_id == 0x00:
31+
print("Detected: FPC-A005 panel (legacy)")
32+
vcom = 0x28
33+
colstart = 0
34+
lut = adafruit_ssd1680.FPC_A005_LUT
35+
else:
36+
print("Detected: FPC-7519rev.b or newer panel")
37+
vcom = 0x24
38+
colstart = 8
39+
lut = adafruit_ssd1680.FPC7519_LUT
40+
41+
spi = busio.SPI(board.EPD_SCK, board.EPD_MOSI)
42+
display_bus = FourWire(
43+
spi, command=board.EPD_DC, chip_select=board.EPD_CS, reset=board.EPD_RESET, baudrate=1000000
44+
)
45+
time.sleep(1)
4946

50-
if len(ti_290mfgn_gray4_lut_code) != 153:
51-
raise ValueError("ti_290mfgn_gray4_lut_code is not the correct length")
52-
53-
# For issues with display not updating top/bottom rows correctly set colstart to 8, 0, or -8
5447
display = adafruit_ssd1680.SSD1680(
5548
display_bus,
5649
width=296,
5750
height=128,
58-
busy_pin=epd_busy,
51+
busy_pin=board.EPD_BUSY,
5952
rotation=270,
60-
colstart=0,
61-
vcom=0x28,
53+
colstart=colstart,
54+
vcom=vcom,
6255
vsh2=0xAE,
63-
custom_lut=ti_290mfgn_gray4_lut_code,
56+
custom_lut=lut,
6457
grayscale=True,
6558
)
6659

6760
g = displayio.Group()
68-
6961
pic = displayio.OnDiskBitmap("/display-ruler-640x360.bmp")
7062
t = displayio.TileGrid(pic, pixel_shader=pic.pixel_shader)
7163
g.append(t)
7264

7365
display.root_group = g
74-
7566
display.refresh()
7667

7768
print("refreshed")
7869

7970
time.sleep(display.time_to_refresh + 5)
80-
# Always refresh a little longer. It's not a problem to refresh
81-
# a few seconds more, but it's terrible to refresh too early
82-
# (the display will throw an exception when if the refresh
83-
# is too soon)
8471
print("waited correct time")
8572

86-
87-
# Keep the display the same
8873
while True:
8974
time.sleep(10)

0 commit comments

Comments
 (0)