VB环境下实现PCI设备底层访问的两种方法
2009-04-10
作者:姜万波 徐 兴
摘 要: 介绍了在VB开发环境下,对PCI设备进行底层访问的两种方法:一是通过调用用户自己编写的动态连接库(DLL)实现,二是利用WINDRIVER提供的VB运行库编写直接访问硬件的接口函数,并对两种方法进行了比较。
关键词: WINDRIVER PCI 动态连接库 应用程序接口
VB集成化编程语言是一种功能强大而又容易上手的开发工具。在用户界面、数据库、多媒体、网络编程等方面,VB可谓得心应手。然而VB有限的硬件编程能力又使得许多硬件开发者对此深感无奈。尤其在工业控制,测控技术等领域,自行设计开发的I/O卡,数据采集卡等在WIN32下的驱动常常需要借助DDK, VtoolsD等工具进行艰苦而又长期的内核模式开发。本文介绍了在VB开发环境下访问PCI设备的方法。对于其他设备,方法与此大同小异。
在VB开发环境下,用户要访问诸如数据采集卡之类硬件上的PCI设备,一般来说有两种途径:一是直接访问,即用VB直接编写访问PCI设备的接口函数(这种方法需要有相关软件的支持);二是间接访问,即VB调用其它编程语言(如汇编,C/C++等)写的底层驱动模块(一般封装成动态连接库DLL的形式)实现。
1 PCI总线的配置空间
PCI规范定义了三种地址空间,除了存储器和I/O地址空间外,为支持PCI设备系统资源的自动配置,还定义了配置地址空间。
PCI总线的配置空间由256个字节组成,分为预定首区和设备关联区。预定首区包括开始64个字节,对所有的PCI设备来说,都必须支持该区的设置;设备关联区的寄存器有不同的PCI设备厂家自己定义。
配置空间的预定首区分两个部分,前16个字节的定义对各类PCI设备而言都是相同的,后48个字节空间根据设备支持的功能有不同的分配。首区类型定义了该空间的分配情况(目前只有一种类型00H)。表1是首区的组织结构。
所有的PCI设备必须支持首区中的供应商ID、设备ID、指令和状态区。对于其他寄存器的使用可根据设备的功能来选择。对于不同的PCI设备,其供应商ID由PCI SIG分配以确保唯一性,而设备ID则由供应商自己分配。
2 PCI设备的配置过程
PCI总线的配置空间规范保证了所有PCI设备对“即插即用”的支持。
系统在上电后,“即插即用”BIOS通过隔离算法读取每一个“即插即用”设备的资源申请数据,并分配相应的系统资源,同时检查资源的冲突情况,然后引导、加载操作系统,并将控制权交给操作系统;如果加载的是“即插即用”操作系统(WINDOWS 95及以后版本),那么操作系统将接管系统的资源管理权,它首先从BIOS读取“即插即用”设备的资源配置信息,并仲裁资源冲突情况,然后配置BIOS尚未配置的“即插即用”设备,将设备的配置信息写入配置管理器,最后激活无资源冲突的“即插即用”设备,装载相应的设备驱动程序。
对于PCI设备来说,系统完成引导之后,除了将资源的分配写入系统的配置管理器外,还写入了相应的PCI配置寄存器。程序可以通过直接读取设备的配置寄存器来得到设备的I/O,存储器等资源配置情况。
3 VB下PCI设备的访问
驱动程序访问PCI设备的过程一般包括扫描PCI总线,查找指定的PCI设备,确定I/O等资源分配情况,进行I/O、存储器、中断以及DMA等操作。VB本身并不能实现上述对PCI设备的访问过程,下面介绍在VB下通过其他途径实现对PCI设备的访问。
3.1 VB直接访问
由于VB只提供了非常有限的I/O访问能力(如串口通信),在VB下直接访问PCI设备时需要借助其它软件。目前WINDRIVER提供这方面的支持。WINDRIVER是KRFTech公司主推产品,是许多PCI厂家所推荐的首选驱动程序开发工具。
WINDRIVER为VB 4.0以上版本提供了一个类模块(WINDRAR.CLS),利用这个类模块,用户可以手工编写自己所需的接口函数来访问相应的设备。下面以具体例子来说明WINDRAR.CLS的使用方法。
3.1.1 扫描PCI总线得到指定设备的数目
利用WINDRAR.CLS提供的应用程序接口函数(APIs),编写一个扫描PCI总线,获得指定PCI设备数目的函数如下:
Function GetCardsNum(dwVendorID As
Long, dwDeviceID As Long) As Integer
Dim pciScan As WD_PCI_SCAN_CARDS
Dim hWD As Long
hWD = WD_Open()
If hWD = INVALID_HANDLE_VALUE Then
MsgBox ″设备打开出错!″
Exit Function
End If
pciScan.searchId.dwVendorID =
dwVendorID
pciScan.searchId.dwDeviceID =
dwDeviceID
WD_PciScanCards hWD, pciScan
WD_Close (hWD)
GetCardsNum = pciScan.dwCards
End Function
该函数可以通过输入参数:PCI设备的供应商ID和设备ID得到所需的PCI设备数目。如查找AMCC公司的PCI适配芯片S5933,则输入参数为:&H10E8和&H4750。
下面例子用于读写S5933的PCI配置寄存器。在工程的全局模块中需要先定义下列数据结构,同时设备必须处于打开状态。
Type AMCC_INTERRUPT
Int As WD_INTERRUPT
hThread As Long
Trans(0 To 1) As WD_Transfer
End Type
Type AMCC_ADDR_DESC
dwLocalBase As Long
dwMask As Long
dwBytes As Long
dwAddr As Long
dwAddrDirect As Long
fIsMemory As Boolean
End Type
Type AMCC_STRUCT
hWD As Long
cardLock As WD_CARD
pciSlot As WD_PCI_SLOT
cardReg As WD_CARD_REGISTER
addrDesc(0 To AD_PCI_BARS - 1) As
AMCC_ADDR_DESC
fUseInt As Boolean
Int As AMCC_INTERRUPT
End Type
3.1.2 读写PCI配置寄存器
完成以上数据结构的定义后,用下面的函数可读写S5933的PCI配置寄存器内容。
Function AMCC_ReadPCIReg( hAmcc As
AMCC_STRUCT, dwReg As Long)
Dim pciCnf As WD_PCI_CONFIG_DUMP
Dim dwVal As PVOID
pciCnf.pciSlot = hAmcc.pciSlot
pciCnf.pBuffer = dwVal
pciCnf.dwOffset = dwReg
pciCnf.dwBytes = 4
pciCnf.fIsRead = True
WD_PciConfigDump hAmcc.hWD, pciCnf
AMCC_ReadPCIReg = dwVal
End Function ‘读函数
Sub AMCC_WritePCIReg(hAmcc As
AMCC_STRUCT, dwReg As Long, dwData As PVOID)
Dim pciCnf As WD_PCI_CONFIG_DUMP
pciCnf.pciSlot = hAmcc.pciSlot
pciCnf.pBuffer = dwData
pciCnf.dwOffset = dwReg
pciCnf.dwBytes = 4
pciCnf.fIsRead = False
WD_PciConfigDump hAmcc.hWD, pciCnf
End Sub ‘写过程
· 参数说明:
hAMCC 设备打开后系统分配的句柄
dwReg 读写的PCI配置寄存器
dwVal 读出的寄存器数据
dwData 写入寄存器的数据
以上例子仅仅是抛砖引玉。WINDRAR.CLS类模块提供了功能极为强大的底层驱动的API函数,用户通过编写相应的驱动模块可以方便地实现对各类硬件的I/O、存储器映射、中断以及DMA等操作,同时可以实现WIN32下物理内存空间的申请、读写等处理。另外对于实时性要求较高的设备,WINDRIVER提供的“内插”(Plug-In)特性可以让程序的相关模块运行于Ring 0内核模式(Kernel mode),以提高性能。
开发完成的底层驱动模块既可直接为VB的应用程序调用,也可以在VB下封装成DLLs供其它的WIN32开发工具调用。
3.2 自定义DLL访问
DLL使VB的功能得到极大的增强,使得VB的应用范围不断扩大,使用更加灵活。VB通过调用自定义DLL可以实现对硬件的底层访问。下面用例子说明VB对DLL的调用及DLL的编写过程。
3.2.1 DLL的功能和编写
本例中的DLL通过扫描PCI总线,得到总线上S5933接口芯片的数目,打开指定设备,向S5933的输入邮箱1中写入命令字,然后从输出邮箱1中读取返回数据,最后关闭设备。
extern “C” _declspec(dllexport) int _stdcall GetCardsNum()
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
int cards;
cards=AMCC_CountCards(0x10e8,0x4750);
return cards;
} // 此函数得到S5933的数目;
extern “C” _declspec(dllexport) DWORD_stdcall SendCommand(int CardNum,DWORD dwCmd)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
DWORD data;
If(AMCC_Open(&hAMCC,0x10e8,0x4750, CardNum,0)) // 打开指定设备
{
AMCC_WriteRegDWord(hAMCC,OMB1_ADDR,
dwCmd); // 写入命令字
do{
data=AMCC_ReadRegDWord(hAMCC,MBEF_ADDR);
}while((data&0x000f0000)==0x00000000);
// 等待输入邮箱1满
data=AMCC_ReadRegDWord(hAMCC,IMB1_ADDR);
// 读取返回数据
if(hAMCC) AMCC_Close(hAMCC);
// 关闭设备
return data;
else
{ AfxMessageBox(“打开设备失败?选”);
return 0;}
程序中用到的函数包含在WINDRIVER的API函数库中,在VC++下编译时加上头文件:
#include “amcclib.h”
#include “amcclib.c”
同时在DEF文件中列出DLL的导出函数名,生成的DLL即可为VB所调用。读者也可用其它工具编写驱动模块,最后封装成DLL即可。
3.2.2 VB调用DLL
VB调用动态连接库(DLL)时,首先声明DLL,然后即可像调用VB的语句或函数一样使用DLL中的例程。下面介绍VB调用上例生成的DLL(假设文件名为Test.dll)。
· 声明
Public Declare Function GetCardsNum Lib “Test.dll” () As Integer
Public Declare Function SendCommand Lib “Test.dll” (ByVal dwCmd as Long) As Long
在声明时需要注意:DLL的路径;参数传递的方式;参数的类型。
另外,VB遵从 _stdcall的参数传递约定,而VC++默认_cdecl的传递约定,因此在DLL中的导出声明需采用_stdcall的修饰符。
· 调用
一旦声明后,在VB的应用程序中就可调用DLL中的例程。如:
Private Sub Form_Load()
Dim CardsNum As Integer
CardsNum = GetCardsNum()
MsgBox “系统中有”+ Str(CardsNum)+“块S5933插卡!”
End Sub
WINDRIVER包括了诸如AMCC、Altera、PLX、Galileo、V3、PLDA等公司PCI芯片的专用C/C++的API函数库,其中包含了I/O读写,内存映射,中断处理以及DMA等底层驱动的函数,可以非常方便地用VC++,BC++以及C++ Builder等工具编译成DLLs供VB调用。
本文提供了两种在VB的开发环境下访问PCI设备的方法。第一种方法需要有WINDRIVER的VB运行库支持,可以在VB环境下直接编写所需的接口函数,但对WINDRAR.CLS类模块中定义的内核数据结构要有较深的了解;第二种方法具有一定的灵活性、普遍性,编写DLL的工具较多,DLL除了可用于VB外,还可用于其他的WIN32开发工具,有较强的适应性。
以上方法在北京航空航天大学测控技术研究所研制的PHD2000高速并行数据采集系统中得到实际应用,取得了良好的效果。
参考文献
1 WinDriver V4 Developer's Guide.KRFTech 1997~1999
2 S5933 PCI Matchmaker Controller Data Book. Applied Micro Circuits Corporation,1996
3 余永进译.“即插即用”技术大全.北京:电子工业出版社