STM32F407使用LVGL之字库IC

LVGL使用字库IC - 基于STM32F407

在上一篇笔记中,记录了所以用STM32F407移植LVGL。其中提到了中文显示,使用的是字库IC。相比于大多数使用的数组字库方式,使用字库IC编译后占用更小的存储空间,可以解码并显示更多的汉字,能够支持更多的字体大小等。

字库IC读取字形数据

根据自己所使用的IC,实现字形数据的读取。我用的晶联讯屏幕带字库的,给了字库读取的例程。做了些修改,以获取到LVGL显示字体所需要的数据。

已知:

  1. LVGL库会传入汉字或字符的utf-8编码,字库IC使用的是GB2312编码。
  2. 获取字形数据需要知道字体大小。
  3. 需要数据区存储获取的字形数据。
  4. 需要知道得到的字形数据大小。
  5. 需要知道字体的长*宽。

因此,需要做的工作:

  1. 编码转换(放到后面)。
  2. 修改字库读取的例程,以满足上述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_txtjlx_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编码方式保存。

posted @ 2024-11-16 18:07  河东码士  阅读(96)  评论(0编辑  收藏  举报