【原创】再谈WinCE中断开发

预备文章:

WinCE 6.0中断驱动程序分析 BY:HJB

WIinCE中断流式实现驱动和APP 51wince

【原创】WinCE中断驱动开发实战 51wince

这篇文章主要总结了最近一段时间关于wince下的中断开发过程。本文仅适合初学,高手请多多指教!

首先我们在来回忆下什么是中断,请阅读文章《中断解析》,这里再来回忆下中断的概念是源自于我自身的经历,本人毕业于一所金融类为主的大学的计算机学院,主修方向为软件偏向软件工程及ERP金融管理类软件开发。但由于工作,毕业后的主要工作转向与嵌入式开发,对于中断只能说模糊了解,基本上没有实际操作的经验,以至于在刚入职一段时间内被同事而笑话连中断都没有做过,所以当时非常的沮丧,但下定决心要在这个行当里学到点什么,毕竟入了这行再想换难度也比较大。

关于wince的中断上面的几篇预备文章我们已经给出了一些实例和介绍。这里我们再次来完善一下整体的开发流程思路。

wince做为一个嵌入式的OS,其中断的重要性是不言而喻,在《WinCE 6.0中断驱动程序分析》一文中HJB已经给我们很清楚的介绍了wince下的中断流程,请不熟悉的读者务必仔细阅读此文,我也读过很多遍后才开始写需求分析,也就是《【原创】WinCE中断驱动开发实战 》一问中的一些需求描述。

接下来我再来将一个完整的中断驱动+APP测试程序开发的流程实例分析一边。一是为了自己加深影响,二是为了给一些刚入门的朋友一点借鉴的资料。可能刚入门的朋友也遇到过我相似的遭遇,应为不懂中断而被人嘲笑,我想说的是,虽然一开始不懂,但自己努力学还是可以学会的,也就是,穿别人的鞋,走自己的路,让别人去找去吧

闲话少说我们转入正题。

本次开发的实例是基于wince的中断开发,下面进行具体的内容描述:

 

硬件环境:1.开关电位器(飞梭),用于触发中断并产生一些值(类似于按键按下的功能);
             2.MCU,用于产生中断以及发出值给ARM的主体;
             3.ARM,用于接受MCU发出中断以及接受值的主体;
             4.LCD屏,显示中断后mcu发出值,以及相应处理信息;
软件环境:1.platformbuilder 5.0 OS wince5.0系统开发
             2.VS2005 AP层开发

设计思路:1.开关电位器(飞梭)转动;
             2.MCU采集开关电位器转动状态,通过AD采样的方式取得转动产生的对应值;
             3.MCU产生中断信号,将MCU某引脚拉低,同时该引脚连接于ARM的某一引脚;
             4.ARM定义于MCU连接引脚的中断状态,并等待响应中断命令;
             5.当ARM响应到指定引脚的中断状态时,发出于MCU的通讯指令,获取MCU采集到的值;
             6.获取成功后ARM,将该值读取,并同过驱动将此值由驱动层传递至AP层;
             7.AP层通过API函数于驱动层连接并传递信息,同时将对应接受到的值显示;

实际开发:

MCU程序开发:

     这里关于MCU的开发我们不做过多的描述,只需要实现当开关电位器发生动作时,将AD采样的值获取,同时将对应的IO口拉低,为ARM产生一个中断信号即可,但实际上AD采样值的分析也是一个比较大的工程,需要做一些纠错和处理,但这里我们主要是为wince下的中断开发进行分析,这里我们不多介绍;

OS WinCE 驱动开发:

     关于中断的驱动开发,我的步骤是先参考了HJB大牛的《WinCE 6.0中断驱动程序分析》,然后参考在网上收集的关于2410下按键中断开发的参考代码,具体代码请参考《WIinCE中断流式实现驱动和APP》一文中的资源下载。

      通过以上两片文章的阅读,我们可以对驱动开发的框架有一个大概的认识,并对中断驱动开发的流程有了一个了解。这里我想要提出的是四个函数,XXX_DetectThread,XXX_Init,XXX_Deinit和MCL_Read这四个函数,在这里是因为项目需要,这里的XXX可以理解为MCU或者各自项目中所定义的,可以任意定义,当然,后面三个是流式驱动固定格式即可;

