网络卡顿无法加载图片请访问备用网址

Micropython+esp32 (flowus.cn)

描述

之前做物联网项目使用了 micropython+esp32 ,现在手里刚到了新的板子和一批外设,想过一遍例程 ,再转入espidf深入学习。

教程

it项目网www.itprojects.cn

Python+ESP32 快速上手(持续更新中) wifi 蓝牙 智能控制 单片机_哔哩哔哩_bilibili

esp 32 电路图

Schematic Prints (itprojects.cn)

esp332 技术规格书

esp_wroom_32_datasheet_cn.pdf (itprojects.cn)

开发环境搭建

1. Thonny 下载

https://thonny.org/

2. micropython 下载

下载MicroPython

3. 驱动下载

https://doc.itprojects.cn/0006.zhishi.esp32/01.download/esp32usbDriver.zip

4. 烧录MicroPython到EPS32

运行-配置解释器-选择串口,点击 intall or update micropython ,找到固件 esp32-2022xxx.bin ,安装。

image-20230723235919734

不要直接关闭配置解释器界面,回到配置解释界面,不然相当于没选串口了,点击“好的” ,成功运行

image-20230724000413979命令行界面如下显示连接成功

image-20230724000457492

点亮 LED 灯

查看电路图观察灯与 LED 的连接管脚

image-20230724001005944

转到 Micropython 官方文档,查找怎么设置电平

Quick reference for the ESP32 — MicroPython latest documentation

文档位置Quick reference for the ESP32 — MicroPython latest documentation

image-20230724001607339

1
2
3
4
5
from machine import Pin

pin2 = Pin(2, Pin.OUT)
pin2.value(1)

成功点亮蓝灯 ,效果如下:

image-20230724001416328

PWM 呼吸灯

PWM(Pulse Width Modulation)简称脉宽调制

占空比越高灯越亮,循环改变占空比实现“呼吸”。

MicroPython官方文档:http://docs.micropython.org/en/latest/esp32/quickref.html

使用了machine 的 PWM 类

官方例程

1
2
3
4
5
6
7
8
from machine import Pin, PWM

pwm0 = PWM(Pin(0)) # create PWM object from a pin
freq = pwm0.freq() # get current frequency (default 5kHz)
pwm0.freq(1000) # set PWM frequency from 1Hz to 40MHz

duty = pwm0.duty() # get current duty cycle, range 0-1023 (default 512, 50%)
pwm0.duty(256) # set duty cycle from 0 to 1023 as a ratio duty/1023, (now 25%)

实例

亮度

1
2
3
4
5
6
7
from machine import Pin, PWM
import time


led2 = PWM(Pin(2))
led2.freq(1000)
led2.duty(100)

呼吸灯

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


led2 = PWM(Pin(2))
led2.freq(1000)


while True:
for i in range(0, 1024):
led2.duty(i)
time.sleep_ms(1)

for i in range(1023, -1, -1):
led2.duty(i)
time.sleep_ms(1)

呼吸灯效果如下:

QQ图片20230724002828

链接 Wifi

Wi-Fi网络环境通常有两种设备

  • Access Point(AP) 无线接入点,提供无线接入的设备,家里的光猫就是结合WiFi和internet路由功能的AP。AP和AP可以相互连接。
  • Station(STA) 无线终端,连接到AP的装置,手机,电脑等需要联网的设备都是出于STA模式,这个模式不允许其他设备接入

官方示例

1
2
3
4
5
6
7
8
9
import network

wlan = network.WLAN(network.STA_IF) # create station interface
wlan.active(True) # activate the interface
wlan.scan() # scan for access points
wlan.isconnected() # check if the station is connected to an AP
wlan.connect('essid', 'password') # connect to an AP
wlan.config('mac') # get the interface's MAC address
wlan.ifconfig() # get the interface's IP/netmask/gw/DNS addresses

实例

封装成函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import network
import time
ssid = "Mr.CARD" # wifi 名称
password = "88888888" # wifi 密码

def do_connect():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
wlan.connect(ssid, password)
i = 1
while not wlan.isconnected():
print("正在链接...{}".format(i))
i += 1
time.sleep(1)
print('network config:', wlan.ifconfig())
do_connect()

局域网UDP链接

在连接 wifi 后测试局域网 UDP 通信

ESP32 发送 “hello world ” 给PC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from socket import *

# 1. 创建udp套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)

# 2. 准备接收方的地址
dest_addr = ('192.168.31.56', 8080)

# 3. 从键盘获取数据
send_data = "hello world"

# 4. 发送数据到指定的电脑上
udp_socket.sendto(send_data.encode('utf-8'), dest_addr)

# 5. 关闭套接字
udp_socket.close()

最终效果

image-20230725010217693

如果出现UDP 打不开的情况,找到设备管理器-网络适配器,查看是否开启虚拟机网卡或 LAN ,禁用

image-20230725012146517

PC 发送 “nice to meey you ” 给 ESP32

建立UDP广播后可以与PC 进行 UDP 通信,注意先把上述代码第 5 步注释掉 ,再重新运行。

1
recv_data = udp_socket.recvfrom(1024)

