OpenEdv-开源电子网

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

《MiniPRO H750开发指南》第四十七章 汉字显示实验

[复制链接]

1140

主题

1152

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4896
金钱
4896
注册时间
2019-5-8
在线时间
1248 小时
发表于 2023-2-9 11:17:15 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-2-8 10:10 编辑

第四十七章 汉字显示实验

1)实验平台:正点原子MiniPro STM32H750开发板

2) 章节摘自【正点原子】MiniPro STM32H750 开发指南_V1.1


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

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

6)MiniPro STM32H750技术交流QQ群:170313895

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

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

47.1 汉字显示介绍
47.1.1 字符编码介绍
众所周知,计算机只能识别0和1,所有信息都是以0和1的形式在计算机里面进行存储,文字信息也是一样。那么如何进行区分文字信息呢?这里就引出了字符编码这个概念,就是对字符进行编码,用规定的01数字串来表示特定的文字,最简单的字符编码例子是ASCII码。此外还有中文编码,中文编码又可以细分GB2312,GB13000,GBK,BIG5(繁体)等几种。

1.ASCII编码
ASCII编码可以用来表示一些控制字符、英文以及数字,本质上都是二进制数,遵循一定的约定,所以就有了ASCII码表。ASCII码表分为两个部分,第一部分数字编码范围0~31是控制字符或者是通讯专用字符,是不具备图形显示但对文本显示有影响的编码;第二部分数字编码范围32~127是包含我们日常用到的符号信息,空格、阿拉伯数字、标点符号、大小写英文以及“DEL(删除控制)”,除了DEL不具备图形显示外,其他都具备。工程代码中也有ASCII的编码表。下面看一下ASCII编码表第二部分中的可显示字符如图47.1.1.1所示:
image001.png
图47.1.1.1 可显示字符

2. 中文编码
由于英文文字是由26个字母排列组合而成的,所以ASCII表就可以适用于表达英文词典库。在汉字系统中,每一个汉字都是一个独立的个体,但是汉字可以以偏旁以及笔画进行划分,不过也是十分杂乱,毕竟汉字现在已经有8万多个了,常用的只有3500个。所以中文编码是直接对方块字进行编码的,一个汉字使用一个编码。

汉字库十分庞大,需要使用2个字节进行编码,假如和ASCII码一样只使用1字节去编码,那只能表示256个汉字,太少了。在前面提及到GB2312,GB13000,GBK,BIG5(繁体)等,它们都是一个标准。下面对具有代表性的GB2312和GBK进行讲解。

GB2312编码
GB2312是一个简体中文字符集的中国国家标准,也是我们通常说到的国标码。GB2312由6763个常用汉字和682个全角的非汉字字符组成。其中根据汉字使用的频率分为两级。一级汉字3755个,二级汉字3008个。由于字符数量比较大,GB2312采用了二维矩阵编码法对所有字符进行编码。构造一个94行94列的方阵,对每一行称为一个“区”,每一列称为一个“位”,然后把所有的字符都依照“GB2312字符编码规则”,写入方阵中,这样子所有的字符在方阵中都有一个唯一的位置,这个位置就可以用区号和位号组合表示,称为字符的区位码。

因为GB2312与西文的存储存在冲突,所以GB2312字符在进行存储时,将原来的每一个字节第8位设置为1和西文加以区分,如果第8bit为0,则表示西文字符,否则就是GB2312字符。在实际存储时,采用将区位码的每个字节分别加上A0H(160)的方法转换为储存码,计算机存储规则则是此编码的补码,而且是位码在前,区码在后。举个例子:汉字“啊”的区位码为1601,其存储码为B0A1H,转换过程为:
QQ截图20230208094825.png

GBK编码
GBK编码即汉字内码扩展规范,完全兼容GB2312,在GB2312的基础上,支持繁体字、人名、古汉语等方面出现的罕用字。GBK采用的是双字节表示,总体编码范围为0x8140~0xFEFE,第一个字节在0x81~0xFE之间,第二个字节分为两个部分,一是0x40~0x7E,二是0x80~0xFE。其中和GB2312相同的区域,字完全相同,可表示的汉字数达到了2万多个,完全能满足我们的一般应用的要求。如图47.1.1.2 GBK码位分布图:
image004.png
图47.1.1.2 GBK码位分布图

