├── .gitignore ├── .idea ├── .gitignore ├── inspectionProfiles │ └── profiles_settings.xml ├── micropython-easydisplay.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE ├── README.ZH-CN.md ├── README.md ├── driver ├── README.md ├── epaper_buf.mpy ├── epaper_buf.py ├── sh1106_buf.mpy ├── sh1106_buf.py ├── ssd1306_buf.mpy ├── ssd1306_buf.py ├── ssd1315_buf.mpy ├── ssd1315_buf.py ├── st7735_buf.mpy ├── st7735_buf.py ├── st7735_spi.mpy ├── st7735_spi.py ├── st7789_buf.mpy ├── st7789_buf.py ├── st7789_spi.mpy └── st7789_spi.py ├── font ├── MicroPython-uFont-Tools │ ├── README.md │ ├── bitmapfonts.py │ ├── doc │ │ ├── example.png │ │ └── 如何生成点阵字体文件.md │ ├── main.py │ ├── requirements.txt │ └── soft │ │ ├── bitmap_font_tools_20220827_mac │ │ └── bitmap_font_tools_20220827_windows.exe ├── README.md ├── font_file │ ├── GuanZhi-8px.ttf │ └── unifont-15.1.04.otf ├── font_set │ ├── text_full.txt │ └── text_lite.txt ├── text_full_16px_2312.v3.bmf ├── text_full_24px_2312.v3.bmf ├── text_full_32px_2312.v3.bmf ├── text_full_8px_2312.v3.bmf ├── text_lite_16px_2312.v3.bmf ├── text_lite_24px_2312.v3.bmf ├── text_lite_32px_2312.v3.bmf └── text_lite_8px_2312.v3.bmf ├── img ├── test.bmp └── test.pbm ├── lib ├── easydisplay.mpy └── easydisplay.py ├── main.py └── tool ├── README.md ├── image_tools.py └── video_tools.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | # lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/micropython-easydisplay.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 稽术宅 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. 22 | -------------------------------------------------------------------------------- /README.ZH-CN.md: -------------------------------------------------------------------------------- 1 | [English (英语)](./README.md) 2 | 3 | # micropython-easydisplay 4 | - 适用于 `Micropython` 的:高通用性,多功能,纯 `Micropython` 实现的显示库 5 | - 自用,顺便开源,希望能够推动 `Micropython` 生态的发展 6 | 7 | 8 | ### 显示效果 9 | 以下为 `2.0` 版本的显示效果 10 | ![IMG_20231107_235742](https://github.com/funnygeeker/micropython-easydisplay/assets/96659329/f76a7713-7397-4a99-8ccd-37af7ebe0cbe) 11 | ![IMG_20231107_004226](https://github.com/funnygeeker/micropython-easydisplay/assets/96659329/e765b55a-45bb-486a-b15e-5161b4d876fa) 12 | ![IMG_20231107_004229](https://github.com/funnygeeker/micropython-easydisplay/assets/96659329/f82910c4-b515-4ffd-a00c-9eafffcbb0bf) 13 | 14 | ### 项目特点 15 | - 可以通过导入 `bmf` 字体文件,显示非 `ASCII` 字符,比如:中文 和 特殊符号 16 | - 支持 `P4`/`P6` 格式的 `PBM` 图片显示,以及 `24-bit` 的 `BMP` 图片显示 17 | - 初始化时可以设置默认参数,调用函数时更简洁,同时调用指定函数时,本次调用可覆盖默认参数 18 | - 兼容大多数 `MicroPython` 官方和非官方版本,纯 `MicroPython` 原生实现,不需要进行固件编译,同时尽可能保持了高效率 19 | - 支持多种屏幕的多种工作模式 `SSD1306`,`ST7735`,`ST7789`,支持低内存开发板驱动高分辨率屏幕(如 `ESP32C3` 驱动 `240*240` `ST7789`) 20 | 21 | ### 使用方法 22 | - 详见源码注释 23 | 24 | ### 注意事项 25 | `dat` 格式的图片在非 Framebuffer 驱动模式下,不得超出屏幕显示范围,否则图像可能无法正常显示 26 | 27 | ### 示例代码 28 | ```python 29 | # 这是一个使用示例 This is an example of usage 30 | import time 31 | from machine import SPI, Pin 32 | from driver import st7735_buf 33 | from lib.easydisplay import EasyDisplay 34 | 35 | # ESP32S3 & ST7735 36 | spi = SPI(1, baudrate=20000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(17)) 37 | dp = st7735_buf.ST7735(width=128, height=128, spi=spi, cs=14, dc=15, res=16, rotate=1, bl=13, invert=False, rgb=False) 38 | ed = EasyDisplay(dp, "RGB565", font="/text_lite_16px_2312.v3.bmf", show=True, color=0xFFFF, clear=True) 39 | 40 | ed.bmp("/img/test.bmp", 0, 0) 41 | time.sleep(3) 42 | ed.pbm("/img/test.pbm", 0, 0) 43 | time.sleep(3) 44 | ed.text("你好,世界!\nHello World!\nこんにちは、世界!", 0, 0) 45 | 46 | # 更多高级使用方式详见源码注释:/lib/easydisplay.py 47 | # For more advanced usage, please refer to the source code comments: /lib/easydisplay.py 48 | ``` 49 | 50 | ### 特别致谢 51 | 参考项目: 52 | 53 | 中文显示:[https://github.com/AntonVanke/MicroPython-Chinese-Font](https://github.com/AntonVanke/MicroPython-Chinese-Font) 54 | 55 | BMP图片显示:[https://github.com/boochow/MicroPython-ST7735/blob/master/tftbmp.py](https://github.com/boochow/MicroPython-ST7735/blob/master/tftbmp.py) 56 | 57 | 58 | ### 参考资料 59 | PBM图像显示:[https://www.bilibili.com/video/av798158808/](https://www.bilibili.com/video/av798158808/) 60 | 61 | PBM文件格式:[https://www.cnblogs.com/SeekHit/p/7055748.html](https://www.cnblogs.com/SeekHit/p/7055748.html) 62 | 63 | PBM文件转换:[https://blog.csdn.net/jd3096/article/details/121319042](https://blog.csdn.net/jd3096/article/details/121319042) 64 | 65 | 灰度化、二值化:[https://blog.csdn.net/li_wen01/article/details/72867057](https://blog.csdn.net/li_wen01/article/details/72867057) 66 | 67 | 68 | ### 其他 69 | 感谢各位大佬对开源做出的贡献! 70 | 71 | 交流QQ群:[748103265](https://jq.qq.com/?_wv=1027&k=I74bKifU) 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [简体中文 (Chinese)](./README.ZH-CN.md) 2 | 3 | # micropython-easydisplay 4 | - A display library for `Micropython`: high versatility, multifunctionality, implemented purely in `Micropython`. 5 | - This `README` may contain translations that are not entirely accurate. 6 | 7 | ### Display Effects 8 | The following are the display effects of version `2.0`. 9 | ![IMG_20231107_235742](https://github.com/funnygeeker/micropython-easydisplay/assets/96659329/f76a7713-7397-4a99-8ccd-37af7ebe0cbe) 10 | ![IMG_20231107_004226](https://github.com/funnygeeker/micropython-easydisplay/assets/96659329/e765b55a-45bb-486a-b15e-5161b4d876fa) 11 | ![IMG_20231107_004229](https://github.com/funnygeeker/micropython-easydisplay/assets/96659329/f82910c4-b515-4ffd-a00c-9eafffcbb0bf) 12 | 13 | ### Project Features 14 | - Ability to display non-ASCII characters, such as Chinese and special symbols, by importing `bmf` font files. 15 | - Supports displaying `PBM` images in `P4`/`P6` format and `BMP` images in `24-bit`. 16 | - Default parameters can be set during initialization, making function calls more concise. Additionally, the current function call can override the default parameters. 17 | - Compatible with most official and unofficial versions of `MicroPython`. It is implemented purely with native `MicroPython` and does not require firmware compilation. Additionally, it maintains high efficiency as much as possible. 18 | - Supports multiple screen models such as `SSD1306`, `ST7735`, and `ST7789`. It also supports driving high-resolution screens on low-memory development boards (e.g., `ESP32C3` driving `240*240 ST7789` screens). 19 | 20 | ### Usage 21 | - Please refer to the source code comments.(The comments section is written in Chinese and may need translation in order to read.) 22 | 23 | ### Note 24 | For images in the `dat` format, make sure that they do not exceed the screen display area when using non-framebuffer driver modes. Otherwise, the image may not be displayed correctly. 25 | 26 | ### Example Code 27 | ```python 28 | # 这是一个使用示例 This is an example of usage 29 | import time 30 | from machine import SPI, Pin 31 | from driver import st7735_buf 32 | from lib.easydisplay import EasyDisplay 33 | 34 | # ESP32S3 & ST7735 35 | spi = SPI(1, baudrate=20000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(17)) 36 | dp = st7735_buf.ST7735(width=128, height=128, spi=spi, cs=14, dc=15, res=16, rotate=1, bl=13, invert=False, rgb=False) 37 | ed = EasyDisplay(dp, "RGB565", font="/text_lite_16px_2312.v3.bmf", show=True, color=0xFFFF, clear=True) 38 | 39 | ed.bmp("/img/test.bmp", 0, 0) 40 | time.sleep(3) 41 | ed.pbm("/img/test.pbm", 0, 0) 42 | time.sleep(3) 43 | ed.text("你好,世界!\nHello World!\nこんにちは、世界!", 0, 0) 44 | 45 | # 更多高级使用方式详见源码注释:/lib/easydisplay.py 46 | # For more advanced usage, please refer to the source code comments: /lib/easydisplay.py 47 | ``` 48 | 49 | ### Special Thanks 50 | Reference projects: 51 | 52 | Chinese display: [https://github.com/AntonVanke/MicroPython-Chinese-Font](https://github.com/AntonVanke/MicroPython-Chinese-Font) 53 | 54 | BMP image display: [https://github.com/boochow/MicroPython-ST7735/blob/master/tftbmp.py](https://github.com/boochow/MicroPython-ST7735/blob/master/tftbmp.py) 55 | 56 | ### References 57 | PBM image display: [https://www.bilibili.com/video/av798158808/](https://www.bilibili.com/video/av798158808/) 58 | 59 | PBM file format: [https://www.cnblogs.com/SeekHit/p/7055748.html](https://www.cnblogs.com/SeekHit/p/7055748.html) 60 | 61 | PBM file conversion: [https://blog.csdn.net/jd3096/article/details/121319042](https://blog.csdn.net/jd3096/article/details/121319042) 62 | 63 | Grayscale, binarization: [https://blog.csdn.net/li_wen01/article/details/72867057](https://blog.csdn.net/li_wen01/article/details/72867057) 64 | 65 | ### Others 66 | Thanks to all contributors for their contributions to open source! 67 | -------------------------------------------------------------------------------- /driver/README.md: -------------------------------------------------------------------------------- 1 | ## 屏幕驱动(Chinese) 2 | 3 | ### 说明 4 | - 文件名含有 `buf` 的驱动是使用 `Framebuffer` 的帧缓冲区驱动,具有较高的效率和丰富的功能, 5 | 在开发板内存充足的情况下请尽量选择该驱动 6 | 7 | 8 | - 文件名含有 `spi` 的驱动是使用 `SPI` 对屏幕进行直接驱动,配合 `micropython-easydisplay` 使用时效率略低, 9 | 但是对内存不足以使用 `Framebuffer` 的开发板非常友好 10 | 11 | 12 | - 部分驱动可能存在一些错误,如果您遇到了错误并修复了 `BUG`,别忘记提交 `Pull Request` 来向项目提交您的更改建议。 13 | 14 | 15 | ## Screen Drivers 16 | 17 | ### Description 18 | - Drivers with filenames containing `buf` are `Framebuffer` drivers, which have higher efficiency and richer features. Please choose these drivers when there is sufficient memory available on the development board. 19 | 20 | 21 | - Drivers with filenames containing `spi` are SPI drivers used for direct driving of the screen. When used with `micropython-easydisplay`, they have slightly lower efficiency but are very friendly for development boards with insufficient memory to use `Framebuffer`. 22 | 23 | 24 | - Some drivers may have some errors. If you encounter an error and fix a bug, don't forget to submit a pull request to contribute your suggested changes to the project. -------------------------------------------------------------------------------- /driver/epaper_buf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/driver/epaper_buf.mpy -------------------------------------------------------------------------------- /driver/epaper_buf.py: -------------------------------------------------------------------------------- 1 | # 适用于 E-Paper 的 Framebuffer 驱动 2 | # Github: https://github.com/funnygeeker/micropython-easydisplay 3 | # Author: funnygeeker 4 | # Licence: MIT 5 | # Date: 2024/2/17 6 | # 7 | # 参考资料: 8 | # https://github.com/mcauser/micropython-waveshare-epaper 9 | # https://github.com/AntonVanke/MicroPython-uFont/blob/master/driver/e1in54.py 10 | import math 11 | from struct import pack 12 | from time import sleep_ms 13 | from machine import Pin, PWM 14 | from micropython import const 15 | from framebuf import FrameBuffer, MONO_HLSB 16 | 17 | # Display resolution 18 | EPD_WIDTH = const(200) 19 | EPD_HEIGHT = const(200) 20 | 21 | # Display commands 22 | DRIVER_OUTPUT_CONTROL = const(0x01) 23 | BOOSTER_SOFT_START_CONTROL = const(0x0C) 24 | #GATE_SCAN_START_POSITION = const(0x0F) 25 | DEEP_SLEEP_MODE = const(0x10) 26 | DATA_ENTRY_MODE_SETTING = const(0x11) 27 | #SW_RESET = const(0x12) 28 | #TEMPERATURE_SENSOR_CONTROL = const(0x1A) 29 | MASTER_ACTIVATION = const(0x20) 30 | #DISPLAY_UPDATE_CONTROL_1 = const(0x21) 31 | DISPLAY_UPDATE_CONTROL_2 = const(0x22) 32 | WRITE_RAM = const(0x24) 33 | WRITE_VCOM_REGISTER = const(0x2C) 34 | WRITE_LUT_REGISTER = const(0x32) 35 | SET_DUMMY_LINE_PERIOD = const(0x3A) 36 | SET_GATE_TIME = const(0x3B) # not in datasheet 37 | #BORDER_WAVEFORM_CONTROL = const(0x3C) 38 | SET_RAM_X_ADDRESS_START_END_POSITION = const(0x44) 39 | SET_RAM_Y_ADDRESS_START_END_POSITION = const(0x45) 40 | SET_RAM_X_ADDRESS_COUNTER = const(0x4E) 41 | SET_RAM_Y_ADDRESS_COUNTER = const(0x4F) 42 | TERMINATE_FRAME_READ_WRITE = const(0xFF) # aka NOOP 43 | 44 | BUSY = const(1) # 1=busy, 0=idle 45 | 46 | class EPD(FrameBuffer): 47 | LUT_FULL_UPDATE = bytearray(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') 48 | LUT_PARTIAL_UPDATE = bytearray(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') 49 | 50 | def __init__(self, width: int, height: int, spi, res: int, dc: int, busy:int, 51 | cs: int = None, bl: int = None): 52 | """ 53 | 初始化屏幕驱动 54 | 55 | Args: 56 | width: 宽度 57 | height: 高度 58 | spi: SPI 实例 59 | res: RESET 引脚 60 | dc: Data / Command 引脚 61 | busy: 62 | cs: 片选引脚 63 | bl: 背光引脚 64 | """ 65 | self.width = width 66 | self.height = height 67 | self.spi = spi 68 | self.res = Pin(res, Pin.OUT, Pin.PULL_DOWN, value=0) 69 | self.dc = Pin(dc, Pin.OUT, Pin.PULL_DOWN, value=0) 70 | self.busy = Pin(busy, Pin.IN) 71 | if cs is None: 72 | self.cs = int 73 | else: 74 | self.cs = Pin(cs, Pin.OUT, Pin.PULL_DOWN, value=1) 75 | if bl is not None: 76 | self.bl = PWM(Pin(bl, Pin.OUT)) 77 | self.back_light(255) 78 | else: 79 | self.bl = None 80 | self.pages = self.height // 8 81 | self.buffer = bytearray(self.width * self.pages) 82 | super().__init__(self.buffer, self.width, self.height, MONO_HLSB) 83 | self.init() 84 | 85 | def init(self): 86 | self.hard_reset() 87 | self._write(DRIVER_OUTPUT_CONTROL) 88 | self.write_data(bytearray([(EPD_HEIGHT - 1) & 0xFF])) 89 | self.write_data(bytearray([((EPD_HEIGHT - 1) >> 8) & 0xFF])) 90 | self.write_data(bytearray([0x00])) # GD = 0 SM = 0 TB = 0 91 | self._write(BOOSTER_SOFT_START_CONTROL, b'\xD7\xD6\x9D') 92 | self._write(WRITE_VCOM_REGISTER, b'\xA8') # VCOM 7C 93 | self._write(SET_DUMMY_LINE_PERIOD, b'\x1A') # 4 dummy lines per gate 94 | self._write(SET_GATE_TIME, b'\x08') # 2us per line 95 | self._write(DATA_ENTRY_MODE_SETTING, b'\x03') # X increment Y increment 96 | self.set_lut(self.LUT_FULL_UPDATE) 97 | 98 | def _write(self, command=None, data=None): 99 | """SPI write to the device: commands and data.""" 100 | self.cs(0) 101 | if command is not None: 102 | self.dc(0) 103 | self.spi.write(bytes([command])) 104 | if data is not None: 105 | self.dc(1) 106 | self.spi.write(data) 107 | self.cs(1) 108 | 109 | def write_cmd(self, cmd): 110 | """ 111 | 写命令 112 | 113 | Args: 114 | cmd: 命令内容 115 | """ 116 | self.cs(0) 117 | self.dc(0) 118 | self.spi.write(bytes([cmd])) 119 | self.cs(1) 120 | 121 | def write_data(self, data): 122 | """ 123 | 写数据 124 | 125 | Args: 126 | data: 数据内容 127 | """ 128 | self.cs(0) 129 | self.dc(1) 130 | self.spi.write(data) 131 | self.cs(1) 132 | 133 | def wait_until_idle(self): 134 | while self.busy.value() == BUSY: 135 | sleep_ms(50) 136 | 137 | def hard_reset(self): 138 | """ 139 | Hard reset display. 140 | """ 141 | self.res(0) 142 | sleep_ms(100) 143 | self.res(1) 144 | sleep_ms(100) 145 | 146 | def soft_reset(self): 147 | # Function not realized 148 | pass 149 | 150 | def set_lut(self, lut): 151 | self._write(WRITE_LUT_REGISTER, lut) 152 | 153 | def set_refresh(self, full_update=True): 154 | """ 155 | Set the refresh mode 156 | 157 | Args: 158 | full_update: Full screen refresh 159 | """ 160 | self.set_lut(self.LUT_FULL_UPDATE) if full_update else self.set_lut(self.LUT_PARTIAL_UPDATE) 161 | 162 | # put an image in the frame memory 163 | def set_frame_memory(self, image, x, y, w, h): 164 | # x point must be the multiple of 8 or the last 3 bits will be ignored 165 | x = x & 0xF8 166 | w = w & 0xF8 167 | 168 | if x + w >= self.width: 169 | x_end = self.width - 1 170 | else: 171 | x_end = x + w - 1 172 | 173 | if y + h >= self.height: 174 | y_end = self.height - 1 175 | else: 176 | y_end = y + h - 1 177 | 178 | self.set_window(x, y, x_end, y_end) 179 | self.set_memory_pointer(x, y) 180 | self._write(WRITE_RAM, image) 181 | 182 | # replace the frame memory with the specified color 183 | def clear_frame_memory(self, color): 184 | self.set_window(0, 0, self.width - 1, self.height - 1) 185 | self.set_memory_pointer(0, 0) 186 | self._write(WRITE_RAM) 187 | # send the color data 188 | for i in range(0, self.width // 8 * self.height): 189 | self.write_data(bytearray([color])) 190 | 191 | # draw the current frame memory and switch to the next memory area 192 | def display_frame(self): 193 | self._write(DISPLAY_UPDATE_CONTROL_2, b'\xC4') 194 | self._write(MASTER_ACTIVATION) 195 | self._write(TERMINATE_FRAME_READ_WRITE) 196 | self.wait_until_idle() 197 | 198 | # specify the memory area for data R/W 199 | def set_memory_area(self, x_start, y_start, x_end, y_end): 200 | self._write(SET_RAM_X_ADDRESS_START_END_POSITION) 201 | # x point must be the multiple of 8 or the last 3 bits will be ignored 202 | self.write_data(bytearray([(x_start >> 3) & 0xFF])) 203 | self.write_data(bytearray([(x_end >> 3) & 0xFF])) 204 | self._write(SET_RAM_Y_ADDRESS_START_END_POSITION, pack("> 3) & 0xFF])) 211 | self._write(SET_RAM_Y_ADDRESS_COUNTER, pack("= 0xff: 242 | value = 0xff 243 | data = value * 0xffff >> 8 244 | self.bl.duty_u16(data) 245 | 246 | def circle(self, x, y, radius, c, section=100): 247 | """ 248 | 画圆 249 | 250 | Args: 251 | c: 颜色 252 | x: 中心 x 坐标 253 | y: 中心 y 坐标 254 | radius: 半径 255 | section: 分段 256 | """ 257 | arr = [] 258 | for m in range(section + 1): 259 | _x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + x) 260 | _y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + y) 261 | arr.append([_x, _y]) 262 | for i in range(len(arr) - 1): 263 | self.line(*arr[i], *arr[i + 1], c) 264 | 265 | def fill_circle(self, x, y, radius, c): 266 | """ 267 | 画填充圆 268 | 269 | Args: 270 | c: 颜色 271 | x: 中心 x 坐标 272 | y: 中心 y 坐标 273 | radius: 半径 274 | """ 275 | rsq = radius * radius 276 | for _x in range(radius): 277 | _y = int(math.sqrt(rsq - _x * _x)) # 计算 y 坐标 278 | y0 = y - _y 279 | end_y = y0 + _y * 2 280 | y0 = max(0, min(y0, self.height)) # 将 y0 限制在画布的范围内 281 | length = abs(end_y - y0) + 1 282 | self.vline(x + _x, y0, length, c) # 绘制左右两侧的垂直线 283 | self.vline(x - _x, y0, length, c) -------------------------------------------------------------------------------- /driver/sh1106_buf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/driver/sh1106_buf.mpy -------------------------------------------------------------------------------- /driver/sh1106_buf.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | import framebuf 4 | from machine import Pin 5 | from micropython import const 6 | 7 | # a few register definitions 8 | _SET_CONTRAST = const(0x81) 9 | _SET_NORM_INV = const(0xa6) 10 | _SET_DISP = const(0xae) 11 | _SET_SCAN_DIR = const(0xc0) 12 | _SET_SEG_REMAP = const(0xa0) 13 | _LOW_COLUMN_ADDRESS = const(0x00) 14 | _HIGH_COLUMN_ADDRESS = const(0x10) 15 | _SET_PAGE_ADDRESS = const(0xB0) 16 | 17 | 18 | class SH1106(framebuf.FrameBuffer): 19 | def __init__(self, width, height, external_vcc, rotate=0): 20 | self.width = width 21 | self.height = height 22 | self.external_vcc = external_vcc 23 | self.flip_en = rotate == 180 or rotate == 270 24 | self.rotate90 = rotate == 90 or rotate == 270 25 | self.pages = self.height // 8 26 | self.bufsize = self.pages * self.width 27 | self.buffer = bytearray(self.bufsize) 28 | self.pages_to_update = 0 29 | 30 | if self.rotate90: 31 | self.displaybuf = bytearray(self.bufsize) 32 | # HMSB is required to keep the bit order in the render buffer 33 | # compatible with byte-for-byte remapping to the display buffer, 34 | # which is in VLSB. Else we'd have to copy bit-by-bit! 35 | super().__init__(self.buffer, self.height, self.width, 36 | framebuf.MONO_HMSB) 37 | else: 38 | self.displaybuf = self.buffer 39 | super().__init__(self.buffer, self.width, self.height, 40 | framebuf.MONO_VLSB) 41 | 42 | # flip() was called rotate() once, provide backwards compatibility. 43 | self.rotate = self.flip 44 | self.init_display() 45 | self.back_light(255) 46 | 47 | def init_display(self): 48 | self.reset() 49 | self.fill(0) 50 | self.show() 51 | self.poweron() 52 | # rotate90 requires a call to flip() for setting up. 53 | self.flip(self.flip_en) 54 | 55 | def poweroff(self): 56 | self.write_cmd(_SET_DISP | 0x00) 57 | 58 | def poweron(self): 59 | self.write_cmd(_SET_DISP | 0x01) 60 | if self.delay: 61 | time.sleep_ms(self.delay) 62 | 63 | def flip(self, flag=None, update=True): 64 | if flag is None: 65 | flag = not self.flip_en 66 | mir_v = flag ^ self.rotate90 67 | mir_h = flag 68 | self.write_cmd(_SET_SEG_REMAP | (0x01 if mir_v else 0x00)) 69 | self.write_cmd(_SET_SCAN_DIR | (0x08 if mir_h else 0x00)) 70 | self.flip_en = flag 71 | if update: 72 | self.show(True) # full update 73 | 74 | def sleep(self, value): 75 | self.write_cmd(_SET_DISP | (not value)) 76 | 77 | def contrast(self, contrast): 78 | self.write_cmd(_SET_CONTRAST) 79 | self.write_cmd(contrast) 80 | 81 | def back_light(self, value): 82 | """ 83 | 背光调节 84 | 85 | Args: 86 | value: 背光等级 0 ~ 255 87 | """ 88 | self.contrast(value) 89 | 90 | def rotate(self, rotate): 91 | """ 92 | 设置显示旋转 93 | 94 | Args: 95 | rotate(int): 96 | - 0-Portrait 97 | - 1-Upper right printing left (backwards) (X Flip) 98 | - 2-Inverted Portrait 99 | - 3-Lower left printing up (backwards) (Y Flip) 100 | """ 101 | rotate %= 4 102 | mir_v = False 103 | mir_h = False 104 | if rotate == 0: 105 | mir_v = True 106 | mir_h = True 107 | elif rotate == 1: 108 | mir_h = True 109 | elif rotate == 2: 110 | pass 111 | elif rotate == 3: 112 | mir_v = True 113 | self.write_cmd(_SET_SEG_REMAP | (0x01 if mir_v else 0x00)) 114 | self.write_cmd(_SET_SCAN_DIR | (0x08 if mir_h else 0x00)) 115 | self.show() 116 | 117 | def invert(self, invert): 118 | """ 119 | Invert mode, If true, switch to invert mode (black-on-white), else normal mode (white-on-black) 120 | """ 121 | self.write_cmd(_SET_NORM_INV | (invert & 1)) 122 | 123 | def show(self, full_update=False): 124 | # self.* lookups in loops take significant time (~4fps). 125 | (w, p, db, rb) = (self.width, self.pages, 126 | self.displaybuf, self.buffer) 127 | if self.rotate90: 128 | for i in range(self.bufsize): 129 | db[w * (i % p) + (i // p)] = rb[i] 130 | if full_update: 131 | pages_to_update = (1 << self.pages) - 1 132 | else: 133 | pages_to_update = self.pages_to_update 134 | # print("Updating pages: {:08b}".format(pages_to_update)) 135 | for page in range(self.pages): 136 | if (pages_to_update & (1 << page)): 137 | self.write_cmd(_SET_PAGE_ADDRESS | page) 138 | self.write_cmd(_LOW_COLUMN_ADDRESS | 2) 139 | self.write_cmd(_HIGH_COLUMN_ADDRESS | 0) 140 | self.write_data(db[(w * page):(w * page + w)]) 141 | self.pages_to_update = 0 142 | 143 | def pixel(self, x, y, color=None): 144 | if color is None: 145 | return super().pixel(x, y) 146 | else: 147 | super().pixel(x, y, color) 148 | page = y // 8 149 | self.pages_to_update |= 1 << page 150 | 151 | def text(self, text, x, y, color=1): 152 | super().text(text, x, y, color) 153 | self.register_updates(y, y + 7) 154 | 155 | def line(self, x0, y0, x1, y1, color): 156 | super().line(x0, y0, x1, y1, color) 157 | self.register_updates(y0, y1) 158 | 159 | def hline(self, x, y, w, color): 160 | super().hline(x, y, w, color) 161 | self.register_updates(y) 162 | 163 | def vline(self, x, y, h, color): 164 | super().vline(x, y, h, color) 165 | self.register_updates(y, y + h - 1) 166 | 167 | def fill(self, color): 168 | super().fill(color) 169 | self.pages_to_update = (1 << self.pages) - 1 170 | 171 | def blit(self, fbuf, x, y, key=-1, palette=None): 172 | super().blit(fbuf, x, y, key, palette) 173 | self.register_updates(y, y + self.height) 174 | 175 | def scroll(self, x, y): 176 | # my understanding is that scroll() does a full screen change 177 | super().scroll(x, y) 178 | self.pages_to_update = (1 << self.pages) - 1 179 | 180 | def fill_rect(self, x, y, w, h, color): 181 | super().fill_rect(x, y, w, h, color) 182 | self.register_updates(y, y + h - 1) 183 | 184 | def rect(self, x, y, w, h, color): 185 | super().rect(x, y, w, h, color) 186 | self.register_updates(y, y + h - 1) 187 | 188 | def circle(self, x, y, radius, c, section=100): 189 | """ 190 | 画圆 191 | 192 | Args: 193 | c: 颜色 194 | x: 中心 x 坐标 195 | y: 中心 y 坐标 196 | radius: 半径 197 | section: 分段 198 | """ 199 | arr = [] 200 | for m in range(section + 1): 201 | _x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + x) 202 | _y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + y) 203 | arr.append([_x, _y]) 204 | for i in range(len(arr) - 1): 205 | self.line(*arr[i], *arr[i + 1], c) 206 | 207 | def fill_circle(self, x, y, radius, c): 208 | """ 209 | 画填充圆 210 | 211 | Args: 212 | c: 颜色 213 | x: 中心 x 坐标 214 | y: 中心 y 坐标 215 | radius: 半径 216 | """ 217 | rsq = radius * radius 218 | for _x in range(radius): 219 | _y = int(math.sqrt(rsq - _x * _x)) # 计算 y 坐标 220 | y0 = y - _y 221 | end_y = y0 + _y * 2 222 | y0 = max(0, min(y0, self.height)) # 将 y0 限制在画布的范围内 223 | length = abs(end_y - y0) + 1 224 | self.vline(x + _x, y0, length, c) # 绘制左右两侧的垂直线 225 | self.vline(x - _x, y0, length, c) 226 | 227 | def register_updates(self, y0, y1=None): 228 | # this function takes the top and optional bottom address of the changes made 229 | # and updates the pages_to_change list with any changed pages 230 | # that are not yet on the list 231 | start_page = max(0, y0 // 8) 232 | end_page = max(0, y1 // 8) if y1 is not None else start_page 233 | # rearrange start_page and end_page if coordinates were given from bottom to top 234 | if start_page > end_page: 235 | start_page, end_page = end_page, start_page 236 | for page in range(start_page, end_page + 1): 237 | self.pages_to_update |= 1 << page 238 | 239 | def reset(self, res): 240 | if res is not None: 241 | res(1) 242 | time.sleep_ms(1) 243 | res(0) 244 | time.sleep_ms(20) 245 | res(1) 246 | time.sleep_ms(20) 247 | 248 | 249 | class SH1106_I2C(SH1106): 250 | def __init__(self, width, height, i2c, res=None, addr=0x3c, 251 | rotate=0, external_vcc=False, delay=0): 252 | self.i2c = i2c 253 | self.addr = addr 254 | if res is None: 255 | self.res = None 256 | else: 257 | self.res = Pin(res, Pin.OUT, Pin.PULL_DOWN) 258 | 259 | self.temp = bytearray(2) 260 | self.delay = delay 261 | super().__init__(width, height, external_vcc, rotate) 262 | 263 | def write_cmd(self, cmd): 264 | self.temp[0] = 0x80 # Co=1, D/C#=0 265 | self.temp[1] = cmd 266 | self.i2c.writeto(self.addr, self.temp) 267 | 268 | def write_data(self, buf): 269 | self.i2c.writeto(self.addr, b'\x40' + buf) 270 | 271 | def reset(self): 272 | super().reset(self.res) 273 | 274 | 275 | class SH1106_SPI(SH1106): 276 | def __init__(self, width, height, spi, dc, res=None, cs=None, 277 | rotate=0, external_vcc=False, delay=0): 278 | self.spi = spi 279 | if res is None: 280 | self.res = res 281 | else: 282 | self.res = Pin(res, Pin.OUT, Pin.PULL_DOWN) 283 | self.dc = Pin(dc, Pin.OUT, Pin.PULL_DOWN) 284 | if cs is None: 285 | self.cs = int 286 | else: 287 | self.cs = Pin(cs, Pin.OUT, Pin.PULL_DOWN) 288 | self.delay = delay 289 | super().__init__(width, height, external_vcc, rotate) 290 | 291 | def write_cmd(self, cmd): 292 | if self.cs is not None: 293 | self.cs(1) 294 | self.dc(0) 295 | self.cs(0) 296 | self.spi.write(bytearray([cmd])) 297 | self.cs(1) 298 | else: 299 | self.dc(0) 300 | self.spi.write(bytearray([cmd])) 301 | 302 | def write_data(self, buf): 303 | if self.cs is not None: 304 | self.cs(1) 305 | self.dc(1) 306 | self.cs(0) 307 | self.spi.write(buf) 308 | self.cs(1) 309 | else: 310 | self.dc(1) 311 | self.spi.write(buf) 312 | 313 | def reset(self): 314 | super().reset(self.res) 315 | -------------------------------------------------------------------------------- /driver/ssd1306_buf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/driver/ssd1306_buf.mpy -------------------------------------------------------------------------------- /driver/ssd1306_buf.py: -------------------------------------------------------------------------------- 1 | # 来源:https://pypi.org/project/micropython-ssd1306/ 2 | # MicroPython SSD1306 OLED driver, I2C and SPI interfaces 3 | import math 4 | import framebuf 5 | from machine import Pin 6 | from micropython import const 7 | 8 | # register definitions 9 | SET_CONTRAST = const(0x81) 10 | SET_ENTIRE_ON = const(0xA4) 11 | SET_NORM_INV = const(0xA6) 12 | SET_DISP = const(0xAE) 13 | SET_MEM_ADDR = const(0x20) 14 | SET_COL_ADDR = const(0x21) 15 | SET_PAGE_ADDR = const(0x22) 16 | SET_DISP_START_LINE = const(0x40) 17 | SET_SEG_REMAP = const(0xA0) 18 | SET_MUX_RATIO = const(0xA8) 19 | SET_COM_OUT_DIR = const(0xC0) 20 | SET_DISP_OFFSET = const(0xD3) 21 | SET_COM_PIN_CFG = const(0xDA) 22 | SET_DISP_CLK_DIV = const(0xD5) 23 | SET_PRECHARGE = const(0xD9) 24 | SET_VCOM_DESEL = const(0xDB) 25 | SET_CHARGE_PUMP = const(0x8D) 26 | 27 | 28 | # Subclassing FrameBuffer provides support for graphics primitives 29 | # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html 30 | class SSD1306(framebuf.FrameBuffer): 31 | def __init__(self, width, height, external_vcc, rotate=0): 32 | self.width = width 33 | self.height = height 34 | self.external_vcc = external_vcc 35 | self.pages = self.height // 8 36 | self.buffer = bytearray(self.pages * self.width) 37 | super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) 38 | self.init_display() 39 | self.rotate(rotate) 40 | 41 | def init_display(self): 42 | for cmd in ( 43 | SET_DISP | 0x00, # off 44 | # address setting 45 | SET_MEM_ADDR, 0x00, # horizontal 46 | # resolution and layout 47 | SET_DISP_START_LINE | 0x00, 48 | SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 49 | SET_MUX_RATIO, 50 | self.height - 1, 51 | SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 52 | SET_DISP_OFFSET, 0x00, 53 | SET_COM_PIN_CFG, 54 | 0x02 if self.width > 2 * self.height else 0x12, 55 | # timing and driving scheme 56 | SET_DISP_CLK_DIV, 0x80, 57 | SET_PRECHARGE, 58 | 0x22 if self.external_vcc else 0xF1, 59 | SET_VCOM_DESEL, 0x30, # 0.83*Vcc 60 | # display 61 | SET_CONTRAST, 62 | 0xFF, # maximum 63 | SET_ENTIRE_ON, # output follows RAM contents 64 | SET_NORM_INV, # not inverted 65 | # charge pump 66 | SET_CHARGE_PUMP, 67 | 0x10 if self.external_vcc else 0x14, 68 | SET_DISP | 0x01, 69 | ): # on 70 | self.write_cmd(cmd) 71 | self.fill(0) 72 | self.show() 73 | 74 | def poweroff(self): 75 | self.write_cmd(SET_DISP | 0x00) 76 | 77 | def poweron(self): 78 | self.write_cmd(SET_DISP | 0x01) 79 | 80 | def contrast(self, contrast): 81 | self.write_cmd(SET_CONTRAST) 82 | self.write_cmd(contrast) 83 | 84 | def rotate(self, rotate): 85 | """ 86 | 设置显示旋转 87 | 88 | Args: 89 | rotate(int): 90 | - 0-Portrait 91 | - 1-Upper right printing left (backwards) (X Flip) 92 | - 2-Inverted Portrait 93 | - 3-Lower left printing up (backwards) (Y Flip) 94 | """ 95 | rotate %= 4 96 | mir_v = False 97 | mir_h = False 98 | if rotate == 0: 99 | mir_v = True 100 | mir_h = True 101 | elif rotate == 1: 102 | mir_h = True 103 | elif rotate == 2: 104 | pass 105 | elif rotate == 3: 106 | mir_v = True 107 | self.write_cmd(SET_SEG_REMAP | (0x01 if mir_v else 0x00)) 108 | self.write_cmd(SET_COM_OUT_DIR | (0x08 if mir_h else 0x00)) 109 | self.show() 110 | 111 | def invert(self, invert): 112 | """ 113 | Invert mode, If true, switch to invert mode (black-on-white), else normal mode (white-on-black) 114 | """ 115 | self.write_cmd(SET_NORM_INV | (invert & 1)) 116 | 117 | def show(self): 118 | x0 = 0 119 | x1 = self.width - 1 120 | if self.width == 64: 121 | # displays with width of 64 pixels are shifted by 32 122 | x0 += 32 123 | x1 += 32 124 | self.write_cmd(SET_COL_ADDR) 125 | self.write_cmd(x0) 126 | self.write_cmd(x1) 127 | self.write_cmd(SET_PAGE_ADDR) 128 | self.write_cmd(0) 129 | self.write_cmd(self.pages - 1) 130 | self.write_data(self.buffer) 131 | 132 | def back_light(self, value): 133 | """ 134 | 背光调节 135 | 136 | Args: 137 | value: 背光等级 0 ~ 255 138 | """ 139 | self.contrast(value) 140 | 141 | def circle(self, x, y, radius, c, section=100): 142 | """ 143 | 画圆 144 | 145 | Args: 146 | c: 颜色 147 | x: 中心 x 坐标 148 | y: 中心 y 坐标 149 | radius: 半径 150 | section: 分段 151 | """ 152 | arr = [] 153 | for m in range(section + 1): 154 | _x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + x) 155 | _y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + y) 156 | arr.append([_x, _y]) 157 | for i in range(len(arr) - 1): 158 | self.line(*arr[i], *arr[i + 1], c) 159 | 160 | def fill_circle(self, x, y, radius, c): 161 | """ 162 | 画填充圆 163 | 164 | Args: 165 | c: 颜色 166 | x: 中心 x 坐标 167 | y: 中心 y 坐标 168 | radius: 半径 169 | """ 170 | rsq = radius * radius 171 | for _x in range(radius): 172 | _y = int(math.sqrt(rsq - _x * _x)) # 计算 y 坐标 173 | y0 = y - _y 174 | end_y = y0 + _y * 2 175 | y0 = max(0, min(y0, self.height)) # 将 y0 限制在画布的范围内 176 | length = abs(end_y - y0) + 1 177 | self.vline(x + _x, y0, length, c) # 绘制左右两侧的垂直线 178 | self.vline(x - _x, y0, length, c) 179 | 180 | 181 | class SSD1306_I2C(SSD1306): 182 | def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): 183 | self.i2c = i2c 184 | self.addr = addr 185 | self.temp = bytearray(2) 186 | self.write_list = [b"\x40", None] # Co=0, D/C#=1 187 | super().__init__(width, height, external_vcc) 188 | 189 | def write_cmd(self, cmd): 190 | self.temp[0] = 0x80 # Co=1, D/C#=0 191 | self.temp[1] = cmd 192 | self.i2c.writeto(self.addr, self.temp) 193 | 194 | def write_data(self, buf): 195 | self.write_list[1] = buf 196 | self.i2c.writevto(self.addr, self.write_list) 197 | 198 | 199 | class SSD1306_SPI(SSD1306): 200 | def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): 201 | self.rate = 10 * 1024 * 1024 202 | self.res = Pin(res, Pin.OUT, Pin.PULL_DOWN) 203 | self.dc = Pin(dc, Pin.OUT, Pin.PULL_DOWN) 204 | if cs is None: 205 | self.cs = int 206 | else: 207 | self.cs = Pin(cs, Pin.OUT, Pin.PULL_DOWN) 208 | self.spi = spi 209 | self.dc = dc 210 | self.res = res 211 | self.cs = cs 212 | import time 213 | self.res(1) 214 | time.sleep_ms(1) 215 | self.res(0) 216 | time.sleep_ms(10) 217 | self.res(1) 218 | super().__init__(width, height, external_vcc) 219 | 220 | def write_cmd(self, cmd): 221 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 222 | self.cs(1) 223 | self.dc(0) 224 | self.cs(0) 225 | self.spi.write(bytearray([cmd])) 226 | self.cs(1) 227 | 228 | def write_data(self, buf): 229 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 230 | self.cs(1) 231 | self.dc(1) 232 | self.cs(0) 233 | self.spi.write(buf) 234 | self.cs(1) 235 | -------------------------------------------------------------------------------- /driver/ssd1315_buf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/driver/ssd1315_buf.mpy -------------------------------------------------------------------------------- /driver/ssd1315_buf.py: -------------------------------------------------------------------------------- 1 | # MicroPython SSD1315 OLED driver, I2C and SPI interfaces 2 | import math 3 | import framebuf 4 | from micropython import const 5 | 6 | # register definitions 7 | SET_CONTRAST = const(0x81) 8 | SET_ENTIRE_ON = const(0xA4) 9 | SET_NORM_INV = const(0xA6) 10 | SET_DISP = const(0xAE) 11 | SET_MEM_ADDR = const(0x20) 12 | SET_COL_ADDR = const(0x21) 13 | SET_PAGE_ADDR = const(0x22) 14 | SET_DISP_START_LINE = const(0x40) 15 | SET_SEG_REMAP = const(0xA0) 16 | SET_MUX_RATIO = const(0xA8) 17 | SET_COM_OUT_DIR = const(0xC0) 18 | SET_DISP_OFFSET = const(0xD3) 19 | SET_COM_PIN_CFG = const(0xDA) 20 | SET_DISP_CLK_DIV = const(0xD5) 21 | SET_PRECHARGE = const(0xD9) 22 | SET_VCOM_DESEL = const(0xDB) 23 | SET_CHARGE_PUMP = const(0x8D) 24 | 25 | 26 | # Subclassing FrameBuffer provides support for graphics primitives 27 | # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html 28 | class SSD1315(framebuf.FrameBuffer): 29 | def __init__(self, width, height, external_vcc): 30 | self.width = width 31 | self.height = height 32 | self.external_vcc = external_vcc 33 | self.pages = self.height // 8 34 | self.buffer = bytearray(self.pages * self.width) 35 | super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) 36 | self.init_display() 37 | 38 | def init_display(self): 39 | for cmd in ( 40 | SET_DISP | 0x00, # off 41 | # address setting 42 | SET_MEM_ADDR, 43 | 0x00, # horizontal 44 | # resolution and layout 45 | SET_DISP_START_LINE | 0x00, 46 | SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 47 | SET_MUX_RATIO, 48 | self.height - 1, 49 | SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 50 | SET_DISP_OFFSET, 51 | 0x00, 52 | SET_COM_PIN_CFG, 53 | 0x02 if self.width > 2 * self.height else 0x12, 54 | # timing and driving scheme 55 | SET_DISP_CLK_DIV, 56 | 0x80, 57 | SET_PRECHARGE, 58 | 0x22 if self.external_vcc else 0xF1, 59 | SET_VCOM_DESEL, 60 | 0x30, # 0.83*Vcc 61 | # display 62 | SET_CONTRAST, 63 | 0xFF, # maximum 64 | SET_ENTIRE_ON, # output follows RAM contents 65 | SET_NORM_INV, # not inverted 66 | # charge pump 67 | SET_CHARGE_PUMP, 68 | 0x10 if self.external_vcc else 0x14, 69 | SET_DISP | 0x01, 70 | ): # on 71 | self.write_cmd(cmd) 72 | self.fill(0) 73 | self.show() 74 | 75 | def poweroff(self): 76 | self.write_cmd(SET_DISP | 0x00) 77 | 78 | def poweron(self): 79 | self.write_cmd(SET_DISP | 0x01) 80 | 81 | def contrast(self, contrast): 82 | self.write_cmd(SET_CONTRAST) 83 | self.write_cmd(contrast) 84 | 85 | def invert(self, invert): 86 | self.write_cmd(SET_NORM_INV | (invert & 1)) 87 | 88 | def show(self): 89 | x0 = 0 90 | x1 = self.width - 1 91 | if self.width == 64: 92 | # displays with width of 64 pixels are shifted by 32 93 | x0 += 32 94 | x1 += 32 95 | self.write_cmd(SET_COL_ADDR) 96 | self.write_cmd(x0) 97 | self.write_cmd(x1) 98 | self.write_cmd(SET_PAGE_ADDR) 99 | self.write_cmd(0) 100 | self.write_cmd(self.pages - 1) 101 | self.write_data(self.buffer) 102 | 103 | def back_light(self, value): 104 | """ 105 | 背光调节 106 | 107 | Args: 108 | value: 背光等级 0 ~ 255 109 | """ 110 | self.contrast(value) 111 | 112 | 113 | class SSD1315_I2C(SSD1315): 114 | def __init__(self, width, height, i2c, addr=0x3D, external_vcc=False): 115 | self.i2c = i2c 116 | self.addr = addr 117 | self.temp = bytearray(2) 118 | self.write_list = [b"\x40", None] # Co=0, D/C#=1 119 | super().__init__(width, height, external_vcc) 120 | 121 | def write_cmd(self, cmd): 122 | self.temp[0] = 0x80 # Co=1, D/C#=0 123 | self.temp[1] = cmd 124 | self.i2c.writeto(self.addr, self.temp) 125 | 126 | def write_data(self, buf): 127 | self.write_list[1] = buf 128 | self.i2c.writevto(self.addr, self.write_list) 129 | 130 | def circle(self, center, radius, c, section=100): 131 | """ 132 | 画圆 133 | 134 | Args: 135 | c: 颜色 136 | center: 中心(x, y) 137 | radius: 半径 138 | section: 分段 139 | """ 140 | arr = [] 141 | for m in range(section + 1): 142 | x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + center[0]) 143 | y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + center[1]) 144 | arr.append([x, y]) 145 | for i in range(len(arr) - 1): 146 | self.line(*arr[i], *arr[i + 1], c) 147 | 148 | def fill_circle(self, center, radius, c): 149 | """ 150 | 画填充圆 151 | 152 | Args: 153 | c: 颜色 154 | center: 中心(x, y) 155 | radius: 半径 156 | """ 157 | rsq = radius * radius 158 | for x in range(radius): 159 | y = int(math.sqrt(rsq - x * x)) # 计算 y 坐标 160 | y0 = center[1] - y 161 | end_y = y0 + y * 2 162 | y0 = max(0, min(y0, self.height)) # 将 y0 限制在画布的范围内 163 | length = abs(end_y - y0) + 1 164 | self.vline(center[0] + x, y0, length, c) # 绘制左右两侧的垂直线 165 | self.vline(center[0] - x, y0, length, c) 166 | 167 | 168 | 169 | class SSD1315_SPI(SSD1315): 170 | def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): 171 | self.rate = 10 * 1024 * 1024 172 | dc.init(dc.OUT, value=0) 173 | res.init(res.OUT, value=0) 174 | cs.init(cs.OUT, value=1) 175 | self.spi = spi 176 | self.dc = dc 177 | self.res = res 178 | self.cs = cs 179 | import time 180 | 181 | self.res(1) 182 | time.sleep_ms(1) 183 | self.res(0) 184 | time.sleep_ms(10) 185 | self.res(1) 186 | super().__init__(width, height, external_vcc) 187 | 188 | def write_cmd(self, cmd): 189 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 190 | self.cs(1) 191 | self.dc(0) 192 | self.cs(0) 193 | self.spi.write(bytearray([cmd])) 194 | self.cs(1) 195 | 196 | def write_data(self, buf): 197 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 198 | self.cs(1) 199 | self.dc(1) 200 | self.cs(0) 201 | self.spi.write(buf) 202 | self.cs(1) 203 | 204 | def circle(self, center, radius, c, section=100): 205 | """ 206 | 画圆 207 | 208 | Args: 209 | c: 颜色 210 | center: 中心(x, y) 211 | radius: 半径 212 | section: 分段 213 | """ 214 | arr = [] 215 | for m in range(section + 1): 216 | x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + center[0]) 217 | y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + center[1]) 218 | arr.append([x, y]) 219 | for i in range(len(arr) - 1): 220 | self.line(*arr[i], *arr[i + 1], c) 221 | 222 | def fill_circle(self, center, radius, c): 223 | """ 224 | 画填充圆 225 | 226 | Args: 227 | c: 颜色 228 | center: 中心(x, y) 229 | radius: 半径 230 | """ 231 | rsq = radius * radius 232 | for x in range(radius): 233 | y = int(math.sqrt(rsq - x * x)) # 计算 y 坐标 234 | y0 = center[1] - y 235 | end_y = y0 + y * 2 236 | y0 = max(0, min(y0, self.height)) # 将 y0 限制在画布的范围内 237 | length = abs(end_y - y0) + 1 238 | self.vline(center[0] + x, y0, length, c) # 绘制左右两侧的垂直线 239 | self.vline(center[0] - x, y0, length, c) -------------------------------------------------------------------------------- /driver/st7735_buf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/driver/st7735_buf.mpy -------------------------------------------------------------------------------- /driver/st7735_buf.py: -------------------------------------------------------------------------------- 1 | # 适用于 ST7735 的 Framebuffer 驱动 2 | # Github: https://github.com/funnygeeker/micropython-easydisplay 3 | # Author: funnygeeker 4 | # Licence: MIT 5 | # Date: 2023/11/30 6 | # 7 | # 参考资料: 8 | # https://github.com/AntonVanke/micropython-ufont 9 | # https://blog.csdn.net/weixin_57604547/article/details/120535485 10 | # https://github.com/cheungbx/st7735-esp8266-micropython/blob/master/st7735.py 11 | import gc 12 | import math 13 | import framebuf 14 | from struct import pack 15 | from machine import Pin, PWM 16 | from micropython import const 17 | from time import sleep_us, sleep_ms 18 | 19 | # ST7735V registers definitions 20 | SWRESET = const(0x01) 21 | SLPOUT = const(0x11) 22 | SLPIN = const(0x10) 23 | NORON = const(0x13) 24 | 25 | INVOFF = const(0x20) 26 | INVON = const(0x21) 27 | DISPON = const(0x29) 28 | CASET = const(0x2A) 29 | RASET = const(0x2B) 30 | RAMWR = const(0x2C) 31 | RAMRD = const(0x2E) 32 | 33 | MADCTL = const(0x36) 34 | COLMOD = const(0x3A) 35 | 36 | FRMCTR1 = const(0xB1) 37 | FRMCTR2 = const(0xB2) 38 | FRMCTR3 = const(0xB3) 39 | 40 | INVCTR = const(0xB4) 41 | 42 | PWCTR1 = const(0xC0) 43 | PWCTR2 = const(0xC1) 44 | PWCTR3 = const(0xC2) 45 | PWCTR4 = const(0xC3) 46 | PWCTR5 = const(0xC4) 47 | VMCTR1 = const(0xC5) 48 | 49 | # Color definitions 50 | BLACK = const(0x0000) 51 | BLUE = const(0x001F) 52 | RED = const(0xF800) 53 | GREEN = const(0x07E0) 54 | CYAN = const(0x07FF) 55 | MAGENTA = const(0xF81F) 56 | YELLOW = const(0xFFE0) 57 | WHITE = const(0xFFFF) 58 | 59 | _ENCODE_PIXEL = ">H" 60 | _ENCODE_POS = ">HH" 61 | _DECODE_PIXEL = ">BBB" 62 | 63 | _BUFFER_SIZE = const(256) 64 | 65 | GMCTRP1 = const(0xE0) 66 | GMCTRN1 = const(0xE1) 67 | 68 | # Rotation tables (width, height, xstart, ystart) 69 | 70 | SCREEN_128X160 = [(128, 160, 0, 0), 71 | (160, 128, 0, 0), 72 | (128, 160, 0, 0), 73 | (160, 128, 0, 0), 74 | (128, 160, 0, 0), 75 | (160, 128, 0, 0), 76 | (128, 160, 0, 0)] 77 | SCREEN_128X128 = [(128, 128, 2, 1), 78 | (128, 128, 1, 2), 79 | (128, 128, 2, 3), 80 | (128, 128, 3, 2), 81 | (128, 128, 2, 1), 82 | (128, 128, 1, 2), 83 | (128, 128, 2, 3)] 84 | SCREEN_80X160 = [(80, 160, 26, 1), 85 | (160, 80, 1, 26), 86 | (80, 160, 26, 1), 87 | (160, 80, 1, 26), 88 | (80, 160, 26, 1), 89 | (160, 80, 1, 26), 90 | (80, 160, 26, 1)] 91 | # on MADCTL to control display rotation/color layout 92 | # Looking at display with pins on top. 93 | # 00 = upper left printing right 94 | # 10 = does nothing (MADCTL_ML) 95 | # 40 = upper right printing left (backwards) (X Flip) 96 | # 20 = upper left printing down (backwards) (Vertical flip) 97 | # 80 = lower left printing right (backwards) (Y Flip) 98 | # 04 = (MADCTL_MH) 99 | 100 | # 60 = 90 right rotation 101 | # C0 = 180 right rotation 102 | # A0 = 270 right rotation 103 | ROTATIONS = [0x00, 0x60, 0xC0, 0xA0, 0x40, 0x20, 0x80] # 旋转方向 104 | 105 | 106 | def _encode_pos(x, y): 107 | """Encode a postion into bytes.""" 108 | return pack(_ENCODE_POS, x, y) 109 | 110 | 111 | def _encode_pixel(c): 112 | """Encode a pixel color into bytes.""" 113 | return pack(_ENCODE_PIXEL, c) 114 | 115 | 116 | class ST7735(framebuf.FrameBuffer): 117 | def __init__(self, width: int, height: int, spi, res: int, dc: int, 118 | cs: int = None, bl: int = None, rotate: int = 0, rgb: bool = True, invert: bool = True): 119 | """ 120 | 初始化屏幕驱动 121 | 122 | Args: 123 | width: 宽度 124 | height: 高度 125 | spi: SPI 实例 126 | res: RESET 引脚 127 | dc: Data / Command 引脚 128 | cs: 片选引脚 129 | bl: 背光引脚 130 | rotate: 旋转图像,数值为 0-6 131 | rgb: 使用 RGB 颜色模式,而不是 BGR 132 | invert: 反转颜色 133 | """ 134 | self.width = width 135 | self.height = height 136 | self.x_start = 0 137 | self.y_start = 0 138 | self.spi = spi 139 | self.res = Pin(res, Pin.OUT, Pin.PULL_DOWN) 140 | self.dc = Pin(dc, Pin.OUT, Pin.PULL_DOWN) 141 | if cs is None: 142 | self.cs = int 143 | else: 144 | self.cs = Pin(cs, Pin.OUT, Pin.PULL_DOWN) 145 | if bl is not None: 146 | self.bl = PWM(Pin(bl, Pin.OUT)) 147 | self.back_light(255) 148 | else: 149 | self.bl = None 150 | self._rotate = rotate 151 | self._rgb = rgb 152 | self.hard_reset() 153 | self.soft_reset() 154 | self.poweron() 155 | # 156 | sleep_us(300) 157 | self._write(FRMCTR1, bytearray([0x01, 0x2C, 0x2D])) 158 | self._write(FRMCTR2, bytearray([0x01, 0x2C, 0x2D])) 159 | self._write(FRMCTR3, bytearray([0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D])) 160 | sleep_us(10) 161 | self._write(INVCTR, bytearray([0x07])) 162 | self._write(PWCTR1, bytearray([0xA2, 0x02, 0x84])) 163 | self._write(PWCTR2, bytearray([0xC5])) 164 | self._write(PWCTR3, bytearray([0x0A, 0x00])) 165 | self._write(PWCTR4, bytearray([0x8A, 0x2A])) 166 | self._write(PWCTR5, bytearray([0x8A, 0xEE])) 167 | self._write(VMCTR1, bytearray([0x0E])) 168 | # 169 | self._write(COLMOD, bytearray([0x05])) # color mode 170 | sleep_ms(50) 171 | gc.collect() # 垃圾收集 172 | self.buffer = bytearray(self.height * self.width * 2) 173 | self.rotate(self._rotate) 174 | self.invert(invert) 175 | sleep_ms(10) 176 | self.write_cmd(GMCTRP1) 177 | self.write_data( 178 | bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10])) 179 | self.write_cmd(GMCTRN1) 180 | self.write_data( 181 | bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d, 0x2e, 0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10])) 182 | self.write_cmd(NORON) 183 | sleep_us(10) 184 | self.write_cmd(DISPON) 185 | sleep_ms(100) 186 | # super().__init__(self.buffer, self.width, self.height, framebuf.RGB565, self.width) 187 | self.clear() 188 | self.show() 189 | 190 | def _write(self, command=None, data=None): 191 | """SPI write to the device: commands and data.""" 192 | self.cs(0) 193 | if command is not None: 194 | self.dc(0) 195 | self.spi.write(bytes([command])) 196 | if data is not None: 197 | self.dc(1) 198 | self.spi.write(data) 199 | self.cs(1) 200 | 201 | def write_cmd(self, cmd): 202 | """ 203 | 写命令 204 | 205 | Args: 206 | cmd: 命令内容 207 | """ 208 | self.cs(0) 209 | self.dc(0) 210 | self.spi.write(bytes([cmd])) 211 | self.cs(1) 212 | 213 | def write_data(self, data): 214 | """ 215 | 写数据 216 | 217 | Args: 218 | data: 数据内容 219 | """ 220 | self.cs(0) 221 | self.dc(1) 222 | self.spi.write(data) 223 | self.cs(1) 224 | 225 | def hard_reset(self): 226 | """ 227 | Hard reset display. 228 | """ 229 | self.cs(0) 230 | self.res(1) 231 | sleep_ms(50) 232 | self.res(0) 233 | sleep_ms(50) 234 | self.res(1) 235 | sleep_ms(150) 236 | self.cs(1) 237 | 238 | def soft_reset(self): 239 | """ 240 | Soft reset display. 241 | """ 242 | self._write(SWRESET) 243 | sleep_ms(150) 244 | 245 | def poweron(self): 246 | """Disable display sleep mode.""" 247 | self._write(SLPOUT) 248 | 249 | def poweroff(self): 250 | """Enable display sleep mode.""" 251 | self._write(SLPIN) 252 | 253 | def invert(self, value): 254 | """ 255 | Enable or disable display inversion mode. 256 | 257 | Args: 258 | value (bool): if True enable inversion mode. if False disable 259 | inversion mode 260 | """ 261 | if value: 262 | self._write(INVON) 263 | else: 264 | self._write(INVOFF) 265 | 266 | def rotate(self, rotate): 267 | """ 268 | Set display rotation. 269 | 270 | Args: 271 | rotate (int): 272 | - 0-Portrait 273 | - 1-Landscape 274 | - 2-Inverted Portrait 275 | - 3-Inverted Landscape 276 | - 4-Upper right printing left (backwards) (X Flip) 277 | - 5-Upper left printing down (backwards) (Vertical flip) 278 | - 6-Lower left printing right (backwards) (Y Flip) 279 | """ 280 | self._rotate = rotate 281 | madctl = ROTATIONS[rotate] 282 | if (self.width == 160 and self.height == 80) or (self.width == 80 and self.height == 160): 283 | table = SCREEN_80X160 284 | elif (self.width == 160 and self.height == 128) or (self.width == 128 and self.height == 160): 285 | table = SCREEN_128X160 286 | elif self.width == 128 and self.height == 128: 287 | table = SCREEN_128X128 288 | else: 289 | raise ValueError( 290 | "Unsupported display. 128x160, 128x128 and 80x160 are supported." 291 | ) 292 | 293 | self.width, self.height, self.x_start, self.y_start = table[rotate] 294 | super().__init__(self.buffer, self.width, self.height, framebuf.RGB565, self.width) 295 | self._write(MADCTL, bytes([madctl | (0x00 if self._rgb else 0x08)])) 296 | 297 | def _set_columns(self, start, end): 298 | """ 299 | Send CASET (column address set) command to display. 300 | 301 | Args: 302 | start (int): column start address 303 | end (int): column end address 304 | """ 305 | if start <= end <= self.width: 306 | self._write(CASET, _encode_pos( 307 | start + self.x_start, end + self.x_start)) 308 | 309 | def _set_rows(self, start, end): 310 | """ 311 | Send RASET (row address set) command to display. 312 | 313 | Args: 314 | start (int): row start address 315 | end (int): row end address 316 | """ 317 | if start <= end <= self.height: 318 | self._write(RASET, _encode_pos( 319 | start + self.y_start, end + self.y_start)) 320 | 321 | def set_window(self, x0, y0, x1, y1): 322 | """ 323 | Set window to column and row address. 324 | 325 | Args: 326 | x0 (int): column start address 327 | y0 (int): row start address 328 | x1 (int): column end address 329 | y1 (int): row end address 330 | """ 331 | if x0 < self.width and y0 < self.height: 332 | self._set_columns(x0, x1) 333 | self._set_rows(y0, y1) 334 | self._write(RAMWR) 335 | 336 | def clear(self): 337 | """ 338 | 清屏 339 | """ 340 | self.fill(0) 341 | 342 | def show(self): 343 | """ 344 | 将帧缓冲区数据发送到屏幕 345 | """ 346 | self.set_window(0, 0, self.width - 1, self.height - 1) 347 | self._write(RAMWR, self.buffer) 348 | 349 | # @staticmethod 350 | # def color(r, g, b): 351 | # c = ((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3) 352 | # return (c >> 8) | ((c & 0xFF) << 8) 353 | 354 | def rgb(self, enable: bool): 355 | """ 356 | 设置颜色模式 357 | 358 | Args: 359 | enable: RGB else BGR 360 | """ 361 | self._rgb = enable 362 | self._write(MADCTL, bytes([ROTATIONS[self._rotation] | (0x00 if self._rgb else 0x08)])) 363 | 364 | @staticmethod 365 | def color(r, g, b): 366 | """ 367 | Convert red, green and blue values (0-255) into a 16-bit 565 encoding. 368 | """ 369 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 370 | 371 | def back_light(self, value): 372 | """ 373 | 背光调节 374 | 375 | Args: 376 | value: 背光等级 0 ~ 255 377 | """ 378 | self.bl.freq(1000) 379 | if value >= 0xff: 380 | value = 0xff 381 | data = value * 0xffff >> 8 382 | self.bl.duty_u16(data) 383 | 384 | def circle(self, x, y, radius, c, section=100): 385 | """ 386 | 画圆 387 | 388 | Args: 389 | c: 颜色 390 | x: 中心 x 坐标 391 | y: 中心 y 坐标 392 | radius: 半径 393 | section: 分段 394 | """ 395 | arr = [] 396 | for m in range(section + 1): 397 | _x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + x) 398 | _y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + y) 399 | arr.append([_x, _y]) 400 | for i in range(len(arr) - 1): 401 | self.line(*arr[i], *arr[i + 1], c) 402 | 403 | def fill_circle(self, x, y, radius, c): 404 | """ 405 | 画填充圆 406 | 407 | Args: 408 | c: 颜色 409 | x: 中心 x 坐标 410 | y: 中心 y 坐标 411 | radius: 半径 412 | """ 413 | rsq = radius * radius 414 | for _x in range(radius): 415 | _y = int(math.sqrt(rsq - _x * _x)) # 计算 y 坐标 416 | y0 = y - _y 417 | end_y = y0 + _y * 2 418 | y0 = max(0, min(y0, self.height)) # 将 y0 限制在画布的范围内 419 | length = abs(end_y - y0) + 1 420 | self.vline(x + _x, y0, length, c) # 绘制左右两侧的垂直线 421 | self.vline(x - _x, y0, length, c) 422 | 423 | # def image(self, file_name): 424 | # with open(file_name, "rb") as bmp: 425 | # for b in range(0, 80 * 160 * 2, 1024): 426 | # self.buffer[b:b + 1024] = bmp.read(1024) 427 | # self.show() 428 | -------------------------------------------------------------------------------- /driver/st7735_spi.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/driver/st7735_spi.mpy -------------------------------------------------------------------------------- /driver/st7735_spi.py: -------------------------------------------------------------------------------- 1 | # 适用于 ST7735 的 SPI 直接驱动 2 | # Github: https://github.com/funnygeeker/micropython-easydisplay 3 | # Author: funnygeeker 4 | # Licence: MIT 5 | # Date: 2023/11/30 6 | # 7 | # 参考资料: 8 | # https://github.com/AntonVanke/micropython-ufont 9 | # https://blog.csdn.net/weixin_57604547/article/details/120535485 10 | # https://github.com/cheungbx/st7735-esp8266-micropython/blob/master/st7735.py 11 | import math 12 | from struct import pack 13 | from machine import Pin, PWM 14 | from micropython import const 15 | from time import sleep_us, sleep_ms 16 | 17 | # ST7735V registers definitions 18 | SWRESET = const(0x01) 19 | SLPOUT = const(0x11) 20 | SLPIN = const(0x10) 21 | NORON = const(0x13) 22 | 23 | INVOFF = const(0x20) 24 | INVON = const(0x21) 25 | DISPON = const(0x29) 26 | CASET = const(0x2A) 27 | RASET = const(0x2B) 28 | RAMWR = const(0x2C) 29 | RAMRD = const(0x2E) 30 | 31 | MADCTL = const(0x36) 32 | COLMOD = const(0x3A) 33 | 34 | FRMCTR1 = const(0xB1) 35 | FRMCTR2 = const(0xB2) 36 | FRMCTR3 = const(0xB3) 37 | 38 | INVCTR = const(0xB4) 39 | 40 | PWCTR1 = const(0xC0) 41 | PWCTR2 = const(0xC1) 42 | PWCTR3 = const(0xC2) 43 | PWCTR4 = const(0xC3) 44 | PWCTR5 = const(0xC4) 45 | VMCTR1 = const(0xC5) 46 | 47 | # Color definitions 48 | BLACK = const(0x0000) 49 | BLUE = const(0x001F) 50 | RED = const(0xF800) 51 | GREEN = const(0x07E0) 52 | CYAN = const(0x07FF) 53 | MAGENTA = const(0xF81F) 54 | YELLOW = const(0xFFE0) 55 | WHITE = const(0xFFFF) 56 | 57 | _ENCODE_PIXEL = ">H" 58 | _ENCODE_POS = ">HH" 59 | _DECODE_PIXEL = ">BBB" 60 | 61 | _BUFFER_SIZE = const(256) 62 | 63 | GMCTRP1 = const(0xE0) 64 | GMCTRN1 = const(0xE1) 65 | 66 | # Rotation tables (width, height, xstart, ystart) 67 | 68 | SCREEN_128X160 = [(128, 160, 0, 0), 69 | (160, 128, 0, 0), 70 | (128, 160, 0, 0), 71 | (160, 128, 0, 0), 72 | (128, 160, 0, 0), 73 | (160, 128, 0, 0), 74 | (128, 160, 0, 0)] 75 | SCREEN_128X128 = [(128, 128, 2, 1), 76 | (128, 128, 1, 2), 77 | (128, 128, 2, 3), 78 | (128, 128, 3, 2), 79 | (128, 128, 2, 1), 80 | (128, 128, 1, 2), 81 | (128, 128, 2, 3)] 82 | SCREEN_80X160 = [(80, 160, 26, 1), 83 | (160, 80, 1, 26), 84 | (80, 160, 26, 1), 85 | (160, 80, 1, 26), 86 | (80, 160, 26, 1), 87 | (160, 80, 1, 26), 88 | (80, 160, 26, 1)] 89 | # on MADCTL to control display rotation/color layout 90 | # Looking at display with pins on top. 91 | # 00 = upper left printing right 92 | # 10 = does nothing (MADCTL_ML) 93 | # 40 = upper right printing left (backwards) (X Flip) 94 | # 20 = upper left printing down (backwards) (Vertical flip) 95 | # 80 = lower left printing right (backwards) (Y Flip) 96 | # 04 = (MADCTL_MH) 97 | 98 | # 60 = 90 right rotation 99 | # C0 = 180 right rotation 100 | # A0 = 270 right rotation 101 | ROTATIONS = [0x00, 0x60, 0xC0, 0xA0, 0x40, 0x20, 0x80] # 旋转方向 102 | 103 | 104 | def _encode_pos(x, y): 105 | """Encode a postion into bytes.""" 106 | return pack(_ENCODE_POS, x, y) 107 | 108 | 109 | def _encode_pixel(c): 110 | """Encode a pixel color into bytes.""" 111 | return pack(_ENCODE_PIXEL, c) 112 | 113 | 114 | class ST7735: 115 | def __init__(self, width: int, height: int, spi, res: int, dc: int, 116 | cs: int = None, bl: int = None, rotate: int = 0, rgb: bool = True, invert: bool = True): 117 | """ 118 | 初始化屏幕驱动 119 | 120 | Args: 121 | width: 宽度 122 | height: 高度 123 | spi: SPI 实例 124 | res: RESET 引脚 125 | dc: Data / Command 引脚 126 | cs: 片选引脚 127 | bl: 背光引脚 128 | rotate: 旋转图像,数值为 0-6 129 | rgb: 使用 RGB 颜色模式,而不是 BGR 130 | invert: 反转颜色 131 | """ 132 | self.width = width 133 | self.height = height 134 | self.x_start = 0 135 | self.y_start = 0 136 | self.spi = spi 137 | self.res = Pin(res, Pin.OUT, Pin.PULL_DOWN) 138 | self.dc = Pin(dc, Pin.OUT, Pin.PULL_DOWN) 139 | if cs is None: 140 | self.cs = int 141 | else: 142 | self.cs = Pin(cs, Pin.OUT, Pin.PULL_DOWN) 143 | if bl is not None: 144 | self.bl = PWM(Pin(bl, Pin.OUT)) 145 | self.back_light(255) 146 | else: 147 | self.bl = None 148 | self._rotate = rotate 149 | self._rgb = rgb 150 | self.hard_reset() 151 | self.soft_reset() 152 | self.poweron() 153 | # 154 | sleep_us(300) 155 | self._write(FRMCTR1, bytearray([0x01, 0x2C, 0x2D])) 156 | self._write(FRMCTR2, bytearray([0x01, 0x2C, 0x2D])) 157 | self._write(FRMCTR3, bytearray([0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D])) 158 | sleep_us(10) 159 | self._write(INVCTR, bytearray([0x07])) 160 | self._write(PWCTR1, bytearray([0xA2, 0x02, 0x84])) 161 | self._write(PWCTR2, bytearray([0xC5])) 162 | self._write(PWCTR3, bytearray([0x0A, 0x00])) 163 | self._write(PWCTR4, bytearray([0x8A, 0x2A])) 164 | self._write(PWCTR5, bytearray([0x8A, 0xEE])) 165 | self._write(VMCTR1, bytearray([0x0E])) 166 | # 167 | self._write(COLMOD, bytearray([0x05])) # color mode 168 | sleep_ms(50) 169 | self.rotate(self._rotate) 170 | self.invert(invert) 171 | sleep_ms(10) 172 | self.write_cmd(GMCTRP1) 173 | self.write_data( 174 | bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10])) 175 | self.write_cmd(GMCTRN1) 176 | self.write_data( 177 | bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d, 0x2e, 0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10])) 178 | self.write_cmd(NORON) 179 | sleep_us(10) 180 | self.write_cmd(DISPON) 181 | sleep_ms(100) 182 | self.clear() 183 | 184 | def _write(self, command=None, data=None): 185 | """SPI write to the device: commands and data.""" 186 | self.cs(0) 187 | if command is not None: 188 | self.dc(0) 189 | self.spi.write(bytes([command])) 190 | if data is not None: 191 | self.dc(1) 192 | self.spi.write(data) 193 | self.cs(1) 194 | 195 | def write_cmd(self, cmd): 196 | """ 197 | 写命令 198 | 199 | Args: 200 | cmd: 命令内容 201 | """ 202 | self.cs(0) 203 | self.dc(0) 204 | self.spi.write(bytes([cmd])) 205 | self.cs(1) 206 | 207 | def write_data(self, data): 208 | """ 209 | 写数据 210 | 211 | Args: 212 | data: 数据内容 213 | """ 214 | self.cs(0) 215 | self.dc(1) 216 | self.spi.write(data) 217 | self.cs(1) 218 | 219 | def hard_reset(self): 220 | """ 221 | Hard reset display. 222 | """ 223 | self.cs(0) 224 | self.res(1) 225 | sleep_ms(50) 226 | self.res(0) 227 | sleep_ms(50) 228 | self.res(1) 229 | sleep_ms(150) 230 | self.cs(1) 231 | 232 | def soft_reset(self): 233 | """ 234 | Soft reset display. 235 | """ 236 | self._write(SWRESET) 237 | sleep_ms(150) 238 | 239 | def poweron(self): 240 | """Disable display sleep mode.""" 241 | self._write(SLPOUT) 242 | 243 | def poweroff(self): 244 | """Enable display sleep mode.""" 245 | self._write(SLPIN) 246 | 247 | def invert(self, value): 248 | """ 249 | Enable or disable display inversion mode. 250 | 251 | Args: 252 | value (bool): if True enable inversion mode. if False disable 253 | inversion mode 254 | """ 255 | if value: 256 | self._write(INVON) 257 | else: 258 | self._write(INVOFF) 259 | 260 | def rotate(self, rotate): 261 | """ 262 | Set display rotation. 263 | 264 | Args: 265 | rotate (int): 266 | - 0-Portrait 267 | - 1-Landscape 268 | - 2-Inverted Portrait 269 | - 3-Inverted Landscape 270 | - 4-Upper right printing left (backwards) (X Flip) 271 | - 5-Upper left printing down (backwards) (Vertical flip) 272 | - 6-Lower left printing right (backwards) (Y Flip) 273 | """ 274 | self._rotate = rotate 275 | madctl = ROTATIONS[rotate] 276 | if (self.width == 160 and self.height == 80) or (self.width == 80 and self.height == 160): 277 | table = SCREEN_80X160 278 | elif (self.width == 160 and self.height == 128) or (self.width == 128 and self.height == 160): 279 | table = SCREEN_128X160 280 | elif self.width == 128 and self.height == 128: 281 | table = SCREEN_128X128 282 | else: 283 | raise ValueError( 284 | "Unsupported display. 128x160, 128x128 and 80x160 are supported." 285 | ) 286 | 287 | self.width, self.height, self.x_start, self.y_start = table[rotate] 288 | self._write(MADCTL, bytes([madctl | (0x00 if self._rgb else 0x08)])) 289 | 290 | def _set_columns(self, start, end): 291 | """ 292 | Send CASET (column address set) command to display. 293 | 294 | Args: 295 | start (int): column start address 296 | end (int): column end address 297 | """ 298 | if start <= end <= self.width: 299 | self._write(CASET, _encode_pos( 300 | start + self.x_start, end + self.x_start)) 301 | 302 | def _set_rows(self, start, end): 303 | """ 304 | Send RASET (row address set) command to display. 305 | 306 | Args: 307 | start (int): row start address 308 | end (int): row end address 309 | """ 310 | if start <= end <= self.height: 311 | self._write(RASET, _encode_pos( 312 | start + self.y_start, end + self.y_start)) 313 | 314 | def set_window(self, x0, y0, x1, y1): 315 | """ 316 | Set window to column and row address. 317 | 318 | Args: 319 | x0 (int): column start address 320 | y0 (int): row start address 321 | x1 (int): column end address 322 | y1 (int): row end address 323 | """ 324 | if x0 < self.width and y0 < self.height: 325 | self._set_columns(x0, x1) 326 | self._set_rows(y0, y1) 327 | self._write(RAMWR) 328 | 329 | def clear(self): 330 | """ 331 | 清屏 332 | """ 333 | self.fill(0) 334 | 335 | def show(self): 336 | """ 337 | 将帧缓冲区数据发送到屏幕 338 | """ 339 | pass # 无帧缓冲区 340 | 341 | # @staticmethod 342 | # def color(r, g, b): 343 | # c = ((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3) 344 | # return (c >> 8) | ((c & 0xFF) << 8) 345 | 346 | def rgb(self, enable: bool): 347 | """ 348 | 设置颜色模式 349 | 350 | Args: 351 | enable: RGB else BGR 352 | """ 353 | self._rgb = enable 354 | self._write(MADCTL, bytes([ROTATIONS[self._rotation] | (0x00 if self._rgb else 0x08)])) 355 | 356 | @staticmethod 357 | def color(r, g, b): 358 | """ 359 | Convert red, green and blue values (0-255) into a 16-bit 565 encoding. 360 | """ 361 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 362 | 363 | def back_light(self, value): 364 | """ 365 | 背光调节 366 | 367 | Args: 368 | value: 背光等级 0 ~ 255 369 | """ 370 | self.bl.freq(1000) 371 | if value >= 0xff: 372 | value = 0xff 373 | data = value * 0xffff >> 8 374 | self.bl.duty_u16(data) 375 | 376 | def vline(self, x, y, h, c): 377 | """ 378 | Draw vertical line at the given location and color. 379 | 380 | Args: 381 | x (int): x coordinate 382 | y (int): y coordinate 383 | h (int): length of line 384 | c (int): 565 encoded color 385 | """ 386 | self.fill_rect(x, y, 1, h, c) 387 | 388 | def hline(self, x, y, w, c): 389 | """ 390 | Draw horizontal line at the given location and color. 391 | 392 | Args: 393 | x (int): x coordinate 394 | y (int): y coordinate 395 | w (int): length of line 396 | c (int): 565 encoded color 397 | """ 398 | self.fill_rect(x, y, w, 1, c) 399 | 400 | def pixel(self, x, y, c: int = None): 401 | """ 402 | Draw a pixel at the given location and color. 403 | 404 | Args: 405 | x (int): x coordinate 406 | y (int): y coordinate 407 | c (int): 565 encoded color 408 | """ 409 | self.set_window(x, y, x, y) 410 | self._write(None, _encode_pixel(c)) 411 | 412 | def blit_buffer(self, buffer, x, y, width, height): 413 | """ 414 | Copy buffer to display at the given location. 415 | 416 | Args: 417 | buffer (bytes): Data to copy to display 418 | x (int): Top left corner x coordinate 419 | y (int): Top left corner y coordinate 420 | width (int): Width 421 | height (int): Height 422 | """ 423 | self.set_window(x, y, x + width - 1, y + height - 1) 424 | self._write(None, buffer) 425 | 426 | def rect(self, x, y, w, h, c): 427 | """ 428 | Draw a rectangle at the given location, size and color. 429 | 430 | Args: 431 | x (int): Top left corner x coordinate 432 | y (int): Top left corner y coordinate 433 | w (int): Width in pixels 434 | h (int): Height in pixels 435 | c (int): 565 encoded color 436 | """ 437 | self.hline(x, y, w, c) 438 | self.vline(x, y, h, c) 439 | self.vline(x + w - 1, y, h, c) 440 | self.hline(x, y + h - 1, w, c) 441 | 442 | def fill_rect(self, x, y, w, h, c): 443 | """ 444 | Draw a rectangle at the given location, size and filled with color. 445 | 446 | Args: 447 | x (int): Top left corner x coordinate 448 | y (int): Top left corner y coordinate 449 | w (int): Width in pixels 450 | h (int): Height in pixels 451 | c (int): 565 encoded color 452 | """ 453 | self.set_window(x, y, x + w - 1, y + h - 1) 454 | chunks, rest = divmod(w * h, _BUFFER_SIZE) 455 | pixel = _encode_pixel(c) 456 | self.dc(1) 457 | if chunks: 458 | data = pixel * _BUFFER_SIZE 459 | for _ in range(chunks): 460 | self._write(None, data) 461 | if rest: 462 | self._write(None, pixel * rest) 463 | 464 | def fill(self, c): 465 | """ 466 | Fill the entire FrameBuffer with the specified color. 467 | 468 | Args: 469 | c (int): 565 encoded color 470 | """ 471 | self.fill_rect(0, 0, self.width, self.height, c) 472 | 473 | def line(self, x1, y1, x2, y2, c): 474 | """ 475 | Draw a single pixel wide line starting at x0, y0 and ending at x1, y1. 476 | 477 | Args: 478 | x1 (int): Start point x coordinate 479 | y1 (int): Start point y coordinate 480 | x2 (int): End point x coordinate 481 | y2 (int): End point y coordinate 482 | c (int): 565 encoded color 483 | """ 484 | steep = abs(y2 - y1) > abs(x2 - x1) 485 | if steep: 486 | x1, y1 = y1, x1 487 | x2, y2 = y2, x2 488 | if x1 > x2: 489 | x1, x2 = x2, x1 490 | y1, y2 = y2, y1 491 | dx = x2 - x1 492 | dy = abs(y2 - y1) 493 | err = dx // 2 494 | ystep = 1 if y1 < y2 else -1 495 | while x1 <= x2: 496 | if steep: 497 | self.pixel(y1, x1, c) 498 | else: 499 | self.pixel(x1, y1, c) 500 | err -= dy 501 | if err < 0: 502 | y1 += ystep 503 | err += dx 504 | x1 += 1 505 | 506 | def circle(self, x, y, radius, c, section=100): 507 | """ 508 | 画圆 509 | 510 | Args: 511 | c: 颜色 512 | x: 中心 x 坐标 513 | y: 中心 y 坐标 514 | radius: 半径 515 | section: 分段 516 | """ 517 | arr = [] 518 | for m in range(section + 1): 519 | _x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + x) 520 | _y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + y) 521 | arr.append([_x, _y]) 522 | for i in range(len(arr) - 1): 523 | self.line(*arr[i], *arr[i + 1], c) 524 | 525 | def fill_circle(self, x, y, radius, c): 526 | """ 527 | 画填充圆 528 | 529 | Args: 530 | c: 颜色 531 | x: 中心 x 坐标 532 | y: 中心 y 坐标 533 | radius: 半径 534 | """ 535 | rsq = radius * radius 536 | for _x in range(radius): 537 | _y = int(math.sqrt(rsq - _x * _x)) # 计算 y 坐标 538 | y0 = y - _y 539 | end_y = y0 + _y * 2 540 | y0 = max(0, min(y0, self.height)) # 将 y0 限制在画布的范围内 541 | length = abs(end_y - y0) + 1 542 | self.vline(x + _x, y0, length, c) # 绘制左右两侧的垂直线 543 | self.vline(x - _x, y0, length, c) 544 | 545 | # def image(self, file_name): 546 | # with open(file_name, "rb") as bmp: 547 | # for b in range(0, 80 * 160 * 2, 1024): 548 | # self.buffer[b:b + 1024] = bmp.read(1024) 549 | # self.show() 550 | -------------------------------------------------------------------------------- /driver/st7789_buf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/driver/st7789_buf.mpy -------------------------------------------------------------------------------- /driver/st7789_buf.py: -------------------------------------------------------------------------------- 1 | # 适用于 ST7789 的 Framebuffer 驱动 2 | # Github: https://github.com/funnygeeker/micropython-easydisplay 3 | # Author: funnygeeker 4 | # Licence: MIT 5 | # Date: 2023/11/30 6 | # 7 | # 参考资料: 8 | # https://github.com/russhughes/st7789py_mpy/ 9 | # https://github.com/AntonVanke/MicroPython-uFont 10 | # https://github.com/cheungbx/st7735-esp8266-micropython/blob/master/st7735.py 11 | import gc 12 | import math 13 | import framebuf 14 | from struct import pack 15 | from machine import Pin, PWM 16 | from micropython import const 17 | from time import sleep_us, sleep_ms 18 | 19 | # commands 20 | ST7789_NOP = const(0x00) 21 | ST7789_SWRESET = const(0x01) 22 | ST7789_RDDID = const(0x04) 23 | ST7789_RDDST = const(0x09) 24 | 25 | ST7789_SLPIN = const(0x10) 26 | ST7789_SLPOUT = const(0x11) 27 | ST7789_PTLON = const(0x12) 28 | ST7789_NORON = const(0x13) 29 | 30 | ST7789_INVOFF = const(0x20) 31 | ST7789_INVON = const(0x21) 32 | ST7789_DISPOFF = const(0x28) 33 | ST7789_DISPON = const(0x29) 34 | ST7789_CASET = const(0x2A) 35 | ST7789_RASET = const(0x2B) 36 | ST7789_RAMWR = const(0x2C) 37 | ST7789_RAMRD = const(0x2E) 38 | 39 | ST7789_PTLAR = const(0x30) 40 | ST7789_VSCRDEF = const(0x33) 41 | ST7789_COLMOD = const(0x3A) 42 | ST7789_MADCTL = const(0x36) 43 | ST7789_VSCSAD = const(0x37) 44 | 45 | ST7789_MADCTL_MY = const(0x80) 46 | ST7789_MADCTL_MX = const(0x40) 47 | ST7789_MADCTL_MV = const(0x20) 48 | ST7789_MADCTL_ML = const(0x10) 49 | ST7789_MADCTL_BGR = const(0x08) 50 | ST7789_MADCTL_MH = const(0x04) 51 | ST7789_MADCTL_RGB = const(0x00) 52 | 53 | ST7789_RDID1 = const(0xDA) 54 | ST7789_RDID2 = const(0xDB) 55 | ST7789_RDID3 = const(0xDC) 56 | ST7789_RDID4 = const(0xDD) 57 | 58 | COLOR_MODE_65K = const(0x50) 59 | COLOR_MODE_262K = const(0x60) 60 | COLOR_MODE_12BIT = const(0x03) 61 | COLOR_MODE_16BIT = const(0x05) 62 | COLOR_MODE_18BIT = const(0x06) 63 | COLOR_MODE_16M = const(0x07) 64 | # 65 | FRMCTR1 = const(0xB1) 66 | FRMCTR2 = const(0xB2) 67 | FRMCTR3 = const(0xB3) 68 | 69 | INVCTR = const(0xB4) 70 | 71 | PWCTR1 = const(0xC0) 72 | PWCTR2 = const(0xC1) 73 | PWCTR3 = const(0xC2) 74 | PWCTR4 = const(0xC3) 75 | PWCTR5 = const(0xC4) 76 | VMCTR1 = const(0xC5) 77 | 78 | # Color definitions 79 | BLACK = const(0x0000) 80 | BLUE = const(0x001F) 81 | RED = const(0xF800) 82 | GREEN = const(0x07E0) 83 | CYAN = const(0x07FF) 84 | MAGENTA = const(0xF81F) 85 | YELLOW = const(0xFFE0) 86 | WHITE = const(0xFFFF) 87 | 88 | _ENCODE_PIXEL = ">H" 89 | _ENCODE_POS = ">HH" 90 | _DECODE_PIXEL = ">BBB" 91 | 92 | _BUFFER_SIZE = const(256) 93 | 94 | _BIT7 = const(0x80) 95 | _BIT6 = const(0x40) 96 | _BIT5 = const(0x20) 97 | _BIT4 = const(0x10) 98 | _BIT3 = const(0x08) 99 | _BIT2 = const(0x04) 100 | _BIT1 = const(0x02) 101 | _BIT0 = const(0x01) 102 | 103 | # Rotation tables (width, height, xstart, ystart) 104 | 105 | WIDTH_320 = [(240, 320, 0, 0), 106 | (320, 240, 0, 0), 107 | (240, 320, 0, 0), 108 | (320, 240, 0, 0), 109 | (240, 320, 0, 0), 110 | (320, 240, 0, 0), 111 | (240, 320, 0, 0)] # 320x240 112 | WIDTH_240 = [(240, 240, 0, 0), 113 | (240, 240, 0, 0), 114 | (240, 240, 80, 0), 115 | (240, 240, 0, 80), 116 | (240, 240, 0, 0), 117 | (240, 240, 0, 0), 118 | (240, 240, 80, 0)] # 240x240 119 | WIDTH_135 = [(135, 240, 52, 40), 120 | (240, 135, 40, 53), 121 | (135, 240, 53, 40), 122 | (240, 135, 40, 52), 123 | (135, 240, 52, 40), 124 | (240, 135, 40, 53), 125 | (135, 240, 53, 40)] # 135x240 126 | # on MADCTL to control display rotation/color layout 127 | # Looking at display with pins on top. 128 | # 00 = upper left printing right 129 | # 10 = does nothing (MADCTL_ML) 130 | # 40 = upper right printing left (backwards) (X Flip) 131 | # 20 = upper left printing down (backwards) (Vertical flip) 132 | # 80 = lower left printing right (backwards) (Y Flip) 133 | # 04 = (MADCTL_MH) 134 | 135 | # 60 = 90 right rotation 136 | # C0 = 180 right rotation 137 | # A0 = 270 right rotation 138 | ROTATIONS = [0x00, 0x60, 0xC0, 0xA0, 0x40, 0x20, 0x80] # 旋转方向 139 | 140 | 141 | def _encode_pos(x, y): 142 | """Encode a postion into bytes.""" 143 | return pack(_ENCODE_POS, x, y) 144 | 145 | 146 | def _encode_pixel(c): 147 | """Encode a pixel color into bytes.""" 148 | return pack(_ENCODE_PIXEL, c) 149 | 150 | 151 | class ST7789(framebuf.FrameBuffer): 152 | def __init__(self, width: int, height: int, spi, res: int, dc: int, 153 | cs: int = None, bl: int = None, rotate: int = 0, rgb: bool = True, invert: bool = True): 154 | """ 155 | 初始化屏幕驱动 156 | 157 | Args: 158 | width: 宽度 159 | height: 高度 160 | spi: SPI 实例 161 | res: RESET 引脚 162 | dc: Data / Command 引脚 163 | cs: 片选引脚 164 | bl: 背光引脚 165 | rotate: 旋转图像,数值为 0-6 166 | rgb: 使用 RGB 颜色模式,而不是 BGR 167 | invert: 反转颜色 168 | """ 169 | self.width = width 170 | self.height = height 171 | self.x_start = 0 172 | self.y_start = 0 173 | self.spi = spi 174 | self.res = Pin(res, Pin.OUT, Pin.PULL_DOWN) 175 | self.dc = Pin(dc, Pin.OUT, Pin.PULL_DOWN) 176 | if cs is None: 177 | self.cs = int 178 | else: 179 | self.cs = Pin(cs, Pin.OUT, Pin.PULL_DOWN) 180 | if bl is not None: 181 | self.bl = PWM(Pin(bl, Pin.OUT)) 182 | self.back_light(255) 183 | else: 184 | self.bl = None 185 | self._rotate = rotate 186 | self._rgb = rgb 187 | self.hard_reset() 188 | self.soft_reset() 189 | self.poweron() 190 | # 191 | sleep_us(300) 192 | self._write(FRMCTR1, bytearray([0x01, 0x2C, 0x2D])) 193 | self._write(FRMCTR2, bytearray([0x01, 0x2C, 0x2D])) 194 | self._write(FRMCTR3, bytearray([0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D])) 195 | sleep_us(10) 196 | self._write(INVCTR, bytearray([0x07])) 197 | self._write(PWCTR1, bytearray([0xA2, 0x02, 0x84])) 198 | self._write(PWCTR2, bytearray([0xC5])) 199 | self._write(PWCTR3, bytearray([0x0A, 0x00])) 200 | self._write(PWCTR4, bytearray([0x8A, 0x2A])) 201 | self._write(PWCTR5, bytearray([0x8A, 0xEE])) 202 | self._write(VMCTR1, bytearray([0x0E])) 203 | # 204 | self._set_color_mode(COLOR_MODE_65K | COLOR_MODE_16BIT) 205 | sleep_ms(50) 206 | gc.collect() # 垃圾收集 207 | self.buffer = bytearray(self.height * self.width * 2) 208 | self.rotate(self._rotate) 209 | self.invert(invert) 210 | sleep_ms(10) 211 | self._write(ST7789_NORON) 212 | sleep_ms(10) 213 | self._write(ST7789_DISPON) 214 | sleep_ms(100) 215 | # super().__init__(self.buffer, self.width, self.height, framebuf.RGB565) 216 | self.clear() 217 | self.show() 218 | 219 | def _write(self, command=None, data=None): 220 | """SPI write to the device: commands and data.""" 221 | self.cs(0) 222 | if command is not None: 223 | self.dc(0) 224 | self.spi.write(bytes([command])) 225 | if data is not None: 226 | self.dc(1) 227 | self.spi.write(data) 228 | self.cs(1) 229 | 230 | def write_cmd(self, cmd): 231 | """ 232 | 写命令 233 | 234 | Args: 235 | cmd: 命令内容 236 | """ 237 | self.cs(0) 238 | self.dc(0) 239 | self.spi.write(bytes([cmd])) 240 | self.cs(1) 241 | 242 | def write_data(self, data): 243 | """ 244 | 写数据 245 | 246 | Args: 247 | data: 数据内容 248 | """ 249 | self.cs(0) 250 | self.dc(1) 251 | self.spi.write(data) 252 | self.cs(1) 253 | 254 | def hard_reset(self): 255 | """ 256 | Hard reset display. 257 | """ 258 | self.cs(0) 259 | self.res(1) 260 | sleep_ms(50) 261 | self.res(0) 262 | sleep_ms(50) 263 | self.res(1) 264 | sleep_ms(150) 265 | self.cs(1) 266 | 267 | def soft_reset(self): 268 | """ 269 | Soft reset display. 270 | """ 271 | self._write(ST7789_SWRESET) 272 | sleep_ms(150) 273 | 274 | def poweron(self): 275 | """Disable display sleep mode.""" 276 | self._write(ST7789_SLPOUT) 277 | 278 | def poweroff(self): 279 | """Enable display sleep mode.""" 280 | self._write(ST7789_SLPIN) 281 | 282 | def invert(self, value): 283 | """ 284 | Enable or disable display inversion mode. 285 | 286 | Args: 287 | value (bool): if True enable inversion mode. if False disable 288 | inversion mode 289 | """ 290 | if value: 291 | self._write(ST7789_INVON) 292 | else: 293 | self._write(ST7789_INVOFF) 294 | 295 | def _set_color_mode(self, mode): 296 | """ 297 | Set display color mode. 298 | 299 | Args: 300 | mode (int): color mode 301 | COLOR_MODE_65K, COLOR_MODE_262K, COLOR_MODE_12BIT, 302 | COLOR_MODE_16BIT, COLOR_MODE_18BIT, COLOR_MODE_16M 303 | """ 304 | self._write(ST7789_COLMOD, bytes([mode & 0x77])) 305 | 306 | def rotate(self, rotate): 307 | """ 308 | Set display rotation. 309 | 310 | Args: 311 | rotate (int): 312 | - 0-Portrait 313 | - 1-Landscape 314 | - 2-Inverted Portrait 315 | - 3-Inverted Landscape 316 | - 4-Upper right printing left (backwards) (X Flip) 317 | - 5-Upper left printing down (backwards) (Vertical flip) 318 | - 6-Lower left printing right (backwards) (Y Flip) 319 | """ 320 | self._rotate = rotate 321 | madctl = ROTATIONS[rotate] 322 | if self.width == 320: 323 | table = WIDTH_320 324 | elif self.width == 240: 325 | table = WIDTH_240 326 | elif self.width == 135: 327 | table = WIDTH_135 328 | else: 329 | raise ValueError( 330 | "Unsupported display. 320x240, 240x240 and 135x240 are supported." 331 | ) 332 | 333 | self.width, self.height, self.x_start, self.y_start = table[rotate] 334 | super().__init__(self.buffer, self.width, self.height, framebuf.RGB565, self.width) 335 | self._write(ST7789_MADCTL, bytes([madctl | (0x00 if self._rgb else 0x08)])) 336 | 337 | def _set_columns(self, start, end): 338 | """ 339 | Send CASET (column address set) command to display. 340 | 341 | Args: 342 | start (int): column start address 343 | end (int): column end address 344 | """ 345 | if start <= end <= self.width: 346 | self._write(ST7789_CASET, _encode_pos( 347 | start + self.x_start, end + self.x_start)) 348 | 349 | def _set_rows(self, start, end): 350 | """ 351 | Send RASET (row address set) command to display. 352 | 353 | Args: 354 | start (int): row start address 355 | end (int): row end address 356 | """ 357 | if start <= end <= self.height: 358 | self._write(ST7789_RASET, _encode_pos( 359 | start + self.y_start, end + self.y_start)) 360 | 361 | def set_window(self, x0, y0, x1, y1): 362 | """ 363 | Set window to column and row address. 364 | 365 | Args: 366 | x0 (int): column start address 367 | y0 (int): row start address 368 | x1 (int): column end address 369 | y1 (int): row end address 370 | """ 371 | if x0 < self.width and y0 < self.height: 372 | self._set_columns(x0, x1) 373 | self._set_rows(y0, y1) 374 | self._write(ST7789_RAMWR) 375 | 376 | def clear(self): 377 | """ 378 | 清屏 379 | """ 380 | self.fill(0) 381 | 382 | def show(self): 383 | """ 384 | 将帧缓冲区数据发送到屏幕 385 | """ 386 | self.set_window(0, 0, self.width - 1, self.height - 1) # 如果没有这行就会偏移 387 | self.write_data(self.buffer) 388 | 389 | def rgb(self, enable: bool): 390 | """ 391 | 设置颜色模式 392 | 393 | Args: 394 | enable: RGB else BGR 395 | """ 396 | self._rgb = enable 397 | self._write(ST7789_MADCTL, bytes([ROTATIONS[self._rotate] | (0x00 if self._rgb else 0x08)])) 398 | 399 | @staticmethod 400 | def color(r, g, b): 401 | """ 402 | Convert red, green and blue values (0-255) into a 16-bit 565 encoding. 403 | """ 404 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 405 | 406 | def back_light(self, value): 407 | """ 408 | 背光调节 409 | 410 | Args: 411 | value: 背光等级 0 ~ 255 412 | """ 413 | self.bl.freq(1000) 414 | if value >= 0xff: 415 | value = 0xff 416 | data = value * 0xffff >> 8 417 | self.bl.duty_u16(data) 418 | 419 | def circle(self, x, y, radius, c, section=100): 420 | """ 421 | 画圆 422 | 423 | Args: 424 | c: 颜色 425 | x: 中心 x 坐标 426 | y: 中心 y 坐标 427 | radius: 半径 428 | section: 分段 429 | """ 430 | arr = [] 431 | for m in range(section + 1): 432 | _x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + x) 433 | _y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + y) 434 | arr.append([_x, _y]) 435 | for i in range(len(arr) - 1): 436 | self.line(*arr[i], *arr[i + 1], c) 437 | 438 | def fill_circle(self, x, y, radius, c): 439 | """ 440 | 画填充圆 441 | 442 | Args: 443 | c: 颜色 444 | x: 中心 x 坐标 445 | y: 中心 y 坐标 446 | radius: 半径 447 | """ 448 | rsq = radius * radius 449 | for _x in range(radius): 450 | _y = int(math.sqrt(rsq - _x * _x)) # 计算 y 坐标 451 | y0 = y - _y 452 | end_y = y0 + _y * 2 453 | y0 = max(0, min(y0, self.height)) # 将 y0 限制在画布的范围内 454 | length = abs(end_y - y0) + 1 455 | self.vline(x + _x, y0, length, c) # 绘制左右两侧的垂直线 456 | self.vline(x - _x, y0, length, c) 457 | 458 | # def image(self, file_name): 459 | # with open(file_name, "rb") as bmp: 460 | # for b in range(0, 80 * 160 * 2, 1024): 461 | # self.buffer[b:b + 1024] = bmp.read(1024) 462 | # self.show() 463 | -------------------------------------------------------------------------------- /driver/st7789_spi.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/driver/st7789_spi.mpy -------------------------------------------------------------------------------- /driver/st7789_spi.py: -------------------------------------------------------------------------------- 1 | # 适用于 ST7789 的 SPI 直接驱动 2 | # Github: https://github.com/funnygeeker/micropython-easydisplay 3 | # Author: funnygeeker 4 | # Licence: MIT 5 | # Date: 2023/11/30 6 | # 7 | # 参考资料: 8 | # https://github.com/russhughes/st7789py_mpy/ 9 | # https://github.com/AntonVanke/MicroPython-uFont 10 | # https://github.com/cheungbx/st7735-esp8266-micropython/blob/master/st7735.py 11 | import math 12 | from struct import pack 13 | from machine import Pin, PWM 14 | from micropython import const 15 | from time import sleep_us, sleep_ms 16 | 17 | # commands 18 | ST7789_NOP = const(0x00) 19 | ST7789_SWRESET = const(0x01) 20 | ST7789_RDDID = const(0x04) 21 | ST7789_RDDST = const(0x09) 22 | 23 | ST7789_SLPIN = const(0x10) 24 | ST7789_SLPOUT = const(0x11) 25 | ST7789_PTLON = const(0x12) 26 | ST7789_NORON = const(0x13) 27 | 28 | ST7789_INVOFF = const(0x20) 29 | ST7789_INVON = const(0x21) 30 | ST7789_DISPOFF = const(0x28) 31 | ST7789_DISPON = const(0x29) 32 | ST7789_CASET = const(0x2A) 33 | ST7789_RASET = const(0x2B) 34 | ST7789_RAMWR = const(0x2C) 35 | ST7789_RAMRD = const(0x2E) 36 | 37 | ST7789_PTLAR = const(0x30) 38 | ST7789_VSCRDEF = const(0x33) 39 | ST7789_COLMOD = const(0x3A) 40 | ST7789_MADCTL = const(0x36) 41 | ST7789_VSCSAD = const(0x37) 42 | 43 | ST7789_MADCTL_MY = const(0x80) 44 | ST7789_MADCTL_MX = const(0x40) 45 | ST7789_MADCTL_MV = const(0x20) 46 | ST7789_MADCTL_ML = const(0x10) 47 | ST7789_MADCTL_BGR = const(0x08) 48 | ST7789_MADCTL_MH = const(0x04) 49 | ST7789_MADCTL_RGB = const(0x00) 50 | 51 | ST7789_RDID1 = const(0xDA) 52 | ST7789_RDID2 = const(0xDB) 53 | ST7789_RDID3 = const(0xDC) 54 | ST7789_RDID4 = const(0xDD) 55 | 56 | COLOR_MODE_65K = const(0x50) 57 | COLOR_MODE_262K = const(0x60) 58 | COLOR_MODE_12BIT = const(0x03) 59 | COLOR_MODE_16BIT = const(0x05) 60 | COLOR_MODE_18BIT = const(0x06) 61 | COLOR_MODE_16M = const(0x07) 62 | # 63 | FRMCTR1 = const(0xB1) 64 | FRMCTR2 = const(0xB2) 65 | FRMCTR3 = const(0xB3) 66 | 67 | INVCTR = const(0xB4) 68 | 69 | PWCTR1 = const(0xC0) 70 | PWCTR2 = const(0xC1) 71 | PWCTR3 = const(0xC2) 72 | PWCTR4 = const(0xC3) 73 | PWCTR5 = const(0xC4) 74 | VMCTR1 = const(0xC5) 75 | 76 | # Color definitions 77 | BLACK = const(0x0000) 78 | BLUE = const(0x001F) 79 | RED = const(0xF800) 80 | GREEN = const(0x07E0) 81 | CYAN = const(0x07FF) 82 | MAGENTA = const(0xF81F) 83 | YELLOW = const(0xFFE0) 84 | WHITE = const(0xFFFF) 85 | 86 | _ENCODE_PIXEL = ">H" 87 | _ENCODE_POS = ">HH" 88 | _DECODE_PIXEL = ">BBB" 89 | 90 | _BUFFER_SIZE = const(256) 91 | 92 | _BIT7 = const(0x80) 93 | _BIT6 = const(0x40) 94 | _BIT5 = const(0x20) 95 | _BIT4 = const(0x10) 96 | _BIT3 = const(0x08) 97 | _BIT2 = const(0x04) 98 | _BIT1 = const(0x02) 99 | _BIT0 = const(0x01) 100 | 101 | # Rotation tables (width, height, xstart, ystart) 102 | 103 | WIDTH_320 = [(240, 320, 0, 0), 104 | (320, 240, 0, 0), 105 | (240, 320, 0, 0), 106 | (320, 240, 0, 0), 107 | (240, 320, 0, 0), 108 | (320, 240, 0, 0), 109 | (240, 320, 0, 0)] # 320x240 110 | WIDTH_240 = [(240, 240, 0, 0), 111 | (240, 240, 0, 0), 112 | (240, 240, 80, 0), 113 | (240, 240, 0, 80), 114 | (240, 240, 0, 0), 115 | (240, 240, 0, 0), 116 | (240, 240, 80, 0)] # 240x240 117 | WIDTH_135 = [(135, 240, 52, 40), 118 | (240, 135, 40, 53), 119 | (135, 240, 53, 40), 120 | (240, 135, 40, 52), 121 | (135, 240, 52, 40), 122 | (240, 135, 40, 53), 123 | (135, 240, 53, 40)] # 135x240 124 | # on MADCTL to control display rotation/color layout 125 | # Looking at display with pins on top. 126 | # 00 = upper left printing right 127 | # 10 = does nothing (MADCTL_ML) 128 | # 40 = upper right printing left (backwards) (X Flip) 129 | # 20 = upper left printing down (backwards) (Vertical flip) 130 | # 80 = lower left printing right (backwards) (Y Flip) 131 | # 04 = (MADCTL_MH) 132 | 133 | # 60 = 90 right rotation 134 | # C0 = 180 right rotation 135 | # A0 = 270 right rotation 136 | ROTATIONS = [0x00, 0x60, 0xC0, 0xA0, 0x40, 0x20, 0x80] # 旋转方向 137 | 138 | 139 | def _encode_pos(x, y): 140 | """Encode a postion into bytes.""" 141 | return pack(_ENCODE_POS, x, y) 142 | 143 | 144 | def _encode_pixel(c): 145 | """Encode a pixel color into bytes.""" 146 | return pack(_ENCODE_PIXEL, c) 147 | 148 | 149 | class ST7789: 150 | def __init__(self, width: int, height: int, spi, res: int, dc: int, 151 | cs: int = None, bl: int = None, rotate: int = 0, rgb: bool = True, invert: bool = True): 152 | """ 153 | 初始化屏幕驱动 154 | 155 | Args: 156 | width: 宽度 157 | height: 高度 158 | spi: SPI 实例 159 | res: RESET 引脚 160 | dc: Data / Command 引脚 161 | cs: 片选引脚 162 | bl: 背光引脚 163 | rotate: 旋转图像,数值为 0-6 164 | rgb: 使用 RGB 颜色模式,而不是 BGR 165 | invert: 反转颜色 166 | """ 167 | self.width = width 168 | self.height = height 169 | self.x_start = 0 170 | self.y_start = 0 171 | self.spi = spi 172 | self.res = Pin(res, Pin.OUT, Pin.PULL_DOWN) 173 | self.dc = Pin(dc, Pin.OUT, Pin.PULL_DOWN) 174 | if cs is None: 175 | self.cs = int 176 | else: 177 | self.cs = Pin(cs, Pin.OUT, Pin.PULL_DOWN) 178 | if bl is not None: 179 | self.bl = PWM(Pin(bl, Pin.OUT)) 180 | self.back_light(255) 181 | else: 182 | self.bl = None 183 | self._rotate = rotate 184 | self._rgb = rgb 185 | self.hard_reset() 186 | self.soft_reset() 187 | self.poweron() 188 | # 189 | sleep_us(300) 190 | self._write(FRMCTR1, bytearray([0x01, 0x2C, 0x2D])) 191 | self._write(FRMCTR2, bytearray([0x01, 0x2C, 0x2D])) 192 | self._write(FRMCTR3, bytearray([0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D])) 193 | sleep_us(10) 194 | self._write(INVCTR, bytearray([0x07])) 195 | self._write(PWCTR1, bytearray([0xA2, 0x02, 0x84])) 196 | self._write(PWCTR2, bytearray([0xC5])) 197 | self._write(PWCTR3, bytearray([0x0A, 0x00])) 198 | self._write(PWCTR4, bytearray([0x8A, 0x2A])) 199 | self._write(PWCTR5, bytearray([0x8A, 0xEE])) 200 | self._write(VMCTR1, bytearray([0x0E])) 201 | # 202 | self._set_color_mode(COLOR_MODE_65K | COLOR_MODE_16BIT) 203 | sleep_ms(50) 204 | self.rotate(self._rotate) 205 | self.invert(invert) 206 | sleep_ms(10) 207 | self._write(ST7789_NORON) 208 | sleep_ms(10) 209 | self._write(ST7789_DISPON) 210 | sleep_ms(100) 211 | self.clear() 212 | 213 | def _write(self, command=None, data=None): 214 | """SPI write to the device: commands and data.""" 215 | self.cs(0) 216 | if command is not None: 217 | self.dc(0) 218 | self.spi.write(bytes([command])) 219 | if data is not None: 220 | self.dc(1) 221 | self.spi.write(data) 222 | self.cs(1) 223 | 224 | def write_cmd(self, cmd): 225 | """ 226 | 写命令 227 | 228 | Args: 229 | cmd: 命令内容 230 | """ 231 | self.cs(0) 232 | self.dc(0) 233 | self.spi.write(bytes([cmd])) 234 | self.cs(1) 235 | 236 | def write_data(self, data): 237 | """ 238 | 写数据 239 | 240 | Args: 241 | data: 数据内容 242 | """ 243 | self.cs(0) 244 | self.dc(1) 245 | self.spi.write(data) 246 | self.cs(1) 247 | 248 | def hard_reset(self): 249 | """ 250 | Hard reset display. 251 | """ 252 | self.cs(0) 253 | self.res(1) 254 | sleep_ms(50) 255 | self.res(0) 256 | sleep_ms(50) 257 | self.res(1) 258 | sleep_ms(150) 259 | self.cs(1) 260 | 261 | def soft_reset(self): 262 | """ 263 | Soft reset display. 264 | """ 265 | self._write(ST7789_SWRESET) 266 | sleep_ms(150) 267 | 268 | def poweron(self): 269 | """Disable display sleep mode.""" 270 | self._write(ST7789_SLPOUT) 271 | 272 | def poweroff(self): 273 | """Enable display sleep mode.""" 274 | self._write(ST7789_SLPIN) 275 | 276 | def invert(self, value): 277 | """ 278 | Enable or disable display inversion mode. 279 | 280 | Args: 281 | value (bool): if True enable inversion mode. if False disable 282 | inversion mode 283 | """ 284 | if value: 285 | self._write(ST7789_INVON) 286 | else: 287 | self._write(ST7789_INVOFF) 288 | 289 | def _set_color_mode(self, mode): 290 | """ 291 | Set display color mode. 292 | 293 | Args: 294 | mode (int): color mode 295 | COLOR_MODE_65K, COLOR_MODE_262K, COLOR_MODE_12BIT, 296 | COLOR_MODE_16BIT, COLOR_MODE_18BIT, COLOR_MODE_16M 297 | """ 298 | self._write(ST7789_COLMOD, bytes([mode & 0x77])) 299 | 300 | def rotate(self, rotate): 301 | """ 302 | Set display rotation. 303 | 304 | Args: 305 | rotate (int): 306 | - 0-Portrait 307 | - 1-Landscape 308 | - 2-Inverted Portrait 309 | - 3-Inverted Landscape 310 | - 4-Upper right printing left (backwards) (X Flip) 311 | - 5-Upper left printing down (backwards) (Vertical flip) 312 | - 6-Lower left printing right (backwards) (Y Flip) 313 | """ 314 | self._rotate = rotate 315 | madctl = ROTATIONS[rotate] 316 | if self.width == 320: 317 | table = WIDTH_320 318 | elif self.width == 240: 319 | table = WIDTH_240 320 | elif self.width == 135: 321 | table = WIDTH_135 322 | else: 323 | raise ValueError( 324 | "Unsupported display. 320x240, 240x240 and 135x240 are supported." 325 | ) 326 | 327 | self.width, self.height, self.x_start, self.y_start = table[rotate] 328 | self._write(ST7789_MADCTL, bytes([madctl | (0x00 if self._rgb else 0x08)])) 329 | 330 | def _set_columns(self, start, end): 331 | """ 332 | Send CASET (column address set) command to display. 333 | 334 | Args: 335 | start (int): column start address 336 | end (int): column end address 337 | """ 338 | if start <= end <= self.width: 339 | self._write(ST7789_CASET, _encode_pos( 340 | start + self.x_start, end + self.x_start)) 341 | 342 | def _set_rows(self, start, end): 343 | """ 344 | Send RASET (row address set) command to display. 345 | 346 | Args: 347 | start (int): row start address 348 | end (int): row end address 349 | """ 350 | if start <= end <= self.height: 351 | self._write(ST7789_RASET, _encode_pos( 352 | start + self.y_start, end + self.y_start)) 353 | 354 | def set_window(self, x0, y0, x1, y1): 355 | """ 356 | Set window to column and row address. 357 | 358 | Args: 359 | x0 (int): column start address 360 | y0 (int): row start address 361 | x1 (int): column end address 362 | y1 (int): row end address 363 | """ 364 | if x0 < self.width and y0 < self.height: 365 | self._set_columns(x0, x1) 366 | self._set_rows(y0, y1) 367 | self._write(ST7789_RAMWR) 368 | 369 | def clear(self): 370 | """ 371 | 清屏 372 | """ 373 | self.fill(0) 374 | 375 | def show(self): 376 | """ 377 | 将帧缓冲区数据发送到屏幕 378 | """ 379 | pass # 无帧缓冲区 380 | 381 | def rgb(self, enable: bool): 382 | """ 383 | 设置颜色模式 384 | 385 | Args: 386 | enable: RGB else BGR 387 | """ 388 | self._rgb = enable 389 | self._write(ST7789_MADCTL, bytes([ROTATIONS[self._rotate] | (0x00 if self._rgb else 0x08)])) 390 | 391 | @staticmethod 392 | def color(r, g, b): 393 | """ 394 | Convert red, green and blue values (0-255) into a 16-bit 565 encoding. 395 | """ 396 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 397 | 398 | def back_light(self, value): 399 | """ 400 | 背光调节 401 | 402 | Args: 403 | value: 背光等级 0 ~ 255 404 | """ 405 | self.bl.freq(1000) 406 | if value >= 0xff: 407 | value = 0xff 408 | data = value * 0xffff >> 8 409 | self.bl.duty_u16(data) 410 | 411 | def vline(self, x, y, h, c): 412 | """ 413 | Draw vertical line at the given location and color. 414 | 415 | Args: 416 | x (int): x coordinate 417 | y (int): y coordinate 418 | h (int): length of line 419 | c (int): 565 encoded color 420 | """ 421 | self.fill_rect(x, y, 1, h, c) 422 | 423 | def hline(self, x, y, w, c): 424 | """ 425 | Draw horizontal line at the given location and color. 426 | 427 | Args: 428 | x (int): x coordinate 429 | y (int): y coordinate 430 | w (int): length of line 431 | c (int): 565 encoded color 432 | """ 433 | self.fill_rect(x, y, w, 1, c) 434 | 435 | def pixel(self, x, y, c: int = None): 436 | """ 437 | Draw a pixel at the given location and color. 438 | 439 | Args: 440 | x (int): x coordinate 441 | y (int): y coordinate 442 | c (int): 565 encoded color 443 | """ 444 | self.set_window(x, y, x, y) 445 | self._write(None, _encode_pixel(c)) 446 | 447 | def blit_buffer(self, buffer, x, y, width, height): 448 | """ 449 | Copy buffer to display at the given location. 450 | 451 | Args: 452 | buffer (bytes): Data to copy to display 453 | x (int): Top left corner x coordinate 454 | y (int): Top left corner y coordinate 455 | width (int): Width 456 | height (int): Height 457 | """ 458 | self.set_window(x, y, x + width - 1, y + height - 1) 459 | self._write(None, buffer) 460 | 461 | def rect(self, x, y, w, h, c): 462 | """ 463 | Draw a rectangle at the given location, size and color. 464 | 465 | Args: 466 | x (int): Top left corner x coordinate 467 | y (int): Top left corner y coordinate 468 | w (int): Width in pixels 469 | h (int): Height in pixels 470 | c (int): 565 encoded color 471 | """ 472 | self.hline(x, y, w, c) 473 | self.vline(x, y, h, c) 474 | self.vline(x + w - 1, y, h, c) 475 | self.hline(x, y + h - 1, w, c) 476 | 477 | def fill_rect(self, x, y, w, h, c): 478 | """ 479 | Draw a rectangle at the given location, size and filled with color. 480 | 481 | Args: 482 | x (int): Top left corner x coordinate 483 | y (int): Top left corner y coordinate 484 | w (int): Width in pixels 485 | h (int): Height in pixels 486 | c (int): 565 encoded color 487 | """ 488 | self.set_window(x, y, x + w - 1, y + h - 1) 489 | chunks, rest = divmod(w * h, _BUFFER_SIZE) 490 | pixel = _encode_pixel(c) 491 | self.dc(1) 492 | if chunks: 493 | data = pixel * _BUFFER_SIZE 494 | for _ in range(chunks): 495 | self._write(None, data) 496 | if rest: 497 | self._write(None, pixel * rest) 498 | 499 | def fill(self, c): 500 | """ 501 | Fill the entire FrameBuffer with the specified color. 502 | 503 | Args: 504 | c (int): 565 encoded color 505 | """ 506 | self.fill_rect(0, 0, self.width, self.height, c) 507 | 508 | def line(self, x1, y1, x2, y2, c): 509 | """ 510 | Draw a single pixel wide line starting at x0, y0 and ending at x1, y1. 511 | 512 | Args: 513 | x1 (int): Start point x coordinate 514 | y1 (int): Start point y coordinate 515 | x2 (int): End point x coordinate 516 | y2 (int): End point y coordinate 517 | c (int): 565 encoded color 518 | """ 519 | steep = abs(y2 - y1) > abs(x2 - x1) 520 | if steep: 521 | x1, y1 = y1, x1 522 | x2, y2 = y2, x2 523 | if x1 > x2: 524 | x1, x2 = x2, x1 525 | y1, y2 = y2, y1 526 | dx = x2 - x1 527 | dy = abs(y2 - y1) 528 | err = dx // 2 529 | ystep = 1 if y1 < y2 else -1 530 | while x1 <= x2: 531 | if steep: 532 | self.pixel(y1, x1, c) 533 | else: 534 | self.pixel(x1, y1, c) 535 | err -= dy 536 | if err < 0: 537 | y1 += ystep 538 | err += dx 539 | x1 += 1 540 | 541 | def circle(self, x, y, radius, c, section=100): 542 | """ 543 | 画圆 544 | 545 | Args: 546 | c: 颜色 547 | x: 中心 x 坐标 548 | y: 中心 y 坐标 549 | radius: 半径 550 | section: 分段 551 | """ 552 | arr = [] 553 | for m in range(section + 1): 554 | _x = round(radius * math.cos((2 * math.pi / section) * m - math.pi) + x) 555 | _y = round(radius * math.sin((2 * math.pi / section) * m - math.pi) + y) 556 | arr.append([_x, _y]) 557 | for i in range(len(arr) - 1): 558 | self.line(*arr[i], *arr[i + 1], c) 559 | 560 | def fill_circle(self, x, y, radius, c): 561 | """ 562 | 画填充圆 563 | 564 | Args: 565 | c: 颜色 566 | x: 中心 x 坐标 567 | y: 中心 y 坐标 568 | radius: 半径 569 | """ 570 | rsq = radius * radius 571 | for _x in range(radius): 572 | _y = int(math.sqrt(rsq - _x * _x)) # 计算 y 坐标 573 | y0 = y - _y 574 | end_y = y0 + _y * 2 575 | y0 = max(0, min(y0, self.height)) # 将 y0 限制在画布的范围内 576 | length = abs(end_y - y0) + 1 577 | self.vline(x + _x, y0, length, c) # 绘制左右两侧的垂直线 578 | self.vline(x - _x, y0, length, c) 579 | 580 | # def image(self, file_name): 581 | # with open(file_name, "rb") as bmp: 582 | # for b in range(0, 80 * 160 * 2, 1024): 583 | # self.buffer[b:b + 1024] = bmp.read(1024) 584 | # self.show() 585 | -------------------------------------------------------------------------------- /font/MicroPython-uFont-Tools/README.md: -------------------------------------------------------------------------------- 1 | ### MicroPython BitMap Tools 2 | 3 | 这里的文件只是作为备份,原Github仓库:[MicroPython-uFont-Tools](https://github.com/AntonVanke/MicroPython-uFont-Tools) 4 | 5 | 6 | This file here is just for backup. The original GitHub repository is: [MicroPython-uFont-Tools](https://github.com/AntonVanke/MicroPython-uFont-Tools) -------------------------------------------------------------------------------- /font/MicroPython-uFont-Tools/bitmapfonts.py: -------------------------------------------------------------------------------- 1 | __version__ = 3 2 | """ 3 | usage: bitmapfonts.py [-h] [-v] -ff FONT_FILE [-tf TEXT_FILE | -t TEXT] [-fs FONT_SIZE] [-o [OFFSET ...]] [-bfn BITMAP_FONT_NAME] 4 | 5 | 生成点阵字体文件 6 | 7 | https://github.com/AntonVanke/MicroPython-Chinese-Font 8 | 9 | options: 10 | -h, --help show this help message and exit 11 | -v, --version 显示生成的点阵字体版本 12 | -ff FONT_FILE, --font-file FONT_FILE 13 | 字体文件 14 | -tf TEXT_FILE, --text-file TEXT_FILE 15 | 文字字符集文件 16 | -t TEXT, --text TEXT 文字字符集 17 | -fs FONT_SIZE, --font-size FONT_SIZE 18 | 生成字体字号 19 | -o [OFFSET ...], --offset [OFFSET ...] 20 | 生成字体偏移 21 | -bfn BITMAP_FONT_NAME, --bitmap-font-name BITMAP_FONT_NAME 22 | 生成的点阵字体文件名称 23 | example: 24 | python bitmapfonts.py -ff unifont-14.0.04.ttf -tf text.txt -fs 16 -o 0 0 -bfn example.bmf 25 | """ 26 | import sys 27 | import struct 28 | import argparse 29 | 30 | try: 31 | import numpy as np 32 | from PIL import ImageFont, ImageDraw, Image 33 | except ImportError as err: 34 | print(err, "尝试运行 `python -m pip install requirements.txt`") 35 | exit() 36 | 37 | 38 | def get_im(word, width, height, font, offset: tuple = (0, 0)) -> Image.Image: 39 | """获取生成的图像 40 | 41 | Args: 42 | word: 字 43 | width: 宽度 44 | height: 高度 45 | font: 字体 46 | offset: 偏移 47 | 48 | Returns: 49 | PIL.Image.Image 50 | """ 51 | im = Image.new('1', (width, height), (1,)) 52 | draw = ImageDraw.Draw(im) 53 | draw.text(offset, word, font=font) 54 | return im 55 | 56 | 57 | def to_bitmap(word: str, font_size: int, font, offset=(0, 0)) -> bytearray: 58 | """ 获取点阵字节数据 59 | 60 | Args: 61 | word: 字 62 | font_size: 字号 63 | font: 字体 64 | offset: 偏移 65 | 66 | Returns: 67 | 字节数据 68 | """ 69 | code = 0x00 70 | data_code = word.encode("utf-8") 71 | 72 | # 获取字节码 73 | try: 74 | for byte in range(len(data_code)): 75 | code |= data_code[byte] << (len(data_code) - byte - 1) * 8 76 | except IndexError: 77 | print(word, word.encode("utf-8")) 78 | 79 | # 获取点阵图 80 | bp = np.pad( 81 | (~np.asarray(get_im(word, width=font_size, height=font_size, font=font, offset=offset))).astype(np.int32), 82 | ((0, 0), (0, int(np.ceil(font_size / 8) * 8 - font_size))), 'constant', 83 | constant_values=(0, 0)) 84 | 85 | # 点阵映射 MONO_HLSB 86 | bmf = [] 87 | for line in bp.reshape((-1, 8)): 88 | v = 0x00 89 | for _ in line: 90 | v = (v << 1) + _ 91 | bmf.append(v) 92 | return bytearray(bmf) 93 | 94 | 95 | def get_unicode(word) -> bytes: 96 | """返回 Unicode 编码 97 | 98 | Args: 99 | word: 100 | 101 | Returns: 102 | 103 | """ 104 | o = ord(word) 105 | if o > 65535: 106 | o = 65311 # ord("?") 107 | return struct.pack(">H", o) 108 | 109 | 110 | def run(font_file, font_size=16, offset=(0, 0), text_file=None, text=None, bitmap_fonts_name=None, 111 | bitmap_fonts_file=None): 112 | # 生成的点阵字体文件 113 | 114 | font = ImageFont.truetype(font=font_file, size=font_size) 115 | 116 | if text: 117 | words = list(set(list(text))) 118 | else: 119 | words = list(set(list(open(text_file, encoding="utf-8").read()))) 120 | words.sort() 121 | font_num = len(words) 122 | # print(args) 123 | bitmap_fonts_name = bitmap_fonts_name or font_file.split('.')[0] + "-" + str(font_num) + "-" + str( 124 | font_size) + f".v{__version__}.bmf" 125 | bitmap_fonts = bitmap_fonts_file or open(bitmap_fonts_name, "wb") 126 | print(f"正在生成点阵字体文件,字体数量{font_num}:") 127 | # 字节记录占位 128 | bitmap_fonts.write(bytearray([ 129 | 66, 77, # 标记 130 | 3, # 版本 131 | 0, # 映射方式 132 | 0, 0, 0, # 位图开始字节 133 | font_size, # 字号 134 | int(np.ceil(font_size / 8)) * font_size, # 每个字占用的大小 135 | 0, 0, 0, 0, 0, 0, 0 # 兼容项 136 | ])) 137 | 138 | for w in words: 139 | bitmap_fonts.write(get_unicode(w)) 140 | 141 | # 位图开始字节 142 | start_bitmap = bitmap_fonts.tell() 143 | print("\t位图起始字节:", hex(start_bitmap)) 144 | for w in words: 145 | bitmap_fonts.write(to_bitmap(w, font_size, font, offset=offset)) 146 | file_size = bitmap_fonts.tell() 147 | print(f"\t文件大小:{file_size / 1024:.4f}KByte") 148 | bitmap_fonts.seek(4, 0) 149 | bitmap_fonts.write(struct.pack(">i", start_bitmap)[1:4]) 150 | print(f"生成成功,文件名称:{bitmap_fonts_name}") 151 | return font_num, start_bitmap, file_size, bitmap_fonts_name, font_size, offset 152 | 153 | 154 | if __name__ == '__main__': 155 | parser = argparse.ArgumentParser(description="生成点阵字体文") 156 | group = parser.add_mutually_exclusive_group() 157 | parser.add_argument('-v', '--version', action='version', 158 | version=f'点阵字体版本 : {__version__}', 159 | help='显示生成的点阵字体版本') 160 | parser.add_argument("-ff", "--font-file", help="字体文件", type=str, required=True) 161 | group.add_argument("-tf", "--text-file", help="文字字符集文件", type=str, default="text.txt") 162 | group.add_argument("-t", "--text", help="文字字符集", type=str) 163 | parser.add_argument("-fs", "--font-size", help="生成字体字号", default=16, type=int) 164 | parser.add_argument("-o", "--offset", nargs="*", help="生成字体偏移", type=int, default=[0, 0]) 165 | parser.add_argument("-bfn", "--bitmap-font-name", help="生成的点阵字体文件名称", type=str) 166 | args = parser.parse_args() 167 | run(font_file=args.font_file, font_size=args.font_size, offset=args.offset, text_file=args.text_file, 168 | text=args.text, bitmap_fonts_name=args.bitmap_font_name) 169 | -------------------------------------------------------------------------------- /font/MicroPython-uFont-Tools/doc/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/MicroPython-uFont-Tools/doc/example.png -------------------------------------------------------------------------------- /font/MicroPython-uFont-Tools/doc/如何生成点阵字体文件.md: -------------------------------------------------------------------------------- 1 | ### 如何生成`ufont`使用的点阵字体? 2 | 3 | --- 4 | 5 | #### 最简单的方式是使用`BitMap Font Tools`生成字体 6 | 7 | example 8 | 9 | 1. 在项目文件夹下运行`pip install -r requirements.txt`安装环境完成后运行`python main.py`运行脚本 10 | 11 | > 如果没有`Python`环境,请下载打包好的软件 12 | > 13 | > [Releases · AntonVanke/MicroPython_BitMap_Tools (github.com)](https://github.com/AntonVanke/MicroPython_BitMap_Tools/releases) 14 | 15 | 2. 选择需要制作的字体 16 | 17 | 3. 选择字体集,或者在下方的字体预览输入字符 18 | 19 | 4. 根据右方预览调整偏移 20 | 21 | 5. 点击生成 22 | 23 | --- 24 | 25 | #### 如果不想使用简化的图形界面,可以通过命令行生成 26 | 27 | 需要`bitmapfonts.py`、字体文件、需要生成字符集 28 | 29 | ```shell 30 | usage: bitmapfonts.py [-h] [-v] -ff FONT_FILE [-tf TEXT_FILE | -t TEXT] [-fs FONT_SIZE] [-o [OFFSET ...]] [-bfn BITMAP_FONT_NAME] 31 | 32 | 生成点阵字体文件 33 | 34 | options: 35 | -h, --help show this help message and exit 36 | -v, --version 显示生成的点阵字体版本 37 | -ff FONT_FILE, --font-file FONT_FILE 38 | 字体文件 39 | -tf TEXT_FILE, --text-file TEXT_FILE 40 | 文字字符集文件 41 | -t TEXT, --text TEXT 文字字符集 42 | -fs FONT_SIZE, --font-size FONT_SIZE 43 | 生成字体字号 44 | -o [OFFSET ...], --offset [OFFSET ...] 45 | 生成字体偏移 46 | -bfn BITMAP_FONT_NAME, --bitmap-font-name BITMAP_FONT_NAME 47 | 生成的点阵字体文件名称 48 | ``` 49 | 50 | ##### 默认的生成命令 51 | 52 | ```shell 53 | python bitmapfonts.py -ff unifont-14.0.04.ttf 54 | ``` 55 | 56 | 会使用项目里的`unifont-14.0.04.ttf`和`text.txt`生成字体文件生成`unifont-14-12888-16.v3.bmf` 57 | 58 | ##### 自定义生成 59 | 60 | 1. 由于一般的项目用不到那么多的字符(`默认是12888个`),所以可以自行导入字符集文件生成。例如 61 | 62 | ```shell 63 | python bitmapfonts.py -ff unifont-14.0.04.ttf -tf example.txt 64 | ``` 65 | 66 | 那么你生成的将会是`example.txt`里所有字符的字体文件;当然你也可以将一个`小说txt`传入,脚本将自动采集所有的字符并生成文件。 67 | 68 | 2. 你也可以通过`-t ***`来指定文字集,但是文字较多就不建议使用。例如 69 | 70 | ```shell 71 | python bitmapfonts.py -ff unifont-14.0.04.ttf -t "你我他123456789" 72 | ``` 73 | 74 | 你将生成含有`"你我他123456789"`的字体文件 75 | 76 | ##### 指定生成点阵字体文件名 77 | 78 | ​ 默认的生成文件名是`字体文件名-字符数量-标准字号.点阵字体版本号.bmf`,可以通过`-bfn`来指定输出文件名。例如 79 | 80 | ```shell 81 | python bitmapfonts.py -ff unifont-14.0.04.ttf -bfn example.bmf 82 | ``` 83 | 84 | ##### 指定生成字号 85 | 86 | ​ 生成字号会很大程度上影响点阵字体的大小和加载速度,使用常用的字号生成有助于快速加载。默认的字号是`16`px,可以通过`-fs` 来指定生成的默认字号。例如 87 | 88 | ```shell 89 | python bitmapfonts.py -ff unifont-14.0.04.ttf -tf text.txt -fs 24 -bfn example.bmf 90 | ``` 91 | 92 | ##### 字体偏移 93 | 94 | ​ 一些字体会出现一些偏移使得最终生成的字体无法完美显示可以通过`-o [x轴偏移] [y轴偏移]`来指定偏移像素 95 | 96 | ```shell 97 | python bitmapfonts.py -ff unifont-14.0.04.ttf -tf text.txt -fs 24 -o 5 1 98 | ``` 99 | 100 | -------------------------------------------------------------------------------- /font/MicroPython-uFont-Tools/main.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter.filedialog import askopenfilename, asksaveasfile 3 | from tkinter.messagebox import showinfo, showerror 4 | 5 | from PIL import ImageTk, ImageDraw, Image, ImageFont 6 | 7 | from bitmapfonts import run 8 | 9 | 10 | def set_font_file(): 11 | """设置字体文件 12 | 13 | Returns: 14 | 15 | """ 16 | global font_file 17 | font_file = askopenfilename(title='选择字体文件', 18 | filetypes=[('Font File', '*.ttf'), ('Font File', '*.ttc'), ('Font File', '*.otf'), 19 | ('All Files', '*')], 20 | initialdir="./") 21 | font_file_show.set(font_file) 22 | get_image() 23 | 24 | 25 | def set_text_file(): 26 | """选择字体集文件 27 | 28 | Returns: 29 | 30 | """ 31 | global text_file, text 32 | text_file = askopenfilename(title='选择字符集文件', 33 | filetypes=[('文本文件', '*.txt'), ('All Files', '*')], initialdir="./") 34 | text_file_buf = open(text_file, "r", encoding="utf-8") 35 | text = text_file_buf.read() 36 | text_input.delete("1.0", tk.END) 37 | text_input.insert(tk.END, text) 38 | text_len.set(f'字符数量: {len(set(text))}') 39 | text_file_show.set(text_file) 40 | get_image() 41 | 42 | 43 | def save_file(): 44 | """生成点阵字体文件 45 | Returns: 46 | 47 | """ 48 | if font_size.get() == 0 or text == "" or font_file == "": 49 | showerror(title="生成失败", message="信息填写不完全!") 50 | return False 51 | bitmap_fonts_file = tk.filedialog.asksaveasfile(mode='wb', 52 | title="选择棋谱", 53 | initialdir="./", 54 | defaultextension=".bmf", 55 | filetypes=[("BitMap Font", ".bmf")]) 56 | if bitmap_fonts_file is None: 57 | return False 58 | data = run(font_file=font_file, font_size=font_size.get(), offset=(offset_x.get(), offset_y.get()), 59 | text=text, bitmap_fonts_file=bitmap_fonts_file) 60 | showinfo(title="生成成功", message=f"字体文件:{data[3]}\n字符数量:{data[0]}个\n字体大小:{data[2] / 1024:.2f}KByte\n字号大小:{data[4]}px") 61 | 62 | 63 | def text_input_update(_): 64 | global text 65 | text = text_input.get("1.0", tk.END)[:-1] 66 | text_len.set(f'字符数量: {len(set(text))}') 67 | 68 | 69 | def get_image(*args): 70 | global img 71 | if font_file == "": 72 | return False 73 | im = Image.new('1', (font_size.get(), font_size.get()), (1,)) 74 | draw = ImageDraw.Draw(im) 75 | if len(preview_text.get()) >= 1: 76 | estimated_size.set( 77 | f"预计大小:{(16 + len(set(text)) * font_size.get() ** 2 // 8 + len(set(text)) * 2) / 1024:.2f}KBytes") 78 | draw.text((offset_x.get(), offset_y.get()), preview_text.get()[0], 79 | font=ImageFont.truetype(font=font_file, size=font_size.get())) 80 | img = ImageTk.BitmapImage(im) 81 | img_label = tk.Label(font_preview, bd=1, relief="sunken", image=img) 82 | img_label.place(x=95, y=100, width=50, height=50, anchor="center") 83 | return True 84 | return False 85 | 86 | 87 | root = tk.Tk() 88 | root.title("BitMap Font Tools") 89 | root.geometry("620x620") 90 | root.minsize(620, 620) 91 | root.maxsize(620, 620) 92 | 93 | font_file = "" 94 | text_file = "" 95 | text = "" 96 | 97 | estimated_size = tk.StringVar() 98 | font_size = tk.IntVar() 99 | font_file_show = tk.StringVar() 100 | text_file_show = tk.StringVar() 101 | offset_x = tk.IntVar() 102 | offset_y = tk.IntVar() 103 | font_size.set(16) 104 | 105 | # 106 | # 第一部分 107 | # 108 | setting_frame = tk.LabelFrame(root, text="参数设置") 109 | setting_frame.place(x=10, y=10, width=400, height=300) 110 | 111 | # 字体文件设置 112 | font_file_frame = tk.LabelFrame(setting_frame, text="字体选择") 113 | font_file_frame.pack() 114 | tk.Entry(font_file_frame, textvariable=font_file_show, width=30).grid(row=1, column=1) 115 | tk.Button(font_file_frame, text="选择文件", command=set_font_file).grid(row=1, column=2) 116 | 117 | # 字符集设置 118 | text_file_frame = tk.LabelFrame(setting_frame, text="字体集选择") 119 | text_file_frame.pack() 120 | tk.Entry(text_file_frame, textvariable=text_file_show, width=30).grid(row=1, column=1) 121 | tk.Button(text_file_frame, text="选择文件", command=set_text_file).grid(row=1, column=2) 122 | 123 | # 字号设置 124 | font_size_frame = tk.LabelFrame(setting_frame, text="字号") 125 | font_size_frame.pack() 126 | tk.Scale(font_size_frame, variable=font_size, from_=8, to=40, orient=tk.HORIZONTAL, length=280, command=get_image).grid( 127 | row=1, column=1) 128 | tk.Button(font_size_frame, text="重置大小", command=lambda: font_size.set(16) or get_image()).grid(row=1, column=2) 129 | 130 | # 偏移设置 131 | offset_frame = tk.LabelFrame(setting_frame, text="偏移") 132 | offset_frame.pack() 133 | tk.Label(offset_frame, text="x轴偏移").grid(row=1, column=1) 134 | tk.Scale(offset_frame, variable=offset_x, from_=-16, to=16, orient=tk.HORIZONTAL, length=230, command=get_image).grid( 135 | row=1, column=2) 136 | 137 | tk.Label(offset_frame, text="y轴偏移").grid(row=2, column=1) 138 | tk.Scale(offset_frame, variable=offset_y, from_=-16, to=16, orient=tk.HORIZONTAL, length=230, command=get_image).grid( 139 | row=2, column=2) 140 | tk.Button(offset_frame, text="重置偏移", command=lambda: offset_y.set(0) or offset_x.set(0) or get_image()).grid(row=2, 141 | column=3) 142 | 143 | # 144 | # 第二部分 145 | # 146 | text_len = tk.StringVar() 147 | text_len.set("字符数量: 0") 148 | 149 | text_frame = tk.LabelFrame(root, text="字体集预览") 150 | text_frame.place(x=10, y=310, width=400, height=300) 151 | 152 | tk.Label(text_frame, textvariable=text_len).pack(side=tk.BOTTOM) 153 | 154 | text_scroll = tk.Scrollbar(text_frame) 155 | text_scroll.pack(side=tk.RIGHT, fill=tk.Y) 156 | 157 | text_input = tk.Text(text_frame, wrap=tk.CHAR, undo=True, yscrollcommand=text_scroll.set, width=50) 158 | text_input.pack() 159 | 160 | text_scroll.config(command=text_input.yview) 161 | text_input.bind('', text_input_update) 162 | 163 | # 164 | # 第三部分 165 | # 166 | preview_text = tk.StringVar() 167 | preview_text.set("你") 168 | 169 | font_preview = tk.LabelFrame(root, text="预览") 170 | font_preview.place(x=420, y=10, width=190, height=200) 171 | preview_text_input = tk.Entry(font_preview, textvariable=preview_text, width=8) 172 | preview_text_input.bind("", get_image) 173 | preview_text_input.grid(row=1, column=1) 174 | tk.Button(font_preview, text="更新图像", command=get_image).grid(row=1, column=2) 175 | 176 | # 177 | # 第四部分 178 | # 179 | bmf_generate = tk.LabelFrame(root, text="生成") 180 | bmf_generate.place(x=420, y=220, width=190, height=390) 181 | tk.Label(bmf_generate, textvariable=estimated_size).grid(row=1, column=1) 182 | tk.Button(bmf_generate, text="生成点阵文件", command=save_file).grid(row=2, column=1) 183 | 184 | root.mainloop() 185 | -------------------------------------------------------------------------------- /font/MicroPython-uFont-Tools/requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow~=9.2.0 2 | numpy~=1.23.2 -------------------------------------------------------------------------------- /font/MicroPython-uFont-Tools/soft/bitmap_font_tools_20220827_mac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/MicroPython-uFont-Tools/soft/bitmap_font_tools_20220827_mac -------------------------------------------------------------------------------- /font/MicroPython-uFont-Tools/soft/bitmap_font_tools_20220827_windows.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/MicroPython-uFont-Tools/soft/bitmap_font_tools_20220827_windows.exe -------------------------------------------------------------------------------- /font/README.md: -------------------------------------------------------------------------------- 1 | ## 字体文件(Chinese) 2 | 3 | ### 文件格式 4 | - `.bmf` 文件均为本 `micropython-easydisplay` 项目的字体文件,包含了一些常见字体。 5 | - `.txt` 文件为字体文件生成时的原始字体集,一般包含了常见的中文,英文,日文,以及其他文字和符号。 6 | 7 | ### 说明 8 | - `text_full` 拥有完善的字体集和符号,可用于绝大部分使用场景,需要较大的存储空间 9 | - `text_lite` 拥有精简的字体集和符号,能够应对基本的使用场景,需要较小的存储空间 10 | 11 | ### 分辨率 12 | - 该版本字体提供了 `8px`,`16px`,`24px`,`36px` 像素的字体,请根据实际情况按需选择 13 | - 一般 `8px` 用于点阵屏(`MAX7219`),`16px` 用于低分辨率屏幕(`SSD1306` `ST7735`),`24px` 一般用于 (`ST7789`) 14 | ### 兼容性 15 | - 当前字体版本为 `V3` 版本,与 [MicroPython-uFont](https://github.com/AntonVanke/MicroPython-uFont) 项目通用 16 | 17 | ### TTF 字体文件 18 | - `8px`:[观致8px](https://www.maoken.com/freefonts/11358.html) 19 | - `16px` `24px` `32px`:[Unifont](https://unifoundry.com/) 20 | 21 | ### 制作 BMF 字体文件 22 | - [https://github.com/AntonVanke/MicroPython-uFont-Tools](https://github.com/AntonVanke/MicroPython-uFont-Tools) 23 | 24 | 25 | ## Font Files 26 | 27 | ### File Format 28 | - `.bmf` files are font files for the `micropython-easydisplay` project, containing various common fonts. 29 | - `.txt` files are the original font sets used to generate the font files and typically include common Chinese, English, Japanese, and other characters and symbols. 30 | 31 | ### Explanation 32 | - `text_full` has a complete font set and symbols, suitable for most scenarios but requires larger storage space. 33 | - `text_lite` has a streamlined font set and symbols, suitable for basic scenarios with smaller storage space requirements. 34 | 35 | ### Resolution 36 | - This version of the font provides fonts in pixel sizes of `8px`, `16px`, `24px`, and `36px`. Please select the appropriate size based on your needs. 37 | - Typically, `8px` is used for dot matrix screens (`MAX7219`), `16px` for low-resolution screens (`SSD1306` `ST7735`), and `24px` is commonly used for (`ST7789`). 38 | 39 | ### Compatibility 40 | - The current font version is `V3` and is compatible with the [MicroPython-uFont](https://github.com/AntonVanke/MicroPython-uFont) project. 41 | 42 | ### TTF Font File 43 | - `8px`:[观致8px](https://www.maoken.com/freefonts/11358.html) 44 | - `16px` `24px` `32px`:[Unifont](https://unifoundry.com/) 45 | 46 | ### Create BMF Font Files 47 | - [https://github.com/AntonVanke/MicroPython-uFont-Tools](https://github.com/AntonVanke/MicroPython-uFont-Tools) 48 | -------------------------------------------------------------------------------- /font/font_file/GuanZhi-8px.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/font_file/GuanZhi-8px.ttf -------------------------------------------------------------------------------- /font/font_file/unifont-15.1.04.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/font_file/unifont-15.1.04.otf -------------------------------------------------------------------------------- /font/font_set/text_full.txt: -------------------------------------------------------------------------------- 1 | 一乙二十丁厂七卜人入八九几儿了力乃刀又三于干亏士工土才寸下大丈与万上小口巾山千乞川亿个勺久凡及夕丸么广亡门义之尸弓己已子卫也女飞刃习叉马乡丰王井开夫天无元专云扎艺木五支厅不太犬区历尤友匹车巨牙屯比互切瓦止少日中冈贝内水见午牛手毛气升长仁什片仆化仇币仍仅斤爪反介父从今凶分乏公仓月氏勿欠风丹匀乌凤勾文六方火为斗忆订计户认心尺引丑巴孔队办以允予劝双书幻玉刊示末未击打巧正扑扒功扔去甘世古节本术可丙左厉右石布龙平灭轧东卡北占业旧帅归且旦目叶甲申叮电号田由史只央兄叼叫另叨叹四生失禾丘付仗代仙们仪白仔他斥瓜乎丛令用甩印乐句匆册犯外处冬鸟务包饥主市立闪兰半汁汇头汉宁穴它讨写让礼训必议讯记永司尼民出辽奶奴加召皮边发孕圣对台矛纠母幼丝式刑动扛寺吉扣考托老执巩圾扩扫地扬场耳共芒亚芝朽朴机权过臣再协西压厌在有百存而页匠夸夺灰达列死成夹轨邪划迈毕至此贞师尘尖劣光当早吐吓虫曲团同吊吃因吸吗屿帆岁回岂刚则肉网年朱先丢舌竹迁乔伟传乒乓休伍伏优伐延件任伤价份华仰仿伙伪自血向似后行舟全会杀合兆企众爷伞创肌朵杂危旬旨负各名多争色壮冲冰庄庆亦刘齐交次衣产决充妄闭问闯羊并关米灯州汗污江池汤忙兴宇守宅字安讲军许论农讽设访寻那迅尽导异孙阵阳收阶阴防奸如妇好她妈戏羽观欢买红纤级约纪驰巡寿弄麦形进戒吞远违运扶抚坛技坏扰拒找批扯址走抄坝贡攻赤折抓扮抢孝均抛投坟抗坑坊抖护壳志扭块声把报却劫芽花芹芬苍芳严芦劳克苏杆杠杜材村杏极李杨求更束豆两丽医辰励否还歼来连步坚旱盯呈时吴助县里呆园旷围呀吨足邮男困吵串员听吩吹呜吧吼别岗帐财针钉告我乱利秃秀私每兵估体何但伸作伯伶佣低你住位伴身皂佛近彻役返余希坐谷妥含邻岔肝肚肠龟免狂犹角删条卵岛迎饭饮系言冻状亩况床库疗应冷这序辛弃冶忘闲间闷判灶灿弟汪沙汽沃泛沟没沈沉怀忧快完宋宏牢究穷灾良证启评补初社识诉诊词译君灵即层尿尾迟局改张忌际陆阿陈阻附妙妖妨努忍劲鸡驱纯纱纳纲驳纵纷纸纹纺驴纽奉玩环武青责现表规抹拢拔拣担坦押抽拐拖拍者顶拆拥抵拘势抱垃拉拦拌幸招坡披拨择抬其取苦若茂苹苗英范直茄茎茅林枝杯柜析板松枪构杰述枕丧或画卧事刺枣雨卖矿码厕奔奇奋态欧垄妻轰顷转斩轮软到非叔肯齿些虎虏肾贤尚旺具果味昆国昌畅明易昂典固忠咐呼鸣咏呢岸岩帖罗帜岭凯败贩购图钓制知垂牧物乖刮秆和季委佳侍供使例版侄侦侧凭侨佩货依的迫质欣征往爬彼径所舍金命斧爸采受乳贪念贫肤肺肢肿胀朋股肥服胁周昏鱼兔狐忽狗备饰饱饲变京享店夜庙府底剂郊废净盲放刻育闸闹郑券卷单炒炊炕炎炉沫浅法泄河沾泪油泊沿泡注泻泳泥沸波泼泽治怖性怕怜怪学宝宗定宜审宙官空帘实试郎诗肩房诚衬衫视话诞询该详建肃录隶居届刷屈弦承孟孤陕降限妹姑姐姓始驾参艰线练组细驶织终驻驼绍经贯奏春帮珍玻毒型挂封持项垮挎城挠政赴赵挡挺括拴拾挑指垫挣挤拼挖按挥挪某甚革荐巷带草茧茶荒茫荡荣故胡南药标枯柄栋相查柏柳柱柿栏树要咸威歪研砖厘厚砌砍面耐耍牵残殃轻鸦皆背战点临览竖省削尝是盼眨哄显哑冒映星昨畏趴胃贵界虹虾蚁思蚂虽品咽骂哗咱响哈咬咳哪炭峡罚贱贴骨钞钟钢钥钩卸缸拜看矩怎牲选适秒香种秋科重复竿段便俩贷顺修保促侮俭俗俘信皇泉鬼侵追俊盾待律很须叙剑逃食盆胆胜胞胖脉勉狭狮独狡狱狠贸怨急饶蚀饺饼弯将奖哀亭亮度迹庭疮疯疫疤姿亲音帝施闻阀阁差养美姜叛送类迷前首逆总炼炸炮烂剃洁洪洒浇浊洞测洗活派洽染济洋洲浑浓津恒恢恰恼恨举觉宣室宫宪突穿窃客冠语扁袄祖神祝误诱说诵垦退既屋昼费陡眉孩除险院娃姥姨姻娇怒架贺盈勇怠柔垒绑绒结绕骄绘给络骆绝绞统耕耗艳泰珠班素蚕顽盏匪捞栽捕振载赶起盐捎捏埋捉捆捐损都哲逝捡换挽热恐壶挨耻耽恭莲莫荷获晋恶真框桂档桐株桥桃格校核样根索哥速逗栗配翅辱唇夏础破原套逐烈殊顾轿较顿毙致柴桌虑监紧党晒眠晓鸭晃晌晕蚊哨哭恩唤啊唉罢峰圆贼贿钱钳钻铁铃铅缺氧特牺造乘敌秤租积秧秩称秘透笔笑笋债借值倚倾倒倘俱倡候俯倍倦健臭射躬息徒徐舰舱般航途拿爹爱颂翁脆脂胸胳脏胶脑狸狼逢留皱饿恋桨浆衰高席准座脊症病疾疼疲效离唐资凉站剖竞部旁旅畜阅羞瓶拳粉料益兼烤烘烦烧烛烟递涛浙涝酒涉消浩海涂浴浮流润浪浸涨烫涌悟悄悔悦害宽家宵宴宾窄容宰案请朗诸读扇袜袖袍被祥课谁调冤谅谈谊剥恳展剧屑弱陵陶陷陪娱娘通能难预桑绢绣验继球理捧堵描域掩捷排掉堆推掀授教掏掠培接控探据掘职基著勒黄萌萝菌菜萄菊萍菠营械梦梢梅检梳梯桶救副票戚爽聋袭盛雪辅辆虚雀堂常匙晨睁眯眼悬野啦晚啄距跃略蛇累唱患唯崖崭崇圈铜铲银甜梨犁移笨笼笛符第敏做袋悠偿偶偷您售停偏假得衔盘船斜盒鸽悉欲彩领脚脖脸脱象够猜猪猎猫猛馅馆凑减毫麻痒痕廊康庸鹿盗章竟商族旋望率着盖粘粗粒断剪兽清添淋淹渠渐混渔淘液淡深婆梁渗情惜惭悼惧惕惊惨惯寇寄宿窑密谋谎祸谜逮敢屠弹随蛋隆隐婚婶颈绩绪续骑绳维绵绸绿琴斑替款堪搭塔越趁趋超提堤博揭喜插揪搜煮援裁搁搂搅握揉斯期欺联散惹葬葛董葡敬葱落朝辜葵棒棋植森椅椒棵棍棉棚棕惠惑逼厨厦硬确雁殖裂雄暂雅辈悲紫辉敞赏掌晴暑最量喷晶喇遇喊景践跌跑遗蛙蛛蜓喝喂喘喉幅帽赌赔黑铸铺链销锁锄锅锈锋锐短智毯鹅剩稍程稀税筐等筑策筛筒答筋筝傲傅牌堡集焦傍储奥街惩御循艇舒番释禽腊脾腔鲁猾猴然馋装蛮就痛童阔善羡普粪尊道曾焰港湖渣湿温渴滑湾渡游滋溉愤慌惰愧愉慨割寒富窜窝窗遍裕裤裙谢谣谦属屡强粥疏隔隙絮嫂登缎缓编骗缘瑞魂肆摄摸填搏塌鼓摆携搬摇搞塘摊蒜勤鹊蓝墓幕蓬蓄蒙蒸献禁楚想槐榆楼概赖酬感碍碑碎碰碗碌雷零雾雹输督龄鉴睛睡睬鄙愚暖盟歇暗照跨跳跪路跟遣蛾蜂嗓置罪罩错锡锣锤锦键锯矮辞稠愁筹签简毁舅鼠催傻像躲微愈遥腰腥腹腾腿触解酱痰廉新韵意粮数煎塑慈煤煌满漠源滤滥滔溪溜滚滨粱滩慎誉塞谨福群殿辟障嫌嫁叠缝缠静碧璃墙撇嘉摧截誓境摘摔聚蔽慕暮蔑模榴榜榨歌遭酷酿酸磁愿需弊裳颗嗽蜻蜡蝇蜘赚锹锻舞稳算箩管僚鼻魄貌膜膊膀鲜疑馒裹敲豪膏遮腐瘦辣竭端旗精歉熄熔漆漂漫滴演漏慢寨赛察蜜谱嫩翠熊凳骡缩慧撕撒趣趟撑播撞撤增聪鞋蕉蔬横槽樱橡飘醋醉震霉瞒题暴瞎影踢踏踩踪蝶蝴嘱墨镇靠稻黎稿稼箱箭篇僵躺僻德艘膝膛熟摩颜毅糊遵潜潮懂额慰劈操燕薯薪薄颠橘整融醒餐嘴蹄器赠默镜赞篮邀衡膨雕磨凝辨辩糖糕燃澡激懒壁避缴戴擦鞠藏霜霞瞧蹈螺穗繁辫赢糟糠燥臂翼骤鞭覆蹦镰翻鹰警攀蹲颤瓣爆疆壤耀躁嚼嚷籍魔灌蠢霸露囊罐匕刁丐歹戈夭仑讥冗邓艾夯凸卢叭叽皿凹囚矢乍尔冯玄邦迂邢芋芍吏夷吁吕吆屹廷迄臼仲伦伊肋旭匈凫妆亥汛讳讶讹讼诀弛阱驮驯纫玖玛韧抠扼汞扳抡坎坞抑拟抒芙芜苇芥芯芭杖杉巫杈甫匣轩卤肖吱吠呕呐吟呛吻吭邑囤吮岖牡佑佃伺囱肛肘甸狈鸠彤灸刨庇吝庐闰兑灼沐沛汰沥沦汹沧沪忱诅诈罕屁坠妓姊妒纬玫卦坷坯拓坪坤拄拧拂拙拇拗茉昔苛苫苟苞茁苔枉枢枚枫杭郁矾奈奄殴歧卓昙哎咕呵咙呻咒咆咖帕账贬贮氛秉岳侠侥侣侈卑刽刹肴觅忿瓮肮肪狞庞疟疙疚卒氓炬沽沮泣泞泌沼怔怯宠宛衩祈诡帚屉弧弥陋陌函姆虱叁绅驹绊绎契贰玷玲珊拭拷拱挟垢垛拯荆茸茬荚茵茴荞荠荤荧荔栈柑栅柠枷勃柬砂泵砚鸥轴韭虐昧盹咧昵昭盅勋哆咪哟幽钙钝钠钦钧钮毡氢秕俏俄俐侯徊衍胚胧胎狰饵峦奕咨飒闺闽籽娄烁炫洼柒涎洛恃恍恬恤宦诫诬祠诲屏屎逊陨姚娜蚤骇耘耙秦匿埂捂捍袁捌挫挚捣捅埃耿聂荸莽莱莉莹莺梆栖桦栓桅桩贾酌砸砰砾殉逞哮唠哺剔蚌蚜畔蚣蚪蚓哩圃鸯唁哼唆峭唧峻赂赃钾铆氨秫笆俺赁倔殷耸舀豺豹颁胯胰脐脓逛卿鸵鸳馁凌凄衷郭斋疹紊瓷羔烙浦涡涣涤涧涕涩悍悯窍诺诽袒谆祟恕娩骏琐麸琉琅措捺捶赦埠捻掐掂掖掷掸掺勘聊娶菱菲萎菩萤乾萧萨菇彬梗梧梭曹酝酗厢硅硕奢盔匾颅彪眶晤曼晦冕啡畦趾啃蛆蚯蛉蛀唬唾啤啥啸崎逻崔崩婴赊铐铛铝铡铣铭矫秸秽笙笤偎傀躯兜衅徘徙舶舷舵敛翎脯逸凰猖祭烹庶庵痊阎阐眷焊焕鸿涯淑淌淮淆渊淫淳淤淀涮涵惦悴惋寂窒谍谐裆袱祷谒谓谚尉堕隅婉颇绰绷综绽缀巢琳琢琼揍堰揩揽揖彭揣搀搓壹搔葫募蒋蒂韩棱椰焚椎棺榔椭粟棘酣酥硝硫颊雳翘凿棠晰鼎喳遏晾畴跋跛蛔蜒蛤鹃喻啼喧嵌赋赎赐锉锌甥掰氮氯黍筏牍粤逾腌腋腕猩猬惫敦痘痢痪竣翔奠遂焙滞湘渤渺溃溅湃愕惶寓窖窘雇谤犀隘媒媚婿缅缆缔缕骚瑟鹉瑰搪聘斟靴靶蓖蒿蒲蓉楔椿楷榄楞楣酪碘硼碉辐辑频睹睦瞄嗜嗦暇畸跷跺蜈蜗蜕蛹嗅嗡嗤署蜀幌锚锥锨锭锰稚颓筷魁衙腻腮腺鹏肄猿颖煞雏馍馏禀痹廓痴靖誊漓溢溯溶滓溺寞窥窟寝褂裸谬媳嫉缚缤剿赘熬赫蔫摹蔓蔗蔼熙蔚兢榛榕酵碟碴碱碳辕辖雌墅嘁踊蝉嘀幔镀舔熏箍箕箫舆僧孵瘩瘟彰粹漱漩漾慷寡寥谭褐褪隧嫡缨撵撩撮撬擒墩撰鞍蕊蕴樊樟橄敷豌醇磕磅碾憋嘶嘲嘹蝠蝎蝌蝗蝙嘿幢镊镐稽篓膘鲤鲫褒瘪瘤瘫凛澎潭潦澳潘澈澜澄憔懊憎翩褥谴鹤憨履嬉豫缭撼擂擅蕾薛薇擎翰噩橱橙瓢蟥霍霎辙冀踱蹂蟆螃螟噪鹦黔穆篡篷篙篱儒膳鲸瘾瘸糙燎濒憾懈窿缰壕藐檬檐檩檀礁磷瞬瞳瞪曙蹋蟋蟀嚎赡镣魏簇儡徽爵朦臊鳄糜癌懦豁臀藕藤瞻嚣鳍癞瀑襟璧戳攒孽蘑藻鳖蹭蹬簸簿蟹靡癣羹鬓攘蠕巍鳞糯譬霹躏髓蘸镶瓤矗乂乜兀弋孑孓幺亓韦廿丏卅仄厄仃仉仂兮刈爻卞闩讣尹夬爿毋邗邛艽艿札叵匝丕匜劢卟叱叻仨仕仟仡仫仞卮氐犰刍邝邙汀讦讧讪讫尻阡尕弁驭匡耒玎玑圩圬圭扦圪圳圹扪圮圯芊芄芨芑芎芗亘厍夼戍尥乩旯曳岌屺凼囡钇缶氘氖牝伎伛伢佤仵伥伧伉伫囟汆刖夙旮刎犷犸舛邬饧汕汔汐汲汜汊忖忏讴讵祁讷聿艮厾阮阪丞妁牟纡纣纥纨玙抟抔圻坂坍抉㧐芫邯芸芾苈苣芷芮苋芼苌苁芩芪芡芟苄苎苡杌杓杞忑孛邴邳矶奁豕忒欤轫迓邶忐卣邺旰呋呒呓呔呖呃旸吡町虬呗吽吣吲帏岐岈岘岑岚兕囵囫钊钋钌迕氙氚牤佞邱攸佚佝佟佗伽彷佘佥孚豸坌肟邸奂劬狄狁邹饨饩饪饫饬亨庑庋疔疖肓闱闳闵羌炀沣沅沔沤沌沏沚汩汨沂汾沨汴汶沆沩泐怃怄忡忤忾怅忻忪怆忭忸诂诃诋诌诏诒孜陇陀陂陉妍妩妪妣妊妗妫妞姒妤邵劭刭甬邰纭纰纴纶纾玮玡玭玠玢玥玦盂忝匦坩抨拤坫拈垆抻劼拃拊坼坻㧟坨坭抿坳耶苷苯苤茏苜苴苒苘茌苻苓茚茆茑茓茔茕茀苕枥枇杪杳枧杵枨枞枋杻杷杼矸砀刳瓯殁郏轭郅鸢盱昊杲昃咂呸昕昀旻昉炅咔畀虮咀呷黾呱呤咚咛呶呣呦咝岢岿岬岫帙岣峁刿迥岷剀帔峄沓囹罔钍钎钏钒钕钗邾迮牦竺迤佶佬佰侑侉臾岱侗侃侏侩佻佾侪佼佯侬帛阜侔徂郄怂籴戗肼䏝肽肱肫剁迩郇狙狎狍狒咎炙枭饯饴冽冼庖疠疝疡兖妾劾炜炖炘炝炔泔沭泷泸泱泅泗泠泺泖泫泮沱泯泓泾怙怵怦怛怏怍㤘怩怫怿宕穹宓诓诔诖诘戾诙戽郓祆祎祉祇诛诜诟诠诣诤诧诨诩戕孢亟陔妲妯姗帑弩孥驽迦迨绀绁绂驷驸绉绌驿骀甾珏珐珂珑玳珀顸珉珈拮垭挝垣挞垤赳贲垱垌郝垧垓挦垠茜荑贳荜莒茼茱莛茯荏荇荃荟荀茗茭茨垩荥荦荨荩剋荪茹荬荮柰栉柯柘栊柩枰栌柙枵柚枳柞柝栀柢栎枸柈柁柽剌酊郦甭砗砘砒斫砭砜奎耷虺殂殇殄殆轱轲轳轶轸虿毖觇尜哐眄眍郢眇眊眈禺哂咴曷昴昱咦哓哔畎毗呲胄畋畈虼虻咣哕剐郧咻囿咿哌哙哚咯咩咤哝哏哞峙峣罘帧峒峤峋峥贶钚钛钡钣钤钨钫钯氡氟牯郜秭竽笈笃俦俨俅俪叟垡牮俣俚皈俑俟逅徇徉舢俞郗俎郤爰郛瓴胨胪胛胂胙胍胗胝朐胫鸨匍狨狯飑狩狲訇逄昝饷饸饹胤孪娈弈庥疬疣疥疭庠竑彦闼闾闿阂羑迸籼酋炳炻炽炯烀炷烃洱洹洧洌浃洇洄洙洎洫浍洮洵浒浔浕洳恸恓恹恫恺恻恂恪恽宥扃衲衽衿袂祛祜祓祚诮祗祢诰诳鸩昶郡咫弭牁胥陛陟娅姮娆姝姣姘姹怼羿炱矜绔骁骅绗绛骈耖挈珥珙顼珰珩珧珣珞琤珲敖恚埔埕埘埙埚挹耆耄埒捋贽垸捃盍莆莳莴莪莠莓莜莅荼莩荽莸荻莘莎莞莨鸪莼栲栳郴桓桡桎桢桤梃栝桕桁桧栟桉栩逑逋彧鬲豇酐逦厝孬砝砹砺砧砷砟砼砥砣剞砻轼轾辂鸫趸龀鸬虔逍眬唛晟眩眙哧哽唔晁晏鸮趵趿畛蚨蚍蚋蚬蚝蚧唢圄唣唏盎唑崂崃罡罟峪觊赅钰钲钴钵钹钺钽钼钿铀铂铄铈铉铊铋铌铍䥽铎氩氤氦毪舐秣盉笄笕笊笏俸倩俵偌俳俶倬倏恁倭倪俾倜隼隽倌倥臬皋郫倨衄颀徕舫釜奚衾胱胴胭脍胼朕脒胺鸱玺鸲狷猁狳猃狺逖桀袅饽凇栾挛亳疳疴疸疽痈疱痂痉衮凋颃恣旆旄旃阃阄訚阆恙粑朔郸烜烨烩烊剡郯烬涑浯涞涟娑涅涠浞涓浥涔浜浠浣浚悚悭悝悒悌悛宸窈剜诹冢诼袢祯诿谀谂谄谇屐屙陬勐奘牂蚩陲姬娠娌娉娲娴娣娓婀畚逡绠骊绡骋绥绦绨骎邕鸶彗耜焘舂琏琇揶埴埯捯掳掴埸埵赧埤捭逵埝堋堍掬鸷捽掊堉捩掮悫埭埽掇掼聃菁萁菘堇萘萋菽菖萜萸萑棻菔菟萏萃菏菹菪菅菀萦菰菡梵梿梏觋桴桷梓棁桫棂啬郾匮敕豉鄄酞酚戛硎硭硒硖硗硐硇硌鸸瓠匏厩龚殒殓殍赉雩辄堑眭眦啧晡眺眵眸圊喏喵啉勖晞唵晗啭趺啮跄蚶蛄蛎蚰蛊圉蚱蛏蚴啁啕唿啐唼唷啖啵啶啷唳唰啜帻崚崦帼崮崤崆赇赈铑铒铗铙铟铠铢铤铧铨铩铪铫铬铮铯铰铱铳铵铷氪牾鸹秾逶笺筇笸笪笮笠笥笳笾笞偾偃偕偈偬偻皑皎鸻徜舸舻舴龛脬脘脲匐猗猡猞猝斛猕馗馃馄鸾孰庹庾痔痍疵翊旌旎袤阇阈阉阊阋阍阏羟粝粕敝焐烯焓烽焖烷焗渍渚淇淅淞渎涿淖挲淠涸渑淦淝淬涪淙涫渌淄惬悻悱惝惘悸惆惚惇惮窕谌谏扈皲谑袷裉谔谕谖谗谙谛谝逯郿隈粜隍隗婧婊婕娼婢婵胬袈翌恿欸绫骐绮绯绱骒绲骓绶绺绻绾骖缁耠琫琵琶琪瑛琦琥琨靓琰琮琯琬琛琚辇鼋揳堞搽揸揠堙趄颉塄揿耋揄蛩蛰塆摒揆掾聒葑葚靰靸葳葺葸萼葆葩葶蒌萱戟葭楮棼椟棹椤棰赍椋椁椪棣椐鹁覃酤酢酡鹂厥殚殛雯雱辊辋椠辍辎斐睄睑睇睃戢喋嗒喃喱喹晷喈跖跗跞跚跎跏跆蛱蛲蛭蛳蛐蛞蛴蛟蛘喁喟啾嗖喑嗟喽嗞喀喔喙嵘嵖崴遄詈嵎崽嵬嵛嵯嵝嵫幄嵋赕铻铼铿锃锂锆锇锏锑锒锔锕掣矬氰毳毽犊犄犋鹄犍嵇稃稂筚筵筌傣傈舄傥傧遑傩遁徨媭畲弑颌翕釉鹆舜貂腈腓腆腴腑腚腱鱿鲀鲂颍猢猹猥飓觞觚猱颎飧馇馊亵脔裒痣痨痦痞痤痫痧赓竦瓿啻颏鹇阑阒阕粞遒孳焯焜焱鹈湛渫湮湎湜渭湍湫溲湟溆湲湔湉渥湄滁愠惺愦惴愀愎愔喾寐谟扉裢裎裥祾祺谠幂谡谥谧遐孱弼巽骘媪媛婷巯翚皴婺骛缂缃缄彘缇缈缌缑缒缗飨耢瑚瑁瑜瑗瑄瑕遨骜韫髡塬鄢趔趑摅摁蜇搋搐搛搠摈彀毂搦搡蓁戡蓍鄞靳蓐蓦鹋蒽蓓蓊蒯蓟蓑蒺蓠蒟蒡蒹蒴蒗蓥颐楠楂楝楫楸椴槌楯皙榈槎榉楦楹椽裘剽甄酮酰酯酩蜃碛碓碚碇碜鹌辏龃龅訾粲虞睚嗪韪嗷嗉睨睢雎睥嘟嗑嗫嗬嗔嗝戥嗄煦暄遢暌跬跶跸跐跣跹跻蛸蜊蜍蜉蜣畹嗣嗯嗥嗲嗳嗌嗍嗨嗐嗵罨嵊嵩嵴骰锗锛锜锝锞锟锢锩锱雉氲犏歃稞稗稔筠筢筮筲筱牒煲敫徭愆艄觎毹貊貅貉颔腠腩腼腭腧塍媵詹鲅鲆鲇鲈稣鲋鲐鹐飕觥遛馐鹑亶瘃痱痼痿瘐瘁瘆麂裔歆旒雍阖阗阙羧豢粳猷煳煜煨煅煊煸煺滟溱溘漭滢溥溧溽裟溻溷滗滫溴滏滃滦溏滂溟滪愫慑慊鲎骞窦窠窣裱褚裨裾裰禊谩谪媾嫫媲嫒嫔媸缙缜缛辔骝缟缡缢缣骟耥璈瑶瑭獒觏慝嫠韬叆髦摽墁撂摞撄翥踅摭墉墒榖綦蔷靺靼鞅靿甍蔸蔟蔺戬蕖蔻蓿斡鹕蓼榧榻榫榭槔榱槁槟槠榷僰酽酶酹厮碡碣碲磋臧豨殡霆霁蜚裴翡龇龈睿䁖睽嘞嘈嘌嘎暧暝踌踉蜞蜥蜮蝈蜴蜱蜩蜷蜿螂蜢嘘嘡鹗嘣嘤嘚嗾嘧罴罱嶂幛赙罂骷骶鹘锲锴锶锷锸锵镁镂犒箐箦箧箸箬箅箪箔箜箢箓毓僖儆僳僭劁僮魃魆睾艋鄱膈膑鲑鲔鲚鲛鲟獐觫雒夤馑銮塾麽瘌瘊瘘瘙廖韶旖膂阚鄯鲞粿粼粽糁槊鹚熘熥潢漕滹漯漶潋潴漪漉漳澉潍慵搴窨寤綮谮褡褙褓褛褊谯谰谲暨屣鹛嫣嫱嫖嫦嫚嫘鼐翟瞀鹜骠缥缦缧骢缪缫耦耧瑾璜璀璎璁璋璇奭髯髫撷撅赭撸鋆撙撺墀聩觐鞑蕙鞒蕈蕨蕤蕞蕺瞢蕃蕲赜槿樯槭樗樘槲醌醅靥魇餍磔磙霈辘龉龊觑瞌瞋瞑嘭噎噶颙暹噘踔踝踟踒踬踮踯踺踞蝽蝾蝻蝰蝮螋蝓蝣蝼噗嘬颚噍噢噙噜噌噔颛幞幡嶙嶝骺骼骸镉镌镍镏镒镓镔稷箴篑篁篌篆牖儋徵磐虢鹞滕鲠鲡鲢鲣鲥鲧鲩獗獠觯馓馔麾廛瘛瘼瘢瘠齑羯羰廗遴糌糍糅熜熵熠澍澌潸潲鋈潟潼潺憬憧寮窳谳褴褟褫谵熨屦勰戮蝥缬缮缯骣畿耩耨耪璞璟靛璠璘聱螯髻髭髹擀熹甏擞縠磬颞蕻鞘颟薤薨檠薏薮薜薅樾橛橇樵檎橹樽樨橼墼橐翮醛醐醍醚磲赝飙殪霖霏霓錾辚臻遽氅瞟瞠瞰嚄嚆噤暾蹀踹踵踽蹉蹁螨蟒螈螅螭螠噱噬噫噻噼罹圜䦃镖镗镘镚镛镝镞镠氇氆憩穑篝篥篦篪盥劓翱魉魈徼歙膦膙鲮鲱鲲鲳鲴鲵鲷鲻獴獭獬邂鹧廨赟瘰廪瘿瘵瘴癃瘳斓麇麈嬴壅羲糗瞥甑燠燔燧濑濉潞澧澹澥澶濂褰寰窸褶禧嬖犟隰嬗颡缱缲缳璨璩璐璪螫擤觳罄擢薹鞡鞬薷薰藓藁檄懋醢翳礅磴鹩龋龌豳壑黻嚏嚅蹑蹒蹊螬螵疃螳蟑嚓羁罽罾嶷黜黝髁髀镡镢镦镧镩镪镫罅黏簌篾篼簖簋鼢黛鹪鼾皤魍龠繇貘邈貔臌膻臆臃鲼鲽鳀鳃鳅鳇鳊螽燮鹫襄縻膺癍麋懑濡濮濞濠濯蹇謇邃襁檗擘孺隳嬷蟊鹬鍪鏊鳌鬈鬃瞽鞯鞨鞫鞧鞣藜藠藩醪蹙礓燹餮瞿曛颢曜躇蹚鹭蟛蟪蟠蟮鹮黠黟髅髂镬镭镯馥簟簪鼬雠艟鳎鳏鳐癔癜癖糨蹩鎏懵彝邋鬏攉鞲鞴藿蘧蘅麓醮醯酃霪霭霨黼嚯蹰蹶蹽蹼蹴蹾蹿蠖蠓蟾蠊黢髋髌镲籀籁齁魑艨鳓鳔鳕鳗鳙麒鏖羸瀚瀣瀛襦谶襞骥缵瓒蘩蘖醴霰酆矍曦躅鼍巉黩黥黪镳镴黧纂璺鼯臜鳜鳝鳟獾孀骧瓘鼙醺礴颦曩鳢癫麝夔爝灏禳鐾羼蠡耱懿鹳霾氍饕躐髑镵穰饔鬻鬟趱攫攥颧躜鼹癯麟蠲蠹躞衢鑫灞襻纛鬣攮囔馕戆爨齉亍尢彳卬殳毌邘戋圢氕伋仝冮氿汈氾忉宄讱扞圲圫芏芃朳朸邨吒吖屼屾辿钆仳伣伈癿甪邠犴冱邡闫汋䜣讻孖纩玒玓玘玚刬坜坉扽坋扺㧑毐芰芣苊苉芘芴芠芤杕杙杄杧杩尪尨轪坒芈旴旵呙㕮岍岠岜呇冏觃岙伾㑇伭佖伲佁飏狃闶汧汫沄沘汭㳇沇忮忳忺祃诇邲诎诐屃岊阽䢺阼妧妘纮驲纻纼玤玞玱玟邽邿坥坰坬坽弆耵䢼茋苧苾苠枅㭎枘枍矼矻匼旿昇昄昒昈咉咇咍岵岽岨岞峂㟃囷钐钔钖牥佴垈侁侹佸佺隹㑊侂佽侘郈舠郐郃攽肭肸肷狉狝饳忞於炌炆泙沺泂泜泃泇怊峃穸祋祊鸤弢弨陑陎卺乸妭姈迳叕驵䌹驺绋绐砉耔㛃玶珇珅珋玹珌玿韨垚垯垙垲埏垍耇鿍垎垴垟垞挓垵垏拶荖荁荙荛茈茽荄茺荓茳茛荭㭕柷柃柊枹栐柖郚剅䴓迺厖砆砑砄耏奓䶮轵轷轹轺昺昽盷咡咺昳昣哒昤昫昡咥昪虷虸哃耑峏峛峗峧帡钘钜钪钬钭矧秬俫舁俜俙俍垕衎舣弇侴鸧䏡胠胈胩胣朏飐訄饻庤疢炣炟㶲洭洘洓洿㳚泚浈浉洸洑洢洈洚洺洨浐㳘洴洣恔宬窀扂袆祏祐祕叚陧陞娀姞姱姤姶姽枲绖骃彖骉恝珪珛珹琊玼珖珽珦珫珒珢珕珝埗垾垺埆垿埌埇莰茝鄀莶莝䓖莙栻桠桄梠栴梴栒酎酏砵砠砫砬硁恧翃郪辀辁剕赀哢晅晊唝哳哱冔晔晐晖畖蚄蚆帱崁峿崄帨崀赆钷眚甡笫倻倴脩倮倕倞倓倧衃虒舭舯舥瓞鬯鸰脎朓胲虓鱽狴峱狻眢勍痄疰痃竘羖羓桊敉烠烔烶烻涍浡浭浬涄涢涐浰浟浛浼浲涘悈悃悢宧窅窊窎扅扆袪袗袯祧隺堲疍陴烝砮㛚哿翀翂剟绤骍䂮琎珸珵琄琈琀珺掭堎堐埼掎埫堌晢掞埪壸㙍聍菝萚菥莿䓫勚䓬萆菂菍菼萣䓨菉䓛梼梽桲梾桯梣梌桹敔厣硔鿎硙硚硊硍勔䴕龁逴唪啫翈㫰晙畤趼跂蛃蚲蚺啴䎃崧崟崞崒崌崡铏铕铖铘铚铞铥铴牻牿稆笱笯偰偡鸺偭偲偁㿠鄅偓徛衒舳舲鸼悆鄃瓻䝙脶脞脟䏲鱾猇猊猄觖庱庼庳痓䴔竫堃阌羝羕焆烺焌淏淟淜淴淯湴涴㥄惛惔悰惙寁逭袼裈祲谞艴弸弶隃婞娵婼媖婳婍婌婫婤婘婠绹骕絜珷琲琡琟琔琭堾堼揕㙘堧喆堨塅堠絷葜惎萳葙靬葴蒇蒈鄚蒉蓇萩蒐葰葎鄑蒎葖蒄萹棤棽棫椓椑鹀椆棓棬棪椀楗甦酦觌奡皕硪欹詟辌棐龂黹牚睎晫晪晱蛑畯斝喤崶嵁崾嵅崿嵚翙圌圐赑淼赒鿏铹铽锊锍锎锓犇颋稌筀筘筜筥筅傃傉翛傒傕舾畬脿腘䐃腙腒鲃猰猯㺄馉凓鄗廋廆鄌粢遆旐焞欻溚溁湝渰湓㴔渟溠渼溇湣湑溞愐愃敩甯棨扊裣祼婻媆媞㛹媓媂媄毵矞缊缐骙瑃瑓瑅瑆䴖瑖瑝瑔瑀瑳瑂嶅瑑遘髢塥堽赪摛塝搒搌蒱蒨蓏蔀蓢蓂蒻蓣椹楪榃榅楒楩榇椸楙歅碃碏碈䃅硿鄠辒龆觜䣘暕鹍㬊暅跱蜐蜎嵲赗骱锖锘锳锧锪锫锬稑稙䅟筻筼筶筦筤傺鹎僇艅艉谼貆腽腨腯鲉鲊鲌䲟鲏雊猺飔觟馌裛廒瘀瘅鄘鹒鄜麀鄣阘煁煃煴煋煟煓滠溍溹滆滉溦溵漷滧滘滍愭慥慆塱裼禋禔禘禒谫鹔愍嫄媱戤勠戣缞耤瑧瑨瑱瑷瑢斠摏墕墈墐墘摴銎墚撖靽鞁蔌蔈蓰蔹蔊嘏榰榑槚槜榍疐酺酾酲酴碶䃎碨碹碥劂䴗夥瞍鹖㬎跽蜾幖嶍圙锺锼锽锾锿镃镄镅馝鹙箨箖劄僬僦僔僎槃㙦鲒鲕鲖鲗鲘鲙夐獍飗凘廑廙瘗瘥瘕鲝鄫熇漹漖潆漤潩漼漴㽏漈漋漻慬窬窭㮾褕禛禚隩嫕嫭嫜嫪㻬麹璆漦叇墣墦墡劐薁蕰蔃鼒槱鹝磏磉殣慭霅暵暲暶踦踣䗖蝘蝲蝤噇噂噀罶嶲嶓㠇嶟嶒镆镈镋镎镕稹儇皞皛䴘艎艏鹟鲦鲪鲬橥觭鹠鹡糇糈翦鹢鹣熛潖潵㵐澂澛瑬潽潾潏憭憕戭褯禤嫽遹璥璲璒憙擐鄹薳鞔黇蕗薢蕹橞橑橦醑觱磡磜豮鹾虤暿曌曈㬚蹅踶䗛螗疁㠓幪嶦馞穄篚篯簉鼽衠盦螣縢鲭鲯鲰鲺鲹亸癀瘭羱糒燋熻燊燚燏濩濋澪澽澴澭澼憷憺懔黉嬛鹨翯璱璬璮髽擿薿薸檑櫆檞醨繄磹磻瞫瞵蹐蟏㘎镤镥镨矰穙穜穟簕簃簏儦魋斶艚谿䲠鲾鲿鳁鳂鳈鳉獯䗪馘襕襚螱甓嬬嬥瓀釐鬶爇鞳鞮藟藦藨鹲檫黡礞礌蹢蹜蟫䗴嚚髃镮镱酂馧簠簝簰鼫鼩皦臑䲢鳑鳒鹱鹯癗旞翷冁䎖瀔瀍瀌襜䴙嚭㰀鬷醭蹯蠋翾鳘儳儴鼗鳚鳛麑麖蠃彟嬿鬒蘘欂醵颥甗巇酅髎犨㸌爔瀱瀹瀼瀵襫孅骦耰瓖鬘趯罍鼱鳠鳡鳣爟爚灈韂糵蘼礵鹴躔皭龢鳤亹籥鼷玃醾齇觿蠼 專業叢東絲丟兩嚴喪個臨麗舉義烏樂喬習鄉書買亂爭虧亞亙畝親褻嚲億僅從侖倉儀們優會傴傘偉傳俔傷倀倫傖佇僉俠侶僥偵側僑儈儕儂俁儔儼倆儷倈儉債傾傯僂僨償儻儐儲儺兌兗蘭關興兹茲養獸囅內岡冊寫軍農馮決況凍淨涼減淒湊凜鳳凱擊鑿芻劉則剛創刪剗剄剎劊劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳勩勻匭匱區醫華協單賣盧衛卻廳厲壓厭厙厐龎廂厴廈廚廄廝縣叄參雙變敘疊號歎嘰嚇呂嗎唚噸聽吳吶嘸囈嘔嚦唄員咼嗆嗚詠嚨嚀噝響啞噠嘵嗶噦嘩噲哜嚌噥喲嘜嗊嘮唡啢嗩喚嘖嗇囀齧嘽嘯㖞喎噴嘍啰囉嚳囁噯噓嚶囑嚕囂園囪圍圇國圖圓聖壙場塊堅壢壩塢墳墜壟垅壠壚壘墾堊墊埡壋塏堖塒壎堝塹墮壈壯聲壺壼處備夠頭夾奪奩奐奮奧妝婦媽嫵嫗姍奼婁婭嬈嬌孌娛媧嫺嫿嬰嬋嬸媼嬡嬪嬙嬤孫學孿寶實寵審憲宮寬賓寢對尋導壽將爾塵嘗尧堯尴尷屍層屭屜屆屬屢屨嶼歲豈嶇崗峴嶴嵐島嶺崬巋嶧峽嶢嶠崢巒嶗崍嶮嶄嶸嶔嶁巅巔鞏巰幣帥師幃帳幟帶幀幫幬幘幗冪莊慶廬廡庫應廟龐廢廩開異棄弒張弪弳彎彈強歸彞彥徹徑徠憶懺憂愾懷態慫憮慪悵愴憐總懟懌戀恆懇慟懨愷惻惱惲悅懸慳悮悞憫驚懼慘懲憊愜慚憚慣慍憤憒懾懣懶懍戇戔戲戧戰戩戯戱戶撲執擴捫掃揚擾撫拋摶摳掄搶護報擬攏揀擁攔擰撥擇掛摯攣挜掗撾撻挾撓擋挢撟掙擠揮撏捝挩撈損撿換搗擄摑擲撣摻摜攬揾搵撳攙擱摟攪攜攝攄擺搖擯攤攖撐撝攆擷擼攛擓擻攢敵斂數齋斕斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖暱機殺雜權桿條來楊榪傑構樅樞棗櫪梘棖槍楓梟檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒椏橈楨檔榿橋樺檜槳樁夢檮棶槤檢梲欞槨櫝槧欏橢樓欖稜榲櫬櫚櫸檟槛檻簷檳櫧橫檣櫻櫫櫥櫓櫞檁歡歟歐殲歿殤殘殞殮殫殯㱮殨毆毀轂畢斃氈毿氌氣氫氬氳漢湯洶溝沒灃漚瀝淪滄滬濘淚泶澩瀧瀘濼瀉潑澤涇湧潔灑窪浹淺漿澆湞濁測澮濟浏瀏滻渾滸濃潯濤澇淶漣潿渦渙滌潤澗漲澀淵淥漬瀆漸澠漁渖瀋滲溫灣濕潰濺漵潷滾滯灧灄滿瀅濾濫羨灤濱灘澦灠瀠潇瀟瀲濰潛瀦瀾瀨瀕灝滅燈靈災燦煬爐燉煒熗點煉熾爍爛烴燭煙煩燒燁燴燙燼熱煥燜燾熅愛爺牘氂牽犧犢狀獷獁猶狽獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣瑒瑪瑋環現瑲璽琺瓏璫琿璉瑣瓊瑤璦瓔瓚甌電畫暢疇癤療瘧癘瘍癧瘲瘡瘋皰痾癰痙癢痖瘂癆瘓癇癉瘮瘞癟癱癮癭癩癬癲皚皺皸盞鹽監蓋盜盤瞘眥矓睜睐睞瞼瞆瞶瞞瞜瞩矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜碩硤磽磑礙磧磣鹼禮禡禕禰禎禱禍稟禄祿禅禪離禿稈積稱穢穠穭稅穌穩穡窮竊竅窵窯竄窩窺竇窶競篤筍筆筧箋籠籩篳篩簹箏䇲筴籌篔簡籙簀篋籜籮簞簫簣簍籃籬籪籟糴類秈糶糲粵糞糧糝餱緊䌷紬絅縶䌸縳䍁繸纟糹糾紆紅紂紇約級紈纊紀紉䌶䊷緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓紺絏紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給绚絢絳絡絞統綆綃絹綌綏繼綈績緒綾绬緓續綺緋綽緄繩維綿綬綢綯綹綣綜綻綰綴緇緙緗緘緬纜緹緲缉緝縕缋繢緦缍綞緞缏緶緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒繾繰繯繳纘罌網羅罰罷羆覊羥翹耮耬聳恥聶聾職聹聯聵聰肅腸膚骯餚腎腫脹脅膽朧腖臚脛膠脈膾臍膕腦膿臠腳脫腡臉齶膩靦膃騰臏臢輿艤艦艙艫艱艷藝節羋薌蕪蘆蓯葦藶莧萇蒼莖蘢蔦塋煢繭荊薦薘莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀荫蔭蕒葒葤蒞萊蓮蒔萵薟蕕瑩鶯蘿螢營縈蕭薩蔥蕆蕢蔣蔞藍薊蘺蕷鎣驀蔂虆薔蘞藺藹薀蘄藪蘚櫱虜慮虛虯蟣雖蝦蠆蝕蟻螞蠔蠶蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠅蟈蟬螻蠑螀螿蟎蠨釁銜補襯袞襖裊褘襪襲襏裝襠褌褳襝褲襇褸襤襴見觀覎規覓視覘覽覺覬覡覿觍覥覦覯覲覷觴觸觶誾訢譽謄讠訁計訂訃認譏訐訌討讓訕訖讬託訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕䜥詬詮詭詢詣諍該詳詫諢詡诪譸誡誣語誚誤誥誘誨誑誦诶誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談誼謀諶諜謊諫諧謔謁謂諤諭諼讒谘諮諳諺諦謎諞諝謨讜謖謝謗諡謙謐謹謾謫謬譚譖譙讕譜譎讞譴譫讖豶貝貞負贠貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀贻貽賊贄賈賄貲賃賂資賅贐賕賑賚賒賦賭贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贊贇贈贍贏赣贛赬趙趕趨趲躉躍蹌躒踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄辗輾轆轍轔辭辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰郟鄶鄭鄆酈鄖鄲酇醱醬釅釃釀醃貛釋鑑鑾鏨钅釒釓釔針釘釗釙釕釷釺釧釤钑鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鉅鈍鈔鈉鋇鋼鈑鈐鑰欽鈞鎢鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷钶鈳鉕钸鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚铇鉋鈰鉉鉈鉍鈮鈹鐸鉶銬銠鉺铓鋩铔錏銪鋮鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩铦銛鏵銓鎩鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鏗銷鎖鋰鋥鋤鍋鋯鋨銼鋝鋒鋅鋶鐦鐧銻鋃鋟鋦錒錆鍺鍩錯錨錛錡鍀錁錕锠錩錫錮鑼錘錐錦鑕錈鍃錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鐨鎇鏌鎮鎛鎘鑷鎲鎳鎿鎦鎬镑鎊鎰鎵鑌鎔鏢鏜鏝镙鏍鏰鏞鏡鏑鏃镟鏇鏐鐔鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔鑣鑞鑱鑲長門閂閃閆闬閈閉問闖閏闈閒閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閬闍閾閹閶鬩閿閽閻閼闡闌闃阓闠闊闋闔闐闒闕闞阛闤隊陽陰陣階際陸隴陳陘陝隉隕險隨隱隸雋難雛讎靂霧霽霡霢靄靚靜靨韃鞽韉韋韌韍韓韙韞韜韻頁頂頃頇項順頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻颒頮頷颕頴穎顆題顒顎顓額顳顢顛顙顥顫顬顰顴風颺颭颮颯颶颸颼飖颻飀飄飆飚飈飛饗饜饣飠饤飣饦飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑饾餖餓馀餘餒馂餕餜餛餡館餷馈饋餶餿饞饁饃馎餺餾饈饉饅饊饌饢馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅骔騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢魘魎魚魛魢魷魨魯魴䰾鲄魺鮁鮃鱸鮋鮓鮒鮊鲍鮑鱟鮍鮐鮭鮚鲓鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鱘鯁鱺鰱鰹鯉鰣鰷鯀鲨鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鲶鯰鯛鯨鰺鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鰍鳆鰒鰉鰁鱂鯿鳋鰠鰲鰭鰨鰥鰩鰟鰜鰳鰾鱈鱉鰻鰵鱅䲁鰼鱖鱔鱗鱒鱯鱤鱧鱣鳾鴷鵁鶄鶪鷈鷿鸊鷉鳥鳩鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鸴鷽鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鵾鵯鵬鵮鶉鶊鹓鵷鷫鶘鶡鶚鶻鶖鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鹥鷖鸚鷓鷚鷯鷦鷲鷸鷺鸇鷹鸌鸏鸛鸘鹺麥麩麼黃黌黶黷黲黽黿鼉鞀鼴齊齏齒齔齕齗齟齡齙齠齜齦齬齪齲齷龍龔龕龜萬與醜豐為雲產僕價眾偽體傭兒黨沖準幾鳧憑劃別滷廠歷廁發葉籲後啟鹹團壞壇牆殼復獎媯寧盡簾並廣彌當錄惡愨願擔據鬥術極櫃匯溈塗澱瘻確硷種豎築簽纖線絕繡絛緔繃綠韁勝臟臘蘇蘋藥獲蓴蘊蟲蠟說謠譾贓齎贗闢適醞採鐘鉤缽鏽銳杴鐫钁閱須頹顏飢罵鯗鱷雞鶿甕靉靆瞇瑯癡臥槓蝨脣麅洩摀翬僱姪羶痺坣壆慂璵伡俥䲞彠䜩讌殭併裡穫衝隻髮齣誌複幹捨慄盃嶽麵穀遊係繫製彿伕佈鍊姦鬍註傢錶儘曆緻鬚纔迴樸噹鬆餵妃週髒嚐綵唸噁囌嚥嚮佔豔兇誇摺睏汙彙嘛弘彆葯戌藉懞禦縴捲捵撚柺栱慾燻釦榦欷矇讚嘆衊硃糰濛瀰灕捱薑癥癒黴皓瞭蔴鞦韆鹵餬纍罈羈闆苑範薙蔔蠁衚衕譁觔盪閑鬱闇冥崑崙颱颳鬅鼕輓弔踰籤鬨枱倖舖撢紮麴痲藭璡䌺䋙䌻䋚䌼綐䌽䌾䋻䌿䋹䍀繿䲘䜧䜀䞌䞍䝼䞐賰䥺釾䥾䥱䦂䥇䦅鐥䦆䦶䦛䦷䦟䩄䭪䯅䯀鮣龑鱲輋䢀䢁綖篸梜鵟頫睍隤塿娙巠鄩嵽巘顗廞彄鷟鱀晛暐櫍鮆璗熰燀燖鸑鶠頠埨塸墠頵鵏頔鷭篢勣軏輗輮軝輶醲闉闑饘餗駓駼駪駉騊騵駃騱騄馼騠騞驎鮡鯻鮈鮠鱚鮀鰤鰊鰶鶱齘齯齮齼澫漍浿瓅僤璕蝀襀訏謏詷諴諲譓諓諟譞詪詝錞錀鍭鉷錤鑪鉊銈鈇鋐釴鏏釿鉥鉧鉮鐇鐍鏻鎓鋗鐩鋹隑隮鄳綧綡紞絪纆綄縯紃絺綎繶綝纁纕綪蒍蔄虉㤖懧銶䴉䓕蠍冑憖悽·!@#¥%…&*()—+-=、|【{}】;:‘“,《。》/? abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890~`!$^()_[]\;:'",<.>?ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω︵︶︹︺︿﹀︽︾﹁﹂﹃﹄︻︼︷︸АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюāáǎàēéěèīíǐìōóǒòūúǔùǖǘǚǜüêɑńňǹɡㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩ” 〔〕ˉˇ¨〃々~‖’〈〉「」『』〖〗±×÷∧∨∑∏∪∩∈√⊥∥∠⌒⊙∫∮≡≌≈∽∝≠≮≯≤≥∞∶∵∴∷♂♀°′″℃¤¢£‰§№☆★〇○●◎◇◆□■△▽⊿▲▼◣◤◢◥▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▓※→←↑↓↖↗↘↙〓ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹ①②③④⑤⑥⑦⑧⑨⑩⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ︱︳︴﹏﹋﹌─━│┃┄┅┆┇┈┉┊┋┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋㊣㈱∟﹊﹍╭╮╰╯﹕﹗℡〝〞﹢﹣≦≧≒﹤﹥я―‥℅℉∕∣═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬╱╲╳▔▕〆〒〡〢〣〤〥〦〧〨〩㎎㎏㎜㎝㎞㎡㏄㏎㏑㏒㏕︰﹎̄ế̌ềḿ奔廼乹亁箇凢亾丫枒巳匃匄紥歴厤疋曰壬殀讐崘亢爲鬦鬪鬭宂㕥㠯栞卉戊衹秖呌叩敂冉冄坵僊尒怱悤卯夘戼饑滙寕牠弗妳嬭臺檯糺戎働摃攷塲敭裌衺麯喫囙嵗㠶颿廻逥秊倣髣僞曏佀閤衆繖剏剙朶襍粧氷産竝汚汝譌辳堦隂媍懽讙驩䭾廵挵衖靭靱罎壜搤阯撦垻埳阬殻儗㕁刦刧刼芲蘤尅蘓吾荳酉尬旹裏獃唫脗鍼佐皁髴隣疘膓㠀鑤㱃畂畆畮牀恡竈汎沁烖菑啓唘祀禩愬䛐侷跼坿玅姉妬矣鷄駈敺駮帋翫槼搨拕拎牴觝抝桮鎗搆欝鬰鑛厠逩輭肎虜菓崐迪呪咋虖嘑謼咄㟁嵒巗巖迭雰咊儌妷遉凴廹徃寀覔脇昬兎俻飤亯亱䘚庚癈牐閙劵匟㳒灋霑恠怡寳寔眎眡䛡録隷箒絃圅妮迢㕘葠蓡綫駞旾幇幚珎攩拽哉垜艸蠒廕茘査栢栁桺柹塼甎麪鵶韮揹竪甞昰哇閧冐暎駡傌勛倃偺喒齩欬罸鈎榘氊秌禹㑺儁敍劒肧䘑衇陿奬蹟砲礮鬀怳卹䘏賉擧説婣癸毧羢遶絶畊豓琖醆揑綑擣躭鸎桔覈翄獘槕㨪螡蟁哦㤙陗峨峩峯贜鑚鈆乗椉咲俛勌䠶躳慇拏㧱挐脃胷肐貍㽞畱蓆亝効傚閲缾菸淛澁濇悖誖猂醼韈韤㝠寃娟娥孃桒綉騐毬瑠璢㨗焉搥搯㨿聆蔆婪惏楳槑捄醖慽慼埜啪畧虵鄂嶃帷崛剷稭棃犂迻媮兠啣衘舩歛豚舘庻菴朢羚睠觕麤釬銲痳殽婬滛湻㴱樑慙惟顇寅㝛窰旤隋緜綳緑琹欵墖趂隄愽揷揫煑朞㪚塟蔕棊碁椶偪㕑鴈蹔煇冣㝡晳鼃餧嗁諠㡌鎻鉏耡銹鋭鵞䳘賸筞筩栰暠皜皖臈餽㪟斌濶燄溼渝渲愣媿嘅庽窓牎牕窻徧袴帬裠謡彊疎壻瓌瑙䰟皷襬擕㩗㩦懃鞾幙㮣頼酧詶醻掽踫㼝盌磟鑒鍳覩睫倸㬉煗煖晻炤蹻跥跤䗬蠭寘辠鎚辤稺穉頽穨燬譭瘉顋骽猨蝯饝亷㢘滇泝遡昚闚寑躶臝羣㬪曡勦琍瓈墻墟蔡熈牓搾謌堿鹻瞅矁鍫魅髈尠尟鱻辢旂煽潄砦詧肇嫰櫈驘墪譔鞵鞌蕋橤蘂藴飃醕磊霄嘻譆跴蜨稾惪厀襃㿜癅顔䊀癄顦頟鷰藷螎蹏蟇譟賛簒彫琱鵰餹餻嬾繮粦燐簧緐幑鰐蹧粇穅臋籐鎌䥥繙飜孼曝蠏鼈馨燿蝡稬穤惷覇黯贑灨鑵戹阨剳帀巵竚鳬穽玕岅隖抃㩳蕓匲匳籨餁羗渢潙啎姙嬀㘭̀袟袠逈㒺犛偘罋膞冺㥮紲瑇荍斮斲斵毘蝱吚哶峝粃竢狥踁饟䬃烱㳄濜袵酖塤盇涖蒓碪唕鉢盋鏺倐儵皐臯衂䶊臙獧嫋褭嬝爗湼澣濬塚襢謟嫻娿縧縚粰䴸慤勅勑戞廐輙覜勗嚙囓啗噉牋椾挱㥦㥫惥陻搇壪蕚萲蕿蘐藼櫂箠賫賷啑蹠蚘痐蛕蜖瘖剉遯嬃飱謩謚椷簑枏柟檝楥矴椗嘷獋鍁粺䈰諐堘鮎疿雝秔稉慴讁槀搉叡嘠蜋甖筯篛麞鮝糉鷀緥璿髥艢鎸臕骾籑餈剹橜樐㯭艣艪罇贋蜺矙鐯憇翺饍瞖鐝羴鰌爕繦鼇鬉騣蔾䠀簮蹵㸆鱓䝔麐蔿㠣㑳潕湋澐屓薴棡嶨䮄墶峘溮䓣頍輄溳礐蘀罃䃮礄⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿⡀⡁⡂⡃⡄⡅⡆⡇⡈⡉⡊⡋⡌⡍⡎⣧⣣⣟⣚⣙⣗⣑⣔⣳⣷⣴⣼⣺⣸⣻⣵⣹㍘㍙㍚㍛㍜㍝㍞㍟㍠㍡㍢㍣㍤㍥㍦㍧㍨㍩㍪㍫㍬㍭㍮㍯㍰㏠㏡㏢㏣㏤㏥㏦㏧㏨㏩㏪㏫㏬㏭㏮㏯㏰㏱㏲㏳㏴㏵㏶㏷㏸㏹㏺㏻㏼㏽㏾㋀㋁㋂㋃㋄㋅㋆㋇㋈㋉㋊㋋㈀㈁㈂㈆㈉㈎㈍㈪㈮㈲㈶㈵㈳㈴㉦㉥㉪㉮㉲㊍㊑㊐㊕㊔㊚㋐㋔㊭㊬㊮㋙㋕☖ᴩᴪᴦᴯᴴᴳᴲ▚▛▜▞▟◻◼◿◸⎕◛◘◲◱靐龘烎嫑勥奣巭謽 -------------------------------------------------------------------------------- /font/font_set/text_lite.txt: -------------------------------------------------------------------------------- 1 | 一乙二十丁厂七卜人入八九几儿了力乃刀又三于干亏士工土才寸下大丈与万上小口巾山千乞川亿个勺久凡及夕丸么广亡门义之尸弓己已子卫也女飞刃习叉马乡丰王井开夫天无元专云扎艺木五支厅不太犬区历尤友匹车巨牙屯比互切瓦止少日中冈贝内水见午牛手毛气升长仁什片仆化仇币仍仅斤爪反介父从今凶分乏公仓月氏勿欠风丹匀乌凤勾文六方火为斗忆订计户认心尺引丑巴孔队办以允予劝双书幻玉刊示末未击打巧正扑扒功扔去甘世古节本术可丙左厉右石布龙平灭轧东卡北占业旧帅归且旦目叶甲申叮电号田由史只央兄叼叫另叨叹四生失禾丘付仗代仙们仪白仔他斥瓜乎丛令用甩印乐句匆册犯外处冬鸟务包饥主市立闪兰半汁汇头汉宁穴它讨写让礼训必议讯记永司尼民出辽奶奴加召皮边发孕圣对台矛纠母幼丝式刑动扛寺吉扣考托老执巩圾扩扫地扬场耳共芒亚芝朽朴机权过臣再协西压厌在有百存而页匠夸夺灰达列死成夹轨邪划迈毕至此贞师尘尖劣光当早吐吓虫曲团同吊吃因吸吗屿帆岁回岂刚则肉网年朱先丢舌竹迁乔伟传乒乓休伍伏优伐延件任伤价份华仰仿伙伪自血向似后行舟全会杀合兆企众爷伞创肌朵杂危旬旨负各名多争色壮冲冰庄庆亦刘齐交次衣产决充妄闭问闯羊并关米灯州汗污江池汤忙兴宇守宅字安讲军许论农讽设访寻那迅尽导异孙阵阳收阶阴防奸如妇好她妈戏羽观欢买红纤级约纪驰巡寿弄麦形进戒吞远违运扶抚坛技坏扰拒找批扯址走抄坝贡攻赤折抓扮抢孝均抛投坟抗坑坊抖护壳志扭块声把报却劫芽花芹芬苍芳严芦劳克苏杆杠杜材村杏极李杨求更束豆两丽医辰励否还歼来连步坚旱盯呈时吴助县里呆园旷围呀吨足邮男困吵串员听吩吹呜吧吼别岗帐财针钉告我乱利秃秀私每兵估体何但伸作伯伶佣低你住位伴身皂佛近彻役返余希坐谷妥含邻岔肝肚肠龟免狂犹角删条卵岛迎饭饮系言冻状亩况床库疗应冷这序辛弃冶忘闲间闷判灶灿弟汪沙汽沃泛沟没沈沉怀忧快完宋宏牢究穷灾良证启评补初社识诉诊词译君灵即层尿尾迟局改张忌际陆阿陈阻附妙妖妨努忍劲鸡驱纯纱纳纲驳纵纷纸纹纺驴纽奉玩环武青责现表规抹拢拔拣担坦押抽拐拖拍者顶拆拥抵拘势抱垃拉拦拌幸招坡披拨择抬其取苦若茂苹苗英范直茄茎茅林枝杯柜析板松枪构杰述枕丧或画卧事刺枣雨卖矿码厕奔奇奋态欧垄妻轰顷转斩轮软到非叔肯齿些虎虏肾贤尚旺具果味昆国昌畅明易昂典固忠咐呼鸣咏呢岸岩帖罗帜岭凯败贩购图钓制知垂牧物乖刮秆和季委佳侍供使例版侄侦侧凭侨佩货依的迫质欣征往爬彼径所舍金命斧爸采受乳贪念贫肤肺肢肿胀朋股肥服胁周昏鱼兔狐忽狗备饰饱饲变京享店夜庙府底剂郊废净盲放刻育闸闹郑券卷单炒炊炕炎炉沫浅法泄河沾泪油泊沿泡注泻泳泥沸波泼泽治怖性怕怜怪学宝宗定宜审宙官空帘实试郎诗肩房诚衬衫视话诞询该详建肃录隶居届刷屈弦承孟孤陕降限妹姑姐姓始驾参艰线练组细驶织终驻驼绍经贯奏春帮珍玻毒型挂封持项垮挎城挠政赴赵挡挺括拴拾挑指垫挣挤拼挖按挥挪某甚革荐巷带草茧茶荒茫荡荣故胡南药标枯柄栋相查柏柳柱柿栏树要咸威歪研砖厘厚砌砍面耐耍牵残殃轻鸦皆背战点临览竖省削尝是盼眨哄显哑冒映星昨畏趴胃贵界虹虾蚁思蚂虽品咽骂哗咱响哈咬咳哪炭峡罚贱贴骨钞钟钢钥钩卸缸拜看矩怎牲选适秒香种秋科重复竿段便俩贷顺修保促侮俭俗俘信皇泉鬼侵追俊盾待律很须叙剑逃食盆胆胜胞胖脉勉狭狮独狡狱狠贸怨急饶蚀饺饼弯将奖哀亭亮度迹庭疮疯疫疤姿亲音帝施闻阀阁差养美姜叛送类迷前首逆总炼炸炮烂剃洁洪洒浇浊洞测洗活派洽染济洋洲浑浓津恒恢恰恼恨举觉宣室宫宪突穿窃客冠语扁袄祖神祝误诱说诵垦退既屋昼费陡眉孩除险院娃姥姨姻娇怒架贺盈勇怠柔垒绑绒结绕骄绘给络骆绝绞统耕耗艳泰珠班素蚕顽盏匪捞栽捕振载赶起盐捎捏埋捉捆捐损都哲逝捡换挽热恐壶挨耻耽恭莲莫荷获晋恶真框桂档桐株桥桃格校核样根索哥速逗栗配翅辱唇夏础破原套逐烈殊顾轿较顿毙致柴桌虑监紧党晒眠晓鸭晃晌晕蚊哨哭恩唤啊唉罢峰圆贼贿钱钳钻铁铃铅缺氧特牺造乘敌秤租积秧秩称秘透笔笑笋债借值倚倾倒倘俱倡候俯倍倦健臭射躬息徒徐舰舱般航途拿爹爱颂翁脆脂胸胳脏胶脑狸狼逢留皱饿恋桨浆衰高席准座脊症病疾疼疲效离唐资凉站剖竞部旁旅畜阅羞瓶拳粉料益兼烤烘烦烧烛烟递涛浙涝酒涉消浩海涂浴浮流润浪浸涨烫涌悟悄悔悦害宽家宵宴宾窄容宰案请朗诸读扇袜袖袍被祥课谁调冤谅谈谊剥恳展剧屑弱陵陶陷陪娱娘通能难预桑绢绣验继球理捧堵描域掩捷排掉堆推掀授教掏掠培接控探据掘职基著勒黄萌萝菌菜萄菊萍菠营械梦梢梅检梳梯桶救副票戚爽聋袭盛雪辅辆虚雀堂常匙晨睁眯眼悬野啦晚啄距跃略蛇累唱患唯崖崭崇圈铜铲银甜梨犁移笨笼笛符第敏做袋悠偿偶偷您售停偏假得衔盘船斜盒鸽悉欲彩领脚脖脸脱象够猜猪猎猫猛馅馆凑减毫麻痒痕廊康庸鹿盗章竟商族旋望率着盖粘粗粒断剪兽清添淋淹渠渐混渔淘液淡深婆梁渗情惜惭悼惧惕惊惨惯寇寄宿窑密谋谎祸谜逮敢屠弹随蛋隆隐婚婶颈绩绪续骑绳维绵绸绿琴斑替款堪搭塔越趁趋超提堤博揭喜插揪搜煮援裁搁搂搅握揉斯期欺联散惹葬葛董葡敬葱落朝辜葵棒棋植森椅椒棵棍棉棚棕惠惑逼厨厦硬确雁殖裂雄暂雅辈悲紫辉敞赏掌晴暑最量喷晶喇遇喊景践跌跑遗蛙蛛蜓喝喂喘喉幅帽赌赔黑铸铺链销锁锄锅锈锋锐短智毯鹅剩稍程稀税筐等筑策筛筒答筋筝傲傅牌堡集焦傍储奥街惩御循艇舒番释禽腊脾腔鲁猾猴然馋装蛮就痛童阔善羡普粪尊道曾焰港湖渣湿温渴滑湾渡游滋溉愤慌惰愧愉慨割寒富窜窝窗遍裕裤裙谢谣谦属屡强粥疏隔隙絮嫂登缎缓编骗缘瑞魂肆摄摸填搏塌鼓摆携搬摇搞塘摊蒜勤鹊蓝墓幕蓬蓄蒙蒸献禁楚想槐榆楼概赖酬感碍碑碎碰碗碌雷零雾雹输督龄鉴睛睡睬鄙愚暖盟歇暗照跨跳跪路跟遣蛾蜂嗓置罪罩错锡锣锤锦键锯矮辞稠愁筹签简毁舅鼠催傻像躲微愈遥腰腥腹腾腿触解酱痰廉新韵意粮数煎塑慈煤煌满漠源滤滥滔溪溜滚滨粱滩慎誉塞谨福群殿辟障嫌嫁叠缝缠静碧璃墙撇嘉摧截誓境摘摔聚蔽慕暮蔑模榴榜榨歌遭酷酿酸磁愿需弊裳颗嗽蜻蜡蝇蜘赚锹锻舞稳算箩管僚鼻魄貌膜膊膀鲜疑馒裹敲豪膏遮腐瘦辣竭端旗精歉熄熔漆漂漫滴演漏慢寨赛察蜜谱嫩翠熊凳骡缩慧撕撒趣趟撑播撞撤增聪鞋蕉蔬横槽樱橡飘醋醉震霉瞒题暴瞎影踢踏踩踪蝶蝴嘱墨镇靠稻黎稿稼箱箭篇僵躺僻德艘膝膛熟摩颜毅糊遵潜潮懂额慰劈操燕薯薪薄颠橘整融醒餐嘴蹄器赠默镜赞篮邀衡膨雕磨凝辨辩糖糕燃澡激懒壁避缴戴擦鞠藏霜霞瞧蹈螺穗繁辫赢糟糠燥臂翼骤鞭覆蹦镰翻鹰警攀蹲颤瓣爆疆壤耀躁嚼嚷籍魔灌蠢霸露囊罐匕刁丐歹戈夭仑讥冗邓艾夯凸卢叭叽皿凹囚矢乍尔冯玄邦迂邢芋芍吏夷吁吕吆屹廷迄臼仲伦伊肋旭匈凫妆亥汛讳讶讹讼诀弛阱驮驯纫玖玛韧抠扼汞扳抡坎坞抑拟抒芙芜苇芥芯芭杖杉巫杈甫匣轩卤肖吱吠呕呐吟呛吻吭邑囤吮岖牡佑佃伺囱肛肘甸狈鸠彤灸刨庇吝庐闰兑灼沐沛汰沥沦汹沧沪忱诅诈罕屁坠妓姊妒纬玫卦坷坯拓坪坤拄拧拂拙拇拗茉昔苛苫苟苞茁苔枉枢枚枫杭郁矾奈奄殴歧卓昙哎咕呵咙呻咒咆咖帕账贬贮氛秉岳侠侥侣侈卑刽刹肴觅忿瓮肮肪狞庞疟疙疚卒氓炬沽沮泣泞泌沼怔怯宠宛衩祈诡帚屉弧弥陋陌函姆虱叁绅驹绊绎契贰玷玲珊拭拷拱挟垢垛拯荆茸茬荚茵茴荞荠荤荧荔栈柑栅柠枷勃柬砂泵砚鸥轴韭虐昧盹咧昵昭盅勋哆咪哟幽钙钝钠钦钧钮毡氢秕俏俄俐侯徊衍胚胧胎狰饵峦奕咨飒闺闽籽娄烁炫洼柒涎洛恃恍恬恤宦诫诬祠诲屏屎逊陨姚娜蚤骇耘耙秦匿埂捂捍袁捌挫挚捣捅埃耿聂荸莽莱莉莹莺梆栖桦栓桅桩贾酌砸砰砾殉逞哮唠哺剔蚌蚜畔蚣蚪蚓哩圃鸯唁哼唆峭唧峻赂赃钾铆氨秫笆俺赁倔殷耸舀豺豹颁胯胰脐脓逛卿鸵鸳馁凌凄衷郭斋疹紊瓷羔烙浦涡涣涤涧涕涩悍悯窍诺诽袒谆祟恕娩骏琐麸琉琅措捺捶赦埠捻掐掂掖掷掸掺勘聊娶菱菲萎菩萤乾萧萨菇彬梗梧梭曹酝酗厢硅硕奢盔匾颅彪眶晤曼晦冕啡畦趾啃蛆蚯蛉蛀唬唾啤啥啸崎逻崔崩婴赊铐铛铝铡铣铭矫秸秽笙笤偎傀躯兜衅徘徙舶舷舵敛翎脯逸凰猖祭烹庶庵痊阎阐眷焊焕鸿涯淑淌淮淆渊淫淳淤淀涮涵惦悴惋寂窒谍谐裆袱祷谒谓谚尉堕隅婉颇绰绷综绽缀巢琳琢琼揍堰揩揽揖彭揣搀搓壹搔葫募蒋蒂韩棱椰焚椎棺榔椭粟棘酣酥硝硫颊雳翘凿棠晰鼎喳遏晾畴跋跛蛔蜒蛤鹃喻啼喧嵌赋赎赐锉锌甥掰氮氯黍筏牍粤逾腌腋腕猩猬惫敦痘痢痪竣翔奠遂焙滞湘渤渺溃溅湃愕惶寓窖窘雇谤犀隘媒媚婿缅缆缔缕骚瑟鹉瑰搪聘斟靴靶蓖蒿蒲蓉楔椿楷榄楞楣酪碘硼碉辐辑频睹睦瞄嗜嗦暇畸跷跺蜈蜗蜕蛹嗅嗡嗤署蜀幌锚锥锨锭锰稚颓筷魁衙腻腮腺鹏肄猿颖煞雏馍馏禀痹廓痴靖誊漓溢溯溶滓溺寞窥窟寝褂裸谬媳嫉缚缤剿赘熬赫蔫摹蔓蔗蔼熙蔚兢榛榕酵碟碴碱碳辕辖雌墅嘁踊蝉嘀幔镀舔熏箍箕箫舆僧孵瘩瘟彰粹漱漩漾慷寡寥谭褐褪隧嫡缨撵撩撮撬擒墩撰鞍蕊蕴樊樟橄敷豌醇磕磅碾憋嘶嘲嘹蝠蝎蝌蝗蝙嘿幢镊镐稽篓膘鲤鲫褒瘪瘤瘫凛澎潭潦澳潘澈澜澄憔懊憎翩褥谴鹤憨履嬉豫缭撼擂擅蕾薛薇擎翰噩橱橙瓢蟥霍霎辙冀踱蹂蟆螃螟噪鹦黔穆篡篷篙篱儒膳鲸瘾瘸糙燎濒憾懈窿缰壕藐檬檐檩檀礁磷瞬瞳瞪曙蹋蟋蟀嚎赡镣魏簇儡徽爵朦臊鳄糜癌懦豁臀藕藤瞻嚣鳍癞瀑襟璧戳攒孽蘑藻鳖蹭蹬簸簿蟹靡癣羹鬓攘蠕巍鳞糯譬霹躏髓蘸镶瓤矗·!@#¥%…&*()—+-=、|【{}】;:‘“”,《。》/?℃℉£ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890~`!$^()_[]\;:'",<.>?ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω︵︶︹︺︿﹀︽︾﹁﹂﹃﹄︻︼︷︸АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюāáǎàēéěèīíǐìōóǒòūúǔùǖǘǚǜüêɑńňǹɡㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩ -------------------------------------------------------------------------------- /font/text_full_16px_2312.v3.bmf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/text_full_16px_2312.v3.bmf -------------------------------------------------------------------------------- /font/text_full_24px_2312.v3.bmf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/text_full_24px_2312.v3.bmf -------------------------------------------------------------------------------- /font/text_full_32px_2312.v3.bmf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/text_full_32px_2312.v3.bmf -------------------------------------------------------------------------------- /font/text_full_8px_2312.v3.bmf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/text_full_8px_2312.v3.bmf -------------------------------------------------------------------------------- /font/text_lite_16px_2312.v3.bmf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/text_lite_16px_2312.v3.bmf -------------------------------------------------------------------------------- /font/text_lite_24px_2312.v3.bmf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/text_lite_24px_2312.v3.bmf -------------------------------------------------------------------------------- /font/text_lite_32px_2312.v3.bmf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/text_lite_32px_2312.v3.bmf -------------------------------------------------------------------------------- /font/text_lite_8px_2312.v3.bmf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/font/text_lite_8px_2312.v3.bmf -------------------------------------------------------------------------------- /img/test.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/img/test.bmp -------------------------------------------------------------------------------- /img/test.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/img/test.pbm -------------------------------------------------------------------------------- /lib/easydisplay.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnygeeker/micropython-easydisplay/7c0186bdc79e891fb636ab59cfbddea09fdef91d/lib/easydisplay.mpy -------------------------------------------------------------------------------- /lib/easydisplay.py: -------------------------------------------------------------------------------- 1 | # Github: https://github.com/funnygeeker/micropython-easydisplay 2 | # Author: funnygeeker 3 | # Licence: MIT 4 | # Date: 2023/2/18 5 | # 6 | # 参考项目: 7 | # https://github.com/AntonVanke/micropython-ufont 8 | # https://github.com/boochow/MicroPython-ST7735/blob/master/tftbmp.py 9 | # 10 | # 参考资料: 11 | # PBM图像显示:https://www.bilibili.com/video/av798158808 12 | # PBM文件格式:https://www.cnblogs.com/SeekHit/p/7055748.html 13 | # PBM文件转换:https://blog.csdn.net/jd3096/article/details/121319042 14 | # 灰度化、二值化:https://blog.csdn.net/li_wen01/article/details/72867057 15 | # Framebuffer 的 Palette: https://forum.micropython.org/viewtopic.php?t=12857 16 | from io import BytesIO 17 | from struct import unpack 18 | from framebuf import FrameBuffer, MONO_HLSB, RGB565 19 | 20 | 21 | class EasyDisplay: 22 | READ_SIZE = 32 # Limit the picture read size to prevent memory errors in low-performance development boards 23 | 24 | def __init__(self, display, 25 | color_type, 26 | font: str = None, 27 | key: int = -1, 28 | show: bool = None, 29 | clear: bool = None, 30 | invert: bool = False, 31 | color: int = 0xFFFF, 32 | bg_color: int = 0, 33 | size: int = None, 34 | auto_wrap: bool = False, 35 | half_char: bool = True, 36 | line_spacing: int = 0, 37 | *args, **kwargs): 38 | """ 39 | 初始化 EasyDisplay 40 | 41 | Args: 42 | display: The display instance 43 | 表示显示的实例 44 | color_type: Color type of screen, "MONO" or "RGB565" 45 | 屏幕的颜色类型,"MONO" 或者 “RGB565” 46 | font: The location of the font file 47 | 字体文件位置 48 | key: The specified color will be treated as transparent (only applicable for Framebuffer mode) 49 | 指定的颜色将被视为透明(仅适用于 Framebuffer 模式) 50 | show: Show immediately (only applicable for Framebuffer mode) 51 | 立即显示(仅适用于 Framebuffer 模式) 52 | clear: Clear the screen 53 | 清理屏幕 54 | invert: Invert colors 55 | 反转颜色 56 | color_type: Image format, "RGB565" for RGB565 screen, "MONO" for black and white screen 57 | 图像格式,RGB565 屏幕用 "RGB565",黑白 屏幕用 "MONO" 58 | color: The main color of the image (only effective when displaying black and white images on a color screen) 59 | 图像主体颜色(仅彩色屏幕显示黑白图像时生效) 60 | bg_color: The background color of the image (only effective when displaying black and white images on a color screen) 61 | 图像背景颜色(仅彩色屏幕显示黑白图像时生效) 62 | size: Font size 63 | 文本字体大小 64 | auto_wrap: Automatically wrap text 65 | 文本自动换行 66 | half_char: Display ASCII characters in half width 67 | 半宽显示 ASCII 字符 68 | line_spacing: Line spacing for text 69 | 文本行间距 70 | """ 71 | self.display = display 72 | self._buffer = hasattr(display, 'buffer') # buffer: 驱动是否使用了帧缓冲区,False(SPI 直接驱动) / True(Framebuffer) 73 | self._font = None 74 | self._key = key 75 | self._show = show 76 | self._clear = clear 77 | self.invert = invert 78 | self.color_type = color_type 79 | self.color = color 80 | self.bg_color = bg_color 81 | self.size = size 82 | self.auto_wrap = auto_wrap 83 | self.half_char = half_char 84 | self.line_spacing = line_spacing 85 | self.font_size = None 86 | self.font_bmf_info = None 87 | self.font_version = None 88 | self.font_file = None 89 | self.font_map_mode = None 90 | self.font_start_bitmap = None 91 | self.font_bitmap_size = None 92 | if font: 93 | self.load_font(font) 94 | 95 | # Framebuffer Function: https://docs.micropython.org/en/latest/library/framebuf.html 96 | def fill(self, *args, **kwargs): 97 | self.display.fill(*args, **kwargs) 98 | 99 | def pixel(self, *args, **kwargs): 100 | return self.display.pixel(*args, **kwargs) 101 | 102 | def hline(self, *args, **kwargs): 103 | self.display.hline(*args, **kwargs) 104 | 105 | def vline(self, *args, **kwargs): 106 | self.display.vline(*args, **kwargs) 107 | 108 | def line(self, *args, **kwargs): 109 | self.display.line(*args, **kwargs) 110 | 111 | def rect(self, *args, **kwargs): 112 | self.display.rect(*args, **kwargs) 113 | 114 | def fill_rect(self, *args, **kwargs): 115 | self.display.fill_rect(*args, **kwargs) 116 | 117 | def scroll(self, *args, **kwargs): 118 | self.display.scroll(*args, **kwargs) 119 | 120 | def blit(self, *args, **kwargs): 121 | self.display.blit(*args, **kwargs) 122 | 123 | def ellipse(self, *args, **kwargs): 124 | self.display.ellipse(*args, **kwargs) 125 | 126 | def poly(self, *args, **kwargs): 127 | self.display.poly(*args, **kwargs) 128 | 129 | # Only partial screen driver support 130 | def circle(self, *args, **kwargs): 131 | self.display.circle(*args, **kwargs) 132 | 133 | def fill_circle(self, *args, **kwargs): 134 | self.display.fill_circle(*args, **kwargs) 135 | 136 | def clear(self): 137 | """ 138 | Clear screen 139 | """ 140 | self.display.fill(0) 141 | 142 | def show(self): 143 | """ 144 | Display 145 | """ 146 | try: 147 | self.display.show() 148 | except AttributeError: 149 | pass 150 | 151 | @staticmethod 152 | def rgb565_color(r, g, b): 153 | """ 154 | Convert red, green and blue values (0-255) into a 16-bit 565 encoding. 155 | """ 156 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 157 | 158 | def _get_index(self, word: str) -> int: 159 | """ 160 | Get Text Index 获取文字索引 161 | 162 | Args: 163 | word: Character 字符 164 | """ 165 | word_code = ord(word) 166 | start = 0x10 167 | end = self.font_start_bitmap 168 | _seek = self._font.seek 169 | _font_read = self._font.read 170 | while start <= end: 171 | mid = ((start + end) // 4) * 2 172 | _seek(mid, 0) 173 | target_code = unpack(">H", _font_read(2))[0] 174 | if word_code == target_code: 175 | return (mid - 16) >> 1 176 | elif word_code < target_code: 177 | end = mid - 2 178 | else: 179 | start = mid + 2 180 | return -1 181 | 182 | # @timeit 183 | @staticmethod 184 | def _hlsb_font_size(bytearray_data: bytearray, new_size: int, old_size: int) -> bytearray: 185 | """ 186 | Scale HLSB Characters 缩放字符 187 | 188 | Args: 189 | bytearray_data: Source char data 源字符数据 190 | new_size: New char size 新字符大小 191 | old_size: Old char size 旧字符大小 192 | 193 | Returns: 194 | Scaled character data 缩放后的数据 195 | """ 196 | r = range(new_size) # Preload functions to avoid repeated execution and improve efficiency 197 | if old_size == new_size: 198 | return bytearray_data 199 | _t = bytearray(new_size * ((new_size >> 3) + 1)) 200 | _new_index = -1 201 | for _col in r: 202 | for _row in r: 203 | if _row % 8 == 0: 204 | _new_index += 1 205 | _old_index = int(_col / (new_size / old_size)) * old_size + int(_row / (new_size / old_size)) 206 | _t[_new_index] = _t[_new_index] | ( 207 | (bytearray_data[_old_index >> 3] >> (7 - _old_index % 8) & 1) << (7 - _row % 8)) 208 | return _t 209 | 210 | def get_bitmap(self, word: str) -> bytes: 211 | """ 212 | Get Dot Matrix Image 获取点阵图 213 | 214 | Args: 215 | word: Single character 单个字符 216 | 217 | Returns: 218 | Bytes representing the dot matrix image of the character 字符点阵 219 | """ 220 | index = self._get_index(word) 221 | if index == -1: 222 | 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' \ 223 | b'?\xff?\xff\xff\xff\xff' # Returns the question mark icon 224 | self._font.seek(self.font_start_bitmap + index * self.font_bitmap_size, 0) 225 | return self._font.read(self.font_bitmap_size) 226 | 227 | def load_font(self, file: str): 228 | """ 229 | Load Font File 加载字体文件 230 | 231 | Args: 232 | file: Path to the font file 文件路径 233 | """ 234 | self.font_file = file 235 | self._font = open(file, "rb") 236 | # 获取字体文件信息 237 | # 字体文件信息大小 16 byte ,按照顺序依次是 238 | # 文件标识 2 byte 239 | # 版本号 1 byte 240 | # 映射方式 1 byte 241 | # 位图开始字节 3 byte 242 | # 字号 1 byte 243 | # 单字点阵字节大小 1 byte 244 | # 保留 7 byte 245 | self.font_bmf_info = self._font.read(16) 246 | # 判断字体是否正确,文件头和常用的图像格式 BMP 相同,需要添加版本验证来辅助验证 247 | if self.font_bmf_info[0:2] != b"BM": 248 | raise TypeError("Incorrect font file format: {}".format(file)) 249 | self.font_version = self.font_bmf_info[2] 250 | if self.font_version != 3: 251 | raise TypeError("Incorrect font file version: {}".format(self.font_version)) 252 | # 映射方式,目前映射方式并没有加以验证,原因是 MONO 最易于处理 253 | self.font_map_mode = self.font_bmf_info[3] 254 | # 位图开始字节,位图数据位于文件尾,需要通过位图开始字节来确定字体数据实际位置 255 | self.font_start_bitmap = unpack(">I", b'\x00' + self.font_bmf_info[4:7])[0] 256 | # 字体大小,默认的文字字号,用于缩放方面的处理 257 | self.font_size = self.font_bmf_info[7] 258 | if self.size is None: 259 | self.size = int(self.font_size) 260 | # 点阵所占字节,用来定位字体数据位置 261 | self.font_bitmap_size = self.font_bmf_info[8] 262 | 263 | def text(self, s: str, x: int, y: int, 264 | color: int = None, bg_color: int = None, size: int = None, 265 | half_char: bool = None, auto_wrap: bool = None, show: bool = None, clear: bool = None, 266 | key: bool = None, invert: bool = None, line_spacing: int = None, *args, **kwargs): 267 | """ 268 | Args: 269 | s: String 270 | 字符串 271 | x: X-coordinate of the string 272 | x 坐标 273 | y: Y-coordinate of the string 274 | y 坐标 275 | color: Text color (RGB565 range is 0-65535, MONO range is 0 and greater than zero - typically use 1) 276 | 文字颜色 (RGB565 范围为 0-65535,MONO 范围为 0 和 大于零-通常使用 1) 277 | bg_color: Text background color (RGB565 range is 0-65535, MONO range is 0 and greater than zero - typically use 1)\ 278 | 文字背景颜色 (RGB565 范围为 0-65535,MONO 范围为 0 和 大于零-通常使用 1) 279 | key: Transparent color, when color matches key, it becomes transparent (only applicable in Framebuffer mode) 280 | 透明色,当颜色与 key 相同时则透明 (仅适用于 Framebuffer 模式) 281 | size: Text size 282 | 文字大小 283 | show: Show immediately 284 | 立即显示 285 | clear: Clear buffer / Clear screen 286 | 清理缓冲区 / 清理屏幕 287 | invert: Invert (MONO) 288 | 逆置(MONO) 289 | auto_wrap: Enable auto wrap 290 | 自动换行 291 | half_char: Display ASCII characters in half width 292 | 半宽显示 ASCII 字符 293 | line_spacing: Line spacing 294 | 行间距 295 | """ 296 | if color is None: 297 | color = self.color 298 | if bg_color is None: 299 | bg_color = self.bg_color 300 | if key is None: 301 | key = self._key 302 | if size is None: 303 | size = self.size 304 | if show is None: 305 | show = self._show 306 | if clear is None: 307 | clear = self._clear 308 | if invert is None: 309 | invert = self.invert 310 | if auto_wrap is None: 311 | auto_wrap = self.auto_wrap 312 | if half_char is None: 313 | half_char = self.half_char 314 | color_type = self.color_type 315 | if line_spacing is None: 316 | line_spacing = self.line_spacing 317 | 318 | # 如果没有指定字号则使用默认字号 319 | font_size = size or self.font_size 320 | # 记录初始的 x 位置 321 | init_x = x 322 | 323 | try: 324 | _seek = self._font.seek 325 | except AttributeError: 326 | raise AttributeError("The font file is not loaded... Did you forgot?") 327 | 328 | dp = self.display 329 | font_offset = font_size // 2 330 | 331 | # 颜色反转 332 | if invert: 333 | color, bg_color = bg_color, color 334 | 335 | # 配置调色板 336 | if color_type == "MONO": 337 | palette = FrameBuffer(bytearray(1), 2, 1, MONO_HLSB) # MONO pixels occupy 1 byte for every 8 pixels 338 | elif color_type == "RGB565": 339 | palette = FrameBuffer(bytearray(4), 2, 1, RGB565) # RGB565 pixels occupy 2 bytes for every 1 pixel 340 | else: 341 | raise KeyError("Unsupported color_type: {}".format(color_type)) 342 | palette.pixel(1, 0, color) 343 | palette.pixel(0, 0, bg_color) 344 | 345 | # 清屏 346 | if clear: 347 | self.clear() 348 | 349 | for char in s: 350 | if auto_wrap and ((x + font_offset > dp.width and ord(char) < 128 and half_char) or 351 | (x + font_size > dp.width and (not half_char or ord(char) > 128))): 352 | y += font_size + line_spacing 353 | x = init_x 354 | 355 | # 对控制字符的处理 356 | if char == '\n': 357 | y += font_size + line_spacing 358 | x = init_x 359 | continue 360 | elif char == '\t': 361 | x = ((x // font_size) + 1) * font_size + init_x % font_size 362 | continue 363 | elif ord(char) < 16: 364 | continue 365 | 366 | # 超过范围的字符不会显示 367 | if x > dp.width or y > dp.height: 368 | continue 369 | 370 | # 获取字体的点阵数据 371 | byte_data = self.get_bitmap(char) 372 | 373 | # 准备并缩放字符数据 374 | byte_data = bytearray(byte_data) 375 | if font_size != self.font_size: 376 | byte_data = self._hlsb_font_size(byte_data, font_size, self.font_size) 377 | 378 | # 显示字符 379 | fbuf = FrameBuffer(byte_data, font_size, font_size, MONO_HLSB) 380 | if self._buffer: # FrameBuffer Driven 381 | dp.blit(fbuf, x, y, key, palette) 382 | else: 383 | if color_type == "RGB565": 384 | n_fbuf = FrameBuffer(bytearray(font_size * font_size * 2), font_size, font_size, RGB565) 385 | n_fbuf.blit(fbuf, 0, 0, key, palette) # Render black and white pixels to color 386 | elif color_type == "MONO": 387 | n_fbuf = fbuf # Not tested 388 | else: 389 | raise ValueError("Unsupported color_type: {}".format(color_type)) 390 | dp.set_window(x, y, x + font_size - 1, y + font_size - 1) 391 | dp.write_data(n_fbuf) 392 | 393 | # 英文字符半格显示 394 | if ord(char) < 128 and half_char: 395 | x += font_offset 396 | else: 397 | x += font_size 398 | 399 | self.show() if show else 0 400 | 401 | def ppm(self, *args, **kwargs): 402 | self.pbm(*args, **kwargs) 403 | 404 | def pbm(self, file, x, y, key: int = None, show: bool = None, clear: bool = None, invert: bool = False, 405 | color: int = None, bg_color: int = None): 406 | """ 407 | Display PBM / PPM Image 408 | 显示 pbm / ppm 图片 409 | 410 | # You can use the Pillow library in python3 to convert the image to PBM format. For example: 411 | # 您可以通过使用 python3 的 pillow 库将图片转换为 pbm 格式,比如: 412 | # convert_type = "1" # "1" for black and white image, "RGBA" for colored image 413 | # convert_type = "1" # 1 为黑白图像,RGBA 为彩色图像 414 | # 415 | # from PIL import Image 416 | # with Image.open("filename.png", "r") as img: 417 | # img2 = img.convert(convert_type) 418 | # img2.save("filename.pbm") 419 | 420 | Args: 421 | file: PBM file 422 | pbm 文件 423 | File path (str) 424 | 文件路径 425 | Raw data (BytesIO) 426 | 原始数据 427 | x: X-coordinate 428 | X 坐标 429 | y: Y-coordinate 430 | Y 坐标 431 | key: Specified color to be treated as transparent (only applicable in Framebuffer mode) 432 | 指定的颜色将被视为透明(仅适用于 Framebuffer 模式) 433 | show: Show immediately (only applicable in Framebuffer mode) 434 | 立即显示(仅适用于 Framebuffer 模式) 435 | clear: Clear screen 436 | 清理屏幕 437 | invert: Invert colors 438 | 反转颜色 439 | color: Image main color (only effective when displaying black and white image on a color screen) 440 | 图像主体颜色(仅彩色屏幕显示黑白图像时生效) 441 | bg_color: Image background color (only effective when displaying black and white image on a color screen) 442 | 图像背景颜色(仅彩色屏幕显示黑白图像时生效) 443 | """ 444 | if key is None: 445 | key = self._key 446 | if show is None: 447 | show = self._show 448 | if clear is None: 449 | clear = self._clear 450 | if invert is None: 451 | invert = self.invert 452 | color_type = self.color_type 453 | if color is None: 454 | color = self.color 455 | if bg_color is None: 456 | bg_color = self.bg_color 457 | if clear: # 清屏 458 | self.clear() 459 | dp = self.display 460 | if isinstance(file, BytesIO): 461 | func = file 462 | else: 463 | func = open(file, "rb") 464 | with func as f: 465 | file_format = f.readline() # 获取文件格式 466 | _width, _height = [int(value) for value in f.readline().split()] # 获取图片的宽度和高度 467 | f_read = f.read 468 | if file_format == b"P4\n": # P4 位图 二进制 469 | # 颜色反转 470 | if invert: 471 | color, bg_color = bg_color, color 472 | # 配置调色板 473 | if color_type == "MONO": 474 | palette = FrameBuffer(bytearray(1), 2, 1, MONO_HLSB) 475 | elif color_type == "RGB565": 476 | palette = FrameBuffer(bytearray(4), 2, 1, RGB565) 477 | else: 478 | raise KeyError("Unsupported color_type: {}".format(color_type)) 479 | palette.pixel(1, 0, color) 480 | palette.pixel(0, 0, bg_color) 481 | 482 | if self._buffer: # Framebuffer 模式 483 | data = bytearray(f_read()) # 读取并显示图像 484 | fbuf = FrameBuffer(data, _width, _height, MONO_HLSB) 485 | dp.blit(fbuf, x, y, key, palette) 486 | else: # 直接驱动 487 | write_data = dp.write_data 488 | dp.set_window(x, y, x + _width - 1, y + _height - 1) # 设置窗口 489 | buffer_size = self.READ_SIZE 490 | width = buffer_size * 8 491 | # Use different types of buffers according to different color types 492 | if color_type == "RGB565": 493 | data_fbuf = FrameBuffer(bytearray(buffer_size * 16), width, 1, RGB565) 494 | elif color_type == "MONO": 495 | data_fbuf = FrameBuffer(bytearray(buffer_size), width, 1, MONO_HLSB) # Not tested 496 | else: 497 | raise ValueError("Unsupported color_type: {}".format(color_type)) 498 | data_fbuf_blit = data_fbuf.blit 499 | # Read a picture several times, taking a part of it each time 500 | data = bytearray(f_read(buffer_size)) 501 | while data: 502 | fbuf = FrameBuffer(data, width, 1, MONO_HLSB) 503 | data_fbuf_blit(fbuf, 0, 0, key, palette) # Render MONO pixels into RGB565 pixels 504 | len_data = len(data) 505 | if len_data < buffer_size: # Limit the data sent to no more than the Buffer size, so as to avoid data overflow and affect the display 506 | if color_type == "RGB565": 507 | fbuf_data = bytearray(data_fbuf)[:len_data * 16] 508 | elif color_type == "MONO": 509 | fbuf_data = bytearray(data_fbuf)[:len_data] 510 | else: 511 | raise ValueError("Unsupported color_type: {}".format(color_type)) 512 | else: 513 | fbuf_data = bytearray(data_fbuf) 514 | write_data(fbuf_data) 515 | data = bytearray(f_read(buffer_size)) 516 | 517 | elif file_format == b"P6\n": # P6 像素图 二进制 518 | max_pixel_value = f.readline() # 获取最大像素值 519 | r_height = range(_height) 520 | r_width = range(_width) 521 | color_bytearray = bytearray(3) # 为变量预分配内存 522 | f_rinto = f.readinto 523 | try: 524 | dp_color = dp.color 525 | except AttributeError: 526 | dp_color = self.rgb565_color 527 | dp_pixel = dp.pixel 528 | if self._buffer: # Framebuffer 模式 529 | if color_type == "RGB565": 530 | buffer = bytearray(_width * 2) 531 | for _y in r_height: # 逐行显示图片 532 | for _x in r_width: 533 | f_rinto(color_bytearray) 534 | r, g, b = color_bytearray[0], color_bytearray[1], color_bytearray[2] 535 | if invert: 536 | r = 255 - r 537 | g = 255 - g 538 | b = 255 - b 539 | if color_type == "RGB565": 540 | buffer[_x * 2: (_x + 1) * 2] = dp_color(r, g, b).to_bytes(2, 'big') # 通过索引赋值 541 | elif color_type == "MONO": 542 | _color = int((r + g + b) / 3) >= 127 543 | if _color: 544 | _color = color 545 | else: 546 | _color = bg_color 547 | if _color != key: # 不显示指定颜色 548 | dp_pixel(_x + x, _y + y, _color) 549 | if color_type == "RGB565": 550 | fbuf = FrameBuffer(buffer, _width, 1, RGB565) 551 | dp.blit(fbuf, x, y + _y, key) 552 | else: # 直接驱动 553 | dp.set_window(x, y, x + _width - 1, y + _height - 1) # 设置窗口 554 | buffer = bytearray(_width * 2) 555 | for _y in r_height: # 逐行显示图片 556 | for _x in r_width: 557 | color_bytearray = f_read(3) 558 | r, g, b = color_bytearray[0], color_bytearray[1], color_bytearray[2] 559 | if invert: 560 | r = 255 - r 561 | g = 255 - g 562 | b = 255 - b 563 | if color_type == "RGB565": 564 | buffer[_x * 2: (_x + 1) * 2] = dp_color( 565 | r, g, b).to_bytes(2, 'big') # 通过索引赋值 566 | elif color_type == "MONO": 567 | _color = int((r + g + b) / 3) >= 127 568 | if _color: 569 | _color = color 570 | else: 571 | _color = bg_color 572 | if _color != key: # 不显示指定颜色 573 | dp_pixel(_x + x, _y + y, _color) 574 | if color_type == "RGB565": 575 | dp.write_data(buffer) 576 | else: 577 | raise TypeError("Unsupported File Format Type.") 578 | 579 | self.show() if show else 0 # 立即显示 580 | 581 | def bmp(self, file, x, y, key: int = None, show: bool = None, clear: bool = None, invert: bool = False, 582 | color: int = None, bg_color: int = None): 583 | """ 584 | Display BMP Image 显示 bmp 图片 585 | 586 | # You can convert the image to `24-bit` `bmp` format using the Paint application in Windows. 587 | # Alternatively, you can use software like `Image2Lcd` to convert the image to `24-bit` `bmp` format (horizontal scan, includes image header data, 24-bit grayscale). 588 | # 您可以通过使用 windows 的 画图 将图片转换为 `24-bit` 的 `bmp` 格式 589 | # 也可以使用 `Image2Lcd` 这款软件将图片转换为 `24-bit` 的 `bmp` 格式(水平扫描,包含图像头数据,灰度二十四位) 590 | 591 | Args: 592 | file: bmp file 593 | bmp 文件 594 | File path (str) 595 | 文件路径 596 | Raw data (BytesIO) 597 | 原始数据 598 | x: X-coordinate 599 | X 坐标 600 | y: Y-coordinate 601 | Y 坐标 602 | key: Specified color to be treated as transparent (only applicable in Framebuffer mode) 603 | 指定的颜色将被视为透明(仅适用于 Framebuffer 模式) 604 | show: Show immediately (only applicable in Framebuffer mode) 605 | 立即显示(仅适用于 Framebuffer 模式) 606 | clear: Clear screen 607 | 清理屏幕 608 | invert: Invert colors 609 | 反转颜色 610 | color: Image main color (only effective when displaying black and white image on a color screen) 611 | 图像主体颜色(仅彩色图片显示以黑白形式显示时生效) 612 | bg_color: Image background color (only effective when displaying black and white image on a color screen) 613 | 图像背景颜色(仅彩色图片显示以黑白形式显示时生效) 614 | """ 615 | if key is None: 616 | key = self._key 617 | if show is None: 618 | show = self._show 619 | if clear is None: 620 | clear = self._clear 621 | if invert is None: 622 | invert = self.invert 623 | color_type = self.color_type 624 | if color is None: 625 | color = self.color 626 | if bg_color is None: 627 | bg_color = self.bg_color 628 | if isinstance(file, BytesIO): 629 | func = file 630 | else: 631 | func = open(file, "rb") 632 | with func as f: 633 | f_read = f.read 634 | f_rinto = f.readinto 635 | f_seek = f.seek 636 | f_tell = f.tell() 637 | dp = self.display 638 | try: 639 | dp_color = dp.color 640 | except AttributeError: 641 | dp_color = self.rgb565_color 642 | dp_pixel = dp.pixel 643 | if f_read(2) == b'BM': # 检查文件头 644 | dummy = f_read(8) # 文件大小占四个字节,文件作者占四个字节,file size(4), creator bytes(4) 645 | int_fb = int.from_bytes 646 | offset = int_fb(f_read(4), 'little') # 像素存储位置占四个字节 647 | hdrsize = int_fb(f_read(4), 'little') # DIB header 占四个字节 648 | _width = int_fb(f_read(4), 'little') # 图像宽度 649 | _height = int_fb(f_read(4), 'little') # 图像高度 650 | if int_fb(f_read(2), 'little') == 1: # 色彩平面数 planes must be 1 651 | depth = int_fb(f_read(2), 'little') # 像素位数 652 | # 转换时只支持二十四位彩色,不压缩的图像 653 | if depth == 24 and int_fb(f_read(4), 'little') == 0: # compress method == uncompressed 654 | row_size = (_width * 3 + 3) & ~3 655 | if _height < 0: 656 | _height = -_height 657 | flip = False 658 | else: 659 | flip = True 660 | if _width > dp.width: # Limit the maximum size of image display 661 | _width = dp.width 662 | if _height > dp.height: 663 | _height = dp.height 664 | _color_bytearray = bytearray(3) # 像素的二进制颜色 665 | if clear: # 清屏 666 | self.clear() 667 | buffer = bytearray(_width * 2) 668 | self_buf = self._buffer 669 | if not self_buf: 670 | dp.set_window(x, y, x + _width - 1, y + _height - 1) # 设置窗口 671 | r_width = range(_width) 672 | r_height = range(_height) 673 | for _y in r_height: 674 | if flip: 675 | pos = offset + (_height - 1 - _y) * row_size 676 | else: 677 | pos = offset + _y * row_size 678 | if f_tell != pos: 679 | f_seek(pos) # 调整指针位置 680 | for _x in r_width: 681 | f_rinto(_color_bytearray) 682 | r, g, b = _color_bytearray[2], _color_bytearray[1], _color_bytearray[0] 683 | if invert: # 颜色反转 684 | r = 255 - r 685 | g = 255 - g 686 | b = 255 - b 687 | if self_buf: # Framebuffer 模式 688 | if color_type == "RGB565": 689 | buffer[_x * 2: (_x + 1) * 2] = dp_color( 690 | r, g, b).to_bytes(2, 'big') # 通过索引赋值 691 | elif color_type == "MONO": 692 | _color = int((r + g + b) / 3) >= 127 693 | if _color: 694 | _color = color 695 | else: 696 | _color = bg_color 697 | if _color != key: # 不显示指定颜色 698 | dp_pixel(_x + x, _y + y, _color) 699 | else: 700 | if color_type == "RGB565": 701 | buffer[_x * 2: (_x + 1) * 2] = dp_color( 702 | r, g, b).to_bytes(2, 'big') # 通过索引赋值 703 | elif color_type == "MONO": 704 | _color = int((r + g + b) / 3) >= 127 705 | if _color: 706 | _color = color 707 | else: 708 | _color = bg_color 709 | if _color != key: # 不显示指定颜色 710 | dp_pixel(_x + x, _y + y, _color) 711 | 712 | if color_type == "RGB565": 713 | if self_buf: 714 | fbuf = FrameBuffer(buffer, _width, 1, RGB565) 715 | dp.blit(fbuf, x, y + _y, key) 716 | else: 717 | dp.write_data(buffer) 718 | 719 | self.show() if show else 0 # 立即显示 720 | else: 721 | raise TypeError("Unsupported file type: only 24-bit uncompressed BMP images are supported.") 722 | else: 723 | raise TypeError("Unsupported file type: only BMP images are supported.") 724 | 725 | def dat(self, file, x, y, key=None): 726 | """ 727 | Display screen raw data file, with extremely high efficiency, only supports RGB565 format. 728 | 显示表示屏幕原始数据的文件,拥有极高的效率,仅支持 RGB565 格式 729 | 730 | Args: 731 | file: dat file dat 文件 732 | File path (str) 文件路径 733 | Raw data (BytesIO) 原始数据 734 | x: X-coordinate X坐标 735 | y: Y-coordinate Y 坐标 736 | key: Specified color to be treated as transparent (only applicable in Framebuffer mode) 737 | 指定的颜色将被视为透明(仅适用于 Framebuffer 模式) 738 | """ 739 | if key is None: 740 | key = self._key 741 | if isinstance(file, BytesIO): 742 | func = file 743 | else: 744 | func = open(file, "rb") 745 | with func as f: 746 | f_readline = f.readline 747 | f_read = f.read 748 | file_head = f_readline().rstrip(b'\n') 749 | if file_head == b'EasyDisplay': # 文件头 750 | version = f_readline().rstrip(b'\n') 751 | if version == b'V1': # 文件格式版本 752 | _width, _height = f_readline().rstrip(b'\n').split(b' ') 753 | _width, _height = int(_width), int(_height) 754 | if self._buffer: # Framebuffer 模式 755 | data = f_read(_width) 756 | dp_blit = self.display.blit 757 | y_offset = 0 758 | while data: 759 | buf = FrameBuffer(bytearray(data), _width, 1, RGB565) 760 | dp_blit(buf, x, y + y_offset, key) 761 | data = f_read(_width) 762 | y_offset += 1 763 | else: # 直接驱动模式 764 | size = self.READ_SIZE * 10 765 | data = f_read(size) 766 | dp_write = self.display.write_data 767 | self.display.set_window(x, y, x + _width - 1, y + _height - 1) 768 | while data: 769 | dp_write(data) 770 | data = f_read(size) 771 | else: 772 | raise TypeError("Unsupported Version: {}".format(version)) 773 | 774 | else: 775 | try: 776 | raise TypeError("Unsupported File Type: {}".format(file_head)) 777 | except: 778 | raise TypeError("Unsupported File Type!") 779 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # 这是一个使用示例 This is an example of usage 2 | import time 3 | from machine import SPI, Pin 4 | from driver import st7735_buf 5 | from lib.easydisplay import EasyDisplay 6 | 7 | # ESP32S3 & ST7735 8 | spi = SPI(1, baudrate=20000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(17)) 9 | dp = st7735_buf.ST7735(width=128, height=128, spi=spi, cs=14, dc=15, res=16, rotate=1, bl=13, invert=False, rgb=False) 10 | ed = EasyDisplay(dp, "RGB565", font="/text_lite_16px_2312.v3.bmf", show=True, color=0xFFFF, clear=True) 11 | 12 | ed.bmp("/img/test.bmp", 0, 0) 13 | time.sleep(3) 14 | ed.pbm("/img/test.pbm", 0, 0) 15 | time.sleep(3) 16 | ed.text("你好,世界!\nHello World!\nこんにちは、世界!", 0, 0) 17 | 18 | # 更多高级使用方式详见源码注释:/lib/easydisplay.py 19 | # For more advanced usage, please refer to the source code comments: /lib/easydisplay.py -------------------------------------------------------------------------------- /tool/README.md: -------------------------------------------------------------------------------- 1 | ## 工具(Chinese) 2 | 3 | ### 说明 4 | - 这里包含了一些 `Python` 程序,可以用它们将您的的图片或者视频转换为 `micropython-easydisplay` 所支持的图片格式 5 | - 请注意,这是 `Python` 程序,并不能在 `MicroPython` 上面运行,请保证您的 `Python` 版本大于 `3.8` 6 | 7 | 8 | ## Tools (English) 9 | 10 | ### Description 11 | - This section includes some Python programs that can be used to convert your images or videos to the image format supported by `micropython-easydisplay`. 12 | - Please note that these are Python programs and cannot be run on MicroPython. Please ensure that your Python version is greater than `3.8`. -------------------------------------------------------------------------------- /tool/image_tools.py: -------------------------------------------------------------------------------- 1 | # 您可以通过使用 python3 的 pillow 库将图片转换为 pbm 格式,比如: 2 | # convert_type = "1" # 1 为黑白图像,RGBA 为彩色图像 3 | # from PIL import Image 4 | # with Image.open("文件名.png", "r") as img: 5 | # img2 = img.convert(convert_type) 6 | # img2.save("文件名.pbm") 7 | 8 | import os 9 | 10 | try: 11 | from PIL import Image 12 | except ImportError: 13 | os.system('pip3 install pillow -i https://mirrors.aliyun.com/pypi/simple/') 14 | from PIL import Image 15 | 16 | 17 | def _get_output_path(file: str, path: str, extension: str = None): 18 | """ 19 | 根据文件路径和输出路径,获取输出文件的路径 20 | 21 | Args: 22 | file: 文件路径 23 | path: 输出路径 24 | extension: 文件扩展名,为 None 时保持扩展名不变 25 | 26 | Returns: 27 | 文件名 28 | """ 29 | file_name = file.split("/")[-1] # 除去原路径,保留文件名 30 | file_name = file_name.split(".") 31 | if extension: 32 | if len(file_name) > 1: # 若需要更改扩展名且已存在文件扩展名,则去除 33 | del file_name[-1] 34 | file_name[-1] += ".{}".format(extension.lstrip(".")) # 增加扩展名 35 | 36 | file_name = ".".join(file_name) # 考虑文件名含多个 "." 的情况 37 | return "{}/{}".format(path.rstrip("/"), file_name) # 将文件名与输出路径合并 38 | 39 | 40 | def _color(r: int, g: int, b: int) -> int: 41 | """ 42 | 将 (0-255) 值的:红绿蓝 转换为 16-bit 的 565 编码 43 | 44 | Args: 45 | r: 红 46 | g: 绿 47 | b: 蓝 48 | 49 | Returns: 50 | color (int): 0-65535 51 | """ 52 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 53 | 54 | 55 | def _calculate_palette(color, bg_color) -> tuple: 56 | """ 57 | 通过 主体颜色 和 背景颜色 计算调色板 58 | 59 | Args: 60 | color: 主体颜色 61 | bg_color: 背景颜色 62 | """ 63 | return [bg_color & 0xFF, (bg_color & 0xFF00) >> 8], [color & 0xFF, (color & 0xFF00) >> 8] 64 | 65 | 66 | def _flatten_byte_data(byte_data, palette) -> bytearray: 67 | """ 68 | 将 二进制 黑白图像渲染为 RGB565 彩色图像 69 | Args: 70 | byte_data: 图像数据 71 | palette: 调色板 72 | 73 | Returns: 74 | 75 | """ 76 | _temp = [] 77 | r = range(7, -1, -1) 78 | for _byte in byte_data: 79 | for _b in r: 80 | _temp.extend(palette[(_byte >> _b) & 0x01]) 81 | return bytearray(_temp) 82 | 83 | 84 | def _convert_image(file, output_path=".", convert_type="1", size: tuple = None, image_type: str = "pbm"): 85 | """ 86 | 将图片转换为 指定 格式 87 | 88 | Args: 89 | file: 原图片路径 90 | output_path: 输出路径(文件夹) 91 | convert_type: 图像类型("1" 为黑白图像,"RGBA" 为彩色图像) 92 | size: 图像大小 93 | image_type: 图像类型(图像文件后缀名) 94 | """ 95 | with Image.open(file, "r") as img: 96 | img2 = img.convert(convert_type) 97 | if size: 98 | img2 = img2.resize(size, Image.LANCZOS) 99 | file_path = _get_output_path(file, output_path, image_type) 100 | path = file_path.split('/') 101 | del path[-1] 102 | path = "/".join(path) 103 | if not os.path.exists(path): 104 | os.makedirs(path) 105 | img2.save(file_path) 106 | 107 | 108 | def image_to_pbm(file, output_path=".", convert_type="1", size: tuple = None): 109 | """ 110 | 将图片转换为 PBM 格式 111 | 112 | Args: 113 | file: 原图片路径 114 | output_path: 输出路径(文件夹) 115 | convert_type: 图像类型("1" 为黑白图像,"RGBA" 为彩色图像) 116 | size: 图像大小 117 | """ 118 | _convert_image(file, output_path, convert_type, size, "pbm") 119 | 120 | 121 | def image_to_bmp(file, output_path=".", convert_type="1", size: tuple = None): 122 | """ 123 | 将图片转换为 BMP 格式 124 | 125 | Args: 126 | file: 原图片路径 127 | output_path: 输出路径(文件夹) 128 | convert_type: 图像类型("1" 为黑白图像,"RGBA" 为彩色图像) 129 | size: 图像大小 130 | """ 131 | _convert_image(file, output_path, convert_type, size, "bmp") 132 | 133 | 134 | def pbm_to_dat(file, output_path=".", color: int = 65535, bg_color: int = 0, reverse: bool = False): 135 | """ 136 | 将 PBM 图片转换为 micropython-easydisplay 专用的 DAT 格式 137 | 138 | Args: 139 | file: 原图片路径 140 | output_path: 输出路径(文件夹) 141 | color: 图案颜色(仅黑白两色的 PBM 图片生效) 142 | bg_color: 背景颜色(仅黑白两色的 PBM 图片生效) 143 | reverse: 图像颜色反转 144 | """ 145 | with open(file, "rb") as img: 146 | file_format = img.readline() # 读取文件格式 147 | if file_format == b"P4\n" or file_format == b"P6\n": 148 | _width, _height = [int(value) for value in img.readline().split()] # 获取图片的宽度和高度 149 | with open(_get_output_path(file, output_path, "dat"), "wb") as f: 150 | f.write('EasyDisplay\nV1\n{} {}\n'.format(_width, _height).encode('utf-8')) # 写入文件头 151 | print(_width, _height) 152 | if file_format == b"P4\n": 153 | palette = _calculate_palette(color, bg_color) # 计算调色板 154 | data = img.read(4096) 155 | while data: 156 | if reverse: 157 | data = bytes([~b & 0xFF for b in data]) 158 | f.write(_flatten_byte_data(data, palette)) 159 | data = img.read(4096) 160 | elif file_format == b"P6\n": 161 | max_pixel_value = img.readline() # 获取最大像素值 162 | buffer = bytearray(_width * 2) 163 | for _y in range(_height): # 逐行处理图片 164 | for _x in range(_width): # 逐像素处理 165 | color_bytearray = img.read(3) 166 | r, g, b = color_bytearray[0], color_bytearray[1], color_bytearray[2] 167 | if reverse: 168 | r = 255 - r 169 | g = 255 - g 170 | b = 255 - b 171 | buffer[_x * 2: (_x + 1) * 2] = _color(r, g, b).to_bytes(2, 'big') # 通过索引赋值 172 | f.write(buffer) 173 | else: 174 | try: 175 | raise TypeError("[ERROR] Unsupported File Format {} !".format(file_format.rstrip(b"\n"))) 176 | except: 177 | raise TypeError("[ERROR] Unsupported File Format!") 178 | 179 | 180 | if __name__ == "__main__": 181 | # 使用示例 182 | image_to_pbm("(image_file_path)", "(image_output_folder)", "RGBA", size=(64, 64)) 183 | pbm_to_dat("(pbm_file_path)", "(image_output_folder)") 184 | image_to_bmp("(image_file_path)", "(image_output_folder)", "RGBA", size=(64, 64)) 185 | -------------------------------------------------------------------------------- /tool/video_tools.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | try: 4 | import cv2 5 | except ImportError: 6 | os.system('pip3 install opencv-python -i https://mirrors.aliyun.com/pypi/simple/') 7 | import cv2 8 | 9 | 10 | def convert_video_to_png(video_path: str, output_path: str, interval: int = 10): 11 | """ 12 | 将视频转换为图片 13 | 14 | Args: 15 | video_path: 视频文件路径 16 | output_path: 图片输出路径(文件夹) 17 | interval: 每间隔 X 帧,截取取一张图片 18 | """ 19 | num = 1 20 | vid = cv2.VideoCapture(video_path) 21 | while vid.isOpened(): 22 | is_read, frame = vid.read() 23 | if is_read: 24 | if num % interval == 1: 25 | file_num = '%08d' % num 26 | cv2.imwrite(f"{output_path.rstrip('/')}/{file_num}.png", frame) 27 | # 00000100.jpg 代表第111帧 28 | cv2.waitKey(1) 29 | num += 1 30 | else: 31 | break 32 | --------------------------------------------------------------------------------