注意远程主机填写,点击清除后复制 ESP32 的 IP 和端口

image-20230725011852003

最终效果

image-20230725011719057

PC 控制 LED 灯

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
# 整体流程
# 1. 链接wifi
# 2. 启动网络功能(UDP)
# 3. 接收网络数据
# 4. 处理接收的数据


import socket
import time
import network
import machine


def do_connect():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
wlan.connect('Mr.CARD', '88888888')
i = 1
while not wlan.isconnected():
print("正在链接...{}".format(i))
i += 1
time.sleep(1)
print('network config:', wlan.ifconfig())


def start_udp():
# 2. 启动网络功能(UDP)

# 2.1. 创建udp套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 2.2. 绑定本地信息
udp_socket.bind(("0.0.0.0", 7788))

# 2.3 准备接收方的地址
dest_addr = ('192.168.126.156', 8080)

# 2.4 从键盘获取数据
send_data = "hello world"

# 2.5 发送数据到指定的电脑上
udp_socket.sendto(send_data.encode('utf-8'), dest_addr)

return udp_socket


def main():
# 1. 链接wifi
do_connect()
# 2. 创建UDP
udp_socket = start_udp()
# 3. 创建灯对象
led = machine.Pin(2, machine.Pin.OUT)
# 4. 接收网络数据
while True:

recv_data, sender_info = udp_socket.recvfrom(1024)
print("{}发送{}".format(sender_info, recv_data))
recv_data_str = recv_data.decode("utf-8")
try:
print(recv_data_str)
except Exception as ret:
print("error:", ret)

# 5. 处理接收的数据
if recv_data_str == "light on":
print("这里是要灯亮的代码...")
led.value(1)
elif recv_data_str == "light off":
print("这里是要灯灭的代码...")
led.value(0)


if __name__ == "__main__":
main()



第 35 行 #2.2 udp_socket.bind(("0.0.0.0", 7788)) 在 esp32 上绑定端口,不然每次 UDP 广播时使用的端口都不一样,造成网络调试器每次要发送信息给 esp32 时都要修改端口

实例

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
# 整体流程
# 1. 链接wifi
# 2. 启动网络功能(UDP)
# 3. 接收网络数据
# 4. 处理接收的数据


import socket
import time
import network
import machine


def do_connect():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
wlan.connect('Mr.CARD', '88888888')
i = 1
while not wlan.isconnected():
print("正在链接...{}".format(i))
i += 1
time.sleep(1)
print('network config:', wlan.ifconfig())


def start_udp():
# 2. 启动网络功能(UDP)

# 2.1. 创建udp套接字

udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2.2. 绑定本地信息
udp_socket.bind(("0.0.0.0", 7788))

# 2.3 准备接收方的地址
dest_addr = ('192.168.126.156', 8080)


# 2.4 从键盘获取数据
send_data = "hello world"

# 2.5 发送数据到指定的电脑上
udp_socket.sendto(send_data.encode('utf-8'), dest_addr)

return udp_socket


def main():
# 1. 链接wifi
do_connect()
# 2. 创建UDP
udp_socket = start_udp()
# 3. 创建灯对象
led = machine.Pin(2, machine.Pin.OUT)
# 4. 接收网络数据
while True:

recv_data, sender_info = udp_socket.recvfrom(1024)
print("{}发送{}".format(sender_info, recv_data))
recv_data_str = recv_data.decode("utf-8")
try:
print(recv_data_str)
except Exception as ret:
print("error:", ret)

# 5. 处理接收的数据
if recv_data_str == "light on":
print("这里是要灯亮的代码...")
led.value(1)
elif recv_data_str == "light off":
print("这里是要灯灭的代码...")
led.value(0)


if __name__ == "__main__":
main()

最终效果

image-20230726013403379
image-20230726013433213

数码管显示

面包板使用

image-20230726014805551

共阴数码管

image-20230726014835104
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
import machine
import time


a = machine.Pin(13, machine.Pin.OUT)
b = machine.Pin(12, machine.Pin.OUT)
c = machine.Pin(14, machine.Pin.OUT)
d = machine.Pin(27, machine.Pin.OUT)
e = machine.Pin(26, machine.Pin.OUT)
f = machine.Pin(25, machine.Pin.OUT)
g = machine.Pin(33, machine.Pin.OUT)
dot = machine.Pin(32, machine.Pin.OUT)

number_led = [a, b, c, d, e, f, g, dot]

number_dict = {
0: "11111100",
1: "01100000",
2: "11011010",
3: "11110010",
4: "01100110",
5: "10110110",
6: "10111110",
7: "11100000",
8: "11111110",
9: "11110110",
"open": "11111111",
"close": "00000000"
}

def show_number(number):
if number_dict.get(number):
i = 0
for bit in number_dict.get(number):
if bit == "1":
number_led[i].value(1)
else:
number_led[i].value(0)
i += 1

def main():
# show_number("open") # 全亮
# show_number("close") # 全灭
while True:
for i in range(10):
show_number(i)
time.sleep(0.6)
print(i)

if __name__ == "__main__":
main()

32 行

if number_dict.get(number): 判断列表里是否有该索引的值

34 ~39 行

for bit in number_dict.get(number):

