主题 : 韦东山视频第三期之心得体会 复制链接 | 浏览器收藏 | 打印
级别: 新手上路
UID: 115290
精华: 0
发帖: 6
金钱: 35 两
威望: 7 点
贡献值: 0 点
综合积分: 12 分
注册时间: 2015-05-11
最后登录: 2016-02-22
楼主  发表于: 2015-05-12 08:40

 韦东山视频第三期之心得体会

                              
   这几天刚开始看韦东山老师的第三期视频,感觉在韦东山老师的帮助下,一场新的旅行又开始了。第一期项目是做一个数码相框,我在看到字符编码这一节,就想起之前LCD驱动那一节了。只是第三期从项目实战的角度出发看问题更加透彻,非常值得学习,在这里,我想分享学习之后的一些心得体会。
   我们通常在LCD上显示汉字和字母等各种各样的部分时,都要调用字库,而调用字库之前都先要对汉字或字母进行编码,如然后取它们的ASCII码或者UTF-8码对字库中的对应数据进行检索得到点阵的数据,最后将数据显示在framebuffer中,这样屏幕上就会显示出我们需要的东西了,下面是我参考视频所写的的一个显示的测试程序。
   首先我们要准备两个文件,一个汉字库文件,我是百度上找的HZK16文件,用来进行汉字检索;还有一个是ASCII自模文件,它来自于Linux源码,路径为:/drivers/video/console/font_8x16.c。该文件不能复制过来直接使用,要进行修改,如下:
   (1).屏蔽无关的头文件
    //#include <linux/font.h>
    //#include <linux/module.h>
   (2).删除无效的数组定义代码,将文件中最后一段代码删除,如下:
    /*
    const struct font_desc font_vga_8x16=
    {
        .idx=VGA8x16_IDX,
        .name="VGA8x16",
        .width=8,
        .height=16,
        .data=fontdata_8x16,
        .pref=0,
    };
    */

  然后开始编写主程序文件:lcd.c
  
  1.首先贴上要用的头文件:
  #include <sys/mman.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <unistd.h>
  #include <linux/fb.h>
  #include <fcntl.h>
  #include <stdio.h>
  #include <string.h>
  #include <stdint.h>

  2.先从主函数开始看看大体的程序流程吧。
  int main(int argc,char **argv)中:
   (1)先打开需要的fb0设备文件,并且读取LCD驱动的相关参数信息。在做这一步之前先要确保设备文件存在,也就是LCD的驱动已经存在,所以最好对open函数和ioctl函数的返回值进行判断,便于出错检查。应用层调用open和ioctl函数之后,在驱动中会调用相应的函数来实现各种操作。ioctl函数将LCD设备结构体fb_info中的成员var结构体和fix结构体这两个固定和可变参数参数结构体的内容从驱动层调到应用层,因为之后要根据驱动中配置好的图像宽高、图像深度等重要信息设置一幅图的图像数据。得到var结构体和fix结构体之后得到的数据要注意有些如图像深度、图像宽度和图像的大小的单位都是字节。这一部分具体的代码如下:
    
     fd_fb=open("/dev/fb0",O_RDWR);//打开LCD设备
     if(fd_fb<0)
     {
         printf("can not open /dev/fb0!\n");
         return -1;
     }
     //获取LCD驱动的可变参数信息
     if(ioctl(fd_fb,FBIOGET_VSCREENINFO,&var))//0:true    else:false
     {
           printf("can not get var!\n");
        return -1;
     }
     //获取LCD驱动的固定参数信息
     if(ioctl(fd_fb,FBIOGET_FSCREENINFO,&fix))
     {
          printf("can not get fix!\n");
     }
     printf("像素值是:%d 个字节\n",var.bits_per_pixel/8);
     pixel_width=var.bits_per_pixel/8;//像素深度
     line_width=var.xres*pixel_width; //一行大小
     screen_size=var.yres*line_width; //一场大小

   (2)对要操作的文件进行内存映射。
    这一步包括对设备文件fb0进行映射,这样将图像数据直接填入映射后的内存就相当于直接写入文件了,也就可以直接显示了;也包括对要读取的HZK16文件进行映射,方便从HZK16文件中读取汉字对应的编码数据。注意,对应内核中的ASCII编码文件这里不需映射。应为我直接将他放在当前目录下,并且通过extern的形式来读取其中存放ASCII编码的结构体。具体的实现代码如下:

     fbmem_start=(unsigned char *)mmap(NULL,screen_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd_fb,0);
     if(fbmem_start==(unsigned char *)-1)
     {
         printf("can not mmap!\n");
         return -1;
     }
     fd_hzk16=open("HZK16",O_RDONLY);//打开汉字库文件
     if(fd_hzk16<0)
     {
          printf("can not open HZK16 file!\n");
          return -1;
     }
      //获取文件信息
     if(fstat(fd_hzk16,&hzk_stat))
     {
         printf("can not get fstat!\n");
      return -1;
     }
     //把HZK16文件映射到内存进程空间
    hzk_memmap=(unsigned char *)mmap(NULL,hzk_stat.st_size,PROT_READ,MAP_SHARED,fd_hzk16,0);
    if(hzk_memmap==(unsigned char *)-1)
    {
        printf("can not mmap for hzk16!\n");
     return -1;
    }

    (3).做好前面的准备工作之后就可以调用相关的函数来显示东西了。在这里我对一些基本的功能分别实现,在显示之前先通过memset函数将数据00写入显存,也就是做清屏操作,然后开始显示,分别显示字母'A',和汉字,以及汉字和字母的混合。这三部分分别封装成三个函数来实现,下面是对他们的调用。
    memset(fbmem_start,0x00,screen_size);//清屏

    lcd_show_ascii(fbmem_start,var.xres/2,var.yres/2,'A',forecolor,backcolor);
  
    lcd_show_chinese(fbmem_start,var.xres/2+8,var.yres/2,str,forecolor,backcolor);
    lcd_show_chinese(fbmem_start,var.xres/2+24,var.yres/2,str+2,forecolor,backcolor);
    lcd_show_chinese(fbmem_start,var.xres/2+40,var.yres/2,str+4,forecolor,backcolor);
    lcd_show_chinese(fbmem_start,var.xres/2+56,var.yres/2,str+6,forecolor,backcolor);
    lcd_show_chinese(fbmem_start,var.xres/2+72,var.yres/2,str+8,forecolor,backcolor);

    lcd_printf_string(fbmem_start, var.xres/2-var.xres/2, var.xres/2+20,"苍茫的天空是我的爱ybylzy",forecolor,backcolor);
    
    (4).接下来是对之前的函数进行具体的阐述,之前要先阐述对一个点的操作函数的实现,因为显示字符的本质都是从显示点开始的。在显示点的函数lcd_show_point中,几个传入的参数有内存的地址、点的横纵坐标以及显示的颜色。要先对前三个参数及内存的起始地址和横纵坐标进行整合和转化,转化成最终点的写入地址,至于颜色要注意要根据颜色的深度进行处理。如果是八位或者32位的颜色,可以直接写入显存中。但如果是16位的话,要按照5:6:5的RGB比例对int类型的颜色值进行处理,处理方法是取高五位或者高六位然后进行移位和异或处理的方法整合成16位。


   void lcd_show_point(unsigned char * fbmem_start, int x, int y, unsigned int color)
{
    unsigned char *bpp8;
    unsigned short *bpp16;
    unsigned int *bpp32;
    unsigned int red,green,blue;
    bpp8=fbmem_start+y*line_width+x*pixel_width;//都是以字节为单位的
    bpp16=(unsigned short *)bpp8;//重新映射为2个字节
    bpp32=(unsigned int *)bpp8; //重新映射为4个字节
    switch(var.bits_per_pixel)
    {
       case 8:  //8位深度
      {
          *bpp8=color; //赋值,染色
          break;
      }
      case 16:  //16位深度
      {
               red=(color>>16)&0xff;
        green=(color>>8)&0xff;      
               blue=(color>>0)&0xff;
        //将颜色压缩为16位格式,去掉低位
          *bpp16=((red>>3)<<11)|((green>>2)<<5)|((blue>>3)<<0); //赋值,染色
          break;
      }
     case 32:  //32位深度
      {
          *bpp32=color; //赋值,染色
          break;
      }
      default:
           {
               printf("err %d bpp val\n",var.bits_per_pixel);
            break;
    }    
   }
}

