OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2439|回复: 0

《STM32F407 探索者开发指南》第五十二章 汉字显示实验

[复制链接]

1140

主题

1152

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4895
金钱
4895
注册时间
2019-5-8
在线时间
1248 小时
发表于 2023-9-7 16:05:44 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-9-4 15:03 编辑

第五十二章 汉字显示实验

1)实验平台:正点原子探索者STM32F407开发板

2) 章节摘自【正点原子】STM32F407开发指南 V1.1


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)STM32技术交流QQ群:151941872

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

本章,我们将介绍如何使用STM32控制LCD显示汉字。在本章中,我们将使用外部FLASH来存储字库,并可以通过SD卡更新字库。STM32读取存在FLASH里面的字库,然后将汉字显示在LCD上面。本章分为如下几个小节:
52.1 汉字显示原理简介
52.2 硬件设计
52.3 程序设计
52.4 下载验证

52.1 汉字显示原理简介
汉字的显示和ASCII显示其实是一样的原理,如图51.1.1所示:                                 
image002.png
图51.1.1 单个汉字显示原理框图

上图显示了单个汉字显示的原理框图,单片机(MCU)先根据汉字编码(①,②)从字库里面找到该汉字的点阵数据(③),然后通过描点函数,按字库取模方式,将点阵数据在LCD上画出来(④),就可以实现一个汉字的显示。
接下来,重点介绍一下汉字的:编码、字库及显示等相关知识。

52.1.1 字符编码介绍
单片机只能识别0和1,所有信息都是以0和1的形式存储的,单片机本身并不能识别字符,所以我们需要对字符进行编码(也叫内码,特定的编码对应特定的字符),单片机通过编码来识别具体的汉字。常见的字符集编码如表:51.1.1.1所示:
QQ截图20230904145927.png
表51.1.1.1 常见字符集编码

其中ASCII编码最简单,采用单字节编码,在前面的OLED和LCD实验,我们已经有所接触。ASCII是基于拉丁字母的一套电脑编码系统,仅包括128个编码,其中95个显示字符,使用一个字节即可编码完所有字符,我们常见的英文字母和数字,就是使用ASCII字符编码,另外ASCII字符显示所占宽度为汉字宽度的一半!也可以理解成:ASCII字符的宽度 = 高度的一半。

GB2312、GBK和BIG5都是汉字编码,GBK码是GB2312的扩充,是国内计算机系统默认的汉字编码,而BIG5则是繁体汉字字符集编码,在香港和台湾的计算机系统汉字编码一般默认使用BIG5编码。一般来说,汉字显示所占的宽度等于高度,即宽度和高度相等。

UNICODE是国际标准编码,支持各国文字,一般是2字节编码(也可以是3字节),这里不做讨论。想详细了解的可以自行百度学习。

接下来,我们重点介绍一下GBK编码。

GBK是一套汉字编码规则,采用双字节编码,共23940个码位,收录汉字和图形符号21886个,其中汉字(含繁体字和构件)21003个,图形符号883个。

每个GBK码由2个字节组成,第一个字节范围:0X81~0XFE,第二个字节分为两部分,一是:0X40~0X7E,二是:0X80~0XFE。其中与GB2312相同的区域,字完全相同。GBK编码规则如表51.1.1.2所示:
QQ截图20230904145940.png
表51.1.1.2 GBK编码规则

我们把第一个字节(高字节)代表的意义称为区,那么GBK里面总共有126个区(0XFE - 0X81 + 1),每个区内有190个汉字(0XFE - 0X80 + 0X7E - 0X40 + 2),总共就有126*190=23940个汉字。

第一个编码:0X8140,对应汉字:丂;
第二个编码:0X8141,对应汉字:丄;
第三个编码:0X8142,对应汉字:丅;
第四个编码:0X8143,对应汉字:丆;

依次对所有汉字进行编码,详见:www.qqxiuzi.cn/zh/hanzi-gbk-bianma.php

52.1.2 汉字字库简介
光有汉字编码,单片机还是无法在LCD上显示这个汉字的,必须有对应汉字编码的点阵数据,才可以通过描点的方式,将汉字显示在LCD上。所有汉字点阵数据的集合,就叫做汉字字库。而不同大小的汉字,其字库大小也不一样,因此又有不同大小汉字的字库(如:12*12汉字字库、16*16汉字字库、24*24汉字字库等)。

单个汉字的点阵数据,也称之为字模。汉字在液晶上的显示其实就是一些点的显示与不显示,这就相当于我们的笔一样,有笔经过的地方就画出来,没经过的地方就不画。为了方便取模和描点,我们一般规定一个取模方向,当取模和描点都按取模方向来操作,就可以实现一个汉字的点阵数据提取和显示。

以12*12大小的“好”字为例,假设我们规定取模方向为:从上到下,从左到右,且高位在前,则其取模原理如图51.1.2.1所示:   
image003.png
图51.1.2.1 从上到下,从左到右取模原理