bit 是具体位的值(0/1),i 是 第 i 位(0~7)

问题

1、 数码管不亮:限流电阻过大

2、有的数字显示缺笔画: 电阻没插好或电阻引脚碰撞

最终效果

QQ图片20230726024609

四位数数码管显示

四个七段数码管共用 a~h ,因此只能同时有一位有效

这个代码通过循环位选操作,循环点亮四个位

第一次运行例程发现显示乱码,发现原作者所用的是共阳极数码管,而自己手里的四位数码管是共阴极,低电平有效.因此需要将 value(0) 、value(1) 交换

实例

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
import machine
import time


led1 = machine.Pin(5, machine.Pin.OUT)
led2 = machine.Pin(18, machine.Pin.OUT)
led3 = machine.Pin(19, machine.Pin.OUT)
led4 = machine.Pin(21, machine.Pin.OUT)

number_led_list = [led1, led2, led3, led4]


a = machine.Pin(13, machine.Pin.OUT)
b = machine.Pin(12, machine.Pin.OUT)
c = machine.Pin(14, machine.Pin.OUT)
d = machine.Pin(27, machine.Pin.OUT)
e = machine.Pin(26, machine.Pin.OUT)
f = machine.Pin(25, machine.Pin.OUT)
g = machine.Pin(33, machine.Pin.OUT)
h = machine.Pin(32, machine.Pin.OUT)

# 将对应的引脚对象存储到列表
led_list = [a, b, c, d, e, f, g, h]

number_dict = {
0: "11111100",
1: "01100000",
2: "11011010",
3: "11110010",
4: "01100110",
5: "10110110",
6: "10111110",
7: "11100000",
8: "11111110",
9: "11110110",
}



def show_number(number):
if number_dict.get(number):
i = 0
for num in number_dict.get(number): # 此时就是对应的value,即类似的"01110111"
if num == "1":
led_list[i].value(1)
else:
led_list[i].value(0)
i += 1

def led_light_on(i):
# 全部设置为0
for led in number_led_list:
led.value(1)

number_led_list[i].value(0)


def show_4_number(number):
if 0 <= number <= 9999:
i = 0
for num in "%04d" % number:
led_light_on(i)
print("i=",i)
print(num)
show_number(int(num))
time.sleep_ms(10)
i += 1

while True:

for i in range(1234, 10000):d
for j in range(10):
show_4_number(i)



66 行的延时是用来确定频闪

72 循环是用来校正时间间隔为1s左右

计数间隔是由这两个循环的延时积得到

最终效果

QQ图片20230728204255

1602LCD与I2C

image-20230728214247443

1602LCD

1602LCD是指显示的内容为16X2,即可以显示两行,每行16个字符液晶模块(显示字符和数字)

市面上字符液晶大多数是基于HD44780液晶芯片的

I2C

I2C(Inter-Integrated Circuit),中文叫集成电路总线,它是一种串行通信总线,使用多主从架构,是由飞利浦公司在1980年代初设计的,方便了主板、嵌入式系统或手机与周边设备组件之间的通讯。由于其简单性,它被广泛用于微控制器与传感器阵列,显示器,IoT设备,EEPROM等之间的通信。

最重要的功能包括:

只需要两条总线 所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一地址进行软件寻址 I2C是真正的多主设备总线,可提供仲裁和冲突检测 传输速度 标准模式:Standard Mode = 100 Kbps 快速模式:Fast Mode = 400 Kbps 高速模式: High speed mode = 3.4 Mbps 超快速模式: Ultra fast mode = 5 Mbps 最大主设备数:无限制 最大从机数:理论上是127

大白话:一种只用2根线就可以传递很多数据给其它多台设备的方法,这种方法叫I2C

大大减少了MCU上IO的使用

PCF8574

芯片简介之PCF8574

PCF8574 是 NXP公司生产的一款芯片。该芯片可以通过IIC协议进行IO口扩展。每个IO口可以单独的分配为输入或者输出。例如,作为输入时,可以用于监控中断或者键盘。作为输出时,可以用于点亮发光二极管。系统控制器可以通过单独的寄存器读取输入端口状态或者配置输出端口状态。

因为该芯片具有三个地址管脚,也就是最多可以同时分配8个地址。所以,在同一个系统中,最多可以通过16个管脚拓展出128个IO口。此外,该芯片还提供一个中断管脚(低电平有效)。当任何一个输入管脚和其对应的寄存器状态不同时,触发该中断。

为了节省电能,PCF8574内部没有在地址管脚放置上拉电阻,因此,这几个地址必须外部置高或者置低。这些地址管脚可以直连或者通过一个电阻连接到VCC或者GND。

Micropython官方文档(IIC)

Quick reference for the ESP32 — MicroPython latest documentation

需要的api、驱动模块

  • lcd_api.py:https://doc.itprojects.cn/0006.zhishi.esp32/01.download/lcd_api.py
  • esp32_i2c_1602lcd.py:https://doc.itprojects.cn/0006.zhishi.esp32/01.download/esp32_i2c_1602lcd.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
"""Provides an API for talking to HD44780 compatible character LCDs."""

import time

