原创 【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第四十六章 汉字显示实验

2013-4-6 23:19 2715 19 20 分类: MCU/ 嵌入式 文集: STM32学习

 

第四十六章 汉字显示实验

汉字显示在很多单片机系统都需要用到,少则几个字,多则整个汉字库的支持,更有甚者还要支持多国字库,那就更麻烦了。本章,我们将向大家介绍,如何用STM32控制LCD显示汉字。在本章中,我们将使用外部FLASH来存储字库,并可以通过SD卡更新字库。STM32读取存在FLASH里面的字库,然后将汉字显示在LCD上面。本章分为如下几个部分:

46.1 汉字显示原理简介

46.2 硬件设计

46.3 软件设计

46.4 下载验证

46.1 汉字显示原理简介

常用的汉字内码系统有GB2312GB13000GBKBIG5(繁体)等几种,其中GB2312支持的汉字仅有几千个,很多时候不够用,而GBK内码不仅完全兼容GB2312,还支持了繁体字,总汉字数有2万多个,完全能满足我们一般应用的要求。

本实例我们将制作一个GBK字库,制作好的字库放在SD卡里面,然后通过SD卡,将字库文件复制到外部FLASH芯片W25Q64里,这样,W25Q64就相当于一个汉字字库芯片了。

汉字在液晶上的显示原理与前面显示字符的是一样的。汉字在液晶上的显示其实就是一些点的显示与不显示,这就相当于我们的笔一样,有笔经过的地方就画出来,没经过的地方就不画。所以要显示汉字,我们首先要知道汉字的点阵数据,这些数据可以由专门的软件来生成。只要知道了一个汉字点阵的生成方法,那么我们在程序里面就可以把这个点阵数据解析成一个汉字。

知道显示了一个汉字,就可以推及整个汉字库了。汉字在各种文件里面的存储不是以点阵数据的形式存储的(否则那占用的空间就太大了),而是以内码的形式存储的,就是GB2312/GBK/BIG5等这几种的一种,每个汉字对应着一个内码,在知道了内码之后再去字库里面查找这个汉字的点阵数据,然后在液晶上显示出来。这个过程我们是看不到,但是计算机是要去执行的。

