ESP32-C3 PlatformIO 'embed_txtfiles' 修复

前言

9.9元入手了一块ESP32-C3板子, 用来取代手上引脚不足的ESP8266.
但是编译时候总是失败,从Log上看是这一步出了错误.

1
2
3
4
5
prepare_file([".pio\build\esp32c3\lite.ttf.txt.o"], ["src\lite.ttf"])
Converting .pio\build\esp32c3\lite.ttf.txt.o
'xtensa-esp32-elf-objcopy' \xb2\xbb\xca\xc7\xc4ڲ\xbf\xbb\xf2\xcdⲿ\xc3\xfc\xc1Ҳ\xb2\xbb\xcaǿ\xc9\xd4\xcb\xd0еij\xcc\xd0\xf2
\xbb\xf2\xc5\xfa\xb4\xa6\xc0\xed\xceļ\xfe\xa1\xa3
*** [.pio\build\esp32c3\lite.ttf.txt.o] Error 1

此外并没有任何提示了.

错误分析

lite.ttf是我的一个放在src目录下的文件,

为其在platformio.ini配置”board_build.embed_txtfiles = src/lite.ttf”,就可以让这个文件嵌入固件中.
从日志里看到是转化的时候出了问题, xtensa-esp32-elf-objcopy

xtensa是ESP32,ESP32-S2,ESP32-S3的处理器结构,而ESP32-C3是RISC-V的,所以这里出错应该是结构不匹配.
去ESP-IDF的文件夹搜索”elf-objcopy”后发现的确有不同很多类似关键字.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for dir in esp32 esp32s2 esp32c3 esp32s3; do
if [ $dir = esp32 ]; then
TOOLCHAIN="xtensa-esp32-elf"
elif [ $dir = esp32s2 ]; then
TOOLCHAIN="xtensa-esp32s2-elf"
elif [ $dir = esp32c3 ]; then
TOOLCHAIN="riscv32-esp-elf"
elif [ $dir = esp32s3 ]; then
TOOLCHAIN="xtensa-esp32s3-elf"
else
echo "$dir does not exist"
fi
if [ -d "$dir" ]; then
cd $dir
git status libphy.a | grep "modified" >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo $dir/libphy.a fixed
$TOOLCHAIN-objcopy --redefine-sym ets_printf=phy_printf libphy.a
fi

所以应该是PlatformIO在转化的时候调用了错误的程序导致的.

解决问题

去PlatformIO的工作目录(.platformio)下搜索xtensa-esp32-elf-objcopy

1
2
3
4
5
6
7
8
9
10
11
12
13
[
"xtensa-esp32-elf-objcopy",
"--input-target",
"binary",
"--output-target",
"elf32-xtensa-le",
"--binary-architecture",
"xtensa",
"--rename-section",
".data=.rodata.embedded",
"$SOURCE",
"$TARGET",
]

确实搜索到了多个包含这个关键字的文件_embed_files.py
(platforms\espressif32\builder\frameworks_embed_files.py)
其中platforms下有好几个文件夹

1
2
3
4
5
6
7
├── .platformio
│ └── platforms
│ └── espressif32
│ └── espressif32@src-ba2d3999402da5eaf2c9d5863ef113c7
│ └── espressif32@src-ebefb4289db63a9f0ca5ee29fa328eef
│ └── espressif8266
│ └── raspberrypi

这应该是不同项目所需的platform,凑巧这次C3项目的platform是专门下载的,所以从文件夹修改日期上来看,我这次C3使用的应该是espressif32@src-ebefb4289db63a9f0ca5ee29fa328eef
打开espressif32@src-ebefb4289db63a9f0ca5ee29fa328eef下的_embed_files.py
里面果然还在使用xtensa-esp32-elf-objcopy
按照ESP-IDF里面的描述,应该使用riscv32-esp-elf-objcopy,并且要把xtensa相关的都修改为riscv.

所以最后修改成了

1
2
3
4
5
6
7
8
9
10
11
12
13
[
"riscv32-esp-elf-objcopy",
"--input-target",
"binary",
"--output-target",
"elf32-littleriscv",
"--binary-architecture",
"riscv",
"--rename-section",
".data=.rodata.embedded",
"$SOURCE",
"$TARGET",
]

重新执行编译,这一次终于通过了.

