Arduino + USB Host Sheild 實現USB鼠標轉PS/2接口

製作緣由

公司內網只有PS/2接口,希望可以使用無線鼠標(貌似沒有PS/2接口的)。而那種USB轉PS/2的轉接頭只是簡單的連線,需要鍵盤或鼠標本身支持PS/2模式纔可以正常工作,現代的USB鼠標接收器顯然沒有考慮這一點。無意中發現有人用Arduino製作過USB鍵盤轉PS/2的裝置,那麼鼠標一定也可以。這個裝置,從原理上來說,就是一個轉發器。對於USB鼠標(非藍牙無線鼠標對主機來說仍是USB鼠標,和有線的沒啥區別),他是一個主機,接收來自鼠標的數據;對於真正的主機,他是一個PS/2鼠標,負責向其發送轉換過的數據。


PS/2部分

物理接口

如下圖,實際有用的只有4根線,1 - Data:用於傳輸數據(雙向);5 - Clock:用於向主機發送脈衝,控制讀寫數據;3 - Ground:接地;4 - Vcc (+5V) :電源。其中1和2可以接在Arduino任意的數字引腳上(但要避開USB Host Sheild佔用的那些引腳,我實際使用的是2號、3號引腳),而3和4分別接在Arduino的5V和GND引腳上,這樣不用額外供電,連到主機的PS/2接口就能正常工作。

數據傳輸(1位)

設備 > 主機 :由設備控制Clock以產生脈衝,首先,設置Data爲要發送的位(高/低電平表示1/0),同時保持Clock爲高電平一段時間,然後拉低Clock爲低電平,產生一個下降沿,通知主機讀取(鎖存)Data,繼續保持Clock爲低電平一段時間後釋放Clock(恢復爲高電平,爲傳輸下1位數據做準備)。。

主機 > 設備:仍然由設備控制Clock脈衝。當主機拉低Data或Clock時表示想要向設備發送(命令)數據,此時設備應該停止發送數據,優先讀取來自主機的數據,待主機釋放Clock(變爲高電平)時,表示設備可以開始讀入數據。首先,設備讓Clock保持高電平一段時間,然後拉低Clock,產生一個下降沿,通知主機設置Data,繼續保持Clock爲低電平一段時間後,釋放Clock爲高電平(此時設備可以鎖存當前Data,並且準備讀入下1位數據)

數據傳輸(1字節)

每傳輸1字節數據,需要額外附帶起始位(0),奇校驗位,停止位(1),共11位

代碼實現

最初在Arduino官方網站找到了一個叫做ps2dev的示例代碼,裏面的PS2dev類看上去實現了單個字節的讀寫,但是版本過於老舊。後來發現這個ps2dev已經是Arduino的一個庫了,在庫管理器中就可以搜索安裝,但還是不夠新,存在一些問題。他的最新版本在這裏 https://github.com/Harvie/ps2dev,其中簡單模擬鼠標的例子在某些時候會死循環出不來,我的修正已經被作者合併進去了。進一步地,爲了提高通訊速度,我修改了時鐘頻率以及發送單個字節的間隔,目前工作正常。:)

鼠標協議

總的來說,主機向鼠標發送各種命令,以設置或詢問鼠標的狀態或參數;而鼠標呢,除了答覆來自主機的命令,就是向主機發送位移數據包(處於Stream模式並且“數據報告”使能狀態下,可以主動上報);更具體的可以參考網上的文檔。(有中文版哦,搜索“PS2 鼠標 鍵盤技術參考”)下面列出我抓到的命令序列:

