轉自關於鍵盤沖突那點事(3鍵沖突/7鍵沖突/PS2/USB的各種原理)
最近閑得無聊,正好看到有人發帖提問,於是就來詳細說說所謂鍵位沖突和無沖突的各種原理——基本上這也是個老生常談的話題了,但相關的技術帖比較零亂難找,而且充斥了大量電工術語,也不是很容易看懂。這里就盡量用通俗易懂的語言來講(我的目標是即使你只有初中文化水平也能看懂,保守地說絕對不超過高中文科生能理解的范圍),帖子比較長,有興趣的朋友請慢慢閱讀。慢慢看,用心理解,包你看懂。
為了降低閱讀門檻,本文難免有不嚴謹之處,還請工科同學高抬貴手。如果是特別荒謬的原則性錯誤,歡迎指正。
——————電路基本常識:輸出與輸入——————
我們的手指按下一個鍵,電腦是怎么知道的呢?在這短短幾十微秒的時間里發生了什么事呢?為什么有時候同時按下幾個鍵就沒反應了呢?首先要講講電路的通斷。
即使你沒有什么計算機知識,大概也應該聽過一個詞:【二進制】。不管你家里的電腦外表多么五顏六色,它底層的邏輯卻是非黑即白,只有【1】和【0】。任何儲存在你電腦里的東西,無論游戲、音樂還是你最鍾愛的小電影,都是用一長串你數不清的1和0的組合來記錄和處理的。
明白了這個概念以后,再想想,電腦電腦,它的基礎是什么?對,要有【電】。下一個問題很自然地:這電怎么就能變成1和0呢?說來更簡單,有電就是1,沒電就是0唄——這么說似乎太不專業了。嚴謹一點說:在電路中一個點,它當前表示的數據是1還是0,需要檢測這一點的電壓到底是更接近【懸空】(對於USB和PS/2接口,指+5V),還是更接近【大地】(0V)。如果高於某個界限值,稱作【高電平】,也就是1;而相對地,低於某個界限值,稱作【低電平】,也就是0。
接下來的問題更是小學生也會答:1×1等於多少?
你當然知道答案是1。
那么1×0呢?
對了,不管什么數字乘以0,結果都是0。
如同在游泳池里面尿尿一樣,一泡尿就把干凈水變成臟水。大地就是這么邪惡:無數個懸空的點,它們之間互相連接還是懸空,然而只要其中有一個點接着地,它們就全等於接地了。
(重要知識)高電平的點和低電平的點連通短接之后,兩點都成為低電平。
你壓住不耐煩,看到這里,心想,這他媽的和鍵盤有毛的關系?
別着急,我們再來看看一個典型的可編程芯片是什么樣子(圖片引自泡泡網的poker拆解):
這個黑色方方的就是芯片,它周圍那一排排張牙舞爪的刺叫做【引腳】,是芯片用來和外界溝通的渠道,圖中這只芯片一共有48個引腳。
其中一些引腳負責電源、時鍾、控制等基礎功能,但占絕對多數的是負責輸入/輸出數據的,稱為【I/O引腳】。
通過程序設置,芯片既可以改變每個I/O引腳的電壓(設置1或0的值),也可以檢測引腳的電壓(讀取1或0的值),以下如果沒有特殊說明,提到引腳一詞均指數據I/O引腳。
現在請假設這樣一個場景:
你是一顆芯片,你的左手和右手是兩個引腳,有一大團雜亂無章的導線,露出兩個線頭擺在你面前,你如何判斷它們是否是同一根線的兩頭?(即這兩個線頭是否連通)
仔細考慮之后,聰明的你大概可以想到:只要把左手設置為0,右手設置為1,分別握上兩個線頭,然后檢查右手的狀態,如果變成0了,說明它們剛剛做過一次相乘運算,1被拉下水變成了0,這條線是連通的。
換一個比喻:如同一根管道,在左手的洞口放一只小老鼠,右手的洞口放一塊奶酪(這只小老鼠的速度無敵快)。當小老鼠從左邊進去,又從右邊鑽出來吃掉了奶酪,說明管道中間沒有被堵死。
對,這就是鍵盤按鍵接通的原理。
在按鍵下面的【電路板】(或者電路薄膜)上,印制有許多導線,導線經過每個按鍵下方的部分是斷開的。按鍵,也就是開關,當它壓下的時候,下面的導線會被接通。而導線最終兩端都是連接到芯片上,芯片會不停地反復檢測每條線的連通情況,從而隨時判斷哪個鍵當前是按下的。這就好像學校的保安頭子坐在監控室里,切換着鏡頭,偷窺哪個自習室中有男女生OOXX那樣。
——————主控芯片與矩陣設計——————
我們繼續深入話題:一塊普通的鍵盤,少則幾十個多則上百個按鍵,顯然無論從哪個方面看,怎么都不可能給每個鍵都單獨連個導線到CPU芯片去——先不說成本多高,誰願意桌面上橫着手腕粗的一大捆線呢?
在上個世紀末,電腦開始走入尋常百姓家庭,當時的PC界霸主是IBM公司。為了簡化接口,順便壟斷標准,IBM陸續設計了XT、AT、PS/2協議用來處理鍵盤這樣的輸入設備,大體意思是,只要在鍵盤內部放一塊主控芯片,用來管理所有按鍵狀態並轉換為串行信號,包括電源在內總共只要4根線就可以傳輸所有的數據(掃描碼),而相應地,主板上也會有一個稱作鍵盤控制器的IO芯片(一般集成在南橋中),把這些掃描碼翻譯為ASCII碼給CPU。
最后,PS/2協議作為成熟而穩定的形態,成為了二十多年來的市場規范,也就是大家熟知的那個圓形接口,里面實際用到的4根線分別負責:時鍾、數據、電源、接地。
上面這一段可能有點復雜,如果你沒能全看懂,也沒啥大礙,只是為了說明【鍵盤主控芯片】的存在。
總之,整理一下到目前為止的知識,現在你應當知道鍵盤是遵循如下的通訊過程:
【按鍵】——【鍵盤主控芯片】——(翻譯成掃描碼,經過PS/2協議)——【主板IO芯片】——(翻譯成ASCII碼)——【CPU】
這樣看起來不錯,但還有個問題:主控芯片是怎么“知道”所有鍵的狀態的?
按照前面說的,要得知一個按鍵是否按下,需要在引腳A輸出0,引腳B輸出1,再檢測引腳B的值是1還是0。(如果這里看不懂就麻煩了,請向上翻翻,復習一下左右手攥電線或者小老鼠吃奶酪的例子)
現在,假設我們要做一個36鍵的鍵盤,包括10個數字和26個英文字母。
於是我們令引腳A永遠=0,而且連接到所有的按鍵上。
然后做引腳B1、B2、B3、……、B36,分別與對應的36個按鍵連接。
這樣總共是需要37個引腳。
接着,先令所有B引腳=1,然后從B1到B36挨個檢查,誰變成0了,就說明誰對應的按鍵按下了。當然,為了時刻獲取最新的狀態,每秒鍾要進行幾十至上百輪這樣的掃描。
但是104個鍵的鍵盤怎么辦?老老實實做105個引腳嗎?這也太復雜了吧!有沒有辦法能用更小、更簡單一些的芯片實現呢?要知道這可直接關系到成本啊。
工程師們想了個辦法:【矩陣】。聽起來很專業,其實就是利用“組合”,來成倍地提高引腳利用率。還拿上面的例子說,我們可以把引腳數量從37縮減到12。怎么做呢?
請想象一個表格,行標題為A1、A2、A3、A4、A5、A6,列標題為B1、B2、B3、B4、B5、B6。這樣就構成了一個6×6=36的矩陣。然后把按鍵分別放到每個格子里面去,如下圖。
在電路中,每個按鍵都是負責連接它所對應的兩個引腳,比如按鍵A連接引腳A1和B1,而按鍵W連接A5和B4。這樣一來,引腳之間就形成了【交叉組合關系】,也就是矩陣。任意兩個引腳之間只通過一個按鍵連接。
現在我們按下J鍵,芯片中的程序是怎么檢測到這個行動的呢?
首先令A1=0,其他所有引腳=1,然后從B1到B6挨個檢查。由於那一列的按鍵都沒有按下,沒有任何一個B引腳和A1接通,因此它們的值都是1。
接下來,令A2=0,其他所有引腳=1,重復以上工作。接着再檢查A3列……
最后所有行列檢查完畢后,結果發現只有在A4=0的時候,B2=0,也就是說A4和B2是接通的。於是程序便通過預先定義好的按鍵表格,知道按下的是J鍵。
同樣地,這一整輪掃描每秒要重復幾十上百遍,所以你在任何時候敲下或抬起按鍵,電腦都能很快反應出來。
現在市面上絕大多數鍵盤的工作原理都是基於這種矩陣的。我們很容易想到,矩陣的行數乘以列數的結果,就是它能夠容納按鍵的最大數量。普通的104鍵鍵盤是應用16×8的矩陣,來覆蓋所有按鍵。只需要24個數據引腳。
——————三鍵沖突:矩陣的麻煩——————
如果你耐心地一行一行讀到這里,我相信經過了兩節的鋪墊,你已經掌握了足以繼續讀下去的基礎知識。那么廢話到此為止,下面開始介紹本帖的重點問題:【鍵位沖突】。
在剛才的段落中,你已經知道了系統是如何判定單個鍵有沒有按下的。但我們人類的雙手上長了十個手指,誰也不能保證不會同時按下兩個按鍵——甚至很多時候組合鍵是故意設計要用的。這樣一來,就會有一個潛在的問題出現……
請回憶一下剛才用來舉例的36格矩陣圖,如果我們同時按下B、H、G鍵,在程序看來是什么樣子呢?
像平時一樣,它從(A1,B1)開始檢測,現實中我們並沒有按下A鍵,所以當A1=0,其他引腳=1的時候,B1的值應該是1,表示A鍵沒有被按下才對。但是,請注意:
由於G鍵被按下,A1和B2是接通的,
由於H鍵被按下,B2和A2是接通的,
由於B鍵被按下,A2和B1也是接通的!
也就是說,現在的電路中,A1和B1其實是連在一起的!
還記得嗎?不管多少個1相乘,只要中間有0,最后就會變成0。
換句話說,我們見A1和B1沒有直接連通,就天真地以為B1的奶酪不會被吃掉——但有個致命的錯誤就在於我們根本不關注其它奶酪。瞬間,電流飛馳,經過3個按鍵,最終鑽進地下。這只飛快的小老鼠沿着管線從A1出發,先是吃掉了B2的奶酪,然后又吃掉了A2,最后從B1鑽出來大快朵頤。(注:嚴格來說,其實老鼠與電流方向是相反的,此處的比喻是為了更容易理解)
就這樣,芯片以為A鍵也被按下了。
事實上,按下這4鍵中的任意3鍵,在電腦看來都是相同的,因為A1、A2、B1、B2這四點已經變成短路的狀態。
任意兩行兩列所構成的4個交點,也即某長方形的四角所對應的4個鍵,同時按下3個時,都會出現這樣的問題——在四通八達的管道中,剩余的那個鍵的狀態到底是按下還是沒按下,對於芯片來講是一片茫然。怎么辦呢?
掃描按鍵的程序是人寫的,稍作改動也不是不可能。於是需要增加如下的處理方法:給它一個“小賬本”,隨時記錄當前按下的所有按鍵。每當按下或抬起某個鍵時,就在賬本中如實增加或抹除。但是,如果賬本顯示:某個“四角組合”其中已經有兩個按鍵同時按下時,這個組合剩余的鍵就被邏輯鎖定——即使你按了,程序也拒絕接受,除非之前的某個鍵抬起。
這樣設計的理由很簡單:寧可錯殺一千,不能放過一個,不知道按沒按的話,當成沒按更保險。你能想象當你同時按下B鍵和G鍵以后,再按H鍵,屏幕上出現的卻是A嗎?太無厘頭了,還不如什么反應都沒有。
這也就是所謂的三鍵沖突的原型所在。
任何沒做無沖處理的矩陣式鍵盤,都存在許多特定的三鍵組合不能同時按。舉個著名的例子,黑寡婦的A、W、L。
你可能會說:“不會啊我的鍵盤可以七鍵一起按都沒沖突的。”
是的,不同品牌型號的鍵盤走線設計可能有區別,因此它們存在沖突的鍵位也不一樣。只要不構成四角組合關系,大部分鍵都是可以隨便同按的,以打字為主要用途的普通鍵盤,即使有這樣那樣的沖突,也足夠日常使用了。
但是四角組合數不勝數——比如上面例子中6×6的矩陣就存在多達55個四角組合,220種三鍵沖突,可想而知全尺寸鍵盤會有多少個鍵位沖突。雖然大部分沖突組合都是你平時不會按到的,但玩游戲的時候需要的鍵位總是千奇百怪各不相同,比如玩勁樂團可能需要SDF空格JKL不沖突,而BMIIDX則需要ZSXDCFV不沖突。如果你什么都玩,有很大幾率會碰到那么一兩個沖突鍵位郁悶你。即使對鍵盤最沒要求的FPS游戲,還是有少數鍵盤的四角組合悲劇地包含QWA或者1WD之類經常需要一起按的鍵……
一個比較討巧的辦法就是把左側常用十來個鍵位的走線全部串到一起,這樣至少可以保證打CS情緒穩定。因為我們知道,會起沖突的按鍵是位於任意兩行兩列的4個交點中的3個,而全部處於同一列或同一行的鍵,不管怎么按也不會沖突。
當然,最完美的還是全鍵盤無沖突,也就是所謂的【NKRO】。這就要放在下一節講了。
——————無沖突的技術本源——————
之前你已經意識到了,普通的矩陣鍵盤,都會存在成百上千的三鍵沖突組合。但是市面上卻有那么幾款鍵盤,號稱全鍵無沖突,實際測試也是威武異常,整個手掌拍下去都能毫不猶豫地識別出來,這是為什么呢?
這里要介紹一個美妙的電氣元件——【二極管】。
二極管是計算機邏輯電路最基本的元件之一(包括CPU芯片在內的各種集成電路芯片內部都有大量的二極管和三極管),大家津津樂道的LED就是二極管中能發光的一種。
一個典型的二極管會有兩條腿,即陽極和陰極。它的特點就是——電流只能從它的陽極流向陰極,而反向則難以通過。
如果身為芯片的你捏着一個二極管的兩端,你左手是1,右手是0時,只消一瞬間,左手的1就會變成0。但若調換成右手是1,左手是0,右手的1則不會受到影響。這二極管就相當於一個單向的小門,老鼠只可以從這邊跑到那邊,卻不能從那邊跑到這邊。
那么這個特點對我們具體有什么幫助呢?
只要你回憶一下按鍵沖突的問題是如何產生的,就會恍然大悟了。
沖突,是為了防止當A1和B2、A2和B2、A2和B1分別連通時,程序誤以為A1和B1也連通,因此當發現3個按鍵互相形成回路時,就屏蔽第三顆按鍵的設計。
現在,我們在每個按鍵的電路中增加一個二極管,讓小老鼠只能從A端跑到B端,而不能從B跑向A。
回到之前的例子,同時按下B、H、G三個鍵。盡管H鍵接通了A2和B2,但由於二極管的限制,信息只能從A2到B2傳導,而不能從B2到A2。
於是,雖然受G鍵按下的影響,當A1=0的時候,B2的值被修改為0,但這個0在這里就到此為止了。因為老鼠到達B2后,被門擋住,無法繼續去吃A2的奶酪。既然A2不會跟着變成0,而是保持正確的1,B1的值當然也還是1。
由此,系統自然能夠判斷出,A鍵沒有被按下,和事實一致。也就是說,二極管的防逆流特性,徹底消除了按鍵之間的干擾。
有了這些二極管做保障,自然根本不需要什么屏蔽第三顆按鍵的邏輯了。於是,每一顆按鍵可以獨立自主反應,活動自如,成就了我們的無沖突鍵盤。
至於為什么無沖突鍵盤基本都是機械鍵盤,我想可能有兩個原因:
1,機械鍵盤采取的電路板比較容易安裝二極管。而薄膜鍵盤基本無解。
2,機械鍵盤本身的定位也比較高,相對這個售價水平來講,增加一百顆二極管的成本並不顯著。
——————USB永遠的痛——————
講了這么多,終於到最后一節了。前面已經把造成鍵盤沖突的原理和解決辦法從頭到尾介紹了一遍,但還沒有講過USB接口的鍵盤,即使硬件上是NKRO結構了,為什么還是只能做到6鍵無沖突。
這里所指的6鍵,是除去Ctrl、Shift、Alt、Win之外的鍵,同時按下任意6個都不會有沖突,但第7個鍵按下就沒有反應——或者會直接抹掉第一個鍵,總之邏輯上同時只能有6個鍵處於按下的狀態。
但是這樣的鍵盤,使用PS2轉接頭連接電腦,又可以實現完美NKRO(除了部分鍵盤干脆不支持PS/2轉接,例如poker)。
看來問題就出在USB接口上了。
事實上的確是這樣,因為鍵盤輸入設備在USB接口和PS/2接口的傳輸協議完全不同,也就是說,它們采取了完全不同的工作方式,也難怪效果不同。現在你能買到的大部分機械鍵盤,其主控芯片可以根據當前連接的端口,自動適應PS/2或USB協議。只有少量無法轉接。
既然你已經堅持看到這里了,我相信你對它們的具體區別會比較感興趣,別着急,這就慢慢道來。
(還是有些廢話:如果你搞不清【字節】和【位】的概念請看本段)
位(bit,縮寫為小寫的b),就是二進制位,取值范圍只有0和1兩個值,是最小的單位。
字節(Byte,縮寫為大寫的B),為8個位的組合,取值范圍是從0到255(2的8次方),也是常見的計算機數據量單位。
1字節=8位,所以如果你的網速標稱10Mb,實際下載速度只有1.25MB。
PS/2協議下,鍵盤是每次發生按鍵/抬鍵動作,都會發送數據信號給主機。通常按下一個鍵這個動作所包含的數據(通碼)為1或2個字節,抬起一個鍵(斷碼)則是2或3個字節。如果按住一個鍵不放,則會不停地向主機循環發送通碼,直到抬起按鍵發送斷碼。根據10-20kHz的工作頻率規范,每位數據的傳輸時間大約是40-80微秒,加上中間的保留延遲,每個字節會占用0.5-1ms的傳輸時間。不過在實際應用中,這個延遲完全可以接受——即使像鐵拳那樣以幀來計算的格斗游戲,對出招的嚴格度也不會低於16ms。
而USB協議下,鍵盤會以某個固定的回報率(每秒125-1000次),定期向主機發送當前按鍵的狀態,每次發送8個字節,這8個字節的具體內容則是:
第一個字節:8位分別表示左右的Ctrl、Shift、Alt、Win各自是否被按下。這8個鍵統稱為【modifer key】,因為規范已經事先定義好每一位的含義,從而得以能夠只用一個字節就表示8個鍵的狀態。
第二個字節:保留(無用)
其余6個字節:當前正按下的6個【普通按鍵】(如果按了7個以上,根據鍵盤主控芯片內置的程序,可能取最先按的6個,也可能取最后按的6個)。
即每1-8ms,可以發送最多14個按鍵的狀態信息。
發現問題所在了吧?如果說按鍵是上廁所的人,傳輸協議是看守廁所的大叔……
PS/2大叔會一直盯着廁所門口,每次有人進去就向主機匯報,有人出來再匯報。
USB大叔呢,則是急急忙忙沖進廁所,看有哪些人在,記在小紙條上,然后跑出來一起匯報,之后再沖進去,如此循環。可惜他的小紙條地方太小,只夠寫下6個人的名字(另外還有8位鬧肚子的熟客是事先打好招呼的,只要用暗號記載匯報就可以)。
所以說,USB協議下,包含兩邊的Ctrl、Shift、Alt、Win在內,單鍵盤最多只能同時識別14個鍵。如果只算普通鍵,則只能同時識別6個。
至於最近一年剛興起的【USB無沖】技術,似乎是通過將一個物理鍵盤虛擬成多個邏輯鍵盤實現的,程序兼容性還有待提高,在此暫且不表。
關於鍵盤沖突那點事,差不多也說完了。感謝你耐心閱讀本文。