图中,我们取模的时候,从最左上方的点开始取(从上到下,从左到右),且高位在前(bit7在表示第一个位),那么:
第一个字节是:0X11(1,表示浅蓝色的点,即要画出来的点,0则表示不要画出来);
第二个字节是:0X10;
第三个字节是:0X1E(到第二列了,每列2个字节);
第四个字节是:0XA0;

以此类推,共12列,每列2个字节,总共24字节,12*12“好”字完整的字模如下:
  1. uint8_t hzm_1212[24]={
  2. 0x11,0x10,0x1E,0xA0,0xF0,0x40,0x11,0xA0,0x1E,0x10,0x42,0x00,
  3. 0x42,0x10,0x4F,0xF0,0x52,0x00,0x62,0x00,0x02,0x00,0x00,0x00};  /* 好字字模*/
复制代码
在显示的时候,我们只需要读取这个汉字的点阵数据(12*12字体,一个汉字的点阵数据为24个字节),然后将这些数据,按取模方式,反向解析出来(坐标要处理好),每个字节,是1的位,就画出来,不是1的位,就忽略,这样,就可以显示出这个汉字了。

知道显示一个汉字的原理,就可以推及整个汉字库了,要显示任意汉字,我们首先要知道该汉字的点阵数据,整个GBK字库是比较大的(2W多个汉字),这些数据可以由专门的软件来生成。

字库的制作
字库的制作,我们要用到一款软件,由易木雨软件工作室设计的点阵字库生成器 V3.8。该软件可以在WINDOWS 系统下生成任意点阵大小的ASCII,GB2312(简体中文)、GBK(简繁体中文)、BIG5(繁体中文)、HANGUL(韩文)、SJIS(日文)、Unicode 以及泰文,越南文、俄文、乌克兰文,拉丁文,8859 系列等共二十几种编码的字库,不但支持生成二进制文件格式的文件,也可以生成BDF文件,还支持生成图片功能,并支持横向,纵向等多种扫描方式,且扫描方式可以根据用户的需求进行增加。该软件的默认界面如图51.1.2.2所示:
image005.png
图51.1.2.2 点阵字库生成器默认界面

要生成16*16的GBK字库,则选择:936中文PRC GBK,字宽和高均选择16,字体大小选择12,然后模式选择纵向取模方式二(从上到下,从左到右,且字节高位在前,低位在后),最后点击创建,就可以开始生成我们需要的字库了(.DZK文件,在生成完以后,我们手动修改后缀为.fon)。具体设置如图51.1.2.3所示:
image006.png
图51.1.2.3 生成GBK16*16字库的设置方法

注意:电脑端的字体大小与我们生成点阵大小的关系为:
  1. WCHAR ff_uni2oem (  /* Returns OEMcode character, zero on error */
  2.     DWORD  uni,     /* UTF-16 encodedcharacter to be converted */
  3.     WORD   cp        /* Code page for the conversion */
  4. )
  5. {
  6.     const WCHAR *p;
  7.     WCHAR c = 0, uc;
  8.     UINT i = 0, n, li, hi;
  9.     if (uni < 0x80) /* ASCII? */
  10.     {
  11.         c = (WCHAR)uni;
  12.     }
  13.     else              /* Non-ASCII */
  14.     {
  15.         if (uni < 0x10000 && cp == FF_CODE_PAGE)/* in BMP andvalid code page? */
  16.         {
  17.             uc = (WCHAR)uni;
  18.             p = CVTBL(uni2oem, FF_CODE_PAGE);
  19.             hi = sizeof CVTBL(uni2oem, FF_CODE_PAGE) / 4 - 1;
  20.             li = 0;
  21.             for (n = 16; n; n--)
  22.             {
  23.                 i = li + (hi - li) / 2;
  24.                 if (uc == p[i * 2]) break;
  25.                 if (uc > p[i * 2])
  26.                 {
  27.                     li = i;
  28.                 }
  29.                 else
  30.                 {
  31.                     hi = i;
  32.                 }
  33.             }
  34.             if (n != 0) c = p[i * 2 + 1];
  35.         }
  36.     }
  37.     return c;
  38. }
  39. WCHAR ff_oem2uni (  /* Returns Unicodecharacter in UTF-16, zero on error */
  40.     WCHAR  oem,     /* OEM code to beconverted */
  41.     WORD   cp        /* Code page for the conversion */
  42. )
  43. {
  44.     const WCHAR *p;
  45.     WCHAR c = 0;
  46.     UINT i = 0, n, li, hi;
  47.     if (oem < 0x80) /* ASCII? */
  48.     {
  49.         c = oem;
  50.     }
  51.     else              /* Extended char*/
  52.     {
  53.         if (cp == FF_CODE_PAGE)     /* Is it validcode page? */
  54.         {
  55.             p = CVTBL(oem2uni, FF_CODE_PAGE);
  56.             hi = sizeof CVTBL(oem2uni, FF_CODE_PAGE) / 4 - 1;
  57.             li = 0;
  58.             for (n = 16; n; n--)
  59.             {
  60.                 i = li + (hi - li) / 2;
  61.                 if (oem == p[i * 2]) break;
  62.                 if (oem > p[i * 2])
  63.                 {
  64.                     li = i;
  65.                 }
  66.                 else
  67.                 {
  68.                     hi = i;
  69.                 }
  70.             }
  71.             if (n != 0) c = p[i * 2 + 1];
  72.         }
  73.     }
  74.     return c;
  75. }
