嵌入式人工智能ESP32(7-OLED显示中英文)

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,请更换。该类提供的显示英文和中文的方法直接调用即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值