1
2
3
4
5
6
7
8
9
10
11
12
13
Building in release mode
prepare_file([".pio\build\esp32c3\lite.ttf.txt.o"], ["src\lite.ttf"])
Converting .pio\build\esp32c3\lite.ttf.txt.o
revert_original_file([".pio\build\esp32c3\lite.ttf.txt.o"], ["src\lite.ttf"])
Linking .pio\build\esp32c3\firmware.elf
Retrieving maximum program size .pio\build\esp32c3\firmware.elf
Checking size .pio\build\esp32c3\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM: [ ] 4.7% (used 15480 bytes from 327680 bytes)
Flash: [= ] 14.1% (used 443870 bytes from 3145728 bytes)
Building .pio\build\esp32c3\firmware.bin
esptool.py v3.1
Merged 2 ELF sections

_embed_files.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# Copyright 2014-present PlatformIO <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import shutil
from os import SEEK_CUR, SEEK_END
from os.path import basename, isfile, join

from SCons.Script import Builder

Import("env")

board = env.BoardConfig()

#
# Embedded files helpers
#


def extract_files(cppdefines, files_type):
files = []
if "build." + files_type in board:
files.extend(
[
join("$PROJECT_DIR", f)
for f in board.get("build." + files_type, "").split()
if f
]
)
else:
files_define = "COMPONENT_" + files_type.upper()
for define in cppdefines:
if files_define not in define:
continue

value = define[1]
if not isinstance(define, tuple):
print("Warning! %s macro cannot be empty!" % files_define)
return []

if not isinstance(value, str):
print(
"Warning! %s macro must contain "
"a list of files separated by ':'" % files_define
)
return []

for f in value.split(":"):
if not f:
continue
files.append(join("$PROJECT_DIR", f))

for f in files:
if not isfile(env.subst(f)):
print('Warning! Could not find file "%s"' % basename(f))

return files


def remove_config_define(cppdefines, files_type):
for define in cppdefines:
if files_type in define:
env.ProcessUnFlags("-D%s" % "=".join(str(d) for d in define))
return


def prepare_file(source, target, env):
filepath = source[0].get_abspath()
shutil.copy(filepath, filepath + ".piobkp")

with open(filepath, "rb+") as fp:
fp.seek(-1, SEEK_END)
if fp.read(1) != "\0":
fp.seek(0, SEEK_CUR)
fp.write(b"\0")


def revert_original_file(source, target, env):
filepath = source[0].get_abspath()
if isfile(filepath + ".piobkp"):
shutil.move(filepath + ".piobkp", filepath)


def embed_files(files, files_type):
for f in files:
filename = basename(f) + ".txt.o"
file_target = env.TxtToBin(join("$BUILD_DIR", filename), f)
env.Depends("$PIOMAINPROG", file_target)
if files_type == "embed_txtfiles":
env.AddPreAction(file_target, prepare_file)
env.AddPostAction(file_target, revert_original_file)
env.AppendUnique(PIOBUILDFILES=[env.File(join("$BUILD_DIR", filename))])


def transform_to_asm(target, source, env):
files = [join("$BUILD_DIR", s.name + ".S") for s in source]
return files, source


env.Append(
BUILDERS=dict(
TxtToBin=Builder(
action=env.VerboseAction(
" ".join(
[
"riscv32-esp-elf-objcopy",
"--input-target",
"binary",
"--output-target",
"elf32-littleriscv",
"--binary-architecture",
"riscv",
"--rename-section",
".data=.rodata.embedded",
"$SOURCE",
"$TARGET",
]
),
"Converting $TARGET",
),
suffix=".txt.o",
),
TxtToAsm=Builder(
action=env.VerboseAction(
" ".join(
[
join(
env.PioPlatform().get_package_dir("tool-cmake") or "",
"bin",
"cmake",
),
"-DDATA_FILE=$SOURCE",
"-DSOURCE_FILE=$TARGET",
"-DFILE_TYPE=TEXT",
"-P",
join(
env.PioPlatform().get_package_dir("framework-espidf") or "",
"tools",
"cmake",
"scripts",
"data_file_embed_asm.cmake",
),
]
),
"Generating assembly for $TARGET",
),
emitter=transform_to_asm,
single_source=True,
),
)
)


flags = env.get("CPPDEFINES")
for files_type in ("embed_txtfiles", "embed_files"):
if (
"COMPONENT_" + files_type.upper() not in env.Flatten(flags)
and "build." + files_type not in board
):
continue

files = extract_files(flags, files_type)
if "espidf" in env.subst("$PIOFRAMEWORK"):
env.Requires(join("$BUILD_DIR", "${PROGNAME}.elf"), env.TxtToAsm(files))
else:
embed_files(files, files_type)
remove_config_define(flags, files_type)

