查看: 54|回复: 0

零死角玩转stm32-高级篇之液晶显示(中、英、Pic)

[复制链接]

主题

好友

1167

积分

进士

  • TA的每日心情
    慵懒
    2015-5-29 12:01
  • 签到天数: 11 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2013-8-26 17:18:31 |显示全部楼层
        5、液晶显示(中、英、Pic)

    5.1 实验简介
    在《液晶触摸画板》中,我们已经成功地实现了驱动LCD和触摸屏,并制作了触摸画板小应用,但是若要显示文字或图片文件,则还需要利用文件系统,读取保存在SD卡中的字库文件、图片文件。
    5.2 什么是字模
    我们知道其实液晶屏就是一个由像素点组成的点阵,若要显示文字,则需要很多像素点的共同构成。图中是两个由16*16的点阵显示的两个汉字。

    如果我们规定:每个汉字都由这样16*16的点阵来显示,把笔迹经过的像素点以“1”表示,没有笔迹的点以“0”表示,每个像素点的状态以一个二进制位来记录,用16*16/8 =32个字节就可以把这个字记录下来。这32个字节数据就称为该文字的字模,还有其它常用字模是24*24、32*32的。16*16的“字”的字模数据为:
    /* 字 */
    unsigned char code Bmp003[]=
    {
    /*------------------------------------------------------------
    ;  源文件 / 文字 : 字
    ;  宽×高(像素): 16×16
    ;  字模格式/大小 : 单色点阵液晶字模,横向取模,字节正序/32字节
    ----------------------------------------------------------*/
    0x02,0x00,0x01,0x00,0x3F,0xFC,0x20,0x04,0x40,0x08,0x1F,0xE0,0x00,0x40,0x00,0x80,
    0xFF,0xFF,0x7F,0xFE,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x05,0x00,0x02,0x00,
    };
    在这样的字模中,以两个字节表示一行像素点,16行构成一个字模。如果使用LCD的画点函数,按位来扫描这些字模数据,把为1的位以黑色来显示(也可以使用其它颜色),即可把整个点阵还原出来,显示在液晶屏上。
    5.3 制作字模
    我们采用“字模III-增强版v3.91” 软件来制作中文字库,步骤如下:
    1、打开字模软件

    2、点击“自动批量生成字库”按钮选项
    软件界面左下角将出现一下几个按钮选项:

    3、点击选择“二级汉字库”按钮。
    在“输入批量字符”框里面将会列出二级汉字的所有汉字,其中共收录了6768个汉字字符,非特殊情况下都能够满足大家的要求,如图:

    点击“字库智能生成”按钮,弹出“字库批量参数确认”对话框。
    我们在“源字体”选项里面做如下设置,需要注意的是大小问题,因为我们本次的设计目标是实现16*16的汉字,所以在此选择’小四’字体。
    设置好之后如下:

    5、点击“开始转换进程”按钮.就会在安装目录下或者你设置好的目录下生成.c后缀的字库文件。
    对于LCD显示来说,只要能够在指定的位置描写制定颜色的点,那么就能够很好地根据汉字字模信息来描写汉字。在此, 为了能够更好的清楚字模的取向和高低位的排列顺序,我们可以现先在pc测试我们刚才制作好的库文件。
    在这里我们取“当”字符的数据来测试。

    VC6.0测试源码如下,该代码实现了把字模中为1的点都用数字“8”来表示:
    #include <stdio.h>
    unsigned char cc[] =
    {/*"当"字符*/
    0x00,0x80,0x10,0x90,0x08,0x98,0x0C,0x90,0x08,0xA0,0x00,0x80,0x3F,0xFC,0x00,0x04,
    0x00,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x04,0x3F,0xFC,0x00,0x04,0x00,0x00
    };
    void main()
    {
    int i,j;
    unsigned char kk;
    for ( i=0;  i<16; i++)
    {
    for(j=0; j<8; j++)
    {
    kk = cc[2*i] << j ;  //左移J位
    if( kk & 0x80)  //如果最高位为1
    {
    printf("8");
    }
    else
    {
    printf(" ");
    }
    }
    for(j=0; j<8; j++)
    {
    kk = cc[2*i+1] << j ;  //左移J位
    if( kk & 0x80)      //如果最高位为1
    {
    printf("8");
    }
    else
    {
    printf(" ");
    }
    }
    printf("");
    }
    printf("
    ");
    }
    测试结果如下:
    看到以上的测试结果,相信大家对汉字的取模方向和高低位的排列顺序有了比较直观的了解。
    7、回到“字模III-增强版v3.91”软件,采用与之前同样的方式生成bin格式的字库文件(即“生成格式”选项设置为“bin文件格式”)。

    在软件安装目录下会生成Font.dat文件,我们用“WinHex”软件查看他的具体内容,与刚才制作的.c字库的文件内容是一致的,对比如下:

    将生成的汉字字库拷贝到SD卡根目录下并重命名为“HZLIB.bin”。把该文件保存到SD卡中,STM32芯片通过文件系统读取文件即可获得字库的数据。
    5.4. BMP图片格式
    使用LCD屏来显示BMP图片,就如同播放MP3文件一样,首先要了解其文件的格式。
    BMP文件格式,又称为位图(Bitmap)或是DIB(Device-Independent Device,设备无关位图),是Windows系统中广泛使用的图像文件格式。BMP文件保存了一幅图像中所有的像素。
    BMP格式可以保存单色位图、16色或256色索引模式像素图、24位真彩色图象,每种模式中单一像素点的大小分别为1/8字节,1/2字节,1字节和32字节。目前最常见的是256位色BMP和24位色BMP
    BMP文件格式还定义了像素保存的几种方法,包括不压缩、RLE压缩等。常见的BMP文件大多是不压缩的。
    Windows所使用的BMP文件,在开始处有一个文件头,大小为54字节。保存了包括文件格式标识、颜色数、图像大小、压缩方式等信息,因为我们仅讨论24位色不压缩的BMP,所以文件头中的信息基本不需要注意,只有“大小”这一项对我们比较有用。图像的宽度和高度都是一个32位整数,在文件中的地址分别为0x0012和0x0016。54个字节以后,如果是16色或256色BMP,则还有一个颜色表,但在24位色BMP文件则没有,我们这里不考虑。接下来就是实际的像素数据了。因此总的来说BMP图片的优点是简单。
    5.4.1 BMP图片分析
    下面来用WinHex软件(跟UltraEdit软件功能类似)来分析一下BMP图像的文件内容:
    测试图片为123.bmp,先用ACDSee软件打开,查看图像,其图像内容见图 01。

    图 01 测试图片

    可知,这幅图像的大小是15*8,是24位真彩色bmp图像。
    接着使用WinHex软件打开,可读取到该文件的原始数据见图 02。

    图 02 测试图片的原始数据

    文件头部信息部分(前面54字节)

    图 03 文件头部信息

    图 03,阴影部分是文件头部信息。它可以分为两块:BMP文件头位图信息头
    0~1字节
    BMP文件的 0和1字节用于表示文件的类型。如果是位图文件类型,必须分别为0x42 和0x4D ,0x424D=’BM’。
    3~14字节
    3到14字节的意义可以用一个结构体来描述。如下:
    typedef struct tagBITMAPFILEHEADER
    {
    //attention: sizeof(DWORD)=4 sizeof(WORD)=2
    DWORD bfSize;      //文件大小
    WORD bfReserved1;  //保留字,不考虑
    WORD bfReserved2;  //保留字,同上
    DWORD bfOffBits;   //实际位图数据的偏移字节数,即前三个部分长度之和
    } BITMAPFILEHEADER,tagBITMAPFILEHEADER;
    14~53字节
    头部信息剩下的部分就是位图信息头,14到53字节内容的意义如下:
    typedef struct tagBITMAPINFOHEADER
    {
    //attention: sizeof(DWORD)=4 sizeof(WORD)=2
    DWORD biSize;          //指定此结构体的长度,为40
    LONG biWidth;          //位图宽,说明本图的宽度,以像素为单位
    LONG biHeight;         //位图高,指明本图的高度,像素为单位
    WORD biPlanes;         //平面数,为1
    WORD biBitCount;      //采用颜色位数,可以是1,2,4,8,16,24新的可以是32
    DWORD biCompression;    //压缩方式,可以是0,1,2,其中0表示不压缩
    DWORD biSizeImage;      //实际位图数据占用的字节数
    LONG biXPelsPerMeter;   //X方向分辨率
    LONG biYPelsPerMeter;   //Y方向分辨率
    DWORD biClrUsed;        //使用的颜色数,如果为0,则表示默认值(2^颜色位数)
    DWORD biClrImportant;   //重要颜色数,如果为0,则表示所有颜色都是重要的
    } BITMAPINFOHEADER,tagBITMAPINFOHEADER;
    由上述分析与WinHex软件的分析内容结合得到该图片的信息如下:
    文件大小:438
    保留字:0
    保留字:0
    实际位图数据的偏移字节数:54
    位图信息头:
    结构体的长度:40
    位图宽:15
    位图高:8
    biPlanes平面数:1
    biBitCount采用颜色位数:24
    压缩方式:0
    biSizeImage实际位图数据占用的字节数:0
    X方向分辨率:3780
    Y方向分辨率:3780
    使用的颜色数:0
    重要颜色数:0
    图像像素数据部分(如果是24位真彩色,则54字节之后就是像素部分):

    图 04 图像像素数据

    有些读者可能已经发现,在像素部分夹杂着一些值为0的数据信息,如下图灰色部分所示:

    本例子中整张图片都是灰色的,为什么会有0像素的出现呢?
    对齐的数据,更容易被操作系统或者硬件调入cache,使得cache的命中率提高,从而提高访问效率。也就是基于性能上得考虑,所以Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充。由前面位图信息头的分析可知,位图宽为15个像素点,由于是24位图,每个像素点由3个字节构成。 因此,未补充字节前字节数为15*3等于45字节,要到4的倍数,必须向上取4的倍数即48,所以就有了不上3个字节0的结果。
    因此,整个图像文件的大小为:54+ (15*3 + 3)*8 等于438字节,和前面所得到的信息文件大小为438是一致的。
    另外一点需要注意的是显示图像的顺序是 由下到上,由左到右。即像素数据部分的第一像素数据是我们见到的图像的左下角的像素的数据;而像素数据的最后一个有效数据是我们见到的图像的右上角的像素的数据。
    下面我们修改图片来验证一下:
    我们将左下角画上白色,右上角画上黑色,图片放大之后如下

    图片数据分析如下:

    我们可以看到像素部分开始部分是白色像素(灰色部分) ,对应我们图像的左下角:

    后尾部分是黑色像素(灰色部分)对应我们图像的右上角:

    对BMP图像文件内容有了具体的了解之后就可以开始编写我们的应用程序,根据图片的头部信息,合理地读出其中的像素部分,把读出的像素点数据送到LCD屏,就可以显示出该图片了。
    5.5 显示中英文及 BMP图片实验
    5.5.1实验描述及工程文件清单
    实验描述使用软件制作自定义类型的字库,然之后将字库放入SD卡中,并且在SD卡中放入三张BMP图片。最后调用截屏函数截取LCD背景并保存为BMP图片。
    硬件连接本实验的硬件连接包括MicroSD卡控制信号、TFT 控制信号线及触摸屏TSC2046控制线,这些连接与前面章节的一样,可参照前面的电路图。
    用到的库文件startup/start_stm32f10x_hd.cCMSIS/core_cm3.cCMSIS/system_stm32f10x.cFWlib/misc.cFWlib/stm32f10x_rcc.cFwlib/stm32f10x_systick.cFWlib/stm32f10x_exti.cFWlib/stm32f10x_gpio.cFWlib/stm32f10x_sdio.cFWlib/stm32f10x_dma.cFWlib/stm32f10x_usart.cFWlib/stm32f10x_fsmc.c
    用户编写的文件USER/main.cUSER/stm32f10x_it.cUSER/systick.cUSER/usart1.cUSER/lcd.cUSER/ff.cUSER/ sdio_sdcard.cUSER/lcd_botton.cUSER/Sd_bmp.cUSER/sd_fs_app.c
    文件系统文件ff9/diskio.cff9/ff.cff9/cc936.c
    5.5.2配置工程环境
    本实验需要制作字库,其文件名为:HZLIB.bin,三个BMP图片文件,文件名为:pic1.bmp、pic2.bmp、pic3.bmp,把这四个文件保存到SD卡中,再把该SD卡插入开发板的SD卡接口。
    本实验中要把旧文件:systick.c、usart1.c、lcd.c、ff.c、sdio_sdcard.c 、lcd_botton.c文件添加进新工程,新建Sd_bmp.c、sd_fs_app.c文件,分别用于编写BMP文件相关的函数和字模获取函数。
    5.5.3 main文件
    从main函数看起,它调用了很多函数,用于显示BMP图和沿各种方向排列的文字。
    int main(void)
    {
    /* USART1 config */
    USART1_Config();
    SysTick_Init();
    LCD_Init();                         /* LCD 初始化*/
    sd_fs_init();
    /*显示图像*/
    Lcd_show_bmp(0, 0,"/pic3.bmp");
    Lcd_show_bmp( 0,0,"/pic2.bmp");
    Lcd_show_bmp( 0,0,"/pic1.bmp");
    /*横屏显示*/
    LCD_Str_O(20, 10, "lCD_DEMO",0);
    LCD_Str_CH(20,30,"阿莫论坛野火专区",0,0xffff);
    LCD_Str_CH_O(20,50,"阿莫论坛野火专区",0);
    LCD_Num_6x12_O(20, 70, 65535, BLACK);
    LCD_Str_6x12_O(20, 90,"LOVE STM32", BLACK);
    /*竖屏显示*/
    LCD_Str_O_P(300, 10, "Runing", 0);
    LCD_Str_CH_P(280,10,"阿莫论坛野火专区欢迎你",0xff,0xffff);
    LCD_Str_CH_O_P(260,10,"阿莫论坛野火专区",0);
    LCD_Str_6x12_O_P(240, 10,"LOVE STM32", 0);
    LCD_Str_ENCH_O_P(220,10,"欢迎使用野火stm32开发板",0);
    /*截图*/
    LCD_Str_CH(20,150,"正在截图",0,0xffff);
    Screen_shot(0, 0, 240, 320, "/myScreen");
    LCD_Str_CH(20,150,"截图完成",0,0xffff);
    while (1)
    {}
    }
    5.5.4显示汉字
    这里先说汉字字符的显示,第17行:LCD_Str_CH(20,30,"阿莫论坛野火专区",0,0xffff);
    该函数功能是显示汉字字符串,源码如下:
    /******************************************************************
    * 函数名:LCD_Str_CH
    * 描述  :在指定坐标处显示16*16大小的指定颜色汉字字符串
    * 输入  :    - x: 显示位置横向坐标
    *          - y: 显示位置纵向坐标
    *          - str: 显示的中文字符串
    *          - Color: 字符颜色
    *          - bkColor: 背景颜色
    * 输出  :无
    * 举例  :    LCD_Str_CH(0,0,"阿莫论坛野火专区",0,0xffff);
    LCD_Str_CH(50,100,"阿莫论坛野火专区",0,0xffff);
    LCD_Str_CH(320-16*8,240-16,"阿莫论坛野火专区",0,0xffff);
    * 注意  :    字符串显示方向为横向 已测试
    *************************************************************/
    void LCD_Str_CH(u16 x,u16 y,const u8 *str,u16 Color,u16 bkColor)
    {
    Set_direction(0);
    while(*str != '�')
    {
    if(x>(320-16))
    {
    /*换行*/
    x =0;
    y +=16;
    }
    if(y >(240-16))
    {
    /*重新归零*/
    y =0;
    x =0;
    }
    LCD_Char_CH(x,y,str,Color,bkColor);
    str += 2 ;
    x += 16 ;
    }
    }
    该函数其实没做到什么工作,对超出屏幕范围的显示坐标进行换行处理,并把字符串中的汉字一个一个提取出来调用单字符显示函数LCD_Char_CH()显示出来,LCD_Char_CH()函数的源码如下:
    /*********************************************
    * 函数名:LCD_Char_CH
    * 描述  :显示单个汉字字符
    * 输入  :    x: 0~(319-16)
    *          y: 0~(239-16)
    *          str: 中文字符串首址
    *          Color: 字符颜色
    *          bkColor: 背景颜色
    * 输出  :无
    * 举例  :    LCD_Char_CH(200,100,"好",0,0);
    * 注意    :如果输入大于1的汉字字符串,显示将会截断,只显示最前面一个汉字
    *********************************************************/
    void LCD_Char_CH(u16 x,u16 y,const u8 *str,u16 Color,u16 bkColor)
    {
    #ifndef NO_CHNISEST_DISPLAY         /*如果汉字显示功能没有关闭*/
    u8 i,j;
    u8 buffer[32];
    u16 tmp_char=0;
    GetGBKCode_from_sd(buffer,str);  /* 取字模数据 */
    for (i=0;i<16;i++)
    {
    tmp_char=buffer[i*2];
    tmp_char=(tmp_char<<8);
    tmp_char|=buffer[2*i+1];
    for (j=0;j<16;j++)
    {
    if ( (tmp_char >> 15-j) & 0x01 == 0x01)
    {
    LCD_ColorPoint(x+j,y+i,Color);
    }
    else
    {
    LCD_ColorPoint(x+j,y+i,bkColor);
    }
    }
    }
    #endif
    }
    函数中的条件编译#ifndef  NO_CHNISEST_DISPLAY ,是用于开关汉字显示功能的,若定义了NO_CHNISEST_DISPLAY,则本函数为空,关闭了显示汉字的功能。
    LCD_Char_CH()这个函数中,首先调用GetGBKCode_from_sd()从SD卡中读出我们需要显示在LCD上的指定汉字的字模数据。
    接着在22~40行的代码就根据字模数据来描写,把字模中为1的数据位,在LCD屏中的像素点中使用画点函数LCD_ColorPoint()显示特定的颜色。思路和前面VC测试部分用数字“8”来显示“当”字是一样的。
    5.5.4.1查找字模
    读者现在可能在想,字库里面保存着大量的汉字字幕信息,现在输入GetGBKCode_from_sd(buffer,str)就能够拷贝出这个字符的字模数据,是怎样定位字模信息所在的位置的呢?换句话说,假如现在要显示“吾”字,是怎样根据这个字来确定“吾”字符在字库中的保存位置的呢?其实这里面有一定的映射关系,那就是接下来要说的汉字“区码”和“位码”。
    在前面生成的HZLIB.bin文件,实际是按国标GB2312生成的二级汉字库。在国标GB2312—80中规定,所有的国标汉字及符号在字库中的存储形式是:分配在一个94行、94列的阵列中,阵列的每一行称为一个“区”,共有01区到94区;每一列称为一个“位”,共有01位到94位,阵列中的每一个汉字和符号所在的区号和位号组合在一起形成的四个阿拉伯数字就是它们的“区位码”。区位码的前两位是它的区号,后两位是它的位号。我们生成的汉字库就是这样按区位码排列的阵列,通过区位码,就能查找出该字的字模。
    汉字的机内码是指在计算机中表示一个汉字的编码。为避免与ASCII码混淆。用机内码的两个字节表示一个汉字,这两个字节分别称为高位字节和低位字节。高位字节 = 区码 + 20H + 80H(或区码 + A0H)低位字节 = 位码 + 20H + 80H(或位码 + AOH)
    因此,我们就可以通过汉字的机内码,运算得出汉字在字库中的区位码,由区位码查找出该汉字的字模。
    下面以vc6.0的测试源码来说明机内码、区位码的关系:
    #include <stdio.h>
    void main ()
    {
    unsigned char * s , * e = "A" , * c = "古" ;
    unsigned char high_byte,lower_byte; //内码高字节,内码低字节
    printf ( "字母'%s的ASCII码'=",e ) ;
    s = e ;
    while ( * s != 0 )  //C的字符串以0为结束符*
    {
    printf ( "%3d," , *s ) ;
    s ++ ;
    }
    printf ( "汉字内码(10进制)'%s'=",c ) ;
    s = c ;
    while ( *s != 0 )
    {
    printf ( "%3d," , * s );
    s ++ ;
    }
    printf ( "汉字内码(16进制)'%s'=",c ) ;
    s = c ;
    while ( *s != 0 )
    {
    printf ( "%0X," , * s );
    s ++ ;
    }
    s = c ;
    high_byte = *s;
    s ++ ;
    lower_byte = *s;
    printf("
    汉字'%s'对应的内码高字节:%d内码低字节:%d",c,high_byte,lower_byte);
    printf("
    汉字'%s'对应的区码为:%d-160 = %d位码为:%d-160 = %d",c,high_byte,high_byte-160,lower_byte,lower_byte-160);
    printf("
    汉字'%s'在区位码表中的位置为%d%d",c,high_byte-160,lower_byte-160);
    printf("汉字区位码表可参考网站:https://cs.scu.edu.cn/~wangbo/others/quweima.htm");
    printf("通过在线查阅,编号为%d%d对应的汉字刚好就是'%s'
    ",high_byte-160,lower_byte-160,c);
    }
    测试结果如下:

    打开汉字区位码表在线查询网站:https://www.jscj.com/index/gb2312.php
    查询“古”汉字的区位码刚好如计算所得,为
    上面的测试结果说明了每一个汉字的内码具体作用。回到本实验工程中获取字模函数GetGBKCode_from_sd()中,它的具体定义如下:
    /******************************************************
    * 函数名:GetGBKCode_from_sd
    * 描述  :从sd卡上的字库文件中拷贝指定汉字的字模数据
    * 输入  : pBuffer---数据保存地址
    *         c--汉字字符低字节码
    * 输出  :    0                (成功)
    *            -1           (失败)
    *********************************************************/
    int GetGBKCode_from_sd(unsigned char* pBuffer,unsigned char * c)
    {
    unsigned char High8bit,Low8bit;
    unsigned int pos;
    High8bit=*c;
    Low8bit=*(c+1);
    pos = ((High8bit-0xb0)*94+Low8bit-0xa1)*2*16 ;
    f_mount(0, &myfs[0]);
    myres = f_open(&myfsrc , "0:/HZLIB.bin", FA_OPEN_EXISTING | FA_READ);
    if ( myres == FR_OK )
    {
    f_lseek (&myfsrc, pos);     //制定读取位置
    myres = f_read( &myfsrc, pBuffer, 32, &mybr ); //16*16大小的汉字其字模占用16*2个字节
    f_close(&myfsrc);
    return 0;
    }
    else
    return -1;
    }
    第17行中的: pos = ((High8bit-0xb0)*94+Low8bit-0xa1)*2*16 语句就是根据约定的映射关系由汉字内码求得该汉字字模在字库中的存放起始位置。之后就到指定的位置去拷贝字模数据就可以了。
    以上就是利用SD卡字库实现LCD显示汉字的具体流程。对于ASCII码(包括英文字符)的显示,原理实际上也是一样的,由于ASCII码占用空间较少,所以我们直接把它的字模数据以数组的形式存储在代码中,这些数组在文件ascii.hasc_font.h中定义。
    5.6 实现SD卡BMP图像的读取与保存
    5.6.1显示BMP图
    再回到前面的main函数,其中调用了一个BMP图片显示函数Lcd_show_bmp(),其定义如下:
    /******************************************************
    * 函数名:Lcd_show_bmp
    * 描述  :LCD显示RGB888位图图片
    * 输入  : x                  --显示横坐标(0-319)
    y                    --显示纵坐标(0-239)
    *               pic_name       --图片名称
    * 输出  :无
    * 举例  :Lcd_show_bmp(0, 0,"/test.bmp");
    * 注意  :图片为24为真彩色位图图片
    图片宽度不能超过320
    图片在LCD上的粘贴范围为:纵向:  [x,x+图像高度]   横向 [Y,Y+图像宽度]
    当图片为320*240时--建议X输入0  y输入0
    *********************************************************/
    void Lcd_show_bmp(unsigned short int x, unsigned short int y,unsigned char *pic_name)
    {
    int i, j, k;
    int width, height, l_width;
    BYTE red,green,blue;
    BITMAPFILEHEADER bitHead;
    BITMAPINFOHEADER bitInfoHead;
    WORD fileType;
    unsigned int read_num;
    unsigned char tmp_name[20];
    sprintf((char*)tmp_name,"0:%s",pic_name);
    f_mount(0, &bmpfs[0]);
    BMP_DEBUG_PRINTF("file mount ok ");   //使用串口输出调试信息
    bmpres = f_open( &bmpfsrc , (char *)tmp_name, FA_OPEN_EXISTING | FA_READ);
    Set_direction(0);
    if(bmpres == FR_OK)
    {
    BMP_DEBUG_PRINTF("Open file success");
    //读取位图文件头信息
    f_read(&bmpfsrc,&fileType,sizeof(WORD),&read_num);
    if(fileType != 0x4d42)
    {
    BMP_DEBUG_PRINTF("file is not .bmp file!");
    return;
    }
    else
    {
    BMP_DEBUG_PRINTF("Ok this is .bmp file");
    }
    f_read(&bmpfsrc,&bitHead,sizeof(tagBITMAPFILEHEADER),&read_num);
    showBmpHead(&bitHead);
    BMP_DEBUG_PRINTF("");
    //读取位图信息头信息
    f_read(&bmpfsrc,&bitInfoHead,sizeof(BITMAPINFOHEADER),&read_num);
    showBmpInforHead(&bitInfoHead);
    BMP_DEBUG_PRINTF("");
    }
    else
    {
    BMP_DEBUG_PRINTF("file open fail!");
    return;
    }
    width = bitInfoHead.biWidth;
    height = bitInfoHead.biHeight;
    l_width = WIDTHBYTES(width* bitInfoHead.biBitCount);        //计算位图的实际宽度并确保它为32的倍数
    if(l_width>960)
    {
    BMP_DEBUG_PRINTF("SORRY, PIC IS TOO BIG (<=320)");
    return;
    }
    if(bitInfoHead.biBitCount>=24)
    {
    bmp_lcd(x,240-y-height,width, height);    //LCD参数相关设置
    for(i=0;i<height+1; i++)
    {
    for(j=0; j<l_width; j++)  //将一行数据全部读入
    {
    f_read(&bmpfsrc,pColorData+j,1,&read_num);
    }
    for(j=0;j<width;j++)//一行有效信息
    {
    k = j*3;                                                                                //一行中第K个像素的起点
    red = pColorData[k+2];
    green = pColorData[k+1];
    blue =  pColorData[k];
    LCD_WR_Data(RGB24TORGB16(red,green,blue));      //写入LCD-GRAM
    }
    }
    bmp_lcd_reset();   //lcd扫描方向复原
    }
    else
    {
    BMP_DEBUG_PRINTF("SORRY, THIS PIC IS NOT A 24BITS REAL COLOR");
    return ;
    }
    f_close(&bmpfsrc);
    }
    该函数的主要工作流程是:读取头部信息确定宽度和高度并确定每一行后面具体需要读出的字节数(保证是4字节的倍数)----->读取一行像素点并显示-->读取下一行并显示--> ……….直至读完所有行。
    另外一点就是:RGB24TORGB16是个宏定义,因为图像数据是RGB888即24为真彩色,而我们的LCD是RGB565即16位色度的,所以我们需要按比例将24为真彩色压缩为16位。宏定义如下:
    #define RGB24TORGB16(R,G,B) ((unsigned short int)((((R)>>3)<<11)|(((G)>>2)<<5)| ((B)>>3)))
    5.6.2 LCD截图功能
    为了实现截图功能,我们可以根据用户截屏范围的宽和高来构造BMP文件的信息头,并且根据位图宽与四字节对齐的关系来补充需要的0大小字节。在main函数中,我们调用了Screen_shot()这个函数来截图。保存的图片是24位的真彩色。Screen_shot()的源码如下:
    /**********************************************************
    * 函数名:Screen_shot
    * 描述  :截取LCD指定位置  指定宽高的像素 保存为24位真彩色bmp格式图片
    * 输入  :    x                               ---水平位置
    *                  y                               ---竖直位置
    *                  Width                       ---水平宽度
    *                  Height                  ---竖直高度
    *                  filename                ---文件名
    * 输出  :    0       ---成功
    *                  -1      ---失败
    *              8           ---文件已存在
    * 举例  :Screen_shot(0, 0, 320, 240, "/myScreen");-----全屏截图
    * 注意  :x范围[0,319]  y范围[0,239]  Width范围[0,320-x]  Height范围[0,240-y]
    *                  如果文件已存在,将直接返回
    **************************************************************/
    int Screen_shot(unsigned short int x, unsigned short int y, unsigned short int Width, unsigned short int Height, unsigned char *filename)
    {
    unsigned char header[54] =
    {
    0x42, 0x4d, 0, 0, 0, 0,
    0, 0, 0, 0, 54, 0,
    0, 0, 40,0, 0, 0,
    0, 0, 0, 0, 0, 0,
    0, 0, 1, 0, 24, 0,
    0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0
    };
    int i;
    int j;
    long file_size;
    long width;
    long height;
    unsigned short int tmp_rgb;
    unsigned char r,g,b;
    unsigned char tmp_name[30];
    unsigned int mybw;
    char kk[4]={0,0,0,0};
    // if(!(Width%4))
    //    file_size = (long)Width * (long)Height * 3 + 54;
    //else
    file_size = (long)Width * (long)Height * 3 + Height*(Width%4) + 54;     //宽*高 +补充的字节 + 头部信息
    header[2] = (unsigned char)(file_size &0x000000ff);
    header[3] = (file_size >> 8) & 0x000000ff;
    header[4] = (file_size >> 16) & 0x000000ff;
    header[5] = (file_size >> 24) & 0x000000ff;
    width=Width;
    header[18] = width & 0x000000ff;
    header[19] = (width >> 8) &0x000000ff;
    header[20] = (width >> 16) &0x000000ff;
    header[21] = (width >> 24) &0x000000ff;
    height = Height;
    header[22] = height &0x000000ff;
    header[23] = (height >> 8) &0x000000ff;
    header[24] = (height >> 16) &0x000000ff;
    header[25] = (height >> 24) &0x000000ff;
    sprintf((char*)tmp_name,"0:%s.bmp",filename);
    f_mount(0, &bmpfs[0]);
    bmpres = f_open( &bmpfsrc , (char*)tmp_name, FA_CREATE_NEW | FA_WRITE);
    f_close(&bmpfsrc);   //新建文件之后要先关闭再打开才能写入
    bmpres = f_open( &bmpfsrc , (char*)tmp_name, FA_OPEN_EXISTING | FA_WRITE);
    if ( bmpres == FR_OK )
    {
    bmpres = f_write(&bmpfsrc, header,sizeof(unsigned char)*54, &mybw);
    for(i=0;i<Height;i++)                       //高
    {
    if(!(Width%4))
    {
    for(j=0;j<Width;j++)    //宽
    {
    #ifdef HX8347
    tmp_rgb = bmp4(j+y,Height-i+x);
    #else
    tmp_rgb = bmp4(Height-i+x,j+y);
    #endif
    r =  GETR_FROM_RGB16(tmp_rgb);
    g =  GETG_FROM_RGB16(tmp_rgb);
    b =  GETB_FROM_RGB16(tmp_rgb);
    bmpres = f_write(&bmpfsrc, &b,sizeof(unsigned char), &mybw);
    bmpres = f_write(&bmpfsrc, &g,sizeof(unsigned char), &mybw);
    bmpres = f_write(&bmpfsrc, &r,sizeof(unsigned char), &mybw);
    }
    }
    else
    {
    for(j=0;j<Width;j++)
    {
    #ifdef HX8347
    tmp_rgb = bmp4(j+y,Height-i+x);
    #else
    tmp_rgb = bmp4(Height-i+x,j+y);
    #endif
    r =  GETR_FROM_RGB16(tmp_rgb);
    g =  GETG_FROM_RGB16(tmp_rgb);
    b =  GETB_FROM_RGB16(tmp_rgb);
    bmpres = f_write(&bmpfsrc, &b,sizeof(unsigned char), &mybw);
    bmpres = f_write(&bmpfsrc, &g,sizeof(unsigned char), &mybw);
    bmpres = f_write(&bmpfsrc, &r,sizeof(unsigned char), &mybw);
    }
    bmpres = f_write(&bmpfsrc, kk,sizeof(unsigned char)*(Width%4), &mybw);
    }
    }
    f_close(&bmpfsrc);
    return 0;
    }
    else if ( bmpres == FR_EXIST )  //如果文件已经存在
    {
    f_close(&bmpfsrc);
    return FR_EXIST;                        //8
    }
    else
    {   f_close(&bmpfsrc);
    return -1;
    }
    }
    该函数中,调用了bmp4()这个函数,该函数返回LCD上指定位置的像素信息。GETG_FROM_RGB16(绿色)GETB_FROM_RGB16(蓝色)GETR_FROM_RGB16 (红色)都是宏定义,将RGB565即16位色度抽取出其中的RGB数据并分别将其线性映射为8位数据即映射为RGB888真彩色。宏定义的内容如下:
    #define GETR_FROM_RGB16(RGB565)  ((unsigned char)(( ((unsigned short int )RGB565) >>11)<<3))            //返回8位 R
    #define GETG_FROM_RGB16(RGB565)  ((unsigned char)(( ((unsigned short int )(RGB565 & 0x7ff)) >>5)<<2))   //返回8位 G
    #define GETB_FROM_RGB16(RGB565)  ((unsigned char)(( ((unsigned short int )(RGB565 & 0x1f))<<3)))        //返回8位 B
    利用Screen_shot()函数,就实现了把屏幕的当前显示的图像转换为BMP文件的功能。
    5.7实验现象
    把文件HZLIB.bin、pic1.bmp、pic2.bmp、pic3.bmp,保存到SD卡中,再把该SD卡插入开发板的SD卡接口(也可直接把工程下的SD字库备份文件夹下的内容复制到SD卡的根目录),然后将野火STM32开发板供电(DC5V),插上JLINK,插上串口线(两头都是母的交叉线),接上液晶屏,将编译好的程序下载到开发板。
    程序运行之后截图保存为“myScreen.bmp”如下:

    ( ^ ^ 截图保存图片功能+摄像头模块 就构成了一个完整的照相机啦!!)
    回复

    使用道具 举报

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

    关闭

    站长推荐上一条 /4 下一条

    手机版|电路城

    GMT+8, 2019-7-20 01:35 , Processed in 0.085593 second(s), 14 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz!

    返回顶部