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