环境:

1
2
3
4
5
PLATFORM: Espressif 32 (3.3.0+sha.3b5de56) > Espressif ESP32 Dev Module
- framework-arduinoespressif32 0.0.0+sha.68daea4
- tool-esptoolpy 1.30100.210531 (3.1.0)
- toolchain-riscv-esp 1.80400.0 (8.4.0)
- toolchain-riscv32-esp 8.4.0+2021r1

ESP32并口屏幕和串口屏幕下帧率的对比

前言

之前在ESP32上运行LVGL一直使用的是SPI串口驱动LCD, 当时就比较好奇如果换成了并口驱动LCD会对帧率有何影响.于是乎终于在2022年后买入了一块并口屏幕来测试一下.

测试项目

  • 使用LVGL自带的lv_demo_music,开启LV_DEMO_MUSIC_AUTO_PLAY
  • 使用TFT_eSPI自带的Viewport_graphicstest

测试结果

运行LVGL自带的lv_demo_music

对照组序号 屏幕参数 ESP32运行频率 LVGL缓冲参数 SPI速率 TFT_eSPI版本 LVGL版本 帧率
1 240x240 1.54寸 SPI LCD 240MHz 240*120 启用DMA,未使用双缓冲 60MHz 2.3.70 8.1.1-dev 26帧
2 240x240 1.33寸 8位并口 LCD 240MHz 240*120 未使用双缓冲 \ 2.3.70 8.1.1-dev 28帧

感觉帧率差异不是很大的样子.

运行TFT_eSPI自带的Viewport_graphicstest

对照组序号 屏幕参数 ESP32运行频率 SPI速率 TFT_eSPI版本
1 240x240 1.54寸 SPI LCD 240MHz 60MHz 2.3.70
2 240x240 1.33寸 8位并口 LCD 240MHz \ 2.3.70

运行时候明显感觉到并口的绘制速度更快,不过还是得数据说话,于是有如下Log

8位并口结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TFT_eSPI library test!
Benchmark Time (microseconds)
Screen fill 53073
Text 21126
Lines 36424
Horiz/Vert Lines 10469
Rectangles (outline) 5918
Rectangles (filled) 122742
Circles (filled) 28151
Circles (outline) 22372
Triangles (outline) 14111
Triangles (filled) 46193
Rounded rects (outline) 16475
Rounded rects (filled) 127122
Done!

串口结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TFT_eSPI library test!
Benchmark Time (microseconds)
Screen fill 102725
Text 12506
Lines 38321
Horiz/Vert Lines 9293
Rectangles (outline) 6594
Rectangles (filled) 234378
Circles (filled) 30350
Circles (outline) 16884
Triangles (outline) 12893
Triangles (filled) 79688
Rounded rects (outline) 11730
Rounded rects (filled) 240258
Done!

并口驱动情况下绘制时间几乎是串口驱动绘制时间的一半.

果然还是有提升的.

至于为什么在LVGL中拉不开差距,经过群友Principle点拨后意识到,运行LVGL时候CPU性能方面出现了瓶颈,我的ESP32是初代版本,如果换成最新的ESP32 S3则会拉开差距.

LGVL配合FreeType为可变字体设置字重-ESP32篇(下)

前言

上篇中介绍了如何在ESP32上运行LVGL,现在终于可以开始奔着第一篇文章末尾的效果去了.

准备工作

软件准备

访问https://download.savannah.gnu.org/releases/freetype/下载源码,解压后重命名为freetype并复制到项目lib文件夹下
我下载的是目前(2021.12.11)最新版本freetype-2.11.1.tar.gz(较老的版本不支持可变字体操作)
然后去这个Repo获取SD和SPI库,复制到项目lib文件夹下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.
├── include
│ └── README
├── lib
│ └── README
│ └── lvgl_freetype
│ └── lvgl
│ └── freetype
│ └── SD
│ └── SPI
│ └── TFT_eSPI
├── platformio.ini
├── src
│ └── main.cpp
│ └── Port
│ └── lv_port_disp.cpp
│ └── lv_port_disp.h
└── test
└── README

到freetype文件夹下,创建FreeType的library.jsonlibrary.properties文件
library.properties内容如下

