作为一款多媒体处理芯片,TI公司推出的DM6446 采用ARM+DSP的双内核架构,有着丰富的外设资源和强大的计算能力,因此一般通过操作系统对其复杂资源进行有效管理。DM6446现有平台操作系统主要基于嵌入式linux 系统,但同时也有支持其他主流嵌入式操作系统的能力。
由于多媒体应用常常需要有图形界面,而现有平台操作系统Linux其内核和图形界面是分离的,需要进行另外移植,相比之下Windows CE本身自带了较为优秀的图形界面,只需对其进行显示驱动的开发就能拥有一个优秀的图形界面。因此选择在DM6446进行其他系统的移植开发无疑能使该平台程序开发具有更多的选择余地和更高的性价比。
Windows CE是Microsoft公司专门针对嵌入式产品领域开发的嵌入式操作系统,具有图形用户界面出色、多任务处理能力、可裁剪性和可移植性、应用软件支持丰富、实时性良好等特点。本文选用的Windows CE版本为Windows CE.NET 5.0,以下简称为WINCE.
1 DM6446芯片及其显示模块介绍
1.1 DM6446芯片简介
DM6446 芯片,如下图1所示,由ARM子系统、DSP子系统、VICP协处理器、视频处理子系统和众多的芯片外设组成。其中ARM 核用作整个系统的控制功能,DSP 子系统用于复杂的数据和图像处理功能,视频处理子系统用于和图像输入和输出。这些模块的联系通过中心资源交换通道(Switch CentralResources,SCR) 进行管理。
图1 DM6446 芯片总体架构
1.2 芯片显示模块功能介绍
DM6446 显示模块又称为视频处理后端(VPBE,Video Processing Back End),为芯片视频处理子系统的一部分。VPBE 总体结构如图2 所示。由图2 可以看出,VPBE主要由1 个OSD (On Screen Display)引擎和1 个视频编码器(VENC,Video ENCoder)组成。OSD引擎可以处理2个独立的视频窗口和两个独立的OSD窗口,VENC视频编码器则能提供四路视频数据转换,工作频率高达54MHz,兼容NTSC/PAL制式视频和S-Video.
图2 DM6446 显示模块总体架构。
DM6446 视频编码器还能够向RGB888 的显示设备提供24 bit的数字视频输出接口,支持8/16为的BT.656 输出和垂直/水平同步分离的CCIR.601.
OSD 模块的视频信号在输出之前会经过合成然后送到VENC最终转变成YCbCr格式输出。视频数据是建立在外部存储器DDR2的,并直接送到显示设备作显示。从DAC出来就可以通过RCA端子接上LCD液晶电视。更详细的硬件说明可参考TI 的官方数据手册TMS320DM644x DMSoC Video Processing BackEnd (VPBE) User's Guide.pdf.
2 WINCE驱动架构分析
将WINCE 移植到DM6446 上面除了需要进行OAL层的代码和源码配置文件的编写以外,还需进行大量的设备驱动程序开发。
2.1 WINCE驱动原理
设备驱动程序作为一个抽象物理设备或虚拟设备的功能程序,它管理设备的操作,并将设备的功能导出给应用程序和操作系统。因此用户程序访问这些硬件设备只需要通过调用驱动程序提供的接口函数。
WINCE 的所有设备驱动程序都是以用户态下动态链接库(Dynamic Linkable Library,DLL)文件形式存在的。像所有的Windows DLL一样,DLL是无法单独被加载和运行的。如果要运行DLL 中的代码,必须有一个EXE 进程首先把该 DLL 加载到自己的地址空间内,然后才可以执行DLL 中的代码。WINCE 下的驱动程序也必须被其他EXE加载。
2.2 WINCE驱动分类
基于WINCE的驱动程序有两种模型:本地设备驱动程序(Native Device Driver) 和流接口驱动( Streams Device Driver) 程序。本机设备驱动程序适用于集成到WINCE平台的设备,总是在WINCE的平台启动时被加载;流接口驱动程序也称为可安装的驱动程序,它们使用流接口驱动并借助于文件系统调用(如Createfile,DeviceIoControl等)从设备管理器或应用程序获得命令。本文讨论的显示驱动属于本地设备驱动程序。
而从驱动实现方式来区分,无论流接口驱动还是本地驱动设备驱动,都可以采用两种实现方式:单体结构方式和分层结构方式,它们都向上提供DDI (Device Driver Interface)调用,供其他模块或应用程序调用。无论采用哪种结构,驱动程序都必须与其控制设备的DDI 相一致。DDI是与WINCE 系统的接口,流接口设备的DDI 都是流接口函数。
3 显示驱动的实现
3.1 显示驱动的加载管理
图3 WINCE显示驱动主体架构示意图。
WINCE下的驱动程序必须被其他EXE加载,显示驱动也不例外。WINCE 显示驱动在系统启动时由GWES.exe 加载和管理,并驻留在GWES 的进程地址空间内。如图3 所示,GWES 子系统,由GDI 和DirectDraw两部分组成,为运行在操作系统之上的应用程序提供图形功能的系统调用,例如CreateDC,ReleaseDC等等。GWES加载显示驱动的具体过程如下:GWES启动时将去访问候选显示设备列表(该列表在注册表HKEY_LOCAL_MACHINESystemGDIDisplayCandidates下面),看看是否有驱动程序已经在本机上实例化,如果有的话GWES 会使用它找到的第一个已经实例化的驱动;如果驱动程序没有在本机上实例话或者找不到合适的驱动程序,接下来GWES尝试加载Ddi.dll.默认情况下加载的是Ddi.dll,但如果存在KEY_LOCAL_MACHINESystemGDIDriversDisplay项,GWES会加载此注册表项所指定的显示驱动。
3.2 显示驱动主要组成部分
WINCE 的显示驱动程序如图3 所示,由DDI(Display Device Interface) 和HAL(Hardware Abstraction Layer)两部分组成。
HAL 主要为DirectDraw 服务,只需要在驱动中向GDI 导出HALinit()即可,因此本文研究的重点是DDI 部分,即通常的显示驱动部分。由于在显示中存在大量硬件无关操作,显示驱动通常采用分层结构,采用分层结构有助于降低代码复杂度提高代码效率,其中MDD 层实现缺省的绘图功能,由微软提供的图形原语引擎模块(GPE , GraphicsPrimitive Engine)组成,如果要支持Directdraw,则要使用DDGPE模块;而PDD层与硬件具体相关,则是显示驱动的主要内容,一般由OEM 厂商或独立硬件商实现。
WINCE 上层程序通过一组(约20 多个)显示驱动接口函数同显示驱动打交道,因此显示设备驱动程序必须实现这些显示驱动接口函数,GDI 通过调用这组函数初始化显示设备驱动程序和将图形输出到显示设备上。由于采用分层结构,显示驱动由MDD 层负责对上层的GWES模块提供函数接口,但是这些函数并不是直接提供出来的,实际上只是通过一个DrvEnabLEDriver( )函数来完成的。作为DDI部分的一个导出函数,DrvEnableDriver会在GDI初始化时被调用。
DrvEnableDriver 在MDD 层中没有实现,所以需要在PDD层中定义,主要代码如下:
BOOL APIENTRY DrvEnableDriver
(ULONG engineVersion,ULONG cj,DRVENABLEDATA *data,PENGCALLBACKS engineCallbacks)
{
BOOL fOk = FALSE;
if(gszBaseInstance[0] != 0)
{
fOk =
GPEEnableDriver(engineVersion, cj, data,engineCallbacks);
}
return fOk;
}
这里GPEEnableDriver 是微软预先编写的一个MDD层函数。该函数位于源文件ddi_if.cpp里, 因此我们只需简单调用就可以了。
GPEEnableDriver 函数通过执行语句memcpy(pded, &pDrvFn, cj) 将一个预先定义好的DRVENABLEDATA 结构体变量pDrvFn 的地址传给一个上层结构体指针pded.而在结构体变量pDrvFn 中预先已包含了20 多个底层显示驱动函数指针,这样GWES 就可以通过这些指针操纵底层显示硬件了。例如应用程序想创建一个到图形设备的连接时可以通过GWES.exe 调用CreateDC(),而该函数会调用DrvEnablePDEV()函数,当应用程序需要从显示设备上断开时则会调用DeleteDC() , DeleteDC() 则会调用DrvDisablePDEV() .DrvEnablePDEV() 和DrvDisablePDEV()就属于这20 多个被GWES 调用的底层显示驱动函数。
以上这些底层显示驱动函数大部分跟硬件密切相关,因此需要进一步调用PDD层函数。由于不同的显示硬件特点都不尽相同,因此势必造成PDD层暴露给MDD层的接口函数各不相同,这样势必会增加代码的复杂性。为此微软设计了一个GPE类,一个GPE类实例代表一个显示设备硬件,其所有数据成员都对应于一个显示设备的属性数据,并设计了多个成员函数用以操纵这些数据成员。考虑到硬件的多样性,GPE 类的有些函数并为全部实现,或为空函数或者虚函数,需要其子类实现或者覆盖。因此不能直接定义GPE类型的变量,只能以先构造GPE类为父类的继承类,然后才能定义实例。
MDD 层的底层显示驱动函数通过实例化一个GPE 继承类的实例就可以直接调用PDD 层代码了,这一般是通过SafeGetGPE 函数来实现的。
SafeGetGPE 由微软设计实现,位于MDD 层的ddi_if.cpp,一般无须改动。在SafeGetGPE 函数中调用了GetGPE 函数,这个函数MDD 层没有,需要我们在PDD 层实现。GetGPE 函数可以简单实现如下:
这里代码利用了C++的多态性和继承性。在C++中父类或更上一级的类的指针可以引用继承类中相同的变量,并且对数据成员和成员函数的引用以继承类的实现或定义优先。这样在MDD 中使用指针gGPE 所指向的数据或函数时得到的都是类DM6446VPBE 的成员变量和成员函数。由此可以看出GetGPE 函数是显示驱动中联系MDD和PDD 的桥梁,通过它MDD 可以直接调用PDD的代码。
3.3 GPE继承类的实现
通过上面的分析可以看出,WINCE 的显示驱动主要部分在于PDD 层,而PDD 层除了向MDD导出一些接口函数外如DrvEnableDriver,其余主要是构建一个GPE 或是DDGPE 的子类(如果要实现DirectDraw)。由于DDGPE 的父类是GPE,因此无论是DDGPE 还是GPE 的子类差别并不大。
构建一个GPE 的子类其实就是实现一个有具体数据和函数并且具体准确的反映了特定显示设备硬件属性的GPE 类的子类,并通过该子类去实例化一个对象。
一个GPE 子类通常需要重载GPE 类中的同名函数和实现GPE中的虚函数以及子类独有的一些函数如初始化构造函数[3].子类构造函数主要是初始化硬件和子类成员变量,譬如视频处理时钟寄存器设置,OSD Window 的大小和坐标,VENC 的输出模式,以及子类的成员变量如显示宽度m_nScreenWidth 和显示高度m_nScreenHeight 等等。子类要GPE 类中的函数包括GPE 的空函数和虚函数,这些函数实际上就是MDD 调用PDD 层驱动中需要实现的函数,主要函数包括:SetMode(),用于设置一个显示设备能够支持的显示模式;GetPhysicalVideoMemory(),用于获取显示设备内存的系统基地址和内存大小; 以及AllocSurface() SetPointerShape()BltComplete() SetPalette()等。这些函数具体可以参考微软提供的驱动示例代码,它们位于Public CommonOAKDriversDisplay 目录下[ 1].除了这些函数外PDD 还需实现一个MDD 层函数DrvGetMask,但比较简单,只需要定义一个全局数组gBitMasks,该数组内容是代表RGB 的所占的位域,与具体的显示硬件有关。
3.4 驱动程序与应用程序的通信
不同于其他流式驱动可以由应用程序直接调用,显示驱动由操作系统调用,应用程序不能直接访问。具体来说,应用程序不是通过CreateFile等这些文件系统API接口来访问,而是通过GDI接口间接访问。对于GDI调用而言,对应的后台服务进程是GWES.exe,然后GWES.exe再进一步调用MDD和PDD函数,即WINCE底层显示驱动。例如如果要画一个矩形,则可以调用SetRect、GetDC和FillRect等函数在图形界面上面进行显示,而要在图形界面上输出一段文字只需调用DrawText函数就可以了,至于显示驱动调用就可以交给GDI就可以了。
4 结束语
本文阐述和分析了DM6446 显示硬件原理和Windows CE驱动模型,剖析了显示驱动程序的工作原理和显示工作流程。本文的创新点在于完整的阐述了WINCE显示驱动程序在DM6446上的设计实现,而以往WINCE 的显示驱动都是基于LCD,因此本文对编写同类驱动程序的开发人员将有一定的参考价值。WINCE启动运行后,图形界面运行稳定,并可支持Windows CE下的应用软件运行,表明驱动程序设计良好。