(5).做好lcd_show_point点处理函数之后就可以去实现字符和汉字的显示了。先说字母吧。
  显示字母函数的第一步就是将&fontdata_8x16.c文件中的&fontdata_8x16结构体通过指针的方式来进行读取,如一个字符'a',显示大小为8x16,即显示16行8列的大小,一行有一个字节8位的大小,每一位置一时表示灯点亮,不信的话可以打开这个内核中的编码文件看看是不是显示如此。设定好要显示在屏幕中的哪个坐标之后,将坐标值和颜色值调用lcd_show_point即可。当然颜色值有前景色和背景色之分,前景色即是字体的颜色,如果为背景色就是让这一个点不显示。

void lcd_show_ascii(unsigned char *fbmem_start,int x,int y,unsigned char c,unsigned int forecolor,int backcolor)
{
    //指向内核中已定义的ASCII数组
    unsigned char *dots=(unsigned char *)&fontdata_8x16[c*16];
    int i,b;
    unsigned char by;
    
    for(i=0;i<16;i++)  //一个字母占16个字节共16*8位
    {
        by=dots;

        for(b=7;b>=0;b--)  //16表示行数,8表示列数
        {
             if(by&(1<<b))  //如果当前位为1说明要点亮
             {
                 lcd_show_point(fbmem_start,x+7-b,y+i,forecolor);
             }
             else  //否则为背景色,即不显示
         {
             lcd_show_point(fbmem_start,x+7-b,y+i,backcolor);
         }
        }
        
    }
  
}