复制代码
以上两个函数,我们只需要关心对中文的处理,也就是对936的处理,这两个函数通过二分法来查找UNICODE(或GBK)码对应的GBK(或UNICODE)码。当我们将两个数组存放在外部flash的时候,这两个函数该可以修改为:
  1. WCHAR ff_uni2oem (  /* Returns OEMcode character, zero on error */
  2.     DWORD  uni,     /* UTF-16 encodedcharacter to be converted */
  3.     WORD   cp        /* Code page for the conversion */
  4. )
  5. {
  6.     WCHAR t[2];
  7.     WCHAR c;
  8.     uint32_t i, li, hi;
  9.     uint16_t n;
  10.     uint32_t gbk2uni_offset = 0;
  11.     if (uni < 0x80)
  12.     {
  13.         c = uni;     /* ASCII,直接不用转换 */
  14.     }
  15.     else
  16.     {
  17.         hi = ftinfo.ugbksize / 2;    /* 对半开 */
  18.         hi = hi / 4 - 1;
  19.         li = 0;
  20.         for (n = 16; n; n--)         /* 二分法查找 */
  21.         {
  22.             i = li + (hi - li) / 2;
  23.             norflash_read((uint8_t *)&t, ftinfo.ugbkaddr + i * 4 +
  24. gbk2uni_offset, 4);   /* 读出4个字节 */
  25.             if (uni == t[0]) break;
  26.             if (uni > t[0])
  27.             {
  28.                 li = i;
  29.             }
  30.             else
  31.             {
  32.                 hi = i;
  33.             }
  34.         }
  35.         c = n ? t[1] : 0;
  36.     }
  37.     return c;
  38. }
  39. WCHAR ff_oem2uni (  /* Returns Unicodecharacter, zero on error */
  40.     WCHAR  oem,     /* OEM code to beconverted */
  41.     WORD   cp        /* Code page for the conversion */
  42. )
  43. {
  44.     WCHAR t[2];
  45.     WCHAR c;
  46.     uint32_t i, li, hi;
  47.     uint16_t n;
  48.     uint32_t gbk2uni_offset = ftinfo.ugbksize / 2;
  49.     if (oem < 0x80)
  50.     {
  51.         c = oem;    /* ASCII,直接不用转换 */
  52.     }
  53.     else
  54.     {
  55.         hi = ftinfo.ugbksize / 2;    /* 对半开 */
  56.         hi = hi / 4 - 1;
  57.         li = 0;
  58.         for (n = 16; n; n--)         /* 二分法查找 */
  59.         {
  60.             i = li + (hi - li) / 2;
  61.             norflash_read((uint8_t *)&t, ftinfo.ugbkaddr + i * 4 +
  62. gbk2uni_offset, 4);   /* 读出4个字节 */
  63.             if (oem == t[0]) break;
  64.             if (oem > t[0])
  65.             {
  66.                 li = i;
  67.             }
  68.             else
  69.             {
  70.                 hi = i;
  71.             }
  72.         }
  73.         c = n ? t[1] : 0;
  74.     }
  75.     return c;
  76. }
复制代码
代码中的ftinfo.ugbksize为我们刚刚生成的UNIGBK.bin的大小,而ftinfo.ugbkaddr是我们存放UNIGBK.bin文件的首地址,这里同样采用的是二分法查找。

修改后的ffunicode.c,我们将其命名为myffunicode.c,并保存在exfuns文件夹下,将工程FATFS组下的ffunicode.c删除,然后重新添加myffunicode.c到FATFS组下,myffunicode.c的源码就不贴出来了,其实就是在ffunicode.c的基础上去掉了两个大数组,然后对ff_uni2oem和ff_oem2uni两个函数进行了修改,详见本例程源码。

关于ffunicode.c的修改,我们就介绍到这。

52.2 硬件设计
1. 例程功能
本实验开机的时候先检测norflash中是否已经存在字库,如果存在,则按次序显示汉字(四种字体都显示)。如果没有,则检测SD卡和文件系统,并查找SYSTEM文件夹下的FONT文件夹,在该文件夹内查找UNIGBK.BIN、GBK12.FON、GBK16.FON和GBK24.FON。在检测到这些文件之后,就开始更新字库,更新完毕才开始显示汉字。通过按按键KEY0,可以强制更新字库。
LED0闪烁,提示程序运行。

2. 硬件资源
1)LED灯
     LED0 – PF9
2)独立按键
     KEY0 – PE4
3)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
4)正点原子 2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
5)SD卡
6)NOR FLASH(SPI FLASH芯片,连接在SPI1上)

52.3 程序设计
52.3.1 程序流程图
QQ截图20230904150202.png
图 52.3.1.1汉字显示实验程序流程图

52.3.2 程序解析
1. TEXT代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。TEXT驱动源码包括四个文件:text.c、text.h、fonts.c和fonts.h。

