怯肇乾,史繼峰
(南昌職業(yè)大學(xué) 信息技術(shù)學(xué)院,南昌 330500)
現(xiàn)有手勢識別導(dǎo)航,最常見的是紅外收發(fā)/分析識別,雖然經(jīng)典,但距離短,通常最遠(yuǎn)只有150 mm,若作為論壇、會展、會議等演示場合應(yīng)用,使用局限,不現(xiàn)實際;更有傳統(tǒng)的激光筆導(dǎo)航,雖然簡便,但需要正對接收操作,客戶體驗不佳;還有重大國際/國家類論壇、會展中運用的攝像識別,盡管操控便捷而且技術(shù)高大上,但價格不匪,難以拓展應(yīng)用到廣泛的課堂教學(xué)等尋常場景。
教學(xué)、論壇、會展、會議市場需求空間巨大,尋找和建立更為可靠、準(zhǔn)確、高效、廉價的檢測傳感器和手勢識別算法模型意義重大。經(jīng)反復(fù)查閱和對比,最終聚焦了激光飛秒測距ToF(Time of Flight)傳感器和“人工智能AI(Artificial Intelligence)深度學(xué)習(xí)自適應(yīng)”算法建模,于是有了這個省廳科技項目——激光飛秒手勢導(dǎo)航簡易微系統(tǒng)實現(xiàn)。
采用高性價比的激光飛秒測距ToF傳感器,得到原始距離數(shù)據(jù)??蛻趔w驗考慮,使用3個傳感器呈上、左、右布局[避免1個傳感器不合常規(guī)的特定手勢定義與強制學(xué)習(xí)],以此3路距離數(shù)據(jù)組成采集數(shù)據(jù)輸入。以最簡潔的“人工智能AI(Artificial Intelligence)深度學(xué)習(xí)自適應(yīng)”算法模型——“后向反饋神經(jīng)元網(wǎng)絡(luò)BP_NN(Back Propagation Neural Networks)”為核心,輔以“智能模糊控制”和“專家經(jīng)驗控制”,構(gòu)成“運算處理分析”中心,最終得到并輸出“手勢數(shù)據(jù)信息”[1-3,18]。整個“ToF手勢識別算法”模型如圖1所示。BP_NN運算處理分析框架,主要由適配器和調(diào)整器構(gòu)成;適配器,前向傳輸(Feed Forward),逐層波浪式的傳遞輸出值[左右、上下、前后];調(diào)整器,逆向反饋(Back Propagation),反向逐層調(diào)整權(quán)重和閾值,修正誤差至最小?!爸悄苣:刂啤焙汀皩<医?jīng)驗控制”,進行前級“預(yù)處理”和后級“結(jié)果識別”;前級“預(yù)處理”,通過“濾波”,得到50~600 mm內(nèi)的傳感器“視場出入及其運動的距離和時刻數(shù)據(jù)”,并對快速移動情形完成數(shù)據(jù)插入,便利BP_NN更加有效運算;后級“結(jié)果識別”處理,即后處理,對BP_NN運算得到的左右、上下、前后數(shù)據(jù)進一步處理分析,得到左右、上下、點擊等手勢直接數(shù)據(jù)。借助專家經(jīng)驗,可以把實際操控中多數(shù)習(xí)慣的“斜上再斜下”的動作解釋為前后運動的“點擊”。
圖1 激光飛秒手勢識別算法模型框圖
BP_NN框架,選用3-4-3層次,如圖2所示,3個輸入層,4個隱藏層,3個輸出層,便于常用的Cortex-Mx/RISC-V類型的微控制器MCU (Microcontroller Unit)在軟件層次上進行快速高效的實現(xiàn)[MCU,優(yōu)勢在控制,數(shù)字信號處理是弱勢]。BP_NN運算,經(jīng)典成熟,不再贅述相關(guān)過程的各個離散處理公式,其關(guān)鍵是誤差計算及其對各層權(quán)值和閥值的修正,盡管已經(jīng)采用最簡層次框架,放在MCU上實現(xiàn)仍然運算繁鎖、耗時、耗資源,便于MCU硬軟件進一步最簡化及其成本最少考慮起見,借助Mablib、Maple等的NN工具函數(shù)和仿真功能,針對實際測量數(shù)據(jù),訓(xùn)練各層的權(quán)值和閥值,形成表格,縮短訓(xùn)練和學(xué)習(xí)時間,即采用Mablib實現(xiàn)適配器,MCU僅做調(diào)整器實現(xiàn)。環(huán)境光的變化是手勢準(zhǔn)確度的最大影響因素,從系統(tǒng)實用和精簡方面考慮,訓(xùn)練4種常用光環(huán)境:夜晚、白熾燈、日光燈、太陽光,MCU系統(tǒng)在硬件上采用以光敏電阻-模數(shù)轉(zhuǎn)換ADC(Analog-to-Digital Converter)電路進行選擇識別。
圖2 后向反饋神經(jīng)元網(wǎng)絡(luò)層次構(gòu)成示意圖
MCU在BP_NN中執(zhí)行前向輸出的運算公式如下,這里激勵函數(shù)采用修正線性單元ReLU(Rectified Linear Unit),其中x/h/y分別對應(yīng)輸入/隱藏/輸出各層神經(jīng)元節(jié)點,w/θ表示相應(yīng)前級的權(quán)重和閥值。
系統(tǒng)由三部分組成:信號采集識別發(fā)送終端(以下簡稱采發(fā)終端)、手勢數(shù)據(jù)收集轉(zhuǎn)發(fā)終端(以下簡稱收轉(zhuǎn)終端)和視窗展示服務(wù)軟件(以下簡稱視窗展示軟件)。短距離無線通信采用抗干擾強、傳輸距離大、穿墻能力強、功耗低(發(fā)射最大功耗35 mA)的遠(yuǎn)距無線LoRa通信(Long Range Radio)形式。由于系統(tǒng)主要工作在Windows、UOS等通用操作系統(tǒng)下,并且服務(wù)以優(yōu)秀的微軟office等辦公軟件,轉(zhuǎn)換接口采用精簡易用的WinUSB形式。終端微控制核心MCU,選用高性價比的32位的GD32F103TB(Cortex-M3內(nèi)核)或GD32VF103TB(RISC-V內(nèi)核),以C語言編程實現(xiàn)BP_NN適配器及其TOF數(shù)據(jù)采集與LoRa、USB傳輸通信。視窗展示軟件,以C++語言編程實現(xiàn)WinUSB驅(qū)動,以C++或Python語言編程實現(xiàn)手勢隨動的各個視窗切換,伴隨視窗上隱含的微透明動態(tài)指示,既可完成基本的PPT幻燈選擇播放,也可完成包括office(word、excel、ppt等)、acrobat(pdf電子文檔)在內(nèi)常用辦公軟件的遠(yuǎn)程切換翻頁操作,更可完成涵蓋辦公軟件在內(nèi)包括eclipse、notepad等集成開發(fā)環(huán)境的遠(yuǎn)程研發(fā)演示,即3個版本:幻燈播放版、辦公播控版和開發(fā)播控版[4-5,17]。完整的系統(tǒng)構(gòu)成及其核心算法模型與語言編程實現(xiàn),如圖3所示,詳細(xì)的微產(chǎn)品系統(tǒng)如圖4演示PPT截圖所示,圖4還示意了產(chǎn)品系統(tǒng)化的生產(chǎn)測試與配置的演示軟件。
圖3 微系統(tǒng)構(gòu)成及其核心技術(shù)手段示意圖
圖4 微產(chǎn)品系統(tǒng)應(yīng)用及其測試與配置示意圖
3.1.1 電子電路設(shè)計
核心功能實現(xiàn),以MCU片上接口——內(nèi)部集成電路IIC(Inter-Integrated Circuit)掛接ToF傳感器、異同步串行收發(fā)器USART(Universal Synchronous/Asynchronous Receiver/Transmitter)掛接LoRa無線模塊和ADC片上外設(shè)連接光敏電阻,同時以其片內(nèi)實時時鐘RTC(Real_Time Clock)完成ToF測距時刻記錄。便攜低功耗設(shè)計考慮,采用1 000~2 500 mA扁平鋰電池供電,以高性價比的ETA7640芯片作為充供電管理,同時設(shè)置按鈕喚醒重啟電路。ToF激光發(fā)射測距和LoRa數(shù)據(jù)發(fā)送,最大功耗達(dá)100 mA,供電轉(zhuǎn)換電路選用性能優(yōu)良的大功率LM1117_3V3,無檢測傳感時必須預(yù)以關(guān)斷,因而設(shè)計10 min內(nèi)無動作時通過軟件關(guān)閉系統(tǒng),再次使用時以上述按鈕喚醒重啟。由于采用QFN36最小MCU封裝,此類MCU無VB電池待機/休眠管理腳,則控制關(guān)斷時直接切斷LM117供電電路。電源開關(guān)選用P溝通MOS場效應(yīng)管PDN304P。ToF傳感器2.8 V供電時性能最佳,選用XC6206P28MR為其集群獨立供電,3.3 V與2.8 V之間的IIC總線采用N溝通MOS場效應(yīng)管2N7002過渡。模塊化電路設(shè)計,MCU與ToF傳感器及其喚醒按鈕構(gòu)成小頂板,大底板攜帶鋰電池及其充供電路,并背附LoRa無線通信模塊。一大一小兩板在兩側(cè)通過“信號排針”連通[5,11,17]。終端平面大小不超過鋰電池大小35 mm×50 mm,包括鋰電池在內(nèi)整體厚度控制在8 mm內(nèi),“胸針”或“別針”形式,供胸前佩帶,充分體現(xiàn)“便攜”。選用透明熱縮管外封,既時尚大方又可免去昂貴的模具設(shè)制費用。識別模塊設(shè)計最大持續(xù)工作時間不低于8h@1 000 mA,總體重量控制在250 gf內(nèi)。電路原理及其整體外觀造型如圖5所示。
圖5 采發(fā)終端電路原理及其整體造型示意圖
3.1.2 軟件體系構(gòu)造及其編制
穩(wěn)定高效設(shè)計及其開發(fā)考慮,基于MCU寄存器和多中斷處理機制,構(gòu)造嵌入式軟件應(yīng)用體系,這里采用項目主持人的發(fā)明專利——ARM/RISC-V系列微控制處理器軟件架構(gòu)工具,快速得到包括啟動代碼、系統(tǒng)時鐘變換配置、片內(nèi)接口/外設(shè)驅(qū)動程序、中斷分配及其處理程序、多任務(wù)調(diào)度程序等的基本框架代碼,直接在此基礎(chǔ)上展開填空式的功能代碼設(shè)計。
3.1.2.1 TOF測距及其BP_NN手勢識別
TOF測距,采用意法半的VL53L0X模塊,需要兩級驅(qū)動,一級驅(qū)動IIC傳輸完成數(shù)據(jù)讀寫,二級驅(qū)動,移植相應(yīng)的應(yīng)用程序接口API(Application Programming Interface),配合模塊內(nèi)嵌算法處理得到最終距離數(shù)據(jù)。采用兩個中斷處理完成核心操作:IIC中斷和距離獲取事件中斷,優(yōu)先級最高,僅次任務(wù)調(diào)度的時鐘節(jié)拍中斷。兩級中斷的使用,把單次測距時間提升了18 ms內(nèi)(廠家提供的最小值為22 ms)。
中斷處理IIC操作,效率最優(yōu),但時序節(jié)點把握要求苛刻,特別是單字節(jié)的讀操作,很難駕馭,常常因此造成系統(tǒng)停滯,多采用通用輸入輸出GPIO(General-Purpose Input/Output)模擬實現(xiàn),軟件架構(gòu)工具得到的IIC驅(qū)動程序,很好解決了這個問題[19-21]。核心的底層IIC驅(qū)動程序流程如圖6所示。
圖6 IIC中斷收發(fā)驅(qū)動程序流程圖
關(guān)鍵的單字節(jié)讀取IIC操控時序如圖7所示,先寫指定寄存器地址,再從中讀出數(shù)據(jù)。
圖7 恰當(dāng)?shù)钠瑑?nèi)IIC驅(qū)動程序流程圖
距離獲取事件中斷,設(shè)置任務(wù)啟動標(biāo)識,處理程序在濾波插值、BP_NN適配輸出、手勢識別3個順序任務(wù)中實現(xiàn),首先是濾波插值,依據(jù)專家經(jīng)驗,只取連續(xù)(最大時間間隔100 ms)有效手勢距離(110~550 mm內(nèi))構(gòu)成原始數(shù)據(jù)隊列(長度30內(nèi)),且每個傳感器應(yīng)用至少3個數(shù)據(jù),數(shù)量不足按照進出計量場的數(shù)據(jù)變化規(guī)律插入,然后是BP_NN適配和手勢識別[8-9,19-21],核心的處理函數(shù)代碼如下:
char vldDtLgth = 0, vldDtFlg = 0, lstGst =‘z’; // 有效數(shù)據(jù)數(shù)量、標(biāo)識、上次手勢值
VL53L0X_RangingPoint vldDt[30] = {0}; // 有效數(shù)據(jù)存儲
unsigned int lstTs = 0; // 最近測量活動時間記錄
void tof_bpnn_process(char maxNum) { // ToF數(shù)據(jù)分析處理[傳感器最大數(shù)量]
uint8_t i, m; unsigned short v; unsigned int ts;
VL53L0X_RangingMeasurementData_t msData[maxNum];
for(i=0; i if(((alarm_flag>>i)&1) 1) { alarm_flag &= ~(1 << i); VL53L0X_GetRangingMeasurementData(&vl53l0x_dev[i], &msData[i]); // 獲取測量距離 msData[i].TimeStamp = RTC_GetCounter(); ts = msData[i].TimeStamp; v = msData[i].RangeMilliMeter; if((v>110)&&(v<550)) { if((vldDtLgth 0)&&(vldDtFlg 0)) { vldDt[vldDtLgth].dvcNum = i; vldDt[vldDtLgth].TimeStamp = msData[i].TimeStamp; vldDt[vldDtLgth].RangeMilliMeter = msData[i].RangeMilliMeter; vldDtLgth += 1; lstTs = ts; vldDtFlg = 1; if((optFlg>>1)&1) printf("**%d-%d: %4dmm,%8d;
",vldDtLgth, i, v, ts); } else { if(((ts-lstTs)<10)&&(vldDtLgth<30)&&(vldDtFlg 1)) { vldDt[vldDtLgth].dvcNum = i; vldDt[vldDtLgth].TimeStamp = msData[i].TimeStamp; vldDt[vldDtLgth].RangeMilliMeter = msData[i].RangeMilliMeter; vldDtLgth += 1; lstTs = ts; if((optFlg>>1)&1) printf("**%d-%d: %4dmm,%8d;
",vldDtLgth, i, v, ts); } else vldDtFlg = 2; } } VL53L0X_ClearInterruptMask(&vl53l0x_dev[i],0); // 清除VL53L0X中斷標(biāo)志位 } } if((vldDtFlg 1)&&((RTC_GetCounter()-lstTs)>53)) vldDtFlg = 2; if(vldDtFlg 2) { // 手勢分析識別 VL53L0X_DtSetInterpolation (); // 插值 VL53L0X_BpNnAdepter(); // BP_NN適配輸出 VL53L0X_gestureRecognition(); // 手勢識別 if(gestureFlg) { // Lora外傳 printf(" gesture:%d
", m); gesture[5] = m | 0x30; USART1_SendData(gesture, 6); } } } 3.1.2.2 LoRa配置及其有效數(shù)據(jù)無線發(fā)送 配置包括LoRa地址、通道、升級通信速率,并進入易用的透明傳輸方式,需要反復(fù)寫入驗證特別耗時,僅做一次即可,無需每次啟動時都執(zhí)行,設(shè)計快速啟動運行,運行中需要修改時通過UART串口或USB接口通信實現(xiàn),盡管耗時,卻一勞永逸,相關(guān)操控函數(shù)代碼如下: void Lora2G4_smpInit(void) { // Lora2G4模塊快速常規(guī)初始化 PIO_DataOutput(0, 5, 1); // 設(shè)置進入連續(xù)透傳工作模式 PIO_DataOutput(0, 6, 0); PIO_DataOutput(0, 7, 0); while(PIO_DataInput(0, 4) 0); // 等待模塊有效 } void Lora2G4_vInit(void) { // Lora2G4模塊配置初始化 unsigned char i, b[15] = {0}; unsigned char a[6] = { 0xC0, // 工作模式: 參數(shù)掉電保存 {0x30, 0x31}, 0x28, 0x03, 0x04 }; // 地址, 波特率, 通道號,透傳/功耗 inFlshByteRead(0, AddrChnl, 3); // Lora地址通道號 a[1] = AddrChnl[0]; a[2] = AddrChnl[1]; // Lora地址通道號 a[4] = AddrChnl[2]; PIO_DataOutput(0, 5, 1); // 設(shè)置模塊進入配置模式 PIO_DataOutput(0, 6, 1); PIO_DataOutput(0, 7, 1); while(PIO_DataInput(0, 4) 0); // 等待模塊有效 do { // 配置參數(shù)及其驗證 Delay(300); USART1_SendData(a, 6); // 發(fā)送配置參數(shù) Delay(300); b[0] = b[1] = b[2] = 0xC1; // 驗證配置 USART1_SendData(b, 3); Delay(100); i = USART1_QueueRcvData(b); }while((b[0]!=a[0]) || (b[1]!=a[1]) || (b[2]!=a[2]) || (b[3]!=a[3]) || (b[4]!=a[4]) || (b[5]!=a[5]) || (i!=6) ); PIO_DataOutput(0, 6, 0); // 設(shè)置進入連續(xù)透傳模式 PIO_DataOutput(0, 7, 0); USART1_CR1 &= ~6; // USART波特率調(diào)整: 禁止收發(fā) USART1_BRR = 0x00000138; // 波特率115200 USART1_CR1 |= 6; // 收發(fā)使能 while(PIO_DataInput(0, 4) 0); // 等待模塊有效 } void USART0_Process(void) // USART0數(shù)據(jù)收發(fā)處理 { unsigned int status = USART0_SR; if(status&15) return; // 異常處理: 校驗錯, 幀錯, 噪聲, 溢出 else if((status>>5)&1) // 數(shù)據(jù)接收(環(huán)形隊列存放, 隊列滿則拋掉) { status = Usart0RcvPrt + 1; if(status>Usart0QueueLth) status = 0; if(status!=Usart0AppPrt) { status = USART0_DR; switch(Usart0RcvFlg) { case 0: if(status '*') Usart0RcvFlg += 1; break; case 1: if(status ' ') Usart0RcvFlg += 1; else Usart0RcvFlg = 0; break; case 2: case 3: case 4: Usart0RcvFlg += 1; break; default: break; } Usart0RcvQueue[Usart0RcvPrt++] = status; if(Usart0RcvPrt>=Usart0QueueLth) Usart0RcvPrt = 0; } else USART0_DR; } } 3.2.1 電子電路設(shè)計 收轉(zhuǎn)終端,USART串口無線LoRa接收,通用串行總線USB(Universal Serial Bus )“二傳”并供電,采用MCU片內(nèi)外設(shè)——USB2.0全速模塊[5-8][10-12],電路原理及其正反面mini造型如圖8所示。 圖8 收轉(zhuǎn)終端原理及其正反面mini造型圖 3.2.2 軟件體系構(gòu)造及其編制 仍然采用項目主持人的發(fā)明專利——ARM/RISC-V系列微控制處理器軟件架構(gòu)工具,快速得到基本框架代碼(包括USB驅(qū)動程序),在此基礎(chǔ)上展開填空式的功能代碼設(shè)計。采用USART中斷和USB中斷進行LoRa無線透傳信息接收和USB收發(fā)通信,中斷里接收,相應(yīng)任務(wù)中分析處理[17,22]。 LoRa手勢信息接收,在中斷處理程序中設(shè)置環(huán)形隊列高效接收識別,相應(yīng)任務(wù)中轉(zhuǎn)包調(diào)用USB發(fā)送任務(wù)上傳,關(guān)鍵程序代碼如下: int USART1_QueueRcvData(unsigned char *Data) // 環(huán)形隊列中斷數(shù)據(jù)接收 { int i = 0; USART1_CR1 |= 1 << 2; // 使能啟動接收 if(Usart1AppPrt { do { i++; *Data++ = Usart1RcvQueue[Usart1AppPrt++]; } while(Usart1AppPrt!=Usart1RcvPrt); } else if(Usart1AppPrt>Usart1RcvPrt) // 隊列反向增長數(shù)據(jù)接收 { do { *Data++ = Usart1RcvQueue[Usart1AppPrt++]; i++; if(Usart1AppPrt Usart1QueueLth) Usart1AppPrt = 0; } while(Usart1AppPrt!=Usart1RcvPrt); } return i; // 返回數(shù)據(jù)接收量 } void USART1_Process(void) // USART1數(shù)據(jù)收發(fā)處理 { unsigned int status = USART1_SR; if(status&15) return; // 異常處理: 校驗錯, 幀錯, 噪聲 if((status>>5)&1) // 數(shù)據(jù)接收(環(huán)形隊列存放,滿則拋掉) { status = Usart1RcvPrt + 1; if(status>Usart1QueueLth) status = 0; if(status!=Usart1AppPrt) { status = USART1_DR; switch(Usart1RcvFlg) { case 0: if(status '*') Usart1RcvFlg += 1; break; case 1: if(status '&') Usart1RcvFlg += 1; else Usart1RcvFlg = 0; break; case 2: if(status '^') Usart1RcvFlg += 1; else Usart1RcvFlg = 0; break; case 3: if(status ' ') Usart1RcvFlg += 1; else Usart1RcvFlg = 0; break; case 4: if(status '@') Usart1RcvFlg += 1; else Usart1RcvFlg = 0; break; case 5: Usart1RcvFlg += 1; break; default: break; } Usart1RcvQueue[Usart1RcvPrt++] = status; if(Usart1RcvPrt>=Usart1QueueLth) Usart1RcvPrt = 0; } else USART1_DR; } } while(1) { m = DrvUSB_EpRead(2, tmp, 6); // USB接收數(shù)據(jù)處理 if(m 0) { DrvUSB_EpWrite(1, tmp, 5); // 通過USB上傳 if((tmp[0] '*')&&(tmp[1] ' ')) { // Lora地址通道號變更 AddrChnl[0] = tmp[2] & 0x0F; AddrChnl[1] = tmp[3] & 0x0F; AddrChnl[2] = tmp[4] & 0x0F; inFlshPageErase(); // 閃存記錄修正 inFlshByteWrite(0, AddrChnl, 3); USART1_CR1 &= ~6; // USART波特率調(diào)整 USART1_BRR = 0x00000E98; // 波特率9600 USART1_CR1 |= 6; Lora2G4_vInit(); // Lora重新初始化 } } if(Usart1RcvFlg 6) { // Lora收到有效數(shù)據(jù) m = USART1_QueueRcvData(tmp); Usart1RcvFlg = 0; if(m 6) DrvUSB_EpWrite(1, tmp, 6); // 通過USB上傳 } } USB收發(fā)傳輸,不以傳統(tǒng)的人機接口設(shè)備HID(Human Interface Device)/配合主機USART轉(zhuǎn)USB做“借尸還魂”,直接采用高效的WinUSB格式,增加特定的操作系統(tǒng)描述、兼容ID特征描述和設(shè)備接口GUID描述符,并對收發(fā)任務(wù)函數(shù)做超時處理以避免程序阻塞,主要程序代碼如下: const unsigned char OsDscrptStr[] = // 操作系統(tǒng)描述字符串 { USBStrDscrpt_Lth(8), 3, USBStrDscrpt_Uncd('M'), USBStrDscrpt_Uncd('S'), USBStrDscrpt_Uncd('F'), USBStrDscrpt_Uncd('T'), USBStrDscrpt_Uncd('1'), USBStrDscrpt_Uncd('0'), USBStrDscrpt_Uncd('0'), USBStrDscrpt_Uncd(1) }; const CptbIdDscrpt cptbIdDscrpt = // 兼容ID特征描述符 {0x28, 0x100, 4, 1, // dwLength, bcdVersion, wIndex, wCount {0, 0, 0, 0, 0, 0, 0}, // Reserved[7] 0, 1, // bFirstInterfaceNumber, RESERVED {'W', 'I', 'N', 'U', 'S', 'B', 0, 0}, // compactiableID[8] {0, 0, 0, 0, 0, 0, 0, 0}, // subCompactiableID[8] {0, 0, 0, 0, 0, 0} // Reserved[6] }; const ItfcGuidDscrpt itfcGuidDscrpt = // 設(shè)備接口GUID描述符 { 0x8E, 0x100, 5, 1, // dwTotalSize, bcdVersion, wIndex, wCount 0x84, 1, 0x28, // dwSize, dwPropertyDataType, wPropertyNameLength USBStrDscrpt_Uncd('DeviceInterfaceGUID'), // bProperytName 0x4E, // dwPropertyDataLength USBStrDscrpt_Uncd('{12345678-1234-1344-1234-12345678ABCD}0') // bPropertyData }; // 單緩端點數(shù)據(jù)發(fā)送(查詢激發(fā)中斷發(fā)出), 參數(shù): 端點號1-7, 預(yù)發(fā)數(shù)據(jù), 數(shù)量, 返回[0-正確/-1超時] char DrvUSB_EpWrite(char EpNum, unsigned char* data, unsigned int counts) { unsigned int i; short sz; if(EpNum 1) sz = CfgDscrpts.EpIn1.wMaxPcktSz; while(counts) { i = 0; do { i++; if(i>maxTmOut) return 0xFF; // 超時退出 } while((EpDtSts>>EpNum)&1); // 時機查詢: 等待USBD空閑 if(counts>sz) // 超過最大包尺寸的發(fā)送 { USBD_BasicWrite(EpNum, 0, data, sz); data += sz; counts -= sz; } else // 最大包尺寸內(nèi)的發(fā)送 { USBD_BasicWrite(EpNum, 0, data, counts); counts = 0; } EpDtSts |= 1 << EpNum; // 標(biāo)識需要數(shù)據(jù)發(fā)送 } return 0; } // 端點數(shù)據(jù)接收, 參數(shù): 端點號1-7, 預(yù)發(fā)數(shù)據(jù), 數(shù)量, 返回[0-正確/-1超時] char DrvUSB_EpRead(char EpNum, unsigned char* data, unsigned int counts) { unsigned int i; RcvPt = data; while(counts) { i = 0; do { i++; if(i>maxTmOut) return 0xFF; // 超時退出 } while(!((EpDtSts>>EpNum)&1)); // 等待USB數(shù)據(jù)到來 counts -= RcvCnt; EpDtSts &= ~(1 << EpNum); // 標(biāo)識已經(jīng)取得USBD收到數(shù)據(jù) } return 0; } 3.3.1 WinUSB驅(qū)動及其數(shù)據(jù)接收實現(xiàn) 針對常規(guī)Windows、UOS操作系統(tǒng)應(yīng)用,直接調(diào)用Win7以上內(nèi)嵌的WinUSB.dll動態(tài)庫實現(xiàn),主要是操控句柄的開關(guān)和接收監(jiān)聽,采用兩個定時器分別開關(guān)線程實現(xiàn)收轉(zhuǎn)USB終端的即插即用和動態(tài)接收監(jiān)聽。只要底層嵌入式應(yīng)用軟件中添加特定描述支持,Windows就可以自動識其為WinUSB設(shè)備,上層可視化測試/應(yīng)用程序就可以通過WinUSB.dll與其進行USB數(shù)據(jù)傳輸通信。這樣, USB設(shè)備的數(shù)據(jù)采集、監(jiān)視控制、配置參數(shù)、軟件刷新等編程操控,就同傳統(tǒng)的“RS-232C通信”一樣了,無需USB轉(zhuǎn)串口作硬軟件轉(zhuǎn)接,而且速度快、實時性強,直截了當(dāng)[6][10-15]。相關(guān)關(guān)鍵代碼如下: HANDLE hDeviceHandle = INVALID_HANDLE_VALUE; // 設(shè)備文件句柄 WINUSB_INTERFACE_HANDLE hWinUSBHandle // WinUSB操作句柄 = INVALID_HANDLE_VALUE; OVERLAPPED overlapped; // 異步收發(fā)緩存結(jié)構(gòu)變量 BOOL flgWinUSB = false; // WinUSB設(shè)備存在與否[true存在] char thrdStt = 0; // 線程建立標(biāo)識狀態(tài)[0-無/1-建] void __fastcall TfmShow::SystemInit(void) { // 系統(tǒng)初始化 static const GUID stm32F1xxDvcItfc = { 0x12345678, 0x1234, 0x1344, { 0x12, 0x34, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC } }; GUID guidDeviceInterface = stm32F1xxDvcItfc; BOOL bResult = TRUE; AnsiString str; bResult = GetDeviceHandle(guidDeviceInterface, &hDeviceHandle); if (!bResult) { inOutTmr->Enabled = true; sttFlg->Font->Color = clGray; return; } bResult = GetWinUSBHandle(hDeviceHandle, &hWinUSBHandle); if (!bResult) { inOutTmr->Enabled = true; sttFlg->Font->Color = clGray; return; } flgWinUSB = true; // 設(shè)備已經(jīng)正常尋址到 inOutTmr->Enabled = false; sttFlg->Font->Color = clRed; if(thrdStt 0) { // 啟動并運行線程 rcvThrdDt = new rcvThrd(true, imgDrct, sttFlg, spcTmr, inOutTmr); thrdStt = 1; } rcvThrdDt->FreeOnTerminate = true; rcvThrdDt->Resume(); UINT timeout = 100; // 最大傳輸時間[ms] bResult = WinUsb_SetPipePolicy(hWinUSBHandle, 0x81, PIPE_TRANSFER_TIMEOUT, sizeof(timeout), &timeout); if(!bResult) { str = "設(shè)置超時錯誤:"; str += GetLastError(); ShowMessage(str); return; } ZeroMemory(&overlapped, sizeof(overlapped)); overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); } void __fastcall rcvThrd::RcvDt() { if(hWinUSBHandle INVALID_HANDLE_VALUE) return; ULONG cbRead = 0; UCHAR szBffr[20] = {'0'}; AnsiString str; BOOL bResult = WinUsb_ReadPipe(hWinUSBHandle, 0x81, szBffr, 15, &cbRead, &overlapped); if(bResult) { const char* t = szBffr; str = t; windowChange(str); } else if(ERROR_IO_PENDING GetLastError()) { bResult = WinUsb_GetOverlappedResult(hWinUSBHandle, &overlapped, &cbRead, true); if(NULL != cbRead) { int m = cbRead; cbRead = 0; if(bResult) { const char* d = szBffr; str = d; char i = 0; windowChange(str); } } } else if(22 GetLastError()) inOutTmr->Enabled = true; // 設(shè)備撥出 } 3.3.2 視窗動態(tài)展現(xiàn)及其控制軟件編制 開發(fā)之初,設(shè)想用通用流行的Python語言開發(fā),擬用PyUSB、Kivy/Tkinter、python-pptx/word/excel/acrobat,PyKeyboard/PyMouse等庫,深入之后發(fā)現(xiàn)這些庫多是用C++開發(fā)打包的,多是針對WinAPI的包,而且不新、不全面,作為解釋語言也沒有編譯語言C++運行效率高,與其特制一些把C++庫彌補Python應(yīng)用,倒不如與底層合二為一,直接嵌入其監(jiān)聽線物程中實現(xiàn),更為經(jīng)典直接,于是回到了C++開發(fā)實現(xiàn)。這里選用Enbarcadero的RadStudio開發(fā)環(huán)境,主要是兩類窗口:引導(dǎo)窗口和展示窗口。引導(dǎo)窗口,小視野透明暗標(biāo)動態(tài)圖文指示,控制展示窗口切換和翻頁。展示窗口,展示常規(guī)辦公軟件和特定開發(fā)軟件的窗口,主要通過調(diào)用Windows視窗變換和鍵盤模擬操控API函數(shù)實現(xiàn)[6][10-15]。核心程序函數(shù)代碼如下: BOOL SetTopWindow(HWND hWnd) { // 設(shè)置窗口到最頂層[參數(shù):窗口句柄] HWND hForeWnd = GetForegroundWindow(); DWORD dwForeID = GetWindowThreadProcessId(hForeWnd, NULL); DWORD dwCurID = GetCurrentThreadId(); AttachThreadInput(dwCurID, dwForeID, TRUE); ShowWindow(hWnd, SW_MAXIMIZE); SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); SetForegroundWindow(hWnd); AttachThreadInput(dwCurID, dwForeID, FALSE); return TRUE; } 窗口隨動變換展示的程序流程圖如圖9所示。 圖9 窗口隨動變換展示的程序流程圖 生產(chǎn)出廠測試,輸出指示原始的手勢分析結(jié)果和通信通道的暢通性,配置LoRa無線通信地址、通道及其是否輸出原始測距數(shù)據(jù)。設(shè)計有專用的可視化USB軟件,完成手勢分析原始數(shù)據(jù)的即時查看和LoRa無線通信配置。同時底層采發(fā)終端還支持使用USB串口助手查看手勢分析原始數(shù)據(jù)、原始測距數(shù)據(jù)詳細(xì)查看和LoRa無線通信配置[6-8]。專用可視化USB軟件和USB串口助手操控界面,如圖10~11所示。 圖10 專用可視化USB軟件實現(xiàn)的測試與配置示意圖 圖11 USB串口助手實現(xiàn)的測試與配置示意圖 要做到這些,終端軟件中需要添加相應(yīng)支持,采發(fā)終端中相關(guān)的典型實現(xiàn)函數(shù)代碼如下: void Lora2G4_chgCfg(void) { // Lora地址通道變更/系統(tǒng)打印信息[配合USART0接收中斷] unsigned char tmp[10] = {0}, m; if(Usart0RcvFlg 5) { // 收到有效數(shù)據(jù) m = USART0_QueueRcvData(tmp); Usart0RcvFlg = 0; if(m 5) { if(tmp[2]<0x41) { // Lora地址通道變更 AddrChnl[0] = tmp[2] & 15; AddrChnl[1] = tmp[3] & 15; AddrChnl[2] = tmp[4] & 15; inFlshPageErase(); // 閃存記錄修正 inFlshByteWrite(0, AddrChnl, 3); USART1_CR1 &= ~6; // USART波特率調(diào)整 USART1_BRR = 0x00000E98; // 波特率9600 USART1_CR1 |= 6; Lora2G4_vInit(); // Lora重新初始化 } else { // 系統(tǒng)輸出指示信息變更 if(((optFlg>>1)&1) 0) optFlg |= 1 <<1; // 詳細(xì) else optFlg &= ~(1 << 1); } } } } 激光手指飛行導(dǎo)航簡易微系統(tǒng),試銷以后,以極高的性價比,滿足了各類學(xué)校、企業(yè)、酒店、機關(guān)、場所的尋常應(yīng)用需要,內(nèi)外循環(huán)行銷,市場前景頗佳,緊接著就圍繞微型化和低功耗設(shè)計又推出了二代產(chǎn)品。另外,頂板即主板,還作為獨立的ToF手勢或雷達(dá)測距測向模塊,單獨出售,填補遠(yuǎn)距離手勢導(dǎo)航和低成本雷達(dá)測距測向領(lǐng)域的空白。 激光手指飛行導(dǎo)航簡易微系統(tǒng),麻雀雖小,五臟俱全,以神經(jīng)元深度學(xué)習(xí)自適應(yīng)人工智能手勢識別核心算法數(shù)學(xué)處理模型為中心,既涵蓋了數(shù)電/模電/射電在內(nèi)的低功耗微型電子電路設(shè)計,也包括了嵌入式C語言應(yīng)用軟件設(shè)計、C++驅(qū)動程序設(shè)計和C++/Python動態(tài)視窗切換技術(shù),通用流行的局部長距離無線通信和各類局部總線接口技術(shù)一應(yīng)俱全,既是可探索開發(fā)項目的過程研發(fā),也產(chǎn)生了可測試生產(chǎn)行銷的產(chǎn)品系統(tǒng)。在此基礎(chǔ)上,加大深度學(xué)習(xí)自適應(yīng)人工智能算法處理,實現(xiàn)鼠標(biāo)功能的“遠(yuǎn)距離移動飛鼠”,將有望成為可能,“學(xué)研”應(yīng)用顛覆突破,意義重大,拭目以待,為期不遠(yuǎn)。3.2 接收轉(zhuǎn)換傳輸電子終端構(gòu)造
3.3 WinUSB接收及其視窗展示控制實現(xiàn)
4 產(chǎn)品系統(tǒng)化測試及其配置考慮
5 產(chǎn)品系統(tǒng)的應(yīng)用統(tǒng)計分析
6 結(jié)束語