1
2
3
4
5
6
7
8
9
10
11
name=FreeType
version=2.11.1
author=David Turner, Robert Wilhelm, Werner Lemberg
maintainer=Werner Lemberg
sentence=A freely available software library to render fonts.
paragraph=It is written in C, designed to be small, efficient, highly customizable, and portable while capable of producing high-quality output (glyph images) of most vector and bitmap font formats.documentation.
category=Font
url=https://freetype.org/
architectures=*
repository=https://gitlab.freedesktop.org/freetype/freetype
license=GNU

library.json内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
"name":"freetype",
"version": "2.11.1",
"description":"Software library to render fonts",
"keywords":"freetype",
"license": "FreeType License",
"repository": {
"type": "git",
"url": "https://gitlab.freedesktop.org/freetype"
},
"frameworks": "*",
"platforms": "*",
"build": {
"srcFilter": [
"+<base/ftsystem.c>",
"+<base/ftmm.c>",
"+<base/ftinit.c>",
"+<base/ftdebug.c>",
"+<base/ftbase.c>",
"+<base/ftbbox.c>",
"+<base/ftglyph.c>",
"+<base/ftbdf.c>",
"+<bdf/bdf.c>",
"+<cff/cff.c>",
"+<truetype/truetype.c>",
"+<sfnt/sfnt.c>",
"+<smooth/smooth.c>",
"+<cache/ftcache.c>",
"+<gzip/ftgzip.c>",
"+<base/ftbitmap.c>"
],
"flags": [ "-DFT2_BUILD_LIBRARY", "-I include" ],
"includeDir": "devel"
}
}

硬件准备

和上篇相比,多了一个SD卡模块.

起初也尝试了直接把字体文件存在SPIFFS里面,减少涉及到的硬件,奈何速度慢的感人,于是回到这里重新写了SD卡版本.

2022.02.08: 发现LVGL自8.1.1-dev(正式版为8.2.0)开始,内置的lv_freetype模块已经支持FT_New_Memory_Face方式创建字体了.
如果没有内存卡的话,请将字体文件命名为Lite.ttf放到源码src文件夹内,
在platformio.ini内添加

1
2
board_build.embed_txtfiles = 
src/Lite.ttf

并在main.cpp中添加

1
2
3
extern const uint8_t font_start[] asm("_binary_src_Lite_ttf_start");
extern const uint8_t font_end[] asm("_binary_src_Lite_ttf_end");
extern const size_t size = font_end - font_end - 1;

创建字体时候额外添加如下内容

1
2
3
4
5
static lv_ft_info_t info;
/**/
info.mem = font_start;
info.mem_size = size;
/**/

有SD卡的情况下

名称 数量 备注 图例
ESP32 开发板 1 \
1.54寸LCD 1 驱动ST7789,分辨率240x240
杜邦线若干 N \
Micro SD卡模块和卡 1 \

接线和点亮屏幕请去参考LGVL配合FreeType为可变字体设置字重-ESP32篇(上)

Micro SD卡模块的接线如下

ESP32引脚名称 Micro SD卡模块引脚名称
GND GND
G26 MISO
G13 MOSI
G14 SCK
G15 CS
5V(Vin) VCC

准备完毕,开干

启用LVGL的FreeType

将lv_conf.h内的宏定义内容, 并启用FTC_SBitCache_Lookup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*FreeType library*/
#define LV_USE_FREETYPE 0
#define LV_USE_FREETYPE 1
#if LV_USE_FREETYPE
/*Memory used by FreeType to cache characters [bytes] (-1: no caching)*/
#define LV_FREETYPE_CACHE_SIZE (16 * 1024)
#if LV_FREETYPE_CACHE_SIZE >= 0
/* 1: bitmap cache use the sbit cache, 0:bitmap cache use the image cache. */
/* sbit cache:it is much more memory efficient for small bitmaps(font size < 256) */
/* if font size >= 256, must be configured as image cache */
#define LV_FREETYPE_SBIT_CACHE 1
/* Maximum number of opened FT_Face/FT_Size objects managed by this cache instance. */
/* (0:use system defaults) */
#define LV_FREETYPE_CACHE_FT_FACES 0
#define LV_FREETYPE_CACHE_FT_SIZES 0
#endif
#endif

0设置为1

配置FreeType的文件系统

创建一个命为ft_fs_port.cpp的文件,内容为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "FS.h"
#include "SD.h"
#include "SPIFFS.h"