汉字显示实验代码主要分为两部分:一部分就是对字库的更新,另一部分就是对汉字的显示。字库的更新代码放在font.c和font.h文件中,汉字的显示代码就放在text.c和text.h中。

下面我们介绍一下有关字库操作的代码,首先我们先看一下fonts.h文件中字库信息结构体定义,其代码如下:
  1. /* 字库信息结构体定义
  2. * 用来保存字库基本信息,地址,大小等
  3. */
  4. __packed typedef struct
  5. {
  6.     uint8_t  fontok;             /* 字库存在标志,0XAA,字库正常;其他,字库不存在 */
  7.     uint32_t ugbkaddr;           /* unigbk的地址 */
  8.     uint32_t ugbksize;           /* unigbk的大小 */
  9.     uint32_t f12addr;             /* gbk12地址 */
  10.     uint32_t gbk12size;          /* gbk12的大小 */
  11.     uint32_t f16addr;             /* gbk16地址 */
  12.     uint32_t gbk16size;          /* gbk16的大小 */
  13.     uint32_t f24addr;             /* gbk24地址 */
  14. uint32_t gbk24size;          /* gbk24的大小 */
  15. } _font_info;
复制代码
这个结构体用于记录字库的首地址以及字库大小等信息,总共占用33个字节,第一个字节用来标识字库是否OK,其他的用来记录地址和文件大小。因为我们将NORFLASH(25Q128)的前12M字节给了FATFS管理(用做本地磁盘),12M字节后紧跟3个字库+UNIGBK.BIN总大小3.09M字节791个扇区,在15.10M字节后,预留了100K字节给用户自己使用。所以,我们的存储地址是从12*1024*1024处开始的。最开始的33个字节给_font_info用,用于保存_font_info结构体数据,之后是UNIGBK.BIN、GBK12.FON、GBK16.FON和GBK24.FON。

下面介绍font.c文件几个重要的函数。

字库初始化函数也是利用其存储顺序,进行检查字库,其定义如下:
  1. /**
  2. *@brief        初始化字体
  3. *@param        无
  4. *@retval       0, 字库完好; 其他, 字库丢失;
  5. */
  6. uint8_t fonts_init(void)
  7. {
  8.     uint8_t t = 0;
  9.     while (t < 10)  /* 连续读取10次,都是错误,说明确实是有问题,得更新字库了 */
  10.     {
  11.        t++;
  12. /* 读出ftinfo结构体数据 */
  13.        norflash_read((uint8_t *)&ftinfo, FONTINFOADDR, sizeof(ftinfo));
  14.        if (ftinfo.fontok == 0XAA)
  15.        {
  16.            break;
  17.        }
  18.       
  19.        delay_ms(20);
  20.     }
  21.     if (ftinfo.fontok != 0XAA)
  22.     {
  23.        return 1;
  24.     }
  25.     return 0;
  26. }
复制代码
这里就是把NORFLASH的12M地址的33个字节数据读取出来,进而判断字库结构体ftinfo的字库标记fontok是否为AA,确定字库是否完好。