在前面GB2312也说到,第一字节称为区,那么GBK里面总共就有126个区(0xFE-0x81+1),第二个字节称为位,我们也可以理解为每个区里面包含的汉字即190个汉字(0xFE-0X80+0X7E-0X40+2),GBK字库总共就126*190=23940个汉字。

3.全球统一编码
前一小节讲解的都是中国标准,只有中国使用,并没有表示大多数其他国家的编码。而其他国家又陆续推出各自的编码标准,互不兼容,非常不利于国际交流。所以在后来国际标准化组织(ISO)发行了一个全球统一编码表,把全球各国文字都统一在一个编码标准里,这个全球统一编码表就叫做Unicode。Unicode字符集对世界上各国文化使用到的字母和符号进行标号,对每一个字符都分配一个唯一的编号,字符的编号从0x000000到0x10FFFF。

Unicode没有规定字符对应的二进制码如何存储,只是对每一个字符进行编号。为了解决Unicode编码问题,UTF-8、UTF-16和UTF-32的编码方式诞生了。

UTF-8编码
UTF-8是一个非常常用的编码方式,漂亮的实现了对ASCII码的向后兼容,它是目前互联网上使用最广泛的一种Unicode编码方式,它的最大特点就是可变长。它可以使用1-4个字节表示一个字符,根据字符的不同变换长度。编码规则如下:

1.对于单字节的字符,第一位设为0,后面7位对应这个字符的Unicode码点(即编号)。因此英文中0~127字符与ASCII码完全相同。这意味着可以使用UTF-8编码格式打开用ASCII码编写的文档。

2.对于需要使用N个字节来表示的字符(N>1),第一个字节的前N位都设为1,第N+1位设为0,剩下的N-1个字节的前两位都设为10,剩下的二进制位则使用这个字符的Unicode的码点进行填充。

上面的表述可能比较难接受,下面看一下编码规则表就清晰很多,如表47.1.1.1所示:
QQ截图20230208095015.png
表47.1.1.1 UTF-8编码规则表

有了上面这个编码规则表,进行UTF-8编码和解码就简单多了。以汉字“汉”为例,它的Unicode的码点是0x6c49(110 1100 0100 1001),通过查表发现它属于第三行的规则,具体格式:1110xxxx 10xxxxxx 10xxxxxx。接着我们就可以从“汉”的二进制数的最后一位开始,从后向前依次填充对应的格式中的x,多出的x用0补充。这样就可以的到“汉”的UTF-8编码:1110011010110001 10001001,转换成十六进制就是0xE60xB1 0x89。

解码的过程也是十分简单,通过判断一个字节的第一位,假如是0,说明这个字节对应一个字符;假如是1则需要往后数有多少个连续不间断的1,就表示该字符占用多少个字节。

UTF-16
UTF-16采用的是2字节或4字节编码方式。对于Unicode编码范围在0x0000~0xFFFF之间字符,UTF-16使用两个字节存储,并直接存储Unicode编号,不用编码转换。对于Unicode编号范围在0x10000~0x10FFFF之间的字符,UTF-16使用四个字节存储,具体来说是:将字符编号的所有比特位分成两部分,较高的一些比特位用一个值介于0xD800和0xDBFF之间的双字节存储,较低的一些比特位(剩下的比特位),用一个值介于0xDC00和DFFF之间的双字节存储。

上面表述的可能有点模糊,请看下面的表格47.1.1.2所示:
QQ截图20230208095054.png
表47.1.1.2 UTF-16编码规则表

举个例子,用UTF-16两个字节去表示“汉”字,它的Unicode码点是0x6C49,那么遵行上面的表格规则,那么用UTF-16表示的话就是01101100 01001001(共16bit,两个字节)。

现在以第二种情况讲解,汉字“?”为例,它的Unicode码点为0x20BB7,该码点显然超出了基本平面的范围(0x0000 – 0xFFFF),因此需要使用四个字节进行表示,首先用0x00BB7–0x10000计算超出部分,然后将其用20个二进制位表示(不足的前面补0),结果为0001000010 1110110111。接着将高10位映射到U+D800到U+DBFF之间,低10位映射到U+DC00到U+DFFF即可。那么换算一下即(110110 0000000000 + 0001000010),高位转换一下16进制即0xD842,同理低位也是这样运算,得到0xDFB7。因此,得出汉字“?”的UTF-16编码为0xD842 0xDFB7。

