ESP32(ESP8266)提供中文显示解决方案:软字库详解

本方案已经开源到了 https://github.com/StarCompute/tftziku ,详细内容请访问Github.

本方案在esp32 下经过测试在tft屏幕上可以正常输出文字,也就是说经过了验证。

目录

说明

缘起

系统结构

软字库的创建

软字库包含的内容:

软字库的格式

字模的格式

软字库在单片机中的使用

在终端输出

通过单片机在tft屏幕显示


说明

本项目是为了在各种单片机使用中创建更方便易用的字库,配合使用了  ``TFT_eSPI`` 进行显示使用,实际上可以用于其他任何点阵屏。

缘起

我们都知道,要在单片机上使用汉字输出,都必须汉字点阵化,然后再集成到单片机中进行输出;然而这里有个弊病,就是你输出的汉字每次都必须使用类似于 PCtoLCD2018 这样的软件来处理,极其不方便;而如果单片机要同上位机进行文字通信的时候,上位机的文字其实不受限的,这个时候采取预处理文字的方式就不行了,虽然我自己采用过像素化进行通信,但是始终觉得非常不便。 事实上,文字的调整是个高频的事情,如果每次使用 PCtoLCD2018 来处理实在太繁琐了,因此就产生了一个基本问题:重复修改字体数据,这也同时提出了一个需求:使用软字库。 如果时光倒退无数年,会有专门卖 硬件字库 的,以及PC上类似于 UCDOS 这样软字库的东西。 单片机上各种中文的显示,大家常见的是U8G2这种东西,但是它的汉字非常不全面,基本用不起来。在使用 Tft_eSPI 的字库工具包 processing 过程中,网上有些教程误导我们先用 processing 创建vlw文件,再去转换成.h文件,最后编译进项目里面去。 这是一种完全的误导: – vlw 文件是可以被 tft_espi 项目直接使用的; – 无论是vlw还是.h格式的文件,如果是个别中文的处理毫无意义,而如果是完全的字符(类似于GB2312),这个vlw和.h文件都是非常巨大的,在我的esp32上是无法上传到flash空间中。

基于经验的积累和对于 tft_espi 的部分学习,尝试用自己的方式来创建包含 gb2312 字符集的字库,经过测试后发觉:自己创建的16号字体 GB2312 字符集整体只有507k,这个比vlw文件少了一半的空间占用。

并且, tft_espi 装载自己的vlw字库需要2秒,然后显示对应文字需要0.1x秒左右,而使用我自己创建字库利用tft_espi驱动显示同样的文字只需要0.28秒左右,整体性能提升已经达到了一个相对很高的程度。

从空间占用和显示速度上都b表现出了更优,为了方便大家更方便的进行单片机,所以把这个项目进行开源。

系统结构

软字库的创建

软字库包含的内容:

软字体用
python 进行创建,在本项目中是
getunicode.py ,它是把绝大部分能够显示出来的GB2312字符集包含进来了,然后包括进来了常见的大小写英文字母和字符(从chr(33)=chr(128)),由于
GB18030 的字库太大,所以是不可能使用
GB18030 ,使用了
GB2312 ,我们平常使用的文字基本都在这个字库中,例如“饕餮”这两个字都在GB2312中。