单片机要显示汉字也与此类似:汉字内码(GBK/GB2312à查找点阵库à解析à显示。

所以只要我们有了整个汉字库的点阵,就可以把电脑上的文本信息在单片机上显示出来了。这里我们要解决的最大问题就是制作一个与汉字内码对的上号的汉字点阵库。而且要方便单片机的查找。每个GBK码由2个字节组成,第一个字节为0X81~0XFE,第二个字节分为两部分,一是0X40~0X7E,二是0X80~0XFE。其中与GB2312相同的区域,字完全相同。

我们把第一个字节代表的意义称为区,那么GBK里面总共有126个区(0XFE-0X81+1),每个区内有190个汉字(0XFE-0X80+0X7E-0X40+2),总共就有126*190=23940个汉字。我们的点阵库只要按照这个编码规则从0X8140开始,逐一建立,每个区的点阵大小为每个汉字所用的字节数*190。这样,我们就可以得到在这个字库里面定位汉字的方法:

        GBKL<0X7F时:Hp=((GBKH-0x81)*190+GBKL-0X40)*(size*2)

        GBKL>0X80时:Hp=((GBKH-0x81)*190+GBKL-0X41)*(size*2)

其中GBKHGBKL分别代表GBK的第一个字节和第二个字节(也就是高位和低位)size代表汉字字体的大小(比如16字体,12字体等),Hp则为对应汉字点阵数据在字库里面的起始地址(假设是从0开始存放)

这样我们只要得到了汉字的GBK码,就可以显示这个汉字了。从而实现汉字在液晶上的显示。

上一章,我们提到要用cc936.c,以支持长文件名,但是cc936.c文件里面的两个数组太大了(172KB),直接刷在单片机里面,太占用flash了,所以我们必须把这两个数组存放在外部flashcc936里面包含的两个数组oem2uniuni2oem存放unicodegbk的互相转换对照表,这两个数组很大,这里我们利用ALIENTEK 提供的一个C语言数组转BIN(二进制)的软件:C2B转换助手V1.1.exe,将这两个数组转为BIN文件,我们将这两个数组拷贝出来存放为一个新的文本文件,假设为UNIGBK.TXT,然后用C2B转换助手打开这个文本文件,如图46.1.1所示:

 


46.1.1 C2B转换助手

然后点击转换,就可以在当前目录下(文本文件所在目录下)得到一个UNIGBK.bin的文件。这样就完成将C语言数组转换为.bin文件,然后只需要将UNIGBK.bin保存到外部FLASH就实现了该数组的转移。

cc936.c里面,主要是通过ff_convert调用这两个数组,实现UNICODEGBK的互转,该函数原代码如下:

WCHAR ff_convert (    /* Converted code, 0 means conversion error */

       WCHAR src,      /* Character code to be converted */

       UINT      dir          /* 0: Unicode to OEMCP, 1: OEMCP to Unicode */

)

{

       const WCHAR *p;

       WCHAR c;

       int i, n, li, hi;

       if (src < 0x80) {     /* ASCII */

              c = src;

       } else {

              if (dir) {         /* OEMCP to unicode */

                     p = oem2uni;

                     hi = sizeof(oem2uni) / 4 - 1;

              } else {          /* Unicode to OEMCP */

                     p = uni2oem;

                     hi = sizeof(uni2oem) / 4 - 1;

              }

              li = 0;

              for (n = 16; n; n--) {

                     i = li + (hi - li) / 2;

                     if (src == p[i * 2]) break;

                     if (src > p[i * 2]) li = i;

                     else hi = i;    

              }

              c = n ? p[i * 2 + 1] : 0;

       }

       return c;

}

此段代码,通过二分法(16阶)在数组里面查找UNICODE(或GBK)码对应的GBK(或UNICODE)码。当我们将数组存放在外部flash的时候,将该函数修改为:

WCHAR ff_convert (    /* Converted code, 0 means conversion error */

       WCHAR src,         /* Character code to be converted */

       UINT      dir          /* 0: Unicode to OEMCP, 1: OEMCP to Unicode */

)

{

       WCHAR t[2];

       WCHAR c;

       u32 i, li, hi;

       u16 n;                  

       u32 gbk2uni_offset=0;         

       if (src < 0x80)c = src;//ASCII,直接不用转换.

       else

       {

             if(dir) gbk2uni_offset=ftinfo.ugbksize/2;     //GBK 2 UNICODE

              else gbk2uni_offset=0;                               //UNICODE 2 GBK 

              /* Unicode to OEMCP */

              hi=ftinfo.ugbksize/2;//对半开.

              hi =hi / 4 - 1;

              li = 0;

              for (n = 16; n; n--)

              {

                     i = li + (hi - li) / 2;

                     SPI_Flash_Read((u8*)&t,ftinfo.ugbkaddr+i*4+gbk2uni_offset,4);//读出4个字节 

                     if (src == t[0]) break;

                     if (src > t[0])li = i; 

                     else hi = i;   

              }

              c = n ? t[1] : 0;         

       }

       return c;

}

代码中的ftinfo.ugbksize为我们刚刚生成的UNIGBK.bin的大小,而ftinfo.ugbkaddr是我们存放UNIGBK.bin文件的首地址。这里同样采用的是二分法查找,关于cc936.c的修改,我们就介绍到这。

非常抱歉,由于编辑器篇幅所限,剩下内容,请看附件

文章评论1条评论)

登录后参与讨论

用户1308405 2014-3-21 08:27

原子兄的都是精品!!!
相关推荐阅读
正点原子 2013-05-17 23:47
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第六十一章 战舰STM32开发板综合实验(标准例程终结篇)
   第六十一章 战舰STM32开发板综合实验        前面已经给大家讲了55个实例了,本章将设计一个综合实例,作为本指南的最后一个实验 ,该实验向大家展示了STM...
正点原子 2013-05-03 23:02
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第六十章 UCOSII实验3-消息队列、信号量集和软件定时器
   第六十章 UCOSII实验3-消息队列、信号量集和软件定时器   上一章,我们学习了UCOSII的信号量和邮箱的使用,本章,我们将学习消息队列、信号量集和软件定时器...
正点原子 2013-05-03 20:42
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第五十七章 ENC28J60网络实验
第五十七章 ENC28J60网络实验   本章,我们将向大家介绍ALIENTEK ENC28J60网络模块及其使用。本章,我们将使用ALIENTEK ENC28J60网络模块...
正点原子 2013-05-01 23:00
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第五十九章 UCOSII实验2-信号量和邮箱
第五十九章 UCOSII实验2-信号量和邮箱      上一章,我们学习了如何使用UCOSII,学习了UCOSII的任务调度,但是并没有用到任务间的同步与通信,本章我们将学习两个最基本的...
正点原子 2013-04-30 10:55
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第五十八章 UCOSII实验1-任务调度
  第五十八章 UCOSII实验1-任务调度      前面我们所有的例程都是跑的裸机程序(裸奔),从本章开始,我们将分3个章节向大家介绍UCOSII(实时多任务操作系...
正点原子 2013-04-26 23:16
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第五十七章 ENC28J60网络实验
 第五十七章 ENC28J60网络实验  本章,我们将向大家介绍ALIENTEK ENC28J60网络模块及其使用。本章,我们将使用ALIENTEK ENC28J60网络模块和uIP 1...
我要评论
1
19
关闭 站长推荐上一条 /2 下一条