UTF-32
UTF-32是固定长度的编码,始终占用4个字节,足以容纳所有的Unicode字符,所以直接存储Unicode编码即可,不需要任何编码转换。浪费了空间,提供了效率。

大小端模式
在了解了前面的各种解码之后,就知道一个字符可能占用多个字节,那么多个字节在计算机中如何进行存储呢?比如字符0xABCD,它的存储格式到底时AB CD,还是CD AB呢?

实际上两者都有可能,并且分别有不同的名字。如果存储为AB CD,则称为Big Endian即大端;如果存储为CD AB,则称为Little Endian即小端。

Big Endian:高字节在前,低字节在后,详细一点,就是将高位的字节放在低地址表示。

Little Endian:低字节在前,高字节在后,详细一点,就是将高位的字节放在高地址表示。

在了解大小端的意思之后,我们稍微科普一下BOM。

BOM
BOM是文档最前面的标记,位于文本文件的开头,一种标记对应一种编码方式。为什么会需要这个东西呢?因为计算机打开文档时,它首先需要获取一些信息,知道用什么编码方式去解码,才能知道文档写的是什么内容。注意:BOM是对Unicode的几种编码而言的。下面我们看一下BOM的标记表,如表47.1.1.3所示:
QQ截图20230208095206.png
表47.1.1.3 BOM标记表

47.1.2 字库的生成
有了编码,我们就能可以在计算机上对字符进行操作,但是如果计算机处理完字符后直接以编码的形式输出,我们很难一下子知道究竟是什么图形的字符。因为我们平常看到的都是字符的图形化显示,这里呢,就需要我们提供配套的字符图形。字符图形又称为字模,多个字模组成的文件就叫做字库。当我们为计算机提供了编码和字库的前提下,计算机就可以把字符编码转化成对应的字符图形,我们才能清晰识别。

字模的结构
在前面OLED显示实验章节也有说到字模相关知识,这里我们继续展开详细讲解一下,字模是字符的图形结构,字模的实质就是一个个像素点数据。为了方便处理,我们把字模定义成方块形状的像素点阵,像素点只有0和1两种状态。在单色图像数据中,像素点数值置1时,点亮了该像素点,若像素点数值置0时,熄灭该像素点。“汉字”字模图,如图47.1.2.1所示:     
image006.png
图47.1.2.1“汉字”字模图

这两个字模的大小都是16*16,计算机要表示这样的图形,只需要16x16个二进制数据位即可,需要使用16x16/8=32个字节保存字模数据。这里存在一个问题,字模数据是从哪里标记为第一个字节呢?我们知道了第一个字节以及字模的走向,我们就可以把生成的点阵数据和字模图像上的小方块进行匹配,更好了解两者的关系。下面我们解密一下,如图47.1.2.2取模方式(从上到下,从左到右)进行取模,这里的取模方式也是和前面的OLED显示实验章节一样的,可以往前面章节翻翻。
image008.png
图47.1.2.2取模方式

设置好后,生成的数据如下:
  1. {0x08,0x20,0x06,0x20,0x40,0x3E,0x30,0xC0,0x03,0x01,0x40,0x01,0x78,0x02,0x47,0x04,0x40,0xC8,0x40,0x30,0x40,0xC8,0x47,0x04,0x78,0x02,0x00,0x01, 0x00,0x01,0x00,0x00},/*"汉",0*/
复制代码
便于大家更好地将点阵数据和图形连接起来,请看图47.1.2.3所示:
image010.png
图47.1.2.3点阵数据和图形匹配图

了解了字模的结构之后,我们就需要去生成字库了。我们用到一款由易木雨软件工作室涉及的点阵字库生成器V3.8软件。该软件可以在WINDOWS系统下生成任意点阵大小的ASCII,GB2312(简体中文)、GBK(简体中文)、BIG5(繁体中文)和Unicode等共二十几种编码的字库,不但支持生成二进制文件格式的文件,也可以生成BDF文件,还支持生成图片功能,并支持横向,纵向等多种扫描方式,扫描方式可以根据用户的需求进行增加。该软件的界面如图 47.1.2.4 所示:
image012.png
图47.1.2.4 点阵字库生成器默认界面

