曾經(jīng)對(duì)USB開(kāi)發(fā)有過(guò)興趣,也買(mǎi)了開(kāi)發(fā)板和配套書(shū)籍,研究了半天,發(fā)現(xiàn)自己對(duì)用DIY一個(gè)USB鼠標(biāo)或者一個(gè)USB鍵盤(pán)根本不感興趣,書(shū)上的也盡是接口啊/協(xié)議什么,目的還是教你做個(gè)鼠標(biāo)器..而我只想做一個(gè)能通過(guò)USB接口實(shí)現(xiàn)簡(jiǎn)單通訊功能的小玩意兒~~~
其實(shí)早就對(duì)AVRUSB(現(xiàn)在叫V-USB)這套東西有興趣了,它使用AVR單片機(jī)模擬USB接口協(xié)議,所需要的元器件很簡(jiǎn)單,不需要專用的USB接口芯片,也不需要弄懂很復(fù)雜的協(xié)議,相關(guān)的程序/小制作網(wǎng)上也多的是,只是對(duì)USB接口的電壓表/溫度計(jì)/紅外分析儀這類制作沒(méi)心情,想搞一個(gè)獨(dú)特的,有一定實(shí)用性的,又能跟網(wǎng)絡(luò)搭上關(guān)系的東西,這不又在"愛(ài)折騰"看到這個(gè)網(wǎng)站訪客計(jì)數(shù)器,于是DIY開(kāi)始了...
規(guī)劃:
1.首先需要一個(gè)上位機(jī)軟件去取得網(wǎng)站訪客計(jì)數(shù).
愛(ài)折騰上的這個(gè)是通過(guò)web程序生成一個(gè)包含計(jì)數(shù)值的文本文件,然后又上位機(jī)讀取后發(fā)給下位機(jī)顯示,這樣就要求這個(gè)網(wǎng)站計(jì)數(shù)器必須跟網(wǎng)站在一起,也就是說(shuō)你能控制這個(gè)訪客計(jì)數(shù)程序提供你想要的數(shù)據(jù)格式,而我想實(shí)現(xiàn)的讀取現(xiàn)在使用很廣泛的cnzz的訪客計(jì)數(shù)器,這就要求上位機(jī)必須能實(shí)現(xiàn)模擬登錄,并解析web頁(yè)面,取出所需的數(shù)字,經(jīng)過(guò)合理的數(shù)據(jù)轉(zhuǎn)換后發(fā)給下位機(jī)顯示,能顯示今日/昨日/每日/累計(jì)的PV/訪客/IP共12個(gè)項(xiàng)目,其中目前累計(jì)PV已經(jīng)達(dá)到了4千萬(wàn),這就要求下位機(jī)至少有8個(gè)數(shù)碼管.
2.需要一個(gè)開(kāi)發(fā)V-USB設(shè)備上位機(jī)的接口庫(kù),因?yàn)閂-USB并未為C#提供一個(gè)標(biāo)準(zhǔn)開(kāi)發(fā)庫(kù),所以要找到這么一個(gè)支持C#開(kāi)發(fā)的接口,所幸找到了,這就是LIBUSBDOTNET,而且是開(kāi)源的,當(dāng)然這個(gè)庫(kù)提供的例程是針對(duì)通用USB設(shè)備的,還好網(wǎng)上也有這種用AVRUSB的上位機(jī)下位機(jī)例程.
3.下位機(jī):
(1)USB接口:MCU采用M8,電壓5V,通過(guò)3.6V穩(wěn)壓管實(shí)現(xiàn)跟USB連接(USB接口電平是3.0~3.6V)
(2)數(shù)據(jù)顯示器:規(guī)劃是顯示9位數(shù)字,即最多能顯示9億點(diǎn)擊量,有相應(yīng)的顯示項(xiàng)目的指示,有按鍵實(shí)現(xiàn)顯示項(xiàng)目的切換,網(wǎng)上可供選擇的模塊有兩種(這只是我個(gè)人的想法,想一次就購(gòu)買(mǎi)全所有的元件,這樣只能在一家店鋪選擇了),一種可以顯示16位數(shù)字,沒(méi)有LED指示燈,有鍵盤(pán),數(shù)碼管個(gè)頭比較小,不能顯示小數(shù)點(diǎn),另一種是能顯示8位數(shù),8個(gè)指示燈,8個(gè)按鈕,能顯示小數(shù)點(diǎn),考慮到今后的幾年計(jì)數(shù)值不太可能達(dá)到億次,所以8位即可,加上有LED指示燈,減少了清晰指示當(dāng)前顯示項(xiàng)目的編程壓力,所以最后選擇了后一個(gè)模塊.
實(shí)戰(zhàn):
1.上位機(jī)編程:
因?yàn)椴挥胐elphi好多年,目前用的最多的客戶端編程軟件還是vs2008(C#),因此搜索了網(wǎng)絡(luò),找到了模擬登錄的方法,并且研究后確認(rèn)cnzz可以通過(guò)GET的方法提交數(shù)據(jù),返回的數(shù)據(jù)也是有規(guī)律的,可以被分類提取,整個(gè)過(guò)程用到了"模擬提交"/"正則匹配"等技術(shù),經(jīng)過(guò)編程驗(yàn)證,數(shù)據(jù)讀取成功!而且就算沒(méi)有下位機(jī),這個(gè)程序也是一個(gè)很好的網(wǎng)站訪客技術(shù)軟件,能實(shí)現(xiàn)定時(shí)刷新功能.
2.下位機(jī)硬件:
因?yàn)閂-USB要求AVR單片機(jī)的晶振頻率為12M,而且必須有精度保證,所以內(nèi)置的RC振蕩器是不能用了,不需外置晶振電路,至于USB硬件接口,參照相關(guān)的標(biāo)準(zhǔn)及電路即可,3.6V的穩(wěn)壓管平時(shí)不太用,一般手頭不會(huì)有存貨的,所幸我早有打造v-usb的計(jì)劃,早已經(jīng)買(mǎi)了一批放那兒了.按電路搭建起來(lái),檢查電源是否短路,然后插入電腦USB接口,順利被電腦發(fā)現(xiàn),盡管還不能被識(shí)別.
3.下位機(jī)程序:
網(wǎng)上下到的avr驅(qū)動(dòng)包大部分用的是AVR-GCC,此前一直用的是CVAVR,所以專門(mén)下載了winavr和avrstudio的最新版本,經(jīng)過(guò)幾天折騰,好歹能順利編譯出基本的程序了,只是對(duì)GCC的MAKEFILE還是不甚了了,不過(guò)要研究也是以后的事情,現(xiàn)在最主要的還是先把眼前的事情完成.
程序分三大部分:一部分是V-USB驅(qū)動(dòng)程序,這個(gè)按照通常的方法修改必要的參數(shù),比如PID,VID,USB接入的端口及引腳等等,程序本身是不能動(dòng)的,可以自定義的僅僅是usbFunctionSetup,usbFunctionRead,usbFunctionWrite這三個(gè)函數(shù),一般來(lái)說(shuō)usbFunctionSetup就可以傳送少量的數(shù)據(jù),這在小制作中是夠用了,如果需要大量數(shù)據(jù)傳輸則要用到usbFunctionRead,usbFunctionWrite了.這部分要完成數(shù)據(jù)的接收/判別/存儲(chǔ)功能.
第二部分是對(duì)接收的數(shù)據(jù)按事先規(guī)定好的協(xié)議進(jìn)行處理,處理成可供顯示的格式.
第三部分是數(shù)據(jù)的顯示,及按鍵輸入的控制,因?yàn)橘I(mǎi)顯示模塊的時(shí)候店家已經(jīng)提供相應(yīng)的庫(kù),應(yīng)用到程序里就可以了.
4.上下位機(jī)的通訊:
這是最重要也是最困難的.
首先要先定下通訊協(xié)議和數(shù)據(jù)格式.剛開(kāi)始的時(shí)候打算用LIBUSBDOTNET中的 OpenEndpointWriter 類向下位機(jī)傳送批量數(shù)據(jù),就是從網(wǎng)絡(luò)接收到的12個(gè)訪問(wèn)計(jì)數(shù)項(xiàng),后來(lái)發(fā)現(xiàn)網(wǎng)上根本沒(méi)有跟V-USB有關(guān)的 OpenEndpointWriter 類的用法例程,下位機(jī)倒是有用usbFunctionRead,usbFunctionWrite來(lái)處理批量數(shù)據(jù)的,不過(guò)人家用的上位機(jī)是delphi或者BCB的,根本沒(méi)有C#的,所以這個(gè)方案最終放棄,轉(zhuǎn)而使用 UsbSetupPacket 和 ControlTransfer 傳輸數(shù)據(jù),下位機(jī)則使用usbFunctionSetup接收數(shù)據(jù).
因?yàn)閡sbFunctionSetup只能傳送8個(gè)字節(jié),data[0]專門(mén)用于主機(jī)向設(shè)備發(fā)送命令的,比如要向設(shè)備發(fā)送數(shù)據(jù)或者要求設(shè)備向主機(jī)發(fā)送數(shù)據(jù)等等,這是USB協(xié)議規(guī)定好的,不能亂用,data[1]一般用做存放自定義的命令字節(jié),比如要發(fā)送的數(shù)據(jù)類型/命令類型,這是可以任意規(guī)定的,剩下的data[2..7]則是可以任意用來(lái)放置要傳送的數(shù)據(jù),直到了這個(gè)規(guī)定,接下來(lái)就要設(shè)計(jì)一個(gè)數(shù)據(jù)格式/協(xié)議了:data[1]放置12個(gè)計(jì)數(shù)項(xiàng)目的編號(hào),從0到11,data[2..7]放置計(jì)數(shù)數(shù)值,這個(gè)數(shù)值該如何放置才能更簡(jiǎn)單更容易處理呢?可以用的方法有兩個(gè),一個(gè)是直接將一個(gè)整型數(shù)(四字節(jié))拆分成4個(gè)字節(jié)放入data[2..3],data[4..5],下位機(jī)接收后再還原出來(lái),這樣能表示的數(shù)字非常巨大,完全滿足下傳數(shù)據(jù)的范圍,但是這樣的話下位機(jī)的數(shù)據(jù)處理要求比較難,而V-USB要求除驅(qū)動(dòng)外的程序運(yùn)行時(shí)間必須小于20ms,否則可能造成數(shù)據(jù)丟失,另外一個(gè)方案是將8位數(shù)字拆成8個(gè)BCD碼,放入4個(gè)字節(jié),傳到下位機(jī)后,下位機(jī)只要按順序?qū)⑦@8個(gè)數(shù)分別寫(xiě)入8個(gè)數(shù)碼管即可,根本無(wú)需任何的數(shù)據(jù)轉(zhuǎn)換,這樣僅僅是上位機(jī)多了點(diǎn)程序處理環(huán)節(jié),而下位機(jī)則節(jié)約了大量的運(yùn)行時(shí)間!因此最終方案選擇了后一個(gè).但是后來(lái)還是出問(wèn)題了,出問(wèn)題的就是這個(gè)UsbSetupPacket這個(gè)類的參數(shù)(跟下位機(jī)的usbFunctionSetup對(duì)應(yīng))的data[2..3],data[4..5],data[6..7]的數(shù)據(jù)類型是有符號(hào)16位整型,也就是說(shuō),其數(shù)據(jù)范圍是+-32768,轉(zhuǎn)換成二進(jìn)制數(shù)就是0111 1111 1111 1111高字節(jié)最大的數(shù)字范圍只有127,而兩位BCD碼轉(zhuǎn)成10二進(jìn)制最大是9*256+9=2313,遠(yuǎn)大于能存儲(chǔ)的數(shù)值,最終采取了如下的數(shù)據(jù)結(jié)構(gòu):
按8位的顯示數(shù)字算,每4位用兩個(gè)字節(jié)表示,比如9999 9999這個(gè)數(shù)就分成兩個(gè)9999來(lái)表示,這兩個(gè)字節(jié)又分高低字節(jié),每個(gè)字節(jié)表示2位數(shù),也就是十進(jìn)制的99,因?yàn)檫@個(gè)高字節(jié)最大是127,不能用BCD碼表示,所以仍按二進(jìn)制來(lái)表達(dá),最大是99,正好在127之內(nèi),這兩個(gè)字節(jié)傳到下位機(jī)后再轉(zhuǎn)成BCD碼送顯示器顯示,這樣就成功的解決了數(shù)據(jù)格式的表示問(wèn)題,而且最大的數(shù)字仍要大于8位數(shù)(因?yàn)橛?個(gè)字節(jié)可用,8位數(shù)只用掉了4個(gè)字節(jié)).
安排好上位機(jī)的數(shù)據(jù)格式問(wèn)題,下位機(jī)只要按協(xié)議還原出來(lái)就可以了.
5.顯示/按鍵模塊的程序
剛測(cè)試店家提供的模塊的時(shí)候,怎么都不能讓模塊點(diǎn)亮,懷疑是模塊有問(wèn)題,后來(lái)讓店家傳了一個(gè)他自己做的一個(gè)示例hex文件,寫(xiě)入M8后,模塊能正常點(diǎn)亮,說(shuō)明模塊本身沒(méi)問(wèn)題,問(wèn)題出在編譯器上,掙扎了一個(gè)晚上,終于發(fā)現(xiàn)是makefile寫(xiě)的有問(wèn)題.
6.系統(tǒng)總成
最終上位機(jī)傳送12項(xiàng)計(jì)數(shù)值,最小刷新時(shí)間是10秒,并同時(shí)每隔一秒傳送一個(gè)有別于12個(gè)項(xiàng)目編號(hào)的值,如果下位機(jī)在若干秒內(nèi)沒(méi)有接收到這個(gè)數(shù)值,則顯示某些符號(hào),以告知用戶,USB通訊已中斷,顯示模塊的8個(gè)數(shù)碼管用于顯示8位計(jì)數(shù)值,如果數(shù)字少于8位,前導(dǎo)的0自動(dòng)消隱,LED指示燈有8個(gè),前4個(gè)分別只是今日/昨日/每日/累計(jì),后3個(gè)用于只是PV/訪客/IP,用3個(gè)按鍵操作項(xiàng)目的顯示,頭兩個(gè)向前向后選擇顯示項(xiàng)目,后一個(gè)按鍵切換到自動(dòng)輪換顯示.上位機(jī)方面,原本是用管理員帳號(hào)實(shí)現(xiàn)模擬登錄的,后來(lái)考慮到安全問(wèn)題,改用查看密碼登錄,這樣只要填寫(xiě)計(jì)數(shù)器的ID和查看密碼就可登錄了,所幸最終的數(shù)據(jù)頁(yè)面跟管理員的是一樣的,程序不用大的改動(dòng).
7.遇到的問(wèn)題:
(1)USB識(shí)別問(wèn)題,在家里的電腦上都能成功的識(shí)別出硬件插入的,但是拿到單位的電腦上卻發(fā)現(xiàn)3臺(tái)電腦8個(gè)USB端口只有一個(gè)能識(shí)別出硬件插入,并成功安裝驅(qū)動(dòng),也問(wèn)過(guò)其他人,但是沒(méi)有答案,后來(lái)拿萬(wàn)用表測(cè)data-的引腳電壓,發(fā)現(xiàn)只有3.1v,而相同電路相同參數(shù)的USBASP有3.4,照理說(shuō)3.0~3.6之間都能應(yīng)該能識(shí)別的,可實(shí)際上就是不行,最后還是更換了這兩個(gè)穩(wěn)壓管,更換后的電壓是3.3v,果然能識(shí)別出來(lái)了.
(2)然后是V-USB的PC驅(qū)動(dòng)問(wèn)題,因?yàn)檫@類USB設(shè)備(包括USBASP)都是用的免費(fèi)的PID/VID,就是說(shuō)都是相同的,這樣不同的設(shè)備插入后都會(huì)被識(shí)別為同一個(gè)硬件,后來(lái)用了LIBUSBDOTNET自動(dòng)的inf向?qū)Чぞ?生成了自己的驅(qū)動(dòng)安裝文件,這樣插入硬件后就能顯示出不同的硬件名稱了.
(3)LIBUSBDOTNET的動(dòng)態(tài)庫(kù)問(wèn)題,USBASP和我的計(jì)數(shù)器顯示器都顯示為liabusb_win32類型的設(shè)備,用到的硬件驅(qū)動(dòng)都是libusb0,奇怪的是插入任意一個(gè)硬件,用這個(gè)庫(kù)做的程序都不能正常的識(shí)別出來(lái),不管是官方的還是我自己做的,盡管在設(shè)備管理器上顯示都正常的設(shè)備.但是USBASP的客戶端軟件avr_fighter卻能正常識(shí)別出usbasp,但是它用的是BCB編程的,沒(méi)有用到libusbdotnet的庫(kù),估計(jì)是兩個(gè)硬件的驅(qū)動(dòng)程序都相同的問(wèn)題(即libusb0不能同時(shí)支持兩個(gè)USB設(shè)備),后來(lái)是修改了我自己的PID/VID,并且將驅(qū)動(dòng)安裝到自己定義的一個(gè)目錄上(默認(rèn)是windows\system32)但是還是沒(méi)有解決根本問(wèn)題,設(shè)備插入后程序還是不能打開(kāi)它,有時(shí)要兩個(gè)設(shè)備同時(shí)插入,程序才能識(shí)別出來(lái),重啟后又不能識(shí)別了,到目前為止這個(gè)問(wèn)題還是不能很好的解決,不過(guò)在只有一個(gè)V-USB設(shè)備的電腦上還是很正常的.
又:寫(xiě)完上面的文字后,又去libusbdotnet官網(wǎng)上逛了一下,都是E文,基本看不懂,湊合用google網(wǎng)頁(yè)翻譯看了一下,發(fā)現(xiàn)確也有人提出同時(shí)插入兩個(gè)相同v-usb設(shè)備時(shí),不能被枚舉的問(wèn)題,又有人提到他用修改libusb0.sys,libusb0.dll為自己的名字后,再安裝驅(qū)動(dòng)的事情,給了我一點(diǎn)點(diǎn)靈感,那么改哪個(gè)的驅(qū)動(dòng)呢?是usbasp的還是我的網(wǎng)頁(yè)計(jì)數(shù)顯示器?因?yàn)橛?jì)數(shù)器可能要拿到外面去用的,而usbasp是自己用的設(shè)備,在自己的電腦上才有可能同時(shí)插入兩個(gè)設(shè)備,因此先徹底刪除了原來(lái)的USBASP驅(qū)動(dòng)程序,然后修改.inf文件,將所有涉及到libusb0.sys,libusb0.dll,及安裝的服務(wù)名都改成自定義的名稱,當(dāng)然也要改一下驅(qū)動(dòng)文件的名稱,然后再安裝,果然現(xiàn)在我的上位機(jī)能完全正常的識(shí)別出我的小設(shè)備了,當(dāng)然了,兩個(gè)設(shè)備還是不能同時(shí)插入,不過(guò)這個(gè)已經(jīng)不是大問(wèn)題了,畢竟usbasp只是個(gè)寫(xiě)入設(shè)備,寫(xiě)入完成后即可拔出的.
附靚照一張,至于要不要給它按一個(gè)漂亮點(diǎn)的外殼,看心情而定~~~