×
单片机 > 单片机程序设计 > 详情

24. 汉字显示实验

发布时间:2020-06-05 发布时间:
|

一。汉字显示原理

1. 常用汉字编码


这里我们采用GBK编码

2. 汉字显示原理


假如显示一个16*8的汉字,高16,宽8,则高需要2个字节,显示这个汉字需要2*8=16个字节,这16个字节的数据就是汉字的点阵数据。

3. 字符/汉字显示方法


利用软件取模

取模方向设置:

扫描方向:纵向为先显示第一个字节,然后显示第二个字节,从低位到高位,然后换到第二列。

数据前缀改为“0x”,数据后缀改为“,”,行后缀为“;”。

对于汉字显示采用16*16。

例如:开,为16*16,得到下面的字模数据。


得到字模数据后,程序中就可以从中取得相应的字节进行显示。

4. 汉字显示的过程

如何显示字库中所有的汉字?利用专用的软件实现。


根据内码,查找相应的字库点阵,然后解析,显示。

对于汉字的内码常用GBK码。每一个汉字都对应一个GBK的编码。

认识GBK码:


GBK码共有126个区,每个区里有190个汉字。

如何根据GBK编码找到汉字:

1.首先找到GBK编码的高位,得到这个汉字在哪个区。(高位-0x81)确定在哪个区。

(GBKH-0x81)*190表示这个汉字所在区的前面几个区已经有这么多个汉字。

当GBKL<0x7F时:汉字所在区的偏移为GBKL-0x40。

当GBKL>0x7F时:汉字所在区的偏移为GBKL-0x41,因为前面空掉一个0x7F。

因为每个汉字占用的字节数为size*2,比如16*16的汉字,那么每个汉字占用的空间是16*2=32个字节。

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

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

根据GBK编码得到这个汉字所在汉字库的起始地址。

从以上的方法找到了汉字所在的位置,下面开始制作点阵字库。

采用字库制作软件制作汉字库。



制作点阵字库

生成字库软件设置:

注意字体大小选12,这个是电脑端字体的大小。


注意电脑端的字体大小与生成点阵的字体大小有区别

模式选择纵向取模方式二。

生成点阵字库后,要用文件系统读取字库,然后存到SPI-Flash中。


在SPI FLASH中的某个地方写个标志0xAA,表示已经更新过字库。


Show_Str函数既能显示字符,也能显示汉字,如果是显示汉字就调用Show_Font函数。

Get_HzMat函数根据编码取得字库中汉字的字模。然后根据字模数据解析,显示。

二。实验讲解

生成的字库文件名为:16.DZK,字库大小为766,080字节,然后把名字改成:GBK16.FON。

然后把这个文件存进SD卡,程序会从SD卡中把这个文件写入SPI FLASH。

fontupd.c  更新字库

text.c  显示汉字

1. 程序主函数中开始的时候会检查字库,调用fontinit()函数。

//初始化字体

//返回值:0,字库完好.

//其他,字库丢失

u8 font_init(void)

{    

SPI_Flash_Init();    //初始化25Q**系列。

FONTINFOADDR=(1024*6+500)*1024; //W25Q64,6M以后  

ftinfo.ugbkaddr=FONTINFOADDR+25; //UNICODEGBK 表存放首地址固定地址

SPI_Flash_Read((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo));//读出ftinfo结构体数据

if(ftinfo.fontok!=0XAA)return 1; //字库错误. 

return 0;    

}

字体信息保存地址,占25个字节,第1个字节用于标记字库是否存在.后续每8个字节一组,分别保存起始地址和文件大小。

//字库信息结构体定义

//用来保存字库基本信息,地址,大小等

__packed typedef struct 

{

u8 fontok; //字库存在标志,0XAA,字库正常;其他,字库不存在

u32 ugbkaddr; //unigbk的地址

u32 ugbksize; //unigbk的大小  

u32 f12addr; //gbk12地址

u32 gbk12size; //gbk12的大小  

u32 f16addr; //gbk16地址

u32 gkb16size; //gbk16的大小  

}_font_info;

__packed是字节对齐的意思。

比如说int float double char它的总大小是4 + 4 + 8 + 1 = 17
但如果不用__packed的话,系统将以默认的方式对齐(假设是4字节),那么它占4 + 4 + 8 + 4 = 20;(不足4字节以4字节补齐)。

 

ftinfo 是我们在 fontupd.h 里面定义的一个结构体,用于记录字库首地址及字库大小等信息。

因为我们将W25Q64 的前 6M 字节给 FATFS 管理(用做本地磁盘),然后又预留了 500K 字节给用户自己使用,最后的 1.5M 字节(W25Q64 总共 8M 字节),才是 UNIGBK 码表和字库的存储空间。

finfo结构体的存放地址为:

FONTINFOADDR=(1024*6+500)*1024;

GBK字库存放的首地址为:

ftinfo.ugbkaddr=FONTINFOADDR+25;

2. 主函数如果没有检测到字库

a. 初始化SD卡

while(SD_Initialize())   //检测SD卡

没检测到SD卡报错