本实验,我们总共要生成3个字库:12*12字库、16*16字库和24*24字库。这里以16*16字库为例进行介绍,其他两个字库的制作方法类似。要生成16*16的GBK字库,则需要选择字体大小、选择编码字库、选择字宽和高度、取模方式、存放路径等操作,最后点击创建,就可以开始生成我们需要的字库(.DZK文件),具体设置如图47.1.2.5所示:
image014.png
图47.1.2.5 生成GBK16*16字库的设置方法

注意:电脑端的字体大小与我们生成点阵大小的关系为:
fsize = dsize * 6 / 8

其中,fsize是电脑端字体的大小,dsize是点阵大小(12、16、24等)。所以16*16点阵大小对应的是12号字体。

生成完之后,我们把文件名和后缀改成:GBK16.FON。同样的方法,生成12*12的点阵库(GBK12.FON)和24*24的点阵库(GBK24.FON),总共制作3个字库。

构建字库完成,那我们怎么把字库跟编码连接起来呢?以GBK16.FON为例子,它是根据GBK的编码规则从0X8140开始,逐一建立。因为16*16的点阵大小为32字节,所以在FON文件中第一个32字节的数据就是“ 丂”的字模数据,它的编码是0x8140,而第二个32字节的数据就是“丄”的字模数据,它的编码就是0x8141。在前面讲解GBK的时候,也提到了GBK具有126区,每个区具有190个汉字,那么我们就可以通过计算地址偏移,最终在这个字库里面定位汉字了:

当GBKL<0X7F时:Hp=((GBKH-0x81)*190+GBKL-0X40)*csize;

当GBKL>0X80 时:Hp=((GBKH-0x81)*190+GBKL-0X41)*csize;

其中 GBKH、GBKL 分别代表 GBK 的第一个字节和第二个字节(也就是高位和低位),Hp 为对应汉字点阵数据在字库里面的起始地址(假设是从0开始存放),csize代表一个汉字点阵所占的字节数。

这样我们只要得到了汉字的GBK码,就可以得到该汉字点阵在点阵库里面的位置,从而获取其点阵数据,显示这个汉字了。

47.1.3 汉字显示原理
汉字在液晶上的显示原理与前面OLED实验中显示字符的是一样的。汉字在液晶上的显示其实就是一些点的显示与不显示,这就相当于我们的笔一样,有笔经过的地方就画出来,没经过的地方就不画。所以要显示汉字,我们首先要知道汉字的点阵数据,这些数据可以由专门的软件来生成,在前面字库生成已经有讲到如何生成字模与字库了。

在我们前面制作的三个GBK字库的基础上,将它们放在SD卡里,然后通过SD卡,将字库文件复制到外部FLASH芯片NORFLASH里,这样,NORFLASH就相当于一个汉字字库芯片了。

单片机要显示汉字的步骤:汉字内码(GBK/GB2312)→查找点阵库→解析→显示。所以只要我们有了整个汉字库的点阵,就可以把字符图形在单片机上显示出来了。

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

2. 硬件资源
1)RGB灯
     RED:LED0 - PB4
2)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
3)串口1 (PA9/PA10连接在板载USB转串口芯片CH340上面)
4)SD卡,通过SDMMC1(SDMMC_D0~D4(PC8~PC11),SDMMC_SCK(PC12),SDMMC_CMD(PD2))连接
5)norflash(QSPI FLASH芯片,连接在QSPI上)
6)独立按键
    KEY0 - PA1

47.3 程序设计
47.3.1 程序流程图
image017.png
图 47.3.1.1汉字显示实验程序流程图

47.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.     uint32_t f32addr;             /* gbk32地址 */
  16.     uint32_t gbk32size;          /* gbk32的大小 */
  17. } _font_info;
复制代码
这个结构体用于记录字库的首地址以及字库大小等信息,总共占用41个字节,第一个字节用来标识字库是否OK,其他的用来记录地址和文件大小。因为我们将NORFLASH的前800KB(占200个扇区)字节用作代码区,紧接着就是2004KB(占501个扇区)的内存空间用作SPB数据区,然后就是UNIGBK码表和字库数据区,存储位置是从NORFLASH芯片的第701个扇区开始,到第2239个扇区,总共6156KB字节,最后,NORFLASH芯片仅剩下7424KB的空间地址,用作文件系统区。

