文章目录
- 1、显存的分配和获取
- 1.1、构造显存链表的结构体
- 1.2、显存分配函数
- 1、将我们自己分配的显存加入显存链表
- 2、 将LCD 实际设备FB 显存加入链表
- 1.3、显存获取函数
- 2、显示页面图标函数框架构思
- 3、编写MainPage页面
- 3.1、获得显存
- 3.2、描画数据
- 1、判断或得到的显存是否已经存在像素数据
- 2、 获得 LCD 的分辨率
- 3、确定缩放尺寸
- 4、首张图标在 LCD 的位置
- 5、提供缩放后的目标图标的图片数据描述结构体
- 6、定义一个图标位置描述结构体
- 7、循环获取原始图片的像素数据,图标的左边数据,缩放、合并
- 8、释放空间和修改显存数据状态标志位
- 3.3、数据刷入LCD
- 3.4、解放显存
- 4、输入功能
- 4.1、修改事件类型结构体
- 4.2、修改输入设备的获取事件函数
- 4.3、实现对应页面中获取事件并处理的部分
- 5、测试
- 5.1、修改之前的代码
- 5.2、启动开发板
上一节:10、数码相框编写程序之图标显示
下一节:12、数码相框编写程序之效果演示与代码讲解
在上一小节中实现了图标的显示,基本工作就准备好了。现在要来做的是显示下面这些页面:
先做主页面的显示:
我们要在显示main_page.c
页面的三个图标出来,其实完成的就是我们之前每个页面的框架中的第一步显示页面,即完成如下截图部分的代码。
1、显存的分配和获取
在写这个代码之前,我们先来了解一下硬件上面是怎么回事:不管你用的是什么开发板,在我们的板子上面,2440
也好或者其他单板也好,里面会有 LCD
控 制 器 或 者 称 为 显 卡 ,外 面 接 有LCD
。LCD
控制器或者显卡上面对应要有显存。我们写显示器的驱动程序的时候会在内存里面分配一块所谓的显存,在我们这里称为 framebuffer
。而 LCD
控制器或者显卡会从 framebuffer
里把数据取出来,发给 LCD
。当我们想显示图片的话,直接把图片写到 framebuffer
里就可以了。
那我们写应用程序时怎么做呢?可以先打开驱动程序,得到这块framebuffer
,然后就可以在这块显存里面直接写数据了。但是这样有缺点,如果你的程序运行得很慢,当你在 LCD
上面描图片的时候就会发现是一行一行很慢地描。就是说内容更新的很慢。
那我们到底一般会这么做呢?会先 malloc
一块内存,这块内存和 framebuffer
的大小是一样的。我们事先将要显示的图片数据全部放到这块内存里,弄好之后再一股脑 memcpy
到framebuffer
里面。这样 LCD
的图片就一下子更新好了。就不会出现逐行逐行或者逐块逐块地显示了。
所以针对于每个页面我们要先获得分配好的内存,然后设置里面的内容(主要是RGB
数据部分和一些flag
),然后写到我们实际的FB
的显存中。
代码见:第 1 个 项 目 数 码 相 框 全 部 源 码 _ 图 片 _ 文 档 \ 源 码 ( 含 讲 课 过 程 中 即 时 编 写 的 文 档 )\12. 数 码 相 框 项 目\14.digital_photo_frame_8.4.2_图片_源码\14.digital_photo_frame_8.4.2 节_图片_源码\14.digial_photo_frame
。
应用程序事先分配好多块内存,用于显存:
1.1、构造显存链表的结构体
但是要考虑由于我们是嵌入式系统,针对于某些型号可能内存不够用,所以分配多个显存并非对应每种型号都适用的,所以我们要兼容这些型号,我们在构造显存的链表的时候,应该在最头部加上设备实际分配的fb
的显存,对应这些内存不够用的设备,我们可以只使用这个显存链表的第一个成员即可,那么对于每个显存链表中结构体的成员应该如何考虑呢?(实际上也就是每个显存里面除了要保存RGB
数据供实际的设备显存fb
使用,还需要哪些表示来表示和区分这些其余的显存的,比如我现在有5个页面,也就是要分配5
个显存,外加一个fb
的实际设备的显存,假如我要显示main_page
这个页面,我们首先要把main_page
页面对应的显存里面对应的数据给fb
就可以显示,但是要解决几个问题:怎么从显存链表里面找到main_page
的显存呢?main_page
里面的显存里面的RGB
数据有没有准备好呢?等等),从如上几点考虑,我们的显存链表结构体构造成员如下:
- 1、由于我们的目的是为了让所有的页面都对应分配一个显存,那样对应每个页面都会有对应的显存保存它的数据,我们之后用户操作返回,或者前进时,在每个页面的显存就不用再次去设置里面的内容,所以我们需要一个
ID
来标识每个显存,当用户去返回,或者跳转到另外的页面时,就可以直接利用原来的对应的显存来进行显示里面的数据,所以需要以ID来辨别显存; - 2、我们之前有提到过,在用户点击触摸之后,会有一段时间的间隙,我们利用这段间隙,把可能下次要准备的页面通过多线程的方式准备好,所以我们还需要一个成员用来判断这个显存是给主线程使用,还是
prepare
线程使用的,另外给个初始状态是未被使用的,所以需要一个显存的进程使用状态eVideoMemState
; - 3、我们所分配的显存中主要还是用来存放要显示的每一页的数据,我们需要定义一个标志位来表示这个显存里面的对应的数据的情况,,是已经有了数据,还是正在产生数据,还是空的,成员为
ePicState
; - 4、最开始我们说了我们要在链表的头部加上一个实际
LCD
设备的fb
的显存,但是我们如何标识出链表中的成员是不是fb
的实际设备显存呢,用标志位bDevFrameBuffer
表示; - 5、最主要的还是给每个显存里面提供要现实页面的
RGB
数据,也就是我们之前有定义的T_PixelDatas tPixelDatas
; - 6、最后就是要提供链表的指向下一个链表的成员
struct VideoMem *ptNext
。
最终的结构体定义如下:
构造完显存的链表的结构体之后,我们就要实现这个链表,我们肯定要提供一个分配显存的函数int AllocVideoMem(int iNum)
,其次我们最终是要把链表里面的某一个显存成员memcpy
个fb
实际的显存,所以我们还需要一个从链表中提取显存的函数,我们分配显存肯定能是在应用层最开始就配分的,比如说我们要分配5
块显存,里面的具体内容在最开始分配的时候基本上都是一样的,比如ID
都设置为0
,当具体的页面比如main_page
去取显存的时候,发现5
个的ID
都是0
,就会随机取一块然后再具体分配ID
和对应的页面的RGB
数据给显存,下次相同的页面只需要再取到同样的ID
的显存就可以了。
1.2、显存分配函数
提供一个分配显存的函数给最顶层使用,我们先来看分配显存的函数如何实现。
1、将我们自己分配的显存加入显存链表
针对于我们要分配的显存,我们肯定是用malloc
来分配,那么要分配多大呢?要注意的是并非sizeof(T_VideoMem)
这么大,T_VideoMem
这个结构体只是用来标识我们的分配的显存的,里面虽然有定义对应页面的数据的指针,但sizeof(T_VideoMem)
并没有包含页面对应数据的大小,所以还要分配对应页面数据的大小,其实我们的数据也就是我们LCD
的分辨率的大小:sizeof(T_VideoMem)
+ LCD的数据大小
。
- 1、获取LCD的分辨率
要获得我们LCD
的数据大小,就是要获取我们LCD
的分辨率来计算的,我们之前的代码有获取x、y
值,但要知道要分配多少个字节,还需要知道bpp
参数,所以修改代码:
然后我们就可以获取bpp
了:
- 2、然后就是初始化
PT_VideoMem ptNew
里面成员的各项
在PT_VideoMem ptNew
中有一个指向对应页面数据的指针,分配到我们链表结构体后面即可:
也就是说我们分配的显存的结构就是前面有个PT_VideoMem类
型的显存描述结构体指针,后面紧接着显存的实际页面数据,让PT_VideoMem
里面的显存某个指向数据的指针指向后面的实际数据。 - 3、初始化完成之后就是要这项显存放入链表中
如何理解这样就把新的显存结构体加入了链表呢?
我们每次都让刚定义的结构体的下一项指向链表头部ptNew->ptNext = g_ptVideoMemHead;
,然后让链表的头部更新为刚才最后加入的结构体g_ptVideoMemHead = ptNew;
这种方式就是链表的头部永远是最后更新的结构体。
2、 将LCD 实际设备FB 显存加入链表
加入完链表之后别忘了我们在链表里面还要添加一个FB
的实际设备的显存,对于我们的LCD
的真正的设备显存,由于我们在初始化display
设备的时候就已经分配了,这里我们只需要分配一个显存链表结构体的大小,然后让显存链表结构体里面的实际像素数据成员指向我们之前分配的LCD
的设备显存即可,然后再初始化实际显存对应的链表结构体各项,最后加入链表。
- 1、分配实际链表显存对应的结构体
- 2、初始化实际显存对应的链表结构体各项
- 3、加入链表
最终的分配显存的代码如下:
1.3、显存获取函数
下面来看看取显存的函数,我们肯定是根据ID
来取
PT_VideoMem GetVideoMem(int iID, int bCur) {1. 优先: 取出空闲的、ID相同的videomem2. 取出任意一个空闲videomem
}
2、显示页面图标函数框架构思
回到我们本节的主要目的就是为了显示main_page
上面的三个图标,之前的我们完成了准备工作,显存的分配和提取函数,接下来我们看看如何显示图标,也就是完成下面的第一步显示页面:
创建显示页面的函数,并构思实现的步骤:
- 1、获得显存:应用层通过调用刚才的分配显存函数,分配了若干个显存,我们们首先要获取一个显存(这个显存可能是已经被使用过的相同
ID
的显存,也可能是块空显存)。 - 2、描画数据:我们要将我们的
main_page
里面的图标对应的像素数据提取出来,经过图片缩放和合并之后,最终将像素数据放到刚才分配的显存中。 - 3、刷到设备上去:由于我们之前只是把数据放到了我们自己分配的显存中,并没有放到
LCD
真正的设备FB
显存,所以并不会显示,我们需要想之前显存的数据拷贝到FB
显存中。 - 4、解放显存:使用完我们分配的显存之后,我们要把他的状态置为
VMS_FREE
状态,供给下次或者别人使用,这是不是释放掉而是置标志位而已。
3、编写MainPage页面
上面我们知道针对于每个页面我们要先获得分配好的内存,然后设置里面的内容(主要是RGB
数据部分和一些flag
),然后写到我们实际的FB
的显存,上一节中我们已经写好了分配内存和提取内存的函数,这一节,我们来按照上面的步骤针对于每一页具体的来显示一个一个页面的内容:
具体的就是实现上面的显示页面的函数:
应该如何实现上面的函数呢?
3.1、获得显存
第二参数1,代表这个显存用于主线程,而不是prepare
线程。我们这里的ID
并不是简单的1,2,3,4,
而是根据名字字符串转换为一个int
型数据:
3.2、描画数据
我们要把main_page
里面的所有图标的数据经过缩放和合并之后保存到我们的上面一步获得的显存中,那么要缩放和合并的话,我们首先就要知道原始图片的数据和我们想缩放到什么尺寸,其次合并的话我们还要确定我们每个图标在LCD
上显示的位置,我们的图片的位置是根据LCD
的实际分辨率来按比例设置的。
1、判断或得到的显存是否已经存在像素数据
判断获得到的显存结构体里面对应的像素数据是否已经生成过了,首先要判断显存的里面的RGB
数据是否已经存在,只要判断ePicState
就行。
2、 获得 LCD 的分辨率
3、确定缩放尺寸
由于我们的原始图片的大小为256*128
,我们自己定义下要显示的图片的位置(这是我们自己规划的),如下图:红色的边框就认为是LCD
的边界。
即缩放后的长和宽是多少?(矩形图片只要知道这两个值就好)
在我们main_page
页面中有三个单元要显示,每个大小是一样的,都是:
4、首张图标在 LCD 的位置
5、提供缩放后的目标图标的图片数据描述结构体
由于我们知道我们的缩放函数出入的参数是原图片和目标图片的图片数据结构体
缩放前后bpp
值不变(LCD
的bpp
),长宽为缩放后的数据,即如下:
6、定义一个图标位置描述结构体
我们知道了缩放后目标图片的大小,现就待会要去合并,合并需要提供一个显示起点的左标,我们有3个图标,可以通过定义一个结构体,这个结构体用来描述每个图标应该位于的左边,和图标的名字,然后通过循环去一个个缩放合并。
我们现在自己定义了每个图标的位置,我们对于每个图标抽象出一个结构体来描述这个图标的。我们知道每个图标都是矩形,我们只要知道了图标的左上和右下的坐标就基本上知道了图标的位置。在page_manager.h
里面定义如下结构体:
接下来我们在main_page.c
里面针对于每个图标都定义一个该结构体,所以定义一个结构体数组:
一开始我们都设置前面的坐标都为0,由于我们待会要循环去显示给每个图标赋值,那么我们怎么知道图标结尾了呢?我们就添加了最后一项,每个成员都设置为空。现在我们就是要在描绘图标里面把前面的上面的结构体的前面几项描绘出来,所以我们直接把每个图标作为参数传递给显示的函数就比较方便了:
这也是我们这个函数参数的由来,知道了这些我们就可以设置我们显存中关于图片的信息的数据了。
7、循环获取原始图片的像素数据,图标的左边数据,缩放、合并
由于我们每个图标只有Y值会变,X不变(右下的x,y都是根据左上来计算的)我们设定好了缩放后的图片的数据,现在要获取原始图片的像素数据
分析上面的重点部分,上面的:
他需要传入一个原始图片的数据信息和新图片的数据信息,那么我们如何获得一个原始图片的信息呢?我们之前8.3.3测试的main
函数里面是先open
一个文件,然后再去获取的,现在我们就参照之前的步骤:
定义一个函数,传入图标的名字来获取对应图标的像素数据,该函数的步骤:
- 1、定义一个文件的描述结构体,里面有文件的路径、大小、描述符、还有一个指针(是用来指向文件的内容的)
- 2、通过传入的图标名构造正确的文件路径,并保存在上面的一个结构体变量中
- 3、构造一个
MapFile
去打开刚才路径的图标文件,并将文件的内容映射到内存中
MapFile
的实现如下:
这里就让我们的传入的PT_FileMap
变量中的一个指针pucFileMapMem
指向了我们分配的内存,而这个内存里面的数据就是文件内容,我们想要知道文件的像素数据,就只要操作这个pucFileMapMem
就可以了,同时构造一个取消映射的函数:
- 4、之后去解析刚才的放到内存的文件的数据,判断是否为
bmp
格式文件,通过判断文件内容的第一个和第二个内容数据是否为424d
。
g_tBMPParser
这个结构体变量是个全局结构体变量,都可以用。 - 5、在获取原始图片的像素的数据之前,由于
GetPixelDatas
函数需要对不同的bpp
有不同的数据返回,我们要先获取lcd
的bpp
。
8、释放空间和修改显存数据状态标志位
3.3、数据刷入LCD
我们前面已经获取完显存,然后把要显示的数据放到了我们获取到的显存上,现在我们要把这个显存的数据复制到FB
的实际显存上才能显示,构造如下函数来实现:
函数实现如下:
要先判断得到的显存不是实际的FB
的显存,因为我们的FB
显存也是在显存链表中的。
3.4、解放显存
实现如下:
4、输入功能
在上一小节里面,我们描绘了在 LCD
上显示的主页面的三个图标,这一节讲输入功能。
我们前面完成了显示页面,按照流程我们还有创建parepare线程
和获取输入事件处理
,我们先做后面一个环节,然后就可以调试了。
现在我们要实现,当用触摸笔在 LCD
上点击图标时,图标就要改变一下颜色。当松开触摸笔时,图标恢复原来的颜色。
4.1、修改事件类型结构体
我们这个要产生触摸事件的代码目前还是参照在电子书的基础上的代码,我们回顾下电子书关于触摸事件的代码,input
文件里面定义的结构体是:
进入产生事件的函数的代码,发现是根据我们的触摸位置来产生翻页等几个简单的动作,肯定是不能满足我们目前的需求的:
对于我们数码相框而言,我们需要知道更精确的触摸位置是点下还是松开,我们现在要修改获取触摸事件的函数,先修改我们的事件类型的结构体:
之前我们只抽象出事件的时间值,还有事件触发的类型(标准输入还是ts),还有事件值(翻页等动作)现在我们要定义一个抽象出一个事件的结构体,他既能描述触摸点事件和按键事件,对于触摸点事件,我们要有触摸的x,y坐标,按下还是松开,对于按键事件,要有按下和松开,还有是哪个按键按下,结构体如下:
其实上面的这个结构体可以参考tslib
,上面的结构体中
类别:当我的type
是触摸屏的时候,我们用到成员有x、y、还有压力
;
当我的type
是按键或者标准输入的时候,用到key和压力
上面的结构体中的x、y
坐标对触摸有用;
上面的压力值对于按键和触摸都用,按键松开时压力为0,按下为1;
上面的IKEY
对按键有用。
4.2、修改输入设备的获取事件函数
修改完结构体后,我们修改touchscreen.c
中的TouchScreenGetInputEvent
函数,之前是根据触摸在LCD
的位置来返回事件,现在我们要在获取事件后返回真实的数据,也就是上面定义的事件的结构体:
对于触摸屏基本就这样就可以了,接下来我们改下标准输入的事件获取函数StdinGetInputEvent
,由于我们目前还没确定每个图标对应哪个按键来触发,所以暂时先不管StdinGetInputEvent
,我们先暂时只考虑从触摸屏来触发事件。
4.3、实现对应页面中获取事件并处理的部分
接下来我们要看下我们的page
里面的main_page.c
,我们要通过获取到事件,然后进行动作:
接下来我们来实现MainPageGetInputEvent
函数,肯定这个函数里面也是调用我们之前input
里面的int GetInputEvent(PT_InputEvent ptInputEvent)
所以我们传入一个事件类型的结构体,用来保存触发的事件的相关信息,另外我们要判断触发的事件为我们页面中的哪个图标,所以需要传入我们的图标描述的结构体PT_Layout atLayout
确认了函数的参数,继续构想要如何实现main_page
里面的获取事件函数
- 1、通过
input
层提供的GetInputEvent
来获取事件
没获取到事件就休眠,有事件后触发,由于现在只考虑触摸屏触发,我们要知道我们触摸到了哪一个图标,由于我们之前有定义过每个页面中的每个图标的左上和右下的坐标的结构体:
我们就可以根据这个结构体来判断我们的触摸的位置是否在这个图标的坐标之内来判断,由于我们要返回一个哪个Layout
的图标被触发了,所以我们把ptInputEvent
里面新增一个Layout
成员。 - 2、根据
mian_page
里面的图标的坐标和刚才通过事件获取函数记录到的x,y
等信息来判断
这个函数返回:事件触发的图标的序号。
接下来返回我们的MainPageRun
继续写代码:我们要实现当用触摸笔在 LCD
上点击图标是,图标就要改变一下颜色。当松开触摸笔时,图标恢复原来的颜色。
刚才我们已经可以获取事件,并知道是哪个图标被触摸了,找到了这个图标是否被触摸了之后,那么我们怎么知道他是被点下了还是松开了?针对于我们触发的事件,我们只要按照如下逻辑分类,我们只对所有的触发事件的两个时刻做处理,其余不理会
- 1、某触摸图标被按下,且之前未被按下
- 2、某触摸图标未被按下,且之前该图被按下
我们用bPressed
来代表该图标之前是否被按下过,用iIndexPressed
来记录触发按键的图标序号。
每次图标的变化动作就将图标的对应的显存的里面的像素数据取反即可(要注意的是这里的的显存是当前用于显示的真正的设备FB
显存,而不是给每个页面分配的显存)。
像素取反函数static InvertButton(PT_Layout ptLayout)
先获取真正的显存FB
:
然后找到要取反的图标的像素数据在FB
显存的位置:
然后一行行取反:
上一节:10、数码相框编写程序之图标显示
下一节:12、数码相框编写程序之效果演示与代码讲解
5、测试
前面三节讲了 mainpage
的显存管理、页面规划、输入功能。
现在这一个我们把之前写的程序调试一下,然后在开发板上运行,观察效果。当韦东山录制完这一节之后,会发布一个 8.4.4 源码
可以跟 8.4.3 源码比较一下,这样就可以看出来做了哪些修改。因为这一节会讲得比较啰嗦,如果你不想从头到尾一句话一句话地改的话,直接比较最终结果。
修改前的代码见:第 1 个项目数码相框全部源码_图片_文档\源码(含讲课过程中即时编写的文档)\12.数码相框项目\14.digital_photo_frame_8.4.3_ 源 码\14.digital_photo_frame_8.4.3 节 _ 源 码\14.digial_photo_frame
。
修改后的代码见:第 1 个项目数码相框全部源码_图片_文档\源码(含讲课过程中即时编写的文档)\12.数码相框项目\14.digital_photo_frame_8.4.4_源码_文档\14.digital_photo_frame_8.4.4 节_源码_文档\14.digial_photo_frame
。
以后参考代码时:
8.4.3 里的代码就不要参考了,里面有很多错误。
8.4.4 里的代码可以参考。
5.1、修改之前的代码
修改顶层 Makefile
,把所有的警告当做错误处理:
修改为:
这样就可以编译出一点警告都没有的程序了。顶层 Makefile
的其他修改参考说明
修改底层 Makefile
: 参考说明文档
定位到 132 行:
5.2、启动开发板
上一节:10、数码相框编写程序之图标显示
下一节:12、数码相框编写程序之效果演示与代码讲解