XXX_DetectThread,在HJB大牛的文中用的是PowerButtonIntrThread,在参考2410的驱动开发中的叫EINTKey_IntrThread,这里我们折中取了个名子叫XXX_DetectThread,这个函数的实现的主要功能是做一个死循环,等待型号量,这里我们给出这个函数的实现部分,其中有一个MCUCTL结构体,大家可以根据自己的需要去定义,主要是一些handle和dword型,为事件和优先级做一个事先的准备,在程序中大家可以根据赋值来区分我结构体中定义的内容,这里我不一一介绍,给大家一个读程序的空间

   1:  /////////////////////////////////////////////////////////////////////
   2:  //=============================================================================
   3:  //Title : MCU_DetectThread
   4:  //Detail: Receive INTR EVENT from gpio for mcu communication
   5:  //Input : PVOID pArg
   6:  //Output: DWORD
   7:  //Author: Mercury        
   8:  //Data  : 2009-12-26
   9:  //=============================================================================
  10:  DWORD MCU_DetectThread(PVOID pArg)
  11:  {
  12:      DWORD dwRet, dwAction;
  13:      MCUCTL*pMcuCtl = (MCUCTL *)pArg;    
  14:      HANDLE rghEvents[2] = {pMcuCtl->hDeinitEvt, pMcuCtl->hGioEvt};    
  15:      unsigned char  i = 0;
  16:      RETAILMSG(1, (TEXT("MCU_DetectThread Enter\r\n")));
  17:      CeSetThreadPriority(GetCurrentThread(), pMcuCtl->dwPriority256);
  18:      Sleep(3000);    
  19:      while (1) 
  20:      {
  21:          dwRet = WaitForMultipleObjects(2, rghEvents, FALSE, INFINITE);
  22:          if(pMcuCtl->bDeinit)
  23:     {
  24:              return 0;    
  25:         }            
  26:          switch(dwRet) 
  27:    {
  28:          case WAIT_OBJECT_0:
  29:              RETAILMSG(1, (TEXT("mcu_DetectThread Wait deinit\r\n")));
  30:              //deinit event
  31:              return 0;
  32:          break;
  33:          
  34:          case WAIT_OBJECT_0+1: //power key event    
  35:              {
  36:                       //RETAILMSG(1, (TEXT("mcu_DetectThread Wait gio\r\n")));
  37:              //__try
  38:              //{
  39:                          OperationMCU(READ, &sendOut[0], 2);    //于mcu的通讯函数模块
  40:              #if 1
  41:                  for(i = 0 ; i < 2 ; i++)
  42:                  {
  43:                  RETAILMSG(1,(TEXT("the %d number  SentOut Value for point variable is %x\r\n"),i,sendOut[i]));
  44:                  }
  45:              #endif    
  46:              //}
  47:              //__except (GetExceptionCode() == STATUS_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
  48:              //{
  49:              //    RETAILMSG(1, (TEXT("Emcu_DetectThread MCU_gio!!!!\r\n")));
  50:              //    SetLastError(E_FAIL);
  51:              //}
  52:                  SetEvent(gReadKeyEvent[0]);    /* 通知读函数, 外部中断按键按键按下 */        
  53:              }
  54:          break;
  55:          
  56:          default:
  57:              //error
  58:              dwAction = 0x00;//MCU_STAT_NOCHANGE;
  59:          break;
  60:          }
  61:           //do real action
  62:      }  
  63:      return 0;
  64:  }

这里大家可以注意下CeSetThreadPriority,WaitForMultipleObjects和Swich这三处的使用技巧。

