预备文章:
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层的整个开发流程进行一个梳理,希望对大家有帮助。