【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十:PS/2模組④ — 普通滑鼠
實驗十:PS/2模組④ — 普通滑鼠
學習PS/2鍵盤以後,接下來就要學習 PS/2 滑鼠。PS/2滑鼠相較PS/2鍵盤,驅動難度稍微高了一點點,因為FPGA(從機)不僅僅是從PS/2滑鼠哪裡讀取資料,FPGA還要往滑鼠裡寫資料 ... 反之,FPGA只要對PS/2鍵盤讀取資料即可。然而,最傷腦筋的地方就在於PS/2傳輸協議有奇怪的寫時序。
圖10.1 從機視角,從機讀資料。
為了方便理解,餘下我們經由從機的視角去觀察PS/2的讀寫時序。圖10.1是從機視角的讀時序,從機都是皆由 PS2_CLK的下降沿讀取1幀為11位的資料 ... 期間,有11個下降沿,不過為了方便呼叫,筆者將其整理為偽函式,結果如程式碼10.1所示:
1. 32: // Start bit
2. if( isH2L ) i <= i + 1'b1;
3.
4. 33,34,35,36,37,38,39,40: // Data byte
5. if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1; end
6.
7. 41: // Parity bit
8. if( isH2L ) i <= i + 1'b1;
9.
10. 42: // Stop bit
11. if( isH2L ) i <= Go;
程式碼10.1
圖10.2從機視角,從機寫資料,第一幀。
首先我們必須明白,PS2_CLK 與 PS2_DAT 訊號是雙向的IO口,由於FPGA是從機的關係,所以FPGA一開始都處於輸入狀態,而PS/2滑鼠一開始則處於輸出狀態。假設 isQ1 是FPGA針對PS2_CLK的輸出控制,isQ2 是FPGA針對 PS2_DAT 的輸出控制,然而復位狀態(初始化)都為拉低狀態。
FPGA為了獲取資料的傳送權,首先它必須摘取 PS2_CLK,亦即拉高 isQ1然後又拉低PS2_CLK 100us。
事後,FPGA必須釋放 PS2_CLK,亦即關閉 IO或者拉低 isQ1,期間順手拉高 isQ2,讓 PS2_DAT 成為輸出狀態。那麼重點來了,讀者是否看見黃色的圈圈?當FPGA釋放 PS2_CLK瞬間,PS/2滑鼠早已拉低 PS2_CLK,FPGA也因此錯過PS2_CLK第一次珍貴的下降沿。在此,讀者需要好好注意,因為許多資料都忽略這點,筆者也不小心撲街好幾次。
讀者需要知道,根據PS/2傳輸協議,從機(FPGA)不管怎樣掙扎也沒有 PS2_CLK的擁有權。換句話說,無論從機(FPGA)是寫資料還是讀資料,從機(FPGA)都必須藉助主機(PS/2滑鼠)傳送過來的 PS2_CLK下降沿。如果我們不小心忽略第一個下降沿,餘下幾個下降沿,我們也會搞錯次序,最終造成資料讀取錯位的悲劇。
FPGA無論是寫資料還是讀資料,從機都必須借用 PS2_CLK 的下降沿。FPGA釋放PS2_CLK之後,isQ2立即拉高,PS2_DAT 也隨之拉低以示一幀資料的起始位 ... 直至下一個下降沿到來為此。
FPGA一共用了10個下降沿,即T0~T9傳送8位資料位,1位校驗位,還有1位停止位。T9過去不久,PS2_CLK就會引來上升沿,此刻PS/2滑鼠則會反饋1位應答位,不過此刻也無暇招呼它。T10之際,那是最後一個下降沿,FPGA拉低isQ2讓PS2_DAT成為輸入狀態,並且讀取應答位,然而懶惰的筆者決定無視它。就這樣從機寫一幀資料就結束了。
在這裡,讀者是否覺得從機寫一幀資料比從機讀一幀資料還要麻煩呢?沒錯,筆者也這樣覺得。可是,麻煩歸麻煩,餘下我們還要完成Verilog的描述工作,結果如程式碼10.2所示:
1. /****************/ // PS2 Write Function
2.
3. 32: // Press low CLK 100us
4. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
5. else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
6.
7. 33: // Release PS2_CLK and set in, PS2_DAT set out
8. begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
9.
10. 34: // start bit
11. begin rDAT <= 1'b0; i <= i + 1'b1; end
12.
13. 35,36,37,38,39,40,41,42,43: // Data byte
14. if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
15.
16. 44: // Stop bit
17. if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
18.
19. 45: // Ack bit
20. if( isH2L ) begin i <= i + 1'b1; end
21.
22. 46: // PS2_DAT set in
23. begin isQ2 <= 1'b0; i <= i + 1'b1; end
24. 。。。。。。
25.
26. assign PS2_CLK = isQ1 ? rCLK : 1'bz;
27. assign PS2_DAT = isQ2 ? rDAT : 1'bz;
程式碼10.2
如程式碼10.2所示:
步驟32,來拉高 isQ1又拉低 rCLK 持續 100us。
步驟33,拉低 isQ1釋放 PS2_CLK,然後又拉高 isQ2 準備傳送資料。
步驟34,重點!由於錯過第一個下降沿,我們只能拉低 rDAT產生起始位。
步驟35~43,傳送8位資料位,還有1位校驗位。
步驟44,傳送結束位。
步驟45,無視應答位。
步驟46,拉低isQ2,讓PS2_CLK為輸入狀態。
圖10.3主機視角,從機寫資料,第一幀。
接下來,讓我們經由主機的視角去觀察,主機如何讀取從機發送過來的資料。初始狀態,主機亦即 PS/2滑鼠掌握 PS2_CLK與 PS2_DAT。一旦從機即FPGA載取PS2_CLK,並且拉低輸出100us,立刻PS/2滑鼠已經理解從機準備傳送資料。100us過後,FPGA釋放 PS2_CLK並且載取PS2_DAT,PS/2滑鼠便開始產生時鐘。如圖10.2所示,PS/2滑鼠都是借用上升沿讀取FPGA傳送過來的資料。大約11個上升沿過後,PS/2滑鼠就結束讀取動作,並且反饋應答位。
圖10.4主機視角,從機寫資料,第二幀。
每當主機接收完畢一幀資料,就會反饋一幀資料。如圖10.2所示,那是圖10.1的下半部分,依然是從主機視角觀察從機寫資料。圖中顯示,每當PS/2滑鼠(主機)接收完畢一幀資料以後,PS/2滑鼠便會反饋一幀資料,亦即PS/2滑鼠會再度借用10個上升沿傳送一幀資料。期間,FPGA(從機)的 PS2_CLK 與 PS2_DAT都都處於輸入狀態。
圖10.5主機視角,從機寫資料,完整時序。
圖10.5是圖10.3與圖10.4的完整時序(主機視角)。
圖10.6從機視角,從機寫資料,第二幀。
既然主機反饋一幀資料,那麼從機也不能無視,如圖10.6所示,那是經由從機視角觀察從機如何讀取下一幀資料。期間,從機借用11個下降沿讀取1一幀資料。
圖10.7從機視角,從機寫資料,完整時序。
圖10.7是圖10.2與圖10.5的完整時序(從機視角)。
為此,程式碼10.2可以繼續擴張,結果如程式碼10.3所示:
1. /****************/ // PS2 Write Function
2.
3. 32: // Press low CLK 100us
4. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
5. else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
6.
7. 33: // Release PS2_CLK and set in, PS2_DAT set out
8. begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
9.
10. 34: // start bit
11. begin rDAT <= 1'b0; i <= i + 1'b1; end
12.
13. 35,36,37,38,39,40,41,42,43: // Data byte
14. if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
15.
16. 44: // Stop bit
17. if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
18.
19. 45: // Ack bit
20. if( isH2L ) begin i <= i + 1'b1; end
21.
22. 46: // PS2_DAT set in
23. begin isQ2 <= 1'b0; i <= i + 1'b1; end
24.
25. 47,48,49,50,51,52,53,54,55,56,57: // 1 Frame
26. if( isH2L ) i <= i + 1'b1;
27.
28. 58: // Return
29. i <= Go;
30.
31. ......
32.
33. assign PS2_CLK = isQ1 ? rCLK : 1'bz;
34. assign PS2_DAT = isQ2 ? rDAT : 1'bz;
程式碼10.3
步驟32~46曾在前面說過,即從機發送一幀資料。餘下步驟47~57(共有11個步驟),主要用來過濾主機反饋過來的下一幀資料,步驟58則是步驟返回。不過為什麼步驟47~57不是讀取資料,而是過濾資料?彆著急,答案很快就會揭曉,暫時忍耐一下。
呼!PS/2傳輸協議的寫資料(從機視角)解釋起來真有夠嗆。最後讓我們來總結一下,主機無論是輸出資料,還是讀取資料都是借用 PS2_CLK的上升沿。反之,從機無論是輸出資料,還是讀取資料都是借用 PS2_CLK 的下降沿。PS/2傳輸協議是可謂是爺爺級別的傳輸協議吧,因為近代的傳輸協議不管物件是從機還是主機,或者是寫還是讀,一般都是上升沿設定資料下降沿鎖存資料。很少情況是主機使用一個時間沿,從機使用另一個時間沿,例如 SPI傳輸協議就是最好的例子。
說完PS/2傳輸協議,接下來我們要進入PS/2滑鼠的主題了。
滑鼠也有普通滑鼠與滾擴充套件滑鼠之分 ... 所謂普通滑鼠就是包含左鍵,中建還有右鍵;所謂擴充套件滑鼠則包含左鍵,中建,右鍵還有滾輪,擴充套件滑鼠也稱為滾輪滑鼠,不過一般的擴充套件滑鼠只有一個滾輪而已。實驗十的實驗目的就是驅動普通滑鼠。PS/2滑鼠不像PS/2鍵盤,PS/2滑鼠即使一上電也不會立即工作,期間從機必須將它使能才行。
普通滑鼠一旦上電就便會立即復位,然後得到預設化引數,並且進入Steam模式。所謂Steam模式,即位置狀況或者按鍵狀況一有變化就會滑鼠便會立即傳送報告。話雖然那麼說,實際上 Stream 模式還要依賴採集頻率,預設的採集頻率是 100次/秒,即採集間隔為10ms,也就是說滑鼠在一秒內會檢測100次位置狀況還有按鍵狀況。
舉例而言,假設筆者按著左鍵不放,那麼滑鼠在一秒內會發送100次“左鍵好疼!左鍵好疼!“,直至筆者釋放左鍵為止。再假設滑鼠不小心被筆者退了一下,然後滑鼠在10ms內向左移動10mm,當滑鼠察覺位置狀況發生變化以後,滑鼠便會發送“哎呀!被人推向左邊10mm了!”。
圖10.7.1 滑鼠的位置標示。
滑鼠為了標示位置,內建2組9位的暫存器X與Y,結果如圖10.7.1所示。預設下,滑鼠的解析度為4計數/mm。此外,滑鼠也有能力辨識4處的移動方向,例如左移 10mm 暫存器X便計數 -40,右移10mm 暫存器X便計數 +40,上移10mm 暫存器Y便計數 +40,下移 10mm 暫存器Y便計數 -40。滑鼠每隔10ms(預設採集頻率)便會清零一次暫存器X與Y的內容。
PS/2滑鼠不像PS/2鍵盤一上電便立即工作,我們必須事先發送命令8’hF4即“使能滑鼠傳送資料”,開啟資料的水龍頭。每當滑鼠接收一幀資料,滑鼠便會反饋一幀資料,為此 ... 從機每次向滑鼠寫入一幀資料,就必須接收一幀反饋資料。反饋資料為8’hFA表示“資料接收成功”,反饋資料為 8’hFE表示“第一幀資料接收失敗”,反饋資料為 8’hFC則表示“第二幀資料接收成功”(有些命令是由2幀或者以上組成)。
圖10.8 從機發送 “使能報告”命令,滑鼠反饋接收成功。
為了驅使滑鼠工作,PS/2滑鼠上電以後,從機必須傳送命令 8’hF4,並且接收反饋 8’hFA。如果一切順利,那麼滑鼠就會開始工作,結果如圖10.8所示。滑鼠“使能”以後,滑鼠便處於就緒狀態,採集便開始 ... 此刻,如果滑鼠的位置狀況或者按鍵狀況發生變化,滑鼠就會發送3幀,亦即3位元組的報告。
圖10.9 滑鼠傳送報告。
如圖10.9所示,那是一份3個位元組的報告,Verilog可以這樣描述:
1. 0: // Read 1st byte
2. begin i <= FF_Read; Go <= i + 1'b1; end
3.
4. 1: // Store 1st byte
5. begin D1[7:0] <= T; i <= i + 1'b1; end
6.
7. 2: // Read 2nd byte
8. begin i <= FF_Read; Go <= i + 1'b1; end
9.
10. 3: // Store 2nd byte
11. begin D1[15:8] <= T; i <= i + 1'b1; end
12.
13. 4: // Read 3rd byte
14. begin i <= FF_Read; Go <= i + 1'b1; end
15.
16. 5: // Store 3rd byte
17. begin D1[23:16] <= T; i <= i + 1'b1; end
18.
19. ......
20.
21. 32: // Start bit
22. if( isH2L ) i <= i + 1'b1;
23.
24. 33,34,35,36,37,38,39,40: // Data byte
25. if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1; end
26.
27. 41: // Parity bit
28. if( isH2L ) i <= i + 1'b1;
29.
30. 42: // Stop bit
31. if( isH2L ) i <= Go;
程式碼10.4
步驟32~42是讀一幀資料的偽函式,步驟0~1讀取第一位元組並且暫存道D[7:0],步驟
2~3讀取第二位元組並且暫存到D[15:8],步驟4~5讀取第三位元組並且暫存到D[23:16]。
至於報告的內容如表10.1所示:
表10.1 普通滑鼠的報告。
位元組/位 |
[7] |
[6] |
[5] |
[4] |
[3] |
[2] |
[1] |
[0] |
位元組一 |
Y溢位位 |
X溢位位 |
Y[8]符號位 |
X[8]符號位 |
保留 |
中鍵 |
右鍵 |
左鍵 |
位元組二 |
X[7:0] |
|||||||
位元組三 |
Y[7:0] |
如表10.1所示,位元組一的第四位表示按鍵狀況以外,位元組一的高四位也與內部暫存器X與Y有關。位元組二為暫存器X的內容,位元組三位暫存器Y的內容。
位元組一,[0]標示左鍵,1表示左鍵按下;[1]標示右鍵,1表示右鍵按下;[2]標示中鍵,1表示中鍵按下;[4]為字暫存器的最高位也是符號位;[5]為暫存器最高位也是符號位;節二是暫存器X的低八位,位元組三是暫存器Y的低八位,因此暫存器X與Y的位寬為9。這樣作的目的是為了使用補碼錶示滑鼠的移動狀況。至於補碼是什麼?失憶的朋友請複習《時序篇》。
圖10.9.1 滑鼠的有效位置。
假設暫存器X的內容為 9’b1_1111_1100,[8]為1’b1表示滑鼠正在左移,[7:0]為8’b1111_1100也是 8個計數,亦即移動2mm的距離。因此 9’b1_1111_1100 表示滑鼠左移2mm的舉例。再假設暫存器Y的內容為 9’b0_0001_0000,[8]為0表示滑鼠正在上移,[7:0] 為 8’b0001_0000也是16個計數,亦即移動4mm。因此 9’b0_0001_0000表示滑鼠上移4mm。結果如圖10.9.1所示。
上述內容理解完畢以後,我們便可以開始建模了。
圖10.10 實驗十的建模圖。
圖10.10 是實驗十的建模圖,組合模組 ps2_demo 內部包含,PS/2初始化功能模組,PS/2讀功能模組,數碼管基礎模組,然後中間還要正直化的即時操作。顧名思義,PS/2初始化功能模組主要負責初始化的工作,簡言之就是傳送命令 8’hF4,完後便拉高oEn使能PS/2讀功能模組。PS/2讀功能模組接收 iEn拉高便會開始讀取3位元組的報告,並且經由oData將其輸出。
稍微注意一下PS2_CLK還有PS2_DAT頂層訊號,由於PS/2初始化功能模組需要雙向訪問PS/2滑鼠,為此該頂層訊號皆是出入狀態(IO)。反之,PS/2讀功能模組只有接收資料而已,因此該頂層訊號只是出入狀態。PS/2讀功能模組的oData,其中[2:0]是3只按鍵的狀況,並且直接驅動三位LED資源。
至於[23:4]則是暫存器X與暫存器Y的內容,它們經由即時操作正直化以後便聯合驅動數碼管基礎模組,然後再顯示內容。
ps2_init_funcmod.v
圖10.11 PS/2 初始化功能模組的建模圖。
由於該模組需要來問讀寫PS/2滑鼠,因此頂層訊號 PS2_CLK與 PS2_DAT 都是雙向,亦即IO口。此外,一旦該模組完成初始化的工作,oEn就會一直拉高。
1. module ps2_init_funcmod
2. (
3. input CLOCK, RESET,
4. inout PS2_CLK,
5. inout PS2_DAT,
6. output oEn
7. );
8. parameter T100US = 13'd5000;
9. parameter FF_Write = 7'd32;
以上內容是出入端宣告。第8行是100us的常量宣告,第9行則是偽函式的入口。
11. /*******************************/ // sub1
12.
13. reg F2,F1;
14.
15. always @ ( posedge CLOCK or negedge RESET )
16. if( !RESET )
17. { F2,F1 } <= 2'b11;
18. else
19. { F2, F1 } <= { F1, PS2_CLK };
20.
21. /*******************************/ // Core
22.
23. wire isH2L = ( F2 == 1'b1 && F1 == 1'b0 );
以上是檢測電平變化的周邊操作。第23行則是下降沿的即時宣告。
24. reg [8:0]T;
25. reg [6:0]i,Go;
26. reg [12:0]C1;
27. reg rCLK,rDAT;
28. reg isQ1,isQ2,isEn;
29.
30. always @ ( posedge CLOCK or negedge RESET )
31. if( !RESET )
32. begin
33. T <= 9'd0;
34. C1 <= 13'd0;
35. { i,Go } <= { 7'd0,7'd0 };
36. { rCLK,rDAT } <= 2'b11;
37. { isQ1,isQ2,isEn } <= 3'b000;
38. end
39. else
以上內容為相關的暫存器宣告以及復位操作。注意,PS2_CLK與PS2_DAT預設下都是高電平,所示 rCLK 與 rDAT 被賦予復位值 2’b11。
40. case( i ) 41. 42. /***********/ // INIT Normal Mouse 43. 44. 0: // Send F4 1111_0100 45. begin T <= { 1'b0, 8'hF4 }; i <= FF_Write; Go <= i + 1'b1; end 46. 47. 1: 48. isEn <= 1'b1; 49.
以上內容是部分核心操作。步驟0~1是主操作,主要傳送命令 8’hF4,然後拉高isEn。第45行{ 1'b0, 8'hF4 },其中 1’b0是校驗位,PS/2的校驗位是“奇校驗”,如果“1”的數量為單數,那麼校驗位便是 0。如第45所示,8’hF4有5個“1”所示,校驗位為0。
50. /****************/ // PS2 Write Function
51.
52. 32: // Press low CLK 100us
53. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
54. else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
55.
56. 33: // Release PS2_CLK and set in, PS2_DAT set out
57. begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
58.
59. 34: // start bit
60. begin rDAT <= 1'b0; i <= i + 1'b1; end
61.
62. 35,36,37,38,39,40,41,42,43: // Data byte
63. if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
64.
65. 44: // Stop bit
66. if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
67.
68. 45: // Ack bit
69. if( isH2L ) begin i <= i + 1'b1; end
70.
71. 46: // PS2_DAT set in
72. begin isQ2 <= 1'b0; i <= i + 1'b1; end
73.
74. 47,48,49,50,51,52,53,54,55,56,57: // 1 Frame
75. if( isH2L ) i <= i + 1'b1;
76.
77. 58: // Return
78. i <= Go;
79.
80. endcase
以上內容是部分核心操作。第32~58行是從機寫一幀資料,讀一幀反饋資料的偽函式。
81.
82. assign PS2_CLK = isQ1 ? rCLK : 1'bz;
83. assign PS2_DAT = isQ2 ? rDAT : 1'bz;
84. assign oEn = isEn;
85.
86. endmodule
以上內容是輸出驅動宣告。
ps2_read_funcmod.v
圖10.12 PS/2 讀化功能模組的建模圖。
PS/2讀功能模組,如果iEn不拉高就不工作。此外,該模組也只是讀入3位元組的報告而已,完後便經由oTrig產生完成訊號,報告內容則經由 oData。
1. module ps2_read_funcmod
2. (
3. input CLOCK, RESET,
4. input PS2_CLK,PS2_DAT,
5. input iEn,
6. output oTrig,
7. output [23:0]oData
8. );
9. parameter FF_Read = 7'd32;
以上內容是出入端宣告。第9行則是偽函式的入口地址。
11. /*******************************/ // sub1
12.
13. reg F2,F1;
14.
15. always @ ( posedge CLOCK or negedge RESET )
16. if( !RESET )
17. { F2,F1 } <= 2'b11;
18. else
19. { F2, F1 } <= { F1, PS2_CLK };
20.
21. /*******************************/ // core
22.
23. wire isH2L = ( F2 == 1'b1 && F1 == 1'b0 );
以上內容是檢測電平變化的周邊操作,第23行則是下降沿的即時宣告。
24. reg [23:0]D1;
25. reg [7:0]T;
26. reg [6:0]i,Go;
27. reg isDone;
28.
29. always @ ( posedge CLOCK or negedge RESET )
30. if( !RESET )
31. begin
32. D1 <= 24'd0;
33. T <= 8'd0;
34. { i,Go } <= { 7'd0,7'd0 };
35. isDone <= 1'b0;
36. end
以上內容是相關的暫存器宣告以及復位操作。
37. else if( iEn )
38. case( i )
39.
40. /*********/ // Normal mouse
41.
42. 0: // Read 1st byte
43. begin i <= FF_Read; Go <= i + 1'b1; end
44.
45. 1: // Store 1st byte
46. begin D1[7:0] <= T; i <= i + 1'b1; end
47.
48. 2: // Read 2nd byte
49. begin i <= FF_Read; Go <= i + 1'b1; end
50.
51. 3: // Store 2nd byte
52. begin D1[15:8] <= T; i <= i + 1'b1; end
53.
54. 4: // Read 3rd byte
55. begin i <= FF_Read; Go <= i + 1'b1; end
56.
57. 5: // Store 3rd byte
58. begin D1[23:16] <= T; i <= i + 1'b1; end
59.
60. 6:
61. begin isDone <= 1'b1; i <= i + 1'b1; end
62.
63. 7:
64. begin isDone <= 1'b0; i <= 7'd0; end
65.
以上內容為部分核心操作。第37行 if( iEn ) 表示,iEn不拉高核心操作就不執行。步驟0~1是讀取第一位元組,步驟2~3是讀取第二位元組,步驟4~5是讀取第三位元組,步驟6~7則是反饋完成訊號,以示一次性的報告讀取已經完成。完後,i便指向步驟0。
66. /****************/ // PS2 Write Function
67.
68. 32: // Start bit
69. if( isH2L ) i <= i + 1'b1;
70.
71. 33,34,35,36,37,38,39,40: // Data byte
72. if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1; end
73.
74. 41: // Parity bit
75. if( isH2L ) i <= i + 1'b1;
76.
77. 42: // Stop bit
78. if( isH2L ) i <= Go;
79.
80. endcase
以上內容為部分核心操作。步驟32~42則是偽函式,主要是負責讀取一幀資料。
81.
82. assign oTrig = isDone;
83. assign oData = D1;
84.
85. endmodule
以上內容是輸出驅動宣告。
ps2_demo.v
組合模組ps2_demo.v的建模圖就不再重複貼上了。
1. module ps2_demo
2. (
3. input CLOCK, RESET,
4. inout PS2_CLK, PS2_DAT,
5. output [7:0]DIG,
6. output [5:0]SEL,
7. output [2:0]LED
8. );
9. wire EnU1;
10.
11. ps2_init_funcmod U1
12. (
13. .CLOCK( CLOCK ),
14. .RESET( RESET ),
15. .PS2_CLK( PS2_CLK ), // < top
16. .PS2_DAT( PS2_DAT ), // < top
17. .oEn( EnU1 ) // > U2
18. );
19.
20. wire [23:0]DataU2;
21.
22. ps2_read_funcmod U2
23. (
24. .CLOCK( CLOCK ),
25. .RESET( RESET ),
26. .PS2_CLK( PS2_CLK ), // < top
27. .PS2_DAT( PS2_DAT ), // < top
28. .iEn( EnU1 ), // < U1
29. .oTrig(),
30. .oData( DataU2 ) // > U2
31. );
32.
33. // immediate proses
34. wire[7:0] X = DataU2[4] ? (~DataU2[15:8] + 1'b1) : DataU2[15:8];
35. wire[7:0] Y = DataU2[5] ? (~DataU2[23:16] + 1'b1) : DataU2[23:16];
36.
37. smg_basemod U3
38. (
39. .CLOCK( CLOCK ),
40. .RESET( RESET ),
41. .DIG( DIG ), // > top
42. .SEL( SEL ), // > top
43. .iData( { 3'd0,DataU2[5],Y,3'd0,DataU2[4],X }) // < U2
44. );
45.
46. assign LED = {DataU2[1], DataU2[2], DataU2[0]};
47.
48. endmodule
該程式碼非常簡單,第30行表示U2的oTrig無用武之地。第34~35行是正直化的即時宣告。第43行是U3的聯合驅動,其中 3’d0, DataU2[5] 表示第1位數碼管顯示Y的符號位,Y表示第2~3位的數碼管顯示Y的正直結果,3’d0,DataU2[4] 表示第4位數碼管顯示X的符號位,X表示第5~6位數碼管顯示X的正直結果。第46行則是各個按鍵情況直接驅動LED資源。
編譯完成並且下載程式。假設筆者按下左鍵,那麼LED[0]便會點亮,釋放則消滅。再假設筆者向左移動滑鼠,那麼滑鼠第4位數碼管會顯示1,第5~6數碼管則會顯示X的內容,亦即滑鼠移動的舉例。
細節一:精簡與直觀
1. 0: // Read 1st byte
2. begin i <= FF_Read; Go <= i + 1'b1; end
3. 1: // Store 1st byte
4. begin D1[7:0] <= T; i <= i + 1'b1; end
5. 2: // Read 2nd byte
6. begin i <= FF_Read; Go <= i + 1'b1; end
7. 3: // Store 2nd byte
8. begin D1[15:8] <= T; i <= i + 1'b1; end
9. 4: // Read 3rd byte
10. begin i <= FF_Read; Go <= i + 1'b1; end
11. 5: // Store 3rd byte
12. begin D1[23:16] <= T; i <= i + 1'b1; end
程式碼10.5
程式碼10.5是PS/2讀功能模組的部分內容,期間步驟0~5表示3位元組讀取且暫存的過程
。事實上,程式碼10.5可以進一步精簡,結果如程式碼10.6所示:
13. 0: // Read 1st byte
14. begin i <= FF_Read; Go <= i + 1'b1; end
15. 1: // Store 1st byte
16. begin D1[7:0] <= T; i <= FF_Read; Go <= i + 1'b1;end
17. 2: // Read 2nd byte
18. begin D1[15:8] <= T; i <= FF_Read; Go <= i + 1'b1; end
19. 3: // Store 2nd byte
20. begin D1[23:16] <= T; i <= i + 1'b1; end
21. ......
程式碼10.6
程式碼10.6相較程式碼10.5,它雖然有很高程度的精簡度,不過直觀程度卻不如程式碼10.6。
到頭來到底是直觀好,還是精簡好,唯有見仁見智了。
細節二:完整的個體模組
圖10.13 實驗十的完整個體模組。
圖10.13是PS/2滑鼠基礎模組,裡邊包含PS/2初始化功能模組,還有PS/2讀功能模組。
該模組的最左邊是頂層訊號 PS2_CLK 與 PS2_DAT 的輸入,滑鼠完成初始化以後,PS/2初始化功能模組便會拉高 oEn 使能 PS/2讀功能模組。PS/2讀功能模組的左邊除了頂層訊號以外還有iEn,iEn不拉高該模組就不工作。PS/2讀功能模組每完成3位元組報告的讀取,就會經由 oTrig 產生完成訊號。
ps2mouse_basemod.v
1. module ps2mouse_basemod
2. (
3. input CLOCK, RESET,
4. inout PS2_CLK, PS2_DAT,
5. output oTrig,
6. output [31:0]oData
7. );
8. wire EnU1;
9.
10. ps2_init_funcmod U1
11. (
12. .CLOCK( CLOCK ),
13. .RESET( RESET ),
14. .PS2_CLK( PS2_CLK ), // < top
15. .PS2_DAT( PS2_DAT ), // < top
16. .oEn( EnU1 ) // > U2
17. );
18.
19. ps2_read_funcmod U2
20. (
21. .CLOCK( CLOCK ),
22. .RESET( RESET ),
23. .PS2_CLK( PS2_CLK ), // < top
24. .PS2_DAT( PS2_DAT ), // < top
25. .iEn( EnU1 ), // < U1
26. .oTrig( oTrig ), // > Top
27. .oData( oData ) // > Top
28. );
29.
30. endmodule
相關推薦
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十:PS/2模組④ — 普通滑鼠
實驗十:PS/2模組④ — 普通滑鼠 學習PS/2鍵盤以後,接下來就要學習 PS/2 滑鼠。PS/2滑鼠相較PS/2鍵盤,驅動難度稍微高了一點點,因為FPGA(從機)不僅僅是從PS/2滑鼠哪裡讀取資料,FPGA還要往滑鼠裡寫資料 ... 反之,FPGA只要對PS/2鍵盤讀取資料即可。然而,最傷腦筋的地方就在
【黑金原創教程】【FPGA那些事兒-驅動篇I 】【實驗一】流水燈模組
實驗一:流水燈模組 對於發展商而言,動土儀式無疑是最重要的任務。為此,流水燈實驗作為低階建模II的動土儀式再適合不過了。廢話少說,我們還是開始實驗吧。 圖1.1 實驗一建模圖。 如圖1.1 所示,實驗一有名為 led_funcmod的功能模組。如果無視環境訊號(時鐘訊號還有復位訊號),該功能模組只有
【黑金原創教程】【FPGA那些事兒-驅動篇I 】連載導讀
前言: 無數晝夜的來回輪替以後,這本《驅動篇I》終於編輯完畢了,筆者真的感動到連鼻涕也流下來。所謂驅動就是認識硬體,還有前期建模。雖然《驅動篇I》的硬體都是我們熟悉的老友記,例如UART,VGA等,但是《驅動篇I》貴就貴在建模技巧的昇華,亦即低階建模II。 話說低階建模II,讀過《建模篇》的朋友多少也會面
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗三:按鍵模組② — 點選與長點選
實驗三:按鍵模組② — 點選與長點選 實驗二我們學過按鍵功能模組的基礎內容,其中我們知道按鍵功能模組有如下操作: l 電平變化檢測; l 過濾抖動; l 產生有效按鍵。 實驗三我們也會z執行同樣的事情,不過卻是產生不一樣的有效按鍵: l 按下有效(點選); l 長按下有效(長點選)。 圖3
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二:按鍵模組①
實驗二:按鍵模組① - 消抖 按鍵消抖實驗可謂是經典中的經典,按鍵消抖實驗雖曾在《建模篇》出現過,而且還惹來一堆麻煩。事實上,筆者這是在刁難各位同學,好讓對方的慣性思維短路一下,但是慘遭口水攻擊 ... 面對它,筆者宛如被甩的男人,對它又愛又恨。不管怎麼樣,如今 I’ll be back,筆者再也不會重複一
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗六:數碼管模組
實驗六:數碼管模組 有關數碼管的驅動,想必讀者已經學爛了 ... 不過,作為學習的新儀式,再爛的東西也要溫故知新,不然學習就會不健全。黑金開發板上的數碼管資源,由始至終都沒有改變過,筆者因此由身懷念。為了點亮多位數碼管從而顯示數字,一般都會採用動態掃描,然而有關動態掃描的資訊請怒筆者不再重複。在此,同樣也是
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗四:按鍵模組③ — 單擊與雙擊
實驗四:按鍵模組③ — 單擊與雙擊 實驗三我們建立了“點選”還有“長點選”等有效按鍵的多功能按鍵模組。在此,實驗四同樣也是建立多功能按鍵模組,不過卻有不同的有效按鍵。實驗四的按鍵功能模組有以下兩項有效按鍵: l 單擊(按下有效); l 雙擊(連續按下兩下有效)。 圖4.1 單擊有效按鍵,時序示意圖
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗五:按鍵模組④ — 點選,長點選,雙擊
實驗五:按鍵模組④ — 點選,長點選,雙擊 實驗二至實驗四,我們一共完成如下有效按鍵: l 點選(按下有效) l 點選(釋放有效) l 長擊(長按下有效) l 雙擊(連續按下有效) 然而,不管哪個實驗都是隻有兩項“功能”的按鍵模組而已,如今我們要建立三項“功能”的按鍵模組,亦即點選(按下有效),長
【黑金原創教程】【FPGA那些事兒-驅動篇I 】原創教程連載導讀【連載完成,共二十九章】
前言: 無數晝夜的來回輪替以後,這本《驅動篇I》終於編輯完畢了,筆者真的感動到連鼻涕也流下來。所謂驅動就是認識硬體,還有前期建模。雖然《驅動篇I》的硬體都是我們熟悉的老友記,例如UART,VGA等,但是《驅動篇I》貴就貴在建模技巧的昇華,亦即低階建模II。 話說低階建模II,讀過《建模篇》的朋友多少也會面
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十一:SDRAM模組④ — 頁讀寫 β
實驗二十一:SDRAM模組④ — 頁讀寫 β 未進入主題之前,讓我們先來談談一些重要的體外話。《整合篇》之際,筆者曾經比擬Verilog如何模仿for迴圈,我們知道for迴圈是順序語言的產物,如果Verilog要實現屬於自己的for迴圈,那麼它要考慮的東西除了步驟以外,還有非常關鍵的時鐘。 for(
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十八:SDRAM模組① — 單字讀寫
實驗十八:SDRAM模組① — 單字讀寫 筆者與SDRAM有段不短的孽緣,它作為冤魂日夜不斷糾纏筆者。筆者嘗試過許多方法將其退散,不過屢試屢敗的筆者,最終心情像橘子一樣橙。《整合篇》之際,筆者曾經大戰幾回兒,不過內容都是點到即止。最近它破蠱而出,日夜不停:“好~痛苦!好~痛苦!”地呻吟著,嚇得筆者不敢半夜如
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十七:TFT模組
實驗二十七:TFT模組 - 顯示 所謂TFT(Thin Film Transistor)就是眾多LCD當中,其中一種支援顏色的LCD,相較古老的點陣LCD(12864笑),它可謂高階了。黑金的TFT LCD除了320×240大小以外,內建SSD1289控制器,同時也是獨立模組。事實上,無論是驅動點陣LCD還
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十三:串列埠模組② — 接收
實驗十三:串列埠模組② — 接收 我們在實驗十二實現了串列埠傳送,然而這章實驗則要實現串列埠接收 ... 在此,筆者也會使用其它思路實現串列埠接收。 圖13.1 模組之間的資料傳輸。 假設我們不考慮波特率,而且一幀資料之間的傳輸也只是發生在FPGA之間,即兩隻模組之間互轉,並且兩塊模組都使用相同的時
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗七:PS/2模組① — 鍵盤
實驗七:PS/2模組① — 鍵盤 實驗七依然也是熟爛的PS/2鍵盤。相較《建模篇》的PS/2鍵盤實驗,實驗七實除了實現基本的驅動以外,我們還要深入解PS/2時序,還有PS/2鍵盤的行為。不過,為了節省珍貴的頁數,怒筆者不再重複有關PS/2的基礎內容,那些不曉得的讀者請複習《建模篇》或者自行谷歌一下。 市場
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗九:PS/2模組③ — 鍵盤與多組合鍵
實驗九:PS/2模組③ — 鍵盤與多組合鍵 筆者曾經說過,通碼除了單位元組以外,也有雙位元組通碼,而且雙位元組通碼都是 8’hE0開頭,別名又是 E0按鍵。常見的的E0按鍵有,<↑>,<↓>,<←>,<→>,<HOME>,<PRTSC>
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十五:SDHC模組
實驗二十五:SDHC模組 筆者曾經說過,SD卡發展至今已經衍生許多版本,實驗二十四就是針對版本SDV1.×的SD卡。實驗二十四也說過,CMD24還有CMD17會故意偏移地址29,讓原本範圍指向從原本的232 變成 223,原因是SD卡讀寫一次都有512個位元組。為此我們可以這樣計算: SDV1.x = 2
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十:SDRAM模組③ — 頁讀寫 α
實驗二十:SDRAM模組③ — 頁讀寫 α 完成單字讀寫與多字讀寫以後,接下來我們要實驗頁讀寫。醜話當前,實驗二十的頁讀寫只是實驗性質的東西,其中不存在任何實用價值,筆者希望讀者可以把它當成頁讀寫的熱身運動。 表示20.1 Mode Register的內容。 Mode Register
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十八:TFT模組
實驗二十八:TFT模組 - 觸屏 讀者在上一個實驗所玩弄過的 TFT LCD模組,除了顯示大小為 320 × 240,顏色為16位RGB的影象資訊以外,它還支援觸屏。所謂觸屏就是滑鼠還有鍵盤以外的輸入手段,例如現在流行平板還有智慧手機,觸屏輸入對我們來說,已經成為日常的一部分。描述語言一門偏向硬體的語言
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十二:SDRAM模組⑤ — FIFO讀寫
經過漫長的戰鬥以後,我們終於來到最後。對於普通人而言,頁讀寫就是一名戰士的墓碑(最終戰役) ... 然而,怕死的筆者想透過這個實驗告訴讀者,旅程的終點就是旅程的起點。一直以來,筆者都在煩惱“SDRAM是否應該成為儲存類?”SDRAM作為一介儲存資源(儲存器),它的好處就是大容量空間,壞處則就是麻煩的控制規
【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十四:儲存模組
實驗十四比起動手筆者更加註重原理,因為實驗十四要討論的東西,不是其它而是低階建模II之一的模組類,即儲存模組。接觸順序語言之際,“儲存”不禁讓人聯想到變數或者陣列,結果它們好比資料的暫存空間。 1. int main() 2. { 3. int VarA; 4.