extern "C" {
typedef void lvbe_FILE;
lvbe_FILE *lvbe_fopen(const char *filename, const char *mode) {
File f = SD.open(filename, mode);
if (f) {
File *f_ptr = new File(f); // copy to dynamic object
*f_ptr = f; // TODO is this necessary?
return f_ptr;
}
return nullptr;
}
int lvbe_fclose(lvbe_FILE *stream) {
File *f_ptr = (File *)stream;
f_ptr->close();
delete f_ptr;
return 0;
}
size_t lvbe_fread(void *ptr, size_t size, size_t count, lvbe_FILE *stream) {
File *f_ptr = (File *)stream;
int32_t ret = f_ptr->read((uint8_t *)ptr, size * count);
if (ret < 0) { // error
ret = 0;
}
return ret;
}
int lvbe_fseek(lvbe_FILE *stream, long int offset, int origin) {
File *f_ptr = (File *)stream;
fs::SeekMode mode = fs::SeekMode::SeekSet;
if (SEEK_CUR == origin) {
mode = fs::SeekMode::SeekCur;
} else if (SEEK_END == origin) {
mode = fs::SeekMode::SeekEnd;
}
bool ok = f_ptr->seek(offset, mode);
return ok ? 0 : -1;
}
int lvbe_ftell(lvbe_FILE *stream) {
File *f_ptr = (File *)stream;
return f_ptr->position();
}
}

打开位于freetype文件夹下的devel\ft2build.h
并添加

1
2
3
4
5
6
7
8
9
10
#ifndef FT2BUILD_H_
#define FT2BUILD_H_

#define FT_CONFIG_MODULES_H <ftmodule.h>
#define FT_CONFIG_OPTIONS_H <ftoption.h>
#define FT_CONFIG_STANDARD_LIBRARY_H <ftstdlib.h>

#include <freetype/config/ftheader.h>

#endif /* FT2BUILD_H_ */

在同级目录下创建ftstdlib.h(文末Repo内有完整版)文件,内容从freetype\include\freetype\config\ftstdlib.h复制

修改file handling部分的内容为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  /**************************************************************************
*
* file handling
*
*/
#include <stdio.h>
typedef void lvbe_FILE;
extern lvbe_FILE * lvbe_fopen(const char * filename, const char * mode );
extern int lvbe_fclose(lvbe_FILE * stream);
extern size_t lvbe_fread(void * ptr, size_t size, size_t count, lvbe_FILE * stream);
extern int lvbe_fseek(lvbe_FILE * stream, long int offset, int origin );
extern int lvbe_ftell(lvbe_FILE * stream);

#define FT_FILE lvbe_FILE
#define ft_fclose lvbe_fclose
#define ft_fopen lvbe_fopen
#define ft_fread lvbe_fread
#define ft_fseek lvbe_fseek
#define ft_ftell lvbe_ftell
#define ft_sprintf sprintf

在同级目录下创建ftmodule.h,启用所需模块
内容为

1
2
3
FT_USE_MODULE( FT_Driver_ClassRec, tt_driver_class )
FT_USE_MODULE( FT_Module_Class, sfnt_module_class )
FT_USE_MODULE( FT_Renderer_Class, ft_smooth_renderer_class )

在同级目录下创建ftoption.h**, 内容从freetype\include\freetype\config\ftoptions.h复制,详细的配置过长,故此处省略,见文末Repo内**

此时FreeType的基本功能配置完成

主要的涉及到freetype\devel下的4个文件

1
2
3
4
5
6
│       └── freetype
│ └── devel
│ └── ft2build.h
│ └── ftmodule.h
│ └── ftoption.h
│ └── ftstdlib.h

测试FreeType移植结果

打开main.cpp 随便写点内容,烧录到ESP32上运行查看效果.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <Arduino.h>

#include "./Port/lv_port_disp.h"
#include "SD.h"
#include "SPI.h"

// 初始化SD卡
bool SD_Init() {
pinMode(22, INPUT);

SPIClass *sd_spi = new SPIClass(HSPI); // another SPI
if (!SD.begin(15, *sd_spi, 12000000, "")) // SD-Card SS pin is 15
{
Serial.println("Card Mount Failed");
return false;
}
uint8_t cardType = SD.cardType();

if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return false;
}

Serial.print("SD Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}

uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB, used size:%llu\n", cardSize,
SD.usedBytes());

return true;
}

