上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

LVGL使用字库IC芯片显示中文

[复制链接]
  • TA的每日心情
    开心
    2025-7-8 11:40
  • 签到天数: 53 天

    [LV.5]常住居民I

    150

    主题

    48

    回帖

    458

    积分

    高级工程师

    积分
    458
    发表于 昨天 16:10 | 显示全部楼层 | 阅读模式

    LVGL使用字库IC芯片显示中文

    LVGL使用字库IC - 基于STM32F407

    LVGL使用的是字库IC。相比于大多数使用的数组字库方式,使用字库IC编译后占用更小的存储空间,可以解码并显示更多的汉字,能够支持更多的字体大小等。

    (顺便吆喝一句,民族企业核心部门年底前的一波岗,base武汉、深圳、苏州等地,前、后端or测试>>>机会;语言:Java、Js、测试、python、ios、安卓、C++等)!

    字库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成员表示像素内容。这里字体最大是3232,占用324个像素,因此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编码方式保存。

    转自:RT-Thread论坛用户「用RTT跑流水灯」的原创文章

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表