这个中笔墨库一样平常会有3种存在办法:
以大数组的形式直接写在代码里. 这种适用于须要的汉字个数较少的情形;如果把几千个常用汉字都搞成大数组写在code里的话, 一方面code编译天生的bin会超大, 另一方面 你不以为这种办法太野蛮太不优雅了吗?(不过这种办法, 程序读取字体数据的速率倒是挺快)直接烧写在flash里. 也分两种情形, 一种是烧在ESP32模块内部flash里, 一种是在外部flash芯片里. 这种办法, 读取字体数据的速率也很快.以文件的形式存在文件系统里. 当然, 这个文件系统也是存在于flash上, 相称于把第2种办法套了一层文件系统的壳. 据官方文档描述, 由于每次读取字体数据时都须要通过文件系统API, 速率较慢, 会引起LVGL界面显示卡顿. 以是此办法, 我们不考虑.由于我们选用的ESP32模块是16MB版本的, 模块内置flash的存储空间绰绰有余, 以是我们选用将中笔墨库烧写在模块内部flash的办法.
这样, 既担保了读取速率, 本钱也增加不了多少(毕竟外置flash芯片也要钱, 还增加板上面积).

先来天生字库, 字库天生利用LvglFontTool软件, 官方下载地址, 绿色版软件, 直接解压即可利用. 感谢作者
软件运行如上图, 软件的利用方法很大略, 稍稍摸索一下就会了, 软件下载地址也有解释, 我这里就不赘述了.
我导入了6千多个常用汉字以及一些字母符号, 利用32像素高的字体, 字体名设为font_cn_32,
点击右下的“开始转换”按钮, 软件会天生2个文件:
font_cn_32.bin: 字库bin文件, 须要烧写入flash中的文件, 大小约为3.45MBfont_cn_32.c:供LVGL调用的字体接口API函数C文件, 本文后面还会须要对其进行小小的修正(把稳: 软件中填写的字体名不同, 天生的2个文件名也随之改变)
2. 字库的烧写好了, 字库文件已经有了, 终于到了本文的正题——字库烧写了.
先别急, 说到烧写字库, 是不是要想想, 烧写的地址是多少?烧进去的数据又如何读取出来呢?
烧进去读不出来不也没用, 以是说到如何烧写字库, 该当想的是 如何烧写和读取数据?
我们在序言里已经操持好了, 把字库烧写在ESP32模块内置flash中, 那么问题就变成了, 如何读取内置flash里的数据, 如何向内置flash里写入数据?
2.1 分区表理解ESP32开拓的朋友可能都知道, ESP32模块的内置flash, 乐鑫官方因此名为分区表的形式, 进行组织管理的(它还真的类似于Windows的硬盘分区).
乐鑫官网文档 API指南 >> 分区表 章节中有详细的先容, 本文截取部分内容先容一下.
2.1.1 概述每片 ESP32 的 flash 可以包含多个运用程序, 以及多种不同类型的数据(例如校准数据、文件系统数据、参数存储数据等). 因此, 我们在 flash 的 默认偏移地址 0x8000 处烧写一张分区表(把稳:分区表是终极被烧写入flash里的).
分区表的长度为 0xC00 字节, 最多可以保存 95 条分区表条款. MD5 校验和附加在分区表之后, 用于在运行时验证分区表的完全性. 分区表霸占了全体 flash 扇区, 大小为 0x1000 (4 KB). 因此, 它后面的任何分区至少须要位于 (默认偏移地址) + 0x1000 处.
2.1.2 预定义的内置分区表要理解分区表, 最大略的方法便是打开项目配置菜单(idf.py menuconfig), 在CONFIG_PARTITION_TABLE_TYPE 下选择一个预定义的分区表.
有2个预定义的内置分区表:
“Single factory app, no OTA”“Factory app, two OTA definitions”我们来看看这2个分区表的内容,
先看看 “Single factory app, no OTA” 这个分区表的内容, 如下:
# ESP-IDF Partition Table# Name, Type, SubType, Offset, Size, Flagsnvs, data, nvs, 0x9000, 0x6000,phy_init, data, phy, 0xf000, 0x1000,factory, app, factory, 0x10000, 1M,
一共3个条款.
定义了2个数据分区(Type字段值为data), 分别用于存储 NVS 库专用分区 和 PHY 初始化数据, 其详细意义超出本文主题太多, 请查阅官方文档.定义了1个运用程序分区(Type字段值为app), flash 的 0x10000 (64 KB) 偏移地址处存放一个name为 “factory” 的二进制运用程序, 启动加载器将默认加载这个运用程序.再来看 “Factory app, two OTA definitions” 分区表的内容:
# ESP-IDF Partition Table# Name, Type, SubType, Offset, Size, Flagsnvs, data, nvs, 0x9000, 0x4000,otadata, data, ota, 0xd000, 0x2000,phy_init, data, phy, 0xf000, 0x1000,factory, app, factory, 0x10000, 1M,ota_0, app, ota_0, 0x110000, 1M,ota_1, app, ota_1, 0x210000, 1M,
一共6个条款, 多了3个条款.
新增了一个名为 “otadata” 的数据分区, 用于保存 OTA 升级时须要的数据. 启动加载器会查询该分区的数据, 以判断该从哪个 OTA 运用程序分区加载程序. 如果 “otadata” 分区为空, 则会实行出厂程序.分区表中定义了3个运用程序分区, 这3个分区的类型都被设置为 “app”, 但详细 app 类型不同. 个中, 位于 0x10000 偏移地址处的为出厂运用程序 (factory), 别的两个为 OTA 运用程序(ota_0, ota_1).这里既然提到了 出厂运用程序 和 OTA运用程序, 就不得不解释一下:
ESP32启动, 会从 flash 的 0x1000 偏移地址处加载Bootloader, Bootloader会读取分区表, 并根据个中otadata(如果存在)的内容选择须要勾引的运用程序 (app) 分区.
详细的请拜会官方文档的 API 指南 >> 运用程序的启动流程 和 API 指南 >> 勾引加载程序 (Bootloader), 以及API 参考 >> System API >> 空中升级 (OTA) 等章节.
2.1.3 关于分区表须要把稳的点通过前面2个预定义分区表, 我们对分区表有了一个直不雅观粗浅的认识, 详细理解还请参看官方文档.
这里只列出几个需关注的点:
内置flash的扇区大小为0x1000(4KB), 分区的偏移地址(Offset)必须是 0x1000(4KB) 的倍数, 即必须4K对齐app分区的偏移地址(Offset)必须要与 0x10000 (64 K) 对齐Name 字段可以是任何故意义的名称, 但不能超过 16 个字节, 个中包括一个空字节(之后的内容将被截断), 该字段对 ESP32 并不是特殊主要Type 字段可以指定为 app (0x00) 或者 data (0x01), 也可以直策应用数字 0-254(或者十六进制 0x00-0xFE), 把稳: 0x00-0x3F 不得利用(预留给 esp-idf 的核心功能), 如果运用程序想自定义Type值, 请利用 0x40 ~ 0xFE.启动加载器将忽略 app (0x00) 和 data (0x01) 以外的其他Type分区类型当 Type 定义为 app 时, SubType 字段可以指定为 factory (0x00)、 ota_0 (0x10) … ota_15 (0x1F) 或者 test (0x20)。当 Type 定义为 data 时,SubType 字段可以指定为 ota (0x00)、phy (0x01)、nvs (0x02)、nvs_keys (0x04) 或者其他组件特定的子类型(请参考子类型列举).当 Type 值是由运用程序定义的任意值 0x40-0xFE时, subtype字段可以是由运用程序选择的任何值 0x00-0xFEFlags 字段当前仅支持 encrypted 标记. 如果 Flags 字段设置为 encrypted,且已启用 Flash 加密 功能, 则该分区将会被加密.2.1.4 自定义分区表好了, 对分区表有一定的认识了. 为了把中笔墨库写入内置flash的分区内, 我们须要自定义分区表.
先给出我的自定义分区表:
# Name, Type, SubType, Offset, Size, Flagsnvs, data, nvs, 0x9000, 0x4000,otadata, data, ota, 0xd000, 0x2000,phy_init, data, phy, 0xf000, 0x1000,factory, app, factory, 0x10000, 2M,ota_0, app, ota_0, 0x210000, 2M,ota_1, app, ota_1, 0x410000, 2M,font_cn_32, 0x50, 0x32, 0x610000, 4M,
下面解释一下,
基本上便是在 内置分区表“Factory app, two OTA definitions”的根本上, 增加了一个字库分区.分区Name直策应用了 字库的字体名由于第1节中我们天生的字库文件有3.45MB, 以是字库分区的Size设为了4M.字库分区的Type值, 利用了自定义的0x50(在0x40~FE范围内), SubType值设为了0x32, 也是自定义值, 让它表示字体的高度值把3个app分区的Size改为了2M, 目前我的程序bin大小为500K旁边, 裕量留的满满的0x100000 对应 1M, 0x200000是2M, 把稳后面4个大分区的Offset值这个分区表已利用的空间为 10MB+, 模块内置flash的size是16MB, 还有剩余, 后面还能增加小size字体的字库分区2.2 配置menuconfig自定义的分区表在电脑上因此.csv文件的形式, 保存在工程根目录下, 比如我的自定义分区表文件为partitions.csv.
我们在前面提到过分区表终极是被烧写到flash 的 默认偏移地址 0x8000 处, 因此csv文件形式的分区表须要被二进制化, 才能被烧写.
我们在menuconfig中选择“Custom partition table CSV”, 然后输入 分区表的csv文件名以及在工程中的路径, 即可.
实操一下, idf环境中, 输入idf.py menuconfig 命令:
在主界面下选择 Partition Table 分区表,
进入
再选择 Partition Table (Custom partition table CSV), 进入
选中 Custom partition table CSV (定制分区表CSV), 再回到上一层
在第二行选中, 可输入 定制分区表的 CSV文件名.
到此定制分区表的配置完毕.
其余提一下, 我们可在menuconfig中, 设置一下flash的size大小, 一定要和自己利用的模块同等, 如下图操作:
自定义分区表在menuconfig中配置好后, 后面编译工程实行idf.py build时, 会自动将将csv分区表天生二进制bin文件.
2.3 修正C文件字库数据写在flash什么地方已经安排好了, 现在要考虑怎么把flash里的字库数据读出来了.
回顾第1节中, 我们用字库天生软件 天生了 font_cn_32.c 文件, 个中有这么一段代码:
//static uint8_t __g_font_buf[714];//如bin文件存在SPI FLASH可利用此buffstatic uint8_t __user_font_getdata(int offset, int size){ //如字模保存在SPI FLASH, SPIFLASH_Read(__g_font_buf,offset,size); //如字模已加载到SDRAM,直接返回偏移地址即可如:return (uint8_t)(sdram_fontddr+offset); return __g_font_buf;}
须要在__user_font_getdata函数体内, 写入实际的flash读数据的代码, 读出的数据放到buffer __g_font_buf中(buffer数组的size是字库天生软件自动设定的, 和字体大小有关, 我们32的字体算较大的了, 以是buffer也不小.).
既然我们用了分区表, 乐鑫官方也供应了分区内数据读写的API函数, 拜会官方文档API参考>>存储API>>分区API, 截取官方文档中的一段内容如下:
该组件在esp_partition.h中声明了一些 API 函数,用以列举在分区表中找到的分区,并对这些分区实行操作:
esp_partition_find():在分区表中查找特定类型的条款,返回一个不透明迭代器;
esp_partition_get():返回一个构造体,描述给定迭代器的分区;
esp_partition_next():将迭代器移至下一个找到的分区;
esp_partition_iterator_release():开释 esp_partition_find() 中返回的迭代器;
esp_partition_find_first():返回描述 esp_partition_find() 中找到的第一个分区的构造;
esp_partition_read()、esp_partition_write() 和 esp_partition_erase_range() 等同于 esp_flash_read()、esp_flash_write() 和 esp_flash_erase_region(),但在分区边界内实行。
我们从flash分区中读数据, 终极只须要用到2个函数即可, esp_partition_find_first()(用来找到我们的 字库 分区) 和 esp_partition_read() (读出数据).
这两个函数的详细声明如下:
const esp_partition_t esp_partition_find_first (esp_partition_type_t type, esp_partition_subtype_t subtype, const char label)
Find first partition based on one or more parameters.
参数:
type – Partition type, one of esp_partition_type_t values or an 8-bit unsigned integer. To find all partitions, no matter the type, use ESP_PARTITION_TYPE_ANY, and set subtype argument to ESP_PARTITION_SUBTYPE_ANY.subtype – Partition subtype, one of esp_partition_subtype_t values or an 8-bit unsigned integer To find all partitions of given type, use ESP_PARTITION_SUBTYPE_ANY.label – (optional) Partition label. Set this value if looking for partition with a specific name. Pass NULL otherwise.返回: pointer to esp_partition_t structure, or NULL if no partition is found. This pointer is valid for the lifetime of the application.
esp_err_t esp_partition_read (const esp_partition_t partition, size_t src_offset, void dst, size_t size)
Read data from the partition.
Partitions marked with an encryption flag will automatically be be read and decrypted via a cache mapping.
参数:
partition – Pointer to partition structure obtained using esp_partition_find_first or esp_partition_get. Must be non-NULL.dst – Pointer to the buffer where data should be stored. Pointer must be non-NULL and buffer must be at least ‘size’ bytes long.src_offset – Address of the data to be read, relative to the beginning of the partition.size – Size of data to be read, in bytes.返回: ESP_OK, if data was read successfully; ESP_ERR_INVALID_ARG, if src_offset exceeds partition size; ESP_ERR_INVALID_SIZE, if read would go out of bounds of the partition; or one of error codes from lower-level flash driver.
终极, 我们对font_cn_32.c 文件的修正如下:
#include "esp_partition.h".........static uint8_t __g_font_buf[714];//如bin文件存在SPI FLASH可利用此buffstatic esp_partition_t partition_font = NULL;static uint8_t __user_font_getdata(int offset, int size){ //如字模保存在SPI FLASH, SPIFLASH_Read(__g_font_buf,offset,size); //如字模已加载到SDRAM,直接返回偏移地址即可如:return (uint8_t)(sdram_fontddr+offset); if( partition_font == NULL ) { partition_font = esp_partition_find_first(0x50, 0x32, "font_cn_32"); assert(partition_font != NULL); } esp_err_t err = esp_partition_read(partition_font, offset, __g_font_buf, size);//读取数据 if(err != ESP_OK) { printf("Failed to reading cn font date\n"); } return __g_font_buf;}
我们加了#include "esp_partition.h", 以便调用2个API函数.
通过分区的 Type值0x50, SunType值0x32 和 Name值"font_cn_32" 来找到我们的 字库分区,
代码很大略, 其他就没什么好解释的了.
记得把这个c文件加入到工程里, 至于如何添加, 就不是本文的范畴了.
2.4 烧写字库2.4.1 工程编译现在, 我们分区表设定好了, 代码也改好了, 可以编译了.
idf环境里工程目录下, 实行idf.py build
编译成功, 末了会输出如下:
Project build complete. To flash, run this command:C:\Users\admin\.espressif\python_env\idf4.3_py3.8_env\Scripts\python.exe ..\..\..\Users\admin\Desktop\esp-idf\components\esptool_py\esptool\esptool.py -p (PORT) -b 460800 --before default_reset --after hard_reset --chip esp32s2 write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x1000 build\bootloader\bootloader.bin 0x8000 build\partition_table\partition-table.bin 0xd000 build\ota_data_initial.bin 0x10000 build\myapp.binor run 'idf.py -p (PORT) flash'
2.4.2 烧写
终于到了心心念念的烧写字库这个步骤了.
前面我们编译成功后, 末了的输出中, 可以看到:
要么用idf.py -p (PORT) flash这个命令来烧写, 要么用那个"一长串的命令".
便是说这两者是等效的.
那个一长串的命令里, 不仅列出来很多烧写时的参数, 还列出了要烧写的各个bin文件及其开始地址, 如下:
0x1000 build\bootloader\bootloader.bin 0x8000 build\partition_table\partition-table.bin 0xd000 build\ota_data_initial.bin 0x10000 build\myapp.bin
看, 有bootloader, 分区表, ota_data初始值 (我打开看了全是0xFF) 和 我们的app 共4个bin文件,
它们前面的烧写地址也和本文前面所描述的预期地址同等.
但, 有个问题, 没有我们的字库bin文件.
没有我们就自己加上呗, 在末了加上0x610000 main\font_cn_32.bin即可!
这样相称于修正了烧写命令, 就不能用idf.py -p (PORT) flash这个命令来烧写了, 下面是我修正的烧写命令, 精简掉了python前的一大串路径(可惜esptool.py前的路径不能精简)
python ..\..\..\Users\admin\Desktop\esp-idf\components\esptool_py\esptool\esptool.py -p COM3 -b 460800 --before default_reset --after hard_reset --chip esp32s2 write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x1000 build\bootloader\bootloader.bin 0x8000 build\partition_table\partition-table.bin 0xd000 build\ota_data_initial.bin 0x10000 build\myapp.bin 0x610000 main\font_cn_32.bin
把稳几点:
我用COM3带入了(PORT)上面命令行, 我为了看起来清晰, 加入了回车. 利用时, 请一定要去掉回车让其成为一行, 或 利用命令行的换行符来替代, 不然肯定无法实行用这个命令, 就可以在烧写程序的同时, 顺便把字库烧写进去了.
而且经由测试, 后续再用idf.py -p (PORT) flash命令烧写更新程序, 也不会覆盖掉后面的字库分区, app烧写更新, 字库不受影响, 烧写一次会一贯妥妥的在那里, nice.
如果仅烧写字库, 也可以利用下面的精简命令:
python ..\..\..\Users\admin\Desktop\esp-idf\components\esptool_py\esptool\esptool.py -p COM3 write_flash 0x610000 main\font_cn_32.bin
3. 字库的利用
至于如何在LVGL中显示汉字, 代码编写和显示英文差不多, 只是多个字体声明语句.
代码如下:
...LV_FONT_DECLARE( font_cn_32 ); // 声明我们的中笔墨体, 如果代码里已声明过, 就不用再声明...lv_label_set_text(btnInfoLab, (LV_SYMBOL_HOME "你好 天下"));lv_obj_set_style_local_text_font(btnInfoLab, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &font_cn_32);...
OK, 这样就可以了.