1
+ # SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """
6
+ `adafruit_epd.uc8179` - Adafruit UC8179 - ePaper display driver
7
+ ====================================================================================
8
+ CircuitPython driver for Adafruit UC8179 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
+ # UC8179 commands
34
+ _UC8179_PANELSETTING = const (0x00 )
35
+ _UC8179_POWERSETTING = const (0x01 )
36
+ _UC8179_POWEROFF = const (0x02 )
37
+ _UC8179_POWERON = const (0x04 )
38
+ _UC8179_DEEPSLEEP = const (0x07 )
39
+ _UC8179_WRITE_RAM1 = const (0x10 )
40
+ _UC8179_DATASTOP = const (0x11 )
41
+ _UC8179_DISPLAYREFRESH = const (0x12 )
42
+ _UC8179_WRITE_RAM2 = const (0x13 )
43
+ _UC8179_DUALSPI = const (0x15 )
44
+ _UC8179_WRITE_VCOM = const (0x50 )
45
+ _UC8179_TCON = const (0x60 )
46
+ _UC8179_TRES = const (0x61 )
47
+ _UC8179_GET_STATUS = const (0x71 )
48
+
49
+ BUSY_WAIT = const (500 ) # milliseconds
50
+
51
+
52
+ class Adafruit_UC8179 (Adafruit_EPD ):
53
+ """driver class for Adafruit UC8179 ePaper display breakouts"""
54
+
55
+ # Default initialization sequence
56
+ _default_init_code = bytes ([
57
+ _UC8179_POWERSETTING , 4 ,
58
+ 0x07 , # VGH=20V
59
+ 0x07 , # VGL=-20V
60
+ 0x3F , # VDH=15V
61
+ 0x3F , # VDL=-15V
62
+
63
+ _UC8179_POWERON , 0 ,
64
+ 0xFF , 100 , # busy wait
65
+
66
+ _UC8179_PANELSETTING , 1 ,
67
+ 0b010111 , # BW OTP LUT
68
+
69
+ _UC8179_TRES , 4 ,
70
+ 0x02 , 0x88 , 0x01 , 0xE0 ,
71
+
72
+ _UC8179_DUALSPI , 1 , 0x00 ,
73
+
74
+ _UC8179_WRITE_VCOM , 2 , 0x10 , 0x07 ,
75
+ _UC8179_TCON , 1 , 0x22 ,
76
+
77
+ 0xFE # End marker
78
+ ])
79
+
80
+ def __init__ (
81
+ self ,
82
+ width : int ,
83
+ height : int ,
84
+ spi : SPI ,
85
+ * ,
86
+ cs_pin : DigitalInOut ,
87
+ dc_pin : DigitalInOut ,
88
+ sramcs_pin : DigitalInOut ,
89
+ rst_pin : DigitalInOut ,
90
+ busy_pin : DigitalInOut ,
91
+ ) -> None :
92
+ # Adjust height to be divisible by 8 (direct from Arduino)
93
+ if (height % 8 ) != 0 :
94
+ height += 8 - (height % 8 )
95
+
96
+ super ().__init__ (width , height , spi , cs_pin , dc_pin , sramcs_pin , rst_pin , busy_pin )
97
+
98
+ # Calculate buffer sizes exactly as Arduino does: width * height / 8
99
+ self ._buffer1_size = width * height // 8
100
+ self ._buffer2_size = self ._buffer1_size
101
+
102
+ if sramcs_pin :
103
+ # Using external SRAM
104
+ self ._buffer1 = self .sram .get_view (0 )
105
+ self ._buffer2 = self .sram .get_view (self ._buffer1_size )
106
+ else :
107
+ # Using internal RAM
108
+ self ._buffer1 = bytearray (self ._buffer1_size )
109
+ self ._buffer2 = bytearray (self ._buffer2_size )
110
+
111
+ # Create frame buffers
112
+ self ._framebuf1 = adafruit_framebuf .FrameBuffer (
113
+ self ._buffer1 ,
114
+ width ,
115
+ height ,
116
+ buf_format = adafruit_framebuf .MHMSB ,
117
+ )
118
+ self ._framebuf2 = adafruit_framebuf .FrameBuffer (
119
+ self ._buffer2 ,
120
+ width ,
121
+ height ,
122
+ buf_format = adafruit_framebuf .MHMSB ,
123
+ )
124
+
125
+ # Set up which frame buffer is which color
126
+ self .set_black_buffer (0 , True )
127
+ self .set_color_buffer (1 , False )
128
+
129
+ # UC8179 uses single byte transactions
130
+ self ._single_byte_tx = True
131
+
132
+ # Custom init code if needed
133
+ self ._epd_init_code = None
134
+
135
+ # Default refresh delay (from Adafruit_EPD base class in Arduino)
136
+ self .default_refresh_delay = 15 # seconds
137
+ # pylint: enable=too-many-arguments
138
+
139
+ def begin (self , reset : bool = True ) -> None :
140
+ """Begin communication with the display and set basic settings"""
141
+ # Direct port of Arduino begin() method
142
+
143
+ # Note: Arduino sets _data_entry_mode = THINKINK_UC8179
144
+ # This appears to be specific to SRAM organization for this chip
145
+
146
+ if reset :
147
+ self .hardware_reset ()
148
+
149
+ # Black buffer defaults to inverted (0 means black)
150
+ self .set_black_buffer (0 , True )
151
+ # Red/color buffer defaults to not inverted (1 means red)
152
+ self .set_color_buffer (1 , False )
153
+
154
+ self .power_down ()
155
+
156
+ def busy_wait (self ) -> None :
157
+ """Wait for display to be done with current task, either by polling the
158
+ busy pin, or pausing"""
159
+ if self ._busy :
160
+ # Wait for busy pin to go HIGH
161
+ while not self ._busy .value :
162
+ self .command (_UC8179_GET_STATUS )
163
+ time .sleep (0.1 )
164
+ else :
165
+ # No busy pin, just wait
166
+ time .sleep (BUSY_WAIT / 1000.0 )
167
+ # Additional delay after busy signal
168
+ time .sleep (0.2 )
169
+
170
+ def power_up (self ) -> None :
171
+ """Power up the display in preparation for writing RAM and updating"""
172
+ self .hardware_reset ()
173
+
174
+ # Use custom init code if provided, otherwise use default
175
+ init_code = self ._epd_init_code if self ._epd_init_code else self ._default_init_code
176
+
177
+ # Process initialization sequence
178
+ self ._command_list (init_code )
179
+
180
+ # Set display resolution (using WIDTH and HEIGHT macros in Arduino)
181
+ self .command (
182
+ _UC8179_TRES ,
183
+ bytearray ([self ._width >> 8 , self ._width & 0xFF , self ._height >> 8 , self ._height & 0xFF ]),
184
+ )
185
+
186
+ def power_down (self ) -> None :
187
+ """Power down the display - required when not actively displaying!"""
188
+ self .command (_UC8179_POWEROFF )
189
+ self .busy_wait ()
190
+
191
+ # Only deep sleep if we have a reset pin to wake it up
192
+ if self ._rst :
193
+ self .command (_UC8179_DEEPSLEEP , bytearray ([0x05 ]))
194
+ time .sleep (0.1 )
195
+
196
+ def update (self ) -> None :
197
+ """Update the display from internal memory"""
198
+ self .command (_UC8179_DISPLAYREFRESH )
199
+ time .sleep (0.1 )
200
+ self .busy_wait ()
201
+
202
+ if not self ._busy :
203
+ # If no busy pin, use default refresh delay
204
+ time .sleep (self .default_refresh_delay / 1000.0 )
205
+
206
+ def write_ram (self , index : Literal [0 , 1 ]) -> int :
207
+ """Send the one byte command for starting the RAM write process. Returns
208
+ the byte read at the same time over SPI. index is the RAM buffer, can be
209
+ 0 or 1 for tri-color displays."""
210
+ if index == 0 :
211
+ return self .command (_UC8179_WRITE_RAM1 , end = False )
212
+ if index == 1 :
213
+ return self .command (_UC8179_WRITE_RAM2 , end = False )
214
+ raise RuntimeError ("RAM index must be 0 or 1" )
215
+
216
+ def set_ram_address (self , x : int , y : int ) -> None : # noqa: PLR6301, F841
217
+ """Set the RAM address location, not used on this chipset but required by
218
+ the superclass"""
219
+ # Not used in UC8179 chip
220
+ pass
221
+
222
+ def set_ram_window (self , x1 : int , y1 : int , x2 : int , y2 : int ) -> None : # noqa: PLR6301, F841
223
+ """Set the RAM window, not used on this chipset but required by
224
+ the superclass"""
225
+ # Not used in UC8179 chip
226
+ pass
227
+
228
+ def _command_list (self , init_sequence : bytes ) -> None :
229
+ """Process a command list for initialization"""
230
+ i = 0
231
+ while i < len (init_sequence ):
232
+ cmd = init_sequence [i ]
233
+ i += 1
234
+
235
+ # Check for end marker
236
+ if cmd == 0xFE :
237
+ break
238
+
239
+ # Get number of data bytes
240
+ num_data = init_sequence [i ]
241
+ i += 1
242
+
243
+ # Check for delay command (0xFF)
244
+ if cmd == 0xFF :
245
+ time .sleep (num_data / 1000.0 )
246
+ else :
247
+ # Send command with data if any
248
+ if num_data > 0 :
249
+ self .command (cmd , bytearray (init_sequence [i :i + num_data ]))
250
+ i += num_data
251
+ else :
252
+ self .command (cmd )
0 commit comments