class LcdApi:
"""Implements the API for talking with HD44780 compatible character LCDs.
This class only knows what commands to send to the LCD, and not how to get
them to the LCD.

It is expected that a derived class will implement the hal_xxx functions.
"""

# The following constant names were lifted from the avrlib lcd.h
# header file, however, I changed the definitions from bit numbers
# to bit masks.
#
# HD44780 LCD controller command set

LCD_CLR = 0x01 # DB0: clear display
LCD_HOME = 0x02 # DB1: return to home position

LCD_ENTRY_MODE = 0x04 # DB2: set entry mode
LCD_ENTRY_INC = 0x02 # --DB1: increment
LCD_ENTRY_SHIFT = 0x01 # --DB0: shift

LCD_ON_CTRL = 0x08 # DB3: turn lcd/cursor on
LCD_ON_DISPLAY = 0x04 # --DB2: turn display on
LCD_ON_CURSOR = 0x02 # --DB1: turn cursor on
LCD_ON_BLINK = 0x01 # --DB0: blinking cursor

LCD_MOVE = 0x10 # DB4: move cursor/display
LCD_MOVE_DISP = 0x08 # --DB3: move display (0-> move cursor)
LCD_MOVE_RIGHT = 0x04 # --DB2: move right (0-> left)

LCD_FUNCTION = 0x20 # DB5: function set
LCD_FUNCTION_8BIT = 0x10 # --DB4: set 8BIT mode (0->4BIT mode)
LCD_FUNCTION_2LINES = 0x08 # --DB3: two lines (0->one line)
LCD_FUNCTION_10DOTS = 0x04 # --DB2: 5x10 font (0->5x7 font)
LCD_FUNCTION_RESET = 0x30 # See "Initializing by Instruction" section

LCD_CGRAM = 0x40 # DB6: set CG RAM address
LCD_DDRAM = 0x80 # DB7: set DD RAM address

LCD_RS_CMD = 0
LCD_RS_DATA = 1

LCD_RW_WRITE = 0
LCD_RW_READ = 1

def __init__(self, num_lines, num_columns):
self.num_lines = num_lines
if self.num_lines > 4:
self.num_lines = 4
self.num_columns = num_columns
if self.num_columns > 40:
self.num_columns = 40
self.cursor_x = 0
self.cursor_y = 0
self.backlight = True
self.display_off()
self.backlight_on()
self.clear()
self.hal_write_command(self.LCD_ENTRY_MODE | self.LCD_ENTRY_INC)
self.hide_cursor()
self.display_on()

def clear(self):
"""Clears the LCD display and moves the cursor to the top left
corner.
"""
self.hal_write_command(self.LCD_CLR)
self.hal_write_command(self.LCD_HOME)
self.cursor_x = 0
self.cursor_y = 0

def show_cursor(self):
"""Causes the cursor to be made visible."""
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |
self.LCD_ON_CURSOR)

def hide_cursor(self):
"""Causes the cursor to be hidden."""
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)

def blink_cursor_on(self):
"""Turns on the cursor, and makes it blink."""
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |
self.LCD_ON_CURSOR | self.LCD_ON_BLINK)

def blink_cursor_off(self):
"""Turns on the cursor, and makes it no blink (i.e. be solid)."""
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |
self.LCD_ON_CURSOR)

def display_on(self):
"""Turns on (i.e. unblanks) the LCD."""
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)

def display_off(self):
"""Turns off (i.e. blanks) the LCD."""
self.hal_write_command(self.LCD_ON_CTRL)

def backlight_on(self):
"""Turns the backlight on.

This isn't really an LCD command, but some modules have backlight
controls, so this allows the hal to pass through the command.
"""
self.backlight = True
self.hal_backlight_on()

def backlight_off(self):
"""Turns the backlight off.

This isn't really an LCD command, but some modules have backlight
controls, so this allows the hal to pass through the command.
"""
self.backlight = False
self.hal_backlight_off()

def move_to(self, cursor_x, cursor_y):
"""Moves the cursor position to the indicated position. The cursor
position is zero based (i.e. cursor_x == 0 indicates first column).
"""
self.cursor_x = cursor_x
self.cursor_y = cursor_y
addr = cursor_x & 0x3f
if cursor_y & 1:
addr += 0x40 # Lines 1 & 3 add 0x40
if cursor_y & 2:
addr += 0x14 # Lines 2 & 3 add 0x14
self.hal_write_command(self.LCD_DDRAM | addr)

def putchar(self, char):
"""Writes the indicated character to the LCD at the current cursor
position, and advances the cursor by one position.
"""
if char != '\n':
self.hal_write_data(ord(char))
self.cursor_x += 1
if self.cursor_x >= self.num_columns or char == '\n':
self.cursor_x = 0
self.cursor_y += 1
if self.cursor_y >= self.num_lines:
self.cursor_y = 0
self.move_to(self.cursor_x, self.cursor_y)

def putstr(self, string):
"""Write the indicated string to the LCD at the current cursor
position and advances the cursor position appropriately.
"""
for char in string:
self.putchar(char)

