目录
目录
2.2.4创建CurrentChfonts指针,指向这个外部变量
2.2.5ILI9341_DispGivenChar_CH显示字符
前面介绍了LCD的初始化过程,如何通过FSMC接口模拟8080接口,如何像ILI9341写入不同数据,如何完成ILI9341初始化,如何向LCD写入不同形状的图形。
接下来,我们需要进一步了解,如何像LCD写入中英文文字和图像。毕竟大部分应用场景中,写入文字和图像才是最有必要的。比如我们需要在屏幕上显示温湿度数据,或者我们需要显示番茄时钟的倒计时,或者说我们需要在屏幕上面显示到底实现了多少个番茄,都是需要采用屏幕进行中文显示,英文显示,还有图像显示的。
1. 显示英文
为了能够显示英文,我们至少需要准备好下面三件事情:
1. 创建一个表,根据表中的序号,从表格中显示英文26个小写字母和26个大写字母;
2. 把每个字母按照类似画点的方法,用屏幕上的小点来显示出来,这些小点构成一个数组;
3. 当需要显示字母或者字符串的时候,按图索骥,从表里找到对应的字母,然后按照数组方式,写到屏幕上即可。
思路有了,就可以接着动手干了。
1.1 从ASCII码表说起
计算机只能识别 0 和 1,文字也只能以 0 和 1 的形式在计算机里存储,但是人类识别的语言为中文或者英文,所以我们需 要对文字进行编码才能让计算机处理,编码的过程就是规定特定的 01 数字串来表示特定的 文字,最简单的字符编码例子是 ASCII 码。
ASCII表中,最简单的方式,会用65表示A,97表示a,48表示0,32表示空格,也就是' '。而ASCII表的一个规律是:空格之前的ASCII字码是不能显示的。
0~31的字符为非显示字符,屏幕不能显示。因此,我们只需要把ASCII字符减去空格对应的编码,就可以确定这个字符在ASCII显示表中的偏移量。
例如:我们需要显示字母A, A对应的编码为65, 空格符号对应的编码为32, 我们只需要计算A-' '=33,就可以得到A在子码表里面的偏移量就是33,我们找到第33行数组,把它写到屏幕里面就可以了。
1.2 创建单个字模
找到偏移量之后,我们需要进一步研究一下,如何把这个字母来恰当的显示出来,这个时候,就需要用到字模了。
例如,我们需要显示A这个字符,相当于在屏幕上,在特定点进行填充,最后形成A这个字符。我们用字模工具,来创建这个字符对应的编码。
这个是我们常用的字模软件,主要设置如下:
1.选择字体为Consolas字体;
2. 字宽16 字高 16;
3.选项中,选择逐行扫描;
最后选择生成字模,下面创建的数组,就是A这个字对应的字模,对应数组为:
{0x00,0x00,0x00,0x00,0x18,0x1C,0x34,0x24,0x26,0x62,0x7E,0x43,0xC1,0x00,0x00,0x00},/*"A",0*/
1.3 显示单个字符
前面已经用字模生成了A这个字符对应数组,接下来我们用一个函数把这个字符在LCD上面显示出来。
一个字符为8行16列,其中每一行为一个byte,总共16个byte构成了16列数据。
每一行中8个bit,当这个bit为1时,显示文本颜色,当这个bit为0时,显示背景颜色;
例如第五行中
这一行对应的数据为0x18,对应的二进制位0001 1000,因此这一行中,中间两个bit为1,写字体颜色,其他bit为0,写背景颜色。
这里fontLength为16,表示每个字模对应16个字节。byteCount为字节序号,也就是这个字符的行数。
里面的for循环完成一行内容的显示,外面 的for循环完成16行显示,最终完成字符A在LCD上面的输出。
1.4 从ASCII表中输出
前面我们把A这个字符对应的数组在屏幕上显示的过程讲解了一下。在实际工程中,我们预先把ASCII表对应的字模都创建好,在需要实际输出的时候,根据字母在ASCII表中的编号,找到对应字模,完成显示。
1.4.1定义一个结构体类型用来设置字体库
一种字体库必须包括:字符宽度,字符高度,以及对应ASCII表的地址信息。我们把这些信息放在一起,构建一个结构体。
备注:上面的typedef struct _tFont{} sFont 这种写法,可以用_tFont font1这种方式初始化,相当于定义了两种结构体类型,第一种是_tFont,第二种是sFONT,两种可以互换,_tFont就是sFONT,sFONT就是_tFont;
如果把_tFont去掉,写成typedef struct {}sFONT, 语法上面也是没有任何问题的。
1.4.2 用结构体类型,定义字体库结构体变量
Font8x16这个结构体变量中,第一个成员变量为字体库数组的地址,第二个成员变量为每个字符的宽度,第三个成员变量为每个字符的高度。
1.4.3 创建字体库数组
ASCII8X16_Table对应的就是字体库数组。
字体库数组从空格开始,按照顺序依次排列。
其中字符表中最后一个字符是~
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x71,0x4b,0x06,0x00,0x00,0x00,0x00,0x00,/*"~",0*/
当我们需要显示一个字符时,找到字符在字体库中的位置,在LCD上进行显示即可。
1.4.4 字体变量初始化
前面定义了Font8x16这个结构体变量,这个变量是一个全局变量。我们再定义一个静态结构体变量指针,LCD_Currentfonts,用这个指针指向这个全局变量。
这里我们复习一下结构体指针的知识点:
1. 当结构体作为参数传递时,常常使用结构体指针;
2. Currentfonts->Weight和Currentfonts->Height这种方式可以得到结构体指针指向的结构体的成员变量;
另外需要注意的时:CurrentTextColor和CurrentBackColor这两个变量都是静态全局变量。我们在代码中使用静态全局变量的好处是:
1.避免暴露变量:静态全局变量仅仅在文件内部可见,文件外部不可见。设置静态全局变量的值往往需要通过set函数来设置,查询变量往往通过get函数来设置。通过隐藏变量,降低代码文件之间的耦合性。
例如CurrentTextColor这个静态全局变量,只能通过SetTextColor函数进行设置,而不能直接在其他文件中,修改数值。这个变量在其他文件中是不可见的。
2. 上电完成初始化:静态全局变量放在静态数据区,所以代码开始自动完成初始化,而且初始值为0。
1.4.5调用方式
这样我们在main函数中,即可愉快的调用字符输出函数,实现单个字符输出。其中第一个参数和第二个参数为显示字符的x和y的位置。
字符输出的结果如上图所示,可以看到,每个字符都有8行16列的点阵组合而成。
1.5 英文字符串输出
单个字符输出在实际使用过程中还不太方便,这时就需要采用字符串输出了。把字符输出组合为字符串输出,需要以下几个步骤:
1.通过while(*pStr!='\0')这个循环,判断是否达到了字符串结尾;
2. 需要计算字符输出的位置:如果x方向大于屏幕宽度,y方向需要增加一个字符的距离,实现换行,如果y方向大于屏幕高度,则x y全部置零,从头开始写入;
3. 位置确定好之后,直接调用DispChar_en,完成输出。
在读代码前,我们需要自己先思考一下,如何实现这个功能,然后再和教程中的代码进行比较,这样可以提示用代码解决问题的能力。
上面代码中,首先通过while循环,检查*pStr!='\0',判断是否到了字符串结尾。然后检查x和y数值,判断是否需要换行或者翻页。最后在目标位置,显示字符串信息。
执行结果如图所示,可以看到,已经能够准确的实现按照字符,进行换行。
纸上得来终觉浅,绝知此事要躬行!
马上下载测试代码,看看效果。
1.内容概要:演示讲解如何用字模软件,创建字模,如何创建font.c文件,如何显示单个字符,如何显示字符串-Linux文档类资源-CSDN下载
2. 显示中文
英文字母简单,只有26个英文字母,构建的ASCII表也非常简单。因此,显示英文的方法一般都是把全部ASCII用字模创建出来,放到fonts.c中。
然而中文的显示就会复杂很多。中文的字符数量非常多,如果都放到fonts.c文件,会把MCU内存吃尽。因此,我们必须有一些新的计数,来支持中文的显示。
2.1 GBK编码和GB2312编码
显示英文的时候,我们采用ASCII编码方式,将每个字符减去空格' ',得到的就是这个字符在ASCII表格里面的偏移量。通过偏移量,从fonts.c中,将文件输出出来。
针对中文,我们一般采用GBK编码,GBK编码把ASCII和中文GB2321合并起来了。对于一个字符,我们首先判断其数值是否大于126,如果不大于126,为英文,采用英文显示。否则为中文,采用中文显示即可。
GB2312中,采用了类似于ASCII的方式,对中文进行了编码。
2.1.1 分区表示
GB2312编码对所收录字符进行了“分区”处理,共94个区,每区含有94个位,共8836个码位。这种表示方式也称为区位码。
01-09区收录除汉字外的682个字符。
10-15区为空白区,没有使用。
16-55区收录3755个一级汉字,按拼音排序。
56-87区收录3008个二级汉字,按部首/笔画排序。
88-94区为空白区,没有使用。
举例来说,“啊”字是GB2312编码中的第一个汉字,它位于16区的01位,所以它的区位码就是1601。
2.1.2 双字节编码
GB2312规定对收录的每个字符采用两个字节表示,第一个字节为“高字节”,对应94个区;第二个字节为“低字节”,对应94个位。所以它的区位码范围是:0101-9494。区号和位号分别加上0xA0就是GB2312编码。例如最后一个码位是9494,区号和位号分别转换成十六进制是5E5E,0x5E+0xA0=0xFE,所以该码位的GB2312编码是FEFE。
GB2312编码范围:A1A1-FEFE,其中汉字的编码范围为B0A1-F7FE,第一字节0xB0-0xF7(对应区号:16-87),第二个字节0xA1-0xFE(对应位号:01-94)。
2.1.3 解码过程
GB2312采用分区方式,对汉字进行编码,例如:16分区如上图所示,每一个区有94个字符。
我们现在收到一个编码B0FC,我们如何将它破解为对应汉字呢?
解码过程如下:
0xB0FC对应的高位为0XB0,我们将其减去0XA0,得到了16,说明这个字符在16区;
0XFC-0XA0=92 说明这个字符为16区92字符,对应的字符为“包”。
在STM32中文字库中,是从0开始计数的,也就是说16区前面有15个区,这15个区中有15X94=1401个字符,然后再加上91,也就是偏移91格,就可以得到对应的字符:“包”
2.2 单个字符显示
我们采用字模,创建一些字符,实现单个字符显示功能,下面是详细步骤
2.2.1创建16X16的字模数组
我们先创建这个字模数组,这个数组中只有两个字,每个字都包含了32个byte。
因为这里每2个byte也就是16个bit,构成了一行。
2.2.2创建ChFont16x16结构体变量
2.2.3外部声明ChFont16x16结构体变量
这样声明之后,就可以在其他地方使用这个变量了。
2.2.4创建CurrentChfonts指针,指向这个外部变量
前面吧这个变量声明成为extern,后面才能在9341.c中调用此文件,否则无法调用文件。
2.2.5ILI9341_DispGivenChar_CH显示字符
上面函数中,没有直接给出字符,而是给出了字符编号pos。因为我们自己创建的字符库里面,只有2个字符,所以只需要给出编号即可。如果是完整的gb2312的库,由于库尺寸太大,我们需要把字符放到spi-flash中。
因为我们只简单创建了2个字符,所以我们在参数中,直接指定字符串的序号即可。参数pos就是字符在字体库里面的序号信息。
由于每个字模包含了32个字节,所以fontLength=32;
采用OpenWindow完成开创后,程序按照顺序,依次把32个字节中的每个bit写入屏幕中,构成了这个中文字符。如果这个bit为1,就写入文本颜色,如果这个bit为0,就写入背景颜色。
创建好函数之后,不要忘记在头文件中进行声明。声明之后,main.c才可以调用。
2.2.6 main.c中调用函数验证功能
main.c中,调用这个函数。函数第一个参数为x坐标,第二个参数为y坐标,第三个参数为在字库中的顺序。
2.2.7 代码执行结果
代码执行结果如上图所示,我们可以正确显示处中文字符。
纸上得来终觉浅,绝知此事要躬行!
马上下载测试代码,看看效果。
https://download.csdn.net/download/book_drabit/86166922
3. 创建简单中文字库,并混合显示中英文
前面我们学习了如何创建英文,如何创建中文,如何创建英文字库,如何创建简单的中文字库。
但是这里有个问题,当我们需要显示复杂一些的中文时,我们不可能到字库中,按照顺序一个一个把它找出来,比如:我们不可能去找出来“可”在字库中排第10位,“志”在字库中排20位,而应该是:我们直接在调用参数这里设置好中文,然后就可以直接显示出来。
更进一步,我们需要实现中文和英文的混合显示,这样在后面的工程代码中才能够良好使用它。
3.1 显示中文字符串
在这部分,我们修改字库定义,使得我们可以通过查找的方法,从字库中找到对应的字符。
3.1.1重新定义字库结构体
我们前面的字库结构体类型是按照上面定义的,其中Table放对应字库的地址,这里需要修改。因为这里没有地方放置中文编码,我们只能按照顺序来定位中文,不能按照字符来查找。
中文字库结构体类型修改为上面定义的形式;
两者的区别是:
第一种定义中,Table为一个数组,这个数组中,包含了全部英文字体数组,但是不具备中文编码定义
第二种定义中,ChMini这个结构体类型,仅仅包含了一个汉字的数组;但是它还包含了两个Byte,用来存放对应中文编码。名字中增加了一个mini,表示它仅仅是我们自己定义的一个mini字库,而不是按照GKB定义的全体字库。
3.1.2 在fonts.c中,定义字库变量
前面定义结构体类型的目的,是为了定义字库变量;
上面定义的ChMini16x16[]这个数组变量中,每一行均为一个汉字对应的数组,例如:第一行前面32byte为“可”对应的编码,最后2个byte为“可”对应的编码。注意:fonts.c这里需要采用gb2312编码方式,这样才能够查找识别。
最后一行为自行定义的一个X,字符在这个数组中查找不到后,显示为X。
同样,不要忘记在fonts.h中,对变量进行声明。
3.1.3 增加字库长度宏定义
为了更加方便的了解字库中数字的数量,我们增加了宏定义CH_MINI_SIZE。
由于一个字库占据了33个byte,再加上结尾处有一个X作为替换字符,所以整个字库的尺寸为:
34*(4+1)
所以我们字库变量更新为上面的定义。
对应的变量声明也做了更新。
在C中,我们尽量避免直接采用数字,而是尽量采用宏定义的方式。直接采用数字不方便后面进行修改,采用宏定义的方式,后面修改起来非常方便。
3.1.4 在字库中,查找对应的字符
当我们采用GKB全字库时,可以根据编码顺序进行查找。例如“包”对应的编码是B0FC,我们把前后两个字节都减去A0,得到1692,我们到字库中,把16区第92个编码取出来,显示在屏幕上面即可。
但是对于mini字库,这种方式就不再有效,因为很多字在mini字库中是没有的。所以我们只能用查找法,一个一个查找,找到对应的数字。
上面用到的while+break方法,也是C中常用的编程技巧,我们可以把它叫做“折断法”。也就是当找到了目标数据后,我们通过break当前的循环,来获得目标数据的位置。
上面代码中第二个需要注意的点是:用两个byte来表示中文。对于每个中文,都可以分为byte[0]和byte[1];通过比对这个byte[0]和byte[1],可以判断字符是否能够对应;
第三个需要注意的点是:指针数组转换。DispMiniChar_CH这个函数的形参是char *str,但是我们实际使用时,是用str[0]和str[1]来获取对应参数。用数组来获取指针指向的数值,这也是非常常用的编程方法。
3.1.5 把两个8bit组合成为一个16bit数
ChMini16x16[index].F16x16 [ rowCount * 2 ]这个代码有些难以理解,我们需要将它分解一下,就可以一目了然了:
ChMini16x16[index]:是字库中第n个字符,比如:“可”对应了ChMini16x16[0], “志”对应了ChMini16x16[1];
ChMini16x16[index].F16x16是获取这个字符对应的结构体,这个字符结构体包含了两个成员变量,. F16x16为字库信息;.Byte[0]和.Byte[1]为对应的汉字编码;
[ rowCount * 2 ]为F16x16字库信息中编码顺序,用来在屏幕上面显示一行;
上面的复杂代码的解析方法,叫做“代码分解法”,是我们面临复杂代码时,最佳方法。
usTemp = ChMini16x16[index].F16x16 [ rowCount * 2 ];
usTemp从 ChMini16x16[index].F16x16 [ rowCount * 2 ]中获取第一个byte;
usTemp = ( usTemp << 8 );
然后把这个byte左移;
usTemp |= ChMini16x16[index].F16x16[ rowCount * 2 + 1 ];
然后再去除第二个byte,通过相或,得到最后的2个byte组成的数据。
这样的操作叫做“位操作组合法”,通过位操作组合,我们可以把多个byte组成int,或者把一个int中的多个byte单独分离出来。这些都是我们编程过程中,需要熟练掌握的技巧。
3.1.5 调用代码完成显示
最后调用代码,完成功能的显示。
显示结果如下:
上面图中信这个字有点丑,主要原因时采用了微软雅黑字体的原因。
在微软雅黑字体中这个信就是长成这个样子的。
3.2 混合显示中英文
为了混合显示中英文,我们需要对字符编码进行判断。当字符编码小于127时,用英文显示,否则用中文显示。
3.2.1显示英文部分
当*pStr<=126时,采用英文显示。注意这里参数为*pStr,因此传递的是数值,而不是地址。
完成显示后pStr++,来指向后面一个字符。
备注:++i和i++的小区别,你还记得吗?事实上,i++和++i两个语句执行完毕后,i的数值都会加一。两者唯一的区别是: (++i)这个表达式的值为i+1; (i++)这个表达式的值为i;
3.2.2显示中文部分
如果字符数值大于126则需要调用中文显示。
注意,中文显示函数传递参数为pStr也就是指针,而不是数值,因为中文字符没有排序,因此用指针传递数值更加方便。
另外需要注意的是: pStr+=2,因为每个中文字符带有两个byte。
下载代码,查看一下执行结果吧:
https://download.csdn.net/download/book_drabit/86212085
执行结果如下图:
4. 显示图片
接下来详细讲解制作图片和显示图片的过程。对应的工程源码也将在下面提供。
4.1显示单色图片
单色图片显示不仅仅可以用于LCD,也可以用于单色OLED,墨水屏等单色显示的设备中。
4.1.1制作单色图片
上面这个emoji图片。接下来我们把它做成单色显示的文件。
我们接着用工具把图片二值化,也就是将其转为黑白颜色图片,这样方便LCD进行显示。这里需要用到PhotoShop这个工具进行处理
选择 图像-> 模式->灰度
扔掉颜色信息,这时颜色变成黑白色
再选择 图像-> 调整 ->阈值
调整一个合适的阈值。
在 图像 -> 图像大小 这里,重新设置图像大小。
把图像大小调整为232X232,这样正好是8的倍数。因为我们在写屏幕的时候,是按照8bit分步写入的。
再用字模软件打开,其他参数不变,把每行点阵修改为29,因为232/8=29,这样正好一行byte对应一行图像。
选择 生成字模 ,再把生成的字模赋值到fonts.c中。
生成的字模文件中,带有很多大括号(232X2=464个),可以在vscode中,选择->列选择模式, 这样可以很快地删除这些大括号。
图片字模创建出来的数组如上,上面仅仅显示了部分内容,详细见代码中的数组。
4.1.2显示单色图片
1. 入参为图片的起始点坐标和结束点坐标,用这个坐标进行开窗。入参还包括*buf这个指针,用来指示图片对应参数;
2. p=*buf++;这里需要理解i++和++i的区别,p=*buf++这种方式,p从*buf开始,因为buf++作为表达式,其数值为buf而不是buf+1;
3. 通过起始坐标和结束坐标,设置LCD绘图区域范围,也就是OpenWindow开创;
4. 看到3个for循环不要懵,用分解法一步一步搞清楚:
4.1 最里面for循环,用来显示一个byte也就是8bit数据,这个和中英文字符显示是一模一样的。每次显示一个字节后,指向下一个字节;
4.2 第二个for循环,用来完成整个图片中,一行的显示。因为里面已经显示了8bit了,所以外面这个for循环,循环数量为usWidth/8;
4.3 第三个for循环,用来完成整个图片的显示,每次rowCount加1之后,开始新的一行的绘制。
4.1.3 调用并查看结果
在main.c中调用,并查看运行结果。
希望这张笑脸给辛苦学编程的你带来好心情。
4.2显示彩色图片
黑白图片显示的计数,可以广泛应用在OLED,墨水屏的显示中。但是我们这里用的是TFT真彩屏,所以我们需要进一步掌握彩色图片显示过程。
4.2.1制作彩色图片
我们找到上面一张图片,分辨率为200X200。
用Image2Lcd打开,输出灰度选择16位真彩色,然后点击保存;得到下面的数组。数组长度为80000,相当于 200 X 200 X2,因为每一个像素点为16bit也就是2byte。
注意:需要勾选Big Endian标签,在4.2.3中将详细解释字节序;
4.2.2 输出图片
当我们绘制实心长方形时,采用方法是:
1.OpenWindow,创建绘图区域;
2. FillColor,将文字颜色填入绘图区域,其中中间步骤又包括:
2.1 设置SetPixel命令,准备开始写入数据
2.2 用for循环写入数据
我们采用同样方法,只是把FillColor中的TextColor更换为图片中的颜色即可。
上面代码中,采用同样步骤,完成图片的写入:
1. OpenWindow设定写入区域;
2. SetPixel命令准备写入;
3. WriteData不停写入,在写入数据时,将2个字节的拼接成为一个16bit数据,写入屏幕。
4.2.3 关于字节序
在图片转换文字的时候,提到了字节序。上面红框部分,是两种字节序,其中第二个是STM32对应的Big Edian,也就是大字节序。
大小字节序又叫做大小端,是用来区分数值在内存中摆放的顺序。例如,有一个4byte的变量
uint32_t x = 0x12345678
上面这个数中,高位为0x12, 低位为0x78。这个数在内存中有两种分步方式:
第一种方式,低位数据0x78在地址低位,这个叫做小端,特点是:低位+低位;
第二种方式,低位数据0x78在地址高位,这样叫做大端,特点是:低位+高位,因此为大端;
我们常用的STM32为大端,也就是低位数据在高位地址。
对于一个芯片,如果我们不知道它是大端还是小端,如何通过代码,判断它是大端还是小端呢?按照上面的原理,我们可以从0x12345678中取出低位地址中存放的数据,如果这个数据是低位数据0x78,则说明是:低位地址+低位数据,则为小端字节序;如果得到的数据为0x12,说明低位地址存放高位数据,则为大端字节序。
#include <stdio.h>
int main()
{
uint32_t a = 0x12345678;
uint8_t *p = NULL;
p = (uint8_t *) &a;
if(*p == 0x78)
{
printf("小端字节序\n");
}
if(*p == 0x12)
{
printf("大端字节序\n");
}
printf("p = %x\n", *p);
return 0;
}
对应代码如上,其中:
p = (uint8_t *) &a;
这样代码把a的地址强制转换为(uint8_t *)这样8bit数据对应的地址,用这种方法,从32bit数据中,取出低地址对应的8bit数据。这个操作也正是C的灵活之处。
4.2.4 调用函数,输出图片
调用函数,我们即可输出对应图片。
通过这种方式,我们就可以顺利输出透红透红的番茄了。
(40条消息) STM32创建黑白图片和彩色图片,在TFTLCD上实现显示。详细说明见博客:http://t.csdn.cn/SofN7-Linux文档类资源-CSDN文库
纸上得来终觉浅,绝知此事要躬行!
马上下载测试代码,看看效果。