1+ # SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
2+ #
3+ # SPDX-License-Identifier: MIT
4+
5+ """
6+ `adafruit_epd.ssd1683` - Adafruit SSD1683 - ePaper display driver
7+ ====================================================================================
8+ CircuitPython driver for Adafruit SSD1683 display breakouts
9+ * Author(s): Liz Clark
10+ """
11+
12+ import time
13+
14+ import adafruit_framebuf
15+ from micropython import const
16+
17+ from adafruit_epd .epd import Adafruit_EPD
18+
19+ try :
20+ """Needed for type annotations"""
21+ import typing
22+
23+ from busio import SPI
24+ from digitalio import DigitalInOut
25+ from typing_extensions import Literal
26+
27+ except ImportError :
28+ pass
29+
30+ __version__ = "0.0.0+auto.0"
31+ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_EPD.git"
32+
33+ # Command constants
34+ _SSD1683_DRIVER_CONTROL = const (0x01 )
35+ _SSD1683_GATE_VOLTAGE = const (0x03 )
36+ _SSD1683_SOURCE_VOLTAGE = const (0x04 )
37+ _SSD1683_PROGOTP_INITIAL = const (0x08 )
38+ _SSD1683_PROGREG_INITIAL = const (0x09 )
39+ _SSD1683_READREG_INITIAL = const (0x0A )
40+ _SSD1683_BOOST_SOFTSTART = const (0x0C )
41+ _SSD1683_DEEP_SLEEP = const (0x10 )
42+ _SSD1683_DATA_MODE = const (0x11 )
43+ _SSD1683_SW_RESET = const (0x12 )
44+ _SSD1683_HV_READY = const (0x14 )
45+ _SSD1683_VCI_DETECT = const (0x15 )
46+ _SSD1683_PROGRAM_WSOTP = const (0x16 )
47+ _SSD1683_PROGRAM_AUTO = const (0x17 )
48+ _SSD1683_TEMP_CONTROL = const (0x18 )
49+ _SSD1683_TEMP_WRITE = const (0x1A )
50+ _SSD1683_TEMP_READ = const (0x1B )
51+ _SSD1683_TEMP_CONTROLEXT = const (0x1C )
52+ _SSD1683_MASTER_ACTIVATE = const (0x20 )
53+ _SSD1683_DISP_CTRL1 = const (0x21 )
54+ _SSD1683_DISP_CTRL2 = const (0x22 )
55+ _SSD1683_WRITE_RAM1 = const (0x24 )
56+ _SSD1683_WRITE_RAM2 = const (0x26 )
57+ _SSD1683_READ_RAM1 = const (0x27 )
58+ _SSD1683_SENSE_VCOM = const (0x28 )
59+ _SSD1683_SENSEDUR_VCOM = const (0x29 )
60+ _SSD1683_PROGOTP_VCOM = const (0x2A )
61+ _SSD1683_WRITE_VCOM = const (0x2C )
62+ _SSD1683_READ_OTP = const (0x2D )
63+ _SSD1683_READ_USERID = const (0x2E )
64+ _SSD1683_READ_STATUS = const (0x2F )
65+ _SSD1683_WRITE_LUT = const (0x32 )
66+ _SSD1683_WRITE_BORDER = const (0x3C )
67+ _SSD1683_END_OPTION = const (0x3F )
68+ _SSD1683_SET_RAMXPOS = const (0x44 )
69+ _SSD1683_SET_RAMYPOS = const (0x45 )
70+ _SSD1683_SET_RAMXCOUNT = const (0x4E )
71+ _SSD1683_SET_RAMYCOUNT = const (0x4F )
72+
73+ # Other constants
74+ _EPD_RAM_BW = const (0x10 )
75+ _EPD_RAM_RED = const (0x13 )
76+ _BUSY_WAIT = const (500 )
77+
78+
79+ class Adafruit_SSD1683 (Adafruit_EPD ):
80+ """driver class for Adafruit SSD1683 ePaper display breakouts"""
81+
82+ def __init__ (
83+ self ,
84+ width : int ,
85+ height : int ,
86+ spi : SPI ,
87+ * ,
88+ cs_pin : DigitalInOut ,
89+ dc_pin : DigitalInOut ,
90+ sramcs_pin : DigitalInOut ,
91+ rst_pin : DigitalInOut ,
92+ busy_pin : DigitalInOut ,
93+ ) -> None :
94+ super ().__init__ (width , height , spi , cs_pin , dc_pin , sramcs_pin , rst_pin , busy_pin )
95+
96+ stride = width
97+ if stride % 8 != 0 :
98+ stride += 8 - stride % 8
99+
100+ self ._buffer1_size = int (stride * height / 8 )
101+ self ._buffer2_size = self ._buffer1_size
102+
103+ if sramcs_pin :
104+ self ._buffer1 = self .sram .get_view (0 )
105+ self ._buffer2 = self .sram .get_view (self ._buffer1_size )
106+ else :
107+ self ._buffer1 = bytearray (self ._buffer1_size )
108+ self ._buffer2 = bytearray (self ._buffer2_size )
109+
110+ self ._framebuf1 = adafruit_framebuf .FrameBuffer (
111+ self ._buffer1 , width , height , buf_format = adafruit_framebuf .MHMSB
112+ )
113+ self ._framebuf2 = adafruit_framebuf .FrameBuffer (
114+ self ._buffer2 , width , height , buf_format = adafruit_framebuf .MHMSB
115+ )
116+ self .set_black_buffer (0 , True )
117+ self .set_color_buffer (1 , False )
118+
119+ # Set single byte transactions flag
120+ self ._single_byte_tx = True
121+
122+ # Set the display update value
123+ self ._display_update_val = 0xF7
124+
125+ # Default initialization sequence (tri-color mode)
126+ self ._default_init_code = bytes ([
127+ _SSD1683_SW_RESET , 0 , # Software reset
128+ 0xFF , 50 , # Wait for busy (50ms delay)
129+
130+ _SSD1683_WRITE_BORDER , 1 , # Border waveform control
131+ 0x05 , # Border color/waveform
132+
133+ _SSD1683_TEMP_CONTROL , 1 , # Temperature control
134+ 0x80 , # Read temp
135+
136+ _SSD1683_DATA_MODE , 1 , # Data entry mode
137+ 0x03 , # Y decrement, X increment
138+
139+ 0xFE # End of initialization
140+ ])
141+
142+ def begin (self , reset : bool = True ) -> None :
143+ """Begin communication with the display and set basic settings"""
144+ if reset :
145+ self .hardware_reset ()
146+ self .power_down ()
147+
148+ def busy_wait (self ) -> None :
149+ """Wait for display to be done with current task, either by polling the
150+ busy pin, or pausing"""
151+ if self ._busy :
152+ while self ._busy .value : # wait for busy low
153+ time .sleep (0.01 )
154+ else :
155+ time .sleep (_BUSY_WAIT / 1000.0 ) # Convert ms to seconds
156+
157+ def power_up (self ) -> None :
158+ """Power up the display in preparation for writing RAM and updating"""
159+ self .hardware_reset ()
160+ time .sleep (0.1 )
161+ self .busy_wait ()
162+
163+ # Use custom init code if provided, otherwise use default
164+ init_code = self ._default_init_code
165+ if hasattr (self , '_epd_init_code' ) and self ._epd_init_code is not None :
166+ init_code = self ._epd_init_code
167+
168+ # Send initialization sequence
169+ self ._send_command_list (init_code )
170+
171+ # Set RAM window
172+ self .set_ram_window (0 , 0 , (self ._width // 8 ) - 1 , self ._height - 1 )
173+
174+ # Set RAM address to start position
175+ self .set_ram_address (0 , 0 )
176+
177+ # Set LUT if we have one
178+ if hasattr (self , '_epd_lut_code' ) and self ._epd_lut_code :
179+ self ._send_command_list (self ._epd_lut_code )
180+
181+ # Set display size and driver output control
182+ _b0 = (self ._height - 1 ) & 0xFF
183+ _b1 = ((self ._height - 1 ) >> 8 ) & 0xFF
184+ _b2 = 0x00
185+ self .command (_SSD1683_DRIVER_CONTROL , bytearray ([_b0 , _b1 , _b2 ]))
186+
187+ def power_down (self ) -> None :
188+ """Power down the display - required when not actively displaying!"""
189+ # Only deep sleep if we can get out of it
190+ if self ._rst :
191+ # deep sleep
192+ self .command (_SSD1683_DEEP_SLEEP , bytearray ([0x01 ]))
193+ time .sleep (0.1 )
194+ else :
195+ self .command (_SSD1683_SW_RESET )
196+ self .busy_wait ()
197+
198+ def update (self ) -> None :
199+ """Update the display from internal memory"""
200+ # display update sequence
201+ self .command (_SSD1683_DISP_CTRL2 , bytearray ([self ._display_update_val ]))
202+ self .command (_SSD1683_MASTER_ACTIVATE )
203+ self .busy_wait ()
204+
205+ if not self ._busy :
206+ time .sleep (1 ) # wait 1 second
207+
208+ def write_ram (self , index : Literal [0 , 1 ]) -> int :
209+ """Send the one byte command for starting the RAM write process. Returns
210+ the byte read at the same time over SPI. index is the RAM buffer, can be
211+ 0 or 1 for tri-color displays."""
212+ if index == 0 :
213+ return self .command (_SSD1683_WRITE_RAM1 , end = False )
214+ if index == 1 :
215+ return self .command (_SSD1683_WRITE_RAM2 , end = False )
216+ raise RuntimeError ("RAM index must be 0 or 1" )
217+
218+ def set_ram_address (self , x : int , y : int ) -> None :
219+ """Set the RAM address location"""
220+ # set RAM x address count
221+ self .command (_SSD1683_SET_RAMXCOUNT , bytearray ([x & 0xFF ]))
222+
223+ # set RAM y address count
224+ self .command (_SSD1683_SET_RAMYCOUNT , bytearray ([y & 0xFF , (y >> 8 ) & 0xFF ]))
225+
226+ def set_ram_window (self , x1 : int , y1 : int , x2 : int , y2 : int ) -> None :
227+ """Set the RAM window for partial updates"""
228+ # Set ram X start/end position
229+ self .command (_SSD1683_SET_RAMXPOS , bytearray ([x1 & 0xFF , x2 & 0xFF ]))
230+
231+ # Set ram Y start/end position
232+ self .command (_SSD1683_SET_RAMYPOS , bytearray ([
233+ y1 & 0xFF , (y1 >> 8 ) & 0xFF ,
234+ y2 & 0xFF , (y2 >> 8 ) & 0xFF
235+ ]))
236+
237+ def _send_command_list (self , init_sequence : bytes ) -> None :
238+ """Send a sequence of commands from an initialization list"""
239+ i = 0
240+ while i < len (init_sequence ):
241+ cmd = init_sequence [i ]
242+ i += 1
243+
244+ if cmd == 0xFE : # End marker
245+ break
246+ elif cmd == 0xFF : # Delay marker
247+ if i < len (init_sequence ):
248+ delay_ms = init_sequence [i ]
249+ i += 1
250+ time .sleep (delay_ms / 1000.0 )
251+ else :
252+ # Regular command
253+ if i < len (init_sequence ):
254+ num_args = init_sequence [i ]
255+ i += 1
256+ if num_args > 0 and (i + num_args ) <= len (init_sequence ):
257+ args = init_sequence [i :i + num_args ]
258+ self .command (cmd , bytearray (args ))
259+ i += num_args
260+ else :
261+ self .command (cmd )
0 commit comments