19:44:22.522 -> process_cmd FF       重置命令,進入Reset模式,重置各種狀態,回覆自檢完成、設備ID,進入Steam模式
19:44:22.556 -> process_cmd F2       詢問設備ID,首次詢問應該回復00(標準鼠標),但我一律回覆的03(滾輪鼠標)也沒事哦
19:44:22.556 -> process_cmd E8       設置解析度
19:44:22.556 -> set resolution 0        讀取解析度,1count/mm
19:44:22.556 -> process_cmd E6       設置縮放比例1:1
19:44:22.556 -> process_cmd E6       設置縮放比例1:1 
19:44:22.556 -> process_cmd E6       設置縮放比例1:1,爲啥連續發了3次?
19:44:22.556 -> process_cmd E9       詢問鼠標狀態,此時應該回復3字節的狀態包
19:44:22.556 -> process_cmd E8       設置解析度
19:44:22.589 -> set resolution 3        讀取解析度,8count/mm
19:44:22.589 -> process_cmd F3       設置採樣率
19:44:22.589 -> set sample rate 200  讀取採樣率 200/sec
19:44:22.589 -> process_cmd F3       設置採樣率
19:44:22.589 -> set sample rate 100  讀取採樣率 100/sec
19:44:22.589 -> process_cmd F3       設置採樣率
19:44:22.589 -> set sample rate 80    讀取採樣率 80/sec,注意,當主機按此順序設置採樣率,表示他支持03鼠標(滾輪鼠標)
19:44:22.589 -> process_cmd F2       詢問設備ID,此時應該回復03,如果鼠標支持的話
19:44:27.052 -> process_cmd FF       重置命令
19:44:27.052 -> process_cmd F2       詢問設備ID
19:44:27.052 -> process_cmd E8       設置解析度
19:44:27.052 -> set resolution 0        讀取解析度,1count/mm
19:44:27.052 -> process_cmd E6       設置縮放比例1:1
19:44:27.052 -> process_cmd E6       設置縮放比例1:1
19:44:27.052 -> process_cmd E6       設置縮放比例1:1,同樣的,爲啥連續發了3次?我猜可能有含義的。
19:44:27.086 -> process_cmd E9       詢問鼠標狀態,此時應該回復3字節的狀態包
19:44:27.086 -> process_cmd E8       設置解析度
19:44:27.086 -> set resolution 3        讀取解析度,8count/mm
19:44:27.086 -> process_cmd F3       設置採樣率
19:44:27.086 -> set sample rate 200  讀取採樣率 200/sec
19:44:27.086 -> process_cmd F3       設置採樣率
19:44:27.086 -> set sample rate 100  讀取採樣率 100/sec
19:44:27.086 -> process_cmd F3       設置採樣率
19:44:27.086 -> set sample rate 80    讀取採樣率 80/sec,爲啥又來一遍?囧
19:44:27.086 -> process_cmd F2       詢問設備ID,此時應該回復03,如果鼠標支持的話
19:44:27.120 -> process_cmd F3       設置採樣率
19:44:27.120 -> set sample rate 200  讀取採樣率 200/sec
19:44:27.120 -> process_cmd F3       設置採樣率
19:44:27.120 -> set sample rate 200  讀取採樣率 200/sec
19:44:27.120 -> process_cmd F3       設置採樣率
19:44:27.120 -> set sample rate 80    讀取採樣率 80/sec,注意,當主機按此順序設置採樣率,表示他支持04鼠標(5鍵滾輪)
19:44:27.120 -> process_cmd F2       詢問設備ID,此時應該回復04,如果鼠標支持的話,但是我沒有。:)
19:44:27.120 -> process_cmd F3       設置採樣率
19:44:27.120 -> set sample rate 100  讀取採樣率 100/sec
19:44:27.120 -> process_cmd E8       設置解析度
19:44:27.154 -> set resolution 3        讀取解析度,8count/mm
19:44:27.154 -> process_cmd F4        使能“數據報告”,下面鼠標就可以開始主動上報“位移數據包”了。。


USB部分

使用USB Host Shield擴展板(採用MAX3421E芯片,支持USB2.0),加上USB Host Shield 2.0(它也是Arduino的一個庫),最新版本在這裏 https://github.com/felis/USB_Host_Shield_2.0,只要繼承HIDUniversal類並且覆蓋虛函數ParseHIDData就能得到HID設備的數據(數據格式可用USBBlyzer分析)。實際使用中發現,如果本次數據和上次的完全一致,則不會調用ParseHIDData。(比如同方向滾動滾輪,後面的都被忽略了)只需略微修改hiduniversal.cpp,已提合併請求,作者暫時沒理我。^_^ (我的修改見 https://github.com/felis/USB_Host_Shield_2.0/pull/522

USB是一種主從結構,只有當主機向設備發送IN令牌包(見上述庫中 USB::InTransfer)時,設備纔可以向主機發送數據。在鼠標設備內部,分爲USB芯片(用於與USB主機通訊)和MCU(用於執行鼠標固件程序)。當檢測到鼠標狀態變化(比如按鍵按下或發生移動),MCU將狀態數據寫入USB芯片的緩衝區,待收到IN令牌包時由USB芯片將數據發給主機。如果主機請求數據速度過慢會怎樣?我猜數據會丟失?從目前能找到的固件程序代碼看,都是直接向芯片緩衝區寫數據的,並沒有額外的處理。或許當緩衝區滿了之後,合併後續數據會好一點?


鼠標回報率

也就是上面提到的採樣率。

PS/2理論上支持:10、20、40、60、80、100、200。我的主機最終設置的值爲100,目前USB部分耗時2ms,PS/2部分耗時6-7ms,已經可以滿足。曾經試過把時鐘頻率調的更快、發送單個字節的間隔改的更短,沒成功。:(

USB1.1支持:125

USB2.0支持:250、500、1000

考慮到USB和PS/2收發速率可能不匹配,所以實現了一個循環隊列用於存放來自USB鼠標的數據,並儘可能地將新的數據與隊尾元素合併。但是,實際測試中,隊列長度從未超過1並且合併函數從未被調用,即便在loop()中嘗試讀取多次USB數據再向PS/2發送也是如此,Why?可能是因爲我的USB鼠標只有125的回報率吧。


呼吸燈

爲了瞭解轉換器的工作狀態(以及更炫酷一點),額外加了一個全綵LED燈在盒子頂部。繁忙時,以紅色快速閃爍;而空閒時,則以綠色緩慢呼吸;


最終成品

完整源代碼在這裏 https://github.com/liumazi/MzMouse


參考資料

https://www.zhihu.com/question/46855816/answer/619886526

https://www.arduino.cn/forum.php?mod=viewthread&tid=22851

http://www.burtonsys.com/ps2_chapweske.htm

http://usb.baiheee.com/usb_projects/easy_usb_51_programer_plus/usb_hid_mouse.html

https://blog.csdn.net/suipingsp/article/details/30238891

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章