有人会有疑问,ftinfo.fontok在哪里赋值AA呢?肯定是字库更新完毕后,给该标记赋值的,那下面就来看一下是不是这样子,字库更新函数定义如下:
  1. /**
  2. *@brief        更新字体文件
  3. *  @note       所有字库一起更新(UNIGBK,GBK12,GBK16,GBK24)
  4. *@param        x, y    : 提示信息的显示地址
  5. *@param        size    : 提示信息字体大小
  6. *@param        src     : 字库来源磁盘
  7. *  @arg                  "0:", SD卡;
  8. *  @arg                  "1:", FLASH盘
  9. *@param        color   : 字体颜色
  10. *@retval       0, 成功; 其他, 错误代码;
  11. */
  12. uint8_t fonts_update_font(uint16_t x, uint16_t y, uint8_t size,
  13. uint8_t *src, uint16_t color)
  14. {
  15.     uint8_t *pname;
  16.     uint32_t *buf;
  17.     uint8_t res = 0;
  18.     uint16_t i, j;
  19.     FIL*fftemp;
  20.     uint8_t rval = 0;
  21.     res= 0XFF;
  22.    ftinfo.fontok = 0XFF;
  23.    pname = mymalloc(SRAMIN, 100);      /* 申请100字节内存 */
  24.     buf= mymalloc(SRAMIN, 4096);       /* 申请4K字节内存 */
  25.    fftemp = (FIL *)mymalloc(SRAMIN, sizeof(FIL));  /* 分配内存 */
  26.     if (buf == NULL || pname == NULL || fftemp == NULL)
  27.     {
  28.        myfree(SRAMIN, fftemp);
  29.        myfree(SRAMIN, pname);
  30.        myfree(SRAMIN, buf);
  31.        return 5;   /* 内存申请失败 */
  32.     }
  33.     for (i = 0; i < 4; i++) /* 先查找文件UNIGBK,GBK12,GBK16,GBK24是否正常 */
  34.     {
  35.        strcpy((char *)pname, (char *)src);    /* copy src内容到pname */
  36.        strcat((char *)pname, (char *)FONT_GBK_PATH);     /* 追加具体文件路径 */
  37.        res = f_open(fftemp, (const TCHAR *)pname, FA_READ);/* 尝试打开 */
  38.        if (res)
  39.        {
  40.            rval |= 1 << 7;  /* 标记打开文件失败 */
  41.            break;             /* 出错了,直接退出 */
  42.        }
  43.     }
  44.    myfree(SRAMIN, fftemp);     /* 释放内存 */
  45.     if (rval == 0)                /* 字库文件都存在. */
  46. {
  47. /* 提示正在擦除扇区 */
  48.        lcd_show_string(x, y, 240, 320, size, "Erasingsectors... ", color);
  49.        for (i = 0; i < FONTSECSIZE; i++)        /* 先擦除字库区域,提高写入速度 */
  50.        {
  51.            fonts_progress_show(x + 20 * size / 2, y, size, FONTSECSIZE, i,
  52. color);          /* 进度显示 */
  53.            norflash_read((uint8_t *)buf, ((FONTINFOADDR / 4096) + i) * 4096,
  54. 4096);             /* 读出整个扇区的内容 */
  55.            for (j = 0; j < 1024; j++)            /* 校验数据 */
  56.            {
  57.                 if (buf[j] != 0XFFFFFFFF)break;   /* 需要擦除 */
  58.            }
  59.            if (j != 1024)
  60.            {
  61. /* 需要擦除的扇区 */
  62.                 norflash_erase_sector((FONTINFOADDR / 4096) + i);
  63.            }
  64.        }
  65.        for (i = 0; i < 4; i++)    /* 依次更新UNIGBK,GBK12,GBK16,GBK24 */
  66.        {
  67.            lcd_show_string(x, y, 240, 320, size, FONT_UPDATE_REMIND_TBL,
  68. color);
  69.            strcpy((char *)pname, (char *)src);     /* copy src内容到pname */
  70.            strcat((char *)pname, (char *)FONT_GBK_PATH);   /* 追加具体文件路径 */
  71.            res =fonts_update_fontx(x + 20 * size / 2, y, size, pname, i,
  72. color);    /* 更新字库 */
  73.            if (res)
  74.            {
  75.                 myfree(SRAMIN, buf);
  76.                 myfree(SRAMIN, pname);
  77.                 return 1 + i;
  78.            }
  79.        }
  80.        ftinfo.fontok = 0XAA;    /* 全部更新好了 */
  81. /* 保存字库信息 */
  82.        norflash_write((uint8_t *)&ftinfo, FONTINFOADDR, sizeof(ftinfo));
  83.     }
  84.    myfree(SRAMIN, pname);       /* 释放内存 */
  85.    myfree(SRAMIN, buf);
  86.     return rval;                  /* 无错误. */
  87. }
复制代码
函数的实现:动态申请内存→尝试打开文件(UNIGBK、GBK12、GBK16和GBK24),确定文件是否存在→擦除字库→依次更新UNIGBK、GBK12、GBK16和GBK24→写入ftinfo结构体信息。