void setup() {
Serial.begin(115200); // Set to a high rate for fast image transfer to a PC

Port_Init();
if (!SD_Init()) {
Serial.println("SPIFFS Mount Failed");
};
/*创建字体*/
static lv_ft_info_t info;
/*在SD卡根目录下放置前一节中的可变字体文件,重命名为archivo.ttf*/
info.name = "/archivo.ttf";
info.weight = 18;
info.style = FT_FONT_STYLE_NORMAL;
if (!lv_ft_font_init(&info)) {
LV_LOG_ERROR("create failed.");
} else {
LV_LOG_ERROR("create done.");
}

/*为新字体创建Style*/
static lv_style_t style;
lv_style_init(&style);
lv_style_set_text_font(&style, info.font);
lv_style_set_text_color(&style, lv_color_black());

/*应用Style到Label*/
lv_obj_t *altria = lv_label_create(lv_scr_act());
lv_obj_t *shirou = lv_label_create(lv_scr_act());
// lv_obj_add_style(altria, &style, 0);

lv_label_set_text(altria, "Toou.\nAnata wa watashi no masuta ka?");
lv_label_set_text(shirou, "Master?");
lv_obj_set_style_text_font(shirou, info.font, 0);
lv_obj_center(shirou);

// 一切就绪, 启动LVGL任务
xTaskNotifyGive(handleTaskLvgl);
}

void loop() {}

运行效果如下

Weight=900

拓展LVGL的FreeType

就像前篇内容中的一样,需要手动地为当前版本LVGL添加可变字体支持.

lv_freetype.h内的lv_ft_info_t修改为

1
2
3
4
5
6
7
8
9
typedef struct {
const char * name; /* The name of the font file */
const void * mem; /* The pointer of the font file */
size_t mem_size; /* The size of the memory */
lv_font_t * font; /* point to lvgl font */
uint16_t height; /* font size */
uint16_t weight; /* font weight */
uint16_t style; /* font style */
} lv_ft_info_t;

然后去lv_freetype.c里添加可变字体操作代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FT_MM_Var *amaster = NULL;
FT_Error err = FT_Get_MM_Var(face, &amaster);
if (err) {
LV_LOG_ERROR("FT_Get_MM_Var error:%d\n", err);
return err;
}
FT_Fixed w = dsc->weight << 16;
if (w > amaster->axis->maximum) {
w = amaster->axis->maximum;
}
err = FT_Set_Var_Design_Coordinates(face, 1, &w);
if (err) {
LV_LOG_ERROR("FT_Set_Var_Design_Coordinates error:%d\n", err);
return err;
}
FT_Done_MM_Var(library, amaster);

别忘记我们已经修改了weight的含义,此时字体大小已经由height决定了

回到main.cpp,在创建字体初时候,设置weight为100

1
2
3
4
5
static lv_ft_info_t info;
info.name = "/archivo.ttf";
info.height = 18;
info.weight = 100;
info.style = FT_FONT_STYLE_NORMAL;

然后烧录到ESP32,可以看到字重已经降低了.

Weight=900

动起来吧,字重

利用LVGL的lv_timer_t或者lv_anim_t动态的修改字重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typedef struct _VF_Label {
lv_obj_t *label;
lv_ft_info_t *font;
} VF_Label;

static void onTimer(lv_timer_t *timer) {
static uint16_t weight = 100;
static uint16_t size = 18;
VF_Label *vfl = (VF_Label *)(timer->user_data);
if (vfl == nullptr) {
printf("vfl == nullptr\n");
}

if (vfl == nullptr) {
printf("vfl->font->font == nullptr\n");
}

lv_ft_font_destroy(vfl->font->font);
printf("lv_ft_font_destroy\n");
vfl->font->name = "/archivo.ttf";
vfl->font->height = size;
vfl->font->weight = weight;
vfl->font->style = FT_FONT_STYLE_NORMAL;
lv_ft_font_init(vfl->font);
printf("lv_ft_font_init\n");
size = 48;
weight += 100;
if (weight > 600) {
weight = 100;
}
lv_obj_set_style_text_font(vfl->label, vfl->font->font, 0);
}
1
2
3
lv_timer_t *weightTimer = lv_timer_create(onTimer, 50, &label);
// 一切就绪, 启动LVGL任务
xTaskNotifyGive(handleTaskLvgl);

效果图

dynamic

环境:

1
2
3
4
5
6
7
Espressif 32 (3.4.0) > ESP32 Pico Kit
framework-arduinoespressif32 3.10006.210326 (1.0.6)
tool-esptoolpy 1.30100.210531 (3.1.0)
toolchain-xtensa32 2.50200.97 (5.2.0)
<lvgl> 8.1.1-dev
<TFT_eSPI> 2.3.89
esptool.py v3.1

参考资料

Repo

© 2025 Do U Find IT? All Rights Reserved.
Theme by hiero