def custom_char(self, location, charmap):
"""Write a character to one of the 8 CGRAM locations, available
as chr(0) through chr(7).
"""
location &= 0x7
self.hal_write_command(self.LCD_CGRAM | (location << 3))
time.sleep_us(40)
for i in range(8):
self.hal_write_data(charmap[i])
time.sleep_us(40)
self.move_to(self.cursor_x, self.cursor_y)

def hal_backlight_on(self):
"""Allows the hal layer to turn the backlight on.

If desired, a derived HAL class will implement this function.
"""
pass

def hal_backlight_off(self):
"""Allows the hal layer to turn the backlight off.

If desired, a derived HAL class will implement this function.
"""
pass

def hal_write_command(self, cmd):
"""Write a command to the LCD.

It is expected that a derived HAL class will implement this
function.
"""
raise NotImplementedError

def hal_write_data(self, data):
"""Write data to the LCD.

It is expected that a derived HAL class will implement this
function.
"""
raise NotImplementedError
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
"""Implements a HD44780 character LCD connected via PCF8574 on I2C.
This was tested with: https://www.wemos.cc/product/d1-mini.html"""

from lcd_api import LcdApi
from machine import I2C
from time import sleep_ms

# Defines shifts or masks for the various LCD line attached to the PCF8574

MASK_RS = 0x01
MASK_RW = 0x02
MASK_E = 0x04
SHIFT_BACKLIGHT = 3
SHIFT_DATA = 4


class I2cLcd(LcdApi):
"""Implements a HD44780 character LCD connected via PCF8574 on I2C."""

def __init__(self, i2c, i2c_addr, num_lines, num_columns):
self.i2c = i2c
self.i2c_addr = i2c_addr
self.i2c.writeto(self.i2c_addr, bytearray([0]))
sleep_ms(20) # Allow LCD time to powerup
# Send reset 3 times
self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
sleep_ms(5) # need to delay at least 4.1 msec
self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
sleep_ms(1)
self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
sleep_ms(1)
# Put LCD into 4 bit mode
self.hal_write_init_nibble(self.LCD_FUNCTION)
sleep_ms(1)
LcdApi.__init__(self, num_lines, num_columns)
cmd = self.LCD_FUNCTION
if num_lines > 1:
cmd |= self.LCD_FUNCTION_2LINES
self.hal_write_command(cmd)

def hal_write_init_nibble(self, nibble):
"""Writes an initialization nibble to the LCD.

This particular function is only used during initialization.
"""
byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
self.i2c.writeto(self.i2c_addr, bytearray([byte]))

def hal_backlight_on(self):
"""Allows the hal layer to turn the backlight on."""
self.i2c.writeto(self.i2c_addr, bytearray([1 << SHIFT_BACKLIGHT]))

def hal_backlight_off(self):
"""Allows the hal layer to turn the backlight off."""
self.i2c.writeto(self.i2c_addr, bytearray([0]))

def hal_write_command(self, cmd):
"""Writes a command to the LCD.

Data is latched on the falling edge of E.
"""
byte = ((self.backlight << SHIFT_BACKLIGHT) | (((cmd >> 4) & 0x0f) << SHIFT_DATA))
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
byte = ((self.backlight << SHIFT_BACKLIGHT) | ((cmd & 0x0f) << SHIFT_DATA))
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
if cmd <= 3:
# The home and clear commands require a worst case delay of 4.1 msec
sleep_ms(5)

def hal_write_data(self, data):
"""Write data to the LCD."""
byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | (((data >> 4) & 0x0f) << SHIFT_DATA))
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | ((data & 0x0f) << SHIFT_DATA))
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
self.i2c.writeto(self.i2c_addr, bytearray([byte]))

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import time
from machine import SoftI2C, Pin
from esp32_i2c_1602lcd import I2cLcd


DEFAULT_I2C_ADDR = 0x27
i2c = SoftI2C(sda=Pin(15),scl=Pin(2),freq=100000)
lcd = I2cLcd(i2c, DEFAULT_I2C_ADDR, 2, 16)

for i in range(1, 100):
lcd.clear()
lcd.putstr("loading...{}\n".format(i))
lcd.putstr("zoujiahua")
time.sleep(1)

# SDA GPIO15
# SCL GPIO2
# Vcc 5V (3V显示不清楚)
# GND GND


最终效果

image-20230728220949189

ST7789、240x240彩屏幕与SPI

240*240接口

image-20230728221151321

SPI

SPI使用四根线实现快速数据通信, 理论上,SPI是一种全双工的通讯协议

SPI通讯无起始位和停止位,因此数据可以连续流传输而不会中断;没有像I2C这样的复杂的从站寻址系统,数据传输速率比I2C更高(几乎快两倍)。独立的MISOMOSI线路,可以同时发送和接收数据。

SPI协议中,设备分为主机从机。主机是控制设备(通常是微控制器),而从机(通常是传感器,显示器或存储芯片)从主机那获取指令。

可以外接多个设备

其他制造商可能会遵循其他命名规则,但是最终他们指的相同的含义。以下是一些常用术语;

  • MISO也可以是SIMO,DOUT,DO,SDO或SO(在主机端);
  • MOSI也可以是SOMI,DIN,DI,SDI或SI(在主机端);
  • NSS也可以是CE,CS或SSEL;
  • SCLK也可以是SCK;
