LTDC-DMA2D显示屏显示-编码标准(三)

一、字符编码

由于计算机只能识别0和1,文字也只能以0和1的形式在计算机里存储,所以我们需要对文字进行编码才能让计算机处理,编码的过程就是规定特定的01数字串来表示特定的文字,最简单的字符编码例子是ASCII码。

  • ASCII码(0~255)

    在程序设计中使用ASCII编码表约定了一些控制字符、英文及数字。它们在存储器中,本质也是二进制数,只是我们约定这些二进制数可以表示某些特殊意义。

    ASCII码分为两部分:

    • 第一部分是控制字符或通讯专用字符(编码0~31)

      它们并没有特定的图形显示,但会根据不同的应用程序,而对文本显示有不同的影响。

      十进制十六进制缩写/字符解释
      00NUL(null)空字符
      11SOH(start of headline)标题开始
      22STX (start of text)正文开始
      33ETX (end of text)正文结束
      44EOT (end of transmission)传输结束
      55ENQ (enquiry)请求
      66ACK (acknowledge)收到通知
      77BEL (bell)响铃
      88BS (backspace)退格
      99HT (horizontal tab)水平制表符
      100ALF (NL line feed, new line)换行键
      110BVT (vertical tab)垂直制表符
      120CFF (NP form feed, new page)换页键
      130DCR (carriage return)回车键
      140ESO (shift out)不用切换
      150FSI (shift in)启用切换
      1610DLE (data link escape)数据链路转义
      1711DC1 (device control 1)设备控制1
      1812DC2 (device control 2)设备控制2
      1913DC3 (device control 3)设备控制3
      2014DC4 (device control 4)设备控制4
      2115NAK (negative acknowledge)拒绝接收
      2216SYN (synchronous idle)同步空闲
      2317ETB (end of trans. block)传输块结束
      2418CAN (cancel)取消
      2519EM (end of medium)介质中断
      261ASUB (substitute)替补
      271BESC (escape)换码(溢出)
      281CFS (file separator)文件分割符
      291DGS (group separator)分组符
      301ERS (record separator)记录分离符
      311FUS (unit separator)单元分隔符
    • 第二部分是包括空格、阿拉伯数字、标点符号、大小写英文字母以及“DEL(删除控制)”(32~127)

      除最后一个DEL符号外,都能以图形的方式来表示,它们属于传统文字书写系统的一部分。

      十进制十六进制缩写/字符十进制十六进制缩写/字符
      3220(space)空格8050P
      3321!8151Q
      3422"8252R
      3523#8353S
      3624$8454T
      3725%8555U
      3826&8656V
      3927'8757W
      4028(8858X
      4129)8959Y
      422A*905AZ
      432B+915B[
      442C,925C\
      452D-935D]
      462E.945E^
      472F/955F_
      483009660`
      493119761a
      503229862b
      513339963c
      5234410064d
      5335510165e
      5436610266f
      5537710367g
      5638810468h
      5739910569i
      583A:1066Aj
      593B;1076Bk
      603C<1086Cl
      613D=1096Dm
      623E>1106En
      633F?1116Fo
      6440@11270p
      6541A11371q
      6642B11472r
      6743C11573s
      6844D11674t
      6945E11775u
      7046F11876v
      7147G11977w
      7248H12078x
      7349I12179y
      744AJ1227Az
      754BK1237B{
      764CL1247C|
      774DM1257D}
      784EN1267E~
      794FO1277FDEL (delete) 删除
    • 第三部分(128~255)

      ASCII扩展字符集,方便其他国家进行改进,至此基本存储单位Byte(char)能表示的编号都被用完了。

  • 中文编码

    以中文编码直接对方块字进行编码,一个汉字使用一个号码。由于汉字非常多,常用字就有6000多个,如果像ASCII编码表那样只使用1个字节最多只能表示256个汉字,所以我们使用2个字节来编码。

    • GB2312标准

      我国首先定义的是GB2312标准。它把ASCII码表127号之后的扩展字符集直接取消掉,并规定小于127的编码按原来ASCII标准解释字符。当2个大于127的字符连在一起时,就表示1个汉字,第1个字节使用 (0xA1-0xFE) 编码,第2个字节使用(0xA1-0xFE)编码,这样的编码组合起来可以表示了7000多个符号,其中包含6763个汉字。在这些编码里,我们还把数学符号、罗马字母、日文假名等都编进表中,就连原来在ASCII里原本就有的数字、标点以及字母也重新编了2个字节长的编码,这就是平时在输入法里可切换的“全角”字符,而标准的ASCII码表中127号以下的就被称为“半角”字符。

      • 如何与ASCII码兼容?

      第1字节第2字节表示的字符说明
      0x680x69(hi)两个字节的值都小于127(0x7F),使用ASCII解码
      0xB00xA1(啊)两个字节的值都大于127(0x7F),使用GB2312解码
      • 区位码

      在GB2312编码的实际使用中,有时会用到区位码的概念。GB2312编码对所收录字符进行了“分区”处理,共94个区,每区含有94个位,共8836个码位。而区位码实际是GB2312编码的内部形式,它规定对收录的每个字符采用两个字节表示,第一个字节为“高字节”,对应94个区;第二个字节为“低字节”,对应94个位。所以它的区位码范围是:0101-9494。为兼容ASCII码,区号和位号分别加上0xA0偏移就得到GB2312编码。在区位码上加上0xA0偏移,可求得GB2312编码范围:0xA1A1-0xFEFE,其中汉字的编码范围为0xB0A1-0xF7FE,第一字节0xB0-0xF7(对应区号:16-87),第二个字节0xA1-0xFE(对应位号:01-94)。

 

 在keil5中可以改变编码的形式:

 

    • GBK编码

      GB2312并没有包含很多生僻字,所以在GB2312的基础上又增加了14240个新汉字(包括Big5中的所有汉字)和符号。不再要求第2个字节的编码值必须大于127,只要第1个字节大于127就表示这是一个汉字的开始,这样就做到兼容ASCII和GB2312标准了。总体编码范围为8140-FEFE。

      第1字节第2字节第3字节表示的字符说明
      0x68(<7F)0xB0(>7F)0xA1(>7F)(h啊)第1个字节小于127,使用ASCII解码,每2个字节大于127,直接使用GBK解码,兼容GB2312
      0xB0(>7F)0xA1(>7F)0x68(<7F)(啊h)第1个字节大于127,直接使用GBK码解释,第3个字节小于127,使用ASCII解码
      0xB0(>7F)0x56(<7F)0x68(<7F)(癡h)第1个字节大于127,第2个字节虽然小于127,直接使用GBK解码,第3个字节小于127,使用ASCII解码
    • GB18030

      GB18030的编码使用4个字节,它利用前面标准中的第2个字节未使用的“0x30-0x39”编码表示扩充四字节的后缀,兼容GBK、GB2312及ASCII标准。

      GB18030-2000主要在GBK基础上增加了“CJK(中日韩)统一汉字扩充A”的汉字。加上前面GBK的内容,GB18030-2000一共规定了27533个汉字(包括部首、部件等)的编码,还有一些常用非汉字符号。

      GB18030-2005的主要特点是在GB18030-2000基础上增加了“CJK(中日韩)统一汉字扩充B”的汉字。增加了42711个汉字和多种我国少数民族文字的编码(如藏、蒙古、傣、彝、朝鲜、维吾尔文等)。加上前面GB18030-2000的内容,一共收录了70244个汉字。

  • 各个标准比对:

    GB2312、GBK及GB18030是汉字的国家标准编码,新版向下兼容旧版,各个标准简要说明见下表,目前比较流行的是GBK编码,因为每个汉字只占用2个字节,而且它编码的字符已经能满足大部分的需求,但国家要求一些产品必须支持GB18030标准。

    想要更深刻的了解访问:导航菜单 - 千千秀字

 

 

 

  • Unicode

    关于Unicode:Unicode – The World Standard for Text and Emoji

    由于各个国家或地区都根据使用自己的文字系统制定标准,同一个编码在不同的标准里表示不一样的字符,各个标准互不兼容,而又没有一个标准能够囊括所有的字符,即无法用一个标准表达所有字符。国际标准化组织(ISO)为解决这一问题,它舍弃了地区性的方案,重新给全球上所有文化使用的字母和符号进行编号,对每个字符指定一个唯一的编号(ASCII中原有的字符编号不变),这些字符的号码从0x000000到0x10FFFF,该编号集被称为Universal Multiple-Octet Coded Character Set,简称UCS,也被称为Unicode。最新版的Unicode标准还包含了表情符号(聊天软件中的部分emoji表情)。

    Unicode字符集只是对字符进行编号,但具体怎么对每个字符进行编码,Unicode并没指定,因此也衍生出了如下几种unicode编码方案(Unicode Transformation Format)。

    • UTF-32

      编码时,它直接对Unicode字符集里的每个字符都用4字节来表示,转换方式很简单,直接将字符对应的编号数字转换为4字节的二进制数。由于UTF-32把每个字符都用要4字节来存储,因此UTF-32不兼容ASCII编码,也就是说ASCII编码的文件用UTF-32标准来打开会成为乱码。

      缺点是浪费存储空间,大量常用字符的编号只需要2个字节就能表示。其次,在存储的时候需要指定字节顺序,是高位字节存储在前(大端格式),还是低位字节存储在前(小端格式)。

      字符GBK编码Unicode编号UTF-32编码
      A0x410x0000 0041大端格式0x0000 0041
      0xB0A10x0000 554A大端格式0x0000 554A
    • UTF-16

      针对UTF-32的缺点,人们改进出了UTF-16的编码方式,它采用2字节或4字节的变长编码方式(UTF-32定长为4字节)。对Unicode字符编号在0到65535的统一用2个字节来表示,将每个字符的编号转换为2字节的二进制数,即从0x0000到0xFFFF

      由于Unicode字符集在0xD800-0xDBFF这个区间是没有表示任何字符的,所以UTF-16就利用这段空间,对Unicode中编号超出0xFFFF的字符,利用它们的编号做某种运算与该空间建立映射关系,从而利用该空间表示4字节扩展。

      UTF-16解码时,按两个字节去读取,如果这两个字节不在0xD800到0xDFFF范围内,那就是双字节编码的字符,以双字节进行解析,找到对应编号的字符。如果这两个字节在0xD800到 0xDFFF之间,那它就是四字节编码的字符,以四字节进行解析,找到对应编号的字符。

      UTF-16编码的优点是相对UTF-32节约了存储空间,缺点是仍不兼容ASCII码,仍有大小端格式问题。

      字符GB18030编码Unicode编号UTF-16编码
      A0x410x0000 0041大端格式0x0041
      0xB0A10x0000 554A大端格式0x554A
      𧗌0x9735 F8320x0002 75CC大端格式0xD85D DDCC
    • UTF-8

      <meta http-equiv=Content-Type content="text/html;charset=utf-8">

目前在大部分网页使用UTF-8。

UTF-8也是一种变长的编码方式,它的编码有1、2、3、4字节长度的方式,每个Unicode字符根据自己的编号范围去进行对应的编码。它的编码符合以下规律:

• 对于UTF-8单字节的编码,该字节的第1位设为0(从左边数起第1位,即最高位),剩余的位用来写入字符的Unicode编号。即对于Unicode编号从0x0000 0000-0x0000 007F的字符,UTF-8编码只需要1个字节,因为这个范围Unicode编号的字符与ASCII码完全相同,所以UTF-8兼容了ASCII码表

• 对于UTF-8使用N个字节的编码(N>1),第一个字节的前N位设为1,第N+1位设为0,后面字节的前两位都设为10,这N个字节的其余空位填充该字符的Unicode编号,高位用0补足。

Unicode(16进制)UTF-8(2进制)
编号范围第一字节第二字节第三字节第四字节第五字节
00000000-0000007F0xxxxxxx
00000080-000007FF110xxxxx10xxxxxx
00000800-0000FFFF1110xxxx10xxxxxx10xxxxxx
00010000-0010FFFF11110xxx10xxxxxx10xxxxxx10xxxxxx
111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxx

UTF-8解码的时候以字节为单位去看,如果第一个字节的bit位以0开头,那就是ASCII字符,以单字节进行解析。如果第一个字节的数据位以“110”开头,就按双字节进行解析,3、4字节的解析方法类似。(多少个1则多少个字节,后面全部使用10)

UTF-8的优点是兼容了ASCII码,节约空间,且没有字节顺序的问题,它直接根据第1个字节前面数据位中连续的1个数决定后面有多少个字节。不过使用UTF-8编码汉字平均需要3个字节,比GBK编码要多一个字节。

  • BOM

    由于有太多种编码方式,有些人就想到在文档最前面加标记,一种标记对应一种编码方式,这些标记就称为BOM,它们位于文本文件的开头:

    BOM标记表示的编码
    0xEF 0xBB 0xBFUTF-8
    0xFF 0xFEUTF-16 小端格式
    0xFE 0xFFUTF-16 大端格式
    0xFF 0xFE 0x00 0x00UTF-32 小端格式
    0x00 0x00 0xFE 0xFFUTF-32 大端格式

    由于带BOM的设计很多规范不兼容,不能跨平台,所以这种带BOM的设计没有流行起来。Linux系统下默认不带BOM。

二、字模

在上面我们只知道计算机可以处理、存储字符,但是计算机处理完字符后以编码形式输出则难以分辨,所以计算机必须把字符编码转化成对应的字符图形人类才能正常识别,因此我们要给计算机提供字符的图形数据,这些数据就是字模。多个字模数据组成的文件也被称为字库。计算机显示字符时,根据字符编码与字模数据的映射关系找到它相应的字模数据,液晶屏根据字模数据显示该字符。

  • 字模的构成

    字模其实是一个图形数据,由一副单色的图片组成,字模实质是一个个像素点的数据,把字模定义成为方块形的像素点阵,且每个像素点只有0和1两种状态。

 

 生成的字模数据以C语言的数组形式,以两个字节表示一行像素,如图所示:

 使用字模生成器将生成的16*16字模打印出来:

void dis_charater(void)
{
	uint8_t i,j;
	
	for(i=0;i<16;i++)
	{
		//扫描一行内的第一个字节数据
		for(j=0;j<8;j++)
		{
			if(((charater[2*i]<<j)&0x80))
			{
				printf("*");
			}
			else
			{
				printf(" ")
			}
		}
		
		//扫描一行内的第二个字节数据
		for(j=0;j<8;j++)
		{
			if((charater[2*i+1]<<j)&0x80)
			{
				printf("*");
			}
			else
			{
				printf(" ")
			}
		}
		printf("\n")
	}
}
  • 字模寻址公式

    Addr = (((CodeH-0xA0-1) * 94) +(CodeL-0xA0-1)) * 24 * 24/8

其中CodeH和CodeL分别是GB2312编码的第一字节和第二字节;94是指一个区中有94个位(即94个字符)。公式的实质是根据字符的GB2312编码,求出区位码,然后区位码乘以每个字符占据的字节数,求出地址偏移。

三、显示汉字

其实显示汉字就是将要显示的像素点,使用LTDC+DMA2D显示你需要的颜色,背景色显示为其他颜色,这样就可以显示了,我们将上面的字模显示进行如下的修改:

下面以显示当字为例,大小为24*24:

uint8_t charater[] =
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x0C,0x18,0x70,0x06,0x18,0x60,0x03,0x18,0x80,0x01,0x19,0x00,
0x00,0x1A,0x00,0x00,0x18,0x08,0x1F,0xFF,0xFC,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x0F,0xFF,0xF8,
0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x1F,0xFF,0xF8,0x00,0x00,0x18,0x00,0x00,0x00};

 我们只需要把上面显示“*”的部分换为红色,背景的空格显示为白色即可:

//x和y为汉字显示的起始坐标,charater为汉字的gb2312编码
void dis_charater(uint16_t x, uint16_t y, uint16_t charater) 
{
  uint8_t i,j ;
  
  uint32_t *p = (uint32_t *)(LCD_LAYER2_START_ADDR + x*4+(y*LCD_WIDTH*4) );
  
  for(i=0;i<24;i++)  //扫描N行
  {
    //扫描一行内的第一个字节数据
    for(j=0;j<8;j++)  //扫描各个像素点
    {
      if(((charater[3*i] << j )&0x80) ) //如果运算结果为非0值,表示像素点有笔迹,为0表示空白
        //printf("*");    //笔迹像素点,输出*号
        *p = 0xFFFF0000;  //笔迹像素点,输出红色
      
      else  
        //printf(" ");    //空白像素点,输出空格
        *p = 0xFF000000;  //笔迹像素点,输出黑色
      
      p++;  //指向下一个像素点的显存空间
    }
  
    //扫描一行内的第二个字节数据
    for(j=0;j<8;j++)  //扫描各个像素点
    {
      if(((charater[3*i+1] << j )&0x80) ) //如果运算结果为非0值,表示像素点有笔迹,为0表示空白
        //printf("*");    //笔迹像素点,输出*号
        *p = 0xFFFF0000;  //笔迹像素点,输出红色
      else  
        //printf(" ");    //空白像素点,输出空格
         *p = 0xFF000000;  //笔迹像素点,输出黑色
      
       p++; //指向下一个像素点的显存空间
      }   

    //扫描一行内的第三个字节数据
    for(j=0;j<8;j++)  //扫描各个像素点
    {
      if(((charater[3*i+2] << j )&0x80) ) //如果运算结果为非0值,表示像素点有笔迹,为0表示空白
        //printf("*");    //笔迹像素点,输出*号
        *p = 0xFFFF0000;  //笔迹像素点,输出红色
      else  
        //printf(" ");    //空白像素点,输出空格
         *p = 0xFF000000;  //笔迹像素点,输出黑色
      
       p++; //指向下一个像素点的显存空间
      
      }      
      //一共有800列,显示完字的一行后换行。
      p += (LCD_WIDTH-24); //指向下一行字模矩阵所在的显存空间
    //printf("\n");
  }
  
}

每一次需要去取模,存入数组,是一件很麻烦的事情,所以我们需要将存有字模的表存入我们的Flash或者SD卡中进行读取:

 之后我们就可以使用指针去访问,我们先实现取字,这个过程需要SPI-Flash

 /**
  * @brief  读取FLASH数据
  * @param 	pBuffer,存储读出数据的指针
  * @param   ReadAddr,读取地址
  * @param   NumByteToRead,读取数据长度
  * @retval 无
  */
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
  /* 选择FLASH: CS低电平 */
  SPI_FLASH_CS_LOW();

  /* 发送 读 指令 */
  SPI_FLASH_SendByte(W25X_ReadData);

  /* 发送 读 地址高位 */
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  /* 发送 读 地址中位 */
  SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
  /* 发送 读 地址低位 */
  SPI_FLASH_SendByte(ReadAddr & 0xFF);
  
	/* 读取数据 */
  while (NumByteToRead--)
  {
    /* 读取一个字节*/
    *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);
    /* 指向下一个字节缓冲区 */
    pBuffer++;
  }

  /* 停止信号 FLASH: CS 高电平 */
  SPI_FLASH_CS_HIGH();
}

下面实现一个通过汉字的编码来显示汉字:

//首先定义一个24*24/3大小的数组,用来存放大小为24*24的字模
uint8_t charater_data_buffer[72]={0};

取字模函数:

void GetCharaterData(uint8_t *pBuffer,uint16_t charater)
{
  uint8_t CodeH,CodeL;  
  uint32_t Addr;
  
  CodeH = (charater&0xFF00)>>8;
  CodeL = (charater&0x00FF);
  
  //字模数据在文件中的偏移地址
  Addr = (((CodeH-0xA0-1)*94) +(CodeL-0xA0-1))*24*24/8 ;
  
  //加上文件在SPI FLASH的地址偏移
  Addr += 1360*4096; 
  
  //从字模的spi flash存储空间中读出字模数据
  SPI_FLASH_BufferRead(pBuffer,Addr,24*24/8);

}

显示字模所对应的汉字:

dis_charater(100,100,0xBABA);

void dis_charater(uint16_t x, uint16_t y, uint16_t charater) 
{
  uint8_t i,j ;
  
  
  GetCharaterData(charater_data_buffer,charater);
  
  //显示汉字的坐标修改
  uint32_t *p = (uint32_t *)(LCD_LAYER2_START_ADDR + x*4+(y*LCD_WIDTH*4) );
  
  for(i=0;i<24;i++)  //扫描N行
  {
    //扫描一行内的第一个字节数据
    for(j=0;j<8;j++)  //扫描各个像素点
    {
      if(((charater_data_buffer[3*i] << j )&0x80) ) //如果运算结果为非0值,表示像素点有笔迹,为0表示空白
        //printf("*");    //笔迹像素点,输出*号
        *p = 0xFFFF0000;  //笔迹像素点,输出红色
      
      else  
        //printf(" ");    //空白像素点,输出空格
        *p = 0xFF000000;  //笔迹像素点,输出黑色
      
      p++;  //指向下一个像素点的显存空间
    }
  
    //扫描一行内的第二个字节数据
    for(j=0;j<8;j++)  //扫描各个像素点
    {
      if(((charater_data_buffer[3*i+1] << j )&0x80) ) //如果运算结果为非0值,表示像素点有笔迹,为0表示空白
        //printf("*");    //笔迹像素点,输出*号
        *p = 0xFFFF0000;  //笔迹像素点,输出红色
      else  
        //printf(" ");    //空白像素点,输出空格
         *p = 0xFF000000;  //笔迹像素点,输出黑色
      
       p++; //指向下一个像素点的显存空间
      }   

    //扫描一行内的第三个字节数据
    for(j=0;j<8;j++)  //扫描各个像素点
    {
      if(((charater_data_buffer[3*i+2] << j )&0x80) ) //如果运算结果为非0值,表示像素点有笔迹,为0表示空白
        //printf("*");    //笔迹像素点,输出*号
        *p = 0xFFFF0000;  //笔迹像素点,输出红色
      else  
        //printf(" ");    //空白像素点,输出空格
         *p = 0xFF000000;  //笔迹像素点,输出黑色
      
       p++; //指向下一个像素点的显存空间
      
      }      
    
      p += (LCD_WIDTH-24); //指向下一行字模矩阵所在的显存空间
    //printf("\n");
  }
  
}

其实这么显示也是很麻烦的,每一次都要去查找文字的编码,我们可以实现一个输入字符串,然后显示:

dis_string(100,200,"汉字演示"); 

void dis_string(uint16_t x, uint16_t y,char *pString) 
{
  uint16_t charater;
  uint16_t x_dis = x;
  
  while(*pString != '\0')
  {
    charater = *(uint16_t *)pString ;
    charater = ((charater&0x00FF) <<8) | ((charater&0xFF00) >>8);

    dis_charater(x_dis,y,charater);
    
    //为了防止汉字重叠,坐标偏移
    x_dis += 24;
    
    pString += 2;
  }
}

以上汉字显示都是基于GB2312的文件格式。

四、看看STM32标准库显示英文

这是显示字符的函数,构建的非常的巧妙:

void LCD_DrawChar(uint16_t Xpos, uint16_t Ypos, const uint16_t *c)
{
 uint32_t index = 0, counter = 0, xpos =0;
 uint32_t  Xaddress = 0;

 xpos = Xpos*LCD_PIXEL_WIDTH*2;
 Xaddress += Ypos;

 for(index = 0; index < LCD_Currentfonts->Height; index++)
 {

   for(counter = 0; counter < LCD_Currentfonts->Width; counter++)
   {
	 //显示背景层
     if((((c[index] & ((0x80 << ((LCD_Currentfonts->Width / 12 ) * 8 ) ) >> counter)) == 0x00) &&(LCD_Currentfonts->Width <= 12))||
       (((c[index] & (0x1 << counter)) == 0x00)&&(LCD_Currentfonts->Width > 12 )))
     {
         /* Write data value to all SDRAM memory */
        *(__IO uint16_t*) (CurrentFrameBuffer + (2*Xaddress) + xpos) = CurrentBackColor;
     }
     //显示要显示的字体
     else
     {
         /* Write data value to all SDRAM memory */
        *(__IO uint16_t*) (CurrentFrameBuffer + (2*Xaddress) + xpos) = CurrentTextColor;
     }
     Xaddress++;
   }
     //显示一行后偏移,其中LCD_Currentfonts取决你的字体格式(位于sFONT结构体)
     Xaddress += (LCD_PIXEL_WIDTH - LCD_Currentfonts->Width);
 }
}

 sFONT结构体:

typedef struct _tFont
{    
  const uint16_t *table;    /*指向字模数据的指针*/
  uint16_t Width;           /*字模的像素宽度*/
  uint16_t Height;          /*字模的像素高度*/  
} sFONT;

/*这些可选的字体格式定义在fonts.c文件*/
extern sFONT Font16x24;
extern sFONT Font12x12;
extern sFONT Font8x12;
extern sFONT Font8x8;

具体初始化如下:

void LCD_SetFont(sFONT *fonts)
{
 LCD_Currentfonts = fonts;
}

/*选择字体*/
LCD_SetFont(&Font16x24);

如何显示英文?使用ASCII表,他跟显示汉字还是有很大区别的,我们也可以取字母的模,然后使用汉字显示,但是正常不这么做。

void LCD_DisplayChar(uint16_t Line, uint16_t Column, uint8_t Ascii)
{
 Ascii -= 32;

 LCD_DrawChar(Line, Column, &LCD_Currentfonts->table[Ascii * LCD_Currentfonts->Height]);
}

它直接使用查表得出来的数组进行画点:

void LCD_DrawChar(uint16_t Xpos, uint16_t Ypos, const uint16_t *c)
{
 uint32_t index = 0, counter = 0, xpos =0;
 uint32_t  Xaddress = 0;

 xpos = Xpos*LCD_PIXEL_WIDTH*2;
 Xaddress += Ypos;

 for(index = 0; index < LCD_Currentfonts->Height; index++)
 {

   for(counter = 0; counter < LCD_Currentfonts->Width; counter++)
   {

     if((((c[index] & ((0x80 << ((LCD_Currentfonts->Width / 12 ) * 8 ) ) >> counter)) == 0x00) &&(LCD_Currentfonts->Width <= 12))||
       (((c[index] & (0x1 << counter)) == 0x00)&&(LCD_Currentfonts->Width > 12 )))
     {
         /* Write data value to all SDRAM memory */
        *(__IO uint16_t*) (CurrentFrameBuffer + (2*Xaddress) + xpos) = CurrentBackColor;
     }
     else
     {
         /* Write data value to all SDRAM memory */
        *(__IO uint16_t*) (CurrentFrameBuffer + (2*Xaddress) + xpos) = CurrentTextColor;
     }
     Xaddress++;
   }
     Xaddress += (LCD_PIXEL_WIDTH - LCD_Currentfonts->Width);
 }
}

最后调用以上函数使用这个函数:

void LCD_DisplayStringLine(uint16_t Line, uint8_t *ptr)
{
 uint16_t refcolumn = 0;
 /* Send the string character by character on lCD */
 //防止显示超出显示界限
 while ((refcolumn < LCD_PIXEL_WIDTH) && ((*ptr != 0) & (((refcolumn + LCD_Currentfonts->Width) & 0xFFFF) >= LCD_Currentfonts->Width)))
 {
   /* Display one character on LCD */
   LCD_DisplayChar(Line, refcolumn, *ptr);
   /* Decrement the column position by width */
   refcolumn += LCD_Currentfonts->Width;
   /* Point on the next character */
   ptr++;
 }
}

对于它的使用也是很有趣的,上面那个函数调用如下:

LCD_DisplayStringLine(LINE(2),(uint8_t* )"Image resolution:800x480 px");

其中的line函数可以自动算出需要多少列来显示:

#define LINE(x) ((x) * (((sFONT *)LCD_GetFont())->Height))

sFONT *LCD_GetFont(void)
{
 return LCD_Currentfonts;
}

实在是太巧妙太令人喜欢了!!!这样就不需要自己调显示的高度了。

下面这个函数是既可以显示中文又可以显示英文:

/**
 * @brief  在显示器上显示中英文字符串,超出液晶宽度时会自动换行。
					 中英文混显示时,请把英文字体设置为Font16x24格式
 * @param  Line :行(也可理解为y坐标)
 * @param  Column :列(也可理解为x坐标)
 * @param  pStr :要显示的字符串的首地址
 * @retval 无
 */
void LCD_DispString_EN_CH( uint16_t Line, uint16_t Column, const uint8_t * pStr )
{
	uint16_t usCh;
	
	
	while( * pStr != '\0' )
	{
		if ( * pStr <= 126 )	           	//英文字符
		{
	
			/*自动换行*/
			if ( ( Column + LCD_Currentfonts->Width ) > LCD_PIXEL_WIDTH )
			{
				Column = 0;
				Line += LCD_Currentfonts->Height;
			}
			
			if ( ( Line + LCD_Currentfonts->Height ) > LCD_PIXEL_HEIGHT )
			{
				Column = 0;
				Line = 0;
			}			
					
			LCD_DisplayChar(Line,Column,*pStr);
			
			Column += LCD_Currentfonts->Width;
		
		  pStr ++;

		}
		
		else	                            //汉字字符
		{
			if ( ( Column + macWIDTH_CH_CHAR ) > LCD_PIXEL_WIDTH )
			{
				Column = 0;
				Line += macHEIGHT_CH_CHAR;
			}
			
			if ( ( Line + macHEIGHT_CH_CHAR ) > LCD_PIXEL_HEIGHT )
			{
				Column = 0;
				Line = 0;
			}	
			
			/*一个汉字两字节*/
			usCh = * ( uint16_t * ) pStr;			
			usCh = ( usCh << 8 ) + ( usCh >> 8 );		

			LCD_DispChar_CH (Line,Column, usCh);
			
			Column += macWIDTH_CH_CHAR;
			
			pStr += 2;           //一个汉字两个字节 
		
    }
		
  }
	
	
} 

 显示中文字符:

/**
 * @brief  在显示器上显示一个中文字符
 * @param  usX :在特定扫描方向下字符的起始X坐标
 * @param  usY :在特定扫描方向下字符的起始Y坐标
 * @param  usChar :要显示的中文字符(国标码)
 * @retval 无
 */ 
void LCD_DispChar_CH ( uint16_t usX, uint16_t usY, uint16_t usChar)
{
	uint8_t ucPage, ucColumn;
    //定义好数组了
	uint8_t ucBuffer [ 24*24/8 ];	

    uint32_t usTemp; 	
	
	
	uint32_t  xpos =0;
    uint32_t  Xaddress = 0;
  
	/*xpos表示当前行的显存偏移位置*/
    xpos = usX*LCD_PIXEL_WIDTH*3;
	
	/*Xaddress表示像素点*/
    Xaddress += usY;
	   
    macGetGBKCode ( ucBuffer, usChar );	//取字模数据
	
	/*ucPage表示当前行数*/
	for ( ucPage = 0; ucPage < macHEIGHT_CH_CHAR; ucPage ++ )
	{
    /* 取出3个字节的数据,在lcd上即是一个汉字的一行 */
     	//其实相当于我们上面的3个for循环,只不过它把它集成了在一起
		usTemp = ucBuffer [ ucPage * 3 ];
		usTemp = ( usTemp << 8 );
		usTemp |= ucBuffer [ ucPage * 3 + 1 ];
		usTemp = ( usTemp << 8 );
		usTemp |= ucBuffer [ ucPage * 3 + 2];
	
		
		for ( ucColumn = 0; ucColumn < macWIDTH_CH_CHAR; ucColumn ++ ) 
		{			
			if ( usTemp & ( 0x01 << 23 ) )  //高位在前 				
			{
				//字体色
			  *(__IO uint16_t*)(CurrentFrameBuffer + (3*Xaddress) + xpos) = (0x00FFFF & CurrentTextColor);        //GB
              *(__IO uint8_t*)(CurrentFrameBuffer + (3*Xaddress) + xpos+2) = (0xFF0000 & CurrentTextColor) >> 16; //R

			}				
			else	
			{
				//背景色
				*(__IO uint16_t*)(CurrentFrameBuffer + (3*Xaddress) + xpos) = (0x00FFFF & CurrentBackColor);        //GB
                *(__IO uint8_t*)(CurrentFrameBuffer + (3*Xaddress) + xpos+2) = (0xFF0000 & CurrentBackColor) >> 16; //R

			}	
			/*指向当前行的下一个点*/	
			Xaddress++;			
			usTemp <<= 1;
			
		}
		/*显示完一行*/
		/*指向字符显示矩阵下一行的第一个像素点*/
		Xaddress += (LCD_PIXEL_WIDTH - macWIDTH_CH_CHAR);
		
	}
}

//换字体大小时需要更改
#define      macWIDTH_CH_CHAR		                24	    //中文字符宽度 
#define      macHEIGHT_CH_CHAR		                24		  //中文字符高度 

使用SD卡会比Flash慢很多,因为使用了Fatfs文件系统的处理。

如果要显示任意大小的文字:

/**
 * @brief  利用缩放后的字模显示字符串
 * @param  Xpos :字符显示位置x
 * @param  Ypos :字符显示位置y
 * @param  Font_width :字符宽度,英文字符在此基础上/2。注意为偶数
 * @param  Font_Heig:字符高度,注意为偶数
 * @param  c :要显示的字符串
 * @param  DrawModel :是否反色显示 
 * @retval 无
 */
void LCD_DisplayStringLineEx(uint16_t x, 				//字符显示位置x
							 uint16_t y, 				//字符显示位置y
							 uint16_t Font_width,		//要显示的字体宽度,英文字符在此基础上/2。注意为偶数
							 uint16_t Font_Heig,		//要显示的字体高度,注意为偶数
							 uint8_t *ptr,				//显示的字符内容
							 uint16_t DrawModel)  		//是否反色显示
{
	uint16_t refcolumn = x; //x坐标
	uint16_t Charwidth;
	uint8_t *psr;
	uint8_t Ascii;	//英文
	uint16_t usCh;  //中文
	uint8_t ucBuffer [ 24*24/8 ];	
	
	while ((refcolumn < LCD_PIXEL_WIDTH) && ((*ptr != 0) & (((refcolumn + LCD_Currentfonts->Width) & 0xFFFF) >= LCD_Currentfonts->Width)))
	{
		if(*ptr > 0x80) //如果是中文
		{
			Charwidth = Font_width;
			usCh = * ( uint16_t * ) ptr;				
			usCh = ( usCh << 8 ) + ( usCh >> 8 );
			macGetGBKCode ( ucBuffer, usCh );	//取字模数据
			//缩放字模数据
			LCD_zoomChar(24,24,Charwidth,Font_Heig,(uint8_t *)&ucBuffer,psr,1); 
			//显示单个字符
			LCD_DrawChar_Ex(y,refcolumn,Charwidth,Font_Heig,(uint8_t*)&zoomBuff,DrawModel);
			refcolumn+=Charwidth;
			ptr+=2;
		}
		else
		{
				Charwidth = Font_width / 2;
				Ascii = *ptr - 32;
				//缩放字模数据
				LCD_zoomChar(16,24,Charwidth,Font_Heig,(uint8_t *)&LCD_Currentfonts->table[Ascii * LCD_Currentfonts->Height],psr,0);
			  //显示单个字符
				LCD_DrawChar_Ex(y,refcolumn,Charwidth,Font_Heig,(uint8_t*)&zoomBuff,DrawModel);
				refcolumn+=Charwidth;
				ptr++;
		}
	}
}

 它的放大缩小有点类似于图像处理的间隔放大、缩小:

/**
 * @brief  缩放字模,缩放后的字模由1个像素点由8个数据位来表示
										0x01表示笔迹,0x00表示空白区
 * @param  in_width :原始字符宽度
 * @param  in_heig :原始字符高度
 * @param  out_width :缩放后的字符宽度
 * @param  out_heig:缩放后的字符高度
 * @param  in_ptr :字库输入指针	注意:1pixel 1bit
 * @param  out_ptr :缩放后的字符输出指针 注意: 1pixel 8bit
 *		out_ptr实际上没有正常输出,改成了直接输出到全局指针zoomBuff中
 * @param  en_cn :0为英文,1为中文
 * @retval 无
 */
void LCD_zoomChar(uint16_t in_width,	//原始字符宽度
				  uint16_t in_heig,		//原始字符高度
				  uint16_t out_width,	//缩放后的字符宽度
				  uint16_t out_heig,	//缩放后的字符高度
				  uint8_t *in_ptr,	    //字库输入指针	注意:1pixel 1bit
				  uint8_t *out_ptr,     //缩放后的字符输出指针 注意: 1pixel 8bit
				  uint8_t en_cn)		//0为英文,1为中文	
{
	uint8_t *pts,*ots;
	//根据源字模及目标字模大小,设定运算比例因子,左移16是为了把浮点运算转成定点运算
	unsigned int xrIntFloat_16=(in_width<<16)/out_width+1; 
    unsigned int yrIntFloat_16=(in_heig<<16)/out_heig+1;
	
	unsigned int srcy_16=0;
	unsigned int y,x;
	uint8_t *pSrcLine;
	uint8_t tempBuff[1024] = {0};
	u32			uChar;
	u16			charBit = in_width / 8;
	u16			Bitdiff = 32 - in_width;
	
	//检查参数是否合法
	if(in_width >= 32) return;						//字库不允许超过32像素
	if(in_width * in_heig == 0) return;	
	if(in_width * in_heig >= 1024 ) return; 	    //限制输入最大 32*32
	
	if(out_width * out_heig == 0) return;	
	if(out_width * out_heig >= ZOOMMAXBUFF ) return; //限制最大缩放 128*128
	pts = (uint8_t*)&tempBuff;
	
	//为方便运算,字库的数据由1 pixel 1bit 映射到1pixel 8bit
	//0x01表示笔迹,0x00表示空白区
	if(en_cn == 0x00)//英文
	{
		//这里以16 * 24字库作为测试,其他大小的字库自行根据下列代码做下映射就可以
		//英文和中文字库上下边界不对,可在此次调整。需要注意tempBuff防止溢出
			pts+=in_width*4;
			for(y=0;y<in_heig;y++)	
			{
				uChar = *(u32 *)(in_ptr + y * charBit) >> Bitdiff;
				for(x=0;x<in_width;x++)
					{
						*pts++ = (uChar >> x) & 0x01;
					}
			}		
	}
	else //中文
	{
			for(y=0;y<in_heig;y++)	
			{
				/*源字模数据*/
				uChar = in_ptr [ y * 3 ];
				uChar = ( uChar << 8 );
				uChar |= in_ptr [ y * 3 + 1 ];
				uChar = ( uChar << 8 );
				uChar |= in_ptr [ y * 3 + 2];
				/*映射*/
				for(x=0;x<in_width;x++)
					{
						if(((uChar << x) & 0x800000) == 0x800000)
							*pts++ = 0x01;
						else
							*pts++ = 0x00;
					}
			}		
	}

	//zoom过程
	pts = (uint8_t*)&tempBuff;	//映射后的源数据指针
	ots = (uint8_t*)&zoomBuff;	//输出数据的指针
	for (y=0;y<out_heig;y++)	/*行遍历*/
    {
		unsigned int srcx_16=0;
        pSrcLine=pts+in_width*(srcy_16>>16);				
        for (x=0;x<out_width;x++) /*行内像素遍历*/
        {
            ots[x]=pSrcLine[srcx_16>>16]; //把源字模数据复制到目标指针中
            srcx_16+=xrIntFloat_16;			//按比例偏移源像素点
        }
        srcy_16+=yrIntFloat_16;				  //按比例偏移源像素点
        ots+=out_width;						
    }
	/*!!!缩放后的字模数据直接存储到全局指针zoomBuff里了*/
	out_ptr = (uint8_t*)&zoomBuff;	//out_ptr没有正确传出,后面调用直接改成了全局变量指针!
	
	/*实际中如果使用out_ptr不需要下面这一句!!!
		只是因为out_ptr没有使用,会导致warning。强迫症*/
	out_ptr++; 
}	

显示屏就学到这了叭。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郑烯烃快去学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值