下面介绍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芯片中,701*4096地址开始往后的41个字节的数据读取出来,进而判断字库结构体ftinfo的字库标记fontok是否为AA,确定字库是否完好。
有人会有疑问,ftinfo.fontok在哪里赋值AA呢?肯定是字库更新完毕后,给该标记赋值的,那下面就来看一下是不是这样子,字库更新函数定义如下:
  1. /**
  2. *@brief       更新字体文件
  3. *  @note      所有字库一起更新(UNIGBK,GBK12,GBK16,GBK24,GBK32)
  4. *@param       x, y    : 提示信息的显示地址
  5. *@param       size    : 提示信息字体大小
  6. *@param       src     : 字库来源磁盘
  7. *  @arg                "0:", SD卡;
  8. *  @Arg                "1:", FLASH盘
  9. *  @arg                "2:", U盘
  10. *@param       color   : 字体颜色
  11. *@retval      0, 成功; 其他, 错误代码;
  12. */
  13. uint8_t fonts_update_font(uint16_t x, uint16_t y, uint8_t size,
  14. uint8_t *src, uint16_t color)
  15. {
  16.     uint8_t *pname;
  17.     uint32_t *buf;
  18.     uint8_t res = 0;
  19.     uint16_t i, j;
  20.     FIL*fftemp;
  21.     uint8_t rval = 0;
  22.     res= 0XFF;
  23.    ftinfo.fontok = 0XFF;
  24.    pname = mymalloc(SRAMIN, 100);  /* 申请100字节内存 */
  25.     buf= mymalloc(SRAMIN, 4096);   /* 申请4K字节内存 */
  26.    fftemp = (FIL *)mymalloc(SRAMIN, sizeof(FIL));  /* 分配内存 */
  27.     if (buf == NULL || pname == NULL || fftemp == NULL)
  28.     {
  29.        myfree(SRAMIN, fftemp);
  30.        myfree(SRAMIN, pname);
  31.        myfree(SRAMIN, buf);
  32.        return 5;   /* 内存申请失败 */
  33.     }
  34.     for (i = 0; i < 5; i++) /* 先查找文件UNIGBK,GBK12,GBK16,GBK24,GBK32是否正常 */
  35.     {
  36.        strcpy((char *)pname, (char *)src);             /* copy src内容到pname */
  37.        strcat((char *)pname, (char *)FONT_GBK_PATH);     /* 追加具体文件路径 */
  38.        res = f_open(fftemp, (const TCHAR *)pname, FA_READ);/* 尝试打开 */
  39.        if (res)
  40.        {
  41.            rval |= 1 << 7; /* 标记打开文件失败 */
  42.            break;            /* 出错了,直接退出 */
  43.        }
  44.     }
  45.    myfree(SRAMIN, fftemp);     /* 释放内存 */
  46.     if (rval == 0)                /* 字库文件都存在. */
  47. {
  48. /* 提示正在擦除扇区 */
  49.        lcd_show_string(x, y, 240, 320, size, "Erasingsectors... ", color);   
  50.        for (i = 0; i < FONTSECSIZE; i++)        /* 先擦除字库区域,提高写入速度 */
  51.        {
  52.            fonts_progress_show(x + 20 * size / 2, y, size, FONTSECSIZE, i,
  53. color);          /* 进度显示 */
  54.            norflash_ex_read((uint8_t *)buf, ((FONTINFOADDR / 4096) + i) * 4096,
  55. 4096);             /* 读出整个扇区的内容 */
  56.            for (j = 0; j < 1024; j++)            /* 校验数据 */
  57.            {
  58.                 if (buf[j] != 0XFFFFFFFF)break;   /* 需要擦除 */
  59.            }
  60.            if (j != 1024)
  61.            {
  62. /* 需要擦除的扇区 */
  63.                 norflash_ex_erase_sector((FONTINFOADDR / 4096) + i);
  64.            }
  65.        }
  66.        for (i = 0; i < 5; i++) /* 依次更新UNIGBK,GBK12,GBK16,GBK24,GBK32 */
  67.        {
  68.            lcd_show_string(x, y, 240, 320, size, FONT_UPDATE_REMIND_TBL,
  69. color);
  70.            strcpy((char *)pname, (char *)src);     /* copy src内容到pname */
  71.            strcat((char *)pname, (char *)FONT_GBK_PATH);/* 追加具体文件路径 */
  72.            res =fonts_update_fontx(x + 20 * size / 2, y, size, pname, i,
  73. color);    /* 更新字库 */
  74.            if (res)
  75.            {
  76.                 myfree(SRAMIN, buf);
  77.                 myfree(SRAMIN, pname);
  78.                 return 1 + i;
  79.            }
  80.        }
  81.        /* 全部更新好了 */
  82.        ftinfo.fontok = 0XAA;
  83. /* 保存字库信息 */
  84.        norflash_ex_write((uint8_t *)&ftinfo, FONTINFOADDR, sizeof(ftinfo));
  85.     }
  86.    myfree(SRAMIN, pname);  /* 释放内存 */
  87.    myfree(SRAMIN, buf);
  88.     return rval;              /* 无错误. */
  89. }
