STM32F407使用LVGL之字库IC
LVGL使用字库IC - 基于STM32F407
在上一篇笔记中,记录了所以用STM32F407移植LVGL。其中提到了中文显示,使用的是字库IC。相比于大多数使用的数组字库方式,使用字库IC编译后占用更小的存储空间,可以解码并显示更多的汉字,能够支持更多的字体大小等。
字库IC读取字形数据
根据自己所使用的IC,实现字形数据的读取。我用的晶联讯屏幕带字库的,给了字库读取的例程。做了些修改,以获取到LVGL显示字体所需要的数据。
已知:
- LVGL库会传入汉字或字符的utf-8编码,字库IC使用的是GB2312编码。
- 获取字形数据需要知道字体大小。
- 需要数据区存储获取的字形数据。
- 需要知道得到的字形数据大小。
- 需要知道字体的长*宽。
因此,需要做的工作:
- 编码转换(放到后面)。
- 修改字库读取的例程,以满足上述2-4的内容。
函数原型如下:
void jlx350_read_font(struct jlx350_object* obj, uint32_t gb_code, enum jlx350_font font, uint8_t* buff, uint16_t* size, uint16_t* w, uint16_t* h);
// obj: 自己定义的结构体,不重要,可有可无
// gb_code: GB2312的字符编码
// jlx350_font: 字库IC支持的字体大小,这里使用了枚举
// buff: 用于存储字形数据的数组
// size: 字形数据大小
// w: 字形的宽度
// h: 字形的高度
可以看出,字符编码和字体大小是传入的,而后面的四个参数是读出的。具体内容看函数实现:
void jlx350_read_font(struct jlx350_object* obj, uint32_t gb_code, enum jlx350_font font, uint8_t* buff, uint16_t* size, uint16_t* w, uint16_t* h)
{
obj->ops.pin_cs_zk_set(obj, 0); // 片选
switch(font){ // 跳转不同的字体大小
case JLX_FONT_12:
__read_font_12(obj, gb_code, buff, size, w, h);
break;
case JLX_FONT_16:
__read_font_16(obj, gb_code, buff, size, w, h);
break;
case JLX_FONT_24:
__read_font_24(obj, gb_code, buff, size, w, h);
break;
case JLX_FONT_32:
__read_font_32(obj, gb_code, buff, size, w, h);
break;
}
obj->ops.pin_cs_zk_set(obj, 1);
}
// 以下使用其中一个字体大小举例 font_12
void __read_font_12(struct jlx350_object* obj, uint32_t unicode, uint8_t* buff, uint16_t* size, uint16_t* w, uint16_t* h)
{
uint64_t font_addr = 0;
uint8_t code_H = (unicode >> 8) & 0xFF; // GB2312编码处理,需要根据编码获取到对应的IC存储地址
uint8_t code_L = unicode & 0xFF; // 这部分是根据例程来的,就是计算font_addr
if((code_H >= 0xB0) && (code_H <= 0xF7) && (code_L >= 0xA1)){
font_addr = (code_H - 0xB0) * 94;
font_addr += (code_L - 0xA1) + 846;
font_addr = (uint64_t)(font_addr * 24);
font_addr = (uint64_t)(font_addr + 0x00);
*size = 24; // 字形数据大小,12*12的汉字,需要12*2=24 bytes
*w = 12; // 字形的宽度
*h = 12; // 字形的高度
}
else if((code_H >= 0xA1) && (code_H <= 0xA9) && (code_L >= 0xA1)){
font_addr = (code_H - 0xA1) * 94;
font_addr += (code_L - 0xA1);
font_addr = (uint64_t)(font_addr * 24);
font_addr = (uint64_t)(font_addr + 0x00);
*size = 24;
*w = 12;
*h = 12;
}
else if((code_L >= 0x20) && (code_L <= 0x7E)){
font_addr += (code_L - 0x20);
font_addr = (uint64_t)(font_addr * 12);
font_addr = (uint64_t)(font_addr + 0x1DBE00);
*size = 12; // 这里是英文字符/数字/半角符号 所以大小是12就可以了,IC内的字形是6*12
*w = 6;
*h = 12;
}
// 以上根据IC的手册,确定读取字形的数据长度,字形宽/高,并赋值
// 下面的是读取数据了,存入到buff中
__zk_read_font(obj, font_addr, buff, *size);
}
// SPI读取字形数据
void __zk_read_font(struct jlx350_object* obj, uint32_t glyph_addr, uint8_t* data, uint16_t size)
{
__zk_write_byte(obj, 0x03); // 指令
__zk_write_byte(obj, (glyph_addr >> 16) & 0xFF); // 字形地址,24 bits
__zk_write_byte(obj, (glyph_addr >> 8) & 0xFF);
__zk_write_byte(obj, glyph_addr & 0xFF);
for(int i=0; i<size; i++){ // 读取数据,按size大小
__zk_read_byte(obj, &data[i]);
}
}
完成了根据字体和编码获取数据。至于SPI的内容,就不赘述了。
LVGL中添加字体
看了LVG了关于字体的代码,加入新字体需要定义结构体。如:
#if LVGL_VERSION_MAJOR >= 8
static const lv_font_fmt_txt_dsc_t font_dsc = {
#else
static lv_font_fmt_txt_dsc_t font_dsc = {
#endif
.glyph_bitmap = glyph_bitmap,
.glyph_dsc = NULL,
};
/*-----------------
* PUBLIC FONT
*----------------*/
/*Initialize a public general font descriptor*/
#if LVGL_VERSION_MAJOR >= 8
const lv_font_t jlx_font_24 = {
#else
lv_font_t lv_font_montserrat_24 = {
#endif
.get_glyph_dsc = jlx_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = jlx_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
.line_height = 26, /*The maximum line height required by the font*/
.base_line = 5, /*Baseline measured from the bottom of the line*/
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
.subpx = LV_FONT_SUBPX_NONE,
#endif
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
.underline_position = -2,
.underline_thickness = 1,
#endif
.dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
};
以上的结构体中,指明了两个函数jlx_font_get_glyph_dsc_fmt_txt
和jlx_font_get_bitmap_fmt_txt
。
jlx_font_get_glyph_dsc_fmt_txt
获取字库IC的数据。jlx_font_get_bitmap_fmt_txt
将字库数据转换为像素数据。
dsc
指向的结构体内的glyph_bitmap
成员表示像素内容。这里字体最大是32*32,占用32*4个像素,因此glyph_bitmap
是一个128大小的uint8数组。
其余成员的作用没有深究。
bool jlx_font_get_glyph_dsc_fmt_txt(const lv_font_t * font, lv_font_glyph_dsc_t * dsc_out, uint32_t unicode_letter, uint32_t unicode_letter_next)
{
/*It fixes a strange compiler optimization issue: https://github.com/lvgl/lvgl/issues/4370*/
bool is_tab = unicode_letter == '\t';
if(is_tab) {
unicode_letter = ' ';
}
enum jlx350_font f;
if(font == &jlx_font_12){
f = JLX_FONT_12;
}else if(font == &jlx_font_16){
f = JLX_FONT_16;
}else if(font == &jlx_font_24){
f = JLX_FONT_24;
}else if(font == &jlx_font_32){
f = JLX_FONT_32;
}
uint16_t size = 0, w = 0, h = 0; // size好像没啥用
memset(glyph_bitmap, 0, 128);
jlx350_read_font(&obj_jlx350, unicode_letter, f, glyph_bitmap, &size, &w, &h);
dsc_out->adv_w = w; // 可以调节字符的间距
dsc_out->box_h = h;
dsc_out->box_w = w;
dsc_out->ofs_x = 0;
dsc_out->ofs_y = 0;
dsc_out->format = 1;
dsc_out->is_placeholder = false;
dsc_out->gid.index = 0; // 返回给lvgl字形数据在数组内的索引,因为使用IC,每次都只读一个字形的数据,这里就直接使用0,和使用大数组是不一样的。
if(is_tab) dsc_out->box_w = dsc_out->box_w * 2;
return true;
}
const void * jlx_font_get_bitmap_fmt_txt(lv_font_glyph_dsc_t * g_dsc, lv_draw_buf_t * draw_buf)
{
const lv_font_t * font = g_dsc->resolved_font;
uint8_t * bitmap_out = draw_buf->data;
lv_font_fmt_txt_dsc_t * fdsc = (lv_font_fmt_txt_dsc_t *)font->dsc;
int32_t gsize = (int32_t) g_dsc->box_w * g_dsc->box_h;
if(gsize == 0) return NULL;
if(fdsc->bitmap_format == LV_FONT_FMT_TXT_PLAIN) {
const uint8_t * bitmap_in = &fdsc->glyph_bitmap[0];
uint8_t * bitmap_out_tmp = bitmap_out;
int32_t i = 0;
int32_t x, y;
uint32_t stride = lv_draw_buf_width_to_stride(g_dsc->box_w, LV_COLOR_FORMAT_A8);
if(1) { // 这里的循环,根据字形数据转换成pixel数据。根据自己实际修改
for(y = 0; y < g_dsc->box_h; y ++) {
for(x = 0; x < g_dsc->box_w; x++, i++) {
i = i & 0x7;
if(i == 0) bitmap_out_tmp[x] = (*bitmap_in) & 0x80 ? 0xff : 0x00;
else if(i == 1) bitmap_out_tmp[x] = (*bitmap_in) & 0x40 ? 0xff : 0x00;
else if(i == 2) bitmap_out_tmp[x] = (*bitmap_in) & 0x20 ? 0xff : 0x00;
else if(i == 3) bitmap_out_tmp[x] = (*bitmap_in) & 0x10 ? 0xff : 0x00;
else if(i == 4) bitmap_out_tmp[x] = (*bitmap_in) & 0x08 ? 0xff : 0x00;
else if(i == 5) bitmap_out_tmp[x] = (*bitmap_in) & 0x04 ? 0xff : 0x00;
else if(i == 6) bitmap_out_tmp[x] = (*bitmap_in) & 0x02 ? 0xff : 0x00;
else if(i == 7) {
bitmap_out_tmp[x] = (*bitmap_in) & 0x01 ? 0xff : 0x00;
}
if((i == 7) || (x == g_dsc->box_w-1)){
bitmap_in++;
if(x == g_dsc->box_w-1) i = 7;
}
}
bitmap_out_tmp += g_dsc->box_w;
}
}
return draw_buf;
}
/*If not returned earlier then the letter is not found in this font*/
return NULL;
}
以上两个函数是重要的lvgl字形数据获取和转换的。都是根据原有的函数修改而来。字符的“间隔”和“行高”都可以在字体的结构体内相关参数修改。
注:glyph_bitmap数组需要是全局变量。
最后,再添加字体的声明。我是在lv_font.h
中加的。
LV_FONT_DECLARE(jlx_font_32)
LV_FONT_DECLARE(jlx_font_12)
LV_FONT_DECLARE(jlx_font_16)
LV_FONT_DECLARE(jlx_font_24)
字符编码
以上完成之后,就可以按照字符的编码,查找IC内的字形数据,最后显示到显示屏。
但是,前提是能够正确解码。在lv_text.c
内修改解码函数。
// 添加gb2312的解码函数声明和定义
static uint32_t lv_text_gb2312_next(const char * txt, uint32_t * i);
static uint32_t lv_text_gb2312_next(const char * txt, uint32_t * i)
{
/**
* Unicode to UTF-8
* 00000000 00000000 00000000 0xxxxxxx -> 0xxxxxxx
* 00000000 00000000 00000yyy yyxxxxxx -> 110yyyyy 10xxxxxx
* 00000000 00000000 zzzzyyyy yyxxxxxx -> 1110zzzz 10yyyyyy 10xxxxxx
* 00000000 000wwwzz zzzzyyyy yyxxxxxx -> 11110www 10zzzzzz 10yyyyyy 10xxxxxx
*/
uint32_t result = 0;
/*Dummy 'i' pointer is required*/
uint32_t i_tmp = 0;
if(i == NULL) i = &i_tmp;
/*Normal ASCII*/
if(LV_IS_ASCII(txt[*i])) {
result = txt[*i];
(*i)++;
}
/*Real UTF-8 decode*/
else {
result = (txt[*i] << 8) | txt[(*i)+1];
(*i) += 2;
}
return result;
}
// 修改lv_text_encoded_next函数指针的值
uint32_t (*const lv_text_encoded_next)(const char *, uint32_t *) = lv_text_gb2312_next;
至此,大功告成。要注意有汉字需要显示的文件使用gb2312编码方式保存。