在字库更新函数中能直接看到的是ftinfo.fontok成员被赋值,而其他成员在单个字库更新函数中被赋值,接下来分析一下更新某个字库函数,其代码如下:
  1. /**
  2. *@brief        更新某一个字库
  3. *@param        x, y    : 提示信息的显示地址
  4. *@param        size    : 提示信息字体大小
  5. *@param        fpath   : 字体路径
  6. *@param        fx      : 更新的内容
  7. *  @arg                 0, ungbk;
  8. *  @arg                 1, gbk12;
  9. *  @arg                 2, gbk16;
  10. *   @arg                3, gbk24;
  11. *@param        color   : 字体颜色
  12. *@retval       0, 成功; 其他, 错误代码;
  13. */
  14. static uint8_t fonts_update_fontx(uint16_t x, uint16_t y, uint8_t size,
  15. uint8_t *fpath, uint8_t fx, uint16_t color)
  16. {
  17.     uint32_t flashaddr = 0;
  18.     FIL*fftemp;
  19.     uint8_t *tempbuf;
  20.     uint8_t res;
  21.     uint16_t bread;
  22.     uint32_t offx = 0;
  23.     uint8_t rval = 0;
  24.    fftemp = (FIL *)mymalloc(SRAMIN, sizeof(FIL));      /* 分配内存 */
  25.     if (fftemp == NULL)rval = 1;
  26.    tempbuf = mymalloc(SRAMIN, 4096);                     /* 分配4096个字节空间 */
  27.     if (tempbuf == NULL)rval = 1;
  28.     res= f_open(fftemp, (const TCHAR *)fpath, FA_READ);
  29.     if (res)rval = 2;    /* 打开文件失败 */
  30.     if (rval == 0)
  31.     {
  32.        switch (fx)
  33.        {
  34.            case 0:   /* 更新 UNIGBK.BIN */
  35. /* 信息头之后,紧跟UNIGBK转换码表 */
  36.                 ftinfo.ugbkaddr = FONTINFOADDR + sizeof(ftinfo);
  37.                 ftinfo.ugbksize = fftemp->obj.objsize;   /* UNIGBK大小 */
  38.                 flashaddr = ftinfo.ugbkaddr;
  39.                 break;
  40.            case 1:   /* 更新 GBK12.BIN */
  41. /* UNIGBK之后,紧跟GBK12字库 */
  42.                 ftinfo.f12addr = ftinfo.ugbkaddr + ftinfo.ugbksize;
  43.                 ftinfo.gbk12size = fftemp->obj.objsize;  /* GBK12字库大小 */
  44.                 flashaddr = ftinfo.f12addr;                 /* GBK12的起始地址 */
  45.                 break;
  46.            case 2:   /* 更新 GBK16.BIN */
  47. /* GBK12之后,紧跟GBK16字库 */
  48.                 ftinfo.f16addr = ftinfo.f12addr + ftinfo.gbk12size;
  49.                 ftinfo.gbk16size = fftemp->obj.objsize;  /* GBK16字库大小 */
  50.                 flashaddr = ftinfo.f16addr;                 /* GBK16的起始地址 */
  51.                 break;
  52.            case 3:   /* 更新 GBK24.BIN */
  53. /* GBK16之后,紧跟GBK24字库 */
  54.                 ftinfo.f24addr = ftinfo.f16addr + ftinfo.gbk16size;
  55.                 ftinfo.gbk24size = fftemp->obj.objsize;  /* GBK24字库大小 */
  56.                 flashaddr = ftinfo.f24addr;                 /* GBK24的起始地址 */
  57.                 break;
  58.        }
  59.        while (res == FR_OK)    /* 死循环执行 */
  60.        {
  61.            res = f_read(fftemp, tempbuf, 4096, (UINT *)&bread);    /* 读取数据 */
  62.            if (res != FR_OK)break;    /* 执行错误 */
  63.            /* 从0开始写入bread个数据 */
  64.            norflash_write(tempbuf, offx + flashaddr, bread);
  65.            offx += bread;
  66. /* 进度显示 */
  67.            fonts_progress_show(x, y, size, fftemp->obj.objsize, offx, color);
  68.            if (bread != 4096)break;    /* 读完了. */
  69.        }
  70.        f_close(fftemp);
  71.     }
  72.    myfree(SRAMIN, fftemp);      /* 释放内存 */
  73.    myfree(SRAMIN, tempbuf);     /* 释放内存 */
  74.     return res;
  75. }
复制代码
单个字库更新函数,主要是对把字库从SD卡中读取出数据,写入NORFLASH。同时把字库大小和起始地址保存在ftinfo结构体里,在前面的整个字库更新函数中使用函数:
  1. norflash_write((uint8_t *)&ftinfo,FONTINFOADDR,sizeof(ftinfo));   /*保存字库信息*/
复制代码
结构体的所有成员一并写入到那33个字节。有了这个字库信息结构体,就能很容易进行定位。结合前面的说到的根据地址偏移寻找汉字的点阵数据,我们就可以开始真正把汉字搬上屏幕中去了。

首先我们肯定需要获得汉字的GBK码,这里MDK已经帮我们实现了。这里用一个例子说明:
image011.png      
在这里可以看出MDK识别汉字的方式是GBK码,换句话来说就是MDK自动会把汉字看成是两个字节表示的东西。知道了要表示的汉字和其GBK码,那么就可以去找对应的点阵数据。在text.c文件中,我们就定义了一个获取汉字点阵数据的函数,其定义如下:
  1. /**
  2. *@brief        获取汉字点阵数据
  3. *@param        code  : 当前汉字编码(GBK码)
  4. *@param        mat   : 当前汉字点阵数据存放地址
  5. *@param        size  : 字体大小
  6. *  @note       size大小的字体,其点阵数据大小为: (size / 8 + ((size % 8) ? 1 : 0))
  7. * (size)  字节
  8. *@retval       无
  9. */
  10. static voidtext_get_hz_mat(unsigned char *code, unsigned char *mat,
  11. uint8_t size)
  12. {
  13.     unsigned char qh, ql;
  14.     unsigned char i;
  15. unsigned long foffset;
  16. /* 得到字体一个字符对应点阵集所占的字节数 */
  17.     uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size);
  18.     qh = *code;
  19.     ql = *(++code);
  20.     if (qh < 0x81 || ql < 0x40 || ql == 0xff || qh == 0xff)    /* 非 常用汉字 */
  21.     {
  22.        for (i = 0; i < csize; i++)
  23.        {
  24.            *mat++ = 0x00;    /* 填充满格 */
  25.        }
  26.        return;       /* 结束访问 */
  27.     }
  28.     if (ql < 0x7f)
  29.     {
  30.        ql -= 0x40;  /* 注意! */
  31.     }
  32.     else
  33.     {
  34.        ql -= 0x41;
  35.     }
  36.     qh -= 0x81;
  37.    foffset = ((unsigned long)190 * qh + ql) * csize;    /* 得到字库中的字节偏移量 */
  38.     switch (size)
  39.     {
  40.        case 12:
  41.            norflash_read(mat, foffset + ftinfo.f12addr, csize);
  42.            break;
  43.        case 16:
  44.            norflash_read(mat, foffset + ftinfo.f16addr, csize);
  45.            break;
  46.        case 24:
  47.            norflash_read(mat, foffset + ftinfo.f24addr, csize);
  48.            break;
  49.     }
  50. }
