一、目的
这一节我们来学习如何使用合宙ESP32 C3,连接0.96寸ssd1306屏幕、4x4 矩阵按键模块,进行两个小实验的演示。
二、环境
ESP32 C3开发板(MicroPython v1.19.1 on 2022-06-18)+ I2C 0.96寸ssd1306屏幕模块+ 4x4 矩阵按键模块 + 几根杜邦线 + Win10商业版
ESP32 C3和各个模块接线方法:
三、示例代码1
演示将按下的按键名字显示在屏幕上
from machine import Pin,I2C
from ssd1306 import SSD1306_I2C
from ufont import BMFont
import time
# 创建I2C对象
i2c = I2C(0,scl = Pin(5),sda = Pin(4),freq = 400_000)
# 创建oled屏幕对象
oled = SSD1306_I2C(128,64,i2c,0x3c)
# 定义字库文件
font = BMFont("fonts/unifont-14-12888-16.v3.bmf")
# 创建led对象
led = Pin(12,Pin.OUT) # 板载led D4
# 行定义
R1 = Pin( 2,Pin.OUT)
R2 = Pin( 3,Pin.OUT)
R3 = Pin(10,Pin.OUT)
R4 = Pin( 6,Pin.OUT)
# 列第一
C1 = Pin( 7,Pin.IN,Pin.PULL_UP)
C2 = Pin( 8,Pin.IN,Pin.PULL_UP)
C3 = Pin(18,Pin.IN,Pin.PULL_UP)
C4 = Pin(19,Pin.IN,Pin.PULL_UP)
# 创建按键函数
def Key():
# R1.value(0)
# R2.value(1)
# R3.value(1)
# R4.value(1)
key = 0
bzw = 0
# 第一行
[R1.value(0),R2.value(1),R3.value(1),R4.value(1)]
if(C1.value() == 0 or C2.value() == 0 or C3.value() == 0 or C4.value() == 0):
time.sleep(0.020)
if C1.value() == 0:
bzw = 1
key = 0
while not C1.value():
None
elif C2.value() == 0:
bzw = 1
key = 1
while not C2.value():
None
elif C3.value() == 0:
bzw = 1
key = 2
while not C3.value():
None
elif C4.value() == 0:
bzw = 1
key = 3
while not C4.value():
None
else:
key = 0
bzw = 0
R1.value(1)
# 第二行
[R1.value(1),R2.value(0),R3.value(1),R4.value(1)]
if(C1.value() == 0 or C2.value() == 0 or C3.value() == 0 or C4.value() == 0):
time.sleep(0.020)
if C1.value() == 0:
bzw = 1
key = 4
while not C1.value():
None
elif C2.value() == 0:
bzw = 1
key = 5
while not C2.value():
None
elif C3.value() == 0:
bzw = 1
key = 6
while not C3.value():
None
elif C4.value() == 0:
bzw = 1
key = 7
while not C4.value():
None
else:
key = 0
bzw = 0
R1.value(1)
# 第三行
[R1.value(1),R2.value(1),R3.value(0),R4.value(1)]
if(C1.value() == 0 or C2.value() == 0 or C3.value() == 0 or C4.value() == 0):
time.sleep(0.020)
if C1.value() == 0:
bzw = 1
key = 8
while not C1.value():
None
elif C2.value() == 0:
bzw = 1
key = 9
while not C2.value():
None
elif C3.value() == 0:
bzw = 1
key = 10
while not C3.value():
None
elif C4.value() == 0:
bzw = 1
key = 11
while not C4.value():
None
else:
key = 0
bzw = 0
R1.value(1)
# 第四行
[R1.value(1),R2.value(1),R3.value(1),R4.value(0)]
if(C1.value() == 0 or C2.value() == 0 or C3.value() == 0 or C4.value() == 0):
time.sleep(0.020)
if C1.value() == 0:
bzw = 1
key = 12
while not C1.value():
None
elif C2.value() == 0:
bzw = 1
key = 13
while not C2.value():
None
elif C3.value() == 0:
bzw = 1
key = 14
while not C3.value():
None
elif C4.value() == 0:
bzw = 1
key = 15
while not C4.value():
None
else:
key = 0
bzw = 0
R1.value(1)
if bzw == 1:
bzw = 0
if key == 3:
led.value(not led.value())
print("%d"%key)
font.text(oled,"%.2d"%(key),48,16,color=1,font_size=24,reverse=False,clear=True,show=True,half_char=True, auto_wrap=True)
time.sleep(0.1)
def main():
font.text(oled,str("矩阵按值"),32,24,color=1,font_size=16,reverse=False,clear=False,show=True,half_char=True, auto_wrap=True)
while True:
Key()
if __name__ == "__main__":
main()
演示效果
四、示例代码2
演示使用按键控制开关灯(板载led灯)
from machine import Pin,I2C
from key_matrix import KEY
from ssd1306 import SSD1306_I2C
from ufont import BMFont
import time
i2c = I2C(0,scl = Pin(5),sda = Pin(4),freq = 400_000)
oled = SSD1306_I2C(128,64,i2c,0x3c)
font = BMFont("fonts/unifont-14-12888-16.v3.bmf")
#行
R1 = Pin(2,Pin.OUT)
R2 = Pin(3,Pin.OUT)
R3 = Pin(10,Pin.OUT)
R4 = Pin(6,Pin.OUT)
#列
C1 = Pin(7 ,Pin.IN,Pin.PULL_UP)
C2 = Pin(8 ,Pin.IN,Pin.PULL_UP)
C3 = Pin(18,Pin.IN,Pin.PULL_UP)
C4 = Pin(19,Pin.IN,Pin.PULL_UP)
#创建按键对象
Key = KEY(C1,C2,C3,C4,R1,R2,R3,R4)
led = Pin(12,Pin.OUT)
def Matrix():
code = [
['0','1','2','3'],
['4','5','6','7'],
['8','9','a','b'],
['c','d','e','f'],
]
#接收按键值
value = Key.matrix()
if value == code[3][3]:
led.value(not led.value())
if led.value():
font.text(oled,"开灯",40,24,color=1,font_size=16,reverse=False,clear=True,show=True,half_char=True, auto_wrap=True)
else :
font.text(oled,"关灯",40,24,color=1,font_size=16,reverse=False,clear=True,show=True,half_char=True, auto_wrap=True)
print("%s"%value)
def main():
font.text(oled,str("矩阵按值"),32,24,color=1,font_size=16,reverse=False,clear=False,show=True,half_char=True, auto_wrap=True)
while True:
Matrix()
if __name__ == "__main__":
main()
演示效果
五、key_matrix库
key_matrix.py
'''
#行只负责拉高和拉低电瓶,让交叉的列进行扫描 R简称行
R1 = Pin(2,Pin.OUT)
R2 = Pin(3,Pin.OUT)
R3 = Pin(10,Pin.OUT)
R4 = Pin(6,Pin.OUT)
#列负责进行交叉的键值检测,当按键被按下时读取对应值 C简称列
C1 = Pin(7 ,Pin.IN,Pin.PULL_UP)
C2 = Pin(8 ,Pin.IN,Pin.PULL_UP)
C3 = Pin(18,Pin.IN,Pin.PULL_UP)
C4 = Pin(19,Pin.IN,Pin.PULL_UP)
#创建按键对象
Key = KEY(C1,C2,C3,C4,R1,R2,R3,R4)
#接收按键值
temp = Key.matrix()
'''
from machine import Pin
import time
class KEY(object):
def __init__(self,C1,C2,C3,C4,R1,R2,R3,R4):
self.C1 = C1
self.C2 = C2
self.C3 = C3
self.C4 = C4
self.R1 = R1
self.R2 = R2
self.R3 = R3
self.R4 = R4
#按键扫描函数
def matrix(self):
key = 0
bzw = 0
#设置第一行拉低
[self.R1.value(0),self.R2.value(1),self.R3.value(1),self.R4.value(1)]
#扫描每一列键值
if(self.C1.value() == 0 or self.C2.value() == 0 or self.C3.value() == 0 or self.C4.value() == 0):
time.sleep(0.020)
if self.C1.value() == 0:
bzw = 1
key = '0'
while not self.C1.value():
pass
elif self.C2.value() == 0:
bzw = 1
key = '1'
while not self.C2.value():
pass
elif self.C3.value() == 0:
bzw = 1
key = '2'
while not self.C3.value():
pass
elif self.C4.value() == 0:
bzw = 1
key = '3'
while not self.C4.value():
pass
else:
self.R1.value(1)
#设置第二行拉低
[self.R1.value(1),self.R2.value(0),self.R3.value(1),self.R4.value(1)]
#扫描每一列键值
if(self.C1.value() == 0 or self.C2.value() == 0 or self.C3.value() == 0 or self.C4.value() == 0):
time.sleep(0.020)
if self.C1.value() == 0:
bzw = 1
key = '4'
while not self.C1.value():
pass
elif self.C2.value() == 0:
bzw = 1
key = '5'
while not self.C2.value():
pass
elif self.C3.value() == 0:
bzw = 1
key = '6'
while not self.C3.value():
pass
elif self.C4.value() == 0:
bzw = 1
key = '7'
while not self.C4.value():
pass
else:
key = 0
bzw = 0
self.R2.value(1)
#设置第三行拉低
[self.R1.value(1),self.R2.value(1),self.R3.value(0),self.R4.value(1)]
#扫描每一列键值
if(self.C1.value() == 0 or self.C2.value() == 0 or self.C3.value() == 0 or self.C4.value() == 0):
time.sleep(0.020)
if self.C1.value() == 0:
bzw = 1
key = '8'
while not self.C1.value():
pass
elif self.C2.value() == 0:
bzw = 1
key = '9'
while not self.C2.value():
pass
elif self.C3.value() == 0:
bzw = 1
key = 'a'
while not self.C3.value():
pass
elif self.C4.value() == 0:
bzw = 1
key = 'b'
while not self.C4.value():
pass
else:
key = 0
bzw = 0
self.R3.value(1)
#设置第四行拉低
[self.R1.value(1),self.R2.value(1),self.R3.value(1),self.R4.value(0)]
#扫描每一列键值
if(self.C1.value() == 0 or self.C2.value() == 0 or self.C3.value() == 0 or self.C4.value() == 0):
time.sleep(0.020)
if self.C1.value() == 0:
bzw = 1
key = 'c'
while not self.C1.value():
pass
elif self.C2.value() == 0:
bzw = 1
key = 'd'
while not self.C2.value():
pass
elif self.C3.value() == 0:
bzw = 1
key = 'e'
while not self.C3.value():
pass
elif self.C4.value() == 0:
bzw = 1
key = 'f'
while not self.C4.value():
pass
else:
key = 0
bzw = 0
self.R4.value(1)
if bzw == 1:
bzw = 0
return key
#返回的的是字符串类型
六、字库和ssd1306驱动
字库fonts/unifont-14-12888-16.v3.bmf和ssd1306驱动见这个文章
__version__ = 3
import time
import struct
import framebuf
DEBUG = False
def timeit(f, *args, **kwargs):
try:
myname = str(f).split(' ')[1]
except:
myname = "UNKONW"
def new_func(*args, **kwargs):
if DEBUG:
try:
t = time.ticks_us()
result = f(*args, **kwargs)
delta = time.ticks_diff(time.ticks_us(), t)
print('Function {} Time = {:6.3f}ms'.format(myname, delta / 1000))
except AttributeError:
t = time.perf_counter_ns()
result = f(*args, **kwargs)
delta = time.perf_counter_ns() - t
print('Function {} Time = {:6.3f}ms'.format(myname, delta / 1000000))
return result
else:
return f(*args, **kwargs)
return new_func
class BMFont:
@staticmethod
def _list_to_byte(arr):
b = 0
for a in arr:
b = (b << 1) + a
return bytes([b])
@timeit
def _bit_list_to_byte_data(self, bit_list):
"""将点阵转换为字节数据
Args:
bit_list:
Returns:
"""
byte_data = b''
for _col in bit_list:
for i in range(0, len(_col), 8):
byte_data += self._list_to_byte(_col[i:i + 8])
return byte_data
@timeit
def __init__(self, font_file):
self.font_file = font_file
self.font = open(font_file, "rb", buffering=0xff)
self.bmf_info = self.font.read(16)
if self.bmf_info[0:2] != b"BM":
raise TypeError("字体文件格式不正确: " + font_file)
self.version = self.bmf_info[2]
if self.version != 3:
raise TypeError("字体文件版本不正确: " + str(self.version))
self.map_mode = self.bmf_info[3] # 映射方式
self.start_bitmap = struct.unpack(">I", b'\x00' + self.bmf_info[4:7])[0] # 位图开始字节
self.font_size = self.bmf_info[7] # 字体大小
self.bitmap_size = self.bmf_info[8] # 点阵所占字节
@timeit
def _to_bit_list(self, byte_data, font_size, *, _height=None, _width=None):
"""将字节数据转换为点阵数据
Args:
byte_data: 字节数据
font_size: 字号大小
_height: 字体原高度
_width: 字体原宽度
Returns:
"""
_height = _height or self.font_size
_width = _width or self.bitmap_size // self.font_size * 8
new_bitarray = [[0 for j in range(font_size)] for i in range(font_size)]
for _col in range(len(new_bitarray)):
for _row in range(len(new_bitarray[_col])):
_index = int(_col / (font_size / _height)) * _width + int(_row / (font_size / _width))
new_bitarray[_col][_row] = byte_data[_index // 8] >> (7 - _index % 8) & 1
return new_bitarray
@timeit
def _color_render(self, bit_list, color):
"""将二值点阵图像转换为 RGB565 彩色字节图像
Args:
bit_list:
color:
Returns:
"""
color_array = b""
for _col in range(len(bit_list)):
for _row in range(len(bit_list)):
color_array += struct.pack("<H", color) if bit_list[_col][_row] else b'\x00\x00'
return color_array
@timeit
def _get_index(self, word):
"""获取索引
Args:
word: 字符
Returns:
"""
word_code = ord(word)
start = 0x10
end = self.start_bitmap
while start <= end:
mid = ((start + end) // 4) * 2
self.font.seek(mid, 0)
target_code = struct.unpack(">H", self.font.read(2))[0]
if word_code == target_code:
return (mid - 16) >> 1
elif word_code < target_code:
end = mid - 2
else:
start = mid + 2
return -1
@timeit
def get_bitmap(self, word):
"""获取点阵图
Args:
word: 字符
Returns:
bytes 字符点阵
"""
index = self._get_index(word)
if index == -1:
return b'\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x0f\xcf\xf3\xcf\xf3\xff\xf3\xff\xcf\xff?\xff?\xff\xff\xff' \
b'?\xff?\xff\xff\xff\xff'
self.font.seek(self.start_bitmap + index * self.bitmap_size, 0)
return self.font.read(self.bitmap_size)
@timeit
def text(self, display, string, x, y, color=1, *, font_size=None, reverse=False, clear=False, show=False,
half_char=True, auto_wrap=False, **kwargs):
"""通过显示屏显示文字
使用此函数显示文字,必须先确认显示对象是否继承与 framebuf.FrameBuffer。
如果显示对象没有 clear 方法,需要自行调用 fill 清屏
Args:
display: 显示实例
string: 字符串
x: 字体左上角 x 轴
y: 字体左上角 y 轴
color: 颜色
font_size: 字号
reverse: 是否反转背景
clear: 是否清除之前显示的内容
show: 是否立刻显示
half_char: 是否半字节显示 ASCII 字符
auto_wrap: 自动换行
**kwargs:
Returns:
None
"""
font_size = font_size or self.font_size
initial_x = x
# 清屏
try:
display.clear() if clear else 0
except AttributeError:
print("请自行调用 display.fill(*) 清屏")
for char in range(len(string)):
# 是否自动换行
if auto_wrap:
if auto_wrap and ((x + font_size // 2 >= display.width and ord(string[char]) < 128 and half_char) or
(x + font_size >= display.width and (not half_char or ord(string[char]) > 128))):
y += font_size
x = initial_x
# 回车
if string[char] == '\n':
y += font_size
x = initial_x
continue
# Tab
elif string[char] == '\t':
x = ((x // font_size) + 1) * font_size + initial_x % font_size
continue
# 其它的控制字符不显示
elif ord(string[char]) < 16:
continue
# 超过范围的字符不会显示*
if x > display.width or y > display.height:
continue
byte_data = list(self.get_bitmap(string[char]))
# 反转
if reverse:
for _pixel in range(len(byte_data)):
byte_data[_pixel] = ~byte_data[_pixel] & 0xff
# 缩放和色彩*
if color > 1 or font_size != self.font_size:
bit_data = self._to_bit_list(byte_data, font_size)
if color > 1:
display.blit(
framebuf.FrameBuffer(bytearray(self._color_render(bit_data, color)), font_size, font_size,
framebuf.RGB565), x, y)
else:
display.blit(
framebuf.FrameBuffer(bytearray(self._bit_list_to_byte_data(bit_data)), font_size, font_size,
framebuf.MONO_HLSB), x, y)
else:
display.blit(framebuf.FrameBuffer(bytearray(byte_data), font_size, font_size, framebuf.MONO_HLSB), x, y)
# 英文字符半格显示
if ord(string[char]) < 128 and half_char:
x += font_size // 2
else:
x += font_size
display.show() if show else 0
def char(self, char, color=1, font_size=None, reverse=False):
""" 获取字体字节数据
在没有继承 framebuf.FrameBuffer 的显示驱动,或者内存不足以将一整个屏幕载入缓存帧时
可以直接获取单字的字节数据,局部更新
Args:
char: 单个字符
color: 颜色
font_size: 字体大小
reverse: 反转
Returns:
bytearray
"""
font_size = font_size or self.font_size
byte_data = list(self.get_bitmap(char))
# 反转
if reverse:
for _pixel in range(len(byte_data)):
byte_data[_pixel] = ~byte_data[_pixel] & 0xff
if color > 1 or font_size != self.font_size:
bit_data = self._to_bit_list(byte_data, font_size)
if color > 1:
return self._color_render(bit_data, color)
else:
return self._bit_list_to_byte_data(bit_data)
else:
return bytearray(byte_data)
if __name__ == '__main__':
def show_bitmap(arr):
"""
显示点阵字 MONO_HLSB
:return:
"""
for row in arr:
for i in row:
if i:
print('* ', end=' ')
else:
print('. ', end=' ')
print()
font = BMFont("unifont-14-12888-16.v3.bmf")
print("16 ----")
bd = font.char("我", reverse=True, color=0xffff, font_size=16)
print("24 ----")
bd = font.char("我", reverse=True, color=0xffff, font_size=24)
print("16 ----")
# font._with_color(zoom(byte_to_bit(font.get_bitmap("我"), 16), 24), 0xff00)
font._color_render(font._to_bit_list(font.get_bitmap("我"), 24), 0xff00)
七、4x4矩阵按键购买
4x4 按键模块 矩阵模块 键盘模块 单片机 外围模块
https://detail.tmall.com/item.htm?_u=ip01rchfc25&id=538152113118&spm=a1z09.2.0.0.7f5e2e8dCdkoWOhttps://detail.tmall.com/item.htm?_u=ip01rchfc25&id=538152113118&spm=a1z09.2.0.0.7f5e2e8dCdkoWO我收到的实物图和卖家详情页的不一样,但是可以使用。