物联网开发100 - Micropython ESP32 S3非取字模和刷写中文固件方式显示中文

一、目的

        这一节我们学习如何使用合宙的ESP32 S3开发板来学习在SSD1306液晶屏上显示中文汉字。

二、环境

        合宙ESP32 S3开发板 + USB转type-C线  + SSD1306液晶屏 + Win10商业版

ESP32S3-CORE开发板 - LuatOS 文档https://wiki.luatos.com/chips/esp32s3/board.html

            物联网开发100 - Micropython ESP32 S3非取字模和刷写中文固件方式显示中文_第1张图片

开发板通过USB线插到电脑就行。ESP32C3开发板固件下载地址:

MicroPython - Python for microcontrollershttps://micropython.org/download/GENERIC_S3/SSD1306液晶屏接线方法:

        物联网开发100 - Micropython ESP32 S3非取字模和刷写中文固件方式显示中文_第2张图片
三、ESP32 S3固件烧录方法

         1,官方固件下载,地址见文首。

物联网开发100 - Micropython ESP32 S3非取字模和刷写中文固件方式显示中文_第3张图片

        2,官方烧录工具esptool下载(我使用的第4步的方法,此处可跳过)

Installation instructions

Program your board using the esptool.py program, found here.

If you are putting MicroPython on your board for the first time then you should first erase the entire flash using:

esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash

From then on program the firmware starting at address 0:

esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0 board-20210902-v1.17.bin

        国内镜像计较快:

mirrors / espressif / esptool · GitCodeEspressif SoC serial bootloader utility Github 镜像仓库 源项目地址 ⬇ https://gitcode.net/mirrors/espressif/esptool        3,烧录时请将板子上的拨码开关拨到USB端。

                                            物联网开发100 - Micropython ESP32 S3非取字模和刷写中文固件方式显示中文_第4张图片

         4,然后连接到电脑上,按住板子上的BOOT按键不放,在按住RST键,然后同时松开。此时,进入下载模式。打开esptool工具进行固件的烧录。

工具 | 乐鑫科技Downloads: SDK & Demos, APKs, Tools and Documents for Espressif Systems products and solutionshttps://www.espressif.com.cn/zh-hans/support/download/other-tools物联网开发100 - Micropython ESP32 S3非取字模和刷写中文固件方式显示中文_第5张图片

         5,烧录详情,打开esptool.exe,按下面图片进行烧录

                        物联网开发100 - Micropython ESP32 S3非取字模和刷写中文固件方式显示中文_第6张图片

         物联网开发100 - Micropython ESP32 S3非取字模和刷写中文固件方式显示中文_第7张图片

         烧录完成后按下RST键,重启下开发板。然后保持拨码开关在USB端即可。

四、示例代码

from machine import Pin,I2C
from ssd1306 import SSD1306_I2C
from ufont import BMFont
import time

#液晶地址
addr = 0x3c
#创建I2C对象
i2c  = I2C(0,scl = Pin(12),sda = Pin(11),freq = 400_000)
#创建液晶对象
oled = SSD1306_I2C(128,64,i2c,addr)
#挂载字库
font = BMFont("fonts/unifont-14-12888-16.v3.bmf")
#扫描I2C器件地址并反馈
print("%#x"%i2c.scan()[0])

def main():
    
    #显示文本字体可调节8-64
    font.text(oled,"Welcome to 中国上海。",0,0,color=1,font_size=16,reverse=False,reverse=False,clear=False,show=True,auto_wrap=True)
    
    #官方基本字符显示
    #oled.text("nihao",0,0)
    #oled.show()
    
    while True:
        #动态数字显示
        for i in range(100):
            font.text(oled,"num = %.2d"%i,60,42,color=1,font_size=16,reverse=False,reverse=False,clear=False,show=True,auto_wrap=True)
            time.sleep(0.5)
    
if __name__ == "__main__":
    main()

显示效果:

物联网开发100 - Micropython ESP32 S3非取字模和刷写中文固件方式显示中文_第8张图片

五、示例代码中的字库和ssd1306驱动下载地址

ufont.py

__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", 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)

ssd1306.py

# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
# https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py
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_IREF_SELECT = const(0xAD)
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.init_display()

    def init_display(self):
        for cmd in (
                SET_DISP,  # display off
                # address setting
                SET_MEM_ADDR,
                0x00,  # horizontal
                # resolution and layout
                SET_DISP_START_LINE,  # start at line 0
                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
                SET_IREF_SELECT,
                0x30,  # enable internal IREF during display on
                # charge pump
                SET_CHARGE_PUMP,
                0x10 if self.external_vcc else 0x14,
                SET_DISP | 0x01,  # display on
        ):  # on
            self.write_cmd(cmd)
        self.fill(0)
        self.show()

    def poweroff(self):
        self.write_cmd(SET_DISP)

    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 rotate(self, rotate):
        self.write_cmd(SET_COM_OUT_DIR | ((rotate & 1) << 3))
        self.write_cmd(SET_SEG_REMAP | (rotate & 1))

    def show(self):
        x0 = 0
        x1 = self.width - 1
        if self.width != 128:
            # narrow displays use centred columns
            col_offset = (128 - self.width) // 2
            x0 += col_offset
            x1 += col_offset
        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)
        
    def clear(self):
        self.fill(0)
        self.show()
    
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)

字体下载以及字体制作工具

链接: https://pan.baidu.com/s/1MNzWo_o5b60dLW-63FhiSA 提取码: 6f8z 复制这段内容后打开百度网盘手机App,操作更方便哦

六、字体制作

        制作方法也很简单,从上面的下载 字体制作工具,然后打开按照如下步骤操作。

物联网开发100 - Micropython ESP32 S3非取字模和刷写中文固件方式显示中文_第9张图片

 物联网开发100 - Micropython ESP32 S3非取字模和刷写中文固件方式显示中文_第10张图片

七、ESP32 S3开发板以及ssd1306屏幕购买

开发板购买地址:

https://item.taobao.com/item.htm?spm=a230r.1.14.1.36d12eefkcfEuf&id=696836421009&ns=1&abbucket=8#detailicon-default.png?t=N2N8https://item.taobao.com/item.htm?spm=a230r.1.14.1.36d12eefkcfEuf&id=696836421009&ns=1&abbucket=8#detail

屏幕购买地址:

https://item.taobao.com/item.htm?spm=a1z09.2.0.0.6ea92e8dLMqNQc&id=665786770176&_u=pp01rchb17eicon-default.png?t=N2N8https://item.taobao.com/item.htm?spm=a1z09.2.0.0.6ea92e8dLMqNQc&id=665786770176&_u=pp01rchb17e

你可能感兴趣的:(物联网开发,MicroPython,物联网,Micropython)