1、OLED显示英文
我们之前通过树莓派开发板做过OLED显示,这里就不再赘述OLED显示屏了。直接上接线图与代码。
(1)Adafruit
Adafruit是一家成立于2005年的私营企业,主要业务是设计和制造开源电子硬件。Adafruit在美国设计和制造其产品。该公司鼓励专业工程师和创客使用其丰富的电子产品和配件来设计新产品。他们提供独特而有趣的DIY电子元件和套件,帮助将日常物品打造成适合教育和先进产品概念的高科技原型设计。
Adafruit 也是电子教学平台、原型制作和开发工具领域快速成长的全球领跑者,其制造基地设在纽约市中心。发布前 Limor (公司创建者)会亲自进行选择、测试和核准。Adafruit 是开源硬件方面的先锋,竭力对电子和编程领域的不同年龄段的制造商提供教学服务。
(2)CircuitPython
2017 年 1 月,Adafruit 推出了CircuitPython,这是MicroPython编程语言的一个分支,经过优化,可在选定的 Adafruit 产品上运行。树莓派、ESP32都支持该产品库
2019 年,CircuitPython 的资源转移到了 circuitpython.org,此举表明使用 CircuitPython 的第三方电路板数量已经超过了仅由 Adafruit 制造的电路板数量。这包括用于微控制器的 CircuitPython 和使用名为“Blinka”的兼容层 Adafruit的单板计算机上的CircuitPython,以访问通用输入/输出功能以及与 160 多个传感器和驱动程序库的兼容性。
正是因为有了这些专门做底层驱动的公司的工程师把功能都封装到函数或类方法里面了,大大降低了开发成本,也让初学的同学们能尽快上手实现功能以及部署项目提供了很大的方便。不然同学们要去了解很多底层的硬件知识,包括寄存器、时序逻辑等,才能写出相应的驱动。
我们专注的是业务逻辑,主要是实现软件功能。硬件驱动开发专注于实现硬件的功能。而最难的是芯片设计,他要为硬件功能的实现提供芯片级的支持,就是利用MOS管的开关作用(门电路)实现芯片的底层功能,比如带进位加法器实现,RS触发器实现存储功能等,同学们在数字电路课程中也有一定的了解,这里我们还是回到OLED实验上来吧。
(3)SSD1306
该驱动是micropython的SSD1306 OLED驱动,OLED接口有2种,IIC和SPI,同学注意看你买的OLED屏是哪种,IIC是四线,SPI是六线。该驱动由Adafruit公司编写。
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
from micropython import const
import framebuf
# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xA4)
SET_NORM_INV = const(0xA6)
SET_DISP = const(0xAE)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xA0)
SET_MUX_RATIO = const(0xA8)
SET_COM_OUT_DIR = const(0xC0)
SET_DISP_OFFSET = const(0xD3)
SET_COM_PIN_CFG = const(0xDA)
SET_DISP_CLK_DIV = const(0xD5)
SET_PRECHARGE = const(0xD9)
SET_VCOM_DESEL = const(0xDB)
SET_CHARGE_PUMP = const(0x8D)
# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
def __init__(self, width, height, external_vcc):
self.width = width
self.height = height
self.external_vcc = external_vcc
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB, self.width)
self.init_display()
def init_display(self):
for cmd in (
SET_DISP | 0x00, # off
# address setting
SET_MEM_ADDR,
0x00, # horizontal
# resolution and layout
SET_DISP_START_LINE | 0x00,
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
SET_MUX_RATIO,
self.height - 1,
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
SET_DISP_OFFSET,
0x00,
SET_COM_PIN_CFG,
0x02 if self.width > 2 * self.height else 0x12,
# timing and driving scheme
SET_DISP_CLK_DIV,
0x80,
SET_PRECHARGE,
0x22 if self.external_vcc else 0xF1,
SET_VCOM_DESEL,
0x30, # 0.83*Vcc
# display
SET_CONTRAST,
0xFF, # maximum
SET_ENTIRE_ON, # output follows RAM contents
SET_NORM_INV, # not inverted
# charge pump
SET_CHARGE_PUMP,
0x10 if self.external_vcc else 0x14,
SET_DISP | 0x01,
): # on
self.write_cmd(cmd)
self.fill(0)
self.show()
def poweroff(self):
self.write_cmd(SET_DISP | 0x00)
def poweron(self):
self.write_cmd(SET_DISP | 0x01)
def contrast(self, contrast):
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)
def invert(self, invert):
self.write_cmd(SET_NORM_INV | (invert & 1))
def show(self):
x0 = 0
x1 = self.width - 1
if self.width == 64:
# displays with width of 64 pixels are shifted by 32
x0 += 32
x1 += 32
self.write_cmd(SET_COL_ADDR)
self.write_cmd(x0)
self.write_cmd(x1)
self.write_cmd(SET_PAGE_ADDR)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
self.write_data(self.buffer)
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.temp = bytearray(2)
self.write_list = [b"\x40", None] # Co=0, D/C#=1
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)
def write_data(self, buf):
self.write_list[1] = buf
self.i2c.writevto(self.addr, self.write_list)
class SSD1306_SPI(SSD1306):
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
self.rate = 10 * 1024 * 1024
dc.init(dc.OUT, value=0)
res.init(res.OUT, value=0)
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
import time
self.res(1)
time.sleep_ms(1)
self.res(0)
time.sleep_ms(10)
self.res(1)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytearray([cmd]))
self.cs(1)
def write_data(self, buf):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)
整个驱动代码比较多,放文中有些占用篇幅,如果你是IIC接口的OLED,可以把后面的Class SSD1306_SPI类和其方法都删除。没有关系的。
(4)显示几行英文
由于ESP32开发板的RAM和ROM无法和树莓派媲美,我们不得不改变一下文件存放的位置,如果把所有的文件都放到根目录/下面,文件一多不太好管理,我们还是把各自的驱动放到各自的目录里面。
打开Thonny,在ESP32新建个文件夹为OLED,通过右键选择切换到。然后将驱动文件下载到OLED文件夹内。
然后切换到micropython设备,新建一个test.py文件。在导入ssd1306的时候加上from OLED告诉编译器从OLED文件夹内导入ssd1306模块
from machine import Pin, SoftI2C
from time import sleep
from OLED import ssd1306
# 创建i2c对象
i2c = SoftI2C(scl=Pin(22), sda=Pin(21))
# 宽度高度
oled_width = 128
oled_height = 64
# 创建oled屏幕对象
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
# 在指定位置处显示文字
oled.text('Hello World!', 0, 0)
oled.text('Hello,Classmate', 0, 10)
oled.text('Hello,Lover', 0, 20)
oled.show()
这里还有一点要注意,我是实验中发现的问题,就是在二级目录下无法导入三方库文件。如果我把test文件放到OLED目录下面。运行的时候报错。
2、OLED显示中文
中文显示需要中文字库,可以通过软件来设计中文字库,然后加载,这种方式比较麻烦,但是占用的空间少,用哪些字就设计哪些字,大家可以参考网上的教程来做。我们通过加载通用的GB2312编码字库来实现中文的显示。
将gb2312.py、utf2gb2312.bin、HZK16三个文件放到OLED文件夹内。下面是gb2312.py代码。
class gb2312(object):
def __init__(self):
self.f = open('/OLED/utf2gb2312.bin', 'r', encoding='utf-8')
def b2i(self, byte): # bytes转int
r = 0
for i in range(len(byte)):
r = (r << 8) + byte[i]
return r
def i2b(self, num): # int转bytes
num = int(num, 16)
return num.to_bytes(2, 'big')
def one_char(self, char): # 将一个字符转化成gb2312
utf_byte = char.encode('utf-8')
r = self.B_S(0, 7296, self.b2i(utf_byte))
gb2312_byte = self.i2b(r)
# print(gb2312_byte)
return gb2312_byte
def strs(self, st): # 将字符串转化成gb2312
r = b''
for s in st:
# print(s.encode('utf-8'))
if len(s.encode('utf-8')) <= 1:
r += s.encode('utf-8')
else:
r += self.one_char(s)
return r
def B_S(self, low, high, m): # 二分查找
if 0 <= low <= high <= 7296:
mid = (low + high) // 2
self.f.seek(mid * 12)
data = self.f.read(12)
utf = data[0:6]
if int(utf, 16) < m:
return self.B_S(mid + 1, high, m)
elif int(utf, 16) > m:
return self.B_S(low, mid - 1, m)
else:
return data[7:-1]
def __del__(self):
self.f.close()
在根目录新建显示中文的oled_chinese.py文件。
import binascii
import framebuf
from machine import Pin, I2C
from OLED import ssd1306
from OLED import gb2312
class OLEDController:
def __init__(self, scl_pin=22, sda_pin=21, font_size=2):
self.scl_pin = scl_pin
self.sda_pin = sda_pin
self.font_size = font_size # 添加字体大小变量
self.i2c = I2C(0, scl=Pin(self.scl_pin), sda=Pin(self.sda_pin))
self.oled = ssd1306.SSD1306_I2C(128, 64, self.i2c)
self.buf = bytearray(128 * 64 // 8)
self.fb = framebuf.FrameBuffer(self.buf, 128, 64, framebuf.MONO_HLSB)
self.KEYS = [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]
self.rect_list = [[] for _ in range(16)]
self.font_file = "/OLED/HZK16"
def display_char(self, char, x, y):
try:
self.display_chinese(char, x, y)
except:
self.fb.text(char, x, y)
self.oled.blit(self.fb, 0, 0)
self.oled.show()
def display_chinese(self, char, x, y):
self.rect_list = [[] for _ in range(16)]
get_gb2312 = gb2312.fontbyte.strs(char)
hex_str = binascii.hexlify(get_gb2312).decode('utf-8')
area = eval('0x' + hex_str[:2]) - 0xA0
index = eval('0x' + hex_str[2:]) - 0xA0
offset = (94 * (area-1) + (index-1)) * 32
font_rect = None
with open(self.font_file, "rb") as f:
f.seek(offset)
font_rect = f.read(32)
for k in range(len(font_rect) // 2):
row_list = self.rect_list[k]
for j in range(2):
for i in range(8):
asc = font_rect[k * 2 + j]
flag = asc & self.KEYS[i]
row_list.append(flag)
# 计算新的字体大小
new_font_size = self.font_size // 2
for row in range(len(self.rect_list)):
for col in range(len(self.rect_list[0])):
if self.rect_list[row][col]:
self.fb.fill_rect(x + col * new_font_size, y + row * new_font_size, new_font_size, new_font_size, 1)
def display_chinese_on_oled(self, text, x=0, y=0):
for index, char in enumerate(text):
self.display_char(char, x + index * self.font_size*8, y)
# 创建 OLED 控制器实例
oled_controller = OLEDController()
# 调用示例
oled_controller.display_chinese_on_oled("同学欢迎你!", 0, 0)
oled_controller.display_chinese_on_oled("淮北理工学院", 0, 18)
oled_controller.display_char('who are you?', 0, 36)
注意OLED使用的引脚,如果不是22和21,请更换。该类提供的显示英文和中文的方法直接调用即可。