本系列文章主要介绍了SkyEye硬件模拟平台的实现细节。主要内容包括SkyEye的总体设计、SkyEye的可扩展框架、SkyEye的关键数据结构、SkyEye对各种CPU的模拟实现、SkyEye对各种外设的模拟实现、如何安装使用SkyEye以及如何扩展SkyEye的仿真模块等。对SkyEye的深入了解,有助于对嵌入式硬件系统有更深入的认识,特别是对操作系统、驱动程序如何与嵌入式硬件系统进行交互有更深刻的了解。
1.1.7 SkyEye的LCD/TouchScreen模拟实现
1. LCD模拟的构思和设计概述
LCD模拟模块的设计思路是,使用GTK+图形系统在X Window系统和Win32系统上实现一个LCD屏幕模拟,在SkyEye上运行的嵌入式操作系统中的LCD驱动程序象驱动真正的LCD控制器一样发送控制命令或对LCD显示内存进行访问操作,而SkyEye解释这些控制命令,并根据这些命令对LCD屏幕窗口进行相应的GTK+图形操作,完成对不同灰度或颜色图形的绘制。
在SkyEye模拟器中,如果嵌入式操作系统要执行I/O 地址访问,具体的处理过程由特定CPU 和开发板I/O 模拟模块中的read/write_byte/halfword/word 函数处理。所以LCD模拟模块关注的主要是内存模拟模块模拟出来的LCD显示内存中存储的数据。 LCD的显示内存映射到内存RAM中,代表了要在LCD屏幕上显示的图像。显示内存必须足够大,以处理显示屏幕上所有的象素。应用程序通过直接或间接地存取显示内存中的数据来进行进行图形操作,改变屏幕显示的内容。
LCD模拟模块对GTK+的使用目前仅限于根据分辨率(例如320x240,640x480)创建相应大小的窗口以及根据显示内存中的数据逐点在该窗口进行绘制,因为画点是LCD屏幕最基本的动作,所有其它的相对复杂工作如图形绘制,嵌入式GUI系统的实现都应该由基于LCD驱动程序的应用程序(包括基于FrameBuffer驱动程序的嵌入式GUI系统,例如MiniGUI)通过对LCD显示内存的读写操作来实现,SkyEye"看到"的只是显存中对应于屏幕上各个点的像素值,而不关心这些像素值组成的是什么样的图像。基于MiniGUI的应用程序在SkyEye运行的效果截图如图 0-1所示。
SkyEye中LCD模拟部分的示意图如图 0-2(包括与真实情况的比较):
LCD模拟模块的实现先后采用了两种方案,在第一种方案中,在SkyEye的内存模拟模块中,在每一次的写内存操作之后判断其地址是否属于LCD显示内存的地址范围,如果在该范围之内则调用LCD模拟模块中的GTK+画点函数gdk_draw_point(),根据由像素值查找彩色查找表CLUT得到的RGB值(对于真彩色,颜色深度为16,24,32时,RGB值可以直接由像素值得到),在模拟屏幕窗口的相应位置画一个相应灰度或颜色的点。
该方案的优点在于实现起来简单,且模拟了真实的LCD最基本的画点动作,对于图像随时间流逝而只有小范围变化的情况具有一定的优势,因为对显存有写操作时才有画点操作,但是也有两个方面的缺点,其一,与SkyEye模拟器的内存模拟模块耦合紧密,破坏了模块间的独立性;其二,对于图像随时间流逝而大范围变化的情况,本方案效率低下,在LCD驱动程序连续的每两次写显存操作中,都要经历一个单位延迟时间,其长度等于一次地址范围的判断,一次CLUT查找及一次GTK+画点函数的调用所耗费的时间,对于一次全屏操作,以320x240x8为例,若以字节为单位写显存,则额外的时间延迟将320x240x8/8=76800倍于单位延迟时间。
而第二种方案则直接定时(时间间隔可调,例如设置成200ms) 调用GTK+的绘图函数gdk_draw_rgb_image()将显存中的数据一次性绘制到窗口中。该方案模拟了DMA的定时扫描方式,与真实的DMA方式不同的是,在真实的硬件上,DMA方式无须CPU参与,可与CPU并行工作,而用软件模拟的硬件无法做到这一点,只能串行地定时扫描显示内存,其时间延迟不可避免的比真实硬件大。
第二方案降低了LCD模拟模块与内存模拟模块之间的耦合度,其缺点是不能实时地反映显存的快速变化。如果将定时间隔设置得过大,则增大了窗口内容刷新时的闪烁;如果定时间隔设置得过小,定时扫描过于频繁地发生,对系统资源是一种浪费。
2. SkyEye中的LCD模拟分析
SkyEye的lcd仿真首先是在模拟ep7312时实现的,所以在本文分析lcd仿真时是以ep7312的lcd模块为例,对于模拟其它的开发板时添加lcd模块的方法是一样的。在SkyEye源码中的clps7110.h文件中有如下定义:
|
在SkyEye源码中的skyeye_lcd. c文件中有如下定义:
#define LCD_BASE 0xC0000000 // lcd显示内存起始地址
在SkyEye源码中的skyeye_mach_ep7312.c文件中有:
|
在ep7312_io_do_cycle函数中也就是每个时钟后会调用:
skyeye_config.mach->mach_io_do_cycle(state);
SkyEye模拟ep7312时,会在skyeye_mach_ep7312.c中的函数ep7312_mach_init注册
this_mach->mach_io_do_cycle = ep7312_io_do_cycle;
在ep7312_io_do_cycle中调用lcd_cycle,这里
lcd_cycle(state)=gtk_main_iteration_do(FALSE);
检查gtk窗口是否有事件需要处理,没有则立即返回。
ep7312_io_read_word函数中:
如果读LCDCON寄存器的地址
返回 data = state->io.lcdcon;
|
3. LCD相关函数分析
skyeye_lcd.c中的函数:
|
4. TouchScreen模拟模块的设计与实现概述
TouchScreen模拟模块的设计思路将与LCD模拟窗口同样大小的GTK+组件置于LCD组件容器中,并为该组件注册鼠标键按下,释放及移动三种事件,当鼠标在组件窗口有键按下,释放或移动的动作,则在相应的事件回调函数种记录其在窗口上的坐标及键的状态,并产生修改中断寄存器中的相应位置1,在SkyEye上运行的嵌入式OS检测到中断寄存器的数据变化就产生中断,TouchScreen驱动程序中注册了该中断的中断服务程序ISR则复制所记录的数据供应用程序使用,这一思路简单说来就是,完成GTK+的鼠标事件到TouchScreen事件的映射。
因此TouchScreen模拟模块只需要关注GTK+鼠标事件的发生,记录事件数据并在*_io_do_cycle 函数中对I/O模拟模块所模拟的中断状态寄存器进行置数操作,即为嵌入式操作系统内核产生中断信号的条件。
下图就是SkyEye模拟器的TouchScreen模拟的流程图(包括与真实硬件的比较):
TouchScreen模拟模块的实现采用了与模拟采用DragonBall开发板的Xcopilot模拟器相类似的简化方式。在实际的TouchScreen硬件中,为了定位动作发生的坐标,要先先经过一个12位的A/D转换器分别转换X,Y坐标对应12位数字量,然后由驱动程序通过SPI串行总线串行接收。SkyEye作为一个指令级的模拟器,无需保证与真实时钟节拍在时序上的一致,因此允许对TouchScreen这样的外设的模拟进行简化。