image-20230728222150040
image-20230728222330503

驱动下载

  • st7789py.py库:https://github.com/russhughes/st7789py_mpy

修复屏幕上述驱动不能显示的bug

  • 下载st7789.py文件:https://doc.itprojects.cn/0006.zhishi.esp32/01.download/st7789.py
  • st7789py.py文件中的 204205行 注释

代替方案

  • st7789.py:能够快速的驱动240x240屏幕,但功能少,不能显示图片
  • st7789py.py:能够显示图片、文字灯,但是每次重新上电后,屏幕容易不亮

综上所有,对st7789.py进行了升级,添加图形库功能,这样一来就可以用一个文件,删除st7789py.py即可

2.2 新st7789.py下载

新 st7789(推荐用)

https://doc.itprojects.cn/0006.zhishi.esp32/01.download/new/st7789_itprojects.py

旧st7789(弃用,原因:ST7789芯片更新后添加了伽马值,导致彩色失真)

https://doc.itprojects.cn/0006.zhishi.esp32/01.download/st7789_itprojects.py

st7789.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
import ustruct
import utime


_NOP = const(0x00)
_SWRESET = const(0x01)
_RDDID = const(0x04)
_RDDST = const(0x09)

_SLPIN = const(0x10)
_SLPOUT = const(0x11)
_PTLON = const(0x12)
_NORON = const(0x13)

_INVOFF = const(0x20)
_INVON = const(0x21)
_DISPOFF = const(0x28)
_DISPON = const(0x29)
_CASET = const(0x2A)
_RASET = const(0x2B)
_RAMWR = const(0x2C)
_RAMRD = const(0x2E)

_PTLAR = const(0x30)
_COLMOD = const(0x3A)
_MADCTL = const(0x36)

_FRMCTR1 = const(0xB1)
_FRMCTR2 = const(0xB2)
_FRMCTR3 = const(0xB3)
_INVCTR = const(0xB4)
_DISSET5 = const(0xB6)
_GCTRL = const(0xB7)
_VCOMS = const(0xBB)
_FRCTR2 = const(0xC6)
_D6H = const(0xD6)
_PWCTRL1 = const(0xD0)
_GATECTRL = const(0xE4)

_PWCTR1 = const(0xC0)
_PWCTR2 = const(0xC1)
_PWCTR3 = const(0xC2)
_PWCTR4 = const(0xC3)
_PWCTR5 = const(0xC4)
_VMCTR1 = const(0xC5)

_RDID1 = const(0xDA)
_RDID2 = const(0xDB)
_RDID3 = const(0xDC)
_RDID4 = const(0xDD)

_PWCTR6 = const(0xFC)

_GMCTRP1 = const(0xE0)
_GMCTRN1 = const(0xE1)


def color565(r, g, b):
return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3


class DummyPin:
"""A fake gpio pin for when you want to skip pins."""

OUT = 0
IN = 0
PULL_UP = 0
PULL_DOWN = 0
OPEN_DRAIN = 0
ALT = 0
ALT_OPEN_DRAIN = 0
LOW_POWER = 0
MED_POWER = 0
HIGH_PWER = 0
IRQ_FALLING = 0
IRQ_RISING = 0
IRQ_LOW_LEVEL = 0
IRQ_HIGH_LEVEL = 0

def __call__(self, *args, **kwargs):
return False

init = __call__
value = __call__
out_value = __call__
toggle = __call__
high = __call__
low = __call__
on = __call__
off = __call__
mode = __call__
pull = __call__
drive = __call__
irq = __call__


class Display:
_PAGE_SET = None
_COLUMN_SET = None
_RAM_WRITE = None
_RAM_READ = None
_INIT = ()
_ENCODE_PIXEL = ">H"
_ENCODE_POS = ">HH"
_DECODE_PIXEL = ">BBB"

def __init__(self, width, height):
self.width = width
self.height = height
self.init()

def init(self):
"""Run the initialization commands."""
for command, data in self._INIT:
self._write(command, data)

def _block(self, x0, y0, x1, y1, data=None):
"""Read or write a block of data."""
self._write(self._COLUMN_SET, self._encode_pos(x0, x1))
self._write(self._PAGE_SET, self._encode_pos(y0+80, y1+80))
if data is None:
size = ustruct.calcsize(self._DECODE_PIXEL)
return self._read(self._RAM_READ, (x1 - x0 + 1) * (y1 - y0 + 1) * size)

self._write(self._RAM_WRITE, data)

def _encode_pos(self, a, b):
"""Encode a postion into bytes."""
return ustruct.pack(self._ENCODE_POS, a, b)

def _encode_pixel(self, color):
"""Encode a pixel color into bytes."""
return ustruct.pack(self._ENCODE_PIXEL, color)

def _decode_pixel(self, data):
"""Decode bytes into a pixel color."""
return color565(*ustruct.unpack(self._DECODE_PIXEL, data))

def pixel(self, x, y, color=None):
"""Read or write a pixel."""
if color is None:
return self._decode_pixel(self._block(x, y, x, y))
if not 0 <= x < self.width or not 0 <= y < self.height:
return
self._block(x, y, x, y, self._encode_pixel(color))