复制代码
函数实现的依据就是前面52.1.2小节讲到的两条公式:
当GBKL<0X7F时:Hp=((GBKH-0x81)*190+GBKL-0X40)*csize;
当GBKL>0X80时:Hp=((GBKH-0x81)*190+GBKL-0X41)*csize;

目标汉字的GBK码满足上面两条公式其一,就会得出与一个GBK对应的汉字点阵数据的偏移。在这个基础上,通过判断汉字的大小,就可以从对应的字库提取目标汉字点阵数据。

在获取到点阵数据后,接下来就可以进行汉字显示,下面看一下汉字显示函数,其定义如下:
  1. /**
  2. *@brief        显示一个指定大小的汉字
  3. *@param        x,y   : 汉字的坐标
  4. *@param        font  : 汉字GBK码
  5. *@param        size  : 字体大小
  6. *@param        mode  : 显示模式
  7. *  @note              0, 正常显示(不需要显示的点,用LCD背景色填充,即g_back_color)
  8. *  @note              1, 叠加显示(仅显示需要显示的点, 不需要显示的点, 不做处理)
  9. *@param        color : 字体颜色
  10. *@retval       无
  11. */
  12. voidtext_show_font(uint16_t x, uint16_t y, uint8_t *font, uint8_t size,     uint8_t mode, uint16_t color)
  13. {
  14.     uint8_t temp, t, t1;
  15.     uint16_t y0 = y;
  16. uint8_t *dzk;
  17. /* 得到字体一个字符对应点阵集所占的字节数 */
  18.     uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size);
  19.     if (size != 12 && size != 16 && size != 24 && size != 32)
  20.     {
  21.        return;     /* 不支持的size */
  22.     }
  23.     dzk= mymalloc(SRAMIN, size);        /* 申请内存 */
  24.     if (dzk == 0) return;                  /* 内存不够了 */
  25.    text_get_hz_mat(font, dzk, size);   /* 得到相应大小的点阵数据 */
  26.     for (t = 0; t < csize; t++)
  27.     {
  28.        temp = dzk[t];                      /* 得到点阵数据 */
  29.        for (t1 = 0; t1 < 8; t1++)
  30.        {
  31.            if (temp & 0x80)
  32.            {
  33.                 lcd_draw_point(x, y, color);           /* 画需要显示的点 */
  34.            }
  35.            else if (mode == 0)    /* 如果非叠加模式, 不需要显示的点,用背景色填充 */
  36.            {
  37.                 lcd_draw_point(x, y, g_back_color);  /* 填充背景色 */
  38.            }
  39.            temp <<= 1;
  40.            y++;
  41.            if ((y - y0) == size)
  42.            {
  43.                 y = y0;
  44.                 x++;
  45.                 break;
  46.            }
  47.        }
  48.     }
  49.    myfree(SRAMIN, dzk);    /* 释放内存 */
  50. }
复制代码
汉字显示函数通过调用获取汉字点阵数据函数text_get_hz_mat就获取到点阵数据,使用lcd画点函数把点阵数据中“1”的点都画出来,最终会LCD显示你所要表示的汉字。

其他函数就不多讲解,大家可以自行消化。