复制代码
函数的实现:动态申请内存→尝试打开文件(UNIGBK、GBK12、GBK16、GBK24和GBK32),确定文件是否存在→擦除字库→依次更新UNIGBK、GBK12、GBK16、GBK24和GBK32→写入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. *  @arg                 4, gbk32;
  12. *@param       color   : 字体颜色
  13. *@retval      0, 成功; 其他, 错误代码;
  14. */
  15. static uint8_t fonts_update_fontx(uint16_t x, uint16_t y, uint8_t size,
  16. uint8_t *fpath, uint8_t fx, uint16_t color)
  17. {
  18.     uint32_t flashaddr = 0;
  19.     FIL*fftemp;
  20.     uint8_t *tempbuf;
  21.     uint8_t res;
  22.     uint16_t bread;
  23.     uint32_t offx = 0;
  24.     uint8_t rval = 0;
  25.    fftemp = (FIL *)mymalloc(SRAMIN, sizeof(FIL));  /* 分配内存 */
  26.     if (fftemp == NULL)rval = 1;
  27.    tempbuf = mymalloc(SRAMIN, 4096);                 /* 分配4096个字节空间 */
  28.     if (tempbuf == NULL)rval = 1;
  29.     res= f_open(fftemp, (const TCHAR *)fpath, FA_READ);
  30.     if (res)rval = 2;   /* 打开文件失败 */
  31.     if (rval == 0)
  32.     {
  33.        switch (fx)
  34.        {
  35.            case 0: /* 更新 UNIGBK.BIN*/
  36. /* 信息头之后,紧跟UNIGBK转换码表 */
  37.                 ftinfo.ugbkaddr = FONTINFOADDR + sizeof(ftinfo);   
  38.                 ftinfo.ugbksize = fftemp->obj.objsize;    /* UNIGBK大小 */
  39.                 flashaddr = ftinfo.ugbkaddr;
  40.                 break;
  41.            case 1: /* 更新 GBK12.BIN*/
  42. /* UNIGBK之后,紧跟GBK12字库 */
  43.                 ftinfo.f12addr = ftinfo.ugbkaddr + ftinfo.ugbksize;
  44.                 ftinfo.gbk12size = fftemp->obj.objsize;  /* GBK12字库大小 */
  45.                 flashaddr = ftinfo.f12addr;                 /* GBK12的起始地址 */
  46.                 break;
  47.            case 2: /* 更新 GBK16.BIN*/
  48. /* GBK12之后,紧跟GBK16字库 */
  49.                 ftinfo.f16addr = ftinfo.f12addr + ftinfo.gbk12size;
  50.                 ftinfo.gbk16size = fftemp->obj.objsize;  /* GBK16字库大小 */
  51.                 flashaddr = ftinfo.f16addr;                /* GBK16的起始地址 */
  52.                 break;
  53.            case 3: /* 更新 GBK24.BIN*/
  54. /* GBK16之后,紧跟GBK24字库 */
  55.                 ftinfo.f24addr = ftinfo.f16addr + ftinfo.gbk16size;
  56.                 ftinfo.gbk24size = fftemp->obj.objsize; /* GBK24字库大小 */
  57.                 flashaddr = ftinfo.f24addr;               /* GBK24的起始地址 */
  58.                 break;
  59.            case 4: /* 更新 GBK32.BIN*/
  60. /* GBK24之后,紧跟GBK32字库 */
  61.                 ftinfo.f32addr = ftinfo.f24addr + ftinfo.gbk24size;
  62.                 ftinfo.gbk32size = fftemp->obj.objsize; /* GBK32字库大小 */
  63.                 flashaddr = ftinfo.f32addr;               /* GBK32的起始地址 */
  64.                 break;
  65.        }
  66.        while (res == FR_OK)   /* 死循环执行 */
  67.        {
  68.            res = f_read(fftemp, tempbuf, 4096, (UINT *)&bread);    /* 读取数据 */
  69.            if (res != FR_OK)break;     /* 执行错误 */
  70.             /* 从0开始写入bread个数据 */
  71.            norflash_ex_write(tempbuf, offx + flashaddr, bread);
  72.            offx += bread;
  73. /* 进度显示 */
  74.            fonts_progress_show(x, y, size, fftemp->obj.objsize, offx, color);
  75.            if (bread != 4096)break;    /* 读完了. */
  76.        }
  77.        f_close(fftemp);
  78.     }
  79.    myfree(SRAMIN, fftemp);     /* 释放内存 */
  80.    myfree(SRAMIN, tempbuf);    /* 释放内存 */
  81.     return res;
  82. }