def fill_rectangle(self, x, y, width, height, color):
"""Draw a filled rectangle."""
x = min(self.width - 1, max(0, x))
y = min(self.height - 1, max(0, y))
w = min(self.width - x, max(1, width))
h = min(self.height - y, max(1, height))
self._block(x, y, x + w - 1, y + h - 1, b'')
chunks, rest = divmod(w * h, 512)
print("color:", color)
pixel = self._encode_pixel(color)
print("decode:", pixel)
if chunks:
data = pixel * 512
for count in range(chunks):
self._write(None, data)
if rest:
self._write(None, pixel * rest)

def fill(self, color=0):
"""Fill whole screen."""
self.fill_rectangle(0, 0, self.width, self.height, color)

def hline(self, x, y, width, color):
"""Draw a horizontal line."""
self.fill_rectangle(x, y, width, 1, color)

def vline(self, x, y, height, color):
"""Draw a vertical line."""
self.fill_rectangle(x, y, 1, height, color)

def blit_buffer(self, buffer, x, y, width, height):
"""Copy pixels from a buffer."""
if (not 0 <= x < self.width or
not 0 <= y < self.height or
not 0 < x + width <= self.width or
not 0 < y + height <= self.height):
raise ValueError("out of bounds")
self._block(x, y, x + width - 1, y + height - 1, buffer)


class DisplaySPI(Display):
def __init__(self, spi, dc, cs=None, rst=None, width=1, height=1):
self.spi = spi
self.cs = cs
self.dc = dc
self.rst = rst
if self.rst is None:
self.rst = DummyPin()
if self.cs is None:
self.cs = DummyPin()
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()
super().__init__(width, height)

def reset(self):
self.rst(0)
utime.sleep_ms(50)
self.rst(1)
utime.sleep_ms(50)

def _write(self, command=None, data=None):
if command is not None:
self.dc(0)
self.cs(0)
self.spi.write(bytearray([command]))
self.cs(1)
if data:
self.dc(1)
self.cs(0)
self.spi.write(data)
self.cs(1)

def _read(self, command=None, count=0):
self.dc(0)
self.cs(0)
if command is not None:
self.spi.write(bytearray([command]))
if count:
data = self.spi.read(count)
self.cs(1)
return data


class ST7789(DisplaySPI):
"""
A simple driver for the ST7789-based displays.
>>> from machine import Pin, SPI
>>> import st7789
>>> display = st7789.ST7789(SPI(1), dc=Pin(12), cs=Pin(15), rst=Pin(16))
>>> display = st7789.ST7789R(SPI(1, baudrate=40000000), dc=Pin(12), cs=Pin(15), rst=Pin(16))
>>> display.fill(0x7521)
>>> display.pixel(64, 64, 0)
"""
_COLUMN_SET = _CASET
_PAGE_SET = _RASET
_RAM_WRITE = _RAMWR
_RAM_READ = _RAMRD
_INIT = (
(_SWRESET, None),
(_SLPOUT, None),
(_COLMOD, b"\x55"), # 16bit color
(_MADCTL, b"\x08"),
)

def __init__(self, spi, dc, cs, rst=None, width=240, height=240):
super().__init__(spi, dc, cs, rst, width, height)

def init(self):

super().init()
cols = ustruct.pack(">HH", 0, self.width)
rows = ustruct.pack(">HH", 0, self.height)
# ctr2p= ustruct.pack(">BBBBB", b"\x1F\x1F\x00\x33\x33")
ctr2p= b"\x1F\x1F\x00\x33\x33"
# ctr1p= ustruct.pack(">BB", b"\xA4\xA1")
ctr1p= b"\xA4\xA1"
# e0p= ustruct.pack(">BBBBBBBBBBBBBB", b"\xF0\x08\x0E\x09\x08\x04\x2F\x33\x45\x36\x13\x12\x2A\x2D")
e0p= b"\xF0\x08\x0E\x09\x08\x04\x2F\x33\x45\x36\x13\x12\x2A\x2D"
# e1p= ustruct.pack(">BBBBBBBBBBBBBB", b"\xF0\x0E\x12\x0C\x0A\x15\x2E\x32\x44\x39\x17\x18\x2B\x2F")
e1p= b"\xF0\x0E\x12\x0C\x0A\x15\x2E\x32\x44\x39\x17\x18\x2B\x2F"
# gatep= ustruct.pack(">BBB", b"\x1d\x00\x00")
gatep= b"\x1d\x00\x00"
for command, data in (
(_CASET, cols),
(_RASET, rows),
(_FRMCTR2,ctr2p),
(_GCTRL, b"\x00"),
(_VCOMS, b"\x36"),
(_PWCTR3, b"\x01"),
(_PWCTR4, b"\x13"),
(_PWCTR5, b"\x20"),
(_FRCTR2, b"\x13"),
(_D6H, b"\xA1"),
(_PWCTRL1, ctr1p),
(_GMCTRP1, e0p),
(_GMCTRN1, e1p),
(_GATECTRL, gatep),
(_INVON, None),
(_NORON, None),
(_DISPON, None),
(_MADCTL, b"\xc0"), # Set rotation to 0 and use RGB
):
self._write(command, data)