LCD_ShowString(60,70,200,16,16,"SD Card Failed!");

b. 调用 update_font(20,110,16,0); 从SD卡更新字库

自己做的字库要放在SD卡SYSTEM/FONT目录下

文件名为GBK16.FON或GBK24.FON

//更新字体文件,UNIGBK,GBK12,GBK16一起更新

//x,y:提示信息的显示地址

//size:字体大小

//提示信息字体大小

//src: 0,  从SD卡更新.

//src: 1,  从25QXX更新  

//返回值:0,更新成功;

//其他,错误代码.  

u8 update_font(u16 x,u16 y,u8 size,u8 src)

{

u8 *gbk16_path;

u8 *gbk12_path;

u8 *unigbk_path;

u8 res;  

if(src)//从25qxx更新

{

unigbk_path=(u8*)UNIGBK_25QPATH;

gbk12_path=(u8*)GBK12_25QPATH;

gbk16_path=(u8*)GBK16_25QPATH;

}else//从sd卡更新

{

unigbk_path=(u8*)UNIGBK_SDPATH;

gbk12_path=(u8*)GBK12_SDPATH;

gbk16_path=(u8*)GBK16_SDPATH;

}   

  res=0XFF;

ftinfo.fontok=0XFF;

  SPI_Flash_Write((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo)); //清除之前字库成功的标志.防止更新到一半重启,导致的字库部分数据丢失.

  SPI_Flash_Read((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo)); //重新读出ftinfo结构体数据

  LCD_ShowString(x,y,240,320,size,"Updating UNIGBK.BIN");

res=updata_fontx(x+20*size/2,y,size,unigbk_path,0); //更新UNIGBK.BIN

if(res)return 1;

  LCD_ShowString(x,y,240,320,size,"Updating GBK12.BIN  ");

res=updata_fontx(x+20*size/2,y,size,gbk12_path,1); //更新GBK12.FON

if(res)return 2;

LCD_ShowString(x,y,240,320,size,"Updating GBK16.BIN  ");

res=updata_fontx(x+20*size/2,y,size,gbk16_path,2); //更新GBK16.FON

if(res)return 3;   

//全部更新好了,写入标志位0xAA。

ftinfo.fontok=0XAA;

  SPI_Flash_Write((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo)); //保存字库信息

return 0;//无错误.  

调用 updata_fontx(u16 x,u16 y,u8 size,u8 *fxpath,u8 fx);

//更新某一个

//x,y:坐标

//size:字体大小

//fxpath:路径

//fx:更新的内容 0,ungbk;1,gbk12;2,gbk16;

//返回值:0,成功;其他,失败.

u8 updata_fontx(u16 x,u16 y,u8 size,u8 *fxpath,u8 fx)

{

u32 flashaddr=0;    

FIL * fftemp;

u8 *tempbuf;

  u8 res;

u16 bread;

u32 offx=0;

u8 rval=0;     

fftemp=(FIL*)mymalloc(SRAMIN,sizeof(FIL)); //为文件系统分配内存

if (fftemp==NULL)  rval=1;     //分配失败,rval = 1;

tempbuf=mymalloc(SRAMIN,4096); //为数据缓存区分配4096个字节空间

if (tempbuf==NULL)    rval=1;   //分配失败,rval = 1;

  res=f_open(fftemp,(const TCHAR*)fxpath,FA_READ);  //根据文件路径打开文件

  

  if (res)   rval=2;      //打开文件失败  

  if (rval==0)  

{

if(fx==0) //更新UNIGBK.BIN

{

  ftinfo.ugbkaddr = FONTINFOADDR+sizeof(ftinfo);//信息头之后,紧跟UNIGBK转换码表

  ftinfo.ugbksize = fftemp->fsize; //UNIGBK大小

  flashaddr = ftinfo.ugbkaddr;   //UNIGBK字库的地址

}else if (fx==1)      //更新GBK12

{  

  ftinfo.f12addr=ftinfo.ugbkaddr+ftinfo.ugbksize;   //UNIGBK之后,紧跟GBK12字库

  ftinfo.gbk12size=fftemp->fsize;    //GBK12字库大小

  flashaddr=ftinfo.f12addr;     //GBK12的起始地址

}else     //更新GBK16

{

  ftinfo.f16addr=ftinfo.f12addr+ftinfo.gbk12size;     //GBK12之后,紧跟GBK16字库

  ftinfo.gkb16size=fftemp->fsize;     //GBK16字库大小

  flashaddr=ftinfo.f16addr;     //GBK16的起始地址

}   

while  (res==FR_OK)    //死循环执行

{

   res=f_read(fftemp,tempbuf,4096,(UINT *)&bread); //读取数据  

   if(res!=FR_OK)break; //执行错误

   SPI_Flash_Write(tempbuf,offx+flashaddr,4096); //从0开始写入4096个数据  

   offx+=bread;  

   fupd_prog(x,y,size,fftemp->fsize,offx); //进度显示

   if (bread!=4096)   break; //,如果读出来的数据不是4092个,说明数据读完了,跳出死循环.

}

f_close(fftemp);    //关闭文件

}  

myfree(SRAMIN,fftemp); //释放内存

myfree(SRAMIN,tempbuf); //释放内存

return res;

}

 f_open函数用法:

The f_open function creates a file object to be used to access the file.

  FRESULT f_open (
    FIL* fp,
    const TCHAR* path,
    BYTE mode
 );
f_read函数的用法:
The f_read function reads data from a file.
FRESULT f_read (
 FIL* fp,
 void* buff,
 UINT btr,
 UINT* br
);
text.c中的函数:
1.根据GBK码找到字模函数Get_HzMat(unsigned char *code,unsigned char *mat,u8 size)

//code 字符指针开始
//从字库中查找出字模
//code 字符串的开始地址,GBK码
//mat  字模数据存放地址 size*2 bytes大小
void Get_HzMat(unsigned char *code,unsigned char *mat,u8 size)   //汉字取模
{
    unsigned char qh,ql;
    unsigned char i;
    unsigned long foffset;
    qh=*code;   //分别取出GBK编码的高字节和低字节
    ql=*(++code);
    if(qh<0x81||ql<0x40||ql==0xff||qh==0xff)//非 常用汉字
    {
      for( i=0; i 
       return; //结束访问
     }
      if(ql<0x7f)ql-=0x40;//注意!
      else ql-=0x41;
      qh-=0x81;
      foffset=((unsigned long)190*qh+ql)*(size*2);//得到字库中的字节偏移量
      if(size==16)SPI_Flash_Read(mat,foffset+ftinfo.f16addr,32);  //从SPI_Flash中读取16*16字模数据
      else SPI_Flash_Read(mat,foffset+ftinfo.f12addr,24); //读取12*12字模数据
}
16*16的汉字占用16*2=32个字节
12*12的汉字占用12*2=24个字节
2. 显示一个指定大小的汉字
//在LCD上显示一个指定大小的汉字
//x,y :汉字的坐标
//font:汉字GBK码
//size:字体大小
//mode:0,正常显示,1,叠加显示
void Show_Font(u16 x,u16 y,u8 *font,u8 size,u8 mode)
{
       u8 temp,t,t1;
      u16 y0=y;
  u8 dzk[32];
        u16 tempcolor;
        if(size!=12&&size!=16)return;//不支持的size
    Get_HzMat(font,dzk,size);//得到相应大小的点阵数据
     if(mode==0)//正常显示
  {
         for(t=0;t
         {
                      temp=dzk[t];//得到12数据
                        for(t1=0;t1<8;t1++)
                        {
                          if(temp&0x80)LCD_DrawPoint(x,y);
                           else
                             {
                                  tempcolor=POINT_COLOR;
                                     POINT_COLOR=BACK_COLOR;
                                    LCD_DrawPoint(x,y);
                                        POINT_COLOR=tempcolor;//还原
                         }
                          temp<<=1;
                          y++;
                               if((y-y0)==size)
                           {
                                  y=y0;
                                      x++;
                                       break;
                             }
                  }
       }
        }else//叠加显示
        {
      for(t=0;t
           {
                      temp=dzk[t];//得到12数据
                for(t1=0;t1<8;t1++)
                    {
                          if(temp&0x80)LCD_DrawPoint(x,y);
                              temp<<=1;
                          y++;
                               if((y-y0)==size)
                           {
                                  y=y0;
                                      x++;
                                       break;
                             }
                  }
       }
        }
}
3. 在指定位置开始显示一个字符串

//在指定位置开始显示一个字符串    

//支持自动换行

//(x,y):起始坐标

//width,height:区域

//str  :字符串

//size :字体大小

//mode:0,非叠加方式;1,叠加方式         

void Show_Str(u16 x,u16 y,u16 width,u16 height,u8*str,u8 size,u8 mode)

{

     u16 x0=x;

     u16 y0=y;    

     u8 bHz=0;         //字符或者中文标志, 0--字符,1--中文              

    while(*str!=0)    //数据未结束,字符串以”0“结束

    { 

        if(!bHz)

        {

       if ( *str>0x80 )  bHz=1;     //中文 ,如果字符编码>0x80说明该字符是中文

       else              //否则该字符是字符

       {      

                if ( x>(x0+width-size/2 ))     //换行

{   

y+=size;

x=x0;   

}    

 if ( y>(y0+height-size))     break;     //越界返回      

 if (*str==13)     //换行符号

 {         

  y+=size;

x=x0;

        str++; 

   }  

   else LCD_ShowChar( x,  y,  *str,  size,  mode );     //有效部分写入 

   str++; 

   x+=size/2; //字符,为全字的一半 

     }

        }else//中文 

        {     

            bHz=0;//有汉字库    

            if(x>(x0+width-size))//换行

{    

y+=size;

x=x0;  

}

       if(y>(y0+height-size))break;//越界返回       

       Show_Font(x,y,str,size,mode); //显示这个汉字,空心显示 

       str+=2;     //一个汉字编码占2个字节

       x+=size;    //下一个汉字偏移    

        }  

    }   

}    




『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
浅谈AVR中定时器几种工作模式