(6).然后是实现汉字的显示了。与上面的显示字母还是有一些区别的。
    HZK16字库里的16×16汉字一共需要256个点来显示,也就是说需要32个字节才能达到显示一个普通汉字的目的。
我们知道一个GB2312汉字是由两个字节编码的,范围为0xA1A1~0xFEFE。A1-A9为符号区,B0-F7为汉字区。每一个区有94个字符(注意:这只是编码的许可范围,不一定都有字型对应,比如符号区就有很多编码空白区域)。一个汉字占两个字节,这两个中前一个字节为该汉字的区号,后一个字节为该字的位号。其中,每个区记录94个汉字,位号为该字在该区中的位置。所以要找到一个汉字在hzk16库中的位置就必须得到它的区码和位码。
    区码:汉字的第一个字节-0xA0 (因为汉字编码是从0xA0区开始的, 所以文件最前面就是从0xA0区开始, 要算出相对区码)
    位码:汉字的第二个字节-0xA0
    而绝对偏移计算公式为:offset=(94*(区码-1)+(位码-1))*32。而对于这个公式的理解是:区码减1是因为数组是以0为开始而区号位号是以1为开始的。因此,(94*(区号-1)+位号-1)是一个汉字字模占用的字节数。最后乘以32是因为汉字库文应从该位置起的32字节信息记录该字的字模信息(前面提到一个汉字要有32个字节显示)。按照这样的要求来做,就可以显示出汉字了,下面是最后一段详细的代码,汉字显示也就完成了:
void lcd_show_chinese(unsigned char *fbmem_start,int x,int y,unsigned char *str,unsigned int forecolor,unsigned int backcolor)
{
    unsigned int area=str[0]-0xA1;
    unsigned int where=str[1]-0xA1;
    unsigned char *dots=hzk_memmap+(area*94+where)*32;
    unsigned char by;
    int i,j,b;

    for(i=0;i<16;i++) //汉字16x16位
    {
        for(j=0;j<2;j++) //一行2个字节,共16位
        {
            by=dots[i*2+j];

         for(b=7;b>=0;b--)  //8位
         {
              if(by&(1<<b))
              {
                     lcd_show_point(fbmem_start,x+j*8+7-b,y+i,forecolor);              
              }
              else
              {
                  lcd_show_point(fbmem_start,x+j*8+7-b,y+i,backcolor);
              }
         }
        }
    }  
}