class ST7889_Image(ST7789):

def _set_columns(self, start, end):
if start <= end:
self._write(_CASET, self._encode_pos(start, end))

def _set_rows(self, start, end):
if start <= end:
self._write(_RASET, self._encode_pos(start, end))

def _set_window(self, x0, y0, x1, y1):
"""
x0锛� x璧峰浣嶇疆
y0: y璧峰浣嶇疆
x1: x缁撴潫浣嶇疆
y1: y缁撴潫浣嶇疆
"""
self._set_columns(x0, x1)
self._set_rows(y0, y1)
self._write(_RAMWR)

def show_img(self, x0, y0, x1, y1, img_data):
self._set_window(x0, y0 + 80, x1, y1 + 80)
self._write(None, img_data)

实例-打印 "hello "

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
import random
from machine import Pin, SPI
import ST7789 as st7789
import st7789py
from romfonts import vga2_bold_16x32 as font


# 解决第1次启动时,不亮的问题
st7789.ST7789(SPI(2, 60000000), dc=Pin(2), cs=Pin(5), rst=Pin(15))

# 创建显示屏对象
tft = st7789py.ST7789(SPI(2, 60000000), 240, 240, reset=Pin(15), dc=Pin(2), cs=Pin(5), rotation=0)

# 屏幕显示蓝色
tft.fill(0)

# 显示Hello
tft.text(font, "Hello", 0, 0, st7789py.color565(255, 0, 0), st7789py.color565(0, 0, 255))

"""
def show_text():
for rotation in range(4):
tft.rotation(rotation)
tft.fill(0)
col_max = tft.width - font.WIDTH*6
row_max = tft.height - font.HEIGHT

for _ in range(100):
tft.text(
font,
"Hello!",
random.randint(0, col_max),
random.randint(0, row_max),
st7789py.color565(random.getrandbits(8),random.getrandbits(8),random.getrandbits(8)),
st7789py.color565(random.getrandbits(8),random.getrandbits(8),random.getrandbits(8))
)

# 随机显示Hello!
# while True:
# show_text()
"""


最终效果-打印 "hello "

image-20230729002824921

实例-打印图片

由于图片过大,需要将图片转为 dat 格式 ,并上传 esp32

第一步, 在本地转化格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import struct
import numpy as np
from PIL import Image


def color565(r, g, b):
return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3


def main():
img = Image.open("test1.png")
print(img.format, img.size, img.mode)
img_data = np.array(img)

with open("text_img.dat", "wb") as f:
for line in img_data:
for dot in line:
f.write(struct.pack("H", color565(*dot))[::-1])


if __name__ == '__main__':
main()

第二步 , micropython 开启TCP 服务作为服务端

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
import time
import network
import machine
import socket


def do_connect():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
wlan.connect('dongfeiqiu', 'wangmingdong1225')
i = 1
while not wlan.isconnected():
print("姝e湪閾炬帴...{}".format(i))
i += 1
time.sleep(1)
print('network config:', wlan.ifconfig())


# 0. 连接wifi
do_connect()

# 1.建立 TCP 服务端对象
server_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 绑定本地信息
server_s.bind(("", 8080))

# 3. 最大接收
server_s.listen(128)

print("等待连接...")

# 4. 接收客户端身份信息
new_s, client_info = server_s.accept()

print("等待客户端请求...")

# 3. 鍒涘缓鏂囦欢锛屾帴鏀舵暟鎹�
with open("text_img.dat", "wb") as f:
for i in range(240):
# 3.1 就收dat
data = new_s.recv(480) # 240*2=480
# 3.2 写入写入
f.write(data)
print("接收第%d个数据" % (i+1))

print("接收完成")

# 7.关闭TCP 服务
new_s.close()
server_s.close()



第三步本地客户端上传转化后的数据给 esp32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from socket import *

# 1. 创建客户端
tcp_client_socket = socket(AF_INET, SOCK_STREAM)

# 2. 绑定服务端信息
tcp_client_socket.connect(("192.168.126.132", 8080))

# 2. 发送图片数据
with open("text_img.dat", "rb") as f:
for i in range(240):
# 3.1 读取
data = f.read(480)
# 3.2 发送
tcp_client_socket.send(data) # 240*2=480 涓€琛屾湁240涓偣锛屾瘡涓偣鏈�2涓瓧鑺�

print("发送第%d个数据" % (i + 1))
# time.sleep(0.5)

print("发送完成")

# 7. 关闭 TCP 客户端
tcp_client_socket.close()

第四步-esp32 显示图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from machine import Pin, SPI
import st7789 as st7789_itprojects


tft = st7789_itprojects.ST7889_Image(SPI(2, 60000000), dc=Pin(2), cs=Pin(5), rst=Pin(15))
tft.fill(st7789_itprojects.color565(0, 0, 0)) # 背景设置为黑色


def show_img():
with open("text_img.dat", "rb") as f:
for row in range(240):
buffer = f.read(480)
tft.show_img(0, row, 239, row, buffer)


show_img()


最终效果-打印图片

image-20230729001850387