复制代码
单个字库更新函数,主要是对把字库从SD卡中读取出数据,写入NORFLASH。同时把字库大小和起始地址保存在ftinfo结构体里,在前面的整个字库更新函数中使用函数:
  1. norflash_write((uint8_t *)&ftinfo,FONTINFOADDR,sizeof(ftinfo)); /*保存字库信息*/
复制代码
结构体的所有成员一并写入到那41个字节。有了这个字库信息结构体,就能很容易进行定位。结合前面的说到的根据地址偏移寻找汉字的点阵数据,我们就可以开始真正把汉字搬上屏幕中去了。

首先我们肯定需要获得汉字的GBK码,这里MDK已经帮我们实现了。这里用一个例子说明:
image018.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_ex_read(mat, foffset + ftinfo.f12addr, csize);
  42.            break;
  43.        case 16:
  44.            norflash_ex_read(mat, foffset + ftinfo.f16addr, csize);
  45.            break;
  46.        case 24:
  47.            norflash_ex_read(mat, foffset + ftinfo.f24addr, csize);
  48.            break;
  49.        case 32:
  50.            norflash_ex_read(mat, foffset + ftinfo.f32addr, csize);
  51.            break;
  52.     }
  53. }
复制代码
函数实现的依据就是前面47.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.    sys_cache_enable();                    /* 打开L1-Cache */
  8.    HAL_Init();                              /* 初始化HAL库 */
  9.    sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
  10.    delay_init(480);                        /* 延时初始化 */
  11.    usart_init(115200);                    /* 串口初始化为115200 */
  12.    usmart_dev.init(240);                  /* 初始化USMART */
  13.    mpu_memory_protection();              /* 保护相关存储区域 */
  14.    led_init();                             /* 初始化LED */
  15.    lcd_init();                             /* 初始化LCD */
  16.    key_init();                              /* 初始化按键 */
  17.    my_mem_init(SRAMIN);                   /* 初始化内部内存池(AXI) */
  18.    my_mem_init(SRAM12);                   /* 初始化SRAM12内存池(SRAM1+SRAM2)*/
  19.    my_mem_init(SRAM4);                    /* 初始化SRAM4内存池(SRAM4) */
  20.    my_mem_init(SRAMDTCM);                /* 初始化DTCM内存池(DTCM) */
  21.    my_mem_init(SRAMITCM);                /* 初始化ITCM内存池(ITCM) */
  22.    exfuns_init();                          /* 为fatfs相关变量申请内存 */
  23.    f_mount(fs[0], "0:", 1);              /* 挂载SD卡 */
  24.    f_mount(fs[1], "1:", 1);              /* 挂载FLASH */
  25.     while (fonts_init())                  /* 检查字库 */
  26.     {
  27. UPD:
  28.        lcd_clear(WHITE);                  /* 清屏 */
  29.        lcd_show_string(30, 30, 200, 16, 16, "STM32", RED);
  30.        while (sd_init())                  /* 检测SD卡 */
  31.        {
  32.            lcd_show_string(30, 50, 200, 16, 16, "SD CardFailed!", RED);
  33.            delay_ms(200);
  34.            lcd_fill(30, 50, 200 + 30, 50 + 16, WHITE);
  35.            delay_ms(200);
  36.        }
  37.        lcd_show_string(30, 50, 200, 16, 16, "SD CardOK", RED);
  38.        lcd_show_string(30, 70, 200, 16, 16, "FontUpdating...", RED);
  39.        key =fonts_update_font(20, 90, 16, (uint8_t *)"0:", RED);  /* 更新字库 */
  40.        while (key)   /* 更新失败 */
  41.        {
  42.            lcd_show_string(30, 90, 200, 16, 16, "Font UpdateFailed!", RED);
  43.            delay_ms(200);
  44.            lcd_fill(20, 90, 200 + 20, 90 + 16, WHITE);
  45.            delay_ms(200);
  46.        }
  47.        lcd_show_string(30, 90, 200, 16, 16, "Font UpdateSuccess!   ", RED);
  48.        delay_ms(1500);
  49.        lcd_clear(WHITE);/* 清屏 */
  50.     }
  51.    text_show_string(30, 30, 200, 16, "正点原子STM32开发板", 16, 0, RED);
  52.    text_show_string(30, 50, 200, 16, "GBK字库测试程序", 16, 0, RED);
  53.    text_show_string(30, 70, 200, 16, " ATOM@ALIENTEK", 16, 0, RED);
  54.    text_show_string(30, 90, 200, 16, "按KEY0,更新字库", 16, 0, RED);
  55.    
  56.    text_show_string(30, 130, 200, 16, "内码高字节:", 16, 0, BLUE);
  57.    text_show_string(30, 150, 200, 16, "内码低字节:", 16, 0, BLUE);
  58.    text_show_string(30, 170, 200, 16, "汉字计数器:", 16, 0, BLUE);
  59.    
  60.    text_show_string(30, 200, 200, 32, "对应汉字为:", 32, 0, BLUE);
  61.    text_show_string(30, 232, 200, 24, "对应汉字为:", 24, 0, BLUE);
  62.    text_show_string(30, 256, 200, 16, "对应汉字(16*16)为:", 16, 0, BLUE);
  63.    text_show_string(30, 272, 200, 12, "对应汉字(12*12)为:", 12, 0, BLUE);
  64.     while (1)
  65.     {
  66.        fontcnt = 0;
  67.        for (i = 0x81; i < 0xff; i++)       /* GBK内码高字节范围为0X81~0XFE */
  68.        {
  69.            fontx[0] = i;
  70.            lcd_show_num(118, 130, i, 3, 16, BLUE);  /* 显示内码高字节 */
  71. /* GBK内码低字节范围为 0X40~0X7E, 0X80~0XFE) */
  72.            for (j = 0x40; j < 0xfe; j++)
  73.            {
  74.                 if (j == 0x7f)continue;
  75.                 fontcnt++;
  76.                 lcd_show_num(118, 150, j, 3, 16, BLUE);         /* 显示内码低字节 */
  77.                 lcd_show_num(118, 170, fontcnt, 5, 16, BLUE);  /* 汉字计数显示 */
  78.                 fontx[1] = j;
  79.                 text_show_font(30 + 176, 200, fontx, 32, 0, BLUE);
  80.                 text_show_font(30 + 132, 232, fontx, 24, 0, BLUE);
  81.                 text_show_font(30 + 144, 256, fontx, 16, 0, BLUE);
  82.                 text_show_font(30 + 108, 272, fontx, 12, 0, BLUE);
  83.                 t = 200;
  84.                 while (t--)   /* 延时,同时扫描按键 */
  85.                 {
  86.                     delay_ms(1);
  87.                     key = key_scan(0);
  88.                     if (key == KEY0_PRES)
  89.                     {
  90.                         goto UPD;   /* 跳转到UPD位置(强制更新字库) */
  91.                     }
  92.                 }
  93.                 LED0_TOGGLE();
  94.            }
  95.        }
  96.     }
  97. }
复制代码
main函数实现了我们在硬件设计例程功能所表述的一致,至此整个软件设计就完成了。

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

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

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

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

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

GMT+8, 2025-2-24 20:34

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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