XXX_Init这个函数在参考文章中所起到的作用相同,都是对中断引脚进行初始化工作,完成对对应引脚的初始化后,对我们上面定义好的XXX_DetectThread进行一个创建线程,在初始化结束的时候创建两个读键的事件,下面给出参考代码,部分敏感部分用伪代码代替。

   1:  extern "C" DWORD MCL_Init(DWORD Index)
   2:  {
   3:  #if 1
   4:      BOOL bEn;
   5:      DWORD IDThread, dwPullUpOrDown;
   6:      IOCTL_INFO ioctl_info;    
   7:      WINCE_GPIO_DEFINE pin;                        
   8:      MSGQUEUEOPTIONS msgQueueOptions = {0};
   9:      MCUCTL *pMcuCtl = NULL;
  10:  #endif
  11:   
  12:      RETAILMSG(1,(TEXT("++MCU_Init!\r\n ")));   
  13:      InitializeCriticalSection(&m_removalLock);//add by mercury for lock and unlock 20090819
  14:  #if 1//初始化中断开始
  15:      pMcuCtl  =  (MCUCTL *)LocalAlloc(LPTR, sizeof(MCUCTL));
  16:      if(pMcuCtl == NULL) 
  17:      {
  18:          RETAILMSG(1, (TEXT("-MCU_INIT\r\n")));
  19:              return(0);
  20:          }
  21:      memset(pMcuCtl, 0, sizeof(MCUCTL));
  22:   
  23:      //set rtc thread to priority to 104
  24:   
  25:       pMcuCtl->dwPriority256 = MCU_DEFAULT_THREAD_PRIORITY;
  26:   
  27:      pMcuCtl->hGioEvt = CreateEvent(NULL, FALSE, FALSE, NULL);
  28:      pMcuCtl->hDeinitEvt= CreateEvent(NULL, FALSE, FALSE, NULL);
  29:      if((pMcuCtl->hGioEvt == NULL) || (pMcuCtl->hDeinitEvt == NULL)  ){
  30:          DEBUGMSG(ZONE_ERROR, (TEXT("MCU_Init::CreateEvent failed\r\n")));
  31:        RETAILMSG(1,(TEXT("MCU_Init::CreateEvent failed\r\n")));
  32:          goto Error;
  33:      }    
  34:      
  35:      pMcuCtl->hDetectThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) MCU_DetectThread, (LPVOID)pMcuCtl, 0, &IDThread);
  36:      if (pMcuCtl->hDetectThread == 0) 
  37:      {
  38:          DEBUGMSG(ZONE_ERROR, (TEXT("MCU_Init::: CreateThread Failed\r\n")));
  39:       RETAILMSG(1,(TEXT("MCU_Init::: CreateThread Failed\r\n")));
  40:       goto Error;
  41:      }
  42:   
  43:      pMcuCtl->hGio= CreateFile(L"GIO1:", GENERIC_READ | GENERIC_WRITE,0, NULL, OPEN_EXISTING, 0, NULL);
  44:      if(pMcuCtl->hGio == INVALID_HANDLE_VALUE) {
  45:          DEBUGMSG(ZONE_ERROR, (TEXT("MCU_Init::Open gio failed\r\n")));
  46:       RETAILMSG(1,(TEXT("MCU_Init::: Open gio Failed\r\n")));
  47:          goto Error;
  48:      }
  49:      
  50:      //init gpio here  
  51:      //power button    gpio init start
  52:      pin  = WINCE_DGPIO2;//BSP_GetPowerButtonIO();
  53:      if(pin  == WINCE_NULLIO) {
  54:          goto Error;        
  55:      }
  56:   
  57:      ioctl_info.pin = (    UINT32)pin;
  58:      //set input    
  59:      ioctl_info.parameter.Setting = GPIO_DIR_INPUT;
  60:      DeviceIoControl(pMcuCtl->hGio, DRIVER_GPIO_IOCTL_SET_DIRECTION,
  61:          (LPVOID)&ioctl_info, sizeof(IOCTL_INFO), NULL, 0 ,NULL, NULL);
  62:          
  63:      //set event handle    
  64:      ioctl_info.parameter.Setting = (UINT32)pMcuCtl->hGioEvt;
  65:      DeviceIoControl(pMcuCtl->hGio, DRIVER_GPIO_IOCTL_SET_HANDLE,
  66:          (LPVOID)&ioctl_info, sizeof(IOCTL_INFO), NULL, 0 ,NULL, NULL);
  67:      //polarity to trigger interrupt
  68:      ioctl_info.parameter.Setting = BSP_GetPowerButtonPolarity();
  69:      DeviceIoControl(pMcuCtl->hGio, DRIVER_GPIO_IOCTL_SET_POLARITY,
  70:          (LPVOID)&ioctl_info, sizeof(IOCTL_INFO), NULL, 0 ,NULL, NULL);    
  71:      //set change polarity after interrupt
  72:      ioctl_info.parameter.Setting = GPIO_IO_INT_CHG_POLARITY;
  73:      DeviceIoControl(pMcuCtl->hGio, DRIVER_GPIO_IOCTL_SET_PINCHANGE,
  74:          (LPVOID)&ioctl_info, sizeof(IOCTL_INFO), NULL, 0 ,NULL, NULL);    
  75:      //enable pull down
  76:      dwPullUpOrDown = BSP_GetPowerButtonPullUpDown(&bEn);
  77:      if(bEn) {    
  78:          ioctl_info.parameter.Setting = TRUE;
  79:      }
  80:      else {    
  81:          ioctl_info.parameter.Setting = FALSE;
  82:      }    
  83:      DeviceIoControl(pMcuCtl->hGio, dwPullUpOrDown,
  84:          (LPVOID)&ioctl_info, sizeof(IOCTL_INFO), NULL, 0 ,NULL, NULL);    
  85:      //enable power key interrupt
  86:      ioctl_info.parameter.Setting = GPIO_INTE_ENABLE;
  87:      DeviceIoControl(pMcuCtl->hGio, DRIVER_GPIO_IOCTL_SET_INTR,
  88:          (LPVOID)&ioctl_info, sizeof(IOCTL_INFO), NULL, 0 ,NULL, NULL);
  89:      //初始化结束
  90:      gReadKeyEvent[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
  91:      gReadKeyEvent[1] = CreateEvent(NULL, FALSE, FALSE, NULL);
  92:      RETAILMSG(1,(TEXT("--MCU_Init!\r\n ")));   
  93:      return(DWORD)pMcuCtl;    
  94:      
  95:  Error:
  96:      (VOID)MCL_Deinit((DWORD)pMcuCtl); 
  97:      RETAILMSG(1,(TEXT("--error !MCU_Init!\r\n ")));   
  98:      #endif
  99:      return(0);
 100:  }

 

