最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網 會員登陸 & 注冊

在ESP32模塊內置Flash中燒寫和使用中文字庫的方法

2023-04-19 10:42 作者:有AI野心的電工和碼農  | 我要投稿

0. 前言

用ESP32作了個LVGL界面的項目, 自然少不了中文顯示, 要顯示中文自然需要中文字庫.

這個中文字庫一般會有3種存在方式:

  1. 大數(shù)組的形式直接寫在代碼里. 這種適用于需要的漢字個數(shù)較少的情況;如果把幾千個常用漢字都搞成大數(shù)組寫在code里的話, 一方面code編譯生成的bin會超大, 另一方面 你不覺得這種方式太野蠻太不優(yōu)雅了嗎?(不過這種方式, 程序讀取字體數(shù)據(jù)的速度倒是挺快)

  2. 直接燒寫在flash里. 也分兩種情況, 一種是燒在ESP32模塊內部flash里, 一種是在外部flash芯片里. 這種方式, 讀取字體數(shù)據(jù)的速度也很快.

  3. 以文件的形式存在文件系統(tǒng)里. 當然, 這個文件系統(tǒng)也是存在于flash上, 相當于把第2種方式套了一層文件系統(tǒng)的殼. 據(jù)官方文檔描述, 由于每次讀取字體數(shù)據(jù)時都需要通過文件系統(tǒng)API, 速度較慢, 會引起LVGL界面顯示卡頓. 所以此方式, 我們不考慮.

由于我們選用的ESP32模塊是16MB版本的, 模塊內置flash的存儲空間綽綽有余, 所以我們選用將中文字庫燒寫在模塊內部flash的方式.

這樣, 既保證了讀取速度, 成本也增加不了多少(畢竟外置flash芯片也要錢, 還增加板上面積).

1. 字庫生成

先來生成字庫, 字庫生成使用LvglFontTool軟件, 官方下載地址, 綠色版軟件, 直接解壓即可使用.?感謝作者

軟件運行如上圖, 軟件的使用方法很簡單, 稍稍摸索一下就會了, 軟件下載地址也有說明, 我這里就不贅述了.

我導入了6千多個常用漢字以及一些字母符號, 使用32像素高的字體, 字體名設為font_cn_32,

點擊右下的“開始轉換”按鈕, 軟件會生成2個文件:

  1. font_cn_32.bin: 字庫bin文件, 需要燒寫入flash中的文件, 大小約為3.45MB

  2. font_cn_32.c:供LVGL調用的字體接口API函數(shù)C文件,?本文后面還會需要對其進行小小的修改

    (注意: 軟件中填寫的字體名不同, 生成的2個文件名也隨之改變)

2. 字庫的燒寫

好了, 字庫文件已經有了, 終于到了本文的正題——字庫燒寫了.

先別急, 說到燒寫字庫, 是不是要想想, 燒寫的地址是多少?燒進去的數(shù)據(jù)又如何讀取出來呢?

燒進去讀不出來不也沒用, 所以說到如何燒寫字庫, 應該想的是 如何燒寫和讀取數(shù)據(jù)?

我們在前言里已經計劃好了, 把字庫燒寫在ESP32模塊內置flash中, 那么問題就變成了,?如何讀取內置flash里的數(shù)據(jù), 如何向內置flash里寫入數(shù)據(jù)?

2.1 分區(qū)表

了解ESP32開發(fā)的朋友可能都知道, ESP32模塊的內置flash, 樂鑫官方是以名為分區(qū)表的形式, 進行組織管理的(它還真的類似于Windows的硬盤分區(qū)).

樂鑫官網文檔?API指南 >> 分區(qū)表?章節(jié)中有詳細的介紹, 本文截取部分內容介紹一下.

2.1.1 概述

每片 ESP32 的 flash 可以包含多個應用程序, 以及多種不同類型的數(shù)據(jù)(例如校準數(shù)據(jù)、文件系統(tǒng)數(shù)據(jù)、參數(shù)存儲數(shù)據(jù)等). 因此, 我們在 flash 的?默認偏移地址 0x8000?處燒寫一張分區(qū)表(注意:分區(qū)表是最終被燒寫入flash里的).