软字库的格式

  • 软字体以x.font方式进行命名,采用纯文本进行存储。
  • x.font文件被分成了4个部分:
  • 前6位16进制表示总共存储了多少字符;
  • 7-8位用10进制表示存储的是多少号字体,
  • 9-xxxx*5位存储的是字符的unicode编码,为了节省存储空间,一个汉字采取的是u+4位unicode编码;例如"u3001u3002u30fbu02c9u02c7" 是“、。・ˉˇ”对应的存储;每个GB2312字符转化为5个字符进行存储。
  • xxxx*5+1至xxxx*8存储的是每个字符对应的取字模数据的16进制编码
  • 举例来说,假设我们的字库只有一个 “ “ 字,用16号字体存储,那么它的整体内容如下:

    00000116u597d100010fc10041008fc102420242025fe24204820282010202820442084a00040
    

    如果用12好字体存储,那么存储内容如下::

    00000112u597d200027c02040f880490049004fe091005100210051008b00
    

    对比16号和12号字体::

    000001  16  u597d   100010fc10041008fc102420242025fe24204820282010202820442084a00040
    000001  12  u597d   200027c02040f880490049004fe091005100210051008b00
    

    000001 表示 总共有1个字符 16和12表示存储的字号 u597d 是"好”字的unicode编码 再后面分别对应着

    100010fc10041008fc102420242025fe24204820282010202820442084a00040
    

    对应

    {0x10,0x00,0x10,0xFC,0x10,0x04,0x10,0x08,0xFC,0x10,0x24,0x20,0x24,0x20,0x25,0xFE},
    {0x24,0x20,0x48,0x20,0x28,0x20,0x10,0x20,0x28,0x20,0x44,0x20,0x84,0xA0,0x00,0x40},/*"好",0*/
    

    200027c02040f880490049004fe091005100210051008b00
    

    对应

    {0x20,0x00,0x27,0xC0,0x20,0x40,0xF8,0x80,0x49,0x00,0x49,0x00,0x4F,0xE0,0x91,0x00},
    {0x51,0x00,0x21,0x00,0x51,0x00,0x8B,0x00},/*"好",0*/
    

    为了压缩存储内容,把0x10,0x00, 这种直接转化成了1000,这样的化,存储的内容就只有标志16进制格式的五分之二。

    如果,这个字库存储的字符是 “你真好看啊!” ,它的整体内容如下::

    00000612u4f60u771fu597du770bu554au00211400140027e024206940a10025402520252029202100230004007fc004003f8020803f8020803f802080ffe011002080200027c02040f880490049004fe091005100210051008b0003c07c0004007fc00800ffe020407fc0a0403fc020403fc00000eee0aa40abc0ad40ad40ab40abc0ea40ac40084008c0000000002000200020002000200000000000200000000000
    

    经过整理后如下::

    000006  12  u4f60   u771f   u597d   u770b   u554a   u0021   1400140027e024206940a10025402520252029202100230004007fc004003f8020803f8020803f802080ffe011002080200027c02040f880490049004fe091005100210051008b0003c07c0004007fc00800ffe020407fc0a0403fc020403fc00000eee0aa40abc0ad40ad40ab40abc0ea40ac40084008c0000000002000200020002000200000000000200000000000
    

    掌握了这种格式的存储,就知道了如何获取到对应的内容,值得特别说明的是,为了方便计算,字模的16进制数据采取的是8位一个存储,12号字体其实可以认为是12*12个像素,但是存储的时候为了某种方便采取了16*12这种方式;所以一定程度来讲,这个字库还有压缩和提升性能的潜力可以挖掘;

    在创建字库的时候,如果字符集里面有英文字母,将对英文字母补00进行操作,例如上面的"!" 它的unicode编码系统生产的是"u21",把它补0转换后就是"u0021". 字库中,字符的存储使用 ``u `` 一方面表示这是unicode 编码,另外一方面这个u也骑着分割符的重要作用,它能保证,这段编码被检索的时候不会重复,因为unicode编码不会一样,而如果没有使用 ``u `` ,只是存储 编码数字的话肯定会出现某个编码互相组合出现重复这种情况。

    字模的格式

    经过学习发现 TFT_eSPI 使用的vlw格式字库文件,其实是一种组合式的图片文件,而TFT_eSPI对于每个字符的显示其实就是动态取模。 本字库是提前对于字符取模,它采取把对应的字符画到图片上,然后获取每个位置的像素,每8个模 编码为16进制。取模顺序是从左到右,从上到下

    软字库在单片机中的使用

    字体文件在单片机中的使用,其实是一个逆向过程:

  • 上传生成的字库到单片机中
  • 输入要显示的汉字
  • 读取x.font文件,读取前6位,获得总共有多少个字符;
  • 再读取2位,确定字体对应的字号;
  • 读取unicode字符集,判断是否同输入汉字的unicode匹配
  • 利用匹配到的顺序,计算出字模的位置,依照字号获取对应长度的数据
  • 把字模的16进制编码重新编码为二进制
  • 利用TFT_eSPI 的 drawpix 方法把汉字输出到屏幕上(这里会有个方法计算能够显示多少汉字)
  • 字库调用完成
  • .. 注意:: 英文字符的特殊性暂未处理。
    
        由于英文基本都是半角符号,中文是全角符号,理论上英文的输出只有中文的一半,但是本字库暂时未处理英文的半角输出问题,全部是全角输出,后续再进行整理。
    
    

    在终端输出

       String strBinDisplay = getPixBinStrFromString("这是一个软字体的显示你看看再多如何显示出来啊!你说你项羽突然的自我伍佰向天再借五百年");
    
        // 下面代码在终端输出文字点阵。
        Serial.println(strBinDisplay.length());
        for (int i = 0; i < strBinDisplay.length(); i++)
        {
            if (i % 16 == 0)
            Serial.print("\r\n");
            if (strBinDisplay[i] == '0')
            Serial.print(' ');
            if (strBinDisplay[i] == '1')
            Serial.print(strBinDisplay[i]);
        }
    

    终端显示如下:::

       

               

               1

               1    111111  

               1         1  

               1        1  

            111111     1

              1  1    1

              1  1    1

              1  1 11111111

              1  1    1    

             1  1     1

              1 1     1

               1      1

              1 1     1

             1   1    1

            1    1  1 1

                 1

               1     1

               1     1

               1     1      

            1111111  11111

              1     1    1

              1 1   1   1

             1  1  1  1

             111111   1

                1     1

                1    1 1

                111  1 1

            11111    1 1

             1  1   1   1

                1   1   1

                1  1     1

                1 1       1

     

    通过单片机在tft屏幕显示

    
        // 下面代码在TFT屏幕输出文字
        int pX = 16;
        int pY = 0;
        int fontsize=16; //字号
        int amountDisplay=10; //每行显示多少汉字
        int singleStrPixsAmount=fontsize*fontsize;
        for (int i = 0; i < strBinDisplay.length(); i++)
        {
            // 这里必须有特别的说明,每个字符的像素点总数是singleStrPixsAmount=fontsize*fontsize,如果是16号字体就是256个;
            // 每行显示10个字,那么他们到一点阶段就必须换行,x坐标归0,y坐标必须加上字体对应的像素
            // 对于pX,每显示fontsize个像素后就必须字体归到起始点,注意每显示n字符后,这个起始点就必须加上fontsize*n这个起始值
            // 同时对于换行也必须处理。
    
            pX=int(i%fontsize)+int(i/(singleStrPixsAmount))*fontsize-int(i/(singleStrPixsAmount*amountDisplay))*fontsize*amountDisplay;
    
            // 对于pY,每fontsize个像素后+1,每singleStrPixsAmount个像素后归0,同时每换一行,pY要加上fontsize个像素;
            pY =int((i-int(i/singleStrPixsAmount)*singleStrPixsAmount)/fontsize)+int(i/(singleStrPixsAmount*amountDisplay))*fontsize;
    
            if (strBinDisplay[i] == '1')
            {
            tft.drawPixel(pX, pY, TFT_GREEN);
            }
        }
    

     

    物联沃分享整理
    物联沃-IOTWORD物联网 » ESP32(ESP8266)提供中文显示解决方案:软字库详解

    发表评论