1
+ # 适用于 E-Paper 的 Framebuffer 驱动
2
+ # Github: https://github.com/funnygeeker/micropython-easydisplay
3
+ # Author: funnygeeker
4
+ # Licence: MIT
5
+ # Date: 2024/2/17
6
+ #
7
+ # 参考资料:
8
+ # https://github.com/mcauser/micropython-waveshare-epaper
9
+ # https://github.com/AntonVanke/MicroPython-uFont/blob/master/driver/e1in54.py
10
+ import math
11
+ from struct import pack
12
+ from time import sleep_ms
13
+ from machine import Pin , PWM
14
+ from micropython import const
15
+ from framebuf import FrameBuffer , MONO_HLSB
16
+
17
+ # Display resolution
18
+ EPD_WIDTH = const (200 )
19
+ EPD_HEIGHT = const (200 )
20
+
21
+ # Display commands
22
+ DRIVER_OUTPUT_CONTROL = const (0x01 )
23
+ BOOSTER_SOFT_START_CONTROL = const (0x0C )
24
+ #GATE_SCAN_START_POSITION = const(0x0F)
25
+ DEEP_SLEEP_MODE = const (0x10 )
26
+ DATA_ENTRY_MODE_SETTING = const (0x11 )
27
+ #SW_RESET = const(0x12)
28
+ #TEMPERATURE_SENSOR_CONTROL = const(0x1A)
29
+ MASTER_ACTIVATION = const (0x20 )
30
+ #DISPLAY_UPDATE_CONTROL_1 = const(0x21)
31
+ DISPLAY_UPDATE_CONTROL_2 = const (0x22 )
32
+ WRITE_RAM = const (0x24 )
33
+ WRITE_VCOM_REGISTER = const (0x2C )
34
+ WRITE_LUT_REGISTER = const (0x32 )
35
+ SET_DUMMY_LINE_PERIOD = const (0x3A )
36
+ SET_GATE_TIME = const (0x3B ) # not in datasheet
37
+ #BORDER_WAVEFORM_CONTROL = const(0x3C)
38
+ SET_RAM_X_ADDRESS_START_END_POSITION = const (0x44 )
39
+ SET_RAM_Y_ADDRESS_START_END_POSITION = const (0x45 )
40
+ SET_RAM_X_ADDRESS_COUNTER = const (0x4E )
41
+ SET_RAM_Y_ADDRESS_COUNTER = const (0x4F )
42
+ TERMINATE_FRAME_READ_WRITE = const (0xFF ) # aka NOOP
43
+
44
+ BUSY = const (1 ) # 1=busy, 0=idle
45
+
46
+ class EPD (FrameBuffer ):
47
+ LUT_FULL_UPDATE = bytearray (b'\x02 \x02 \x01 \x11 \x12 \x12 \x22 \x22 \x66 \x69 \x69 \x59 \x58 \x99 \x99 \x88 \x00 \x00 \x00 \x00 \xF8 \xB4 \x13 \x51 \x35 \x51 \x51 \x19 \x01 \x00 ' )
48
+ LUT_PARTIAL_UPDATE = bytearray (b'\x10 \x18 \x18 \x08 \x18 \x18 \x08 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x13 \x14 \x44 \x12 \x00 \x00 \x00 \x00 \x00 \x00 ' )
49
+
50
+ def __init__ (self , width : int , height : int , spi , res : int , dc : int , busy :int ,
51
+ cs : int = None , bl : int = None ):
52
+ """
53
+ 初始化屏幕驱动
54
+
55
+ Args:
56
+ width: 宽度
57
+ height: 高度
58
+ spi: SPI 实例
59
+ res: RESET 引脚
60
+ dc: Data / Command 引脚
61
+ busy:
62
+ cs: 片选引脚
63
+ bl: 背光引脚
64
+ """
65
+ self .width = width
66
+ self .height = height
67
+ self .spi = spi
68
+ self .res = Pin (res , Pin .OUT , Pin .PULL_DOWN , value = 0 )
69
+ self .dc = Pin (dc , Pin .OUT , Pin .PULL_DOWN , value = 0 )
70
+ self .busy = Pin (busy , Pin .IN )
71
+ if cs is None :
72
+ self .cs = int
73
+ else :
74
+ self .cs = Pin (cs , Pin .OUT , Pin .PULL_DOWN , value = 1 )
75
+ if bl is not None :
76
+ self .bl = PWM (Pin (bl , Pin .OUT ))
77
+ self .back_light (255 )
78
+ else :
79
+ self .bl = None
80
+ self .pages = self .height // 8
81
+ self .buffer = bytearray (self .width * self .pages )
82
+ super ().__init__ (self .buffer , self .width , self .height , MONO_HLSB )
83
+ self .init ()
84
+
85
+ def init (self ):
86
+ self .hard_reset ()
87
+ self ._write (DRIVER_OUTPUT_CONTROL )
88
+ self .write_data (bytearray ([(EPD_HEIGHT - 1 ) & 0xFF ]))
89
+ self .write_data (bytearray ([((EPD_HEIGHT - 1 ) >> 8 ) & 0xFF ]))
90
+ self .write_data (bytearray ([0x00 ])) # GD = 0 SM = 0 TB = 0
91
+ self ._write (BOOSTER_SOFT_START_CONTROL , b'\xD7 \xD6 \x9D ' )
92
+ self ._write (WRITE_VCOM_REGISTER , b'\xA8 ' ) # VCOM 7C
93
+ self ._write (SET_DUMMY_LINE_PERIOD , b'\x1A ' ) # 4 dummy lines per gate
94
+ self ._write (SET_GATE_TIME , b'\x08 ' ) # 2us per line
95
+ self ._write (DATA_ENTRY_MODE_SETTING , b'\x03 ' ) # X increment Y increment
96
+ self .set_lut (self .LUT_FULL_UPDATE )
97
+
98
+ def _write (self , command = None , data = None ):
99
+ """SPI write to the device: commands and data."""
100
+ self .cs (0 )
101
+ if command is not None :
102
+ self .dc (0 )
103
+ self .spi .write (bytes ([command ]))
104
+ if data is not None :
105
+ self .dc (1 )
106
+ self .spi .write (data )
107
+ self .cs (1 )
108
+
109
+ def write_cmd (self , cmd ):
110
+ """
111
+ 写命令
112
+
113
+ Args:
114
+ cmd: 命令内容
115
+ """
116
+ self .cs (0 )
117
+ self .dc (0 )
118
+ self .spi .write (bytes ([cmd ]))
119
+ self .cs (1 )
120
+
121
+ def write_data (self , data ):
122
+ """
123
+ 写数据
124
+
125
+ Args:
126
+ data: 数据内容
127
+ """
128
+ self .cs (0 )
129
+ self .dc (1 )
130
+ self .spi .write (data )
131
+ self .cs (1 )
132
+
133
+ def wait_until_idle (self ):
134
+ while self .busy .value () == BUSY :
135
+ sleep_ms (50 )
136
+
137
+ def hard_reset (self ):
138
+ """
139
+ Hard reset display.
140
+ """
141
+ self .res (0 )
142
+ sleep_ms (100 )
143
+ self .res (1 )
144
+ sleep_ms (100 )
145
+
146
+ def soft_reset (self ):
147
+ # Function not realized
148
+ pass
149
+
150
+ def set_lut (self , lut ):
151
+ self ._write (WRITE_LUT_REGISTER , lut )
152
+
153
+ def set_refresh (self , full_update = True ):
154
+ """
155
+ Set the refresh mode
156
+
157
+ Args:
158
+ full_update: Full screen refresh
159
+ """
160
+ self .set_lut (self .LUT_FULL_UPDATE ) if full_update else self .set_lut (self .LUT_PARTIAL_UPDATE )
161
+
162
+ # put an image in the frame memory
163
+ def set_frame_memory (self , image , x , y , w , h ):
164
+ # x point must be the multiple of 8 or the last 3 bits will be ignored
165
+ x = x & 0xF8
166
+ w = w & 0xF8
167
+
168
+ if x + w >= self .width :
169
+ x_end = self .width - 1
170
+ else :
171
+ x_end = x + w - 1
172
+
173
+ if y + h >= self .height :
174
+ y_end = self .height - 1
175
+ else :
176
+ y_end = y + h - 1
177
+
178
+ self .set_window (x , y , x_end , y_end )
179
+ self .set_memory_pointer (x , y )
180
+ self ._write (WRITE_RAM , image )
181
+
182
+ # replace the frame memory with the specified color
183
+ def clear_frame_memory (self , color ):
184
+ self .set_window (0 , 0 , self .width - 1 , self .height - 1 )
185
+ self .set_memory_pointer (0 , 0 )
186
+ self ._write (WRITE_RAM )
187
+ # send the color data
188
+ for i in range (0 , self .width // 8 * self .height ):
189
+ self .write_data (bytearray ([color ]))
190
+
191
+ # draw the current frame memory and switch to the next memory area
192
+ def display_frame (self ):
193
+ self ._write (DISPLAY_UPDATE_CONTROL_2 , b'\xC4 ' )
194
+ self ._write (MASTER_ACTIVATION )
195
+ self ._write (TERMINATE_FRAME_READ_WRITE )
196
+ self .wait_until_idle ()
197
+
198
+ # specify the memory area for data R/W
199
+ def set_memory_area (self , x_start , y_start , x_end , y_end ):
200
+ self ._write (SET_RAM_X_ADDRESS_START_END_POSITION )
201
+ # x point must be the multiple of 8 or the last 3 bits will be ignored
202
+ self .write_data (bytearray ([(x_start >> 3 ) & 0xFF ]))
203
+ self .write_data (bytearray ([(x_end >> 3 ) & 0xFF ]))
204
+ self ._write (SET_RAM_Y_ADDRESS_START_END_POSITION , pack ("<HH" , y_start , y_end ))
205
+
206
+ # specify the start point for data R/W
207
+ def set_memory_pointer (self , x , y ):
208
+ self ._write (SET_RAM_X_ADDRESS_COUNTER )
209
+ # x point must be the multiple of 8 or the last 3 bits will be ignored
210
+ self .write_data (bytearray ([(x >> 3 ) & 0xFF ]))
211
+ self ._write (SET_RAM_Y_ADDRESS_COUNTER , pack ("<H" , y ))
212
+ self .wait_until_idle ()
213
+
214
+ def clear (self ):
215
+ self .fill (0 )
216
+
217
+
218
+ def show (self ):
219
+ self .set_frame_memory (self .buffer , 0 , 0 , 200 , 200 )
220
+ self .display_frame ()
221
+
222
+ # to wake call reset() or init()
223
+ def poweroff (self ):
224
+ """Enable display sleep mode."""
225
+ self ._write (DEEP_SLEEP_MODE , b'\x01 ' ) # enter deep sleep A0=1, A0=0 power on
226
+ self .wait_until_idle ()
227
+
228
+ def poweron (self ):
229
+ """Disable display sleep mode."""
230
+ self .hard_reset ()
231
+ self .wait_until_idle ()
232
+
233
+ def back_light (self , value ):
234
+ """
235
+ 背光调节
236
+
237
+ Args:
238
+ value: 背光等级 0 ~ 255
239
+ """
240
+ self .bl .freq (1000 )
241
+ if value >= 0xff :
242
+ value = 0xff
243
+ data = value * 0xffff >> 8
244
+ self .bl .duty_u16 (data )
245
+
246
+ def circle (self , x , y , radius , c , section = 100 ):
247
+ """
248
+ 画圆
249
+
250
+ Args:
251
+ c: 颜色
252
+ x: 中心 x 坐标
253
+ y: 中心 y 坐标
254
+ radius: 半径
255
+ section: 分段
256
+ """
257
+ arr = []
258
+ for m in range (section + 1 ):
259
+ _x = round (radius * math .cos ((2 * math .pi / section ) * m - math .pi ) + x )
260
+ _y = round (radius * math .sin ((2 * math .pi / section ) * m - math .pi ) + y )
261
+ arr .append ([_x , _y ])
262
+ for i in range (len (arr ) - 1 ):
263
+ self .line (* arr [i ], * arr [i + 1 ], c )
264
+
265
+ def fill_circle (self , x , y , radius , c ):
266
+ """
267
+ 画填充圆
268
+
269
+ Args:
270
+ c: 颜色
271
+ x: 中心 x 坐标
272
+ y: 中心 y 坐标
273
+ radius: 半径
274
+ """
275
+ rsq = radius * radius
276
+ for _x in range (radius ):
277
+ _y = int (math .sqrt (rsq - _x * _x )) # 计算 y 坐标
278
+ y0 = y - _y
279
+ end_y = y0 + _y * 2
280
+ y0 = max (0 , min (y0 , self .height )) # 将 y0 限制在画布的范围内
281
+ length = abs (end_y - y0 ) + 1
282
+ self .vline (x + _x , y0 , length , c ) # 绘制左右两侧的垂直线
283
+ self .vline (x - _x , y0 , length , c )
0 commit comments