分區(qū)表的長度為?0xC00?字節(jié), 最多可以保存?95?條分區(qū)表條目. MD5 校驗和附加在分區(qū)表之后, 用于在運行時驗證分區(qū)表的完整性. 分區(qū)表占據(jù)了整個 flash 扇區(qū), 大小為?0x1000 (4 KB). 因此, 它后面的任何分區(qū)至少需要位于 (默認偏移地址) + 0x1000 處.

2.1.2 預定義的內置分區(qū)表

要了解分區(qū)表, 最簡單的方法就是打開項目配置菜單(idf.py menuconfig), 在CONFIG_PARTITION_TABLE_TYPE?下選擇一個預定義的分區(qū)表.

有2個預定義的內置分區(qū)表:

  • “Single factory app, no OTA”

  • “Factory app, two OTA definitions”

我們來看看這2個分區(qū)表的內容,

先看看 “Single factory app, no OTA” 這個分區(qū)表的內容, 如下:

一共3個條目.

  • 定義了2個數(shù)據(jù)分區(qū)(Type字段值為data), 分別用于存儲?NVS 庫專用分區(qū)?和?PHY 初始化數(shù)據(jù), 其具體意義超出本文主題太多, 請查閱官方文檔.

  • 定義了1個應用程序分區(qū)(Type字段值為app), flash 的 0x10000 (64 KB) 偏移地址處存放一個name為 “factory” 的二進制應用程序, 啟動加載器將默認加載這個應用程序.

再來看 “Factory app, two OTA definitions” 分區(qū)表的內容:

一共6個條目, 多了3個條目.

  • 新增了一個名為 “otadata” 的數(shù)據(jù)分區(qū), 用于保存 OTA 升級時需要的數(shù)據(jù). 啟動加載器會查詢該分區(qū)的數(shù)據(jù), 以判斷該從哪個 OTA 應用程序分區(qū)加載程序. 如果 “otadata” 分區(qū)為空, 則會執(zhí)行出廠程序.

  • 分區(qū)表中定義了3個應用程序分區(qū), 這3個分區(qū)的類型都被設置為 “app”, 但具體 app 類型不同. 其中, 位于 0x10000 偏移地址處的為出廠應用程序 (factory), 其余兩個為 OTA 應用程序(ota_0, ota_1).

這里既然提到了?出廠應用程序?和?OTA應用程序, 就不得不說明一下:

ESP32啟動, 會從 flash 的 0x1000 偏移地址處加載Bootloader, Bootloader會讀取分區(qū)表, 并根據(jù)其中otadata(如果存在)的內容選擇需要引導的應用程序 (app) 分區(qū).

詳細的請參見官方文檔的?API 指南 >> 應用程序的啟動流程?和?API 指南 >> 引導加載程序 (Bootloader), 以及API 參考 >> System API >> 空中升級 (OTA)?等章節(jié).

2.1.3 關于分區(qū)表需要注意的點

通過前面2個預定義分區(qū)表, 我們對分區(qū)表有了一個直觀粗淺的認識, 詳細了解還請參看官方文檔.

這里只列出幾個需關注的點:

  • 內置flash的扇區(qū)大小為0x1000(4KB), 分區(qū)的偏移地址(Offset)必須是?0x1000(4KB)?的倍數(shù), 即必須4K對齊

  • app分區(qū)的偏移地址(Offset)必須要與?0x10000 (64 K)?對齊

  • Name?字段可以是任何有意義的名稱, 但不能超過 16 個字節(jié), 其中包括一個空字節(jié)(之后的內容將被截斷), 該字段對 ESP32 并不是特別重要

  • Type?字段可以指定為?app (0x00)?或者?data (0x01), 也可以直接使用數(shù)字 0-254(或者十六進制 0x00-0xFE),?注意: 0x00-0x3F 不得使用(預留給 esp-idf 的核心功能), 如果應用程序想自定義Type值, 請使用?0x40 ~ 0xFE.

  • 啟動加載器將忽略?app (0x00)?和?data (0x01)?以外的其他Type分區(qū)類型

  • 當 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-0xFE

  • Flags?字段當前僅支持 encrypted 標記. 如果 Flags 字段設置為 encrypted,且已啟用?Flash 加密?功能, 則該分區(qū)將會被加密.

