1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-336836-1-1.html 4)对正点原子STM32感兴趣的同学可以加群讨论:879133275
第四十七章 汉字显示实验本章,我们将介绍如何使用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码表分为两个部分,第一部分数字编码范围031是控制字符或者是通讯专用字符,是不具备图形显示但对文本显示有影响的编码;第二部分数字编码范围32127是包含我们日常用到的符号信息,空格、阿拉伯数字、标点符号、大小写英文以及“DEL(删除控制)”,除了DEL不具备图形显示外,其他都具备。工程代码中也有ASCII的编码表。下面看一下ASCII编码表第二部分中的可显示字符如图47.1.1.1所示:
图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,转换过程为: 区位码 区码转换 位码转换 储存码 1001H 10H+A0H=B0H 01H+A0H=A1H B0A1H GBK编码 GBK编码即汉字内码扩展规范,完全兼容GB2312,在GB2312的基础上,支持繁体字、人名、古汉语等方面出现的罕用字。GBK采用的是双字节表示,总体编码范围为0x81400xFEFE,第一个字节在0x810xFE之间,第二个字节分为两个部分,一是0x400x7E,二是0x800xFE。其中和GB2312相同的区域,字完全相同,可表示的汉字数达到了2万多个,完全能满足我们的一般应用的要求。如图47.1.1.2 GBK码位分布图:
图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所示:
表47.1.1.1 UTF-8编码规则表 有了上面这个编码规则表,进行UTF-8编码和解码就简单多了。以汉字“汉”为例,它的Unicode的码点是0x6c49(110 1100 0100 1001),通过查表发现它属于第三行的规则,具体格式:1110xxxx 10xxxxxx 10xxxxxx。接着我们就可以从“汉”的二进制数的最后一位开始,从后向前依次填充对应的格式中的x,多出的x用0补充。这样就可以的到“汉”的UTF-8编码:11100110 10110001 10001001,转换成十六进制就是0xE6 0xB1 0x89。 解码的过程也是十分简单,通过判断一个字节的第一位,假如是0,说明这个字节对应一个字符;假如是1则需要往后数有多少个连续不间断的1,就表示该字符占用多少个字节。 UTF-16 UTF-16采用的是2字节或4字节编码方式。对于Unicode编码范围在0x00000xFFFF之间字符,UTF-16使用两个字节存储,并直接存储Unicode编号,不用编码转换。对于Unicode编号范围在0x100000x10FFFF之间的字符,UTF-16使用四个字节存储,具体来说是:将字符编号的所有比特位分成两部分,较高的一些比特位用一个值介于0xD800和0xDBFF之间的双字节存储,较低的一些比特位(剩下的比特位),用一个值介于0xDC00和DFFF之间的双字节存储。 上面表述的可能有点模糊,请看下面的表格47.1.1.2所示:
表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所示:
表47.1.1.3 BOM标记表 47.1.2 字库的生成 有了编码,我们就能可以在计算机上对字符进行操作,但是如果计算机处理完字符后直接以编码的形式输出,我们很难一下子知道究竟是什么图形的字符。因为我们平常看到的都是字符的图形化显示,这里呢,就需要我们提供配套的字符图形。字符图形又称为字模,多个字模组成的文件就叫做字库。当我们为计算机提供了编码和字库的前提下,计算机就可以把字符编码转化成对应的字符图形,我们才能清晰识别。 字模的结构 在前面OLED显示实验章节也有说到字模相关知识,这里我们继续展开详细讲解一下,字模是字符的图形结构,字模的实质就是一个个像素点数据。为了方便处理,我们把字模定义成方块形状的像素点阵,像素点只有0和1两种状态。在单色图像数据中,像素点数值置1时,点亮了该像素点,若像素点数值置0时,熄灭该像素点。“汉字”字模图,如图47.1.2.1所示:
图47.1.2.1 “汉字”字模图 这两个字模的大小都是16*16,计算机要表示这样的图形,只需要16x16个二进制数据位即可,需要使用16x16/8=32个字节保存字模数据。这里存在一个问题,字模数据是从哪里标记为第一个字节呢?我们知道了第一个字节以及字模的走向,我们就可以把生成的点阵数据和字模图像上的小方块进行匹配,更好了解两者的关系。下面我们解密一下,如图47.1.2.2取模方式(从上到下,从左到右)进行取模,这里的取模方式也是和前面的OLED显示实验章节一样的,可以往前面章节翻翻。
图47.1.2.2 取模方式 设置好后,生成的数据如下: {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所示:
图47.1.2.3 点阵数据和图形匹配图 了解了字模的结构之后,我们就需要去生成字库了。我们用到一款由易木雨软件工作室涉及的点阵字库生成器V3.8软件。该软件可以在WINDOWS系统下生成任意点阵大小的ASCII,GB2312(简体中文)、GBK(简体中文)、BIG5(繁体中文)和Unicode等共二十几种编码的字库,不但支持生成二进制文件格式的文件,也可以生成BDF文件,还支持生成图片功能,并支持横向,纵向等多种扫描方式,扫描方式可以根据用户的需求进行增加。该软件的界面如图 47.1.2.4 所示:
图47.1.2.4 点阵字库生成器默认界面 本实验,我们总共要生成3个字库:1212字库、1616字库和2424字库。这里以1616字库为例进行介绍,其他两个字库的制作方法类似。要生成16*16的GBK字库,则需要选择字体大小、选择编码字库、选择字宽和高度、取模方式、存放路径等操作,最后点击创建,就可以开始生成我们需要的字库(.DZK文件),具体设置如图47.1.2.5所示:
图47.1.2.5 生成GBK1616字库的设置方法 注意:电脑端的字体大小与我们生成点阵大小的关系为: fsize = dsize * 6 / 8 其中,fsize是电脑端字体的大小,dsize是点阵大小(12、16、24等)。所以1616点阵大小对应的是12号字体。 生成完之后,我们把文件名和后缀改成:GBK16.FON。同样的方法,生成1212的点阵库(GBK12.FON)和2424的点阵库(GBK24.FON),总共制作3个字库。 构建字库完成,那我们怎么把字库跟编码连接起来呢?以GBK16.FON为例子,它是根据GBK的编码规则从0X8140开始,逐一建立。因为16*16的点阵大小为32字节,所以在FON文件中第一个32字节的数据就是“ 丂”的字模数据,它的编码是0x8140,而第二个32字节的数据就是“丄”的字模数据,它的编码就是0x8141。在前面讲解GBK的时候,也提到了GBK具有126区,每个区具有190个汉字,那么我们就可以通过计算地址偏移,最终在这个字库里面定位汉字了: 当GBKL0X80 时: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 硬件设计
- 例程功能 本实验开机的时候先检测norflash中是否已经存在字库,如果存在,则按次序显示汉字(四种字体都显示)。如果没有,则检测SD卡和文件系统,并查找SYSTEM文件夹下的FONT文件夹,在该文件夹内查找UNIGBK.BIN、GBK12.FON、GBK16.FON、GBK24.FON和GBK32.FON(这几个文件的由来,见STM32H750开发指南)。在检测到这些文件之后,就开始更新字库,更新完毕才开始显示汉字。通过按按键KEY0,可以强制更新字库。 LED0闪烁,提示程序运行。
- 硬件资源 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_D0D4(PC8PC11),SDMMC_SCK(PC12), SDMMC_CMD(PD2))连接 5)norflash(QSPI FLASH芯片,连接在QSPI上) 6)独立按键 KEY0 - PA1 47.3 程序设计 47.3.1 程序流程图
图 47.3.1.1汉字显示实验程序流程图 47.3.2 程序解析
- TEXT代码 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。TEXT驱动源码包括四个文件:text.c、text.h、fonts.c和fonts.h。 汉字显示实验代码主要分为两部分:一部分就是对字库的更新,另一部分就是对汉字的显示。字库的更新代码放在font.c和font.h文件中,汉字的显示代码就放在text.c和text.h中。 下面我们介绍一下有关字库操作的代码,首先我们先看一下fonts.h文件中字库信息结构体定义,其代码如下:
/* 字库信息结构体定义
* 用来保存字库基本信息,地址,大小等
*/
__packed typedef struct
{
uint8_t fontok; /* 字库存在标志,0XAA,字库正常;其他,字库不存在 */
uint32_t ugbkaddr; /* unigbk的地址 */
uint32_t ugbksize; /* unigbk的大小 */
uint32_t f12addr; /* gbk12地址 */
uint32_t gbk12size; /* gbk12的大小 */
uint32_t f16addr; /* gbk16地址 */
uint32_t gbk16size; /* gbk16的大小 */
uint32_t f24addr; /* gbk24地址 */
uint32_t gbk24size; /* gbk24的大小 */
uint32_t f32addr; /* gbk32地址 */
uint32_t gbk32size; /* gbk32的大小 */
} _font_info;
这个结构体用于记录字库的首地址以及字库大小等信息,总共占用41个字节,第一个字节用来标识字库是否OK,其他的用来记录地址和文件大小。因为我们将NORFLASH的前800KB(占200个扇区)字节用作代码区,紧接着就是2004KB(占501个扇区)的内存空间用作SPB数据区,然后就是UNIGBK码表和字库数据区,存储位置是从NORFLASH芯片的第701个扇区开始,到第2239个扇区,总共6156KB字节,最后,NORFLASH芯片仅剩下7424KB的空间地址,用作文件系统区。 下面介绍font.c文件几个重要的函数。 字库初始化函数也是利用其存储顺序,进行检查字库,其定义如下:
/**
* @brief 初始化字体
* @param 无
* @retval 0, 字库完好; 其他, 字库丢失;
*/
uint8_t fonts_init(void)
{
uint8_t t = 0;
while (t
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?