├── LICENSE ├── README.md ├── demo ├── epaper1in54_demo.py ├── ssd1306_demo.py └── st7735_demo.py ├── doc ├── MicroPython 中文字库的使用演示文档.md └── 如何生成点阵字体文件.md ├── driver ├── e1in54.py ├── ssd1306.py ├── st7735.py ├── st7789.py └── st77xx.py ├── requirements.txt ├── text.txt ├── ufont.py └── unifont-14-12917-16.v3.bmf /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 AntonVanke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Micropython μFont 2 | 3 | Micropython 的字体模块,可以用来显示所有`Unicode`字符。 4 | 5 | ## 硬件要求 6 | 7 | 1. 运行`micropython`的开发板,且`micropython>=1.17` 8 | 2. 使用`SSD1306`驱动芯片的`OLED屏幕`或者是`ST7735`驱动芯片的`LCD`屏幕亦或是`1.54英寸的e-Paper` 9 | (只要是使用`FrameBuffer`的屏幕都支持,但本项目提供的驱动只有这三种) 10 | 3. 如果想要在`OLED`或者`e-Paper`上使用`ufont`显示支持`GB2312`的所有字符,则至少 `230Kbyte` 的空闲 ROM 空间和 `20 Kbyte`的空闲内存 11 | 如果想要在`ST7735`上使用`ufont`显示支持`GB2312`的所有字符,则至少 `230Kbyte` 的空闲 ROM 空间和 `100 Kbyte`的空闲内存 12 | 13 | ## 快速上手 14 | 15 | 1. 准备运行`micropython`的开发板和一个`SSD1306`的`OLED`屏幕,并完成连接 16 | 17 | 2. 将`demo/ssd1306_demo.py`用编辑器打开 18 | 19 | ```python 20 | # 修改为对应的 Pin 21 | i2c = I2C(scl=Pin(2), sda=Pin(3)) # Line 29 22 | ``` 23 | 24 | 3. 依次将`demo/ssd1306_demo.py`、`driver/ssd1306.py`、`ufont.py`、`unifont-14-12917-16.v3.bmf`上传到**开发板根目录**,运行`ssd1306_demo.py`即可 25 | 26 | ## 使用方法 27 | 28 | 仅需三步就能使用`ufont`: 29 | 30 | ```python 31 | # 第一步:导入 ufont 库 32 | import ufont 33 | ··· 34 | # 第二步:加载字体 35 | font = ufont.BMFont("unifont-14-12917-16.v3.bmf") 36 | ··· 37 | # 第三步:显示文字 38 | font.text(display, "你好", 48, 16, show=True) 39 | ``` 40 | 41 | ### 详细参数 42 | 43 | ```python 44 | text(display, # 显示对象 45 | string: str, # 显示文字 46 | x: int, # 字符串左上角 x 轴 47 | y: int, # 字符串左上角 y 轴 48 | color: int = 0xFFFF, # 字体颜色(RGB565) 49 | bg_color: int = 0, # 字体背景颜色(RGB565) 50 | font_size: int = None, # 字号大小 51 | half_char: bool = True, # 半宽显示 ASCII 字符 52 | auto_wrap: bool = False, # 自动换行 53 | show: bool = True, # 实时显示 54 | clear: bool = False, # 清除之前显示内容 55 | alpha_color: bool = 0, # 透明色(RGB565) 当颜色与 alpha_color 相同时则透明 56 | reverse: bool = False, # 逆置(MONO) 57 | color_type: int = -1, # 色彩模式 0:MONO 1:RGB565 58 | line_spacing: int = 0, # 行间距 59 | **kwargs) 60 | ``` 61 | 62 | 63 | 64 | ## 示例程序 65 | 66 | 1. `SSD1306`演示程序 67 | 68 | ```python 69 | """ 70 | SSD1306(OLED 128*64) 屏幕中文测试 71 | Micropython版本: 1.19.1 72 | 演示硬件: 73 | SSD1306(OLED 128*64 IIC) 74 | 合宙ESP32C3(without ch343) 75 | 所需文件: 76 | ufont.py 77 | unifont-14-12917-16.v3.bmf 78 | ssd1306.py 79 | 链接引脚: 80 | SCL = 2 81 | SDA = 3 82 | 使用字体: unifont-14-12917-16.v3.bmf 83 | """ 84 | import random 85 | import time 86 | 87 | from machine import I2C, Pin 88 | 89 | import ufont 90 | import ssd1306 91 | 92 | 93 | def wait(info, _t=5): 94 | print(info) 95 | time.sleep(_t) 96 | 97 | 98 | i2c = I2C(scl=Pin(2), sda=Pin(3)) 99 | display = ssd1306.SSD1306_I2C(128, 64, i2c) 100 | 101 | # 载入字体 102 | # 使用字体制作工具:https://github.com/AntonVanke/MicroPython_BitMap_Tools 103 | font = ufont.BMFont("unifont-14-12917-16.v3.bmf") 104 | 105 | wait(""" 106 | # 最简单的显示 "你好" 107 | # 其中指定 `show=True` 使得屏幕及时更新 108 | """, 6) 109 | font.text(display, "你好", 0, 0, show=True) 110 | 111 | wait(""" 112 | # 如果想让文字显示在屏幕正中间,可以通过指定文本左上角位置来修改显示位置 113 | """, 5) 114 | font.text(display, "你好", 48, 16, show=True) 115 | 116 | wait(""" 117 | # 此时你会发现:上一次显示显示的文字不会消失。因为你没有指定清屏参数:`clear=True`;让我们再试一次 118 | # 注意,请使用修改后的 `ssd1306.py` 驱动,否则请自行调用`display.fill(0)` 119 | """, 10) 120 | font.text(display, "你好", 48, 16, show=True, clear=True) 121 | 122 | wait(""" 123 | # 显示英文呢? 124 | """, 3) 125 | font.text(display, "He110", 48, 8, show=True, clear=True) 126 | font.text(display, "你好", 48, 24, show=True) 127 | 128 | wait(""" 129 | # 会发现一个汉字的宽度大概是字母的两倍,如果你需要等宽,可以指定参数 `half_char=False` 130 | """, 6) 131 | font.text(display, "HELLO", 32, 16, show=True, clear=True, half_char=False) 132 | 133 | wait(""" 134 | # 显示的文字如果很长,会超出屏幕边界,例如: 135 | """, 3) 136 | poem = "他日若遂凌云志,敢笑黄巢不丈夫!" 137 | font.text(display, poem, 0, 8, show=True, clear=True) 138 | 139 | wait(""" 140 | # 此时,需要指定参数 `auto_wrap=True` 来自动换行 141 | """, 5) 142 | font.text(display, poem, 0, 8, show=True, clear=True, auto_wrap=True) 143 | 144 | wait(""" 145 | # 自动换行的行间距太小了? 146 | # 添加 `line_spacing: int` 参数来调整行间距, 此处指定 8 个像素 147 | """, 8) 148 | font.text(display, poem, 0, 8, show=True, clear=True, auto_wrap=True, line_spacing=8) 149 | 150 | wait(""" 151 | # 调整字体大小,可以指定 `font_size: int` 参数 152 | # 注意:这会严重增加运行时间 153 | """, 8) 154 | font.text(display, "T:" + str(random.randint(-40, 40)) + "℃", 24, 8, font_size=32, show=True, clear=True) 155 | 156 | wait(""" 157 | # 当你使用墨水屏时,颜色可能会出现反转。或者你主动想要颜色反转 158 | # 可以指定参数 `reverse=Ture` 159 | """, 8) 160 | font.text(display, "T:" + str(random.randint(-40, 40)) + "℃", 24, 8, font_size=32, show=True, clear=True, reverse=True) 161 | 162 | ``` 163 | 164 | 165 | 166 | 2. `ST7735`演示程序 167 | 168 | ```python 169 | """ 170 | ST7735(LCD 160*80) 屏幕中文测试 171 | Micropython版本: 1.19.1 172 | 演示硬件: 173 | 合宙 Air10x 系列屏幕扩展板 174 | 合宙ESP32C3(without ch343) 175 | 所需文件: 176 | ufont.py 177 | unifont-14-12917-16.v3.bmf 178 | st7735.py 179 | 链接引脚: 180 | SCL = 2 181 | SDA = 3 182 | RST = 10 183 | DC = 6 184 | CS = 7 185 | BL = 11 186 | 使用字体: unifont-14-12917-16.v3.bmf 187 | """ 188 | import random 189 | import time 190 | 191 | from machine import SPI, Pin 192 | 193 | import ufont 194 | from st7735 import ST7735 195 | 196 | spi = SPI(1, 30000000, sck=Pin(2), mosi=Pin(3)) 197 | display = ST7735(spi=spi, cs=7, dc=6, rst=10, bl=11, width=160, height=80, rotate=1) 198 | 199 | 200 | def wait(info, _t=5): 201 | print(info) 202 | time.sleep(_t) 203 | 204 | 205 | # 载入字体 206 | # 使用字体制作工具:https://github.com/AntonVanke/MicroPython_BitMap_Tools 207 | font = ufont.BMFont("unifont-14-12917-16.v3.bmf") 208 | 209 | wait(""" 210 | # 最简单的显示 "你好" 211 | # 其中指定 `show=True` 使得屏幕及时更新 212 | """, 6) 213 | font.text(display, "你好", 0, 0, show=True) 214 | 215 | wait(""" 216 | # 如果想让文字显示在屏幕正中间,可以通过指定文本左上角位置来修改显示位置 217 | """, 5) 218 | font.text(display, "你好", 64, 32, show=True) 219 | 220 | wait(""" 221 | # 此时你会发现:上一次显示显示的文字不会消失。因为你没有指定清屏参数:`clear=True`;让我们再试一次 222 | """, 6) 223 | font.text(display, "你好", 64, 32, show=True, clear=True) 224 | 225 | wait(""" 226 | # 显示英文呢? 227 | """, 3) 228 | font.text(display, "He110", 64, 26, show=True, clear=True) 229 | font.text(display, "你好", 64, 42, show=True) 230 | 231 | wait(""" 232 | # 会发现一个汉字的宽度大概是字母的两倍,如果你需要等宽,可以指定参数 `half_char=False` 233 | """, 6) 234 | font.text(display, "HELLO", 48, 24, show=True, clear=True, half_char=False) 235 | 236 | wait(""" 237 | # 可以通过指定参数 `color` 来指定字体颜色,其中 color 是 RGB565 格式 238 | """, 6) 239 | font.text(display, "hello", 48, 32, color=0xff00, show=True) 240 | 241 | wait(""" 242 | # 同样,我们可以通过指定 `bg_color` 参数调整背景颜色 243 | """) 244 | font.text(display, "你好", 56, 28, color=0xff00, bg_color=0x00ff, show=True) 245 | 246 | wait(""" 247 | # 大一点?可以使用 `font_size` 指定字号大小 248 | # 注意:放大彩色字体对内存的要求十分巨大 249 | """) 250 | font.text(display, "Temp: 15℃", 0, 26, font_size=32, color=0xff00, bg_color=0x00ff, show=True, clear=True) 251 | 252 | ``` 253 | 254 | 255 | 256 | ## 字体制作工具 257 | 258 | #### GITHUB 259 | 260 | [MicroPython-uFont-Tools/如何生成点阵字体文件.md at master · AntonVanke/MicroPython-uFont-Tools · GitHub](https://github.com/AntonVanke/MicroPython-uFont-Tools/blob/master/doc/如何生成点阵字体文件.md) 261 | 262 | #### GITEE 263 | 264 | [MicroPython-uFont-Tools: MicroPython uFont 工具 (gitee.com)](https://gitee.com/liio/MicroPython-uFont-Tools) 265 | 266 | ## 更多信息 267 | 268 | #### VIDEOS: 269 | 270 | 1. [MicroPython中文字库教程_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV12B4y1B7Ff/) 271 | 2. [MicroPython中文字库:自定义字体生成_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1YD4y16739/) 272 | 273 | #### GITEE: 274 | 275 | [MicroPython-Chinese-Font: MicroPython 的中文字库,使 MicroPython 能够显示中文 当然,不止能够显示中文,还可以显示所有 Unicode 字符 (gitee.com)](https://gitee.com/liio/MicroPython-Chinese-Font) 276 | 277 | [MicroPython-uFont-Tools: MicroPython uFont 工具 (gitee.com)](https://gitee.com/liio/MicroPython-uFont-Tools) 278 | 279 | -------------------------------------------------------------------------------- /demo/epaper1in54_demo.py: -------------------------------------------------------------------------------- 1 | from machine import SPI, Pin 2 | from e1in54 import EPD 3 | import time 4 | import ufont 5 | 6 | spi = SPI(1, 30000000, sck=Pin(2), mosi=Pin(3)) 7 | display = EPD(spi, cs=18, dc=12, rst=1, busy=19) 8 | poem_info = "\t登高\n唐·杜甫" 9 | poem = "风急天高猿啸哀渚清沙白鸟飞回无边落木萧萧下不尽长江滚滚来万里悲秋常作客百年多病独登台艰难苦恨繁霜鬓潦倒新停浊酒杯" 10 | f = ufont.BMFont("unifont-14-12888-16.v3.bmf") 11 | 12 | 13 | def test1(): 14 | display.fill(1) 15 | display.set_refresh(True) 16 | f.text(display, poem_info, 40, 50, font_size=32, reverse=True, clear=True, show=True) 17 | display.set_refresh(False) 18 | f.clear(display, 1) 19 | for _ in range(0, len(poem), 7): 20 | time.sleep(0.8) 21 | f.text(display=display, string=poem[_: _ + 7], x=2, y=(_ // 7) * 25, font_size=25, clear=False, show=True, 22 | reverse=True) 23 | display.set_refresh(True) 24 | 25 | 26 | def test2(): 27 | f.text(display=display, string="测试Test", x=0, y=0, font_size=16, show=True, clear=True, reverse=True) 28 | f.text(display=display, string="测试Test", x=0, y=16, font_size=24, show=True) 29 | f.text(display=display, string="测试Test", x=0, y=40, font_size=32, show=True, reverse=True) 30 | display.set_refresh(False) 31 | f.text(display=display, string="测试Test", x=0, y=72, font_size=40, show=True, reverse=False) 32 | f.text(display=display, string="测试Test", x=0, y=112, font_size=48, show=True, reverse=True) 33 | f.text(display=display, string="ePaperTest", x=0, y=160, font_size=40, show=True, reverse=False) 34 | f.text(display=display, string="O\nK", x=152, y=0, font_size=48, show=True) 35 | display.set_refresh(True) 36 | 37 | 38 | test1() 39 | test2() 40 | -------------------------------------------------------------------------------- /demo/ssd1306_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | SSD1306(OLED 128*64) 屏幕中文测试 3 | Micropython版本: 1.19.1 4 | 演示硬件: 5 | SSD1306(OLED 128*64 IIC) 6 | 合宙ESP32C3(without ch343) 7 | 所需文件: 8 | ufont.py 9 | unifont-14-12917-16.v3.bmf 10 | ssd1306.py 11 | 链接引脚: 12 | SCL = 2 13 | SDA = 3 14 | 使用字体: unifont-14-12917-16.v3.bmf 15 | """ 16 | import time 17 | 18 | from machine import I2C, Pin 19 | 20 | import ufont 21 | import ssd1306 22 | 23 | 24 | def wait(info, _t=5): 25 | print(info) 26 | time.sleep(_t) 27 | 28 | 29 | # 请修改为对应 FootPrint 30 | i2c = I2C(scl=Pin(2), sda=Pin(3)) 31 | display = ssd1306.SSD1306_I2C(128, 64, i2c) 32 | 33 | # 载入字体 34 | # 使用字体制作工具:https://github.com/AntonVanke/MicroPython_BitMap_Tools 35 | font = ufont.BMFont("unifont-14-12917-16.v3.bmf") 36 | 37 | wait(""" 38 | # 最简单的显示 "你好" 39 | # 其中指定 `show=True` 使得屏幕及时更新 40 | """, 6) 41 | font.text(display, "你好", 0, 0, show=True) 42 | 43 | wait(""" 44 | # 如果想让文字显示在屏幕正中间,可以通过指定文本左上角位置来修改显示位置 45 | """, 5) 46 | font.text(display, "你好", 48, 16, show=True) 47 | 48 | wait(""" 49 | # 此时你会发现:上一次显示显示的文字不会消失。因为你没有指定清屏参数:`clear=True`;让我们再试一次 50 | # 注意,请使用修改后的 `ssd1306.py` 驱动,否则请自行调用`display.fill(0)` 51 | """, 10) 52 | font.text(display, "你好", 48, 16, show=True, clear=True) 53 | 54 | wait(""" 55 | # 显示英文呢? 56 | """, 3) 57 | font.text(display, "He110", 48, 8, show=True, clear=True) 58 | font.text(display, "你好", 48, 24, show=True) 59 | 60 | wait(""" 61 | # 会发现一个汉字的宽度大概是字母的两倍,如果你需要等宽,可以指定参数 `half_char=False` 62 | """, 6) 63 | font.text(display, "HELLO", 32, 16, show=True, clear=True, half_char=False) 64 | 65 | wait(""" 66 | # 显示的文字如果很长,会超出屏幕边界,例如: 67 | """, 3) 68 | poem = "他日若遂凌云志,敢笑黄巢不丈夫!" 69 | font.text(display, poem, 0, 8, show=True, clear=True) 70 | 71 | wait(""" 72 | # 此时,需要指定参数 `auto_wrap=True` 来自动换行 73 | """, 5) 74 | font.text(display, poem, 0, 8, show=True, clear=True, auto_wrap=True) 75 | 76 | wait(""" 77 | # 自动换行的行间距太小了? 78 | # 添加 `line_spacing: int` 参数来调整行间距, 此处指定 8 个像素 79 | """, 8) 80 | font.text(display, poem, 0, 8, show=True, clear=True, auto_wrap=True, line_spacing=8) 81 | 82 | wait(""" 83 | # 调整字体大小,可以指定 `font_size: int` 参数 84 | # 注意:这会严重增加运行时间 85 | """, 8) 86 | font.text(display, "T:15℃", 24, 8, font_size=32, show=True, clear=True) 87 | 88 | wait(""" 89 | # 当你使用墨水屏时,颜色可能会出现反转。或者你主动想要颜色反转 90 | # 可以指定参数 `reverse=Ture` 91 | """, 8) 92 | font.text(display, "T:15℃", 24, 8, font_size=32, show=True, clear=True, reverse=True) 93 | -------------------------------------------------------------------------------- /demo/st7735_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | ST7735(LCD 160*80) 屏幕中文测试 3 | Micropython版本: 1.19.1 4 | 演示硬件: 5 | 合宙 Air10x 系列屏幕扩展板 6 | 合宙ESP32C3(without ch343) 7 | 所需文件: 8 | ufont.py 9 | unifont-14-12917-16.v3.bmf 10 | st7735.py 11 | 链接引脚: 12 | SCL = 2 13 | SDA = 3 14 | RST = 10 15 | DC = 6 16 | CS = 7 17 | BL = 11 18 | 使用字体: unifont-14-12917-16.v3.bmf 19 | """ 20 | import random 21 | import time 22 | 23 | from machine import SPI, Pin 24 | 25 | import ufont 26 | from st7735 import ST7735 27 | 28 | # 请修改为对应 FootPrint 29 | spi = SPI(1, 30000000, sck=Pin(2), mosi=Pin(3)) 30 | display = ST7735(spi=spi, cs=7, dc=6, rst=10, bl=11, width=160, height=80, rotate=1) 31 | 32 | 33 | def wait(info, _t=5): 34 | print(info) 35 | time.sleep(_t) 36 | 37 | 38 | # 载入字体 39 | # 使用字体制作工具:https://github.com/AntonVanke/MicroPython-ufont-Tools 40 | font = ufont.BMFont("unifont-14-12917-16.v3.bmf") 41 | 42 | wait(""" 43 | # 最简单的显示 "你好" 44 | # 其中指定 `show=True` 使得屏幕及时更新 45 | """, 6) 46 | font.text(display, "你好", 0, 0, show=True) 47 | 48 | wait(""" 49 | # 如果想让文字显示在屏幕正中间,可以通过指定文本左上角位置来修改显示位置 50 | """, 5) 51 | font.text(display, "你好", 64, 32, show=True) 52 | 53 | wait(""" 54 | # 此时你会发现:上一次显示显示的文字不会消失。因为你没有指定清屏参数:`clear=True`;让我们再试一次 55 | """, 6) 56 | font.text(display, "你好", 64, 32, show=True, clear=True) 57 | 58 | wait(""" 59 | # 显示英文呢? 60 | """, 3) 61 | font.text(display, "He110", 64, 26, show=True, clear=True) 62 | font.text(display, "你好", 64, 42, show=True) 63 | 64 | wait(""" 65 | # 会发现一个汉字的宽度大概是字母的两倍,如果你需要等宽,可以指定参数 `half_char=False` 66 | """, 6) 67 | font.text(display, "HELLO", 48, 24, show=True, clear=True, half_char=False) 68 | 69 | wait(""" 70 | # 可以通过指定参数 `color` 来指定字体颜色,其中 color 是 RGB565 格式 71 | """, 6) 72 | font.text(display, "hello", 48, 32, color=0xff00, show=True) 73 | 74 | wait(""" 75 | # 同样,我们可以通过指定 `bg_color` 参数调整背景颜色 76 | """) 77 | font.text(display, "你好", 56, 28, color=0xff00, bg_color=0x00ff, show=True) 78 | 79 | wait(""" 80 | # 大一点?可以使用 `font_size` 指定字号大小 81 | # 注意:放大彩色字体对内存的要求十分巨大 82 | """) 83 | font.text(display, "Temp: 15℃", 0, 26, font_size=32, color=0xff00, bg_color=0x00ff, show=True, clear=True) 84 | -------------------------------------------------------------------------------- /doc/MicroPython 中文字库的使用演示文档.md: -------------------------------------------------------------------------------- 1 | ## MicroPython 中文字库[ufont]的使用 2 | 3 | Github: https://github.com/AntonVanke/MicroPython-Chinese-Font 4 | 5 | #### 使用方法 6 | 7 | 以【合宙 Air10x 系列屏幕扩展板驱动】为例 8 | 9 | 1. 如何确认驱动是否支持? 10 | 11 | ```python 12 | from st7735 import ST7735 13 | from framebuf import FrameBuffer 14 | print(issubclass(ST7735, FrameBuffer)) # True: 支持;False: 不支持 15 | ``` 16 | 17 | 如果返回的是`True`则支持,`False`的话需要一些额外的操作 18 | 19 | 2. 先初始化屏幕 20 | 21 | ```python 22 | from machine import SPI, Pin 23 | from st7735 import ST7735, color 24 | spi = SPI(1, 30000000, sck=Pin(2), mosi=Pin(3)) 25 | display = ST7735(spi=spi, cs=18, dc=12, rst=1, bl=19, width=160, height=80, rotate=1) 26 | ``` 27 | 28 | 3. 载入字体 29 | 30 | - 上传`ufont.py`、`unifont-14-12888-16.v3.bmf`到开发板 31 | 32 | ```python 33 | import ufont 34 | f = ufont.BMFont("../unifont-14-12888-16.v3.bmf") 35 | ``` 36 | 37 | 4. 测试字体 38 | 39 | 1. 基本使用 40 | 41 | - `text(显示实例,文字,x轴,y轴,颜色 )` 42 | 43 | - `show = True` 立即显示,就不需要执行 `display.show() ` 44 | 45 | ```python 46 | f.text(display, "你好", 0, 0, color=0xf8, show=True) 47 | ``` 48 | 49 | 2. 如何设置字号 50 | 51 | ```python 52 | f.text(display, "你好", 32, 0, color=0xe007, font_size=18, show=True) 53 | ``` 54 | 55 | 3. 如何显示反转背景的字体 56 | 57 | ```python 58 | f.text(display, "你好", 68, 0, color=0xe007, reverse=True, show=True) 59 | ``` 60 | 61 | 4. 如何显示的时候清除之前的内容 62 | 63 | ```python 64 | f.text(display, "你好\nabCD123", 0, 0,font_size=16, color=0xf8, clear=True, show=True) 65 | ``` 66 | 67 | 5. 当某些字体需要全格显示时 68 | 69 | - `half_char=False` 英文数字全格显示 70 | 71 | ```python 72 | f2 = ufont.BMFont("DIGITAL-Dreamfat-95-16.v3.bmf") # 载入特殊字体 73 | f2.text(display, "abCD123", 0, 32,font_size=16, color=0xe007,half_char=False,show=True) 74 | ``` -------------------------------------------------------------------------------- /doc/如何生成点阵字体文件.md: -------------------------------------------------------------------------------- 1 | #### 下载应用程序 2 | 3 | [ufont 点阵字体生成工具](https://github.com/AntonVanke/MicroPython_BitMap_Tools/releases/tag/v0.0.1) 4 | 5 | #### 详见 6 | 7 | [如何生成点阵字体文件](https://github.com/AntonVanke/MicroPython_BitMap_Tools/blob/master/doc/如何生成点阵字体文件.md) 8 | 9 | -------------------------------------------------------------------------------- /driver/e1in54.py: -------------------------------------------------------------------------------- 1 | # 来源:QQ群786510434、https://github.com/mcauser/micropython-waveshare-epaper/blob/master/epaper1in54.py 2 | from micropython import const 3 | from time import sleep_ms 4 | import ustruct 5 | from machine import Pin 6 | import framebuf 7 | 8 | # Display resolution 9 | EPD_WIDTH = const(200) 10 | EPD_HEIGHT = const(200) 11 | 12 | # Display commands 13 | DRIVER_OUTPUT_CONTROL = const(0x01) 14 | BOOSTER_SOFT_START_CONTROL = const(0x0C) 15 | # GATE_SCAN_START_POSITION = const(0x0F) 16 | DEEP_SLEEP_MODE = const(0x10) 17 | DATA_ENTRY_MODE_SETTING = const(0x11) 18 | # SW_RESET = const(0x12) 19 | # TEMPERATURE_SENSOR_CONTROL = const(0x1A) 20 | MASTER_ACTIVATION = const(0x20) 21 | # DISPLAY_UPDATE_CONTROL_1 = const(0x21) 22 | DISPLAY_UPDATE_CONTROL_2 = const(0x22) 23 | WRITE_RAM = const(0x24) 24 | WRITE_VCOM_REGISTER = const(0x2C) 25 | WRITE_LUT_REGISTER = const(0x32) 26 | SET_DUMMY_LINE_PERIOD = const(0x3A) 27 | SET_GATE_TIME = const(0x3B) # not in datasheet 28 | # BORDER_WAVEFORM_CONTROL = const(0x3C) 29 | SET_RAM_X_ADDRESS_START_END_POSITION = const(0x44) 30 | SET_RAM_Y_ADDRESS_START_END_POSITION = const(0x45) 31 | SET_RAM_X_ADDRESS_COUNTER = const(0x4E) 32 | SET_RAM_Y_ADDRESS_COUNTER = const(0x4F) 33 | TERMINATE_FRAME_READ_WRITE = const(0xFF) # aka NOOP 34 | 35 | 36 | class EPD(framebuf.FrameBuffer): 37 | LUT_FULL_UPDATE = bytearray( 38 | b'\x02\x02\x01\x11\x12\x12\x22\x22\x66\x69\x69\x59\x58\x99\x99\x88\x00\x00\x00\x00\xF8\xB4\x13\x51\x35\x51\x51\x19\x01\x00') 39 | LUT_PARTIAL_UPDATE = bytearray( 40 | b'\x10\x18\x18\x08\x18\x18\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x14\x44\x12\x00\x00\x00\x00\x00\x00') 41 | 42 | def __init__(self, spi, cs, dc, rst, busy): 43 | self.spi = spi 44 | self.cs = Pin(cs, Pin.OUT, value=1) 45 | self.dc = Pin(dc, Pin.OUT, value=0) 46 | self.rst = Pin(rst, Pin.OUT, value=0) 47 | self.busy = Pin(busy, Pin.IN) 48 | self.width = EPD_WIDTH 49 | self.height = EPD_HEIGHT 50 | self.pages = self.height // 8 51 | self.buffer = bytearray(self.width * self.pages) 52 | super().__init__(self.buffer, self.width, self.height, 53 | framebuf.MONO_HLSB) 54 | self.init() 55 | 56 | def clear(self): 57 | self.buffer = (b'\xff' * self.width * self.pages) 58 | 59 | def show(self): 60 | self.set_frame_memory(self.buffer, 0, 0, 200, 200) 61 | self.display_frame() 62 | 63 | def _command(self, command, data=None): 64 | self.dc.value(0) 65 | self.cs.value(0) 66 | self.spi.write(command.to_bytes(1, 'big')) 67 | # self.spi.write(bytearray([command])) 68 | self.cs.value(1) 69 | if data is not None: 70 | self._data(data) 71 | 72 | def _data(self, data): 73 | self.dc.value(1) 74 | self.cs.value(0) 75 | self.spi.write(data) 76 | self.cs.value(1) 77 | 78 | def init(self): 79 | self.reset() 80 | self._command(DRIVER_OUTPUT_CONTROL) 81 | self._data(bytearray([(EPD_HEIGHT - 1) & 0xFF])) 82 | self._data(bytearray([((EPD_HEIGHT - 1) >> 8) & 0xFF])) 83 | self._data(bytearray([0x00])) # GD = 0 SM = 0 TB = 0 84 | self._command(BOOSTER_SOFT_START_CONTROL, b'\xD7\xD6\x9D') 85 | self._command(WRITE_VCOM_REGISTER, b'\xA8') # VCOM 7C 86 | self._command(SET_DUMMY_LINE_PERIOD, b'\x1A') # 4 dummy lines per gate 87 | self._command(SET_GATE_TIME, b'\x08') # 2us per line 88 | self._command(DATA_ENTRY_MODE_SETTING, b'\x03') # X increment Y increment 89 | # self._command(DATA_ENTRY_MODE_SETTING, b'\x07') # X increment Y increment 90 | self.set_lut(self.LUT_FULL_UPDATE) 91 | 92 | def wait_until_idle(self): 93 | while self.busy.value() == 1: 94 | sleep_ms(100) 95 | 96 | def reset(self): 97 | self.rst.value(0) 98 | sleep_ms(200) 99 | self.rst.value(1) 100 | sleep_ms(200) 101 | 102 | def set_lut(self, lut): 103 | self._command(WRITE_LUT_REGISTER, lut) 104 | 105 | # put an image in the frame memory 106 | def set_frame_memory(self, image, x, y, w, h): 107 | # x point must be the multiple of 8 or the last 3 bits will be ignored 108 | x = x & 0xF8 109 | w = w & 0xF8 110 | 111 | if x + w >= self.width: 112 | x_end = self.width - 1 113 | else: 114 | x_end = x + w - 1 115 | 116 | if y + h >= self.height: 117 | y_end = self.height - 1 118 | else: 119 | y_end = y + h - 1 120 | 121 | self.set_memory_area(x, y, x_end, y_end) 122 | self.set_memory_pointer(x, y) 123 | self._command(WRITE_RAM, image) 124 | 125 | # replace the frame memory with the specified color 126 | def clear_frame_memory(self, color): 127 | self.set_memory_area(0, 0, self.width - 1, self.height - 1) 128 | self.set_memory_pointer(0, 0) 129 | self._command(WRITE_RAM) 130 | # send the color data 131 | for i in range(0, self.width // 8 * self.height): 132 | # self._data(bytearray([color])) 133 | self._data(bytearray(color)) 134 | 135 | # draw the current frame memory and switch to the next memory area 136 | def display_frame(self): 137 | self._command(DISPLAY_UPDATE_CONTROL_2, b'\xC4') 138 | self._command(MASTER_ACTIVATION) 139 | self._command(TERMINATE_FRAME_READ_WRITE) 140 | self.wait_until_idle() 141 | 142 | # specify the memory area for data R/W 143 | def set_memory_area(self, x_start, y_start, x_end, y_end): 144 | self._command(SET_RAM_X_ADDRESS_START_END_POSITION) 145 | # x point must be the multiple of 8 or the last 3 bits will be ignored 146 | self._data(bytearray([(x_start >> 3) & 0xFF])) 147 | self._data(bytearray([(x_end >> 3) & 0xFF])) 148 | self._command(SET_RAM_Y_ADDRESS_START_END_POSITION, ustruct.pack("> 3) & 0xFF])) 155 | self._command(SET_RAM_Y_ADDRESS_COUNTER, ustruct.pack(" 2 * self.height else 0x12, 55 | # timing and driving scheme 56 | SET_DISP_CLK_DIV, 57 | 0x80, 58 | SET_PRECHARGE, 59 | 0x22 if self.external_vcc else 0xF1, 60 | SET_VCOM_DESEL, 61 | 0x30, # 0.83*Vcc 62 | # display 63 | SET_CONTRAST, 64 | 0xFF, # maximum 65 | SET_ENTIRE_ON, # output follows RAM contents 66 | SET_NORM_INV, # not inverted 67 | SET_IREF_SELECT, 68 | 0x30, # enable internal IREF during display on 69 | # charge pump 70 | SET_CHARGE_PUMP, 71 | 0x10 if self.external_vcc else 0x14, 72 | SET_DISP | 0x01, # display on 73 | ): # on 74 | self.write_cmd(cmd) 75 | self.fill(0) 76 | self.show() 77 | 78 | def poweroff(self): 79 | self.write_cmd(SET_DISP) 80 | 81 | def poweron(self): 82 | self.write_cmd(SET_DISP | 0x01) 83 | 84 | def contrast(self, contrast): 85 | self.write_cmd(SET_CONTRAST) 86 | self.write_cmd(contrast) 87 | 88 | def invert(self, invert): 89 | self.write_cmd(SET_NORM_INV | (invert & 1)) 90 | 91 | def rotate(self, rotate): 92 | self.write_cmd(SET_COM_OUT_DIR | ((rotate & 1) << 3)) 93 | self.write_cmd(SET_SEG_REMAP | (rotate & 1)) 94 | 95 | def show(self): 96 | x0 = 0 97 | x1 = self.width - 1 98 | if self.width != 128: 99 | # narrow displays use centred columns 100 | col_offset = (128 - self.width) // 2 101 | x0 += col_offset 102 | x1 += col_offset 103 | self.write_cmd(SET_COL_ADDR) 104 | self.write_cmd(x0) 105 | self.write_cmd(x1) 106 | self.write_cmd(SET_PAGE_ADDR) 107 | self.write_cmd(0) 108 | self.write_cmd(self.pages - 1) 109 | self.write_data(self.buffer) 110 | 111 | def clear(self): 112 | self.fill(0) 113 | 114 | 115 | class SSD1306_I2C(SSD1306): 116 | def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): 117 | self.i2c = i2c 118 | self.addr = addr 119 | self.temp = bytearray(2) 120 | self.write_list = [b"\x40", None] # Co=0, D/C#=1 121 | super().__init__(width, height, external_vcc) 122 | 123 | def write_cmd(self, cmd): 124 | self.temp[0] = 0x80 # Co=1, D/C#=0 125 | self.temp[1] = cmd 126 | self.i2c.writeto(self.addr, self.temp) 127 | 128 | def write_data(self, buf): 129 | self.write_list[1] = buf 130 | self.i2c.writevto(self.addr, self.write_list) 131 | 132 | 133 | class SSD1306_SPI(SSD1306): 134 | def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): 135 | self.rate = 10 * 1024 * 1024 136 | dc.init(dc.OUT, value=0) 137 | res.init(res.OUT, value=0) 138 | cs.init(cs.OUT, value=1) 139 | self.spi = spi 140 | self.dc = dc 141 | self.res = res 142 | self.cs = cs 143 | import time 144 | 145 | self.res(1) 146 | time.sleep_ms(1) 147 | self.res(0) 148 | time.sleep_ms(10) 149 | self.res(1) 150 | super().__init__(width, height, external_vcc) 151 | 152 | def write_cmd(self, cmd): 153 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 154 | self.cs(1) 155 | self.dc(0) 156 | self.cs(0) 157 | self.spi.write(bytearray([cmd])) 158 | self.cs(1) 159 | 160 | def write_data(self, buf): 161 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 162 | self.cs(1) 163 | self.dc(1) 164 | self.cs(0) 165 | self.spi.write(buf) 166 | self.cs(1) 167 | -------------------------------------------------------------------------------- /driver/st7735.py: -------------------------------------------------------------------------------- 1 | """ 2 | 建议使用 st77xx 驱动 3 | 4 | --- 5 | 6 | 合宙 Air10x 系列屏幕扩展板驱动 7 | 160(H)RGB x 80(V) 8 | 9 | 使用方法(以合宙ESP32C3为例): 10 | from machine import SPI, Pin 11 | from st7735 import ST7735 12 | 13 | spi = SPI(1, 30000000, sck=Pin(2), mosi=Pin(3)) 14 | ST7735(spi, rst=10, dc=6, cs=7, bl=11, width=160, height=80, rotate=1) # 直插横屏显示 15 | ST7735(spi, rst=10, dc=6, cs=7, bl=11, width=160, height=80, rotate=0) # 直插竖屏显示 16 | 17 | 本款LCD使用的内置控制器为ST7735S,是一款162 x RGB x 132像素的LCD控制器,而本LCD本身的像素为160(H)RGB x 80(V)。由于LCD的显示 18 | 起始位置与控制器的原点不一致,因此在使用控制器初始化显示全屏显示区域时需要对做偏移处理:水平方向从第二个像素点开始显示,垂直方向从第27个像素点 19 | 开始。这样就可以保证显示的LCD中RAM对应的位置与实际一致。(https://www.waveshare.net/wiki/Pico-LCD-0.96) 20 | 21 | 屏幕详细信息: https://wiki.luatos.com/peripherals/lcd_air10x/index.html 22 | ST7735S文档: https://www.waveshare.net/w/upload/e/e2/ST7735S_V1.1_20111121.pdf 23 | FrameBuf文档: https://docs.micropython.org/en/latest/library/framebuf.html 24 | 字体文档: https://github.com/AntonVanke/MicroPython-Chinese-Font 25 | """ 26 | import gc 27 | import time 28 | import math 29 | 30 | import machine 31 | import framebuf 32 | from micropython import const 33 | 34 | SWRESET = const(0x01) 35 | SLPOUT = const(0x11) 36 | NORON = const(0x13) 37 | 38 | INVOFF = const(0x20) 39 | DISPON = const(0x29) 40 | CASET = const(0x2A) 41 | RASET = const(0x2B) 42 | RAMWR = const(0x2C) 43 | 44 | MADCTL = const(0x36) 45 | COLMOD = const(0x3A) 46 | 47 | FRMCTR1 = const(0xB1) 48 | FRMCTR2 = const(0xB2) 49 | FRMCTR3 = const(0xB3) 50 | 51 | INVCTR = const(0xB4) 52 | 53 | PWCTR1 = const(0xC0) 54 | PWCTR2 = const(0xC1) 55 | PWCTR3 = const(0xC2) 56 | PWCTR4 = const(0xC3) 57 | PWCTR5 = const(0xC4) 58 | VMCTR1 = const(0xC5) 59 | 60 | GMCTRP1 = const(0xE0) 61 | GMCTRN1 = const(0xE1) 62 | 63 | ROTATIONS = [0x00, 0x60, 0xC0, 0xA0] # 旋转方向 64 | 65 | 66 | def color(r, g, b): 67 | i = (((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3)).to_bytes(2, "little") 68 | return (i[0] << 8) + i[1] 69 | 70 | 71 | RED = color(255, 0, 0) 72 | GREEN = color(0, 255, 0) 73 | BLUE = color(0, 0, 255) 74 | WHITE = color(255, 255, 255) 75 | BLACK = color(0, 0, 0) 76 | 77 | 78 | class ST7735(framebuf.FrameBuffer): 79 | def __init__(self, spi, rst, dc, cs, bl=None, width=80, height=160, offset=None, rotate=1, rgb=True): 80 | """ 81 | :param spi: 82 | :param rst: 83 | :param dc: 84 | :param cs: 使能 85 | :param bl: 背光 86 | :param width: 宽度 87 | :param height: 高度 88 | :param offset: 偏移 (x, y): (23, -1)|(-1, 23) 89 | :param rotate: 旋转 0 横屏 1 竖屏 90 | :param rgb: RGB 色彩模式 91 | """ 92 | # 根据方向自动设置偏移 93 | self.rotate = rotate 94 | self.offset = offset 95 | self.rgb = rgb 96 | if offset is None and rotate == 1: 97 | self.offset = (-1, 23) 98 | elif offset is None and rotate == 0: 99 | self.offset = (23, -1) 100 | self.width = width 101 | self.height = height 102 | 103 | self.spi = spi 104 | self.rst = machine.Pin(rst, machine.Pin.OUT, machine.Pin.PULL_DOWN) 105 | self.dc = machine.Pin(dc, machine.Pin.OUT, machine.Pin.PULL_DOWN) 106 | self.cs = machine.Pin(cs, machine.Pin.OUT, machine.Pin.PULL_DOWN) 107 | if bl is not None: 108 | self.bl = machine.PWM(machine.Pin(bl)) 109 | 110 | gc.collect() 111 | self.buffer = bytearray(self.height * self.width * 2) 112 | super().__init__(self.buffer, self.width, self.height, framebuf.RGB565) 113 | self.init() 114 | self.set_windows() 115 | self.clear() 116 | 117 | def set_windows(self, x_start=None, y_start=None, x_end=None, y_end=None): 118 | """ 119 | 设置窗口 120 | :return: 121 | """ 122 | x_start = (x_start + self.offset[0] + 1) if x_start is not None else (self.offset[0] + 1) 123 | x_end = x_end + self.rotate + self.offset[0] if x_end is not None else self.width + self.rotate + \ 124 | self.offset[0] 125 | y_start = y_start + self.offset[1] + 1 if y_start is not None else self.offset[1] + 1 126 | y_end = y_end + self.rotate + self.offset[1] if y_end is not None else self.height + self.rotate + \ 127 | self.offset[1] 128 | 129 | self.write_cmd(CASET) 130 | self.write_data(bytearray([0x00, x_start, 0x00, x_end])) 131 | 132 | self.write_cmd(RASET) 133 | self.write_data(bytearray([0x00, y_start, 0x00, y_end])) 134 | 135 | self.write_cmd(RAMWR) 136 | 137 | def init(self): 138 | self.reset() 139 | 140 | self.write_cmd(SWRESET) 141 | time.sleep_us(150) 142 | self.write_cmd(SLPOUT) 143 | time.sleep_us(300) 144 | 145 | self.write_cmd(FRMCTR1) 146 | self.write_data(bytearray([0x01, 0x2C, 0x2D])) 147 | self.write_cmd(FRMCTR2) 148 | self.write_data(bytearray([0x01, 0x2C, 0x2D])) 149 | self.write_cmd(FRMCTR3) 150 | self.write_data(bytearray([0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D])) 151 | time.sleep_us(10) 152 | 153 | self.write_cmd(INVCTR) 154 | self.write_data(bytearray([0x07])) 155 | 156 | self.write_cmd(PWCTR1) 157 | self.write_data(bytearray([0xA2, 0x02, 0x84])) 158 | self.write_cmd(PWCTR2) 159 | self.write_data(bytearray([0xC5])) 160 | self.write_cmd(PWCTR3) 161 | self.write_data(bytearray([0x0A, 0x00])) 162 | self.write_cmd(PWCTR4) 163 | self.write_data(bytearray([0x8A, 0x2A])) 164 | self.write_cmd(PWCTR5) 165 | self.write_data(bytearray([0x8A, 0xEE])) 166 | self.write_cmd(VMCTR1) 167 | self.write_data(bytearray([0x0E])) 168 | 169 | self.write_cmd(INVOFF) 170 | 171 | self.write_cmd(MADCTL) 172 | self.write_data(bytearray([ROTATIONS[self.rotate] | 0x00 if self.rgb else 0x08])) 173 | 174 | self.write_cmd(COLMOD) 175 | self.write_data(bytearray([0x05])) 176 | 177 | self.write_cmd(GMCTRP1) 178 | self.write_data( 179 | bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10])) 180 | 181 | self.write_cmd(GMCTRN1) 182 | self.write_data( 183 | bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d, 0x2e, 0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10])) 184 | 185 | self.write_cmd(NORON) 186 | time.sleep_us(10) 187 | 188 | self.write_cmd(DISPON) 189 | time.sleep_us(100) 190 | 191 | self.cs(1) 192 | 193 | def reset(self): 194 | """ 195 | 设备重置 196 | :return: 197 | """ 198 | self.rst(1) 199 | time.sleep(0.2) 200 | self.rst(0) 201 | time.sleep(0.2) 202 | self.rst(1) 203 | time.sleep(0.2) 204 | 205 | def write_cmd(self, cmd): 206 | self.dc(0) 207 | self.cs(0) 208 | self.spi.write(bytearray([cmd])) 209 | self.cs(1) 210 | 211 | def write_data(self, buf): 212 | self.dc(1) 213 | self.cs(0) 214 | self.spi.write(buf) 215 | self.cs(1) 216 | 217 | def back_light(self, value): 218 | """ 219 | 背光调节 220 | :param value: 背光等级 0 ~ 255 221 | :return: 222 | """ 223 | self.bl.freq(1000) 224 | if value >= 0xff: 225 | value = 0xff 226 | data = value * 0xffff >> 8 227 | self.bl.duty_u16(data) 228 | 229 | def clear(self): 230 | """ 231 | 清屏 232 | :return: 233 | """ 234 | self.fill(0) 235 | self.show() 236 | 237 | def show(self): 238 | """ 239 | 显示 240 | :return: 241 | """ 242 | self.set_windows() # 如果没有这行就会偏移 243 | self.write_data(self.buffer) 244 | 245 | def circle(self, center, radius, c=color(255, 255, 255), section=100): 246 | """ 247 | 画圆 248 | :param c: 颜色 249 | :param center: 中心(x, y) 250 | :param radius: 半径 251 | :param section: 分段 252 | :return: 253 | """ 254 | arr = [] 255 | for m in range(section + 1): 256 | x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + center[0]) 257 | y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + center[1]) 258 | arr.append([x, y]) 259 | for i in range(len(arr) - 1): 260 | self.line(*arr[i], *arr[i + 1], c) 261 | 262 | def image(self, file_name): 263 | with open(file_name, "rb") as bmp: 264 | for b in range(0, 80 * 160 * 2, 1024): 265 | self.buffer[b:b + 1024] = bmp.read(1024) 266 | self.show() 267 | -------------------------------------------------------------------------------- /driver/st7789.py: -------------------------------------------------------------------------------- 1 | __version__ = "2.0.1" 2 | """ 3 | ST77XX 通用驱动 v2.0.1 4 | 支持 ST7789 和 ST7735 的驱动,采用 FrameBuffer(RGB565) 缓冲整个屏幕数据,内存较小勿用 5 | 6 | 内存计算方式: 7 | width * height * 2(Byte) 8 | 9 | 使用方法(以 [合宙ESP32C3] + [合宙 Air10x 系列屏幕扩展板] 直插为例): 10 | from machine import SPI, Pin 11 | from st77xx import ST77XX 12 | 13 | spi = SPI(1, 30000000, sck=Pin(2), mosi=Pin(3), polarity=1) 14 | ST7735(spi, rst=10, dc=6, cs=7, bl=11, width=160, height=80, rotate=1) # 直插横屏显示 15 | ST7735(spi, rst=10, dc=6, cs=7, bl=11, width=160, height=80, rotate=0) # 直插竖屏显示 16 | 17 | 偏移问题: 18 | 默认提供了三种屏幕的偏移数据(160*80, 160*128, 240*240),偏移不正确或者没有预设,请自行指定偏移,例如: 19 | ST7789(spi, rst=6, dc=5, bl=4, width=240, height=135, rotate=0, offset=(0, 0, 240, 135)) 20 | 21 | 颜色问题: 22 | 不同厂商生产的屏幕可能会有颜色错误,可以更改 `rgb` 和 `inverse` 参数来校准颜色,例如: 23 | d = ST7789(spi, rst=6, dc=5, bl=4, width=240, height=135, rotate=0, rgb=True, inverse=True) 24 | d = ST7789(spi, rst=6, dc=5, bl=4, width=240, height=135, rotate=0, rgb=True, inverse=False) 25 | ...... 26 | 27 | 无法显示问题: 28 | 查看并尝试更改 SPI的 polarity/firstbit/phase 参数,参考文档: 29 | https://docs.micropython.org/en/latest/library/machine.SPI.html#machine.SoftSPI 30 | 31 | ST7735S文档: https://www.waveshare.net/w/upload/e/e2/ST7735S_V1.1_20111121.pdf 32 | ST7789文档: https://www.waveshare.com/w/upload/a/ae/ST7789_Datasheet.pdf 33 | 34 | FrameBuf 文档: https://docs.micropython.org/en/latest/library/framebuf.html 35 | 字库文档: https://github.com/AntonVanke/MicroPython-uFont 36 | 字体生成工具:https://github.com/AntonVanke/MicroPython-uFont-Tools 37 | 自用 Micropython 驱动库:https://github.com/AntonVanke/MicroPython-Drivers 38 | """ 39 | import gc 40 | import time 41 | import math 42 | 43 | import machine 44 | import framebuf 45 | from micropython import const 46 | 47 | SWRESET = const(0x01) 48 | SLPOUT = const(0x11) 49 | NORON = const(0x13) 50 | 51 | INVOFF = const(0x20) 52 | DISPON = const(0x29) 53 | CASET = const(0x2A) 54 | RASET = const(0x2B) 55 | RAMWR = const(0x2C) 56 | 57 | MADCTL = const(0x36) 58 | COLMOD = const(0x3A) 59 | 60 | FRMCTR1 = const(0xB1) 61 | FRMCTR2 = const(0xB2) 62 | FRMCTR3 = const(0xB3) 63 | 64 | INVCTR = const(0xB4) 65 | 66 | PWCTR1 = const(0xC0) 67 | PWCTR2 = const(0xC1) 68 | PWCTR3 = const(0xC2) 69 | PWCTR4 = const(0xC3) 70 | PWCTR5 = const(0xC4) 71 | VMCTR1 = const(0xC5) 72 | 73 | GMCTRP1 = const(0xE0) 74 | GMCTRN1 = const(0xE1) 75 | 76 | # 旋转方向 77 | ROTATIONS = [0x00, 0x60, 0xC0, 0xA0] 78 | 79 | # 预设偏移 80 | TYPE_A_OFFSET = [(24, 0, 103, 160), (0, 24, 160, 104), (24, 0, 103, 160), (0, 24, 160, 104)] # 80x160 81 | TYPE_B_OFFSET = [(0, 0, 128, 160), (0, 0, 160, 128), (0, 0, 128, 160), (0, 0, 160, 128)] # 128x160 82 | TYPE_C_OFFSET = [(0, 0, 239, 240), (0, 0, 240, 240), (80, 0, 320, 240), (0, 80, 240, 320)] # 240x240 83 | 84 | 85 | def color(r, g, b): 86 | i = (((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3)).to_bytes(2, "little") 87 | return (i[0] << 8) + i[1] 88 | 89 | 90 | RED = color(255, 0, 0) 91 | GREEN = color(0, 255, 0) 92 | BLUE = color(0, 0, 255) 93 | WHITE = color(255, 255, 255) 94 | BLACK = color(0, 0, 0) 95 | 96 | 97 | class ST77XX(framebuf.FrameBuffer): 98 | def __init__(self, spi, rst, dc, cs=None, bl=None, width=80, height=160, offset=(0, 0, 0, 0), rotate=1, 99 | rgb=True, inverse=False, **kwargs): 100 | """ 101 | :param spi: 102 | :param rst: 103 | :param dc: 104 | :param cs: 使能 105 | :param bl: 背光 106 | :param width: 宽度 107 | :param height: 高度 108 | :param offset: 偏移 109 | :param rotate: 旋转 110 | :param rgb: RGB 色彩模式 111 | """ 112 | # 根据方向自动设置偏移 113 | self.rotate = rotate 114 | self.offset = offset 115 | self.inverse = inverse 116 | self.rgb = rgb 117 | self.width = width 118 | self.height = height 119 | 120 | self.spi = spi 121 | self.rst = machine.Pin(rst, machine.Pin.OUT, machine.Pin.PULL_DOWN) 122 | self.dc = machine.Pin(dc, machine.Pin.OUT, machine.Pin.PULL_DOWN) 123 | 124 | # 有的没有cs引脚 125 | if cs is not None: 126 | self.cs = machine.Pin(cs, machine.Pin.OUT, machine.Pin.PULL_DOWN) 127 | else: 128 | self.cs = int 129 | if bl is not None: 130 | self.bl = machine.PWM(machine.Pin(bl), duty=1023) 131 | self.auto_offset() if self.offset == (0, 0, 0, 0) else 0 132 | 133 | gc.collect() 134 | self.buffer = bytearray(self.height * self.width * 2) 135 | super().__init__(self.buffer, self.width, self.height, framebuf.RGB565) 136 | self.init() 137 | self.set_windows() 138 | self.clear() 139 | 140 | def auto_offset(self, _rotate: int = None): 141 | _rotate = self.rotate if _rotate is None else None 142 | if self.width in [80, 160] and self.height in [80, 160]: 143 | self.offset = TYPE_A_OFFSET[_rotate] 144 | elif self.width in [128, 160] and self.height in [128, 160]: 145 | self.offset = TYPE_B_OFFSET[_rotate] 146 | elif self.width == 240 and self.height == 240: 147 | self.offset = TYPE_C_OFFSET[_rotate] 148 | else: 149 | self.offset = (0, 0, self.width if _rotate % 2 and self.width > self.height else self.height, 150 | self.height if _rotate % 2 and self.width > self.height else self.width) 151 | 152 | def set_windows(self, x_start=None, y_start=None, x_end=None, y_end=None): 153 | """ 154 | 设置窗口 155 | :return: 156 | """ 157 | x_start = x_start if x_start else self.offset[0] 158 | y_start = y_start if y_start else self.offset[1] 159 | x_end = x_end if x_end else self.offset[2] 160 | y_end = y_end if y_end else self.offset[3] 161 | 162 | self.write_cmd(CASET) 163 | self.write_data(bytearray([x_start >> 8, x_start & 0xff, x_end >> 8, x_end & 0xff])) 164 | 165 | self.write_cmd(RASET) 166 | self.write_data(bytearray([y_start >> 8, y_start & 0xff, y_end >> 8, y_end & 0xff])) 167 | 168 | self.write_cmd(RAMWR) 169 | 170 | def init(self): 171 | self.reset() 172 | 173 | self.write_cmd(SWRESET) 174 | time.sleep_us(150) 175 | self.write_cmd(SLPOUT) 176 | time.sleep_us(300) 177 | 178 | self.write_cmd(FRMCTR1) 179 | self.write_data(bytearray([0x01, 0x2C, 0x2D])) 180 | self.write_cmd(FRMCTR2) 181 | self.write_data(bytearray([0x01, 0x2C, 0x2D])) 182 | self.write_cmd(FRMCTR3) 183 | self.write_data(bytearray([0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D])) 184 | time.sleep_us(10) 185 | 186 | self.write_cmd(INVCTR) 187 | self.write_data(bytearray([0x07])) 188 | 189 | self.write_cmd(PWCTR1) 190 | self.write_data(bytearray([0xA2, 0x02, 0x84])) 191 | self.write_cmd(PWCTR2) 192 | self.write_data(bytearray([0xC5])) 193 | self.write_cmd(PWCTR3) 194 | self.write_data(bytearray([0x0A, 0x00])) 195 | self.write_cmd(PWCTR4) 196 | self.write_data(bytearray([0x8A, 0x2A])) 197 | self.write_cmd(PWCTR5) 198 | self.write_data(bytearray([0x8A, 0xEE])) 199 | self.write_cmd(VMCTR1) 200 | self.write_data(bytearray([0x0E])) 201 | 202 | self.write_cmd(INVOFF + int(self.inverse)) 203 | 204 | self.write_cmd(MADCTL) 205 | self.write_data(bytearray([ROTATIONS[self.rotate] | 0x00 if self.rgb else 0x08])) 206 | 207 | self.write_cmd(COLMOD) 208 | self.write_data(bytearray([0x05])) 209 | 210 | self.write_cmd(GMCTRP1) 211 | self.write_data( 212 | bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10])) 213 | 214 | self.write_cmd(GMCTRN1) 215 | self.write_data( 216 | bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d, 0x2e, 0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10])) 217 | 218 | self.write_cmd(NORON) 219 | time.sleep_us(10) 220 | 221 | self.write_cmd(DISPON) 222 | time.sleep_us(100) 223 | 224 | self.cs(1) 225 | 226 | def reset(self): 227 | """ 228 | 设备重置 229 | :return: 230 | """ 231 | self.rst(1) 232 | time.sleep(0.2) 233 | self.rst(0) 234 | time.sleep(0.2) 235 | self.rst(1) 236 | time.sleep(0.2) 237 | 238 | def write_cmd(self, cmd): 239 | self.dc(0) 240 | self.cs(0) 241 | self.spi.write(bytearray([cmd])) 242 | self.cs(1) 243 | 244 | def write_data(self, buf): 245 | self.dc(1) 246 | self.cs(0) 247 | self.spi.write(buf) 248 | self.cs(1) 249 | 250 | def back_light(self, value): 251 | """ 252 | 背光调节 253 | :param value: 背光等级 0 ~ 255 254 | :return: 255 | """ 256 | self.bl.freq(1000) 257 | if value >= 0xff: 258 | value = 0xff 259 | data = value * 0xffff >> 8 260 | self.bl.duty_u16(data) 261 | 262 | def clear(self): 263 | """ 264 | 清屏 265 | :return: 266 | """ 267 | self.fill(0) 268 | self.show() 269 | 270 | def show(self): 271 | """ 272 | 显示 273 | :return: 274 | """ 275 | self.set_windows() # 如果没有这行就会偏移 276 | self.write_data(self.buffer) 277 | 278 | def circle(self, center, radius, c=color(255, 255, 255), section=100): 279 | """ 280 | 画圆 281 | :param c: 颜色 282 | :param center: 中心(x, y) 283 | :param radius: 半径 284 | :param section: 分段 285 | :return: 286 | """ 287 | arr = [] 288 | for m in range(section + 1): 289 | x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + center[0]) 290 | y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + center[1]) 291 | arr.append([x, y]) 292 | for i in range(len(arr) - 1): 293 | self.line(*arr[i], *arr[i + 1], c) 294 | 295 | 296 | class ST7789(ST77XX): 297 | pass 298 | 299 | 300 | class ST7735(ST77XX): 301 | pass 302 | -------------------------------------------------------------------------------- /driver/st77xx.py: -------------------------------------------------------------------------------- 1 | __version__ = "2.0.1" 2 | """ 3 | ST77XX 通用驱动 v2.0.1 4 | 支持 ST7789 和 ST7735 的驱动,采用 FrameBuffer(RGB565) 缓冲整个屏幕数据,内存较小勿用 5 | 6 | 内存计算方式: 7 | width * height * 2(Byte) 8 | 9 | 使用方法(以 [合宙ESP32C3] + [合宙 Air10x 系列屏幕扩展板] 直插为例): 10 | from machine import SPI, Pin 11 | from st77xx import ST77XX 12 | 13 | spi = SPI(1, 30000000, sck=Pin(2), mosi=Pin(3), polarity=1) 14 | ST7735(spi, rst=10, dc=6, cs=7, bl=11, width=160, height=80, rotate=1) # 直插横屏显示 15 | ST7735(spi, rst=10, dc=6, cs=7, bl=11, width=160, height=80, rotate=0) # 直插竖屏显示 16 | 17 | 偏移问题: 18 | 默认提供了三种屏幕的偏移数据(160*80, 160*128, 240*240),偏移不正确或者没有预设,请自行指定偏移,例如: 19 | ST7789(spi, rst=6, dc=5, bl=4, width=240, height=135, rotate=0, offset=(0, 0, 240, 135)) 20 | 21 | 颜色问题: 22 | 不同厂商生产的屏幕可能会有颜色错误,可以更改 `rgb` 和 `inverse` 参数来校准颜色,例如: 23 | d = ST7789(spi, rst=6, dc=5, bl=4, width=240, height=135, rotate=0, rgb=True, inverse=True) 24 | d = ST7789(spi, rst=6, dc=5, bl=4, width=240, height=135, rotate=0, rgb=True, inverse=False) 25 | ...... 26 | 27 | 无法显示问题: 28 | 查看并尝试更改 SPI的 polarity/firstbit/phase 参数,参考文档: 29 | https://docs.micropython.org/en/latest/library/machine.SPI.html#machine.SoftSPI 30 | 31 | ST7735S文档: https://www.waveshare.net/w/upload/e/e2/ST7735S_V1.1_20111121.pdf 32 | ST7789文档: https://www.waveshare.com/w/upload/a/ae/ST7789_Datasheet.pdf 33 | 34 | FrameBuf 文档: https://docs.micropython.org/en/latest/library/framebuf.html 35 | 字库文档: https://github.com/AntonVanke/MicroPython-uFont 36 | 字体生成工具:https://github.com/AntonVanke/MicroPython-uFont-Tools 37 | 自用 Micropython 驱动库:https://github.com/AntonVanke/MicroPython-Drivers 38 | """ 39 | import gc 40 | import time 41 | import math 42 | 43 | import machine 44 | import framebuf 45 | from micropython import const 46 | 47 | SWRESET = const(0x01) 48 | SLPOUT = const(0x11) 49 | NORON = const(0x13) 50 | 51 | INVOFF = const(0x20) 52 | DISPON = const(0x29) 53 | CASET = const(0x2A) 54 | RASET = const(0x2B) 55 | RAMWR = const(0x2C) 56 | 57 | MADCTL = const(0x36) 58 | COLMOD = const(0x3A) 59 | 60 | FRMCTR1 = const(0xB1) 61 | FRMCTR2 = const(0xB2) 62 | FRMCTR3 = const(0xB3) 63 | 64 | INVCTR = const(0xB4) 65 | 66 | PWCTR1 = const(0xC0) 67 | PWCTR2 = const(0xC1) 68 | PWCTR3 = const(0xC2) 69 | PWCTR4 = const(0xC3) 70 | PWCTR5 = const(0xC4) 71 | VMCTR1 = const(0xC5) 72 | 73 | GMCTRP1 = const(0xE0) 74 | GMCTRN1 = const(0xE1) 75 | 76 | # 旋转方向 77 | ROTATIONS = [0x00, 0x60, 0xC0, 0xA0] 78 | 79 | # 预设偏移 80 | TYPE_A_OFFSET = [(24, 0, 103, 160), (0, 24, 160, 104), (24, 0, 103, 160), (0, 24, 160, 104)] # 80x160 81 | TYPE_B_OFFSET = [(0, 0, 128, 160), (0, 0, 160, 128), (0, 0, 128, 160), (0, 0, 160, 128)] # 128x160 82 | TYPE_C_OFFSET = [(0, 0, 239, 240), (0, 0, 240, 240), (80, 0, 320, 240), (0, 80, 240, 320)] # 240x240 83 | 84 | 85 | def color(r, g, b): 86 | i = (((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3)).to_bytes(2, "little") 87 | return (i[0] << 8) + i[1] 88 | 89 | 90 | RED = color(255, 0, 0) 91 | GREEN = color(0, 255, 0) 92 | BLUE = color(0, 0, 255) 93 | WHITE = color(255, 255, 255) 94 | BLACK = color(0, 0, 0) 95 | 96 | 97 | class ST77XX(framebuf.FrameBuffer): 98 | def __init__(self, spi, rst, dc, cs=None, bl=None, width=80, height=160, offset=(0, 0, 0, 0), rotate=1, 99 | rgb=True, inverse=False, **kwargs): 100 | """ 101 | :param spi: 102 | :param rst: 103 | :param dc: 104 | :param cs: 使能 105 | :param bl: 背光 106 | :param width: 宽度 107 | :param height: 高度 108 | :param offset: 偏移 109 | :param rotate: 旋转 110 | :param rgb: RGB 色彩模式 111 | """ 112 | # 根据方向自动设置偏移 113 | self.rotate = rotate 114 | self.offset = offset 115 | self.inverse = inverse 116 | self.rgb = rgb 117 | self.width = width 118 | self.height = height 119 | 120 | self.spi = spi 121 | self.rst = machine.Pin(rst, machine.Pin.OUT, machine.Pin.PULL_DOWN) 122 | self.dc = machine.Pin(dc, machine.Pin.OUT, machine.Pin.PULL_DOWN) 123 | 124 | # 有的没有cs引脚 125 | if cs is not None: 126 | self.cs = machine.Pin(cs, machine.Pin.OUT, machine.Pin.PULL_DOWN) 127 | else: 128 | self.cs = int 129 | if bl is not None: 130 | self.bl = machine.PWM(machine.Pin(bl), duty=1023) 131 | self.auto_offset() if self.offset == (0, 0, 0, 0) else 0 132 | 133 | gc.collect() 134 | self.buffer = bytearray(self.height * self.width * 2) 135 | super().__init__(self.buffer, self.width, self.height, framebuf.RGB565) 136 | self.init() 137 | self.set_windows() 138 | self.clear() 139 | 140 | def auto_offset(self, _rotate: int = None): 141 | _rotate = self.rotate if _rotate is None else None 142 | if self.width in [80, 160] and self.height in [80, 160]: 143 | self.offset = TYPE_A_OFFSET[_rotate] 144 | elif self.width in [128, 160] and self.height in [128, 160]: 145 | self.offset = TYPE_B_OFFSET[_rotate] 146 | elif self.width == 240 and self.height == 240: 147 | self.offset = TYPE_C_OFFSET[_rotate] 148 | else: 149 | self.offset = (0, 0, self.width if _rotate % 2 and self.width > self.height else self.height, 150 | self.height if _rotate % 2 and self.width > self.height else self.width) 151 | 152 | def set_windows(self, x_start=None, y_start=None, x_end=None, y_end=None): 153 | """ 154 | 设置窗口 155 | :return: 156 | """ 157 | x_start = x_start if x_start else self.offset[0] 158 | y_start = y_start if y_start else self.offset[1] 159 | x_end = x_end if x_end else self.offset[2] 160 | y_end = y_end if y_end else self.offset[3] 161 | 162 | self.write_cmd(CASET) 163 | self.write_data(bytearray([x_start >> 8, x_start & 0xff, x_end >> 8, x_end & 0xff])) 164 | 165 | self.write_cmd(RASET) 166 | self.write_data(bytearray([y_start >> 8, y_start & 0xff, y_end >> 8, y_end & 0xff])) 167 | 168 | self.write_cmd(RAMWR) 169 | 170 | def init(self): 171 | self.reset() 172 | 173 | self.write_cmd(SWRESET) 174 | time.sleep_us(150) 175 | self.write_cmd(SLPOUT) 176 | time.sleep_us(300) 177 | 178 | self.write_cmd(FRMCTR1) 179 | self.write_data(bytearray([0x01, 0x2C, 0x2D])) 180 | self.write_cmd(FRMCTR2) 181 | self.write_data(bytearray([0x01, 0x2C, 0x2D])) 182 | self.write_cmd(FRMCTR3) 183 | self.write_data(bytearray([0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D])) 184 | time.sleep_us(10) 185 | 186 | self.write_cmd(INVCTR) 187 | self.write_data(bytearray([0x07])) 188 | 189 | self.write_cmd(PWCTR1) 190 | self.write_data(bytearray([0xA2, 0x02, 0x84])) 191 | self.write_cmd(PWCTR2) 192 | self.write_data(bytearray([0xC5])) 193 | self.write_cmd(PWCTR3) 194 | self.write_data(bytearray([0x0A, 0x00])) 195 | self.write_cmd(PWCTR4) 196 | self.write_data(bytearray([0x8A, 0x2A])) 197 | self.write_cmd(PWCTR5) 198 | self.write_data(bytearray([0x8A, 0xEE])) 199 | self.write_cmd(VMCTR1) 200 | self.write_data(bytearray([0x0E])) 201 | 202 | self.write_cmd(INVOFF + int(self.inverse)) 203 | 204 | self.write_cmd(MADCTL) 205 | self.write_data(bytearray([ROTATIONS[self.rotate] | 0x00 if self.rgb else 0x08])) 206 | 207 | self.write_cmd(COLMOD) 208 | self.write_data(bytearray([0x05])) 209 | 210 | self.write_cmd(GMCTRP1) 211 | self.write_data( 212 | bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10])) 213 | 214 | self.write_cmd(GMCTRN1) 215 | self.write_data( 216 | bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d, 0x2e, 0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10])) 217 | 218 | self.write_cmd(NORON) 219 | time.sleep_us(10) 220 | 221 | self.write_cmd(DISPON) 222 | time.sleep_us(100) 223 | 224 | self.cs(1) 225 | 226 | def reset(self): 227 | """ 228 | 设备重置 229 | :return: 230 | """ 231 | self.rst(1) 232 | time.sleep(0.2) 233 | self.rst(0) 234 | time.sleep(0.2) 235 | self.rst(1) 236 | time.sleep(0.2) 237 | 238 | def write_cmd(self, cmd): 239 | self.dc(0) 240 | self.cs(0) 241 | self.spi.write(bytearray([cmd])) 242 | self.cs(1) 243 | 244 | def write_data(self, buf): 245 | self.dc(1) 246 | self.cs(0) 247 | self.spi.write(buf) 248 | self.cs(1) 249 | 250 | def back_light(self, value): 251 | """ 252 | 背光调节 253 | :param value: 背光等级 0 ~ 255 254 | :return: 255 | """ 256 | self.bl.freq(1000) 257 | if value >= 0xff: 258 | value = 0xff 259 | data = value * 0xffff >> 8 260 | self.bl.duty_u16(data) 261 | 262 | def clear(self): 263 | """ 264 | 清屏 265 | :return: 266 | """ 267 | self.fill(0) 268 | self.show() 269 | 270 | def show(self): 271 | """ 272 | 显示 273 | :return: 274 | """ 275 | self.set_windows() # 如果没有这行就会偏移 276 | self.write_data(self.buffer) 277 | 278 | def circle(self, center, radius, c=color(255, 255, 255), section=100): 279 | """ 280 | 画圆 281 | :param c: 颜色 282 | :param center: 中心(x, y) 283 | :param radius: 半径 284 | :param section: 分段 285 | :return: 286 | """ 287 | arr = [] 288 | for m in range(section + 1): 289 | x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + center[0]) 290 | y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + center[1]) 291 | arr.append([x, y]) 292 | for i in range(len(arr) - 1): 293 | self.line(*arr[i], *arr[i + 1], c) 294 | 295 | 296 | class ST7789(ST77XX): 297 | pass 298 | 299 | 300 | class ST7735(ST77XX): 301 | pass 302 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AntonVanke/MicroPython-uFont/ccf2cc13e49de3d5603d582c4cf3844be8bd2dfe/requirements.txt -------------------------------------------------------------------------------- /text.txt: -------------------------------------------------------------------------------- 1 | !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~“”… 、。《》〔〕!,:;?¥—·的年一在为是中国人于大有和以月日后了学时个会地成公上不出行他部斯之与区名发其主作生尔分方下西用到第位而家军由及里被并本民法南这市来长同可多政对子自任动开特文北高加东德最海新克台员也前代等利亚平所三面经度道电因现此得建美进物过要机间内教山至事小立路拉口车当入战体能将世二理工表合马外天州该定十球期种但化赛业使科制次全布称性场通队总者线共设县式数重都系两罗力安士联比王相原基城院站达省属明运目水司正金治米英格尼镇号统华府兰约关组们四如或列元巴选计港起星影语从产首常女提阿改演林量心然向就始接乐议实无界参书务级曾意则义广管单说河卡手受委程形色视保交指门包团维结流湾型已处音传些香直类党太集获史变身万夫点应字五导认岛品校空朝领回更江还展古社积铁示张师持各图洲带奥著官只反别命专纪风游京取活资据造光报举击信少术石支族历神近曲普没清局纳村标解转它剧又情际非节龙每论苏研记波权件条角气放决乡辖片武头即密版伊连商她李先括周职收知座器往去白离未创强果根歌再初福足陆办究六且今派圣亲汉兵复引阳调居亦完打划域帝观装许塔宗季质网除播争编题威随续段很花服较奖令八育给构速言协园爱见戏土功另修死黄置希萨担问父存好客推营规查终超欧宣率雷洛样项录画我吉供护具投哈九源卫班街准馆博瓦青兴伦鱼母求负百湖环印田边男济伯责试例案陈攻故己火那勒落沙助众备儿致皇份航极热莱诺早技整艺鲁容均增副志模升半登群红层七围飞川越堂继失托双告易写食氏评境深独室黑射排塞需证限筑声甲像象织央老真占响医何费才采杀仍让木便森洋步席盟奇牙舰考想施楼感配防哥低岁刘顿住乌效桥止贝届留姆律范几把农千底病埃泰曼料佛冠辑银恩状警云降蒙库话看景宁胜迪息仅测俄唱察移素祖批着败革显康念算消承干索温授控词典藏票远弹载韩思习户做友守确余态精满瑞叶宫督附望依价注预征照房述夏拔毛章请抗执草却压虽简退停封富永澳般左丹值唐丁牌堡丽斗验快拿势候坦宝侧船破隆庄梅邦寺难植含庆郡莫断厂识似细突皮监必闻况礼尚旅蒂走拥久嘉读岸绝切店延右朗麦毕厅换架野轮杰弟核售春临旧亡优松严归油艾弗判枪佳杨异雄遗端短孙码吴伤股灵耶遭练策逐赫轻序帕顺役距旗摩须菲籍树酒送若宋晚乘雅销末暴杂束微阵什玛钟泽摄输诗秀胡冲齐卢译良兼亿阶血署庭略沃款药频尾荣善丰驻盖饰津劳甚散贵启免婚拍尽峰差纽假顶签鲜喜予臣杜迁板减晋朱追兹谷杯财烈购仁养秘背害凯爆宾舞沿盛渐夺企梁廷险互释黎韦酸训玉讯救牛援货罪媒轨圆昌检软够尺谢介补屋透君址审映溪坡币庙仪扩挥讲荷智材访按巨徒颗适乔敦隶郎敌童玩拜益郑冰巡混径辆蓝探宽翰某途诸禁夜穆讨旋觉霍替刻眼献额锦刺殿皆赵描跟吸培岩遇刊借帮符乱你找撤佩竞聚欢午炮爵脑羽充宇私休耳魔固奉邻袭幕洪唯漫返毒坑戈骨雨否逃坐废茨恒雪操靠忠激卷竹络召赞择乎赤栖缺穿钱丝捕震怀盘露御汇疗绿宪么刑灭犯肯娜镜骑尤贡诉兄徐课妻翻顾刚纲迫姓听损抵坚待鳍申桑陵毁柏浙订秋胞呼愿侵炸谋孩汽横脉泛篇谈扎潮鸟惠恶董绩魏泉甘孔融买绍腊偏戴辛舍避芬默缩础急脱奏健佐疑努套危误概翼肉呈墨衣墓莉榜块针宜紧井坏殖裁叫秦冷迎扬寻赖毫染虎隔圈缘珠涉迷捷迹廉渡阻阴哲彩亮剑侯析锡池仙贸付葡虫恐弃辅卖氧函暗招味凤绕琴匹寿尊滨错梦洞绘俗钢麻症伍妹享脚俱硬赢颁豪瑟町笔辐倒租崇绪冬繁截综液驱矿徽誉曹奈静逊楚稳铜冈昂妇逝犹阁昭吕蛋掌柱齿估伐乙墙辞浦萄辽闭郭轴潜振淡岭裂乃纸幼档邀弱暂盐拟零册赏鼓裔拒陶跨浪滑触莎恢昆粉幸藤陷困菜旁剂伟灾偶菌览蔡患障颜牧残涅驶彼糖纯沈宿壁丘税祭蒋邮狱伴勇隐柯句亨汗仔敏厦桃熙龄衡灯键搭遍殊允汤燃珍答疆埔沉促孟恋宅烧茶奴幅拆迈侍紫纬虚厚磁棒莲灰折幻烟葬跑塘坛械赴孝艘叙邓敬沟锋碑贤娱餐篮陕储祥慈晶辉遣萧筹柴勋棋纵箭挑倍谱葛姐螺撞叛峡虑冯慢熟贾柳尖雕苦遂艇伏刀仓妮仰跳燕匈驾纹凡桂违疾朋撒伸瓜枚猎贺衔覆吃贯迅缓彭谓潘详慕兽琳忆粒勤鬼泊脂牠靖趣番猪熊咸罕累鹿涯诚掉芳蛇屿肃屯旺秒擎詹饮纷胶赠惊搜怪倾梯禄硕壳挂羊玄页浮俊拓杉仲抚晨芝乳屏乾鼠诞岳凭痛腹缅锁猫卓泥插藩坎慧凌弘晓鉴畔袁豆厄杭崎隧塑炎旦笑励欲斑甸卿喀鸡戒宏跃乏撰轰苗圳狮牵溶扰亭玻仿罚洗赐寄阮盗尉宙贞彻祝丧坊舒鲤叔巧循焦袖陨泳悬僧箱胁扮妃茂吨郊询壮既弥湿阅耀粤疏衰霸污碳岗枢崔侦翁盾摇契串彰枝伙逢厘滚捐飓浓钦床氢剩歧砲喷拳措勃浩笼鸿荒券浅帅卜傅滩填递渔夷鲍搬裕饭蝶润灌割窗衍怒淮丈惯侠链粮庞儒肥栽贴鹰邨尸旨踪欣抽狂尝珊垂斜赶碍洁仑垒胎膜悉腾娘孤辩闽冒煤湘贫柔疫粗聘廊稿吾握杆耗陀坪迟狄琉彦寨壤妈逆貌粹净朴婆堪抢禅揭翠寒龟俘铺潭阔薄恰丛狗矩弦忍稍魂翌吐赋鼻挪坝颇璃扶堆碎诏遵逻刷姬癌伪脏姚铭蛛胸涵鸣卒矶叠苑罢汪阪淘丙偷怖吏荐狼兆添硫泡栏劝歇涌寸惟奔扫祀押脊姻菊逮趋雇逼琼蒸酶磨携嗣辰屠屈仇夕醒腐雀稣叉奎猛嫌卵韵姜棘诊碟劫涂拼偿斤芒嘴嫁鹤隋凉厢愈薛曰凝礁悲犬鲶摆奶敢娃伽掘铸矛债闪卑忙妙肖氯扁迦姊讷肌醇抑祠忽雍吹狭穷旬侨贩丑鼎碧砖宰邪舱袋幽砂麟炉甫汰铃鳞抱株翔栋怕尹稻夹蕨绳炼鲈竟恭盆拖惨蹈巫谭纤斐剪邑圭汀丸崩寓抓弯寮毅沪茵辟钻汝虹豫胆闸鄂魁蟹渊茅荆斋辨襄埋咨倡氨斩苹廖囊郁巢雾腔雌蜂扣驳蓬丞腺珀咖闲奋鸭暖孕吞铎挖擅崖披谦劲苯拨吧肠吻溯陪耕滕募瓶酷舟脸漠耐荡削忘舌矮毗聪谥坂喇栗蒲蕾亥仕蜜蜀暨拱颈邱噶薇催巷札渠碱姑掠帽鲸遥卸薪帐稀摧顷琪溃锐胺诱谊斥宠婴忧豹桓藻筒踏淑扑棉甜啡椎贼愤邵畅庶拘睡宛梨谁惜烯磷爷穴唇哪澄冕梵帆尿宴葵禾掩亩褐堤榄漏轩凶碰卧绵菩肺瓷誓鹅弓玲逸挡腿怡饼樱棕鹏夸贪瘤肤仆桌姿伞吊悠翅霞慎绥娶颠臂桐框芦爪奠侣绑冀弄柄厝肢恨抄惧涛蝠奸呎抛慰贷窄谐腰牲骗鸦肿删赌腓肝骚辈殷扭沼茎盈吁肩颖颂劣蚀践哀纠涡妖烷祈庇汶忌滤缴猜坟囚尘讼槽逾裸浸赔镑朵爬滋炭屡怎坞埠鳄秩厥饶悦祯祸兔搞扇眉谣虾芭寇跋缝嵌庚凰瑚嫩蓄肇遮窝趁泄祐坠芙跌禧庸猴椅纺焚悟昔蜥骏浏挺虏尧锅橡氟贬舆荃讽肾蕃欠晴柜厉擦眠惩鲷佑喻柬唤茄瑜浆鞋旱捉坤苍钓爸狐剥昏铝挤咏攀歉勘厌萝胀冻怨漂酮踢钩橄璋醉喝棱履塌疯缸荫炳赣臀氮烂淳熔睛穗丢哺裴躲窑僚旭锥撑娅镶厨滥矢歼糕谨盲咒盒匠妥凸榴涨艳斌匪竣牺秉戚冢酿岐巩阀痕棚畜匣煌缔徙辱垦帖摔喉钠霖鞍梭庵酯泪麓漆戊聂勾黛蛙饱阜嘛铅虞畴瑶贿畏糊苔蛮菱笃欺枫闹庐瞄饥澎椒哭浴漳佣廿牟淀罩阎虐瑙戌垣钧笛祷幢拦帜拯彗驰朔韶辣凹谍蛤椭酋噪弧闯棠悼鲨烦彬绅翡颌楠谴濒浑胃硝膨吗粘蝙瓣滴咀杏寅乞氛赦酱燥骤湛焰惑吓瑰牢鹃禹磅堵黏饲冶摘沧橙玫槟淹缆冥棍窟汁缀孜谕窃懂肆畿纂阐芽邸剿簧戎迄窦陇酰艰泌鳗濑瀑沛钥闵匿沦劾滞寡奕懿煮稽橘绣酬孚侄隙荧邹蔽诈妆宦绰掷汕喊绒寂垃啤骂浊缠鞭峻筋梓锣倪勉敖叹妓谜渗镰壬藉伺雁圾荔俞焕颅酵仗竖滇纱萍傲嫡呢榆剌鳉烤诵诛裹吋狩垄缪殉彝狸墅曝萌墩茹蓉锯拾碘蕴藓墟臭耿蹄淋挝稚岑婉暑悔桶缉钙哇彪谏衷蚁芯肪犁樟羌舜粟诠琦嵩竭挽聊牡魅僵轿赚砍啸硅袜愚啦茜爽骸趾芜碗龚乒禽裤咬晰敷壶垫擒弼淫衙舅砌堕绎蔚狙檀豚薯蔓锤鲉账弊亏澜匡膀鹦嘎嫔舖廓莞倭罐皖愉渤蜡毓炒驹鳃赎飘晖钞瞬剖蔬霉哩淆釜蜘樊毙褒烹驼厕扳钾邯渭郝俯饿甄霜胚蕉摊匀钮壹猩癸胤祇檐擢祁睿岱罹抬孢杖卦梧沁钉崛骆泼刹沫哨凑兑脆迭凿沂莽敲娄邢矣榕疲莺梗挫蝴醛裙汐谅猿楷蒿暹袍於蝉媛渝惹犀雏妾溥膝巾盔宵抹笠蕊泗潟喙鳕匾莹晤灿苷乓舶髓肚垩膛搁眷娥簿铀驿黔膏祺涩雯堀渴傍勿栅肋谟峙泾葱讳琅桦卤敛婷溉婿疼汾敕辜渣耆奢幌卯郸蜗俭峨耻冤俩睦谎溢堰磡虔您苻桨洼顽岚羞戟棺烃羚枯鹉寝镍菁璧蚌晏侏绸晃洽吟焊啊馨匙骄磺羟瘦闰塾掀慌缮诬枣雳竺刃埼丕糟胖摸芹泵麒馈揽岬蔷钝萼捞嘲恼酉氰抒殴锌琛霹佥侬衬谬磐骼岔梳棵蝇忒倚锂瑾兀鸽穹萤腕菇娇斧鸠秃枕鲀诃暱懋淞搏衫瀛皓汞棣苞咪舷祂衞焉瑛兜蜒荀锈捧戍瞻兖蜿炬浚霆洒缚憾嗜鑫蔗睹熹芸嘱撕抨胰陡糙篡靶溜茱觅桩蛾噬玺曜蚕萃阙帛韧霄颚稠芮哉沸蟾恪灶栈怜渥艮孵祚莓隍杠砾溴峭芥煞逗锻捍煽妄黜昼鞑沽敞箕俾曙蚤翟偕皂昊薰淇绞粪矽篷邺慨鳅苣屑晕酚赁烛筛肽禺埤躯潼镖妨颐恕麾笨恺臧瘟俸乍靼羁镁蚊硼皋溺吡刮舵龛辗泻暮钊贱隅堇帘酪娟饷沐膳桢盎玮泣楞泸侮譬咽斡曦挟靴晒沥鲑瘾驯巳鹭莆傀铲诅羲彤挠坜铉苟吵纶诰妍庾幡椰柠佬韬栓靡陛煎豁瘫槐衢恤夭醚裘儡腥绫绮砸沮埗赂捆鼬汴葫呆奚鞘芋绶濠喂蓟淄瞒阱蝎峪萱镀炯炫伶晟痴瞩氦扔拢湄隘鄞柑戮翘旷毯倩琵挣钵鸥蒜陋榈醋檬崁矫冉槛娴肛琶锚萎钛燮叱疹鹫讶胥淤扯鸾姨陌磊嘻掳稷佚垮拐幂揆皱寰壕苇竿铠拷桅糯珂跪毘霰笋弈卉巅苛翊渲蕙涧咎莒臼朽懒汲炽悍诡瞿悖虱沾篆漕圻憩昙釉岂蔑掣诫簇聆昧剃悄痪蛱闷铬殡帧媚孺颍悚饪甬崭辕睐琰鲽愁侃钰坍啶濮驸谤泷笙陂甥骇馏峇埕炀绛杞咤僖贮褚珪衮傻隼臻骁镐疟铨撃哆桧妒盼聋瓮砷毋谒跆攸芷谚苓踩昱撼侗挚鳚雉殆躁蜴畸羧铵耦腻烘遏侈歪皈濬褶灼魄樵椿赈绯鳌琐葆茉贻驴楔叡颊浒绢瓯弩趟筠卅锰倦圩馅栉銮脾恳桔沅喃媳羡炜鳎瀚蚜碉跤焘拌傣莘琮辍胱耍飙窜捣澈鹊喘匆肴洮哑哗膺婢圃愧濂刁窥搅迥潍裳瞳潞觐轭翎乂渎狠遁槃襟耸躺姪晦浜颤拂夔呕禀茛瑕铂邕荚蟒猷潢辙蚂吼熵靳遴蠕籽丫吠偃诽鮨洙嫉妳扈勐冗蠢钴逵秽弋柿筝闾浇俨掖畑琥骠谛诀粥枋硒酥邝阡藔朕虢溧栎昕捡曳昨逍斛僻氹菅疮瓒榔羰睾钏宸鲱莪熏涤奘衅岷薨忏忻唑咳轼鸮剔絮匝鹘跻蓼拙栾妊弑檎貂昶嶋眺璜螂孀樽螯啮叭涿捏巽宕颓蛟暇镛檗沌瑄鸢郢佟扼巍筿琏囱伎奄坳惕叮煜熄眶肘腌癫迺缕馥阇挨荥滔湳妲砚歙皿黯颉迂滦佃鄙牒炙慷荟胪氓憎渚掺诣戛贲麝鄱璞篱浔璐茫烽尻碁婺沱脖擂讹灏尬盏亵匮璇瞰髅钨丐胄咲叻劈璟骥惰峦昵涟钳秤倘孛懈鳖珲纥鞠啄仄艋屎舺镕岖歳蝽缎旻撇嗅鲫荼嫂掸亘骅呀卞劭诲呐拇涪怠旌榨枭崧婕谯蚬咐玟缙骷磋尴骧钗汛酗炔饵佗嚣祜乖酌稼淦哮艏潇蝗罂槭岘魮祉鹛槿讥嬴鸫嬉覃咕靓蜱蘑抖贰痒赃螈犍葺嗓醮珉铯遐鲆唆亢谔佘绊癖柚睢冼泓耽羯躬兮纾墘锑迳踝凋笈摹鲿鬣髻恬粲睽惶碌澡腋羹烨娠锺熬鼩瞭湍槌秧滁凳蕈灸浣堑蒴铿蟠徘屁捻俏橇烁泮梢寥淖筱铳徊唔杵喆藜荻忱甩歹柩醯亳燧澧窖氙筲撮晷寞怯讫缢唾叩鸯簷轶琨崴悯鼐赓龈漩疵惇窘郴珑漱渍堺娩脷脐妤锭愍碾俑骰晔邬轧蓓苳聿碇玖僭阗茧镳窒凛痊歆攘胫呜锄荠踞骊溅慑嚼肄诹狡脇戳箴阉钺畠筵垅挛弛娼蔻痫驭呤邳洱拣钼娑桁疣姥鲛矗盂炱扒猕铮璘珈堃筏毡孽辄楣烫揉缇俳蝰洵琢榛钡蜻荨珮牦橱煦叟鹟闺蜓鱿凄鸳隽恃挞澍绚楹珞枉钚驮掏蜕锟铍缨粽琬箔罔逅肱畲阆邂蕲燊杲宥婪疡殇鮠谪鹬岌纣糸辫荪瞎啰勖鹳蚓蚝嘌佼酝榭镭茸腭莅菀抉粱悫佤榻滘祗咙岙嘧鮡獭锢谿拮鳢懦蛭檄俐沔帚腈鳝醍栃戡喧芎猝菠庑獾彧噜骞脓爹暧恽赉吩嬷迩蛎疽黍馀焙矾氐霓钒螨蜈艉脍俚紊硖雒鲗哄寛啼镉醴瑭锷铢苴诟蛊瑁烙醐勺鲂豌羔瓘蚣瑠痉郧赘谳幺媲鲡鲲弁漾蠡骈漓觊蛹苋姗蹲颢栩峤酐遽榉徳垢鼹犊昴庠噩鬃霑夯腱炖悌疚靛钯罄咯杓厮鯙恂鞅匕鄢洄呵繇糜鳟铰殁栢嗽轸茴箫孪疤猬膦柘钜玑邛惮峒谗眩戦佯鹈纭烬涝颔俪饺佰袒俟珩蝮舫皎琊骡隗颙珥沭憬娣鸵诩昀镠绽湟汹揣戸缵敍诘褫蜍珅淅様萸韭铣螃掾蔺萩稔痺湮筷祎痘焯帷谌痰嵊顼咄漪垭廍驷郦菏殓匐斟嬅觎钍郤邾赟裨睫嵇瑯赡笺瓢摒镌镧疱辻璿祢铊邈嫖啉徕逞铋嫦郓垛栱隈邃簪阖踊耘憧阈靺闳渺撷惫厩鞨闫縻拚趴柝鲇鲭晁亟霁砦奂勅阑郯喵涣诒盯廪螳绷辇蒯睁糠玳幔奭邠诋屹鲹涸霾匍馒瘠県孰锗嵯桀髦遶涕鲳锆哔忿娲攒炊嫣澹榷郗扛専咱侥猖砺鲟锶砥荞锜猥饬嘘稗璠苌辎胭疃琇轲窍盱褪箍癣澂玠囤萘牍浬帑刍鵙铷蹦拗珙恿怂锵豺毎棻茗铐痢偈笏珏獠尕谘妞鄯瑗闿璨暐恙梶锕诙肮鄚麋岫菟蔼煲晞込谑碲郃珣谶瀬墉痹刈谙薙儋腮翩璀乩蒨碓鲻吒颞缤汜陟钇逛菈壑踵漯枷蛰爨谧藕煊镓秆惭涓鹑陲鎗笹蝌砵砀湃绾歩蝾蚪猾耒铌呋憨俺陉铆濡橐嵋楯阯蝓燎嶷鹗蛞薮桡霏赭蟑眙唁瑀氪呛槻胧塭雹荖肼氘歴揪锉猗讧蚩咆阂綦蜚珐镒芩俶蹼剽镂碛橹徭姫喱峠勗鲔涞楫恣圜忤殃翱喹铱瞪倻俣蹇菖汨愫坯滂朐龢虬揖碣獐挹菓耙焜贽膑旸旃醌坵鹕黒鳐蚵翦桉崂讣粿炅椽昉钤蟆睇侑捩鳔藐豊莿纮彊嗪脲绀嘶獗砜鹄竑祟曷铼痞藨鳀蚺苎宓颛觞孖啬瘀蜷脯蹂畈坨綝朦葶篙恸僳侪诂袄潦暠羿睑沣嘟玷氡楸恍纫鹞坷蚶芗跖钿笞凪辊钽埭碚婶躏狒哌傈铟缜傕窿扉弭玘槎惺惋闩郜夙褂愔佞骐泞蹠擘墀髯芪箬爰岀哼灞氚锝跷淝氩梆旼崽杷吝僮伉酆噻蚯卍楝铈蒾驒锹禛棂晗妫訇椁阚洺豉柃蹊蜃蛄缄関辂砧戾寔蜑芾臆韫霭晾垠辘囓穰锴璩椴沆惣沢鲮鄜甾裟瑷塬舂牯枇菝搓鞮膊倬跗怛帼飨躄涎芍麂迤藁疝樋搔巻嵙啜鮈蓑萜筊琚蹴憍愕増鼱楂垓袈袂蓁苄彷蟀祛嚎缬淼嘿魇嘅袱袤籁槲埴溟吲郅矜讪泫筐秣揄拭飒毬寐傉玥濯鲎蟋蘸瓌杼刨亶镯趸蔵焗暄鏖轫蹶沓蛸発遹腑晩哚刎铧蜢篝咫峥喔眈佶葜嗝稹狈铛謇蓣瞧冏邽刽钹眨泠拽崮敝馗淌褓镝跛萦舔隰磘昺嵘嗡隷姒拴菰溱佺镅滢瘩疙泚哒鬬茁榎劵嵬镗瘴徇钐邰苾罡疍滏汊揶傚鎏胛狳毌荩犰犒淬诤繙皝澶圹冇鸬箐柞怿勲鸲缭鯓飚铤穂钕璁犸恵鸪簸樾椋坌鹚壅囍伫褥仡圪鲣荳唢咁苜阏爻啾哦髋藿獴枞兢拈倣簋撬鲬洹嵴鲢蛳葭犷勰锇莴芈糅栒弢蛀萁礽磕鹀窈祆鹩婵喋劢霈貘誊疸啃黻铕钸郷谩蘅蓿箓囿镫邙磬鹧馑苫膈狨棨捶锔钫舯榫闼蠹榊酣焓峄荤巯逖蔴殒姝囧円佾鹮攫塱鼯闱疋懊廨咩苕唧皙嘢冴鱚楽髹酢翳罟搐抟侂鹌柽骘讴痲溲悸媾哙葳畹焱搪莳胳牂熠枸噗泯仝饯隣谄孳姉郫蜊掐鉄苈呗凫伝粑咝鹋廸塩蛏稙洸缗坻倔佢颧芊枱埸伕駄颏婧髑蠵羸趺葩缈砻桤雎钌蚋簕瑱烩溆氖稃瘿燿僊骋蒽甑瓩瓠仞麸湜榖睬崃埙铑菘玹炘蛆痣戬俎赝舛燄毂扪噱吮効鸸痔嶂乜鸻舎砭沩抡惘嗟啥鞣渑屌噌黟麿鬓胯玆梿捺扦鸱钋鄄荽鹂阕磴実俦逹薏嘈倜祏鲃蹬胝绌濶虻呦阊锽硐掴捅咭仟黝铪榧拏姣唛鸨谀荏笆炕濉怙徨剡驩锫赜珽嬛鱀馔鋆粧嫚螭缰琯剁黼鬲郇耜湣廆镱缯埒嗲鮗鬻郾逋郞衹孑劻傩濊柊卬镞豨莼绂窰呱砰嶝尪塽锾裱芫绦涮槙枥飏郏蕤茔拧鄣嘏諲罘昪巂嶙埂儆鹎莨翚睺幪圮圉舢肟瀞湫讦纨砹溏屐阋炆枹対仃诧壆嚏鯻镎跸褔総碶栻偲蛉珺燹堉喰绡浯浃揹濞樗屉尙鬘辺芃骜雑豳胍窕煚戋魣骢擧徵啲鄠谲訚腩晸垌哏骝翥団翀秭祔畬坭丶鲵荛宍鱥雩叁鬟粕笕昐呸铄颎鉲蹋盥癞豢芡缑焖柒呷髂酞璎頼鋐谠蒺狛枳戢鳊饕逄迢笳浈呻锿郛臾窠浍鉏邡襦翕缟箧墬圏驺镬荸縁璈玙煨撩赳詧竜炤滉楙昰恫崆娓咧馁箝狎饨邗槺槊戆唬莠瑊浐钷渌椤俵伋馄钪诜袴衽瑈峋啪瘁熨栀搀惚咗踹耨猞悝妣噢嗨臬樨逯猁炁斓匏赊蓖茯笪禩痈潺桠朊売咔黥襁篦掰貉菽糍峁妪颀镥赧豸蛔禑狍孱垸卟觋螫沄捒咚黠蠊懵吱饴裆镤鋹遒茌痤怅帙凊渟哎鹱遑磔碴畋淯揍忡覧腴痍桫戕镣芨箸杳崐埚镨迸豕裒蚱瞥疥獒烜泺庥嚷喾韪铥貊礴玕煖満汧檩啖鹨鲧隠钆郿繖笄烺淸觑痨気劄傥髭薷萣滹丼髪鏊郕襞胼痂狞洎娆埵倧鱵雫铖赀絵篓祓汭駅嚈唭骶鑛蓥縯籐筮眸泱欉鲼闘篾皐疠毽垻卐酩璆牻柢廙仵浠嬗圀嘹鸩髙胂哋俅罴硏梣斫辋粄皑畯揾嗯铫籓獬滓摭抜廻崞鹪馍铽铡逑赍褛肜罽稲秸瘙怆寳嗔咋阄埈裾篪涔悴弇嚟餮甙愎愆寘両髡遨胴橿栝叨豇枧悰広舐簃燏熜淙桷恻峩霎犄垟雠銲璪壸咿鳂竦畦沤徴庹嗤蠲腧纡暾値佻铙钣钖迨螅蚴盅琍珰澥怦廞啧跏菑粳暎帯圯勔镊菡荦徬夤坩偁髀鎓缦経稞硚瞽徂嶲囗冽鲠颕钲瞋瘪畷権扆憔鬪邴谆薜矧摈憙觚腆畀樛垚哟魍觇衾箩盩唃隹羗眭玢犂毖崤麹蓍臃甪犨洧暦姵厔鹇鳠酊诿蘧茆舨聃砒瘸渕浞栲岢鳏遄痿瑢捎悭唠偓鼷颡绉琤澪曽媞魋蜇猊澚叼勷锲翙珧朓啻杙応堞黉鹜蚡肓罨玎狁燐桜暻掟笮瓖潴吖冨鸰陔镔茭篁禳哽呒匄凖乗鮀钬缥篑璹珦棹嘀叽鄩袛螣畤槁梉幷幄嫄鲋谡蒟絜籀櫂柰暌変倓倌鳙頠铒辔菪茑琹琎玚烊彀亹蹉観蜉窣璲暝厍卣镈袅璥栿堦嗒乭鼋黧鄮藦皞甡渉暲撵凃偻蕗苡臯彘妩奁啯伡醺蕰耧纛綎禔曩斎娈仮龋鳓翮穑祧梾慵僰倮鹡鲩茀糬瞌庖哷黩陜衿缶纻燔楢旛搂挈埜嗑鲾魑酎诔虺笥瓟涑檨掲囝鸶袓蔹箦熺沘椙晳掇忞噏厓剣鍱縠狰殚楮桕抠懑壊噤鲥鲅锛蹙貍癜湎栄昝旄惬恚唏伷骙韮礡砟眜歛晙昃搧婊埯唵酃薫蒗舀継籼牝唷砮擀捌拄鏻蹒跚谵荬胙欤岿匂蝣荜罅瘰淍忉夼垡児鼇魃髢鞬镡鄘赑袆缫渋楤巿収鳜霨罍昞捜劼锏躇踌貔螟虓膂潾橼椄栞栌惆嫪媪垱仫铩軚诨篌禵棐晧圄喳啹訾覇珝玨狷毐惦悪嫘埝唉鸷鮻郈螽廌嵛埏垴囹咻颥遛讃蓨忾忑忐屄偌亜亓錞赅葑羕瞑瘗浄泐杈媵喺偎亀麐鲦餽遘蒻煐浤枏朶厰俛鳯魉骕铚袪虿菉玦梂戗愣徼廒娉哂邋谂譔舸瘢爿煅湉惔幛堍齢験茍籴禋盦煴廼尅囷呪伧镃钶遢縄粦箜牁煕旒揸拎呃卮鲚鬯骃鑨轵聩瘘溞撚搾抃愬幞塲垕垍啁働酂蹭跺跞訏菫繋禊獣悻塍偰醪邲苅膻煸瀍姞塀佉黾鲌霊贶蒡舾缛砝皴暕懃悧寤嬖呑鿔趵褴薤粜瑑潋掞怏屻囡処讵蝘晌崚塙噎喽傒馊閦醂葰舗绹绔続筚珵犽炷瀹晊昄摅懽屺図呾龃麇镄郪赙谞紥禤瞠焼溘樯梃朏掮掬嬿叵剜龉鳑鲏鰶蹩裈蝼蘖砬澁淠洌橚榇梏桎晛峣咛侁髁陬蓇莜艛箪玭暸旳撂呔仼饫飑跩赕荑艽缐糀篯濙殛椹旖宧姮喼咥黐骛転葖茚澣棫暊憋堠镏辏蠋蕻荘芴腼猃済泖椥桴暁旵捂懐恁锎谮诳褙芘緜滝沚柟掂庁寯埇哱哖騒酺鄫箨礐碻畧涙歃敔搥慥慜従崠尢坬咂勦倨骓饽鞞蟥绲窨砼溇湔泅彛廵吽黡阌蹿詝衪蘗菔畊爌燝涠槱柷杪敉弐廛巌婼囯嗦倶佝齮锒逶躅読衲紞竽砗烝淓洭櫈杻撄弾鯭镆賛蒌苁筌淏槠扺扞廋帔帏墕劬黁馘颦鄀蹑觌衎缣糗磻琄漈殄搆拊孓龊鼒鍙褡茏筅矸炟朿彟崱嫲妺堌営鷟陁釪逌筼磲磝疖狓烱溍棪柁斝攞揿噍単勍倏馕鞫醢酦踯覚茘眦畚犴澔椪彅廐嚓襌蟌虉葎莸絷疳珎燠煃彖姸姶嗷刋龠魈铦錾郐觿袢蕖蒉缃竝牖漼椝梼侔鲙鐡釆貅蛲蚨蓂粢簗湓沏檫昫愠徜徉夲吿卨鞴贌蠖蠓舣耄穏疎潽浡浉欸楩桄枰撘惴怼媺塅啕伾丏鹣鸑迵跣螠苘翃痧畛甗湼湝洣沇氷槚棓啷刖麈鳡駆飡鞆賨謢苺穉珤狲爀浛樘暅搡婀嗢嗉鳁鲴髌靥锳鄗裿葚芑羱缁禟瑨潆楦搠挙憺彜崘屃孥嗄啱龌霅钔錬醅蜣蜞蘼蘂缒缊絪筩祹玓濩潸溦柉懔帻嵒坮喈劓傧鴫骺馋迓蚧肸祊砫砣璮殽杌旰嬬奀吆侩佖麺鵄骀韡隄钎轾腙聴绺睨痼玡漥淛橛悛崄侘鲊衄绖簠筇礑矍浿汆殳撙摠搦忭庯宬婬娍塆嚭啫凇鼍鹍鵺鳈颋顕靬锍逡跄蛴蕡蒎舲臊糌笫窋瑆焿灊洨検旎戽戯悊忖廑帰垲喦刳僎伥龁鼾鳇頔険轺螉蚰蓦纰粼痖瑺瑸瑫狃浟沨槇梱姘坧仂骖镪镟鑅鋕釴轳薗茈缳缂篥磙烋溷渼汩氲欹橞槰栳杣曌挎憕忝吭儦伢軽跎诶薬蕺臈羂筜祼漑漉泂栴枓斿斲攥掎徧岜屭墁堈啣呓鳒魾譓蕹茼脘籾秕禼癀瘉瓿琫熿柾撸搢拶嵝娀姈埧噫唳傜驎霙銭诓萮艶脒竩禠盹瑔狻犼漷洑殍棼弉麊锱讬綷筰疴疔狔焄瀼澌滃氤檠槩柸摰揑挵扱憻媭喏啭唎剐兕仳仉阃鍪遯踱裢衩蟱艚禨眇璂瑮琠焞曺晈攷摞庒妁垧佧黇钘譲蝈蘩蕚茬缡籥禘瞓瓈瑳獍爝溎殂樉杬昜旽寊嬲堎嘤倞俋阝賸襕薹菼脔聒罾稉禥硷硇瞇璙犠爚燫澯漶渶泩檊桲暪撅捋扃巣巉岞嘞叆匦偪仨鸺鴈鲐髷馐靑輶踫踉觯蔊荅苊臓眛玶焌烔澉晫敩悳嶍岈嫱墭墡喩凼冚兎俌仚丨鼢鹠鹆鲘髫馃讙讉誔虒荵粍箒秾秬磜睥眯盍盉滧渫橦椆枵摁庲嵚嵎峘岣姹妘妑墌咺呖鱾魩邉謩蒄耋綮窓矞甴玗炻泇櫆梠栊帡姁堙咾僜鬷鬰騋郳郄觥蕅蒹膘筈秫痱琡漴滠洊氵揩彄圌喑呉匜倕倂雝锬鐤邶逦觔艟臞肏耷翺绻綧筦秏碏盝瓉玧瀣湴桝曱旿敫拺抿怵壱塡埿垤喟匋刿仏亻鯥觜衜螋蜩蓊萋莕艸脗缲缌磎瑴琲珖猇烇汚氽椟摽摛抺慭愼悱廰巑岵屇媄娫垿卺俀丿鿕鳣鎅鄕邆賹蕯荇艨艅肫绐緖箎窳祃猄牾炝潏氿曮攴慇嵕寗婓夬圧圑厷厐刄鵼鏸錱釭鄡辒貒詥茷舄聡硭硓盌皦滟橓楳曔昚昖擕愒惪恊屦奨咇呙厣冧僔侅亊黙鯵鯈骎騨钅鉨酹鄤賔詈覩蟛蝥蛩虨聼瓐璄猢狝牤熥溁沺沬橥榑楗栐栂曈捭拝悆娌妯奣夨埻哝労鵎驽铏鐬鏳跂豷诌蛐莩苒舁脢縡礤硿珫犇泔栘杧晄庋崀墫囲嗌匚儇侹黈鹓鸹鬈駉饹靭雟阍镲镮鈊辧轱菥莛翫禚皷瓴橒榼楧棁晡擞搽戥弨孬壐塄埨垰卲匽俫価齑鹖馛馎鞌閰镦鍟鋈踼跶蠼蝲蝀藠荭荎苧脳脬缍砈甍獏狯爟爕瀷渓沍汸樻榅棰柙曕敻捽嵓尒嫫婞娡壝壂墼墎吔兪騣馺饦飕隳隃陴鏓鉌郚趼谖诮诎讐薁葇舻聦羑绠縂祤祅皤珹玱猡焴溙涷浵梴枨旉斉攽廃峃娸娳妬妠堽垈嚩嗥呣剀僙鲯鯃霫阼铹铗蹀豲讱諟諌舳脿纟簒癔猱滽滈洢氕椇栟搴抦惓悒忳弖嵖寍宄垆噉嘥嗳嗱喁刧鿏鵞驵颣颃鐩辶辵轘诐褧衒莶茕罣粋礓礅睚睒瘐痳畇甓炌滆楬楘晢旆摃憓悃怱彳峅寃媜媓圴剉凴僩鶏骍趒蓚缱穟祘疐瓤漖洐檞榣掦拑慝慆忯幵屘嫳垞坼坔囟囘咢厳刣倅佮鹁馓饸饣雱镋躔趯赒貤裥蕞舩翾罸筥笱窅瘼疬璛瑍猯牼熘溵溋榍梫枡曧曁昹昑昇旲撖挿扥愐惎嶚嵿崒峧屣寜孮嫒婠娎埽埌坫嚢噼嘭唹唫倢佷佀髆顸韺阬镴镢锠鈜醵酤辿踬踨踎褕裇蟊虵蓐萆荄胗紑粃簟笒硎矻瞫眕甝玒獇燨熈熇潲漒洫洤槏椀柤枬晪敭挻挢愦怔忸徫崶屙宀嬢嫰媿媖堿堮埆叅厖剳乸鼈騑餍霱霦釬鄾鄎鄃逨迆褞褊袿蟷蜮蔫芐艄緁箣祾祩碡疪瑅珷珓珒猋焢滍氇氆歊桯暤摂摀戻憼庼庳嵰崾崈嫮婳妗哐咦刼僴傃倗俍鿫髎颟霤铔邘逷辀踮赪觝蛯蔟蓢蒇葙菻莰芰舡脤耏羖缞縦紃粩笾笤穋禇瞅盋瘜瘕畮甽烚歯檡榽楡棆栜杢斠搨挲慓忬徸彣嵫崟寖嬨婤夂壻墱塜囎喨哓倥佋伃仛亅麁鷰鲕鲒魌鬶駮飮飧鞗隲酏鄅逎讠褰袝蠃蒢葸茝聟耑罊纕緃糢粝皕甭璬琔珔玪熛炲漙漎浥汫櫲檦槪楶椼旡擭揼慊嫈婑塨囇嚤嚆亷乪鹲鰗魠鬭骉饩饧闗镘锧钭鄋逺踭踘趖褵衖蟴蟇蓠羾罳缋簏磪碥睟眄癿璷琁珌犏牴燡洴汋氍毳欷欬槅榃楪椚椑棩棅栔枟昻昣撺揺掊戫愃悺廮尫妏壌哕侉龺龗龅鲖鯮骹骟駼饔霬閧鑓銙鉎輌謤諪螲薾蔎莙茳芛艎脁聱翛羝缷繊粛笊窴穣稾禆祌矬矔睗眬眘眅畳甃璚瑬玼爔燵燚熀焮烒潫湲湌浰洳泃櫾楒棜柶暶暟晅昡旴敳擤擐揲掼払愽巎嶓崿崦岧尨嫠媸妸妌夛坖叧卌匁僡偬佂鼙鼗黗鹾鱎魕锘鏼鉱逈踦謦訄覔裲袷蟪蜾薅萡莃荂芟艿脎罝縢綪絣禓祋礻矟瞾瑝瑎琜琈玊猺牋熯熞焻澋潩潖漭涍沕殪槔杩揵抻抔戣懮慬怍怃廔庀嵵岽尟嫽嫕夒墈埢垶坰囵嚞呴劒刕僬侊丌鼌麤麃鵖霔隤隞閪閟閙锞铓鑁銎釿醘酓遝轹轪跽貎譌謌訔襜裰虁蔀蒥蒈菹荈芿芚艼臑脰耖翯翈纩絫糎箅筳竒禬禖磱硗矴瓞玾犟煟澭澫澙漋滶淐淎歔歅梡昍旯旮斄擥撦摎搋扜怤徤彍幤嶅崋岏寕娙奵奡垯囫啵呌叚卭剰冮冩儞佽亠鹢鳛鳆鱙颿鑢鎜鈏酲跬貮謜覊褭裼蝻蝪虮虗薲葊萳萏莋荙茇苐臶膣翜羼绋绁緈糨糈簖窻窆祫瓓璺瑂玅狴犫犘牓灴溽湑渃洈櫍槜楕柎枒摵捴挼挐扌弍弌巃嶦嵜屛嬭媃媁奓夀壡墒塝埶囥囔嚚唪哞咍呲厯卋卄匸劔刭冔儵儚儍偾俤伣伛伇鿬黮黢鹙鶆鵶鲞魖駈鞲阭閇锃钑鍳鍭鍗鋺鋡釈醸鄹蹰蹓踟贳豋諠訳觱觧觏褆袾蜧蚖虩虘藟菍芼艻艹臙胬糁籣稭秺秌禴礜砑矼眚癯癈畎璒瑥珘玏犭灜澴淲浭氅歭歜歂栯枲朞暀攵揷扵戉憯怄忮忄彡帱帢巆嶌岠尃孏婻妧塉堣圙圐嗬勧儗傪偀侾侒黹髳駹頞韈鞶靁锖铻鋳鋬鋋邔遫轕蹚跹趆赗襷裬裀蟧螵螬蚆虖蘘藳蔍蒱莤茖舠膥脞肭翷翂羮篰筻筸笵穄禸眵眴皲瘖瘅畺璻琭琕獳猰狌爓煿煓炑灋濋澺淰涫洿洇泆汏氶槢榱榚椬棷梈桮枖晬昛擿揃掻掤拠戙愀怩彚彐弽弰弪幚幇帞峼岕孁墠堩埱埓垾垐坣囮嚿嗾嗏呠吚厶劖剅凵冫冖冋兟僇倴龇麑鯸韾靱阨锸锓铇鍌銕酔邧轓蹛踽踣趱赇谼诇襍襃裛裖蟫螔蕸胲肈聨绨纒纉簌篼竉竃窭窬穨稑祦礵磉硙砳矰瞟瞀眎痆瓅珆狶燆煑煆焐烶灉灂濍澨漌滪溰渽涴汈殑欵檰槑榤楛椐柍朒昽斁敇攟撀搣拡慿慱愭惛悕恾恹恝怫忪弶巪岍岊媟姃夊圬圕嚻噙嗖咘俽佴伲亍鲪鱓髾髠髟髄馇饾饎餈顚頙隂阽闶鏱鎋鍝錣鉟鈵酖鄑郙逴逓辚輵躜蹯蹜踶诼詟褎褍螘蘵薢蔕葠葅萭莚荋芺芏芉臮胠罉缧繄綍糭粬箠箆筶竂穐禶禝禐禃祲礿礌磹磏碪碞硁瞉眏疒瑹瑇珄玜獯猘狦牕爋烕灺潪滗溒涢氊橺樴槀椳枻杁曛暭晭揠捦挍拕拃恮嶰嶐嵮嵨屮寈孶孭墐塰塈堨埘嘣唻唊唅厎劦刂凧冦冂僦偱俧亁麞鹴鹯鹔鸤鵸鬛騆饤闬锨鑙錧鉮釡酅郰逭踅跼赆貹貐谰谇誐觳觕襻襸裯裋袗衂蠄蟮螗蝤蚳蚢虙蘡蘐藂薖蕑蔸蓳蒧蒆萑莭苿苤苖芶舋臌腘脋肐耢耇羢纴籖筭笭穜祄硪硦硉砢睭盬皥皡皛甿瓑瑧瑘珇玿獛狖煣煠煔瀜瀙濳濔漧湶淜涭涘泜汯汔毦殣歕歓欝欆櫜檝檑檍橂榟榙楑棌桭枿枘枔枎枌朩朅曶曡曎晹晥擗摏搫扙戠愙愓弅廾巛尭寁宷嬾嫏媮媗娖姤妵奲奰夑壖墺墣塣堔埫垜垔噘噂嗫嗙啴啐咣冝僄偒倰俢俒侞伓乣丂龂麚鱰鰡髽髃驲頣頖頀韨鞄靫霶雊隩陥阒锼锊鏋鍑錖銾鉥鈤醑遅逰逤辌輭踺贠诖訸觭觟觖褾袠蝜蝂蛍虍虌虀藚藋蕀蓪蓩蓘萐荁茺苨苝舭腽腚胨胔肹翣缾绗綗絿糼篢篚箙穸秥禫磵磗碹砉瞷瞢眊盻癶瘵瘥瘣瘝痷璕瑃琩琀猟狆燽煻煶烑灈瀤濵潓漦漇溠溓渇泴泬泑沴沜氻毱欻欥櫏檽檥橏樒槴楎楅椛椓椌柀枠枛朸朘朌朂暍暆晍昬昩昤昢敾擸捃挌挀抋惖悮恇忎徍彑廱巄崳峑峎岹屾尛寷宭娵娋奝壒墦塁堄埮垙垇嚬嚗噺噛嘡嘚喌啇咟叇厫卩勹勫凟儛僾傛偸偡偊倛倎亰乀丄龘麕鹥鴎鮘鬐髲髗騩駪饍鞥靰靇霳霪阛阓镵镚鑚鍷鋷鋘醡酼鄷遬轝躶躭躞譒謷諽誎觽襾裦裎蠛蠎蝹蜼蜺蚠蚍虪薸蔲蔌蓺蓭葴葢萹萠菶菂莝荗荍茞苉臿臝脽聍耤耈緫絭絖絇紏糒簰筯筬笸笩竻竘窎穚秅祳祬磤硩硊砩矺矱瞍睩眀盫皃痩痟甞璔琣珸玝獚狢犡牳牗爯熖焲烄炏澼潄漍湻渒沋汱汦氎檙橔槵榰榥榗榀楃楀椵椗棽梽梪梀桙柣杕晣晜晆旟攧搲搛揞揔戤戞慔恠忔庱帗巙嶤嵷嵞嵂崌峚岃寽宱嬝嫙媠奫奤塼塸塯塟塂埑埅囦嚅噀嗧唰哧咑呫厹勠剱剞刴刲刜冣兝偦偍倷倝侫伬伒龝齹齁鼔黚黓麀鹷鸴鷇鷃鴸鰯鯏鬳鬦鬄骫馵馡飬飂頨頟頋韴鞡雚雘隟阰鑃鑂鐊鍹錻錍鋾鈪鈘醁酾酴酀鄿鄌郻郍邿迻迮踔跕跅趿赿貭貣貜豭譼諿觫襈褖褀袐蠭蠚蝯蝍蜋蛃蚔虡薿薐蕣蔪蓰蓈蒚葍葂菵莾莬荺荝苙芠艪膶膰腯腠腍脭胷肧聠翲罫繦縚絚絋粁籧籑篲箚箑筫筢竸竳竢窸窀穵稊秱秠秝禌磿磈碤砯砕睆癃瘳瘌痄疢畟甶甎甂瓫琖琓玔玃獂猔犎牸牷牣燂熷煳煋焠灟瀴瀓濇澛澏潀漘滀湥渰洟洀泿泒氺歈欂櫶櫰櫑檖橅樌槖楺椠曯曅曀昳旐斾敶撢摷搯揜揎揅掍抪択扠憘愰惤恧恌怘徃廴廏庛庈幙巵巤嶪嵱嵉岾岺岥寴寭宂婥婣娒妱妭奱墪墧塠埁垝圗囬嘠喣啗唝呿吥卥匼勼勮勚劂剤剕刦刓凐兛儅僣傿傤傔傊偭俴乚鿎齄鼫麌鸐鸁鶵鶠鱽鰅鯠鮴魞鬵骻馦馝馌饀餲飖颩頪韤鞯鞜鞔鞒鞉靸靏靃霿霩霚霋雰雂隑陃闚鐾鐭鐚鏣鎕鍧錪鉐鈢鈚醭鄨鄛鄏邟遆迕辳辤轒輙軰軎躼躱躰躐蹐蹏蹅蹁踈跴趫贒貛貆豰豀谝讘譮譠謍諓覑覅襳裭袟袀蠧蠘蠒蟉螜螓蛥蛑蚘虼蘶藘蔆蓏蒝萫萅菿菾菢苪苩芄舃臽脧胐胅肦肕耔耉翖翓翏羬羓羇罻罠罏綼紩粂籺簺簮簙篴篊箂筤笀穈稺稶稡稒秔礩礠磇碜硘硍硌矲矅瞵睸睰睎睌皜皀癹癦痌痀疰疕疁畾畱畒畂琌猼狺狫狕犺牿牥爣爊爁燰燀熤熝煾炣炓灨瀶瀵瀫瀡濲濦澻潶漹滭溚湸湨渜涬涖浫洝歘檿橵樔樀榶榢榞榝楲楜楏椮椧棯梹桹栧柆枃杮杅杄曣暏暋旍斴斳敺搰搒搊揦掯捼捔挒拹拵拫抝抇抆抅扤戺懙惸悩悤怳忼忟徛弻弸弴弎帊巠巜嶬嶫嵺峝岻屚尠尞尓尐寑宼宻孹嫇嫃媦婍姽姖妡奯奊夘夌壴壄墿墇堼堧堢垹坺坶噐嘐嘂嘁嗼嗮啒啍唿哶呇呅卾勭剟剚刬冱冡冓兤儹儯僿僈僆偨倐侸侴伮仭乛丩鿠鿍龖龀鼽鼏麰麛麖鹒鸃鶱鶑鵀鴡鴠鴓鳰鳦鱻鱬鮧魶魗魆鬿鬒鬏鬌髤髣髈骾骽骱骦骔驣驘驆騐駬馰馣馞餂飤飃顜顉韱韘韗韔韑鞊靔霝雺雗雃陭陑阘閖鑐鏪鏧鏄鍂鋻鋞鋀銤鉩鉧鈫釼醾醧醤醄酧酡酁鄪郠郘郔逥迌迀轁輚軮軆踡跰跐趐趍趌趉賎豽譿詏詅詃訬觻觍覴覌襚褷褝褅裵裪裃袼袵袡袚袙衤衟衉蠏蟺蝳蝚蜯蜅蛵蚿虷虭藙藊蕂蔾蔄蓃蒁蒀葓葄萚菚菆茻苰芇芀艔臷臵臖膸膫膋腶腄脃胊肎聸聮耰耩耞耎翧羭羙羍羃罶罇繗繍縀緵緎紻糇粨籹籨籞簝簜簔篈箟箘箖箊箄笘竫穽穧稂秡礞磄碦硠瞹瞱瞏睱睔睄皭癪癍癋瘆痁疿疭疉畖甒瑉琸琟玤猳猐狟狋狇狅犵犳犲牪爼爫爗熼熣熑焍烓烐烌炧灥灅瀁澟潬潡潝漺漟湦湢湗渏淢涥涚浱浧洅泲沷沲沯毹毉殟歬歨歗欶欗欀檋檇橰橑樍槷榯楟楈椫棬棈棃桒朣朡朙曵曒暺暳暚暓晼晘晐晀昷昋旞斮攲攰攮攡攑擆撗摴摌搿搉揳揈掱掁捯捥捄挷挶挰抳抯抮扽扲懬憟慙惙悙恦恉徯幑帟帄巓巏嶻嶒嶆嶀嵑崵峛峍岦屴屲尌寏孴嬁嫥婯娯娭姙姏姌奅夅壔墯堭堘埳垺垎圫囙囏嚡嚜噰噔嗛喴啚啋唦唂哿咡呯呬吤厾厪匟匃剺刱兿兡儫僐傱傰傠偘侓侌侇佹佲佪佁伱伭亸乽乹乆丅龆鼭鼣鼟鼖鼊黵黭黪麽麎麅鹝鸼鸧鸓鸎鷧鶮鶨鶤鵭鵕鴊鳫鳤鳘鲰鮾鮢魬鬇鬅鬂鬁髴髩髥骿骳騞駴馿馉馂饳饡饙饂餹餠餙飰飜飗飐飊颬颪顨顠頯頬頚韣韐韎鞓靿靷靮靐霡霟雸雔隺隡隖陹陧陗阸阤闉閺閠镾镸镙锪铘鑖鑋鐻鐖鐅鐀鏶鏠鏙鏕錋鋑鋊銢銞銝鉼鉰鉫鉓鉇鈩釽醼醰醩醨醕醓醊醆酛酘鄼鄦鄥鄐郹郲郱郋邅逩迼迖迒輎軱軞軁躹躴躧躙躆蹪蹧踳踠踕踁跢跍趞趜贁賮賩賖貃豿豖豓谾谹譳譢謲謮謞誧詨訉觙觃覵覗覕襡襛褹褢褉褄裶袯袘袑袌袇衘衋蠮蠩蟼蟸蟢蟞蟝蟙蟅螾螥螝螁蝃蜫蜦蜎蛨蛣蚼蚭蚑虰虂蘽蘥蘤藾藞藇薝薚薋薉蕫蔠蔏蔈蔇蔃蔂蓷蓱蓫蓗蒪蒩蒒葹葨葘葃萓萀菎莏茮茦茋苶芧芢芔芆艵艴艓艍舴舮舤臛臇膴膆腟腃腂脵胻胩胏肶聝耾耹耵耪翴翨罥缼纎纃繑縤縅緾緭緌綶絽絻絸絯絏紽紷糵糓粻粺粊粆籒籊籈簩簊簉簁篹篶篏箿箾箽筺筘笐笅竾竴窾窫窞窊穾稧稄秼秳秪秞秐秊禭禢祑祍礶礭礨礚礕礇礀磢碯碬碔硶硞硑砎瞨瞆瞂睘睃眽眪眧眗盷盭盠瘛瘚瘊痬痎疌疄畨畞畓甏瓪璱璤璏璌璅瑡琙琂珶珳珢珛珋玽玐獿獽獢獞猨猓猂狥狉犿犮犋牜牅燱燣燘燖燍燋燇熽熻熧煺煡煄焺焸焵焫焔烎烍烉烀炶炵炥炡灱灬灠瀔瀊瀈濴濨濓濎濈澞潵潠潗漻滺滳滱滫滒溤湩湙湏湁渻淽淴淊淈淂涺涱涒浼浕洰洦洚洘泭泙汒汌氥氜氒毺毵毴毑殹殱殀歺歫欎櫿櫵櫡櫌櫊櫉檼檺檛檕檃橾橉樬樚樖槈榾榬楰椸椱椊椉椈棿棳棦棢棔梇桼桞桚桖栰栫栍枼枊枆杺杔杇朾朷朳朲朤朖朆曚晻昈昅旙旑斻敧敤敟敒攺攉攅擵擪撱撍摬摣摡摉搳搤搌揬揫揟揇捵挦挜挓拲拀抴抲抌扢扐戹戭憗憇慴慠慛慉慅慂愺愊惿惷惵悢恴怹怚怓怊忷忩徥徣徏彁弝弜弆廹廘庬庪庢幰幒帤帣巶巭嶟嶕嶃嵲嵃崼峿峸峮峐岶岰岮岟岒屽屳屝尥尗寉孨孠孊嬫嬑嫑嫐嫀媱媤媢媏婏娪妢妛妚妀奌夣夐夋夆壾壏壉墛墚塮塥塛堷堋堁埪埦埛坿坱坙坒坄圤圢圠圂囶嚾嚸嚯嚄噭噣噖噃嘬嘨嘕嘒嗵嗞嗍嗂喭喢啩唼唥唡哳哣咜咊咉咈呶呟呄吇吅叺叜叕叒厡卛卆匤匊勄勀劙劘剸剢剠凾凩凞凅冾冸冞冐冎兾兞兙儶儴儤儃僼僶僢傺傝偫偋倸俹俜俙俇侚佡佊伻伂仌亖乇乁丱鿭鿃龰龥齍齂鼶鼪鼦鼡鼆鼀黸黱黰黦黤黖黕黆麭麧麘麔麏麆麄鹻鹸鸜鸒鸅鸀鷾鷭鷢鷞鷜鶫鶧鶝鶕鶋鶂鶁鵻鵵鵴鵤鵔鵍鵌鵋鵉鴬鴢鴖鴑鴂鳿鳋鲓鲄鱜鱛鰿鰔鯼鯺鯯鯘鮹鮬鮌魿魹魫魤魓魒鬽鬺鬴鬡鬙鬕鬊髼髺髨髧髞髝髇骲骩骒驡驠驖驔驈騺騹騳騛騘駾駦駣駏馾馲馫馟馚饻饐饆餻餤餣餟餋餁飝飍飉飇颵顦顇頩頥韲韖鞼鞵鞳鞪鞤鞢鞚鞕鞁靲靍靋霯霛霐雦雥雈隢隚隒隀陼陯阫闟闛闁闀閮閛閚閕镩铴鑻鑺鑯鑟鑘鐱鐰鐛鐆鏴鏮鏭鏛鏂鎴鎑鎐鎃鎁鍴鍫鍨鍡鍞鍐鍏錷錵錴錅鋿鋧鋖鋍鋄銰銧銗鉹鈶鈱鈨鈓鈂釸釱釢釖醿醲醝酽酻酟酙鄽鄸鄝鄍鄈鄇郩郥郖邤邚遾遟遉逽逬迿迾迠迏迍辡辠辁轞轜轙轋轈輹輰輘輏輆軧軖軓軇躽躗躀蹹蹮蹘蹎蹍踿踲踧踚踙踍踇踆踄踀跲跧跦跜跘跁趷趮趦趡趄赯赮赩赨赥贙賶賱賏賉貕貑貄豩豧豦豘豗豄谺谫诪讁譶譪譧譤譝譋譇謿謼謰謟謋謈諻諵諩諘諎諈諅誵誩誜誖誃誀詶詄訿訹訲觾觷觮觬觘覶覞覝覜覙襶襧襒褺褯褣褟褈褁裻裣裠裌袳袬袨袥袊袃衺衠衇衁蠽蠺蠳蠰蠪蠂蟭蟨蟟蟚蟓蟁螶螱螰螛螖螐螊螇螆螀蝱蝭蝝蝛蝒蝑蝐蝅蜹蜲蜨蜛蜙蜔蜐蜄蛦蛜蛚蛘蛓蚮蚦蚥蚚蚗蚒蚇蚄蚃虸虴虤虣虊虈虅蘷蘰蘯蘦蘁藰藢藗薶薭薠薞薕蕮蕦蕥蕐蕍蕌蔩蔤蔁蓸蓶蓜蓛蓔蓒蒬蒣蒛蒖蒑葪葥葟葈萻萢萞萉萈萂菺菷菧菦菣菙菄莯莟莗莔莂荿荾荲荌茪茠茙苵苢苃苀芲芤芣芁艭艑艀舦舥舝舙舑臸臩臤臜臒臁膼膵膲膱膮膪膗膍膉膅腲腢脺脶脮脡脟脝脕胾胹胘胋胉胈胇肻肙肊肁聑聎耼耱耠耊翶翪翤羴羠罯罒罋缹绱纐纆繵繲繧繝繘繎縥縔縌緺緉緅綕綂絟絗絓絈紾紦紒糿糫糪糋糆粴粰粞粇籯籝籚籗籕籇簶簴簳簱簦簘簎篿篭篣篛篒篃箲箯箤箢箌筞筓笿笴笯笢笡笁竮竧竤竛竗竕竔竓竍窽窹窛窂穬穪穤穝穖穊穅稪稨稥稤稘稖稓稐稌稆秶秢秙禒禈祽祶礟礏礉磳磦磥磟磖磒碵碨碖碊碅碄硾硽硺硵硴硲硰硋硈硄硂砿砡砘砊砆砃矦矙矌矂瞙瞗瞖瞕瞐瞃睼睴睠睂眳眡眆眃盿盺盙盇皫皊癨癠癛癗癓癎癊瘈瘇瘃痡痓痏畽畕畐畍畆畄甹甛甆甁甀瓻瓨瓡瓝瓍瓆瓂璴璢璓瑓琽琒琑珚玵玴玞玍玈獹獦獖獓獑獌獉獈獀猸猲猒狾狘狊犱犩犉牶牱牚牐牉爩爤爢爉燢燓熳熮煹煪煝煘煀焬焪焝焑焅烰烮烞烗烆炰灳灦灐瀖瀄瀀濽濹濅澿澽澰澢澖澓澒潨潧潥潐潅漅漀滰滮滛溻溹溂湭湪湤湡湠湒湐湅湂渹渳渨渧渘渖淃涽涶涐涏涄浲浢浘洡洓洉泶沶沰沞沝沎沊汻汵汳汮汘汍汅氱氠氞氝氉毸毨毧毚毈殠殕歰歮歠歀欿欴欁櫼櫴櫙櫖櫕櫔櫎櫋檈橽橪橕橎橃橁樭樏樈樆槾槥槒榳榁楻楴楥楖楆椻椂棤棝棑梮梜梛梚梕梌梄桸桵桗桍桇栺栭栨栠栕栆栁柹柮柭柫柖柕柌柈柅枾枺枑杽杸杝杘杒杍杋朼朁曋暿暼暵暜暒晿晵晑旹旝旜旔旈斵斦斖敿敹敱敨敥敡敆敁攠攗擶擳擩擙擖擏撽撎撋撉撆摱摫摥摝摙搹搟搚搘揝揕揂掹掭掫掕掔掓掅捿捾捸捝捘捖捊捈挭挧挏挄拻抾扻扚扊扅戝戓懫懩懠憭憡憞憝憏愲愗愖愇愅惢惝惏惈惄惂惀悎悋悁恱恡恟恜恓怴怟怞怗怑怐怌忺忰徦徢徝彯彨彉弿弮弤廽廗廇庽庻庮庣庂幨幜幖幐幈幁帨帒帋帉巼巬嶭嶎嵻嵹嵤嵢嵡嵠嵔崯崡崝崜崕峺峹峕峊岪岨岓岉屪屧尩尣寪寙孒孍孅嬥嬚嬕嬏嬂嫸嫢嫜嫓媬媥媕媎媌媂婸婜婔婌婇娬娚娔姺姱姰姀妷妟奻奷奜夿夶壷壵壍墷墤墋墄塕塐堲堬堜堛堐埲埥埣埞埊垽垷垬垪坸坋坈坆坃圶圲圦圡圝圊圁囸囖囐囄嚵嚱嚰嚘嚒嚋噒噆嘷嘝嘑嗻嗋嗀喿喤喞喛喐啽啨啘啔啎啅唣唞唖唒哻哹哴哠哃咷咮咅呰呭呡呏吺吷吪吣吘吂叾叴叀厽厸厵厱厏厇厀卪匵匴匳匲勥勂劷劤劗劐劎劋剭剦剆刵刞刌凷凘凕凓凎冿冘兲兣兠兓兊儳儱儬儨儖儑儌僽僲僯僪僟僝傫傓傋偼偳偢偠偐倿倽倹倳倯倄倁俻俷俈侼侻侳侲侭侐侎佸佫佄伹伳伩伅伀仾仱仒仈亾亼亯乲乢乕丷丳丠丆°′″$¥〒¢£%@℃℉﹩﹪‰﹫㎡㏕㎜㎝㎞㏎m³㎎㎏㏄º ¤%$ º¹²³ -------------------------------------------------------------------------------- /ufont.py: -------------------------------------------------------------------------------- 1 | # Github: https://github.com/AntonVanke/MicroPython-uFont 2 | # Gitee: https://gitee.com/liio/MicroPython-uFont 3 | # Tools: https://github.com/AntonVanke/MicroPython-ufont-Tools 4 | # Videos: 5 | # https://www.bilibili.com/video/BV12B4y1B7Ff/ 6 | # https://www.bilibili.com/video/BV1YD4y16739/ 7 | __version__ = 3 8 | 9 | import time 10 | import struct 11 | 12 | import framebuf 13 | 14 | DEBUG = False 15 | 16 | 17 | def timeit(func, *args, **kwargs): 18 | """测试函数运行时间""" 19 | # 当交叉编译后无法获取函数名 20 | try: 21 | _name = func.__name__ 22 | except AttributeError: 23 | _name = "Unknown" 24 | 25 | def get_running_time(*args, **kwargs): 26 | if DEBUG: 27 | t = time.ticks_us() 28 | result = func(*args, **kwargs) 29 | delta = time.ticks_diff(time.ticks_us(), t) 30 | print('Function {} Time = {:6.3f}ms'.format(_name, delta / 1000)) 31 | return result 32 | else: 33 | return func(*args, **kwargs) 34 | 35 | return get_running_time 36 | 37 | 38 | class BMFont: 39 | @timeit 40 | def text(self, display, string: str, x: int, y: int, 41 | color: int = 0xFFFF, bg_color: int = 0, font_size: int = None, 42 | half_char: bool = True, auto_wrap: bool = False, show: bool = True, clear: bool = False, 43 | alpha_color: bool = 0, reverse: bool = False, color_type: int = -1, line_spacing: int = 0, **kwargs): 44 | """ 45 | Args: 46 | display: 显示对象 47 | string: 显示文字 48 | x: 字符串左上角 x 轴 49 | y: 字符串左上角 y 轴 50 | color: 字体颜色(RGB565) 51 | bg_color: 字体背景颜色(RGB565) 52 | font_size: 字号大小 53 | half_char: 半宽显示 ASCII 字符 54 | auto_wrap: 自动换行 55 | show: 实时显示 56 | clear: 清除之前显示内容 57 | alpha_color: 透明色(RGB565) 当颜色与 alpha_color 相同时则透明 58 | reverse: 逆置(MONO) 59 | color_type: 色彩模式 0:MONO 1:RGB565 60 | line_spacing: 行间距 61 | **kwargs: 62 | 63 | Returns: 64 | MoreInfo: https://github.com/AntonVanke/MicroPython-uFont/blob/master/README.md 65 | """ 66 | # 如果没有指定字号则使用默认字号 67 | font_size = font_size or self.font_size 68 | # 记录初始的 x 位置 69 | initial_x = x 70 | 71 | # 设置颜色类型 72 | if color_type == -1 and (display.width * display.height) > len(display.buffer): 73 | color_type = 0 74 | elif color_type == -1 or color_type == 1: 75 | palette = [[bg_color & 0xFF, (bg_color & 0xFF00) >> 8], [color & 0xFF, (color & 0xFF00) >> 8]] 76 | color_type = 1 77 | 78 | # 处理黑白屏幕的背景反转问题 79 | if color_type == 0 and color == 0 != bg_color or color_type == 0 and reverse: 80 | reverse = True 81 | alpha_color = -1 82 | else: 83 | reverse = False 84 | 85 | # 清屏 86 | try: 87 | display.clear() if clear else 0 88 | except AttributeError: 89 | print("请自行调用 display.fill() 清屏") 90 | 91 | for char in range(len(string)): 92 | if auto_wrap and ((x + font_size // 2 > display.width and ord(string[char]) < 128 and half_char) or 93 | (x + font_size > display.width and (not half_char or ord(string[char]) > 128))): 94 | y += font_size + line_spacing 95 | x = initial_x 96 | 97 | # 对控制字符的处理 98 | if string[char] == '\n': 99 | y += font_size + line_spacing 100 | x = initial_x 101 | continue 102 | elif string[char] == '\t': 103 | x = ((x // font_size) + 1) * font_size + initial_x % font_size 104 | continue 105 | elif ord(string[char]) < 16: 106 | continue 107 | 108 | # 超过范围的字符不会显示* 109 | if x > display.width or y > display.height: 110 | continue 111 | 112 | # 获取字体的点阵数据 113 | byte_data = list(self.get_bitmap(string[char])) 114 | 115 | # 分四种情况逐个优化 116 | # 1. 黑白屏幕/无放缩 117 | # 2. 黑白屏幕/放缩 118 | # 3. 彩色屏幕/无放缩 119 | # 4. 彩色屏幕/放缩 120 | if color_type == 0: 121 | byte_data = self._reverse_byte_data(byte_data) if reverse else byte_data 122 | if font_size == self.font_size: 123 | display.blit(framebuf.FrameBuffer(bytearray(byte_data), font_size, font_size, framebuf.MONO_HLSB), 124 | x, y, 125 | alpha_color) 126 | else: 127 | display.blit( 128 | framebuf.FrameBuffer(self._HLSB_font_size(byte_data, font_size, self.font_size), font_size, 129 | font_size, framebuf.MONO_HLSB), x, y, alpha_color) 130 | elif color_type == 1 and font_size == self.font_size: 131 | display.blit(framebuf.FrameBuffer(self._flatten_byte_data(byte_data, palette), font_size, font_size, 132 | framebuf.RGB565), x, y, alpha_color) 133 | elif color_type == 1 and font_size != self.font_size: 134 | display.blit(framebuf.FrameBuffer(self._RGB565_font_size(byte_data, font_size, palette, self.font_size), 135 | font_size, font_size, framebuf.RGB565), x, y, alpha_color) 136 | # 英文字符半格显示 137 | if ord(string[char]) < 128 and half_char: 138 | x += font_size // 2 139 | else: 140 | x += font_size 141 | 142 | display.show() if show else 0 143 | 144 | @timeit 145 | def _get_index(self, word: str) -> int: 146 | """ 147 | 获取索引 148 | Args: 149 | word: 字符 150 | 151 | Returns: 152 | ESP32-C3: Function _get_index Time = 2.670ms 153 | """ 154 | word_code = ord(word) 155 | start = 0x10 156 | end = self.start_bitmap 157 | 158 | while start <= end: 159 | mid = ((start + end) // 4) * 2 160 | self.font.seek(mid, 0) 161 | target_code = struct.unpack(">H", self.font.read(2))[0] 162 | if word_code == target_code: 163 | return (mid - 16) >> 1 164 | elif word_code < target_code: 165 | end = mid - 2 166 | else: 167 | start = mid + 2 168 | return -1 169 | 170 | @timeit 171 | def _HLSB_font_size(self, byte_data: bytearray, new_size: int, old_size: int) -> bytearray: 172 | old_size = old_size 173 | _temp = bytearray(new_size * ((new_size >> 3) + 1)) 174 | _new_index = -1 175 | for _col in range(new_size): 176 | for _row in range(new_size): 177 | if (_row % 8) == 0: 178 | _new_index += 1 179 | _old_index = int(_col / (new_size / old_size)) * old_size + int(_row / (new_size / old_size)) 180 | _temp[_new_index] = _temp[_new_index] | ( 181 | (byte_data[_old_index >> 3] >> (7 - _old_index % 8) & 1) << (7 - _row % 8)) 182 | return _temp 183 | 184 | @timeit 185 | def _RGB565_font_size(self, byte_data: bytearray, new_size: int, palette: list, old_size: int) -> bytearray: 186 | old_size = old_size 187 | _temp = [] 188 | _new_index = -1 189 | for _col in range(new_size): 190 | for _row in range(new_size): 191 | if (_row % 8) == 0: 192 | _new_index += 1 193 | _old_index = int(_col / (new_size / old_size)) * old_size + int(_row / (new_size / old_size)) 194 | _temp.extend(palette[byte_data[_old_index // 8] >> (7 - _old_index % 8) & 1]) 195 | return bytearray(_temp) 196 | 197 | @timeit 198 | def _flatten_byte_data(self, _byte_data: bytearray, palette: list) -> bytearray: 199 | """ 200 | 渲染彩色像素 201 | Args: 202 | _byte_data: 203 | palette: 204 | 205 | Returns: 206 | 207 | """ 208 | _temp = [] 209 | for _byte in _byte_data: 210 | for _b in range(7, -1, -1): 211 | _temp.extend(palette[(_byte >> _b) & 0x01]) 212 | return bytearray(_temp) 213 | 214 | @timeit 215 | def _reverse_byte_data(self, _byte_data: bytearray) -> bytearray: 216 | for _pixel in range(len(_byte_data)): 217 | _byte_data[_pixel] = ~_byte_data[_pixel] & 0xff 218 | return _byte_data 219 | 220 | @timeit 221 | def get_bitmap(self, word: str) -> bytes: 222 | """获取点阵图 223 | 224 | Args: 225 | word: 字符 226 | 227 | Returns: 228 | bytes 字符点阵 229 | """ 230 | index = self._get_index(word) 231 | if index == -1: 232 | 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' \ 233 | b'?\xff?\xff\xff\xff\xff' 234 | 235 | self.font.seek(self.start_bitmap + index * self.bitmap_size, 0) 236 | return self.font.read(self.bitmap_size) 237 | 238 | @timeit 239 | def __init__(self, font_file): 240 | """ 241 | Args: 242 | font_file: 字体文件路径 243 | """ 244 | self.font_file = font_file 245 | # 载入字体文件 246 | self.font = open(font_file, "rb") 247 | # 获取字体文件信息 248 | # 字体文件信息大小 16 byte ,按照顺序依次是 249 | # 文件标识 2 byte 250 | # 版本号 1 byte 251 | # 映射方式 1 byte 252 | # 位图开始字节 3 byte 253 | # 字号 1 byte 254 | # 单字点阵字节大小 1 byte 255 | # 保留 7 byte 256 | self.bmf_info = self.font.read(16) 257 | 258 | # 判断字体是否正确 259 | # 文件头和常用的图像格式 BMP 相同,需要添加版本验证来辅助验证 260 | if self.bmf_info[0:2] != b"BM": 261 | raise TypeError("字体文件格式不正确: " + font_file) 262 | self.version = self.bmf_info[2] 263 | if self.version != 3: 264 | raise TypeError("字体文件版本不正确: " + str(self.version)) 265 | 266 | # 映射方式 267 | # 目前映射方式并没有加以验证,原因是 MONO_HLSB 最易于处理 268 | self.map_mode = self.bmf_info[3] 269 | 270 | # 位图开始字节 271 | # 位图数据位于文件尾,需要通过位图开始字节来确定字体数据实际位置 272 | self.start_bitmap = struct.unpack(">I", b'\x00' + self.bmf_info[4:7])[0] 273 | # 字体大小 274 | # 默认的文字字号,用于缩放方面的处理 275 | self.font_size = self.bmf_info[7] 276 | # 点阵所占字节 277 | # 用来定位字体数据位置 278 | self.bitmap_size = self.bmf_info[8] 279 | -------------------------------------------------------------------------------- /unifont-14-12917-16.v3.bmf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AntonVanke/MicroPython-uFont/ccf2cc13e49de3d5603d582c4cf3844be8bd2dfe/unifont-14-12917-16.v3.bmf --------------------------------------------------------------------------------