2.1.4 自定義分區(qū)表

好了, 對分區(qū)表有一定的認識了. 為了把中文字庫寫入內置flash的分區(qū)內, 我們需要自定義分區(qū)表.

先給出我的自定義分區(qū)表:

下面說明一下,

  • 基本上就是在 內置分區(qū)表“Factory app, two OTA definitions”的基礎上, 增加了一個字庫分區(qū).

  • 分區(qū)Name直接使用了 字庫的字體名

  • 由于第1節(jié)中我們生成的字庫文件有3.45MB, 所以字庫分區(qū)的Size設為了4M.

  • 字庫分區(qū)的Type值, 使用了自定義的0x50(在0x40~FE范圍內), SubType值設為了0x32, 也是自定義值, 讓它表示字體的高度值

  • 把3個app分區(qū)的Size改為了2M, 目前我的程序bin大小為500K左右, 裕量留的滿滿的

  • 0x100000 對應 1M, 0x200000是2M, 注意后面4個大分區(qū)的Offset值

  • 這個分區(qū)表已使用的空間為?10MB+, 模塊內置flash的size是16MB, 還有剩余, 后面還能增加小size字體的字庫分區(qū)

2.2 配置menuconfig

自定義的分區(qū)表在電腦上是以.csv文件的形式, 保存在工程根目錄下, 比如我的自定義分區(qū)表文件為partitions.csv.

我們在前面提到過分區(qū)表最終是被燒寫到flash 的?默認偏移地址 0x8000?處, 因此csv文件形式的分區(qū)表需要被二進制化, 才能被燒寫.

我們在menuconfig中選擇“Custom partition table CSV”, 然后輸入 分區(qū)表的csv文件名以及在工程中的路徑, 即可.

實操一下, idf環(huán)境中, 輸入idf.py menuconfig?命令:

在主界面下選擇?Partition Table?分區(qū)表,

進入

再選擇?Partition Table (Custom partition table CSV), 進入

選中?Custom partition table CSV?(定制分區(qū)表CSV), 再回到上一層

在第二行選中, 可輸入 定制分區(qū)表的 CSV文件名.

到此定制分區(qū)表的配置完畢.

另外提一下, 我們可在menuconfig中, 設置一下flash的size大小, 一定要和自己使用的模塊一致, 如下圖操作:

自定義分區(qū)表在menuconfig中配置好后, 后面編譯工程執(zhí)行idf.py build時, 會自動將將csv分區(qū)表生成二進制bin文件.

2.3 修改C文件

字庫數(shù)據(jù)寫在flash什么地方已經安排好了, 現(xiàn)在要考慮怎么把flash里的字庫數(shù)據(jù)讀出來了.

回憶第1節(jié)中, 我們用字庫生成軟件 生成了?font_cn_32.c?文件, 其中有這么一段代碼:

需要在__user_font_getdata函數(shù)體內, 寫入實際的flash讀數(shù)據(jù)的代碼, 讀出的數(shù)據(jù)放到buffer?__g_font_buf中(buffer數(shù)組的size是字庫生成軟件自動設定的, 和字體大小有關, 我們32的字體算較大的了, 所以buffer也不小.).

既然我們用了分區(qū)表, 樂鑫官方也提供了分區(qū)內數(shù)據(jù)讀寫的API函數(shù), 參見官方文檔API參考>>存儲API>>分區(qū)API, 截取官方文檔中的一段內容如下:

該組件在esp_partition.h中聲明了一些 API 函數(shù),用以枚舉在分區(qū)表中找到的分區(qū),并對這些分區(qū)執(zhí)行操作:

  • esp_partition_find():在分區(qū)表中查找特定類型的條目,返回一個不透明迭代器;

  • esp_partition_get():返回一個結構體,描述給定迭代器的分區(qū);

  • esp_partition_next():將迭代器移至下一個找到的分區(qū);

  • esp_partition_iterator_release():釋放 esp_partition_find() 中返回的迭代器;

  • esp_partition_find_first():返回描述 esp_partition_find() 中找到的第一個分區(qū)的結構;

  • esp_partition_read()、esp_partition_write() 和 esp_partition_erase_range() 等同于 esp_flash_read()、esp_flash_write() 和 esp_flash_erase_region(),但在分區(qū)邊界內執(zhí)行。

