ESP32-2432S028R 开发板在创客社区中被称为“Cheap Yellow Display”或简称 CYD。这款开发板的主芯片是 ESP32-WROOM-32 模组,自带

  • 2.8 英寸 TFT 触摸屏 LCD
  • microSD 卡接口
  • RGB 指示灯
  • LDR(光敏电阻器)
  • 供电、串口通信电路

下面是它的背面图以及相关部分的说明:

屏幕显示驱动相关引脚
SPI Pin GPIO
MISO GPIO 12
MOSI GPIO 13
SCKL GPIO 14
CS GPIO 15
DC GPIO 2
RST -1
Backlight Pin GPIO 21
屏幕触屏驱动相关引脚
SPI Pin GPIO
IRQ GPIO 36
MOSI GPIO 32
MISO GPIO 39
CLK GPIO 25
CS GPIO 33
RGB灯珠相关引脚
RGB LED GPIO
Red LED GPIO 4
Green LED GPIO 16
Blue LED GPIO 17
MicroSD卡相关引脚
MicroSD card SPI GPIO
MISO GPIO 19
MOSI GPIO 23
SCK GPIO 18
CS GPIO 5
LDR GPIO 34
Speaker GPIO 26
BOOT Button GPIO 0

通过图示和表格可以看出,ESP32开发板的许多引脚已被占用。因此,若要使用这块板子进行开发,特别是涉及引脚需求较多的外设时,需慎重考虑是否适合选择此板。接下来我们通过一些程序来检验这个板子的可用性。

1.RGB灯珠

RGB,这三个字母分别表示红(red)、绿(green)、蓝(blue),这三种颜色构成了光的三原色,可以说我们能见到的任何一种颜色都由它们混合而成。所以我们只需要分别控制这三种光的强度,就可以控制它最终显示出来的颜色了。

因为这个RGB灯珠内部没有集成特殊芯片(这类芯片可以用最少的引脚来控制尽可能多的led),所以我们还得一个一个的去控制每一个LED。下面,我们通过一个代码来让这个RGB灯珠循环渐变彩虹:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 导入必要的模块
from machine import Pin, PWM
import time

# 定义RGB灯珠的引脚
red_pin = Pin(4, Pin.OUT)
green_pin = Pin(16, Pin.OUT)
blue_pin = Pin(17, Pin.OUT)

# 创建PWM对象来控制亮度
pwm_red = PWM(red_pin)
pwm_green = PWM(green_pin)
pwm_blue = PWM(blue_pin)

# 设置PWM频率为1000Hz
pwm_red.freq(1000)
pwm_green.freq(1000)
pwm_blue.freq(1000)

# 颜色轮函数
def wheel(pos):
"""生成颜色轮上的颜色。"""
if pos < 0 or pos > 255:
r = g = b = 0
elif pos < 85:
r = int(pos * 3)
g = int(255 - pos * 3)
b = 0
elif pos < 170:
pos -= 85
r = int(255 - pos * 3)
g = 0
b = int(pos * 3)
else:
pos -= 170
r = 0
g = int(pos * 3)
b = int(255 - pos * 3)
return (r, g, b)

# 主程序入口
def main():
pos = 0 # 初始化位置变量
try:
while True: # 无限循环
color = wheel(pos)
print("Setting color:", color) # 打印颜色值

# 将RGB值映射到PWM占空比范围(0-1023)
pwm_red.duty(int(color[0] * 1023 / 255))
pwm_green.duty(int(color[1] * 1023 / 255))
pwm_blue.duty(int(color[2] * 1023 / 255))

# 更新位置变量以实现颜色轮的效果
pos = (pos + 1) % 256

time.sleep(0.03) # 可以根据需要调整这个值

except KeyboardInterrupt:
pwm_red.deinit()
pwm_green.deinit()
pwm_blue.deinit()
print("Color cycle stopped.")

# 运行主程序
main()

需要注意的是,RGB LED的引脚是反向逻辑的,也就是说它们是低电平有效。这意味着, HIGH = OFF 和 LOW = ON:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from machine import Pin
import os
from time import sleep

# RGB LED at the back
red_led = Pin(4, Pin.OUT)
green_led = Pin(16, Pin.OUT)
blue_led = Pin(17, Pin.OUT)
# 开启
red_led.off()
green_led.off()
blue_led.off()
sleep(2)
# 关闭
red_led.on()
green_led.on()
blue_led.on()

2.MicroSD卡

在ESP32-CYD上,设计者非常贴心地预留了microSD卡的卡槽。这一设计考虑到了用户未来可能面临的内存不足问题。特别是当你需要加载CYD自带屏幕的两个驱动库(显示与触屏),或者安装其他第三方库时,内存需求会不断增加。因此,拥有一个SD卡作为外部扩展内存是一个十分明智的选择。

为了能够让ESP32设备读取到SD卡的内容,我们需要一个驱动库(sdcard.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
"""
MicroPython driver for SD cards using SPI bus.

Requires an SPI bus and a CS pin. Provides readblocks and writeblocks
methods so the device can be mounted as a filesystem.

Example usage on pyboard:

import pyb, sdcard, os
sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5)
pyb.mount(sd, '/sd2')
os.listdir('/')

Example usage on ESP8266:

import machine, sdcard, os
sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))
os.mount(sd, '/sd')
os.listdir('/')

"""

from micropython import const
import time

_CMD_TIMEOUT = const(100)

_R1_IDLE_STATE = const(1 << 0)
R1_ERASE_RESET = const(1 << 1)
_R1_ILLEGAL_COMMAND = const(1 << 2)
R1_COM_CRC_ERROR = const(1 << 3)
R1_ERASE_SEQUENCE_ERROR = const(1 << 4)
R1_ADDRESS_ERROR = const(1 << 5)
R1_PARAMETER_ERROR = const(1 << 6)
_TOKEN_CMD25 = const(0xFC)
_TOKEN_STOP_TRAN = const(0xFD)
_TOKEN_DATA = const(0xFE)

class SDCard:
def __init__(self, spi, cs, baudrate=1320000):
self.spi = spi
self.cs = cs

self.cmdbuf = bytearray(6)
self.dummybuf = bytearray(512)
self.tokenbuf = bytearray(1)
for i in range(512):
self.dummybuf[i] = 0xFF
self.dummybuf_memoryview = memoryview(self.dummybuf)

# initialise the card
self.init_card(baudrate)

def init_spi(self, baudrate):
try:
master = self.spi.MASTER
except AttributeError:
# on ESP8266
self.spi.init(baudrate=baudrate, phase=0, polarity=0)
else:
# on pyboard
self.spi.init(master, baudrate=baudrate, phase=0, polarity=0)

def init_card(self, baudrate):

# init CS pin
self.cs.init(self.cs.OUT, value=1)

# init SPI bus; use low data rate for initialisation
self.init_spi(100000)

# clock card at least 100 cycles with cs high
for i in range(16):
self.spi.write(b"\xff")

# CMD0: init card; should return _R1_IDLE_STATE (allow 5 attempts)
for _ in range(5):
if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE:
break
else:
raise OSError("no SD card")

# CMD8: determine card version
r = self.cmd(8, 0x01AA, 0x87, 4)
if r == _R1_IDLE_STATE:
self.init_card_v2()
elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND):
self.init_card_v1()
else:
raise OSError("couldn't determine SD card version")

# get the number of sectors
# CMD9: response R2 (R1 byte + 16-byte block read)
if self.cmd(9, 0, 0, 0, False) != 0:
raise OSError("no response from SD card")
csd = bytearray(16)
self.readinto(csd)
if csd[0] & 0xC0 == 0x40: # CSD version 2.0
self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024
elif csd[0] & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB)
c_size = (csd[6] & 0b11) << 10 | csd[7] << 2 | csd[8] >> 6
c_size_mult = (csd[9] & 0b11) << 1 | csd[10] >> 7
read_bl_len = csd[5] & 0b1111
capacity = (c_size + 1) * (2 ** (c_size_mult + 2)) * (2**read_bl_len)
self.sectors = capacity // 512
else:
raise OSError("SD card CSD format not supported")
# print('sectors', self.sectors)

# CMD16: set block length to 512 bytes
if self.cmd(16, 512, 0) != 0:
raise OSError("can't set 512 block size")

# set to high data rate now that it's initialised
self.init_spi(baudrate)

def init_card_v1(self):
for i in range(_CMD_TIMEOUT):
self.cmd(55, 0, 0)
if self.cmd(41, 0, 0) == 0:
# SDSC card, uses byte addressing in read/write/erase commands
self.cdv = 512
# print("[SDCard] v1 card")
return
raise OSError("timeout waiting for v1 card")

def init_card_v2(self):
for i in range(_CMD_TIMEOUT):
time.sleep_ms(50)
self.cmd(58, 0, 0, 4)
self.cmd(55, 0, 0)
if self.cmd(41, 0x40000000, 0) == 0:
self.cmd(58, 0, 0, -4) # 4-byte response, negative means keep the first byte
ocr = self.tokenbuf[0] # get first byte of response, which is OCR
if not ocr & 0x40:
# SDSC card, uses byte addressing in read/write/erase commands
self.cdv = 512
else:
# SDHC/SDXC card, uses block addressing in read/write/erase commands
self.cdv = 1
# print("[SDCard] v2 card")
return
raise OSError("timeout waiting for v2 card")

def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False):
self.cs(0)

# create and send the command
buf = self.cmdbuf
buf[0] = 0x40 | cmd
buf[1] = arg >> 24
buf[2] = arg >> 16
buf[3] = arg >> 8
buf[4] = arg
buf[5] = crc
self.spi.write(buf)

if skip1:
self.spi.readinto(self.tokenbuf, 0xFF)

# wait for the response (response[7] == 0)
for i in range(_CMD_TIMEOUT):
self.spi.readinto(self.tokenbuf, 0xFF)
response = self.tokenbuf[0]
if not (response & 0x80):
# this could be a big-endian integer that we are getting here
# if final<0 then store the first byte to tokenbuf and discard the rest
if final < 0:
self.spi.readinto(self.tokenbuf, 0xFF)
final = -1 - final
for j in range(final):
self.spi.write(b"\xff")
if release:
self.cs(1)
self.spi.write(b"\xff")
return response

# timeout
self.cs(1)
self.spi.write(b"\xff")
return -1

def readinto(self, buf):
self.cs(0)

# read until start byte (0xff)
for i in range(_CMD_TIMEOUT):
self.spi.readinto(self.tokenbuf, 0xFF)
if self.tokenbuf[0] == _TOKEN_DATA:
break
time.sleep_ms(1)
else:
self.cs(1)
raise OSError("timeout waiting for response")

# read data
mv = self.dummybuf_memoryview
if len(buf) != len(mv):
mv = mv[: len(buf)]
self.spi.write_readinto(mv, buf)

# read checksum
self.spi.write(b"\xff")
self.spi.write(b"\xff")

self.cs(1)
self.spi.write(b"\xff")

def write(self, token, buf):
self.cs(0)

# send: start of block, data, checksum
self.spi.read(1, token)
self.spi.write(buf)
self.spi.write(b"\xff")
self.spi.write(b"\xff")

# check the response
if (self.spi.read(1, 0xFF)[0] & 0x1F) != 0x05:
self.cs(1)
self.spi.write(b"\xff")
return

# wait for write to finish
while self.spi.read(1, 0xFF)[0] == 0:
pass

self.cs(1)
self.spi.write(b"\xff")

def write_token(self, token):
self.cs(0)
self.spi.read(1, token)
self.spi.write(b"\xff")
# wait for write to finish
while self.spi.read(1, 0xFF)[0] == 0x00:
pass

self.cs(1)
self.spi.write(b"\xff")

def readblocks(self, block_num, buf):
nblocks = len(buf) // 512
assert nblocks and not len(buf) % 512, "Buffer length is invalid"
if nblocks == 1:
# CMD17: set read address for single block
if self.cmd(17, block_num * self.cdv, 0, release=False) != 0:
# release the card
self.cs(1)
raise OSError(5) # EIO
# receive the data and release card
self.readinto(buf)
else:
# CMD18: set read address for multiple blocks
if self.cmd(18, block_num * self.cdv, 0, release=False) != 0:
# release the card
self.cs(1)
raise OSError(5) # EIO
offset = 0
mv = memoryview(buf)
while nblocks:
# receive the data and release card
self.readinto(mv[offset : offset + 512])
offset += 512
nblocks -= 1
if self.cmd(12, 0, 0xFF, skip1=True):
raise OSError(5) # EIO

def writeblocks(self, block_num, buf):
nblocks, err = divmod(len(buf), 512)
assert nblocks and not err, "Buffer length is invalid"
if nblocks == 1:
# CMD24: set write address for single block
if self.cmd(24, block_num * self.cdv, 0) != 0:
raise OSError(5) # EIO

# send the data
self.write(_TOKEN_DATA, buf)
else:
# CMD25: set write address for first block
if self.cmd(25, block_num * self.cdv, 0) != 0:
raise OSError(5) # EIO
# send the data
offset = 0
mv = memoryview(buf)
while nblocks:
self.write(_TOKEN_CMD25, mv[offset : offset + 512])
offset += 512
nblocks -= 1
self.write_token(_TOKEN_STOP_TRAN)

def ioctl(self, op, arg):
if op == 4: # get number of blocks
return self.sectors
if op == 5: # get block size in bytes
return 512

接下来我们可以通过代码对sd卡进行读写操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import os  
from machine import Pin, SoftSPI
from sdcard import SDCard

# 接线说明:
# MISO -> GPIO 19
# MOSI -> GPIO 23
# CLK -> GPIO 18
# CS -> GPIO 5

spisd = SoftSPI(-1, miso=Pin(19), mosi=Pin(23), sck=Pin(18))
sd = SDCard(spisd, Pin(5))

# os.listdir() --> 返回当前目录下的所有文件和目录的列表
print('未挂载SD之前:', os.listdir())
print("-------------------------------")
# 创建一个虚拟文件系统,用于挂载SD卡
vfs = os.VfsFat(sd)
# 挂载SD卡到虚拟文件系统:将虚拟文件系统挂载到/sd目录。之后,对/sd目录的访问将实际访问SD卡。
os.mount(vfs, '/sd')

print('挂载SD之后:', os.listdir())

# 对SD卡进行操作
try:
# 切换到SD卡目录
os.chdir('/sd')
print('SD卡中的文件:', os.listdir())

# 写入文件到SD卡
with open("/sd/test.txt", "w") as f:
for i in range(1, 101):
f.write(str(i) + "\n")
print("-------------------------------")
print("已经将1~100写入到SD卡中的test.txt文件")
print("-------------------------------")

# 从SD卡读取文件并显示内容
"""
在运行之前,请确保你的sd卡内
包含一个名为url.txt的文件,
且里面写入了些文本
"""
with open("/sd/url.txt", "r") as f:
content = f.readlines()
print("SD卡中url.txt文件的内容:")
print("-------------------------------")
for line in content:
print(line.strip())
print("-------------------------------")

except Exception as e:
print("\n发生错误!", e)