这里请大家注意下CreateThread函数,这个函数是中断程序初始化函数中必须的,通过他去叫起前面定义的XXX_DetectThread函数。

XXX_Deinit函数是有始有终的表示,init对应的处理函数,在init函数中我们在最后的ERROR段中定义了几句话,就是针对当初始化出现问题是跳转至出错处理部分的处理,我们需要将一系列创建对象deinit,也就是将其销毁,防止占用空间以及中断再次启动的失败,也就是HJB大牛文章中的最后一句话:“在使用驱动调试助手调试有关中断的驱动程序时,需要善始善终,否则会出现中断不能正常工作的情况。”

下面给出参考代码

   1:  extern "C" BOOL MCL_Deinit(DWORD dwData)
   2:  {
   3:      MCUCTL *pMcuCtl = (MCUCTL *)dwData;
   4:      WINCE_AK7801_GPIO_DEFINE pin;        
   5:      pMcuCtl->bDeinit = TRUE;
   6:      RETAILMSG(1,(TEXT("MCU_DeInit!\r\n ")));
   7:      DeleteCriticalSection(&m_removalLock);
   8:  //add by mercury xu for lock and unlock si4730 20090819
   9:      if(pMcuCtl->hGio) {
  10:          IOCTL_INFO ioctl_info;            
  11:          //disable power button interrupt
  12:          pin = WINCE_AK7801_DGPIO2;//BSP_GetPowerButtonIO();
  13:          if(pin != WINCE_AK7801_NULLIO) {
  14:              ioctl_info.pin = (UINT32)pin;                 
  15:              ioctl_info.parameter.Setting = GPIO_INTE_DISABLE;
  16:              DeviceIoControl(pMcuCtl->hGio, DRIVER_GPIO_IOCTL_SET_INTR,
  17:                  (LPVOID)&ioctl_info, sizeof(IOCTL_INFO), NULL, 0 ,NULL, NULL);    
  18:          }
  19:          CloseHandle(pMcuCtl->hGio);
  20:          pMcuCtl->hGio = NULL;
  21:      }
  22:   
  23:      if(pMcuCtl->hDetectThread) {
  24:       SetEvent(pMcuCtl->hDeinitEvt);
  25:          WaitForSingleObject(pMcuCtl->hDetectThread, 10000);
  26:       CloseHandle(pMcuCtl->hDetectThread);
  27:          pMcuCtl->hDetectThread = NULL;     
  28:      }
  29:   
  30:      if(pMcuCtl->hDeinitEvt) {
  31:          CloseHandle(pMcuCtl->hDeinitEvt);
  32:          pMcuCtl->hDeinitEvt = NULL;             
  33:      }
  34:   
  35:      if(pMcuCtl->hGioEvt) {
  36:          CloseHandle(pMcuCtl->hGioEvt);
  37:          pMcuCtl->hGioEvt = NULL;             
  38:      }
  39:      
  40:      if(pMcuCtl->hPowerNotify) {
  41:          StopPowerNotifications(pMcuCtl->hPowerNotify);
  42:          pMcuCtl->hPowerNotify = NULL;     
  43:      }    
  44:      
  45:      if(pMcuCtl->hMsgQ) {
  46:          CloseHandle(pMcuCtl->hMsgQ);
  47:          pMcuCtl->hMsgQ = NULL;     
  48:      }    
  49:      LocalFree(pMcuCtl);
  50:      pMcuCtl = NULL;
  51:      SetEvent(gReadKeyEvent[1]);        /* 通知调用读函数的线程, 驱动已经关闭 */
  52:      CloseHandle(gReadKeyEvent[0]);            /* 关闭相关事件 */
  53:      CloseHandle(gReadKeyEvent[1]);
  54:      return TRUE;
  55:  }

 