我們從flash分區(qū)中讀數(shù)據(jù), 最終只需要用到2個函數(shù)即可,?esp_partition_find_first()(用來找到我們的 字庫 分區(qū)) 和?esp_partition_read()?(讀出數(shù)據(jù)).

這兩個函數(shù)的詳細聲明如下:

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.

參數(shù):

  • 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.

參數(shù):

  • 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", 以便調用2個API函數(shù).

通過分區(qū)的?Type值0x50, SunType值0x32 和 Name值"font_cn_32"?來找到我們的 字庫分區(qū),

代碼很簡單, 其他就沒什么好說明的了.

記得把這個c文件加入到工程里, 至于如何添加, 就不是本文的范疇了.

2.4 燒寫字庫

2.4.1 工程編譯

現(xiàn)在, 我們分區(qū)表設定好了, 代碼也改好了, 可以編譯了.

idf環(huán)境里工程目錄下, 執(zhí)行idf.py build

編譯成功, 最后會輸出如下:

2.4.2 燒寫

終于到了心心念念的燒寫字庫這個步驟了.

前面我們編譯成功后, 最后的輸出中, 可以看到:

要么用idf.py -p (PORT) flash這個命令來燒寫, 要么用那個"一長串的命令".

就是說這兩者是等效的.

那個一長串的命令里, 不僅列出來很多燒寫時的參數(shù), 還列出了要燒寫的各個bin文件及其開始地址, 如下:

0x1000 build\bootloader\bootloader.bin?

0x8000 build\partition_table\partition-table.bin?

0xd000 build\ota_data_initial.bin?

0x10000 build\myapp.bin

看, 有bootloader, 分區(qū)表, ota_data初始值?(我打開看了全是0xFF)?和 我們的app 共4個bin文件,

它們前面的燒寫地址也和本文前面所描述的預期地址一致.

但, 有個問題, 沒有我們的字庫bin文件.

沒有我們就自己加上唄, 在最后加上0x610000 main\font_cn_32.bin即可!

這樣相當于修改了燒寫命令, 就不能用idf.py -p (PORT) flash這個命令來燒寫了, 下面是我修改的燒寫命令, 精簡掉了python前的一大串路徑(可惜esptool.py前的路徑不能精簡)

注意幾點:

  • 我用COM3帶入了(PORT)

  • 上面命令行, 我為了看起來清晰, 加入了回車. 使用時, 請一定要去掉回車讓其成為一行, 或 使用命令行的換行符來替代, 不然肯定無法執(zhí)行

用這個命令, 就可以在燒寫程序的同時, 順便把字庫燒寫進去了.

而且經過測試, 后續(xù)再用idf.py -p (PORT) flash命令燒寫更新程序, 也不會覆蓋掉后面的字庫分區(qū), 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中顯示漢字, 代碼編寫和顯示英文差不多, 只是多個字體聲明語句.

代碼如下:

OK, 這樣就可以了.


在ESP32模塊內置Flash中燒寫和使用中文字庫的方法的評論 (共 條)

分享到微博請遵守國家法律
廊坊市| 潜江市| 平武县| 克拉玛依市| 枣阳市| 长汀县| 琼结县| 海口市| 庆云县| 龙川县| 临夏县| 新乡县| 卫辉市| 峨眉山市| 巍山| 津市市| 潼关县| 湘阴县| 星座| 乌兰察布市| 连州市| 吉首市| 顺义区| 沙雅县| 安康市| 萍乡市| 巨鹿县| 内丘县| 新和县| 绥芬河市| 大安市| 淮阳县| 固安县| 东乡| 邢台市| 利辛县| 镇赉县| 黎川县| 浪卡子县| 达州市| 田东县|