finally:
os.umount('/sd')
print("SD卡已卸载")

3.点亮屏幕

3.1显示

ESP32-CYD上自带了一个2.8 英寸(320x240) TFT 触摸屏 (LCD),TFT LCD 屏幕通过控制液晶分子的排列来改变光的透过率,从而实现图像的显示。这块屏幕的驱动芯片为ili9341,ILI9341 控制器负责将来自微控制器的显示数据转换为控制液晶分子的信号。当微控制器向 ILI9341 发送显示数据时,ILI9341 会根据这些数据控制 LCD 屏幕上的像素点,使其呈现出相应的颜色和亮度。

micropython驱动库(ili9341.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
"""ILI9341 LCD/Touch module."""
from time import sleep
from math import cos, sin, pi, radians
from sys import implementation
from framebuf import FrameBuffer, RGB565 # type: ignore
from micropython import const # type: ignore

def color565(r, g, b):
"""Return RGB565 color value.

Args:
r (int): Red value.
g (int): Green value.
b (int): Blue value.
"""
return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3

class Display(object):
"""Serial interface for 16-bit color (5-6-5 RGB) IL9341 display.

Note: All coordinates are zero based.
"""

# Command constants from ILI9341 datasheet
NOP = const(0x00) # No-op
SWRESET = const(0x01) # Software reset
RDDID = const(0x04) # Read display ID info
RDDST = const(0x09) # Read display status
SLPIN = const(0x10) # Enter sleep mode
SLPOUT = const(0x11) # Exit sleep mode
PTLON = const(0x12) # Partial mode on
NORON = const(0x13) # Normal display mode on
RDMODE = const(0x0A) # Read display power mode
RDMADCTL = const(0x0B) # Read display MADCTL
RDPIXFMT = const(0x0C) # Read display pixel format
RDIMGFMT = const(0x0D) # Read display image format
RDSELFDIAG = const(0x0F) # Read display self-diagnostic
INVOFF = const(0x20) # Display inversion off
INVON = const(0x21) # Display inversion on
GAMMASET = const(0x26) # Gamma set
DISPLAY_OFF = const(0x28) # Display off
DISPLAY_ON = const(0x29) # Display on
SET_COLUMN = const(0x2A) # Column address set
SET_PAGE = const(0x2B) # Page address set
WRITE_RAM = const(0x2C) # Memory write
READ_RAM = const(0x2E) # Memory read
PTLAR = const(0x30) # Partial area
VSCRDEF = const(0x33) # Vertical scrolling definition
MADCTL = const(0x36) # Memory access control
VSCRSADD = const(0x37) # Vertical scrolling start address
PIXFMT = const(0x3A) # COLMOD: Pixel format set
WRITE_DISPLAY_BRIGHTNESS = const(0x51) # Brightness hardware dependent!
READ_DISPLAY_BRIGHTNESS = const(0x52)
WRITE_CTRL_DISPLAY = const(0x53)
READ_CTRL_DISPLAY = const(0x54)
WRITE_CABC = const(0x55) # Write Content Adaptive Brightness Control
READ_CABC = const(0x56) # Read Content Adaptive Brightness Control
WRITE_CABC_MINIMUM = const(0x5E) # Write CABC Minimum Brightness
READ_CABC_MINIMUM = const(0x5F) # Read CABC Minimum Brightness
FRMCTR1 = const(0xB1) # Frame rate control (In normal mode/full colors)
FRMCTR2 = const(0xB2) # Frame rate control (In idle mode/8 colors)
FRMCTR3 = const(0xB3) # Frame rate control (In partial mode/full colors)
INVCTR = const(0xB4) # Display inversion control
DFUNCTR = const(0xB6) # Display function control
PWCTR1 = const(0xC0) # Power control 1
PWCTR2 = const(0xC1) # Power control 2
PWCTRA = const(0xCB) # Power control A
PWCTRB = const(0xCF) # Power control B
VMCTR1 = const(0xC5) # VCOM control 1
VMCTR2 = const(0xC7) # VCOM control 2
RDID1 = const(0xDA) # Read ID 1
RDID2 = const(0xDB) # Read ID 2
RDID3 = const(0xDC) # Read ID 3
RDID4 = const(0xDD) # Read ID 4
GMCTRP1 = const(0xE0) # Positive gamma correction
GMCTRN1 = const(0xE1) # Negative gamma correction
DTCA = const(0xE8) # Driver timing control A
DTCB = const(0xEA) # Driver timing control B
POSC = const(0xED) # Power on sequence control
ENABLE3G = const(0xF2) # Enable 3 gamma control
PUMPRC = const(0xF7) # Pump ratio control

MIRROR_ROTATE = { # MADCTL configurations for rotation and mirroring
(False, 0): 0x80, # 1000 0000
(False, 90): 0xE0, # 1110 0000
(False, 180): 0x40, # 0100 0000
(False, 270): 0x20, # 0010 0000
(True, 0): 0xC0, # 1100 0000
(True, 90): 0x60, # 0110 0000
(True, 180): 0x00, # 0000 0000
(True, 270): 0xA0 # 1010 0000
}

def __init__(self, spi, cs, dc, rst, width=240, height=320, rotation=0,
mirror=False, bgr=True, gamma=True):
"""Initialize OLED.

Args:
spi (Class Spi): SPI interface for OLED
cs (Class Pin): Chip select pin
dc (Class Pin): Data/Command pin
rst (Class Pin): Reset pin
width (Optional int): Screen width (default 240)
height (Optional int): Screen height (default 320)
rotation (Optional int): Rotation must be 0 default, 90. 180 or 270
mirror (Optional bool): Mirror display (default False)
bgr (Optional bool): Swaps red and blue colors (default True)
gamma (Optional bool): Custom gamma correction (default True)
"""
self.spi = spi
self.cs = cs
self.dc = dc
self.rst = rst
self.width = width
self.height = height
if (mirror, rotation) not in self.MIRROR_ROTATE:
raise ValueError('Rotation must be 0, 90, 180 or 270.')
else:
self.rotation = self.MIRROR_ROTATE[mirror, rotation]
if bgr: # Set BGR bit
self.rotation |= 0b00001000

# Initialize GPIO pins and set implementation specific methods
if implementation.name == 'circuitpython':
self.cs.switch_to_output(value=True)
self.dc.switch_to_output(value=False)
self.rst.switch_to_output(value=True)
self.reset = self.reset_cpy
self.write_cmd = self.write_cmd_cpy
self.write_data = self.write_data_cpy
else:
self.cs.init(self.cs.OUT, value=1)
self.dc.init(self.dc.OUT, value=0)
self.rst.init(self.rst.OUT, value=1)
self.reset = self.reset_mpy
self.write_cmd = self.write_cmd_mpy
self.write_data = self.write_data_mpy
self.reset()
# Send initialization commands
self.write_cmd(self.SWRESET) # Software reset
sleep(.1)
self.write_cmd(self.PWCTRB, 0x00, 0xC1, 0x30) # Pwr ctrl B
self.write_cmd(self.POSC, 0x64, 0x03, 0x12, 0x81) # Pwr on seq. ctrl
self.write_cmd(self.DTCA, 0x85, 0x00, 0x78) # Driver timing ctrl A
self.write_cmd(self.PWCTRA, 0x39, 0x2C, 0x00, 0x34, 0x02) # Pwr ctrl A
self.write_cmd(self.PUMPRC, 0x20) # Pump ratio control
self.write_cmd(self.DTCB, 0x00, 0x00) # Driver timing ctrl B
self.write_cmd(self.PWCTR1, 0x23) # Pwr ctrl 1
self.write_cmd(self.PWCTR2, 0x10) # Pwr ctrl 2
self.write_cmd(self.VMCTR1, 0x3E, 0x28) # VCOM ctrl 1
self.write_cmd(self.VMCTR2, 0x86) # VCOM ctrl 2
self.write_cmd(self.MADCTL, self.rotation) # Memory access ctrl
self.write_cmd(self.VSCRSADD, 0x00) # Vertical scrolling start address
self.write_cmd(self.PIXFMT, 0x55) # COLMOD: Pixel format
self.write_cmd(self.FRMCTR1, 0x00, 0x18) # Frame rate ctrl
self.write_cmd(self.DFUNCTR, 0x08, 0x82, 0x27)
self.write_cmd(self.ENABLE3G, 0x00) # Enable 3 gamma ctrl
self.write_cmd(self.GAMMASET, 0x01) # Gamma curve selected
if gamma: # Use custom gamma correction values
self.write_cmd(self.GMCTRP1, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08,
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09,
0x00)
self.write_cmd(self.GMCTRN1, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07,
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36,
0x0F)
self.write_cmd(self.SLPOUT) # Exit sleep
sleep(.1)
self.write_cmd(self.DISPLAY_ON) # Display on
sleep(.1)
self.clear()

def block(self, x0, y0, x1, y1, data):
"""Write a block of data to display.

Args:
x0 (int): Starting X position.
y0 (int): Starting Y position.
x1 (int): Ending X position.
y1 (int): Ending Y position.
data (bytes): Data buffer to write.
"""
self.write_cmd(self.SET_COLUMN,
x0 >> 8, x0 & 0xff, x1 >> 8, x1 & 0xff)
self.write_cmd(self.SET_PAGE,
y0 >> 8, y0 & 0xff, y1 >> 8, y1 & 0xff)
self.write_cmd(self.WRITE_RAM)
self.write_data(data)

def cleanup(self):
"""Clean up resources."""
self.clear()
self.display_off()
self.spi.deinit()
print('display off')

def clear(self, color=0, hlines=8):
"""Clear display.

Args:
color (Optional int): RGB565 color value (Default: 0 = Black).
hlines (Optional int): # of horizontal lines per chunk (Default: 8)
Note:
hlines was introduced to deal with memory allocation on some
boards. Smaller values allocate less memory but take longer
to execute. hlines must be a factor of the display height.
For example, for a 240 pixel height, valid values for hline
would be 1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 16, 20, 24, 30, 40, etc.
Higher values may result in memory allocation errors.
"""
w = self.width
h = self.height
assert hlines > 0 and h % hlines == 0, (
"hlines must be a non-zero factor of height.")
# Clear display
if color:
line = color.to_bytes(2, 'big') * (w * hlines)
else:
line = bytearray(w * 2 * hlines)
for y in range(0, h, hlines):
self.block(0, y, w - 1, y + hlines - 1, line)

def display_off(self):
"""Turn display off."""
self.write_cmd(self.DISPLAY_OFF)

def display_on(self):
"""Turn display on."""
self.write_cmd(self.DISPLAY_ON)

def draw_circle(self, x0, y0, r, color):
"""Draw a circle.

Args:
x0 (int): X coordinate of center point.
y0 (int): Y coordinate of center point.
r (int): Radius.
color (int): RGB565 color value.
"""
f = 1 - r
dx = 1
dy = -r - r
x = 0
y = r
self.draw_pixel(x0, y0 + r, color)
self.draw_pixel(x0, y0 - r, color)
self.draw_pixel(x0 + r, y0, color)
self.draw_pixel(x0 - r, y0, color)
while x < y:
if f >= 0:
y -= 1
dy += 2
f += dy
x += 1
dx += 2
f += dx
self.draw_pixel(x0 + x, y0 + y, color)
self.draw_pixel(x0 - x, y0 + y, color)
self.draw_pixel(x0 + x, y0 - y, color)
self.draw_pixel(x0 - x, y0 - y, color)
self.draw_pixel(x0 + y, y0 + x, color)
self.draw_pixel(x0 - y, y0 + x, color)
self.draw_pixel(x0 + y, y0 - x, color)
self.draw_pixel(x0 - y, y0 - x, color)

def draw_ellipse(self, x0, y0, a, b, color):
"""Draw an ellipse.

Args:
x0, y0 (int): Coordinates of center point.
a (int): Semi axis horizontal.
b (int): Semi axis vertical.
color (int): RGB565 color value.
Note:
The center point is the center of the x0,y0 pixel.
Since pixels are not divisible, the axes are integer rounded
up to complete on a full pixel. Therefore the major and
minor axes are increased by 1.
"""
a2 = a * a
b2 = b * b
twoa2 = a2 + a2
twob2 = b2 + b2
x = 0
y = b
px = 0
py = twoa2 * y
# Plot initial points
self.draw_pixel(x0 + x, y0 + y, color)
self.draw_pixel(x0 - x, y0 + y, color)
self.draw_pixel(x0 + x, y0 - y, color)
self.draw_pixel(x0 - x, y0 - y, color)
# Region 1
p = round(b2 - (a2 * b) + (0.25 * a2))
while px < py:
x += 1
px += twob2
if p < 0:
p += b2 + px
else:
y -= 1
py -= twoa2
p += b2 + px - py
self.draw_pixel(x0 + x, y0 + y, color)
self.draw_pixel(x0 - x, y0 + y, color)
self.draw_pixel(x0 + x, y0 - y, color)
self.draw_pixel(x0 - x, y0 - y, color)
# Region 2
p = round(b2 * (x + 0.5) * (x + 0.5) +
a2 * (y - 1) * (y - 1) - a2 * b2)
while y > 0:
y -= 1
py -= twoa2
if p > 0:
p += a2 - py
else:
x += 1
px += twob2
p += a2 - py + px
self.draw_pixel(x0 + x, y0 + y, color)
self.draw_pixel(x0 - x, y0 + y, color)
self.draw_pixel(x0 + x, y0 - y, color)
self.draw_pixel(x0 - x, y0 - y, color)

def draw_hline(self, x, y, w, color):
"""Draw a horizontal line.

Args:
x (int): Starting X position.
y (int): Starting Y position.
w (int): Width of line.
color (int): RGB565 color value.
"""
if self.is_off_grid(x, y, x + w - 1, y):
return
line = color.to_bytes(2, 'big') * w
self.block(x, y, x + w - 1, y, line)

def draw_image(self, path, x=0, y=0, w=320, h=240):
"""Draw image from flash.

Args:
path (string): Image file path.
x (int): X coordinate of image left. Default is 0.
y (int): Y coordinate of image top. Default is 0.
w (int): Width of image. Default is 320.
h (int): Height of image. Default is 240.
"""
x2 = x + w - 1
y2 = y + h - 1
if self.is_off_grid(x, y, x2, y2):
return
with open(path, "rb") as f:
chunk_height = 1024 // w
chunk_count, remainder = divmod(h, chunk_height)
chunk_size = chunk_height * w * 2
chunk_y = y
if chunk_count:
for c in range(0, chunk_count):
buf = f.read(chunk_size)
self.block(x, chunk_y,
x2, chunk_y + chunk_height - 1,
buf)
chunk_y += chunk_height
if remainder:
buf = f.read(remainder * w * 2)
self.block(x, chunk_y,
x2, chunk_y + remainder - 1,
buf)

def draw_letter(self, x, y, letter, font, color, background=0,
landscape=False, rotate_180=False):
"""Draw a letter.

Args:
x (int): Starting X position.
y (int): Starting Y position.
letter (string): Letter to draw.
font (XglcdFont object): Font.
color (int): RGB565 color value.
background (int): RGB565 background color (default: black)
landscape (bool): Orientation (default: False = portrait)
rotate_180 (bool): Rotate text by 180 degrees
"""
buf, w, h = font.get_letter(letter, color, background, landscape)
if rotate_180:
# Manually rotate the buffer by 180 degrees
# ensure bytes pairs for each pixel retain color565
new_buf = bytearray(len(buf))
num_pixels = len(buf) // 2
for i in range(num_pixels):
# The index for the new buffer's byte pair
new_idx = (num_pixels - 1 - i) * 2
# The index for the original buffer's byte pair
old_idx = i * 2
# Swap the pixels
new_buf[new_idx], new_buf[new_idx + 1] = buf[old_idx], buf[old_idx + 1]
buf = new_buf

# Check for errors (Font could be missing specified letter)
if w == 0:
return w, h

if landscape:
y -= w
if self.is_off_grid(x, y, x + h - 1, y + w - 1):
return 0, 0
self.block(x, y,
x + h - 1, y + w - 1,
buf)
else:
if self.is_off_grid(x, y, x + w - 1, y + h - 1):
return 0, 0
self.block(x, y,
x + w - 1, y + h - 1,
buf)
return w, h

def draw_line(self, x1, y1, x2, y2, color):
"""Draw a line using Bresenham's algorithm.

Args:
x1, y1 (int): Starting coordinates of the line
x2, y2 (int): Ending coordinates of the line
color (int): RGB565 color value.
"""
# Check for horizontal line
if y1 == y2:
if x1 > x2:
x1, x2 = x2, x1
self.draw_hline(x1, y1, x2 - x1 + 1, color)
return
# Check for vertical line
if x1 == x2:
if y1 > y2:
y1, y2 = y2, y1
self.draw_vline(x1, y1, y2 - y1 + 1, color)
return
# Confirm coordinates in boundary
if self.is_off_grid(min(x1, x2), min(y1, y2),
max(x1, x2), max(y1, y2)):
return
# Changes in x, y
dx = x2 - x1
dy = y2 - y1
# Determine how steep the line is
is_steep = abs(dy) > abs(dx)
# Rotate line
if is_steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
# Swap start and end points if necessary
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
# Recalculate differentials
dx = x2 - x1
dy = y2 - y1
# Calculate error
error = dx >> 1
ystep = 1 if y1 < y2 else -1
y = y1
for x in range(x1, x2 + 1):
# Had to reverse HW ????
if not is_steep:
self.draw_pixel(x, y, color)
else:
self.draw_pixel(y, x, color)
error -= abs(dy)
if error < 0:
y += ystep
error += dx

def draw_lines(self, coords, color):
"""Draw multiple lines.

Args:
coords ([[int, int],...]): Line coordinate X, Y pairs
color (int): RGB565 color value.
"""
# Starting point
x1, y1 = coords[0]
# Iterate through coordinates
for i in range(1, len(coords)):
x2, y2 = coords[i]
self.draw_line(x1, y1, x2, y2, color)
x1, y1 = x2, y2

def draw_pixel(self, x, y, color):
"""Draw a single pixel.

Args:
x (int): X position.
y (int): Y position.
color (int): RGB565 color value.
"""
if self.is_off_grid(x, y, x, y):
return
self.block(x, y, x, y, color.to_bytes(2, 'big'))

def draw_polygon(self, sides, x0, y0, r, color, rotate=0):
"""Draw an n-sided regular polygon.

Args:
sides (int): Number of polygon sides.
x0, y0 (int): Coordinates of center point.
r (int): Radius.
color (int): RGB565 color value.
rotate (Optional float): Rotation in degrees relative to origin.
Note:
The center point is the center of the x0,y0 pixel.
Since pixels are not divisible, the radius is integer rounded
up to complete on a full pixel. Therefore diameter = 2 x r + 1.
"""
coords = []
theta = radians(rotate)
n = sides + 1
for s in range(n):
t = 2.0 * pi * s / sides + theta
coords.append([int(r * cos(t) + x0), int(r * sin(t) + y0)])

# Cast to python float first to fix rounding errors
self.draw_lines(coords, color=color)

def draw_rectangle(self, x, y, w, h, color):
"""Draw a rectangle.

Args:
x (int): Starting X position.
y (int): Starting Y position.
w (int): Width of rectangle.
h (int): Height of rectangle.
color (int): RGB565 color value.
"""
x2 = x + w - 1
y2 = y + h - 1
self.draw_hline(x, y, w, color)
self.draw_hline(x, y2, w, color)
self.draw_vline(x, y, h, color)
self.draw_vline(x2, y, h, color)

def draw_sprite(self, buf, x, y, w, h):
"""Draw a sprite (optimized for horizontal drawing).

Args:
buf (bytearray): Buffer to draw.
x (int): Starting X position.
y (int): Starting Y position.
w (int): Width of drawing.
h (int): Height of drawing.
"""
x2 = x + w - 1
y2 = y + h - 1
if self.is_off_grid(x, y, x2, y2):
return
self.block(x, y, x2, y2, buf)

def draw_text(self, x, y, text, font, color, background=0,
landscape=False, rotate_180=False, spacing=1):
"""Draw text.

Args:
x (int): Starting X position
y (int): Starting Y position
text (string): Text to draw
font (XglcdFont object): Font
color (int): RGB565 color value
background (int): RGB565 background color (default: black)
landscape (bool): Orientation (default: False = portrait)
rotate_180 (bool): Rotate text by 180 degrees
spacing (int): Pixels between letters (default: 1)
"""
iterable_text = reversed(text) if rotate_180 else text
for letter in iterable_text:
# Get letter array and letter dimensions
w, h = self.draw_letter(x, y, letter, font, color, background,
landscape, rotate_180)
# Stop on error
if w == 0 or h == 0:
print('Invalid width {0} or height {1}'.format(w, h))
return

if landscape:
# Fill in spacing
if spacing:
self.fill_hrect(x, y - w - spacing, h, spacing, background)
# Position y for next letter
y -= (w + spacing)
else:
# Fill in spacing
if spacing:
self.fill_hrect(x + w, y, spacing, h, background)
# Position x for next letter
x += (w + spacing)

# # Fill in spacing
# if spacing:
# self.fill_vrect(x + w, y, spacing, h, background)
# # Position x for next letter
# x += w + spacing

def draw_text8x8(self, x, y, text, color, background=0,
rotate=0):
"""Draw text using built-in MicroPython 8x8 bit font.

Args:
x (int): Starting X position.
y (int): Starting Y position.
text (string): Text to draw.
color (int): RGB565 color value.
background (int): RGB565 background color (default: black).
rotate(int): 0, 90, 180, 270
"""
w = len(text) * 8
h = 8
# Confirm coordinates in boundary
if self.is_off_grid(x, y, x + 7, y + 7):
return
buf = bytearray(w * 16)
fbuf = FrameBuffer(buf, w, h, RGB565)
if background != 0:
# Swap background color bytes to correct for framebuf endianness
b_color = ((background & 0xFF) << 8) | ((background & 0xFF00) >> 8)
fbuf.fill(b_color)
# Swap text color bytes to correct for framebuf endianness
t_color = ((color & 0xFF) << 8) | ((color & 0xFF00) >> 8)
fbuf.text(text, 0, 0, t_color)
if rotate == 0:
self.block(x, y, x + w - 1, y + (h - 1), buf)
elif rotate == 90:
buf2 = bytearray(w * 16)
fbuf2 = FrameBuffer(buf2, h, w, RGB565)
for y1 in range(h):
for x1 in range(w):
fbuf2.pixel(y1, x1,
fbuf.pixel(x1, (h - 1) - y1))
self.block(x, y, x + (h - 1), y + w - 1, buf2)
elif rotate == 180:
buf2 = bytearray(w * 16)
fbuf2 = FrameBuffer(buf2, w, h, RGB565)
for y1 in range(h):
for x1 in range(w):
fbuf2.pixel(x1, y1,
fbuf.pixel((w - 1) - x1, (h - 1) - y1))
self.block(x, y, x + w - 1, y + (h - 1), buf2)
elif rotate == 270:
buf2 = bytearray(w * 16)
fbuf2 = FrameBuffer(buf2, h, w, RGB565)
for y1 in range(h):
for x1 in range(w):
fbuf2.pixel(y1, x1,
fbuf.pixel((w - 1) - x1, y1))
self.block(x, y, x + (h - 1), y + w - 1, buf2)

def draw_vline(self, x, y, h, color):
"""Draw a vertical line.

Args:
x (int): Starting X position.
y (int): Starting Y position.
h (int): Height of line.
color (int): RGB565 color value.
"""
# Confirm coordinates in boundary
if self.is_off_grid(x, y, x, y + h - 1):
return
line = color.to_bytes(2, 'big') * h
self.block(x, y, x, y + h - 1, line)

def fill_circle(self, x0, y0, r, color):
"""Draw a filled circle.

Args:
x0 (int): X coordinate of center point.
y0 (int): Y coordinate of center point.
r (int): Radius.
color (int): RGB565 color value.
"""
f = 1 - r
dx = 1
dy = -r - r
x = 0
y = r
self.draw_vline(x0, y0 - r, 2 * r + 1, color)
while x < y:
if f >= 0:
y -= 1
dy += 2
f += dy
x += 1
dx += 2
f += dx
self.draw_vline(x0 + x, y0 - y, 2 * y + 1, color)
self.draw_vline(x0 - x, y0 - y, 2 * y + 1, color)
self.draw_vline(x0 - y, y0 - x, 2 * x + 1, color)
self.draw_vline(x0 + y, y0 - x, 2 * x + 1, color)

def fill_ellipse(self, x0, y0, a, b, color):
"""Draw a filled ellipse.

Args:
x0, y0 (int): Coordinates of center point.
a (int): Semi axis horizontal.
b (int): Semi axis vertical.
color (int): RGB565 color value.
Note:
The center point is the center of the x0,y0 pixel.
Since pixels are not divisible, the axes are integer rounded
up to complete on a full pixel. Therefore the major and
minor axes are increased by 1.
"""
a2 = a * a
b2 = b * b
twoa2 = a2 + a2
twob2 = b2 + b2
x = 0
y = b
px = 0
py = twoa2 * y
# Plot initial points
self.draw_line(x0, y0 - y, x0, y0 + y, color)
# Region 1
p = round(b2 - (a2 * b) + (0.25 * a2))
while px < py:
x += 1
px += twob2
if p < 0:
p += b2 + px
else:
y -= 1
py -= twoa2
p += b2 + px - py
self.draw_line(x0 + x, y0 - y, x0 + x, y0 + y, color)
self.draw_line(x0 - x, y0 - y, x0 - x, y0 + y, color)
# Region 2
p = round(b2 * (x + 0.5) * (x + 0.5) +
a2 * (y - 1) * (y - 1) - a2 * b2)
while y > 0:
y -= 1
py -= twoa2
if p > 0:
p += a2 - py
else:
x += 1
px += twob2
p += a2 - py + px
self.draw_line(x0 + x, y0 - y, x0 + x, y0 + y, color)
self.draw_line(x0 - x, y0 - y, x0 - x, y0 + y, color)

def fill_hrect(self, x, y, w, h, color):
"""Draw a filled rectangle (optimized for horizontal drawing).

Args:
x (int): Starting X position.
y (int): Starting Y position.
w (int): Width of rectangle.
h (int): Height of rectangle.
color (int): RGB565 color value.
"""
if self.is_off_grid(x, y, x + w - 1, y + h - 1):
return
chunk_height = 1024 // w
chunk_count, remainder = divmod(h, chunk_height)
chunk_size = chunk_height * w
chunk_y = y
if chunk_count:
buf = color.to_bytes(2, 'big') * chunk_size
for c in range(0, chunk_count):
self.block(x, chunk_y,
x + w - 1, chunk_y + chunk_height - 1,
buf)
chunk_y += chunk_height

if remainder:
buf = color.to_bytes(2, 'big') * remainder * w
self.block(x, chunk_y,
x + w - 1, chunk_y + remainder - 1,
buf)

def fill_rectangle(self, x, y, w, h, color):
"""Draw a filled rectangle.

Args:
x (int): Starting X position.
y (int): Starting Y position.
w (int): Width of rectangle.
h (int): Height of rectangle.
color (int): RGB565 color value.
"""
if self.is_off_grid(x, y, x + w - 1, y + h - 1):
return
if w > h:
self.fill_hrect(x, y, w, h, color)
else:
self.fill_vrect(x, y, w, h, color)

def fill_polygon(self, sides, x0, y0, r, color, rotate=0):
"""Draw a filled n-sided regular polygon.

Args:
sides (int): Number of polygon sides.
x0, y0 (int): Coordinates of center point.
r (int): Radius.
color (int): RGB565 color value.
rotate (Optional float): Rotation in degrees relative to origin.
Note:
The center point is the center of the x0,y0 pixel.
Since pixels are not divisible, the radius is integer rounded
up to complete on a full pixel. Therefore diameter = 2 x r + 1.
"""
# Determine side coordinates
coords = []
theta = radians(rotate)
n = sides + 1
for s in range(n):
t = 2.0 * pi * s / sides + theta
coords.append([int(r * cos(t) + x0), int(r * sin(t) + y0)])
# Starting point
x1, y1 = coords[0]
# Minimum Maximum X dict
xdict = {y1: [x1, x1]}
# Iterate through coordinates
for row in coords[1:]:
x2, y2 = row
xprev, yprev = x2, y2
# Calculate perimeter
# Check for horizontal side
if y1 == y2:
if x1 > x2:
x1, x2 = x2, x1
if y1 in xdict:
xdict[y1] = [min(x1, xdict[y1][0]), max(x2, xdict[y1][1])]
else:
xdict[y1] = [x1, x2]
x1, y1 = xprev, yprev
continue
# Non horizontal side
# Changes in x, y
dx = x2 - x1
dy = y2 - y1
# Determine how steep the line is
is_steep = abs(dy) > abs(dx)
# Rotate line
if is_steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
# Swap start and end points if necessary
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
# Recalculate differentials
dx = x2 - x1
dy = y2 - y1
# Calculate error
error = dx >> 1
ystep = 1 if y1 < y2 else -1
y = y1
# Calcualte minimum and maximum x values
for x in range(x1, x2 + 1):
if is_steep:
if x in xdict:
xdict[x] = [min(y, xdict[x][0]), max(y, xdict[x][1])]
else:
xdict[x] = [y, y]
else:
if y in xdict:
xdict[y] = [min(x, xdict[y][0]), max(x, xdict[y][1])]
else:
xdict[y] = [x, x]
error -= abs(dy)
if error < 0:
y += ystep
error += dx
x1, y1 = xprev, yprev
# Fill polygon
for y, x in xdict.items():
self.draw_hline(x[0], y, x[1] - x[0] + 2, color)

def fill_vrect(self, x, y, w, h, color):
"""Draw a filled rectangle (optimized for vertical drawing).

Args:
x (int): Starting X position.
y (int): Starting Y position.
w (int): Width of rectangle.
h (int): Height of rectangle.
color (int): RGB565 color value.
"""
if self.is_off_grid(x, y, x + w - 1, y + h - 1):
return
chunk_width = 1024 // h
chunk_count, remainder = divmod(w, chunk_width)
chunk_size = chunk_width * h
chunk_x = x
if chunk_count:
buf = color.to_bytes(2, 'big') * chunk_size
for c in range(0, chunk_count):
self.block(chunk_x, y,
chunk_x + chunk_width - 1, y + h - 1,
buf)
chunk_x += chunk_width

if remainder:
buf = color.to_bytes(2, 'big') * remainder * h
self.block(chunk_x, y,
chunk_x + remainder - 1, y + h - 1,
buf)

def invert(self, enable=True):
"""Enables or disables inversion of display colors.

Args:
enable (Optional bool): True=enable, False=disable
"""
if enable:
self.write_cmd(self.INVON)
else:
self.write_cmd(self.INVOFF)

def is_off_grid(self, xmin, ymin, xmax, ymax):
"""Check if coordinates extend past display boundaries.

Args:
xmin (int): Minimum horizontal pixel.
ymin (int): Minimum vertical pixel.
xmax (int): Maximum horizontal pixel.
ymax (int): Maximum vertical pixel.
Returns:
boolean: False = Coordinates OK, True = Error.
"""
if xmin < 0:
print('x-coordinate: {0} below minimum of 0.'.format(xmin))
return True
if ymin < 0:
print('y-coordinate: {0} below minimum of 0.'.format(ymin))
return True
if xmax >= self.width:
print('x-coordinate: {0} above maximum of {1}.'.format(
xmax, self.width - 1))
return True
if ymax >= self.height:
print('y-coordinate: {0} above maximum of {1}.'.format(
ymax, self.height - 1))
return True
return False

def load_sprite(self, path, w, h):
"""Load sprite image.

Args:
path (string): Image file path.
w (int): Width of image.
h (int): Height of image.
Notes:
w x h cannot exceed 2048 on boards w/o PSRAM
"""
buf_size = w * h * 2
with open(path, "rb") as f:
return f.read(buf_size)

def reset_cpy(self):
"""Perform reset: Low=initialization, High=normal operation.

Notes: CircuitPython implemntation
"""
self.rst.value = False
sleep(.05)
self.rst.value = True
sleep(.05)

def reset_mpy(self):
"""Perform reset: Low=initialization, High=normal operation.

Notes: MicroPython implemntation
"""
self.rst(0)
sleep(.05)
self.rst(1)
sleep(.05)

def scroll(self, y):
"""Scroll display vertically.

Args:
y (int): Number of pixels to scroll display.
"""
self.write_cmd(self.VSCRSADD, y >> 8, y & 0xFF)

def set_scroll(self, top, bottom):
"""Set the height of the top and bottom scroll margins.

Args:
top (int): Height of top scroll margin
bottom (int): Height of bottom scroll margin
"""
if top + bottom <= self.height:
middle = self.height - (top + bottom)
self.write_cmd(self.VSCRDEF,
top >> 8,
top & 0xFF,
middle >> 8,
middle & 0xFF,
bottom >> 8,
bottom & 0xFF)

def sleep(self, enable=True):
"""Enters or exits sleep mode.

Args:
enable (bool): True (default)=Enter sleep mode, False=Exit sleep
"""
if enable:
self.write_cmd(self.SLPIN)
else:
self.write_cmd(self.SLPOUT)

def write_cmd_mpy(self, command, *args):
"""Write command to OLED (MicroPython).

Args:
command (byte): ILI9341 command code.
*args (optional bytes): Data to transmit.
"""
self.dc(0)
self.cs(0)
self.spi.write(bytearray([command]))
self.cs(1)
# Handle any passed data
if len(args) > 0:
self.write_data(bytearray(args))

def write_cmd_cpy(self, command, *args):
"""Write command to OLED (CircuitPython).

Args:
command (byte): ILI9341 command code.
*args (optional bytes): Data to transmit.
"""
self.dc.value = False
self.cs.value = False
# Confirm SPI locked before writing
while not self.spi.try_lock():
pass
self.spi.write(bytearray([command]))
self.spi.unlock()
self.cs.value = True
# Handle any passed data
if len(args) > 0:
self.write_data(bytearray(args))

def write_data_mpy(self, data):
"""Write data to OLED (MicroPython).

Args:
data (bytes): Data to transmit.
"""
self.dc(1)
self.cs(0)
self.spi.write(data)
self.cs(1)

def write_data_cpy(self, data):
"""Write data to OLED (CircuitPython).

Args:
data (bytes): Data to transmit.
"""
self.dc.value = True
self.cs.value = False
# Confirm SPI locked before writing
while not self.spi.try_lock():
pass
self.spi.write(data)
self.spi.unlock()
self.cs.value = True

3.1.1 绘制文本

要绘制文本的话,我们还需要加装一个用于渲染字符的字体库(xglcd_font.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"""XGLCD Font Utility."""
from math import ceil, floor

class XglcdFont(object):
"""Font data in X-GLCD format.

Attributes:
letters: A bytearray of letters (columns consist of bytes)
width: Maximum pixel width of font
height: Pixel height of font
start_letter: ASCII number of first letter
height_bytes: How many bytes comprises letter height

Note:
Font files can be generated with the free version of MikroElektronika
GLCD Font Creator: www.mikroe.com/glcd-font-creator
The font file must be in X-GLCD 'C' format.
To save text files from this font creator program in Win7 or higher
you must use XP compatibility mode or you can just use the clipboard.
"""

# Dict to tranlate bitwise values to byte position
BIT_POS = {1: 0, 2: 2, 4: 4, 8: 6, 16: 8, 32: 10, 64: 12, 128: 14, 256: 16}

def __init__(self, path, width, height, start_letter=32, letter_count=96):
"""Constructor for X-GLCD Font object.

Args:
path (string): Full path of font file
width (int): Maximum width in pixels of each letter
height (int): Height in pixels of each letter
start_letter (int): First ACII letter. Default is 32.
letter_count (int): Total number of letters. Default is 96.
"""
self.width = width
self.height = max(height, 8)
self.start_letter = start_letter
self.letter_count = letter_count
self.bytes_per_letter = (floor(
(self.height - 1) / 8) + 1) * self.width + 1
self.__load_xglcd_font(path)

def __load_xglcd_font(self, path):
"""Load X-GLCD font data from text file.

Args:
path (string): Full path of font file.
"""
bytes_per_letter = self.bytes_per_letter
# Buffer to hold letter byte values
self.letters = bytearray(bytes_per_letter * self.letter_count)
mv = memoryview(self.letters)
offset = 0
with open(path, 'r') as f:
for line in f:
# Skip lines that do not start with hex values
line = line.strip()
if len(line) == 0 or line[0:2] != '0x':
continue
# Remove comments
comment = line.find('//')
if comment != -1:
line = line[0:comment].strip()
# Remove trailing commas
if line.endswith(','):
line = line[0:len(line) - 1]
# Convert hex strings to bytearray and insert in to letters
mv[offset: offset + bytes_per_letter] = bytearray(
int(b, 16) for b in line.split(','))
offset += bytes_per_letter

def lit_bits(self, n):
"""Return positions of 1 bits only."""
while n:
b = n & (~n+1)
yield self.BIT_POS[b]
n ^= b

def get_letter(self, letter, color, background=0, landscape=False):
"""Convert letter byte data to pixels.

Args:
letter (string): Letter to return (must exist within font).
color (int): RGB565 color value.
background (int): RGB565 background color (default: black).
landscape (bool): Orientation (default: False = portrait)
Returns:
(bytearray): Pixel data.
(int, int): Letter width and height.
"""
# Get index of letter
letter_ord = ord(letter) - self.start_letter
# Confirm font contains letter
if letter_ord >= self.letter_count:
print('Font does not contain character: ' + letter)
return b'', 0, 0
bytes_per_letter = self.bytes_per_letter
offset = letter_ord * bytes_per_letter
mv = memoryview(self.letters[offset:offset + bytes_per_letter])

# Get width of letter (specified by first byte)
letter_width = mv[0]
letter_height = self.height
# Get size in bytes of specified letter
letter_size = letter_height * letter_width
# Create buffer (double size to accommodate 16 bit colors)
if background:
buf = bytearray(background.to_bytes(2, 'big') * letter_size)
else:
buf = bytearray(letter_size * 2)

msb, lsb = color.to_bytes(2, 'big')

if landscape:
# Populate buffer in order for landscape
pos = (letter_size * 2) - (letter_height * 2)
lh = letter_height
# Loop through letter byte data and convert to pixel data
for b in mv[1:]:
# Process only colored bits
for bit in self.lit_bits(b):
buf[bit + pos] = msb
buf[bit + pos + 1] = lsb
if lh > 8:
# Increment position by double byte
pos += 16
lh -= 8
else:
# Descrease position to start of previous column
pos -= (letter_height * 4) - (lh * 2)
lh = letter_height
else:
# Populate buffer in order for portrait
col = 0 # Set column to first column
bytes_per_letter = ceil(letter_height / 8)
letter_byte = 0
# Loop through letter byte data and convert to pixel data
for b in mv[1:]:
# Process only colored bits
segment_size = letter_byte * letter_width * 16
for bit in self.lit_bits(b):
pos = (bit * letter_width) + (col * 2) + segment_size
buf[pos] = msb
pos = (bit * letter_width) + (col * 2) + 1 + segment_size
buf[pos] = lsb
letter_byte += 1
if letter_byte + 1 > bytes_per_letter:
col += 1
letter_byte = 0

return buf, letter_width, letter_height

def measure_text(self, text, spacing=1):
"""Measure length of text string in pixels.

Args:
text (string): Text string to measure
spacing (optional int): Pixel spacing between letters. Default: 1.
Returns:
int: length of text
"""
length = 0
for letter in text:
# Get index of letter
letter_ord = ord(letter) - self.start_letter
offset = letter_ord * self.bytes_per_letter
# Add length of letter and spacing
length += self.letters[offset] + spacing
return length

接下来我们来尝试在屏幕上绘制文本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from machine import Pin, SPI, ADC, idle
import os
from time import sleep

from ili9341 import Display, color565
from xglcd_font import XglcdFont

# 初始化 SPI 总线和显示器
display_spi = SPI(1, baudrate=60000000, sck=Pin(14), mosi=Pin(13))
display = Display(display_spi, dc=Pin(2), cs=Pin(15), rst=Pin(15),
width=320, height=240, rotation=90)

# 绘制文本
def draw_text():
# 颜色
white_color = color565(255, 255, 255)
black_color = color565(0, 0, 0)

# 设置背光
backlight = Pin(21, Pin.OUT)
backlight.on()

# 清除屏幕
display.clear(black_color)

# 绘制文本(用法: draw_text8x8(x, y, 'text', fg_color, bg_color, rotate))
display.draw_text8x8(0, 0, 'ESP32 says hello!', white_color, black_color, 0) # 从(0,0)开始,白色字体,黑色背景,旋转角度0

# 捕获异常并打印错误信息
try:
draw_text()
except Exception as e:
print('Error occured: ', e)
except KeyboardInterrupt:
print('Program Interrupted by the user')
display.cleanup()

我们可以利用display.draw_text8x8(x, y, ‘text’, fg_color, bg_color, rotate)绘制文本,x代表x坐标、y代表有坐标、’text’表示要显示的文本、fg_color指的是文本颜色、bg_color代表的是绘制文本部分背景的颜色、rotate代表角度。

通过上述代码可以在屏幕显示文字,但这文字似乎看起来太小了,且不太符合我们平时的审美,因此,我们可以加入一个字体库来自定义字体(Unispace12x24.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//Font Generated by MikroElektronika GLCD Font Creator 1.2.0.0
//MikroElektronika 2011
//http://www.mikroe.com

//GLCD FontName : Unispace12x24
//GLCD FontSize : 12 x 24 (Fixed Width)

const unsigned short Unispace12x24[] = {
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x07, 0xF8, 0x7F, 0x07, 0xF8, 0x7F, 0x07, 0xF8, 0x7F, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char !
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0xFC, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char "
0x0B, 0x00, 0x00, 0x00, 0x80, 0x61, 0x00, 0x80, 0x61, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x80, 0x61, 0x00, 0x80, 0x61, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x80, 0x61, 0x00, 0x80, 0x61, 0x00, 0x00, 0x00, 0x00, // Code for char #
0x0C, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x01, 0xF0, 0x0F, 0x01, 0x70, 0x0E, 0x01, 0x30, 0x0C, 0x01, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x30, 0x0C, 0x01, 0x30, 0x0C, 0x01, 0x30, 0xFC, 0x01, 0x30, 0xFC, 0x01, 0x00, 0x70, 0x00, // Code for char $
0x0C, 0x00, 0x00, 0x00, 0xF8, 0x03, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x06, 0xF8, 0xC3, 0x07, 0xE0, 0xF8, 0x00, 0x80, 0x1F, 0x00, 0xF0, 0xF1, 0x07, 0x38, 0x30, 0x04, 0x00, 0x10, 0x04, 0x00, 0xF0, 0x07, 0x00, 0xE0, 0x03, // Code for char %
0x0B, 0x00, 0x00, 0x00, 0xE0, 0xF9, 0x01, 0xF8, 0xFF, 0x03, 0xF8, 0xFF, 0x07, 0x18, 0x06, 0x06, 0x18, 0x06, 0x06, 0x18, 0x06, 0x06, 0x18, 0xFE, 0x07, 0x18, 0xFE, 0x07, 0x00, 0x06, 0x06, 0x00, 0x06, 0x06, 0x00, 0x00, 0x00, // Code for char &
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char '
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x3F, 0xFE, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char (
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0x07, 0x00, 0xE0, 0xFE, 0xFF, 0x7F, 0xFC, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char )
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00, 0xB8, 0x01, 0x00, 0xF0, 0x07, 0x00, 0xE0, 0x07, 0x00, 0xF8, 0x01, 0x00, 0xD8, 0x03, 0x00, 0x60, 0x07, 0x00, 0x60, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char *
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0xC0, 0x7F, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, // Code for char +
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ,
0x0B, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, // Code for char -
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x07, 0x00, 0x00, 0x07, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char .
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x07, 0x00, 0xC0, 0x03, 0x00, 0xF0, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x0F, 0x00, 0xC0, 0x03, 0x00, 0xE0, 0x01, 0x00, 0x78, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char /
0x0B, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x01, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0xE0, 0x07, 0x18, 0xFC, 0x06, 0x98, 0x1F, 0x06, 0xF8, 0x03, 0x06, 0xF8, 0x00, 0x07, 0xF8, 0xFF, 0x07, 0xF0, 0xFF, 0x03, 0x00, 0x00, 0x00, // Code for char 0
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char 1
0x0B, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x18, 0xFC, 0x07, 0x18, 0xFC, 0x07, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0xF8, 0x0F, 0x06, 0xF8, 0x0F, 0x06, 0xF0, 0x07, 0x06, 0x00, 0x00, 0x00, // Code for char 2
0x0B, 0x00, 0x00, 0x00, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0xF8, 0xFF, 0x07, 0xF0, 0xFF, 0x07, 0xE0, 0xF3, 0x03, 0x00, 0x00, 0x00, // Code for char 3
0x0B, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xFC, 0x00, 0x00, 0xDF, 0x00, 0xC0, 0xC7, 0x00, 0xF0, 0xC1, 0x00, 0x78, 0xC0, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, // Code for char 4
0x0B, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x06, 0xF8, 0x07, 0x06, 0xF8, 0x07, 0x06, 0x18, 0x06, 0x06, 0x18, 0x06, 0x06, 0x18, 0x06, 0x06, 0x18, 0x06, 0x06, 0x18, 0xFE, 0x07, 0x18, 0xFE, 0x07, 0x00, 0xFC, 0x01, 0x00, 0x00, 0x00, // Code for char 5
0x0B, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0x03, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x06, 0x06, 0x18, 0x06, 0x06, 0x18, 0x06, 0x06, 0x18, 0x06, 0x06, 0x18, 0xFE, 0x07, 0x00, 0xFE, 0x07, 0x00, 0xFC, 0x03, 0x00, 0x00, 0x00, // Code for char 6
0x0C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x04, 0x18, 0x00, 0x07, 0x18, 0xE0, 0x07, 0x18, 0xF8, 0x03, 0x18, 0xFF, 0x00, 0xD8, 0x1F, 0x00, 0xF8, 0x07, 0x00, 0xF8, 0x00, 0x00, 0x38, 0x00, 0x00, 0x08, 0x00, 0x00, // Code for char 7
0x0B, 0x00, 0x00, 0x00, 0xE0, 0xFB, 0x03, 0xF0, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF0, 0xFB, 0x03, 0x00, 0x00, 0x00, // Code for char 8
0x0B, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0xF8, 0x0F, 0x06, 0xF8, 0x0F, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF0, 0xFF, 0x03, 0x00, 0x00, 0x00, // Code for char 9
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x07, 0x00, 0x0E, 0x07, 0x00, 0x0E, 0x07, 0x00, 0x0E, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char :
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x7E, 0x00, 0x0E, 0x7E, 0x00, 0x0E, 0x7E, 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ;
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x39, 0x00, 0xC0, 0x71, 0x00, 0xE0, 0xE0, 0x00, 0x70, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char <
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x19, 0x00, 0x80, 0x19, 0x00, 0x80, 0x19, 0x00, 0x80, 0x19, 0x00, 0x80, 0x19, 0x00, 0x80, 0x19, 0x00, 0x80, 0x19, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char =
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xC0, 0x01, 0x60, 0xE0, 0x00, 0xC0, 0x70, 0x00, 0x80, 0x31, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char >
0x0B, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x30, 0x07, 0x18, 0x7C, 0x07, 0x18, 0x1C, 0x07, 0x18, 0x0C, 0x07, 0x18, 0x0C, 0x00, 0xF8, 0x0F, 0x00, 0xF8, 0x0F, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x00, 0x00, // Code for char ?
0x0B, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x3F, 0xC0, 0xFF, 0x3F, 0xC0, 0x00, 0x30, 0xC0, 0xF8, 0x31, 0xC0, 0xFC, 0x33, 0xC0, 0x0C, 0x33, 0xC0, 0xFC, 0x33, 0xC0, 0x00, 0x33, 0xC0, 0xFF, 0x33, 0xC0, 0xFF, 0x33, 0x00, 0x00, 0x00, // Code for char @
0x0C, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0xFC, 0x07, 0xC0, 0xFF, 0x07, 0xF8, 0xFF, 0x00, 0xF8, 0xC0, 0x00, 0x78, 0xC0, 0x00, 0xF8, 0xDF, 0x00, 0xE0, 0xFF, 0x07, 0x00, 0xFE, 0x07, 0x00, 0xC0, 0x07, 0x00, 0x00, 0x04, // Code for char A
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xE0, 0xF3, 0x03, 0x00, 0x00, 0x00, // Code for char B
0x0B, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x00, 0xF0, 0xFF, 0x03, 0xF8, 0xFF, 0x07, 0x38, 0x00, 0x07, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char C
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF0, 0xFF, 0x03, 0x00, 0x00, 0x00, // Code for char D
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char E
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x0C, 0x00, 0x18, 0x0C, 0x00, 0x18, 0x0C, 0x00, 0x18, 0x0C, 0x00, 0x18, 0x0C, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char F
0x0B, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x01, 0xF0, 0xFF, 0x03, 0xF8, 0xFF, 0x07, 0x38, 0x00, 0x07, 0x18, 0x00, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0xFC, 0x07, 0x18, 0xFC, 0x07, 0x18, 0xFC, 0x07, 0x00, 0x00, 0x00, // Code for char G
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x00, 0x00, 0x00, // Code for char H
0x0B, 0x00, 0x00, 0x00, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char I
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x03, 0xF8, 0xFF, 0x01, 0x00, 0x00, 0x00, // Code for char J
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x00, 0x0E, 0x00, 0x80, 0x3F, 0x00, 0xC0, 0xFF, 0x00, 0xF0, 0xF1, 0x03, 0x78, 0xE0, 0x07, 0x38, 0x80, 0x07, 0x08, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char K
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char L
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0x07, 0x00, 0xC0, 0xFF, 0x01, 0x00, 0xF0, 0x07, 0x00, 0xE0, 0x07, 0x80, 0xFF, 0x03, 0xF8, 0x0F, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x00, 0x00, 0x00, // Code for char M
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0x01, 0x00, 0xF0, 0x07, 0x00, 0x80, 0x3F, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xF0, 0x03, 0x00, 0xC0, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x00, 0x00, 0x00, // Code for char N
0x0B, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x01, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF0, 0xFF, 0x03, 0x00, 0x00, 0x00, // Code for char O
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0xF8, 0x1F, 0x00, 0xF8, 0x1F, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0x00, 0x00, // Code for char P
0x0B, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x01, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x00, 0x06, 0x18, 0x00, 0x06, 0x18, 0x00, 0x1E, 0x18, 0x00, 0x1E, 0xF8, 0xFF, 0x17, 0xF8, 0xFF, 0x07, 0xF0, 0xFF, 0x03, 0x00, 0x00, 0x00, // Code for char Q
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x0C, 0x00, 0x18, 0x0C, 0x00, 0x18, 0x0C, 0x00, 0x18, 0x0C, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF0, 0xF3, 0x07, 0x00, 0x00, 0x00, // Code for char R
0x0B, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x06, 0xF0, 0x07, 0x06, 0xF8, 0x0F, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0x0C, 0x06, 0x18, 0xFC, 0x07, 0x18, 0xFC, 0x07, 0x18, 0xF8, 0x03, 0x00, 0x00, 0x00, // Code for char S
0x0B, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char T
0x0B, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x01, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x03, 0x00, 0x00, 0x00, // Code for char U
0x0C, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0xF8, 0xFF, 0x00, 0x00, 0xFF, 0x07, 0x00, 0xC0, 0x07, 0x00, 0x80, 0x07, 0x00, 0xFE, 0x07, 0xF0, 0xFF, 0x00, 0xF8, 0x0F, 0x00, 0xF8, 0x00, 0x00, 0x08, 0x00, 0x00, // Code for char V
0x0C, 0x38, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0xF8, 0xFF, 0x07, 0x00, 0xFE, 0x07, 0x00, 0xE0, 0x07, 0x80, 0xFF, 0x00, 0x80, 0x3F, 0x00, 0x00, 0xF8, 0x07, 0x00, 0xE0, 0x07, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x03, 0xF8, 0x01, 0x00, // Code for char W
0x0C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x06, 0x78, 0x80, 0x07, 0xF8, 0xE1, 0x07, 0xF0, 0xFF, 0x01, 0x80, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0xC0, 0xFF, 0x01, 0xF0, 0xF3, 0x07, 0xF8, 0xC0, 0x07, 0x38, 0x00, 0x07, 0x08, 0x00, 0x04, // Code for char X
0x0C, 0x08, 0x00, 0x00, 0x38, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xF8, 0x03, 0x00, 0xE0, 0xFF, 0x07, 0x00, 0xFF, 0x07, 0x00, 0xFE, 0x07, 0xC0, 0xFF, 0x07, 0xF8, 0x07, 0x00, 0xF8, 0x00, 0x00, 0x38, 0x00, 0x00, 0x08, 0x00, 0x00, // Code for char Y
0x0B, 0x00, 0x00, 0x00, 0x18, 0x00, 0x06, 0x18, 0x80, 0x07, 0x18, 0xE0, 0x07, 0x18, 0xF8, 0x07, 0x18, 0x7E, 0x06, 0x18, 0x3F, 0x06, 0xD8, 0x0F, 0x06, 0xF8, 0x03, 0x06, 0xF8, 0x00, 0x06, 0x38, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char Z
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char [
0x0B, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x38, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x80, 0x07, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char BackSlash
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ]
0x0C, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x80, 0x01, 0x00, 0xC0, 0x00, 0x00, 0x60, 0x00, 0x00, 0x38, 0x00, 0x00, 0x18, 0x00, 0x00, 0x70, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, // Code for char ^
0x0C, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, // Code for char _
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char `
0x0B, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x03, 0xC0, 0xF8, 0x07, 0xC0, 0xF8, 0x07, 0xC0, 0x18, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, // Code for char a
0x0B, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0x80, 0xFF, 0x03, 0x00, 0x00, 0x00, // Code for char b
0x0B, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x01, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0x00, 0x07, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char c
0x0B, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x03, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0x00, 0x07, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0x00, 0x00, 0x00, // Code for char d
0x0B, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x03, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0x30, 0x06, 0xC0, 0x30, 0x06, 0xC0, 0x30, 0x06, 0xC0, 0x30, 0x06, 0xC0, 0x39, 0x06, 0xC0, 0x3F, 0x06, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x00, // Code for char e
0x0B, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xF8, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0xCC, 0x00, 0x00, 0xCC, 0x00, 0x00, 0xCC, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char f
0x0B, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x03, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0xC7, 0xC0, 0x00, 0xC6, 0xC0, 0x00, 0xC6, 0xC0, 0x00, 0xC6, 0xC0, 0x00, 0xC6, 0xC0, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xC0, 0xFF, 0x7F, 0x00, 0x00, 0x00, // Code for char g
0x0B, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, // Code for char h
0x0B, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xCC, 0x00, 0x06, 0xCC, 0xFF, 0x07, 0xCC, 0xFF, 0x07, 0xCC, 0xFF, 0x07, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char i
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xC0, 0xC0, 0x00, 0xC0, 0xC0, 0x00, 0xC0, 0xC0, 0x00, 0xC0, 0xC0, 0x00, 0xE0, 0xCC, 0xFF, 0xFF, 0xCC, 0xFF, 0xFF, 0xCC, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char j
0x0C, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0x00, 0x7C, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xEF, 0x01, 0xC0, 0xC7, 0x03, 0xC0, 0x81, 0x07, 0xC0, 0x00, 0x06, 0x40, 0x00, 0x04, 0x00, 0x00, 0x04, // Code for char k
0x0B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x06, 0x0C, 0x00, 0x06, 0x0C, 0x00, 0x06, 0x0C, 0x00, 0x06, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char l
0x0B, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0x00, 0x00, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0xFF, 0x07, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, // Code for char m
0x0B, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, // Code for char n
0x0C, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x03, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0x80, 0xFF, 0x03, 0x00, 0xFE, 0x00, // Code for char o
0x0B, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0x80, 0xFF, 0x03, 0x00, 0x00, 0x00, // Code for char p
0x0B, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x03, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0x00, 0x00, 0x00, // Code for char q
0x0B, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x07, 0x80, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0x01, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char r
0x0C, 0x00, 0x00, 0x00, 0x80, 0x0F, 0x06, 0xC0, 0x1F, 0x06, 0xC0, 0x1D, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0xF8, 0x07, 0xC0, 0xF0, 0x03, 0x00, 0xC0, 0x00, // Code for char s
0x0B, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xF8, 0xFF, 0x03, 0xF8, 0xFF, 0x07, 0xF8, 0xFF, 0x07, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char t
0x0B, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x03, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x07, 0x00, 0x00, 0x00, // Code for char u
0x0C, 0x40, 0x00, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0x1F, 0x00, 0xC0, 0xFF, 0x01, 0x00, 0xFC, 0x07, 0x00, 0xC0, 0x07, 0x00, 0x80, 0x07, 0x00, 0xF8, 0x07, 0x80, 0xFF, 0x03, 0xC0, 0x3F, 0x00, 0xC0, 0x07, 0x00, 0x40, 0x00, 0x00, // Code for char v
0x0C, 0xC0, 0x00, 0x00, 0xC0, 0xFF, 0x01, 0xC0, 0xFF, 0x07, 0x00, 0xF0, 0x07, 0x00, 0xF0, 0x07, 0x00, 0xFE, 0x01, 0x00, 0xFE, 0x00, 0x00, 0xF8, 0x07, 0x00, 0xC0, 0x07, 0xC0, 0xFF, 0x07, 0xC0, 0xFF, 0x03, 0xC0, 0x03, 0x00, // Code for char w
0x0C, 0x40, 0x00, 0x04, 0xC0, 0x00, 0x06, 0xC0, 0x01, 0x07, 0xC0, 0xC7, 0x07, 0x00, 0xFF, 0x01, 0x00, 0xFE, 0x00, 0x00, 0x7C, 0x00, 0x00, 0xFF, 0x01, 0x80, 0xC7, 0x07, 0xC0, 0x81, 0x07, 0xC0, 0x00, 0x06, 0x40, 0x00, 0x04, // Code for char x
0x0C, 0x40, 0x00, 0x00, 0xC0, 0x01, 0x00, 0xC0, 0x0F, 0x00, 0xC0, 0x7F, 0xC0, 0x00, 0xFE, 0xF3, 0x00, 0xF0, 0xFF, 0x00, 0xC0, 0x1F, 0x00, 0xF0, 0x07, 0x00, 0xFF, 0x00, 0xC0, 0x1F, 0x00, 0xC0, 0x03, 0x00, 0x40, 0x00, 0x00, // Code for char y
0x0B, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x06, 0xC0, 0x00, 0x07, 0xC0, 0xC0, 0x07, 0xC0, 0xE0, 0x07, 0xC0, 0xF8, 0x06, 0xC0, 0x7E, 0x06, 0xC0, 0x1F, 0x06, 0xC0, 0x0F, 0x06, 0xC0, 0x03, 0x06, 0xC0, 0x00, 0x06, 0x00, 0x00, 0x00, // Code for char z
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x18, 0x00, 0xF8, 0xFF, 0x1F, 0xFE, 0xEF, 0x7F, 0xFE, 0xC3, 0x7F, 0x03, 0x00, 0xC0, 0x01, 0x00, 0x80, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char {
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char |
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x01, 0x00, 0x80, 0x03, 0x00, 0xC0, 0xFE, 0xC3, 0xFF, 0xFE, 0xE7, 0x7F, 0xFC, 0xFF, 0x3F, 0x00, 0x18, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char }
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x06, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ~
0x05, 0x80, 0xFF, 0x03, 0x80, 0xFF, 0x03, 0x80, 0x00, 0x02, 0x80, 0xFF, 0x03, 0x80, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Code for char 
};

接下来我们要在原先的draw_text()函数中小小的修改一下,首先加载这个字体库:unispace_font = XglcdFont(‘Unispace12x24.c’, 12, 24),如果你是将这个字体库放入到一个文件夹内的话,可以修改成unispace_font = XglcdFont(‘font/Unispace12x24.c’, 12, 24)。

然后修改绘制文本的代码:display.draw_text(0, 10, ‘ESP32 says hello!’, unispace_font, white_color, black_color),你会发现在原先的代码上只是加入了字体参数“unispace_font”。需要注意的是,加载字体库要优先于绘制文本(说白了就是先加载后绘制)。运行后会发现加载了字体的文本比原先的要大一些,这就说明我们自定义的文本生效了。

3.1.2 绘制图形

绘制图形不像绘制文本那样复杂,并不需要加装字符渲染库和字体库,ili9341库自带绘制图形的功能,我们只需要在调用时添加参数就行。以下是个绘制图形的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
from machine import Pin, SPI, ADC, idle,SoftSPI
import os
from time import sleep
from ili9341 import Display, color565
from xglcd_font import XglcdFont

display_spi = SPI(1, baudrate=60000000, sck=Pin(14), mosi=Pin(13))
display = Display(display_spi, dc=Pin(2), cs=Pin(15), rst=Pin(15),
width=320, height=240, rotation=90)

print('Display height: ' + str(display.height))
print('Display width: ' + str(display.width))

white_color = color565(255, 255, 255) # white
black_color = color565(0, 0, 0) # Black

backlight = Pin(21, Pin.OUT)
backlight.on()

display.clear(black_color)

def draw_shapes():
# 在(10, 40)位置画一条长度为70的水平线,颜色为紫色
display.draw_hline(10, 40, 70, color565(255, 0, 255))
sleep(1)

# 在(10, 0)位置画一条长度为40的垂直线,颜色为青色
display.draw_vline(10, 0, 40, color565(0, 255, 255))
sleep(1)

# 在(23, 50)位置填充一个宽度为30,高度为75的矩形,颜色为白色
display.fill_hrect(23, 50, 30, 75, color565(255, 255, 255))
sleep(1)

# 在(0, 0)位置画一条长度为100的水平线,颜色为红色
display.draw_hline(0, 0, 100, color565(255, 0, 0))
sleep(1)

# 在(50, 0)和(64, 40)之间画一条线,颜色为黄色
display.draw_line(50, 0, 64, 40, color565(255, 255, 0))
sleep(2)

# 清除显示内容
display.clear()

# 绘制一个由多个点组成的折线,颜色为青色
coords = [[0, 63], [78, 80], [122, 92], [50, 50], [78, 15], [0, 63]]
display.draw_lines(coords, color565(0, 255, 255))
sleep(1)

# 清除显示内容
display.clear()
# 填充一个多边形,颜色为绿色
display.fill_polygon(7, 120, 120, 100, color565(0, 255, 0))
sleep(1)

# 填充一个矩形,颜色为红色
display.fill_rectangle(0, 0, 15, 227, color565(255, 0, 0))
sleep(1)

# 清除显示内容
display.clear()

# 填充一个矩形,颜色为蓝色
display.fill_rectangle(0, 0, 163, 163, color565(128, 128, 255))
sleep(1)

# 画一个矩形框,颜色为紫色
display.draw_rectangle(0, 64, 163, 163, color565(255, 0, 255))
sleep(1)

# 填充一个矩形,颜色为品红色
display.fill_rectangle(64, 0, 163, 163, color565(128, 0, 255))
sleep(1)

# 画一个旋转15度的多边形,颜色为蓝色
display.draw_polygon(3, 120, 110, 30, color565(0, 64, 255), rotate=15)
sleep(3)

# 清除显示内容
display.clear()

# 填充一个圆,颜色为绿色
display.fill_circle(132, 132, 70, color565(0, 255, 0))
sleep(1)

# 画一个圆,颜色为蓝色
display.draw_circle(132, 96, 70, color565(0, 0, 255))
sleep(1)

# 填充一个椭圆,颜色为红色
display.fill_ellipse(96, 96, 30, 16, color565(255, 0, 0))
sleep(1)

# 画一个椭圆,颜色为黄色
display.draw_ellipse(96, 85, 16, 30, color565(255, 255, 0))

sleep(5)
# 清理显示资源
display.cleanup()

try:
draw_shapes()
except Exception as e:
print('Error occured: ', e)
except KeyboardInterrupt:
print('Program Interrupted by the user')
finally:
display.clear()

3.1.3 绘制图片

如何在这块屏幕上绘制一个图片(或者照片),巧的是,ili9341库自带了一个绘制图像的功能。但是要利用这个功能绘制图像的话,这个图像必须转换为.raw类型。

接下来我们通过一个小工具来将平时我们常见的png、jpg类型的图片转换为raw(img2rgb565.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# -*- coding: utf-8 -*-
"""Utility to convert images to raw RGB565 format.

Usage:
python img2rgb565.py <your_image>
<your_image> is the full path to the image file you want to convert.
"""

from PIL import Image
from struct import pack
from os import path
import sys

def error(msg):
"""Display error and exit."""
print (msg)
sys.exit(-1)

def write_bin(f, pixel_list):
"""Save image in RGB565 format."""
for pix in pixel_list:
r = (pix[0] >> 3) & 0x1F
g = (pix[1] >> 2) & 0x3F
b = (pix[2] >> 3) & 0x1F
f.write(pack('>H', (r << 11) + (g << 5) + b))

if __name__ == '__main__':
args = sys.argv
if len(args) != 2:
error('Please specify input file: ./img2rgb565.py test.png')
in_path = args[1]
if not path.exists(in_path):
error('File Not Found: ' + in_path)

filename, ext = path.splitext(in_path)
out_path = filename + '.raw'
img = Image.open(in_path).convert('RGB')
pixels = list(img.getdata())
with open(out_path, 'wb') as f:
write_bin(f, pixels)
print('Saved: ' + out_path)

在正式使用工具之前,请确保你电脑安装了python,并且安装好了该程序需要的第三方库。接下来,将你想要转换的图片置于程序同级目录下。然后打开命令提示符,输入python img2rgb565.py xxx.png回车后就能得到转换后的图片了。

接下来将这个raw类型的图片文件上传至esp32,然后利用

1
2
def load_image():
display.draw_image('xxx.raw', 0, 0, 128, 128)

在屏幕(0,0)处显示此图像。需要注意的是,128,128指的是图片的尺寸,需要根据实际情况填写,如果你选择的图片是100x200,那么你应该填入100,200,填入其它参数会导致图像绘制失败。同时也别让图像超过屏幕的分辨率,不然也会导致图像绘制失败。

3.2 触摸屏

电阻式触摸屏通过在屏幕表面覆盖一层电阻膜来实现触摸功能。当手指或触摸笔按压屏幕时,会改变电阻膜的电阻值,从而产生一个电信号。XPT2046 控制器负责检测这个电信号,并将其转换为触摸点的坐标信息。然后,微控制器可以通过读取这些坐标信息来确定触摸位置,并据此执行相应的操作。

ESP32-CYD的屏幕自带触摸的功能,它采用了xpt2046芯片作为触屏驱动,因此我们需要选择一个合适的驱动库(xpt2046.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
"""XPT2046 Touch module."""
from time import sleep
from micropython import const

class Touch(object):
"""Serial interface for XPT2046 Touch Screen Controller."""

# Command constants from ILI9341 datasheet
GET_X = const(0b11010000) # X position
GET_Y = const(0b10010000) # Y position
GET_Z1 = const(0b10110000) # Z1 position
GET_Z2 = const(0b11000000) # Z2 position
GET_TEMP0 = const(0b10000000) # Temperature 0
GET_TEMP1 = const(0b11110000) # Temperature 1
GET_BATTERY = const(0b10100000) # Battery monitor
GET_AUX = const(0b11100000) # Auxiliary input to ADC

def __init__(self, spi, cs, int_pin=None, int_handler=None,
width=240, height=320,
x_min=100, x_max=1962, y_min=100, y_max=1900):
"""Initialize touch screen controller.

Args:
spi (Class Spi): SPI interface for OLED
cs (Class Pin): Chip select pin
int_pin (Class Pin): Touch controller interrupt pin
int_handler (function): Handler for screen interrupt
width (int): Width of LCD screen
height (int): Height of LCD screen
x_min (int): Minimum x coordinate
x_max (int): Maximum x coordinate
y_min (int): Minimum Y coordinate
y_max (int): Maximum Y coordinate
"""
self.spi = spi
self.cs = cs
self.cs.init(self.cs.OUT, value=1)
self.rx_buf = bytearray(3) # Receive buffer
self.tx_buf = bytearray(3) # Transmit buffer
self.width = width
self.height = height
# Set calibration
self.x_min = x_min
self.x_max = x_max
self.y_min = y_min
self.y_max = y_max
self.x_multiplier = width / (x_max - x_min)
self.x_add = x_min * -self.x_multiplier
self.y_multiplier = height / (y_max - y_min)
self.y_add = y_min * -self.y_multiplier

if int_pin is not None:
self.int_pin = int_pin
self.int_pin.init(int_pin.IN)
self.int_handler = int_handler
self.int_locked = False
int_pin.irq(trigger=int_pin.IRQ_FALLING | int_pin.IRQ_RISING,
handler=self.int_press)

def get_touch(self):
"""Take multiple samples to get accurate touch reading."""
timeout = 2 # set timeout to 2 seconds
confidence = 5
buff = [[0, 0] for x in range(confidence)]
buf_length = confidence # Require a confidence of 5 good samples
buffptr = 0 # Track current buffer position
nsamples = 0 # Count samples
while timeout > 0:
if nsamples == buf_length:
meanx = sum([c[0] for c in buff]) // buf_length
meany = sum([c[1] for c in buff]) // buf_length
dev = sum([(c[0] - meanx)**2 +
(c[1] - meany)**2 for c in buff]) / buf_length
if dev <= 50: # Deviation should be under margin of 50
return self.normalize(meanx, meany)
# get a new value
sample = self.raw_touch() # get a touch
if sample is None:
nsamples = 0 # Invalidate buff
else:
buff[buffptr] = sample # put in buff
buffptr = (buffptr + 1) % buf_length # Incr, until rollover
nsamples = min(nsamples + 1, buf_length) # Incr. until max

sleep(.05)
timeout -= .05
return None

def int_press(self, pin):
"""Send X,Y values to passed interrupt handler."""
if not pin.value() and not self.int_locked:
self.int_locked = True # Lock Interrupt
buff = self.raw_touch()

if buff is not None:
x, y = self.normalize(*buff)
self.int_handler(x, y)
sleep(.1) # Debounce falling edge
elif pin.value() and self.int_locked:
sleep(.1) # Debounce rising edge
self.int_locked = False # Unlock interrupt

def normalize(self, x, y):
"""Normalize mean X,Y values to match LCD screen."""
x = int(self.x_multiplier * x + self.x_add)
y = int(self.y_multiplier * y + self.y_add)
return x, y

def raw_touch(self):
"""Read raw X,Y touch values.

Returns:
tuple(int, int): X, Y
"""
x = self.send_command(self.GET_X)
y = self.send_command(self.GET_Y)
if self.x_min <= x <= self.x_max and self.y_min <= y <= self.y_max:
return (x, y)
else:
return None

def send_command(self, command):
"""Write command to XT2046 (MicroPython).

Args:
command (byte): XT2046 command code.
Returns:
int: 12 bit response
"""
self.tx_buf[0] = command
self.cs(0)
self.spi.write_readinto(self.tx_buf, self.rx_buf)
self.cs(1)

return (self.rx_buf[1] << 4) | (self.rx_buf[2] >> 4)

接下来通过代码来使用触摸屏的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from machine import Pin, SPI, ADC, idle
import os
from time import sleep

from ili9341 import Display, color565
from xpt2046 import Touch
from xglcd_font import XglcdFont

display_spi = SPI(1, baudrate=60000000, sck=Pin(14), mosi=Pin(13))
display = Display(display_spi, dc=Pin(2), cs=Pin(15), rst=Pin(15), width=320, height=240, rotation=90)

print('Display height: ' + str(display.height))
print('Display width: ' + str(display.width))

white_color = color565(255, 255, 255)
black_color = color565(0, 0, 0)

backlight = Pin(21, Pin.OUT)
backlight.on()

display.clear(black_color)

# 显示的中心位置显示文本
font_size = 8
text_msg = 'Touch screen to test'
x_center = int((display.width-len(text_msg)*font_size)/2)
y_center = int(((display.height)/2)-(font_size/2))

display.draw_text8x8(x_center, y_center,text_msg, white_color, black_color, 0)

# 初始化触摸屏
touchscreen_spi = SPI(2, baudrate=1000000, sck=Pin(25), mosi=Pin(32), miso=Pin(39))
# 定义触摸屏按下事件处理函数
def touchscreen_press(x, y):
display.clear(black_color)
text_touch_coordinates = "Touch: X = " + str(x) + " | Y = " + str(y)
x_center = int((display.width-len(text_touch_coordinates)*font_size)/2)
display.draw_text8x8(x_center, y_center, text_touch_coordinates, white_color, black_color, 0)
print("Touch: X = " + str(x) + " | Y = " + str(y))
# 检测触摸
touchscreen = Touch(touchscreen_spi, cs=Pin(33), int_pin=Pin(36), int_handler=touchscreen_press)

try:
while True:
# 等待触摸屏按下事件
touchscreen.get_touch()
except Exception as e:
print('Error occured: ', e)
except KeyboardInterrupt:
print('Program Interrupted by the user')
finally:
display.cleanup()

touchscreen = Touch(touchscreen_spi, cs=Pin(33), int_pin=Pin(36), int_handler=touchscreen_press)将在检测到触摸时运行。该库将自动传递x和y坐标分配给该函数。在我们的例子中,它将调用touchscreen_press函数。当在屏幕上检测到触摸时,touchscreen_press函数将运行,屏幕就会实时显示我们触屏的坐标。我们也可以执行任何其他任务。

4.LDR

LDR(Light Dependent Resistor,光敏电阻)是一种能够根据光照强度改变其电阻值的传感器。其工作原理基于半导体材料的光电效应。它广泛应用于各种光控电路中,如自动照明系统、光敏报警器等。

我们同样可以通过代码来获取传感器参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from machine import Pin, ADC
import os
from time import sleep

try:
# Run the event loop indefinitely
while True:
# Read light sensor
lightsensor = ADC(34, atten=ADC.ATTN_0DB)
print('LDR value: ' + str(lightsensor.read_uv()))
sleep(1)
except Exception as e:
print('Error occured: ', e)
except KeyboardInterrupt:
print('Program Interrupted by the user')

5.合理扩容

实际上,当你将所有上述代码部署到ESP32-CYD时,你会发现随着项目的进行,内存资源会变得越来越紧张。为了有效缓解这一问题,我们可以采取一种策略:将驱动库、图片、字体等辅助文件存储到SD卡中。这样一来,就能为主程序腾出ESP32上宝贵的存储空间,确保系统的稳定运行。

接下来,请将驱动库、图片、字体等辅助文件都转存至SD卡中,然后将其插入进ESP32中,同时在ESP32设备上,删除之前转存的所有辅助文件,以释放存储空间,只留下boot.py、sdcard.py和main.py(或者你的主程序)。

在ESP32设备的启动或重启流程中,boot.py扮演着至关重要的角色。每当ESP32启动或重启时,系统会自动执行boot.py文件一次。这使得boot.py成为初始化ESP32设备的关键代码文件。在boot.py中,通常包含了一些基础配置和初始化操作,如设置WiFi连接、挂载SD卡、配置引脚模式等。

根据这一原理,我们可以在boot.py中写以下代码,以初始化SD卡:

1
2
3
4
5
6
7
8
from machine import Pin, SPI, SoftSPI
import os
from sdcard import SDCard
spisd = SoftSPI(-1, miso=Pin(19), mosi=Pin(23), sck=Pin(18))
sd = SDCard(spisd, Pin(5))
vfs = os.VfsFat(sd)
os.mount(vfs, '/sd')
os.chdir('/sd/libs')

需要注意的是,sd卡目录下的路径视情况而定,比如,在我的SD卡中,我将所有的驱动库都放入了一个名为libs的文件夹中。

在挂载了SD卡后,绘制文本、绘制图像都和之前的代码一样,因为我们已经在boot.py中初始化了有关驱动库的部分,不过对于图片或者是字体库等其它辅助文件,我们也要通过代码初始化。比如绘制一个图片:

1
2
3
4
5
6
def load_image():
# 切换到sd卡目录下
os.chdir('/sd')
# 使用draw_image函数加载图片
# 同时请注意图片的路径,注意是相对于sd卡的路径
display.draw_image('/sd/img/xxx.raw', 0, 0, 150, 110)

结合上述的所有代码,我们可以写出以下代码(大杂烩):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
from machine import Pin, SPI, ADC, idle,SoftSPI
import os
from time import sleep
# 导入ili9341和xglcd_font模块还有xpt2046模块
from ili9341 import Display, color565
from xglcd_font import XglcdFont
from xpt2046 import Touch
# 初始化显示屏
display_spi = SPI(1, baudrate=60000000, sck=Pin(14), mosi=Pin(13))
display = Display(display_spi, dc=Pin(2), cs=Pin(15), rst=Pin(15),
width=320, height=240, rotation=90)
# 图片加载函数
def load_image():
# 因为挂载了sd卡,所以需要先切换到sd卡目录下
os.chdir('/sd')
# 然后使用draw_image函数加载图片,同时请注意图片的路径,注意是相对于sd卡的路径
display.draw_image('/sd/img/logo.raw', 80, 0, 150, 110)
# 绘制图形
def draw_shape():
white_color = color565(255, 255, 255) # white
black_color = color565(0, 0, 0) # Black
backlight = Pin(21, Pin.OUT)
backlight.on()
display.fill_rectangle(0, 0, 15, 240, color565(255, 0, 0))
display.draw_hline(85, 130, 150, color565(255, 151, 0))
display.fill_circle(60, 120, 10, color565(0, 0, 255))
# 绘制文字
def draw_text():

white_color = color565(255, 255, 255)
black_color = color565(0, 0, 0)

backlight = Pin(21, Pin.OUT)
backlight.on()
display.clear(white_color)

print('Loading Unispace font...')
# 挂载sd卡后,也要注意字体库的路径
unispace_font = XglcdFont('/sd/font/Unispace12x24.c', 12, 24)

display.draw_text(20, 210, 'blog.goldenapplepie.xyz', unispace_font, black_color, white_color)

font_size_w = unispace_font.width
font_size_h = unispace_font.height
text_msg = 'GoldenApplePie'
x_center = int((display.width-len(text_msg)*font_size_w)/2)
y_center = int(((display.height)/2)-(font_size_h/2))
display.draw_text(x_center, y_center, text_msg, unispace_font, black_color, white_color)
display.draw_text(display.width-font_size_h, display.height-font_size_w, ' Pie Dream Studio',
unispace_font, black_color, white_color, landscape=True)

# 触摸屏测试
def touch():
def touchscreen_press(x, y):
print("Touch detected at", x, y)
touchscreen_spi = SPI(2, baudrate=1000000, sck=Pin(25), mosi=Pin(32), miso=Pin(39))
touchscreen = Touch(touchscreen_spi, cs=Pin(33), int_pin=Pin(36), int_handler=touchscreen_press)

try:
#os.chdir('/sd')
draw_text()
draw_shape()
load_image()
while True:
touch()
pass
except Exception as e:
print('Error occured: ', e)
except KeyboardInterrupt:
print('Program Interrupted by the user')
display.cleanup()
# 程序结束时,卸载SD卡
finally:
os.umount('/sd')

又或者,我们还可以通过ESP32的联网的功能,搭建一个http服务端,且将服务端的文件置于sd卡内:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import network
import socket
import time
import machine
import os

# 配置网络参数
ssid = ''
password = ''

# 连接到WiFi网络
sta_if = network.WLAN(network.STA_IF)
sta_if.active(True)
sta_if.connect(ssid, password)

print('网络已连接,IP地址:', sta_if.ifconfig()[0])

# 创建socket
addr = ('', 80) # 监听所有可用的网络接口上的80端口
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(addr)
s.listen(1)

def http_response(client_socket, status_line, headers, content=""):
# 发送HTTP状态行
client_socket.sendall(b'HTTP/1.1 ' + status_line.encode() + b'\r\n')
# 遍历headers列表,逐个发送header
for header in headers:
client_socket.sendall(header.encode() + b'\r\n')
# 发送一个空行,表示headers部分结束
client_socket.sendall(b'\r\n')
# 发送响应内容
client_socket.sendall(content.encode())

def serve_file(path):
try:
with open(path, 'r') as f:
return f.read()
except FileNotFoundError:
return "404 Not Found"

try:
while True:
print('等待连接...')
client_socket, addr = s.accept()
print('连接自:', addr)
request = client_socket.recv(1024)
print('收到请求:', request.decode())

# 简单的请求处理(支持GET /)
if request.startswith(b'GET / HTTP/1.1'):
status_line = '200 OK'
headers = [
'Content-Type: text/html',
'Connection: close'
]
# 读取SD卡上的index.html文件内容
html_file_path = '/sd/web/index.html'
content = serve_file(html_file_path)
http_response(client_socket, status_line, headers, content)

client_socket.close()

except KeyboardInterrupt:
s.close()
print('服务器已关闭')

finally:
# 卸载SD卡
os.umount('/sd')

你需要注意的是html_file_path = ‘/sd/web/index.html’部分,需要根据具体的路径修改。对于ssid与password,也应根据具体情况修改。

LVGL

LVGL是一个免费开源的轻量级嵌入式图形库,专为资源受限的硬件设计,仅需64KB Flash和16KB RAM即可运行,提供按钮、图表、动画等丰富组件及抗锯齿等视觉效果,支持多平台、多输入设备,并配备模拟器和可视化工具,帮助开发者快速构建智能穿戴、工业控制等领域的交互界面,其MIT开源协议和活跃社区进一步降低了开发门槛。

在这篇内容中,我们将从 Arduino 的角度探讨 LVGL 的运用方式。选择开发语言时,C/C++(Arduino 主流方案)能直接操作硬件寄存器,精准分配内存与外设资源,尤其适合资源受限的嵌入式场景;而 MicroPython 虽简化了编程门槛,但因动态类型和解释执行的特性,对硬件的控制粒度较弱,且运行时开销较大。若追求高性能、低功耗或复杂图形渲染(如动画、多屏适配),C/C++ 是更可靠的选择;若需快速验证原型或开发简单交互界面,MicroPython 则能提升开发效率。

小插曲:一开始我想用 MicroPython 快速试试 LVGL 的功能,结果发现要把原本用 C/C++ 写的 LVGL 移植到 MicroPython 上特别麻烦。为了做这个测试,我还专门给电脑装了 Ubuntu 系统(因为需要 Linux 环境),又从网上下载了一堆编译工具来配置。结果遇到各种问题:工具版本对不上、编译参数调来调去——光是解决程序报错就花了好几天,好不容易编译生成了能用的文件并导进 ESP32 开发板,结果显示屏驱动又不兼容,界面根本显示不出来。折腾半天没解决,只能先暂停,等以后找到更简单的方法再试。

主要问题是 MicroPython 和 C/C++ 的工作方式差别太大:MicroPython 是边解释边执行,而 C/C++ 需要提前编译好,所以得用工具把 LVGL 的 C 代码转换成 MicroPython 能用的模块。这个过程中,编译工具的版本(比如 CMake、GCC)和依赖库(比如图形库、Python 扩展)得完全匹配,而 ESP32 开发板的特殊要求(比如存储空间分配、接口定义)又让调试变得更复杂。最后才发现直接移植可能不太现实,还是先用稳定的 C/C++ 方案更靠谱。

不过,你想试试我当时最后一次编译生成的lvgl-micropython的话,你可以直接点击链接下载,但我事先声明,此固件(也是针对当前文章所介绍的设备编译的,日期为:2025/6/11)虽然能够正常导入LVGL(import lvgl),但在显示驱动上可能出现了点问题(正如前面所说)。最后附上参考的链接:https://github.com/lvgl-micropython/lvgl_micropython,如果你愿意尝试自行编译优化,或者发现了新的解决方案,非常欢迎和我分享经验!

回到正题,打开的你的arduino,并且进行相关配置(和esp32一样),然后安装好lvgl、TFT_eSPI、XPT2046_Touchscreen库。接下来你需要为TFT库和LVGL库分别更新一个配置文件,找到你的项目文件夹,然后找到libraries文件夹,然后再找到TFT_eSPI文件夹并找到User_Setup.h文件,这个就是该库的配置文件,你可以在里面配置驱动类型、加载的字体、使用的引脚以及 SPI 控制方法等。如果你不知道怎么配置,可以直接下载此文件然后替换掉原先的就行。对于LVGL,你需要回到libraries文件夹,并在里面添加一个lv_conf.h文件,它是LVGL图形库的配置文件,用于设置和调整图形库的各种参数。这些参数包括颜色设置、标准库包装器设置、硬件抽象层(HAL)设置、操作系统支持、渲染配置、功能配置等。如果你不知道怎么填写和修改,也可以直接下载此文件至目标文件夹内。至此,所有准备工作都完成了。

接下来上传并编译下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// 引入必要的库文件
#include <lvgl.h> // LVGL图形库核心头文件
#include <TFT_eSPI.h> // TFT屏幕驱动库
#include <XPT2046_Touchscreen.h> // 触摸屏驱动库

// 触摸屏引脚定义(根据实际硬件连接修改)
#define XPT2046_IRQ 36 // 触摸中断引脚
#define XPT2046_MOSI 32 // SPI数据输出
#define XPT2046_MISO 39 // SPI数据输入
#define XPT2046_CLK 25 // SPI时钟引脚
#define XPT2046_CS 33 // 触摸芯片片选

// 显示屏引脚定义(需与TFT_eSPI的User_Setup.h配置一致)
#define TFT_MISO 12 // SPI数据输入(部分屏幕可能不用)
#define TFT_MOSI 13 // SPI数据输出
#define TFT_SCLK 14 // SPI时钟引脚
#define TFT_CS 15 // 屏幕片选
#define TFT_DC 2 // 数据/命令控制引脚
#define TFT_RST -1 // 复位引脚(-1表示未连接)
#define TFT_BL 21 // 背光控制引脚

// 初始化触摸屏SPI对象(使用VSPI通道)
SPIClass touchscreenSPI = SPIClass(VSPI);
// 创建触摸屏对象,传入CS和IRQ引脚
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);

// 息屏相关变量
unsigned long lastTouchTime = 0; // 记录最后触摸时间
const unsigned long idleTimeout = 25000; // 25秒无操作后息屏

// 屏幕分辨率定义
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320

// 触摸坐标变量
int x, y, z; // z表示触摸压力值

// LVGL绘图缓冲区配置(根据屏幕大小动态计算)
#define DRAW_BUF_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4]; // 缓冲区数组

// LVGL日志打印回调函数
void log_print(lv_log_level_t level, const char * buf) {
LV_UNUSED(level); // 忽略日志级别参数
Serial.println(buf); // 通过串口输出日志
Serial.flush(); // 确保日志立即发送
}

// 触摸屏读取回调函数(LVGL定时调用)
void touchscreen_read(lv_indev_t * indev, lv_indev_data_t * data) {
// 检测触摸状态
if(touchscreen.tirqTouched() && touchscreen.touched()) {
TS_Point p = touchscreen.getPoint(); // 获取原始触摸坐标

// 将原始坐标映射到屏幕分辨率(需根据实际屏幕校准)
x = map(p.x, 200, 3700, 0, SCREEN_WIDTH);
y = map(p.y, 240, 3800, 0, SCREEN_HEIGHT);
z = p.z; // 保存压力值

// 设置LVGL触摸数据
data->state = LV_INDEV_STATE_PRESSED;
data->point.x = x;
data->point.y = y;

// 调试信息输出
Serial.print("X = "); Serial.print(x);
Serial.print(" | Y = "); Serial.print(y);
Serial.print(" | Pressure = "); Serial.println(z);

lastTouchTime = millis(); // 更新最后触摸时间
}
else {
data->state = LV_INDEV_STATE_RELEASED; // 触摸释放状态
}
}

// 按钮1点击计数器
int btn1_count = 0;

// 按钮1事件处理函数
static void event_handler_btn1(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
btn1_count++;
LV_LOG_USER("Button clicked %d", (int)btn1_count); // 记录点击次数
}
}

// 按钮2(切换开关)事件处理
static void event_handler_btn2(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * obj = (lv_obj_t*) lv_event_get_target(e);
if(code == LV_EVENT_VALUE_CHANGED) {
LV_UNUSED(obj);
LV_LOG_USER("Toggled %s", lv_obj_has_state(obj, LV_STATE_CHECKED) ? "on" : "off");
}
}

// 滑动条标签指针(用于更新显示值)
static lv_obj_t * slider_label;

// 滑动条事件回调
static void slider_event_callback(lv_event_t * e) {
lv_obj_t * slider = (lv_obj_t*) lv_event_get_target(e);
char buf[8];
// 格式化显示百分比
lv_snprintf(buf, sizeof(buf), "%d%%", (int)lv_slider_get_value(slider));
lv_label_set_text(slider_label, buf); // 更新标签文本
lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
LV_LOG_USER("Slider changed to %d%%", (int)lv_slider_get_value(slider));
}

// 下拉菜单事件处理
static void dropdown_event_handler(lv_event_t * e) {
lv_obj_t * dropdown = (lv_obj_t *)lv_event_get_target(e);
char buf[32];
lv_dropdown_get_selected_str(dropdown, buf, sizeof(buf));
LV_LOG_USER("Selected option: %s", buf);
}

// 复选框事件处理
static void checkbox_event_handler(lv_event_t * e) {
lv_obj_t * checkbox = static_cast<lv_obj_t*>(lv_event_get_target(e));
bool checked = lv_obj_has_state(checkbox, LV_STATE_CHECKED);
LV_LOG_USER("Checkbox state: %s", checked ? "Checked" : "Unchecked");
}

// 创建主界面GUI
void lv_create_main_gui(void) {
// 创建欢迎文本标签
lv_obj_t * text_label = lv_label_create(lv_screen_active());
lv_label_set_long_mode(text_label, LV_LABEL_LONG_WRAP); // 自动换行
lv_label_set_text(text_label, "Welcome to LVGL GUI!");
lv_obj_set_width(text_label, 150); // 设置宽度触发换行
lv_obj_set_style_text_align(text_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(text_label, LV_ALIGN_CENTER, 0, -90); // 屏幕上方

lv_obj_t * btn_label; // 临时按钮标签指针

// 创建按钮1(普通按钮)
lv_obj_t * btn1 = lv_button_create(lv_screen_active());
lv_obj_add_event_cb(btn1, event_handler_btn1, LV_EVENT_ALL, NULL);
lv_obj_align(btn1, LV_ALIGN_CENTER, 0, -50); // 屏幕中上方
lv_obj_remove_flag(btn1, LV_OBJ_FLAG_PRESS_LOCK); // 允许重复触发

btn_label = lv_label_create(btn1);
lv_label_set_text(btn_label, "Click Me");
lv_obj_center(btn_label);

// 创建按钮2(切换开关)
lv_obj_t * btn2 = lv_button_create(lv_screen_active());
lv_obj_add_event_cb(btn2, event_handler_btn2, LV_EVENT_ALL, NULL);
lv_obj_align(btn2, LV_ALIGN_CENTER, 0, 10); // 屏幕中央
lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE); // 设置为可切换状态
lv_obj_set_height(btn2, LV_SIZE_CONTENT); // 高度自适应内容

btn_label = lv_label_create(btn2);
lv_label_set_text(btn_label, "Toggle");
lv_obj_center(btn_label);

// 创建滑动条
lv_obj_t * slider = lv_slider_create(lv_screen_active());
lv_obj_align(slider, LV_ALIGN_CENTER, 0, 60); // 屏幕中下方
lv_obj_add_event_cb(slider, slider_event_callback, LV_EVENT_VALUE_CHANGED, NULL);
lv_slider_set_range(slider, 0, 100); // 设置范围0-100
lv_obj_set_style_anim_duration(slider, 2000, 0); // 动画持续时间

// 创建滑动条百分比标签
slider_label = lv_label_create(lv_screen_active());
lv_label_set_text(slider_label, "0%");
lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);

// 创建下拉菜单
lv_obj_t * dropdown = lv_dropdown_create(lv_screen_active());
lv_obj_align(dropdown, LV_ALIGN_CENTER, 0, 120); // 屏幕更下方
lv_obj_set_size(dropdown, 150, 40);
lv_dropdown_set_options(dropdown, "Option 1\nOption 2\nOption 3");
lv_obj_add_event_cb(dropdown, dropdown_event_handler, LV_EVENT_VALUE_CHANGED, NULL);

// 创建复选框
lv_obj_t * checkbox = lv_checkbox_create(lv_screen_active());
lv_obj_align(checkbox, LV_ALIGN_CENTER, 0, 160); // 屏幕底部
lv_checkbox_set_text(checkbox, "Enable Feature");
lv_obj_add_event_cb(checkbox, checkbox_event_handler, LV_EVENT_ALL, NULL);
}

// 关闭显示屏(息屏)
void lv_disp_off() {
digitalWrite(TFT_BL, LOW); // 关闭背光
}

// 开启显示屏
void lv_disp_on() {
digitalWrite(TFT_BL, HIGH); // 开启背光
}

void setup() {
// 初始化背光控制引脚
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, HIGH); // 默认开启背光

// 输出LVGL版本信息
String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.begin(115200);
Serial.println(LVGL_Arduino);

// 初始化LVGL核心
lv_init();
lv_log_register_print_cb(log_print); // 注册日志回调

// 初始化触摸屏SPI通信
touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
touchscreen.begin(touchscreenSPI);
touchscreen.setRotation(2); // 设置旋转方向(根据实际屏幕调整)

// 初始化显示屏
TFT_eSPI tft = TFT_eSPI(SCREEN_WIDTH, SCREEN_HEIGHT);
tft.begin();
tft.setRotation(3); // 设置旋转方向(需与LVGL配置一致)

// 创建LVGL显示设备
lv_display_t * disp;
disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270); // 设置旋转

// 创建输入设备(触摸屏)
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, touchscreen_read); // 注册触摸读取回调

// 创建主界面
lv_create_main_gui();
}

void loop() {
// LVGL任务处理(必须周期性调用)
lv_task_handler();
lv_tick_inc(5); // 增加系统时钟(5ms)
delay(5); // 短暂延时

// 息屏逻辑处理
unsigned long currentTime = millis();
if (currentTime - lastTouchTime > idleTimeout) {
lv_disp_off(); // 超时息屏
} else {
lv_disp_on(); // 有操作时保持亮屏
}
}

上传编译成功后(要花很长时间),你能看到屏幕上有相关的组件显示,你可以试着交互一下,如果没报错说明运行成功。

接着我们进阶一下,ESP32-2432S028上有一个RGB灯珠,我们可以通过屏幕上的界面中的相关组件来控制这个RGB灯珠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#include <lvgl.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>

// 触摸屏引脚定义
#define XPT2046_IRQ 36
#define XPT2046_MOSI 32
#define XPT2046_MISO 39
#define XPT2046_CLK 25
#define XPT2046_CS 33

// 显示屏引脚定义
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST -1 // 未连接
#define TFT_BL 21 // 背光控制

// RGB LED引脚定义
const int redPin = 4;
const int greenPin = 16;
const int bluePin = 17;

SPIClass touchscreenSPI = SPIClass(VSPI);
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);

unsigned long lastTouchTime = 0;
const unsigned long idleTimeout = 25000; // 25秒无操作息屏

#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320

int x, y, z;

#define DRAW_BUF_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];

void log_print(lv_log_level_t level, const char * buf) {
LV_UNUSED(level);
Serial.println(buf);
Serial.flush();
}

void touchscreen_read(lv_indev_t * indev, lv_indev_data_t * data) {
if(touchscreen.tirqTouched() && touchscreen.touched()) {
TS_Point p = touchscreen.getPoint();
// 根据实际屏幕旋转调整坐标映射
x = map(p.x, 200, 3700, 0, SCREEN_WIDTH);
y = map(p.y, 240, 3800, 0, SCREEN_HEIGHT);
z = p.z;

data->state = LV_INDEV_STATE_PRESSED;
data->point.x = x;
data->point.y = y;

Serial.print("X = ");
Serial.print(x);
Serial.print(" | Y = ");
Serial.print(y);
Serial.print(" | Pressure = ");
Serial.print(z);
Serial.println();
lastTouchTime = millis();
}
else {
data->state = LV_INDEV_STATE_RELEASED;
}
}

// RGB滑条相关变量
static lv_obj_t * r_slider;
static lv_obj_t * g_slider;
static lv_obj_t * b_slider;
static lv_obj_t * rgb_label;

// RGB值存储变量
static uint8_t r_value = 0;
static uint8_t g_value = 0;
static uint8_t b_value = 0;

// RGB滑条事件回调函数
static void rgb_slider_event_callback(lv_event_t * e) {
lv_obj_t * slider = (lv_obj_t*) lv_event_get_target(e);

// 更新RGB值
if (slider == r_slider) {
r_value = lv_slider_get_value(slider);
} else if (slider == g_slider) {
g_value = lv_slider_get_value(slider);
} else if (slider == b_slider) {
b_value = lv_slider_get_value(slider);
}

// 设置RGB LED的颜色
analogWrite(redPin, r_value);
analogWrite(greenPin, g_value);
analogWrite(bluePin, b_value);

// 更新标签文本
char buf[32];
lv_snprintf(buf, sizeof(buf), "RGB: (%d, %d, %d)", r_value, g_value, b_value);
lv_label_set_text(rgb_label, buf);
}

// 创建RGB控制GUI
void lv_create_rgb_gui(void) {
// 创建RGB标签
rgb_label = lv_label_create(lv_screen_active());
lv_label_set_text(rgb_label, "RGB: (0, 0, 0)");
lv_obj_align(rgb_label, LV_ALIGN_TOP_MID, 0, 10); // 使用 LV_ALIGN_TOP_MID

// 创建红色滑条
r_slider = lv_slider_create(lv_screen_active());
lv_obj_set_size(r_slider, 200, 30);
lv_obj_align(r_slider, LV_ALIGN_TOP_MID, 0, 50); // 使用 LV_ALIGN_TOP_MID
lv_slider_set_range(r_slider, 0, 255);
lv_obj_add_event_cb(r_slider, rgb_slider_event_callback, LV_EVENT_VALUE_CHANGED, NULL);

// 创建绿色滑条
g_slider = lv_slider_create(lv_screen_active());
lv_obj_set_size(g_slider, 200, 30);
lv_obj_align(g_slider, LV_ALIGN_TOP_MID, 0, 120); // 使用 LV_ALIGN_TOP_MID
lv_slider_set_range(g_slider, 0, 255);
lv_obj_add_event_cb(g_slider, rgb_slider_event_callback, LV_EVENT_VALUE_CHANGED, NULL);

// 创建蓝色滑条
b_slider = lv_slider_create(lv_screen_active());
lv_obj_set_size(b_slider, 200, 30);
lv_obj_align(b_slider, LV_ALIGN_TOP_MID, 0, 190); // 使用 LV_ALIGN_TOP_MID
lv_slider_set_range(b_slider, 0, 255);
lv_obj_add_event_cb(b_slider, rgb_slider_event_callback, LV_EVENT_VALUE_CHANGED, NULL);
}

void lv_disp_off() {
// 关闭背光
digitalWrite(TFT_BL, LOW);
}

void lv_disp_on() {
// 开启背光
digitalWrite(TFT_BL, HIGH);
}

void setup() {
// 初始化背光引脚
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, HIGH); // 默认开启背光

// 初始化RGB LED引脚
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);

String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.begin(115200);
Serial.println(LVGL_Arduino);

lv_init();
lv_log_register_print_cb(log_print);

// 初始化触摸屏SPI
touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
touchscreen.begin(touchscreenSPI);
touchscreen.setRotation(2); // 根据实际屏幕方向调整

// 初始化显示屏
TFT_eSPI tft = TFT_eSPI(SCREEN_WIDTH, SCREEN_HEIGHT);
tft.begin();
tft.setRotation(3); // 根据实际屏幕方向调整

lv_display_t * disp;
disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);

lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, touchscreen_read);

lv_create_rgb_gui();
}

void loop() {
lv_task_handler();
lv_tick_inc(5);
delay(5);

unsigned long currentTime = millis();
if (currentTime - lastTouchTime > idleTimeout) {
// 执行息屏操作
lv_disp_off();
} else {
// 如果有触摸操作,保持屏幕亮起
lv_disp_on();
}
}

程序运行后,你可以在屏幕上看到三个滑条组件,它们分别控制着RGB灯珠的红、绿、蓝三种颜色的比例,通过调节滑条,你可以让RGB灯珠显示不同的颜色。

接下来,我们尝试让屏幕显示一张图片。首先你得要准备一张合适大小的图片,然后打开LVGL官网提供的一个图像转换工具,将图片上传,选择ARGB8888颜色格式,最后点击转换,你将会得到一个.c文件,将其打开,找到const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST……一行,你会在后面的{}内发现有一个由超多十六进制字符。接着,你需要在你的这个项目的文件夹里创建一个名为image.h的文件,并在里面加入以下代码(里面有些地方需要你手动替换或修改):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifdef __has_include
#if __has_include("lvgl.h")
#ifndef LV_LVGL_H_INCLUDE_SIMPLE
#define LV_LVGL_H_INCLUDE_SIMPLE
#endif
#endif
#endif

#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif

#ifndef LV_ATTRIBUTE_MEM_ALIGN
#define LV_ATTRIBUTE_MEM_ALIGN
#endif

#ifndef LV_ATTRIBUTE_IMAGE_GAP
#define LV_ATTRIBUTE_IMAGE_GAP
#endif
const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMAGE_GAP uint8_t gap_map[] = { <添加十六进制字符> };

const lv_image_dsc_t gap = {
.header = {
.magic = LV_IMAGE_HEADER_MAGIC,
.cf = LV_COLOR_FORMAT_ARGB8888,
.flags = 0,
.w = 320, // 宽度
.h = 240, // 高度
//.stride = 120,
.reserved_2 = 0
},
.data_size = sizeof(gap_map),
.data = gap_map,
.reserved = NULL
};

保存后,在你的项目代码里写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 引入库文件
#include <lvgl.h> // LVGL图形库
#include <TFT_eSPI.h> // TFT显示屏驱动
#include "image.h" // 图片资源头文件

// 屏幕参数
#define SCREEN_WIDTH 240 // 屏幕宽度
#define SCREEN_HEIGHT 320 // 屏幕高度

// 绘图缓冲区(屏幕大小的1/10)
#define DRAW_BUF_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4]; // 分配缓冲区

// 日志打印函数(调试用)
void log_print(lv_log_level_t level, const char * buf) {
LV_UNUSED(level);
Serial.println(buf);
Serial.flush();
}

// 显示图片(测试用)
void draw_image(void) {
LV_IMAGE_DECLARE(gap); // 声明图片资源
lv_obj_t * img1 = lv_image_create(lv_screen_active()); // 创建图片对象
lv_image_set_src(img1, &gap); // 设置图片源
lv_obj_align(img1, LV_ALIGN_CENTER, 0, 0); // 居中对齐
}

void setup() {
// 初始化串口并打印LVGL版本
Serial.begin(115200);
Serial.println(String("LVGL Version: ") + lv_version_major() + "." +
lv_version_minor() + "." + lv_version_patch());

// 初始化LVGL
lv_init();
lv_log_register_print_cb(log_print); // 注册日志回调

// 初始化显示屏
lv_display_t * disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270); // 旋转屏幕

draw_image(); // 显示测试图片
}

void loop() {
lv_task_handler(); // 处理LVGL任务
lv_tick_inc(5); // 更新系统时间(5ms)
delay(5); // 延时匹配
}

编译上传后,应该就能看到图片了。如果出现问题,你的从转换格式是否正确、图片的宽高是否匹配、代码其它地方是否被修改入手。

未完待续……