最后是XXX_Read,因为这里我们采用的是中断方式,iocontrol在中断中不太适合使用,在轮询的方式下IOcontrol还是比较适合,这里XXX_Read函数中,我们通过pBuf来将值传递给API层的readfile文件中的lpBuffer,这里我们给出XXX_READ和READFILE两个函数的参考.

WINBASEAPI
BOOL
WINAPI
ReadFile(
    HANDLE hFile,
    LPVOID lpBuffer,//-> xxx_read :LPVOID pBuf,
    DWORD nNumberOfBytesToRead,
    LPDWORD lpNumberOfBytesRead,
    LPOVERLAPPED lpOverlapped
    );

DWORD xxx_Read(

  DWORD dwData,

  LPVOID pBuf,

  DWORD dwLen

);

下面给出参考代码

 

   1:  extern "C" DWORD MCL_Read(DWORD dwData,
   2:                 LPVOID pBuf,
   3:                 DWORD Len)
   4:  {
   5:      DWORD ret;
   6:      unsigned char  *pReadBuffer = NULL;
   7:      if ((pBuf == NULL) || (Len <= 0))
   8:          return 0;
   9:      pReadBuffer = (unsigned char  *)MapPtrToProcess(pBuf, GetCallerProcess());
  10:      *pReadBuffer = NULL;
  11:      
  12:          ret = WaitForMultipleObjects(2, gReadKeyEvent, FALSE, INFINITE);    
  13:      if (ret == WAIT_OBJECT_0)
  14:      {
  15:          ResetEvent(gReadKeyEvent[0]);
  16:          *pReadBuffer = sendOut[0];                                        /* 按键按下 */
  17:          return 1;
  18:      }
  19:      else if(ret == (WAIT_OBJECT_0 + 1))
  20:      {
  21:          ResetEvent(gReadKeyEvent[1]);
  22:          *pReadBuffer = sendOut[1];                                        /* 驱动关闭 */
  23:          return 1;        
  24:      }
  25:      return(0);
  26:  }

 

