摘 要:為了提高物流行業(yè)的調(diào)度效率,提出了基于Windows CE.NET在多功能物流終端中實現(xiàn)模擬集群無線通信的方案。基于Windows CE.NET的多" title="的多">的多線程機制,討論了該通信過程中線程間的數(shù)據(jù)同步、通信協(xié)議的解析以及數(shù)據(jù)的分離等問題,并在eMbedded Visual C++ 4.0環(huán)境下實現(xiàn)集群模塊與多功能物流手持終端" title="手持終端">手持終端之間的通信。
關(guān)鍵詞:多功能物流手持終端? 集群通信? 協(xié)議幀格式? 串口" title="串口">串口通信? 多線程通信
?
??? 在現(xiàn)代企業(yè)中降低商品成本的一個重要方法就是降低貨物在流通中的成本,貨物的流通速度直接影響到生產(chǎn)的各個環(huán)節(jié),物流(Logistics)在現(xiàn)代經(jīng)濟的發(fā)展中起著越來越重要的作用。不論是企業(yè)的物流部門還是專門的物流公司都面臨著調(diào)度效率的問題。提高調(diào)度效率的方法是在提高物流管理技術(shù)的同時實現(xiàn)物流技術(shù)的信息化,提高物流技術(shù)信息化的程度。雖然國內(nèi)在物流行業(yè)里也引進了信息化,但信息化的程度與國外相比還是低得多。國內(nèi)的物流終端主要集中在車載型且功能相對簡單,這樣調(diào)度的實時性比較差。
??? 模擬集群通信(Analog Trunking Communication)技術(shù)已經(jīng)相對比較成熟,強大的調(diào)度功能、組呼功能和快速呼叫的特性使得集群通信深受大企業(yè)或集團公司的青睞。對于物流行業(yè)而言,集群通信技術(shù)也是其信息化的一個方向,通過無線調(diào)度實現(xiàn)快速、便捷、有效的貨物投遞、配送,實現(xiàn)有效的實時無線調(diào)度,提高其調(diào)度效率。這里的物流終端是集手機、數(shù)字集群、模擬集群、GPS定位、條碼掃描、無線上網(wǎng)、快速打印等功能于一體的集成度很高的多功能手持終端,針對不同的用戶可以方便快捷地進行不同的功能模塊配置。
????以下將討論在基于Windows CE.NET的多功能物流手持終端上如何用多線程編程實現(xiàn)其與集群模塊通信,以及如何解析集群模塊中發(fā)往手持終端的數(shù)據(jù)。
1 集群模塊與手持終端通信協(xié)議幀格式
??? 集群通信中的協(xié)議幀格式如下:
?? ?SYNC + LENGTH + DIR + COMMAND + DATA +
??? CHECKSUM
??? SYNC:??0x96 (包頭)
??? LENGTH:?DIR(1B)+COMMAND(1B)+DATA(nB) +
??? CHECKSUM(1B)
??? DIR:??0x80 或0x81?
??? COMMAND:?協(xié)議號
??? DATA:??協(xié)議數(shù)據(jù)
??? CHECKSUM:?SYNC ^ LENGTH ^ DIR ^ COMMAND ^
?????????? ??? DATA? 即全部字節(jié)的異或
??? 其中為了保證通信正常完成,LENGTH約定要不大于125字節(jié);?DIR?表示發(fā)送方向: ?0x80表示 上位機>>下位機,?0x81表示下位機>>上位機;(上位機指手持終端,下位機指集群模塊);DATA長度不大于122個字節(jié);COMMAND表示協(xié)議命令包括0x10,0x11,0x12, 0x13,0x15,0x16等;CHECKSUM是數(shù)據(jù)結(jié)束校驗位,判斷數(shù)據(jù)報是否結(jié)束。
??? 在該協(xié)議中漢字采用國標碼進行傳輸,其他字符或數(shù)字等都采用ASCII碼進行顯示或發(fā)送,在這個協(xié)議中字符都有特殊意義,例如:‘<’ 表示下翻, ‘>’ 表示上翻,‘C’表示清除等。
2 Windows CE.NET多線程機制
??? ?Windows CE.NET中線程類似于Windows XP中的線程,每個進程都有一個主線程" title="主線程">主線程,而且還可以創(chuàng)建任意多個子線程,但受系統(tǒng)存儲器大小和堆棧大小的約束,實際上在一個進程中不能創(chuàng)建過多的線程。
線程是在創(chuàng)建它的進程的地址空間中執(zhí)行代碼,并且在該進程的地址空間中進行數(shù)據(jù)操作。一個進程的地址空間中可以有多個線程來共享使用,這個進程的所有線程可以執(zhí)行相同的代碼,對相同的數(shù)據(jù)進行操作,而且這些線程還可以共享相同的內(nèi)核對象句柄。多個線程既然可以執(zhí)行相同的代碼并對相同的數(shù)據(jù)進行操作,就會產(chǎn)生在一個程序里如何控制數(shù)據(jù)同步的問題。這也是多線程編程的一個難點。Windows CE.Net環(huán)境下控制線程同步的方法很多,但用起來也很容易出問題。常用的控制線程同步的方法有:
??? (1)事件對象(Event Object)機制。事件對象是一種在通知狀態(tài)或不在通知狀態(tài)的同步對象,線程可以通過捕獲事件來達到同步。可以創(chuàng)建手動或者自動復位的事件對象,通過命名和共享事件對象來達到不同線程或進程之間的同步。
??? (2)信號量" title="信號量">信號量(Semaphores)。信號量是通過計數(shù)來控制進程或線程間同步的,只要信號量的計數(shù)值大于0,信號量就可以被使用。當計數(shù)計到0時該信號量就處于不可用狀態(tài),它所控制的資源就不可用,直到其他線程釋放了該信號量使得計數(shù)值大于0才可用。適合多個線程間的通信。
??? (3)互斥量(Mutexes)。當一個線程獲得該互斥量時,該互斥量就被鎖定,其他線程就不能使用,直到該互斥量被釋放。??
當然還有其他的方法來控制線程的同步。多線程通過線程間的通信可以達到同步,節(jié)省系統(tǒng)資源,減少損耗。在接下來的串口通信中選擇了事件觸發(fā)機制來控制線程的運行。
3 集群模塊與手持終端的通信以及數(shù)據(jù)的提取和分析
集群模塊負責接收調(diào)度中心或其他終端發(fā)出的信號,手持終端自身的CPU負責處理模塊接收的信息。手持終端的CPU與集群模塊之間通過串口進行通信,首先設(shè)定串口參數(shù)如下:
??? 波特率?????????????????? 9 600bps
??? 數(shù)據(jù)位?????????????????? 8bit
??? 校驗位?????????????????? 無
??? 停止位?????????????????? 1 bit
????串口參數(shù)的設(shè)定以及串口操作在這里通過Windows CE.NET自帶的API函數(shù)來完成,其步驟是:
????(1)打開串口
???? HANDLE? hComPort = CreatFileCreateFile(pszDevName,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING, 0, NULL);
??? 其中,pszDevName表示要打開的串口號,在Windows CE系統(tǒng)中其形式應該為TEXT(“COM1:”)或L“COM1:”,這與UNICODE相一致,當串口打開成功,hComPort應該返回打開的串口句柄,失敗則返回INVALID_HANDLE_VALUE。
??? (2)按上述要求設(shè)定串口參數(shù)
????定義一個DCB結(jié)構(gòu)變量dcb,然后依次設(shè)定該結(jié)構(gòu)的參數(shù):
?? ?dcb.DCBlength = sizeof (dcb);
??? GetCommState (hLocal, &dcb);???//獲得當前串口的參數(shù)放到當前dcb中
??? dcb.BaudRate = CBR_9600; ?????? //設(shè)定串口波特率為要求的9600bps
??? dcb.fParity = FALSE;??? ???//設(shè)定校驗位為無
??? dcb.fNull = FALSE;
??? dcb.StopBits = ONESTOPBIT;? ??//設(shè)定一位停止位
??? dcb.Parity = NOPARITY;? ?????? //低4位校驗設(shè)置為否
??? dcb.ByteSize = 8;????? ???? //數(shù)據(jù)位設(shè)定為8位
??? SetCommState (hLocal, &dcb); ?
??????? //把設(shè)定的參數(shù)寫入串口設(shè)定
?? ?設(shè)定串口時間常數(shù):設(shè)定一個COMMTIME OUTS結(jié)構(gòu)變量cto
??? cto.ReadIntervalTimeout = 0; ?
??????? //讀取兩個字符間隔時間設(shè)定
?? ?cto.ReadTotalTimeoutMultiplier = 0;???? //設(shè)定
??? cto.ReadTotalTimeoutConstant = 0;????//設(shè)定讀超時時間為無限
??? cto.WriteTotalTimeoutMultiplier = 0;????//設(shè)定寫超時時間為無限時
??? SetCommTimeouts (hLocal, &cto); //把時間設(shè)定寫進串口
??? 至此,串口初始化編程設(shè)定完畢。
??? 由于這里涉及實時收發(fā)數(shù)據(jù),為了提高主線程響應其他事件的速度,在主線程中創(chuàng)建一個獨立的子線程來監(jiān)聽串口,通過觸發(fā)事件EV_RXCHAR通知線程去讀串口緩沖區(qū)來得到數(shù)據(jù)。主線程與子線程的調(diào)度關(guān)系如圖1所示。
?
?
????主線程負責處理主窗口及其控件的消息以及子線程發(fā)送過來的消息,子線程主要負責處理串口消息及數(shù)據(jù),大大減小了主線程響應事件的壓力,提高了系統(tǒng)的響應速度。
????以下討論如何操作子線程讓其很好地收發(fā)并解析數(shù)據(jù)。
? 串口監(jiān)聽線程程序流程圖如圖2所示。從中可以看出線程中設(shè)定了事件EV_RXCH-AR,當線程守候到該消息后執(zhí)行讀串口事件,否則線程繼續(xù)等待該消息,這樣既節(jié)省系統(tǒng)資源,也省去不斷地讀串口,減少了資源消耗。
?
??? 其中,串口初始化部分已經(jīng)在前面介紹了,其余程序的簡單實現(xiàn)如下:
??? hRecvThread = CreateThread(NULL, 0, ReceiveProc, hWnd, 0, &dwStat); ???//創(chuàng)建接收線程監(jiān)聽串口
?? ?if (hRecvThread)
???? ??CloseHandle (hRecvThread);
???? 如果子線程創(chuàng)建成功,則線程啟動轉(zhuǎn)入線程處理函數(shù)(RecvProc)處理接收來的數(shù)據(jù)。
???? RecvProc實現(xiàn)對串口的監(jiān)聽,實時接收從集群模塊發(fā)到串口的數(shù)據(jù)。為了實現(xiàn)這一實時功能,通過串口信號觸發(fā)一個事件來通知程序去接收數(shù)據(jù),這樣既可以比較準確地接收數(shù)據(jù),也可以減少處理器不斷查詢串口對系統(tǒng)資源的消耗。下面是簡化的線程處理函數(shù)的實現(xiàn):
??? DWORD WINAPI RecvProc(PVOID pArg) {
??? PurgeComm(hComPort, PURGE_RXCLEAR);
??????????? //清空串口的讀緩沖區(qū)
??? SetCommMask (hComPort, EV_RXCHAR);
?? ?????????? //設(shè)定串口讀事件EV_RXCHAR
??? while (TRUE)
??? {
?? ?if(WaitCommEvent(hComPort, &evtMask, 0)) {
? ????????????? // 等待串口EV_RXCHAR事件的發(fā)生
??? SetCommMask(hComPort, EV_RXCHAR);
??????? //如果事件發(fā)生則重新設(shè)定事件以便下次能再觸發(fā)
??? if(evtMask&EV_RXCHAR)
??? {???? ?????????????????????? //串口有數(shù)據(jù)讀入
??? ClearCommError(hComPort,&dwReadErrors, &cmState);
??? willreadlen = cmState.cbInQue;
???????????????? //查看串口讀緩沖區(qū)里數(shù)據(jù)的實際長度
? if (willreadlen <= 0) {
continue;
? }
? memset(recvText, '0', sizeof(recvText));
??? if(!ReadFile (hComPort, recvText, willreadlen, &cBytes, 0)) //讀串口數(shù)據(jù)到
?{
?if(hComPort= =INVALID_HANDLE_VALUE)
?return 0;
?? ?}
??? …… //按照數(shù)據(jù)包的定義從串口數(shù)據(jù)中提取一個完整的包
???????? }
}
?}
??? 程序至此完成從串口緩沖區(qū)讀數(shù)據(jù)的過程,下一步程序設(shè)計的任務是如何分析分離數(shù)據(jù)。
? 根據(jù)數(shù)據(jù)協(xié)議格式可以知道,這里的數(shù)據(jù)包有相同的包頭(0x96),通過包頭和數(shù)據(jù)長度(LENGTH)就可以把數(shù)據(jù)分離成一個個數(shù)據(jù)包,再根據(jù)協(xié)議號(COMMAND)的不同可以完全解析出該數(shù)據(jù)的含義,從而對數(shù)據(jù)做出相應的決策(送上顯示或報警等)。以下給出一段解析數(shù)據(jù)包的示例:假設(shè)數(shù)據(jù)包存放在數(shù)組RecvText中,則:
??? command = RecvText[3];
??? switch(command){? //由協(xié)議號來解析數(shù)據(jù)
? case 0x12: //數(shù)據(jù)送到上面界面顯示
????if(RecvText[4] == 0x01){?
????????? //表示送到主窗口的第一行顯示
? ??……
?? ?}
??? else{ ????? //表示送主窗口的第二行顯示
……
??}
??break;
??? case 0x13: ?//提取手持機電池電量和集群信號強度,
???? 為判斷手持機能否工作提供提示
??? ?if(RecvText[4] = = 0x01){
????????? //表示該數(shù)據(jù)包為顯示信號強度
??????? …… //把RecvText[5](表信號強度值)進行處理
?????????? }
?????????? else{ ??????? //表示該數(shù)據(jù)包數(shù)據(jù)是電池電量
??? ?? …… ?????//發(fā)到界面顯示電池電量
?????????? }
?????????? break;??????????????????????????????????????????
??? ?case 0x15: ??//進入或退出讀出或設(shè)置集群模塊內(nèi)部具體地址空間的數(shù)據(jù)狀態(tài)
???? ……
???? break;
???? case 0x16: ????? //在進入讀取集群模塊內(nèi)部數(shù)據(jù)狀態(tài)后,讀取或設(shè)置具體地址空間的值
???? ……
???? break;
?}
該程序內(nèi)部具體的實現(xiàn)依據(jù)界面不同其內(nèi)部函數(shù)實現(xiàn)也不同,這里不一一羅列了。
至此程序完成了集群模塊與手持終端間的通信功能,實現(xiàn)了對下位機發(fā)往上位機數(shù)據(jù)的分離與解析,為上層界面對數(shù)據(jù)的處理做好了充分的準備。
??? 以上討論了集群模塊與多功能物流手持終端之間通信的協(xié)議格式,Windows CE.NET的多線程機制,以及如何利用多線程實現(xiàn)集群模塊與手持終端之間的通信,并對數(shù)據(jù)的分離解析以及串口的配置作了討論。通過對應用程序的設(shè)計,可以完全實現(xiàn)集群模塊與手持終端之間的通信以及數(shù)據(jù)分離解析,為上層界面提供完整的數(shù)據(jù)服務,使該終端能實現(xiàn)集群通信功能,滿足物流行業(yè)方便快捷調(diào)度的需要。上述程序在eMbedded Visual C++ 4.0環(huán)境下編譯,下載到Windows CE.NET 的手持終端上能正常穩(wěn)定地運行,并且其功能與專門的對講機相比具有更好的可視性、操作更靈活的特點。
參考文獻
[1]? 汪 斌,李存斌,陳鵬等. EVC高級編程及其應用開發(fā),北京:中國水利電力出版社,2005.
[2] ?BOLING D. Programming Microsoft Windows CE. NET,?Third Edition, Microsoft Press, 2003.
[3] ?周毓林. Windows CE.NET內(nèi)核定制及應用開發(fā).北京:?電子工業(yè)出版社,2005.
?
?