基于PCI卡的FPGA硬件延时驱动

一、软件延时的缺点:

项目来源于一个基于windows操作系统的PCI控制仪器,由于测试过程中需要用到很多微秒(us)和毫秒(ms)级的延时函数,且对延时精度要求相对较高。原有系统采用了windows API 对CPU节拍计数来实现高精度定时,代码如下:

   1: void WINAPI delay_us(DWORD dw_us)
   2: {
   3:     LARGE_INTEGER CurrTicks, TicksCount;
   4:  
   5:     QueryPerformanceFrequency(&TicksCount);
   6:     QueryPerformanceCounter(&CurrTicks);
   7:  
   8:     TicksCount.QuadPart = TicksCount.QuadPart * dw_us /1000000i64;
   9:     TicksCount.QuadPart += CurrTicks.QuadPart;
  10:  
  11:     while(CurrTicks.QuadPart<TicksCount.QuadPart)
  12:         QueryPerformanceCounter(&CurrTicks);
  13: }

这种延时方法有两个主要问题:

  • 定时精度差。windows是一个分时复用的操作系统,若定时时间内进程切换或者系统繁忙时,实际延时时间会变长。
  • CPU资源占用率高。实测在一般PC上(没有考虑多核问题),并行跑两个软件延时进程,CPU占用率几乎100%。

考虑用PCI卡上有一片FPGA芯片,若用FPGA实现硬件逻辑的定时器,能达到更高的定时精度,且能有效降低对CPU资源的占用。

参考资料:

Windows 各种计时函数总结

精确获取时间(QueryPerformanceCounter)

How To Use QueryPerformanceCounter to Time Code

二、硬件延时方案

软件示意图2

                                                                                          硬件延时总体方案结构图

 

  • 上层应用程序需要修改delay_ms()和delay_us()两个函数, 从原来查询CPU节拍数的计时方法,改写为向驱动预置FPGA定时器的起始定时数据并启动定时器。对于用户测试程序不需要任何改动,延时继续调用delay_ms()和delay_us()函数,
  • 需要延时时,delay_ms()或者delay_us()函数根据预设的FPGA时钟频率计算出定时的预置数据,发送给PCI驱动程序。PCI驱动程序起中间桥梁的作用,连接上层测试程序和底层板载FPGA.
  • PCI驱动程序接收到delay_ms()或者delay_us()调用消息后,向FPGA申请定时器,申请成功后,启动FPGA中对应定时器开始定时,同时APP层线程挂起等待定时中断。
  • 当定时到时,FPGA向PCI总线发送硬件中断,PCI驱动响应中断,清除相应中断标志、释放FPGA定时器后触发APP层线程等待事件。
  • APP层接收到定时到事件后恢复线程,恢复执行后续动作。

PCI Driver与FPGA定时控制过程示意图

PCI驱动与FPGA定时控制过程示意图

三、PCI驱动设计

定时过程驱动主要动作为:

  • App层通过WritePort()或者DeviceIoControl()函数向PCI驱动层发送的定时控制指令和数据。
  • 驱动层接收到启动定时指令后产生定时IRP消息包,申请FPGA定时器并启动定时,启动成功后,将IRP消息包挂起(pinding),返回App层状态为挂起。
  • App层接受到驱动挂起信号后,等待定时事件完成。
  • 定时时间到后,FPGA定时单元产生硬件中断信号,PCI驱动进入中断服务例程(ISR),清楚中断信号后,将对应挂起的IRP完成,返回成功完成信号。
  • App层等待的定时到事件被触发,完成等待,继续后续指令。

从上可知,其关键是IRP消息包不是一次直接处理完成的,而是先定时将IRP包暂时挂起,然后在定时到后的中断中完成这个IRP消息包。

微软推荐对IRP的处理流程如下图所示,驱动接受到IRP消息包,先通过分发程序找到相应执行例程,然后通过StartIO将IRP包压入顺序队列,并暂时将IRP包挂起。驱动程序逐一执行IRP队列中的IRP包,返回执行后的状态(penging)。此时IRP包还没有真正完成,中断到达后在中断例程中启动一个延迟执行例程(DPC),然后在DPC中完成此IRP包,并返回成功状态。

 

05330066-9a1e-4855-a1e4-d26a5acec11b

标准驱动IRP消息完成过程示意图

针对这个项目的实时性要求,我改进了其中有两点:

  • 定时IRP包不用StartIO例程串行化执行,而是采用KeSynchronizeExecution()函数完成定时IRP包。

        这主要是因为StartIO例程将IRP包压入队列串行化执行后会增加定时延迟,这个延迟是不可控的。另一反面,定时IRP包在完成定时挂起之前不允许有任何打断(受限于FPGA的状态机),采用KeSynchronizeExecution()函数后,将定时IRP包的优先级暂时提高到和硬件中断一个级别 (DIRQL) ,优先级高于StartIO例程。这样能保证定时IRP包执行的连续性和实时性。主要代码如下:

   1: //------------------------ APP ----------------------------------
   2: int  my_delay_us(DWORD dw_us)
   3: {    ...
   4:     HANDLE hDevice = CreateFile(    _T("\\\\.\\PCI_Driver_Tusion"), 
   5:                                 GENERIC_READ | GENERIC_WRITE,    
   6:                                 0,        // share mode none
   7:                                 NULL,    // no security
   8:                                 OPEN_EXISTING,    
   9:                                 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,    //opened or created for asynchronous I/O.    
  10:                                 NULL
  11:                             );  
  12:     
  13:     //creat overlap event
  14:     OVERLAPPED overlap={0};
  15:     overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); 
  16:     bResult = DeviceIoControl( hDevice, START_TIME_PROCESS, &count, sizeof(count), &dResult, sizeof(dResult), &dwOutput, &overlap );
  17:     ....
  18:     status = WaitForSingleObject( overlap.hEvent,5000 ); //wait for timer
  19:     ...
  20:     bResult = CloseHandle(hDevice);
  21:     ...
  22:     return 0;
  23: }
  24:  
  25: //---------------------- PCI Driver ---------------------------
  26: #pragma LOCKEDCODE
  27: NTSTATUS PCI_Test_DeviceControl(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)
  28: {    ...
  29:     switch (ControlCode)
  30:     {    ...
  31:          // Set timer request
  32:         case START_TIME_PROCESS:
  33:         {
  34:                 ...
  35:                 BOOLEAN  SyncState = KeSynchronizeExecution(dx->InterruptObject,(PKSYNCHRONIZE_ROUTINE)SetTimerRoutine,(PVOID)dx);
  36:                 if( SyncState == TRUE )
  37:                 {
  38:                      
  39:                      KdPrint((" PCI_Test_Driver:KeSynchronizeExecution set OK! Pending the thread\n"));
  40:                      info = 4;
  41:                      pIrp->IoStatus.Status = STATUS_PENDING; 
  42:                      pIrp->IoStatus.Information = info;  
  43:                      // pending the delay timer IRP
  44:                      IoMarkIrpPending(pIrp);
  45:                      KdPrint((" PCI_Test_Driver:complete IoMarkIrpPending(); \n"));  
  46:                      return STATUS_PENDING;
  47:                    }
  48:         }
  49: }
  50:            

 

  • 定时IRP直接在中断服务例程(ISR)中完成,而不采用ISR调用DPC再完成。

     这样做出发点也是为了提高实时性,毕竟DPC的调用需要延迟时间。但这里就出问题了。

未完待续。。。。

参考资料:

Processing IRPs in a Lowest-Level Driver

posted @ 2012-06-17 01:33  tusion  阅读(936)  评论(0编辑  收藏  举报