这里纠正下《WIinCE中断流式实现驱动和APP》提供代码中的一个bug,pReadBuffer = (unsigned char *)MapPtrToProcess(pBuf, GetCallerProcess());这里,需要强制类型转换一下MapPtrToProcess函数,因为该函数在msdn上描述为lpvoid型,而我们这里用到的pReadBuffer 是unsigned char*。

在这里我们也是需要等待我们所创建的gReadKeyEvent事件量,通过这个来判断中断执行的位置,同时作出相应的处理,到这里为止,驱动层的开发完成,对应的变量和结构体定义请大家按照自己的需求来定义,这里我只列出一个结构框架,大家可以做填空题的形式填空即可,这里也是符合wince开发的特点,填空式的开发。

应用层开发:

应用层的开发的灵活度比驱动层要大很多,这里我们以MFC为例,其实不论是MFC还是win32 api,其主旨都是在初始化的时候建立一个线程,类似于驱动层里的XXX_DetectThread和XXX_init之间的关系,这里我定义一个ReadKey1Thread函数,做为读取事件的入口,具体实现如下:

   1:  DWORD CMCUReadDlg::ReadKey1Thread(LPVOID lparam)
   2:  {
   3:      BYTE status;
   4:      DWORD actlen;
   5:      CString strCount;
   6:      CMCUReadDlg *pDlg = (CMCUReadDlg*)lparam;    
   7:      /* 取得对话框指针 */
   8:      CStatic *pCountStatic = (CStatic*)pDlg->GetDlgItem(IDC_NewShow);
   9:      /* 取得显示计数值的文本框指针 */
  10:      while(TRUE)
  11:      {
  12:          if (hStr == INVALID_HANDLE_VALUE)
  13:              break;                                                  /* 驱动未打开, 退出线程 */
  14:          if (ReadFile(hStr, &status, 1, &actlen, NULL) == TRUE)
  15:          {
  16:          Key1Count++;                                        /* 计数器计数 */
  17:          strCount.Format(_T("%d,0x%.2x"), Key1Count,status);
  18:          pCountStatic->SetWindowText(strCount);    /* 显示 */                
  19:          }
  20:          else
  21:          break;    /* ReadFile()执行错误 */
  22:      }
  23:      return 1;
  24:  }

这里大家注意下while的处理,while里我们用了READFILE来于驱动层进行配对联合,读取驱动层来的信息,同时将数据用变量获取出来,实现非常简单。

这个函数完成后就是在AP的初始化部分进行线程的创建,程序很简单:

   1:      hReadKey1Thread = CreateThread(0, 0, ReadKey1Thread, this, 0, &IDThread);
   2:   
   3:      if (hReadKey1Thread == NULL) 
   4:      {
   5:          CloseHandle(hStr);
   6:          hStr = INVALID_HANDLE_VALUE;
   7:          CloseHandle(hReadKey1Thread);
   8:          return FALSE;
   9:      }

 

以上我就把wince下中断驱动开发从驱动层到AP层的整个开发流程进行一个梳理,希望对大家有帮助。

你可能感兴趣的:(WinCE)