首页 » 科学 » 在ESP32模块内置Flash中烧写和运用中文字库的方法_分区表_字库

在ESP32模块内置Flash中烧写和运用中文字库的方法_分区表_字库

admin 2025-01-20 13:59:08 0

扫一扫用手机浏览

文章目录 [+]

这个中笔墨库一样平常会有3种存在办法:

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

由于我们选用的ESP32模块是16MB版本的, 模块内置flash的存储空间绰绰有余, 以是我们选用将中笔墨库烧写在模块内部flash的办法.

在ESP32模块内置Flash中烧写和运用中文字库的方法_分区表_字库 在ESP32模块内置Flash中烧写和运用中文字库的方法_分区表_字库 科学

这样, 既担保了读取速率, 本钱也增加不了多少(毕竟外置flash芯片也要钱, 还增加板上面积).

在ESP32模块内置Flash中烧写和运用中文字库的方法_分区表_字库 在ESP32模块内置Flash中烧写和运用中文字库的方法_分区表_字库 科学
(图片来自网络侵删)
1. 字库天生

先来天生字库, 字库天生利用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.bin3. 字库的利用

至于如何在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, 这样就可以了.

相关文章

苹果A1701自己换电池导致无声音_芯片_主板

拆开后盖创造电池确实是改换过的,这里有惊喜发生,居然掉了一颗螺丝在主板上,我也是醉了,这边扬声器居然还没扣紧。后面经由测试主板依然...

科学 2025-01-20 阅读0 评论0