2. main.c代码
main.c代码如下:
  1. int main(void)
  2. {
  3.     uint32_t fontcnt;
  4.     uint8_t i, j;
  5.     uint8_t fontx[2];   /* GBK码 */
  6.     uint8_t key, t;
  7.    HAL_Init();                              /* 初始化HAL库 */
  8.    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟, 168Mhz */
  9.    delay_init(168);                        /* 延时初始化 */
  10.    usart_init(115200);                    /* 串口初始化为115200 */
  11.    usmart_dev.init(84);                  /* 初始化USMART */
  12.    led_init();                             /* 初始化LED */
  13.    lcd_init();                             /* 初始化LCD */
  14.    key_init();                              /* 初始化按键 */
  15.    my_mem_init(SRAMIN);                   /* 初始化内部SRAM内存池 */
  16.    my_mem_init(SRAMEX);                  /* 初始化外部SRAM4内存池 */
  17.    my_mem_init(SRAMCCM);                 /* 初始化CCM内存池*/
  18.    exfuns_init();                          /* 为fatfs相关变量申请内存 */
  19.    f_mount(fs[0], "0:", 1);              /* 挂载SD卡 */
  20.    f_mount(fs[1], "1:", 1);              /* 挂载FLASH */
  21.     while (fonts_init())                  /* 检查字库 */
  22.     {
  23. UPD:
  24.        lcd_clear(WHITE);                  /* 清屏 */
  25.        lcd_show_string(30, 30, 200, 16, 16, "STM32", RED);
  26.        while (sd_init())                  /* 检测SD卡 */
  27.        {
  28.            lcd_show_string(30, 50, 200, 16, 16, "SD CardFailed!", RED);
  29.            delay_ms(200);
  30.            lcd_fill(30, 50, 200 + 30, 50 + 16, WHITE);
  31.            delay_ms(200);
  32.        }
  33.        lcd_show_string(30, 50, 200, 16, 16, "SD CardOK", RED);
  34.        lcd_show_string(30, 70, 200, 16, 16, "FontUpdating...", RED);
  35.        key =fonts_update_font(20, 90, 16, (uint8_t *)"0:", RED);   /* 更新字库 */
  36.        while (key)   /* 更新失败 */
  37.        {
  38.            lcd_show_string(30, 90, 200, 16, 16, "Font UpdateFailed!", RED);
  39.            delay_ms(200);
  40.            lcd_fill(20, 90, 200 + 20, 90 + 16, WHITE);
  41.            delay_ms(200);
  42.        }
  43.        lcd_show_string(30, 90, 200, 16, 16, "Font UpdateSuccess!   ", RED);
  44.        delay_ms(1500);
  45.        lcd_clear(WHITE);/* 清屏 */
  46.     }
  47.    text_show_string(30, 30, 200, 16, "正点原子STM32开发板", 16, 0, RED);
  48.    text_show_string(30, 50, 200, 16, "GBK字库测试程序", 16, 0, RED);
  49.    text_show_string(30, 70, 200, 16, " ATOM@ALIENTEK", 16, 0, RED);
  50.    text_show_string(30, 90, 200, 16, "按KEY0,更新字库", 16, 0, RED);
  51.    
  52.    text_show_string(30, 130, 200, 16, "内码高字节:", 16, 0, BLUE);
  53.    text_show_string(30, 150, 200, 16, "内码低字节:", 16, 0, BLUE);
  54.    text_show_string(30, 170, 200, 16, "汉字计数器:", 16, 0, BLUE);
  55.    
  56.    text_show_string(30, 180, 200, 24, "对应汉字为:", 24, 0, BLUE);
  57.    text_show_string(30, 204, 200, 16, "对应汉字(16*16)为:", 16, 0, BLUE);
  58.    text_show_string(30, 220, 200, 12, "对应汉字(12*12)为:", 12, 0, BLUE);
  59.     while (1)
  60.     {
  61.        fontcnt = 0;
  62.        for (i = 0x81; i < 0xff; i++)       /* GBK内码高字节范围为0X81~0XFE */
  63.        {
  64.            fontx[0] = i;
  65.            lcd_show_num(118, 130, i, 3, 16, BLUE);   /* 显示内码高字节 */
  66. /* GBK内码低字节范围为 0X40~0X7E, 0X80~0XFE) */
  67.            for (j = 0x40; j < 0xfe; j++)
  68.            {
  69.                 if (j == 0x7f)continue;
  70.                 fontcnt++;
  71.                 lcd_show_num(118, 130, j, 3, 16, BLUE);           /* 显示内码低字节 */
  72.                 lcd_show_num(118, 150, fontcnt, 5, 16, BLUE);    /* 汉字计数显示 */
  73.                 fontx[1] = j;
  74.                 text_show_font(30 + 132, 180, fontx, 24, 0, BLUE);
  75.                 text_show_font(30 + 144, 204, fontx, 16, 0, BLUE);
  76.                 text_show_font(30 + 108, 220, fontx, 12, 0, BLUE);
  77.                 t = 200;
  78.                 while (t--)       /* 延时,同时扫描按键 */
  79.                 {
  80.                     delay_ms(1);
  81.                     key = key_scan(0);
  82.                     if (key == KEY0_PRES)
  83.                     {
  84.                         goto UPD;  /* 跳转到UPD位置(强制更新字库) */
  85.                     }
  86.                 }
  87.                 LED0_TOGGLE();
  88.            }
  89.        }
  90.     }
  91. }
复制代码
main函数实现了我们在硬件设计例程功能所表述的一致,至此整个软件设计就完成了。

52.4 下载验证
本例程支持12*12、16*16和24*24三种字体的显示,将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD开始显示三种大小的汉字及内码如图52.4.1所示:     
image013.png
图52.4.1 汉字显示实验显示效果

一开始就显示汉字,是因为板子在出厂的时候都是测试过的,里面刷了综合测试程序,已经把字库写入到NORFLASH里面,所以并不会提示更新字库。如果你想要更新字库,就需要先找一张SD卡,把A盘资料\5,SD卡根目录文件 文件夹下面的SYSTEM文件夹拷贝到SD卡根目录下,插入开发板,并按复位,之后,在显示汉字的时候,按下KEY0,就可以开始更新字库。字库更新界面如图52.4.2所示:   
image015.png
图52.4.2汉字字库更新界面

此外我们还可以使用USMART来测试该实验。通过USMART调用text_show_string或者text_show_string_middle来实现任意位置显示任何字符串,有兴趣的朋友可以尝试一下。
image008.png
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-2-24 06:23

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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