1. 程式人生 > >【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十:PS/2模組④ — 普通滑鼠

【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十:PS/2模組④ — 普通滑鼠

實驗十:PS/2模組④ — 普通滑鼠

學習PS/2鍵盤以後,接下來就要學習 PS/2 滑鼠。PS/2滑鼠相較PS/2鍵盤,驅動難度稍微高了一點點,因為FPGA(從機)不僅僅是從PS/2滑鼠哪裡讀取資料,FPGA還要往滑鼠裡寫資料 ... 反之,FPGA只要對PS/2鍵盤讀取資料即可。然而,最傷腦筋的地方就在於PS/2傳輸協議有奇怪的寫時序。

clip_image002

圖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

clip_image004

圖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為輸入狀態。

clip_image006

圖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滑鼠就結束讀取動作,並且反饋應答位。

clip_image008

圖10.4主機視角,從機寫資料,第二幀。

每當主機接收完畢一幀資料,就會反饋一幀資料。如圖10.2所示,那是圖10.1的下半部分,依然是從主機視角觀察從機寫資料。圖中顯示,每當PS/2滑鼠(主機)接收完畢一幀資料以後,PS/2滑鼠便會反饋一幀資料,亦即PS/2滑鼠會再度借用10個上升沿傳送一幀資料。期間,FPGA(從機)的 PS2_CLK 與 PS2_DAT都都處於輸入狀態。

clip_image009

圖10.5主機視角,從機寫資料,完整時序。

圖10.5是圖10.3與圖10.4的完整時序(主機視角)。

clip_image011

圖10.6從機視角,從機寫資料,第二幀。

既然主機反饋一幀資料,那麼從機也不能無視,如圖10.6所示,那是經由從機視角觀察從機如何讀取下一幀資料。期間,從機借用11個下降沿讀取1一幀資料。

clip_image012

圖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了!”。

clip_image014

圖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幀或者以上組成)。

clip_image016

圖10.8 從機發送 “使能報告”命令,滑鼠反饋接收成功。

為了驅使滑鼠工作,PS/2滑鼠上電以後,從機必須傳送命令 8’hF4,並且接收反饋 8’hFA。如果一切順利,那麼滑鼠就會開始工作,結果如圖10.8所示。滑鼠“使能”以後,滑鼠便處於就緒狀態,採集便開始 ... 此刻,如果滑鼠的位置狀況或者按鍵狀況發生變化,滑鼠就會發送3幀,亦即3位元組的報告。

clip_image018

圖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。這樣作的目的是為了使用補碼錶示滑鼠的移動狀況。至於補碼是什麼?失憶的朋友請複習《時序篇》。

clip_image020

圖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所示。

上述內容理解完畢以後,我們便可以開始建模了。

clip_image022

圖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

clip_image024

圖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

clip_image026

圖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。

到頭來到底是直觀好,還是精簡好,唯有見仁見智了。

細節二:完整的個體模組

clip_image028

圖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.