考虑到我们接下来的课程需要显示更多的信息,我们今天讲解利用i2c驱动OLED显示屏,并支持显示中文。
我们常用的0.96寸的OLED显示屏,大多是用1306驱动芯片,所以我们需要先大致了解一下framebuf 及SSD1306模块。
一、framebuf
MicroPython 的 framebuf 模块是一个用于操作帧缓冲区(Frame Buffer)的底层库,它提供了一种直接操作像素数据的方式,常用于驱动显示屏(如 OLED、LCD 等)。帧缓冲区本质上是内存中的一块区域,存储了屏幕像素的显示数据,通过修改这些数据可以直接控制屏幕内容。
核心功能
1、直接像素操作:支持读写单个像素或批量修改像素。
2、图形绘制:提供基本图形(线条、矩形、圆形等)和文本的绘制功能。
3、颜色格式支持:支持多种颜色格式(如单色、16位RGB等),适配不同屏幕硬件。
关键类:FrameBuffer
FrameBuffer 是 framebuf 模块的核心类,需要手动初始化。其构造函数如下:
framebuf.FrameBuffer(buffer, width, height, format, stride=width, ...)
**参数说明**:
- buffer: 字节数组(bytearray)或内存缓冲区,用于存储像素数据。
- width: 屏幕宽度(像素数)。
- height: 屏幕高度(像素数)。
- format: 颜色格式(见下文)。
- stride: 每行像素占用的字节数(默认为 width)。
常用方法
以下是 FrameBuffer 对象的常用方法:
| 方法 | 功能描述
|-------------------------------------|----------------------------------------
| fill(color) | 用指定颜色填充整个缓冲区
| pixel(x, y[, color]) | 设置/获取单个像素颜色
| hline(x, y, w, color) | 绘制水平线
| vline(x, y, h, color) | 绘制垂直线
| rect(x, y, w, h, color) | 绘制矩形边框
| fill_rect(x, y, w, h, color) | 绘制填充矩形
| line(x1, y1, x2, y2, color) | 绘制直线
| text(str, x, y, color) | 在指定位置绘制文本(内置字体)
| scroll(dx, dy) | 滚动屏幕内容(偏移量 dx, dy)
| blit(source, x, y, key) | 将另一个帧缓冲区的数据复制到当前缓冲区
更多更详细的信息大家可以从 MicroPython网站上查询获取。
二、SSD1306
MicroPython 的 ssd1306模块是专为 SSD1306 驱动的 OLED 屏幕设计的驱动库,支持常见的 128x64 或 128x32 分辨率单色显示屏。它基于 framebuf 模块实现,提供了简化的接口控制屏幕显示内容,适用于嵌入式开发(如 ESP32、ESP8266、Raspberry Pi Pico 等)。
核心功能
1、屏幕初始化:支持 SPI 或 I2C 通信协议。
2、像素级控制:通过 framebuf 继承的绘图方法(如线条、矩形、文本)。
3、屏幕刷新:将内存中的帧缓冲区内容发送到屏幕。
4、对比度调节:动态调整 OLED 显示亮度。
常用方法
| 方法 | 功能描述
|-----------------------------------------|----------------------------------
| oled.fill(color) | 填充全屏(0=黑,1=白)
| oled.text(str, x, y, color) | 在 (x,y) 显示文本(默认白色)
| oled.pixel(x, y, color) | 设置单个像素颜色
| oled.line(x1, y1, x2, y2, color) | 绘制直线
| oled.rect(x, y, w, h, color) | 绘制矩形边框
| oled.fill_rect(x, y, w, h, color) | 绘制填充矩形
| oled.show() | 更新屏幕内容(必须调用才能显示)
| oled.contrast(value) | 调节对比度(0~255)
| oled.poweron() /oled.poweroff() | 打开/关闭屏幕电源
同样,更多更详细的信息大家可以从 MicroPython网站上查询获取。
硬件连接:
这里,我们使用基于ESP32-C3-WROOM-02的核心模组的开发板ESP32-C3-DevKitC-02 。我们用I2C驱动OLED屏,硬件连接如下:
ESP32C3 | I2C OLED |
IO2 | SCL |
IO3 | SDA |
先上代码:
from machine import I2C,Pin
import time
from neopixel import NeoPixel
from ssd1306 import SSD1306_I2C
i2c = I2C(0,scl=Pin(2),sda=Pin(3),freq=400000)
oled = SSD1306_I2C(128,64,i2c,0x3c)
# 定义全局变量
WS2812_NUM = 1
WS2812_LED_LEVEL = 0x10
G = 0xff
R = 0
B = 0
pin = Pin(8, Pin.OUT) #配置GPIO8为输出端口,控制ws2812灯珠的DIN信号
np = NeoPixel(pin, WS2812_NUM,3,1)
def ws2812_LED_On(phase):
global G,R,B
for i in range(WS2812_NUM - 1):
np[WS2812_NUM -1 - i] = np[WS2812_NUM - 2 - i]
if phase < WS2812_LED_LEVEL:
G = int(G - (0xff / WS2812_LED_LEVEL))
R = int(R + (0xff / WS2812_LED_LEVEL))
phase += 1
if phase == WS2812_LED_LEVEL:
G = 0
R = 0xff
elif phase < (WS2812_LED_LEVEL * 2):
R = int(R - (0xff / WS2812_LED_LEVEL))
B = int(B + (0xff / WS2812_LED_LEVEL))
phase += 1
if phase == (WS2812_LED_LEVEL * 2):
R = 0
B = 0xff
elif phase < (WS2812_LED_LEVEL * 3):
B = int(B - (0xff / WS2812_LED_LEVEL))
G = int(G + (0xff / WS2812_LED_LEVEL))
phase += 1
if phase == (WS2812_LED_LEVEL * 3):
phase = 0
B = 0
G = 0xff
np[0] = (G,R,B)
return phase
utf_8_to_gb2312 = {'深':0xC9EE,'圳':0xDBDA,'源':0xD4B4,'悦':0xD4C3,'科':0xBFC6,
'技':0xBCBC,'有':0xD3D0,'限':0xCFDE,'公':0xB9AB,'司':0xCBBE,
'温':0xCEC2,'湿':0xCAAA,'度':0xB6C8,'显':0xCFD4,'示':0xCABE,
'天':0xCCEC,'气':0xC6F8,'时':0xCAB1,'间':0xBCE4,'日':0xC8D5,'期':0xC6DA,'啊':0xB0A1}
HZFontInfo = [0xa1a1,94,0xb0a1,94,0x8140,190,0xaa40,96,0xa840,96]
HZFontAddr = [0,0x7980,0x3C780,0x6BF80,0xABB80]
#GB18030汉字分5个区,每个区对应2个元素,第一个对应的是该区的起始地址,
#第二个对应的是起始地址第二个字节最大值与最小值的差 + 1
import framebuf
def CalcGBSection(incode):
highbyte = (incode >> 8) & 0xff
lowbyte = incode & 0xff
section = 0xff
if ((highbyte >= 0x81) and (lowbyte >= 0x40)):
if (highbyte < 0xa1):
section = 2
elif ((highbyte >= 0xa1) and (highbyte <= 0xab) and (lowbyte >= 0xa1)):
section = 0
elif ((highbyte >= 0xa8) and (highbyte <= 0xa9)):
section = 4
elif ((highbyte >= 0xaa) and (highbyte <= 0xfe) and (lowbyte <= 0xa0)):
section = 3
elif ((highbyte >= 0xb0) and (highbyte <= 0xf7) and (lowbyte >= 0xa1)):
section = 1
return section
def show_chinese(text,x_start,y_start,color):
with open('GB18030P.BIN','rb') as f:
for char in text :
gb_code = utf_8_to_gb2312[char]
# print(char)
# print(hex(gb_code))
section = CalcGBSection(gb_code)
# print (section)
gb_code -= HZFontInfo[section * 2]
# print (gb_code)
hbyte = (gb_code >> 8) & 0xff
lbyte = gb_code & 0xff
if ((section >= 2) and (lbyte > (0x7e - 0x40))):
lbyte -= 1
offset = HZFontInfo[section * 2 + 1]
offset = (offset * hbyte + lbyte) * 32
# print(hex(offset))
offset += HZFontAddr[section]
f.seek(offset)
font_data = f.read(32)
# for i in range(32):
# print (hex(font_data[i]))
for col in range(16):
for row in range(16):
bit = font_data[col * 2 + row // 8] >> (7 - (row % 8)) & 0x01
oled.pixel(x_start + col,y_start+row,bit)
x_start += 18
def Draw_HZ_Font():
x_start = y_start = 3
H_Scan = 1
if H_Scan == 1:
for row in range(16):
for col in range(16):
bit = fontlist[row * 2 + col // 8] >> (7 - (col % 8)) & 0x01
oled.pixel(x_start + col,y_start+row,bit)
x_start += 18
else :
for col in range(16):
for row in range(16):
bit = fontlist[32 + col * 2 + row // 8] >> (7 - (row % 8)) & 0x01
oled.pixel(x_start + col,y_start+row,bit)
x_start += 18
if __name__ == "__main__":
# pin = Pin(36, Pin.OUT)
p9 = Pin(9, Pin.IN) #配置GPIO9为输入端口
WS2812_POWERON = 1
WS2812_POWEROFF = 0
ws2812_status = WS2812_POWEROFF
offset = 0
while True:
if (p9.value() == 0): #是否有按键?有,切换RGB灯的状态
ws2812_status ^= 1
while True:
if (p9.value() == 1): #等待按键释放
break
if ws2812_status == WS2812_POWEROFF:
np.fill((0,0,0))
np.write() #关闭RGB灯
else :
offset = ws2812_LED_On(offset)
np.write() #点亮RGB灯
oled.fill(0x0)
oled.text("Hello MicroPython!",3,20,1)
oled.rect(0,0,128,64,1)
show_chinese("深圳源悦科技",3,3,1)
# for y in range(60):
# oled.pixel(2*y,y,1)
oled.show()
# time.sleep(1)
time.sleep_ms(100)
# 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)
代码中含有上一课讲解的GPIO的操作内容,大家可以查阅MicroPython 开发ESP32应用教程 之 开发板硬件介绍及ESP32 的GPIO操作讲解_python链接esp32 c3开发板-优快云博客对i2c的初始及OLED的显示,代码也比较简单,对比SSD1306模块的介绍,我们比较容易理解:
i2c = I2C(0,scl=Pin(2),sda=Pin(3),freq=400000) 初始化
oled = SSD1306_I2C(128,64,i2c,0x3c) 初始化模块
oled.fill(0x0) 清屏
oled.text("MicroPython!",3,20,1) 显示相应的字符,8*8英文字符是内置的,不需要提供字库数据。
oled.rect(0,0,128,64,1) 画方框
oled.show() 更新屏幕显示内容
这里我们重点讲解一下中文显示的代码。
MicroPython并不支持中文的显示,我们想要显示中文字符,就要把MicroPython的编码utf-8转换为GBK编码,这个功能,Python好象是支持的, 通过类似char.encode('GBK')的函数就能实现,但很遗憾MicroPython由于空间的限制并不支持GBK编码,甚至不支持unicode编码,这就导致我们很难实现编码的转换,所以我们采取了一种简单的查表的方式来转换编码,但这也就意味着我们无法任意显示中文字符。
utf_8_to_gb2312 = {'深':0xC9EE,'圳':0xDBDA,'源':0xD4B4,'悦':0xD4C3,'科':0xBFC6,
'技':0xBCBC,'有':0xD3D0,'限':0xCFDE,'公':0xB9AB,'司':0xCBBE,
'温':0xCEC2,'湿':0xCAAA,'度':0xB6C8,'显':0xCFD4,'示':0xCABE, '天':0xCCEC,'气':0xC6F8,'时':0xCAB1,'间':0xBCE4,'日':0xC8D5,'期':0xC6DA,'啊':0xB0A1}
通过这个表,我们可以查询到我们需要显示的汉字的GBK编码,然后计算出该字符在GB18030.bin字库文件中的位置,具体方法见上述代码中的函数:def show_chinese(text,x_start,y_start,color)及def CalcGBSection(incode)
关于GB18030.bin中文字库及其说明请下载:
MicroPythonforesp32,I2C驱动OLED显示屏,支持中文字库,带完整的GB18030中文字库资源-优快云文库