NEXYS 3開發板練手--USB UART(三)


  接着上一篇,今天我們來建立一個能用於實際工程中的DEMO。

  首先,為了使我們的發送機不像上一個DEMO一樣無節制的循環發送,我們需要修改代碼,增加使發送機停止發送的控制部分,修改后的代碼如下:

  1 `timescale 1ns / 1ps
  2 //////////////////////////////////////////////////////////////////////////////////
  3 // Company: 
  4 // Engineer:         lwy
  5 // 
  6 // Create Date:    16:00:47 11/11/2013 
  7 // Design Name:    usb-uart tx device for NEXYS3
  8 // Module Name:    UART_CTRL 
  9 // Project Name: 
 10 // Target Devices: 
 11 // Tool versions: 
 12 // Description:This component may be used to transfer data over a UART device. It will
 13 //                    serialize a byte of data and transmit it over a TXD line. The serialized
 14 //                 data has the following characteristics:
 15 //                         *9600 Baud Rate
 16 //                         *8 data bits, LSB first
 17 //                         *1 stop bit
 18 //                         *no parity
 19 //
 20 // Dependencies: 
 21 // Port Descriptions:
 22 //                            clk           -- 外部100M時鍾輸入;
 23 //                            data          -- 需要傳輸的數據,8位;
 24 //                            send      -- 發送使能端,低電平開始一個字符傳輸;
 25 //                            tx        -- 向uart device發送的串行數據;
 26 //                            busy          -- 線路狀態標志,高時表示線路忙,低時表示線路空閑
 27 // Revision: 
 28 // Revision 0.01 - File Created
 29 // Additional Comments: 
 30 //
 31 //////////////////////////////////////////////////////////////////////////////////
 32 module UART_CTRL(
 33     clk,
 34     data,
 35     send,
 36     tx,
 37     busy
 38     );
 39 
 40 input clk,send;
 41 input [7:0] data;
 42 output reg tx,busy;
 43 
 44 //狀態機狀態定義
 45 parameter Idel = 2'b00,//空閑狀態
 46              Rdy = 2'b01,//數據准備完成
 47              LoadByte = 2'b10,//數據傳入
 48              SendBit = 2'b11;//數據發送
 49 
 50 reg [13:0] BspClkReg;//波特率分頻計數
 51 reg BspClk;//波特率時鍾
 52 
 53 reg [9:0] tx_data;//發送的數據,加上起始位和停止位
 54 reg [3:0] tx_byte_count;//發送位數計數
 55 
 56 reg [1:0] state;//狀態寄存器
 57 
 58 
 59 //波特率分頻模塊,波特率:9600
 60 always@(posedge clk)
 61 begin
 62     BspClkReg <= BspClkReg + 1;
 63     if(BspClkReg == 5208)
 64     begin
 65         BspClkReg <= 0;
 66         BspClk <= ~BspClk;
 67     end
 68 end
 69 //串口發送模塊
 70 always@(posedge BspClk)
 71 begin
 72     case(state)
 73         Idel         : begin
 74                         tx <= 1;
 75                         busy <= 0;
 76                         tx_byte_count <= 0;
 77                         if(send) state <= Rdy;
 78                         end
 79         Rdy          : begin
 80                         if(~send)    state <= Idel;
 81                         else begin
 82                             tx_byte_count <= 0;
 83                             tx <= 1;
 84                             busy <= 1;
 85                             state <= LoadByte; 
 86                             end
 87                         end
 88         LoadByte    : begin
 89                         if(~send)    state <= Idel;
 90                         else begin
 91                             tx_data <= {1'b1,data,1'b0};
 92                             tx <= 1;
 93                             busy <= 1;
 94                             state <= SendBit;
 95                             end
 96                         end
 97         SendBit    : begin
 98                         if(~send)    state <= Idel;
 99                         else begin
100                             tx <= tx_data[0];
101                             busy <= 1;
102                             tx_data <= tx_data >> 1;
103                             tx_byte_count <= tx_byte_count + 1;
104                             if(tx_byte_count == 9)
105                                 state <= Idel;
106                             else
107                                 state <= SendBit;
108                             end
109                         end
110     endcase
111 end
112 
113 endmodule
View Code

   對比上一個DEMO中的狀態機部分,我們只是在空閑狀態外的每個狀態中增加了當send信號無效時使狀態機返回空閑狀態的控制代碼,這樣我們就可以通過這個send信號來控制是否使發送機發送信息。

  接下來我們要做的工作就是怎么有效的向發送機輸送我們需要發送的信息,並控制發送機正確的運轉。要做到這一點,就需要發送機有一個反饋信號,告訴發送控制模塊是否已經完成了一次發送工作。其實,我們仔細再看上面的代碼,會發現有一個信號我們一直沒有管它,那就是busy信號,這個信號就是考慮到這一點而做的准備工作。當發送機處在空閑狀態,即不發送信息時,它是低電平,其他時候是高電平,也就是說當我們完成了一次發送工作(一個字符發送完成)的時候,會有一個下降沿。我們的發送控制部分即可捕捉這個下降沿來改變發送機輸入端的信息,從而控制發送機發送下一個需要發送的不同字符。這一部分的控制代碼如下:

  1 `timescale 1ns / 1ps
  2 //////////////////////////////////////////////////////////////////////////////////
  3 // Company: 
  4 // Engineer:         lwy
  5 // 
  6 // Create Date:    16:00:47 11/11/2013 
  7 // Design Name:    usb-uart tx device for NEXYS3
  8 // Module Name:    usbuart 
  9 // Project Name: 
 10 // Target Devices: 
 11 // Tool versions: 
 12 // Description:    這個模塊用來控制向UartCtrl模塊的寫入字符串數據
 13 //
 14 // Dependencies: 
 15 // Port Descriptions:    CLK    --    系統時鍾;
 16 //                                reset -- 系統復位信號
 17 //                                btn    -- 字符串發送命令,高電平有效,這里接入一個button
 18 //                                TxBit    -- 串行字符輸出,接UartCtrl模塊的tx端,即最終串行數據通過這個端口相device傳送
 19 // Revision: 
 20 // Revision 0.01 - File Created
 21 // Additional Comments: 請注意strLen、string這兩個常數,如果要改變發送的字符串內容,只需將string賦值為需要發送
 22 //                                的字符串,將strLen賦值為發送字符串長度即可。
 23 //
 24 //////////////////////////////////////////////////////////////////////////////////
 25 module usbuart(
 26     CLK,
 27     reset,
 28     btn,
 29     TxBit
 30     );
 31 
 32 input CLK,reset,btn;
 33 output TxBit;
 34 
 35 //////////////////////////////////////////////////////////////////////////////////
 36 parameter strLen = 15; //字符串長度
 37 parameter string = "Hello USB_UART!";//字符串數據
 38 //////////////////////////////////////////////////////////////////////////////////
 39 
 40 
 41 reg [7:0] TxData;//當前發送的數據
 42 reg send_n;//發送使能,當啟動一次數據發送且沒有收到UART_CTRL數據發送完成的信號是,因該將它一直保持為高電平
 43 wire lock;//控制數據的改變,當UART_CTRL數據線“忙”(busy為高電平)時,應禁止改變數據,而且send_n應該一直為高
 44 reg [7:0] charCount;//當前發送字符數,一次最多發送255個字符
 45 reg [11:0] Index;//當前字符位索引
 46 
 47 reg [8*strLen:0] str_UART;//存儲字符代碼
 48 
 49 //狀態定義
 50 parameter s0 = 2'b00,s1 = 2'b01,s2 = 2'b10,s3 = 2'b11; 
 51 
 52 reg [1:0] state;//狀態變量
 53 wire trigger;//觸發信號,由固定脈沖觸發模塊輸出
 54 wire trigger_n;//將高電平觸發信號改為低電平
 55 assign trigger_n = ~trigger;
 56 
 57 
 58 //發送字符控制
 59 always@(negedge trigger_n or negedge lock)
 60 begin
 61     if(~trigger_n) //低電平觸發發送
 62     begin 
 63         str_UART = string;//初始化字符串
 64         state = s1;
 65         TxData = 10;//\n
 66         send_n = 1;
 67         charCount = strLen;
 68     end
 69     else begin
 70     case(state)
 71         s0 : begin
 72                     TxData = 0;
 73                     send_n = 0;                
 74                 end
 75         s1 : begin
 76                     state = s2;
 77                     TxData = 13;//\r
 78                     send_n =1;
 79                 end
 80         s2 : begin
 81                     Index = charCount * 8 - 1;
 82                     TxData = str_UART[Index-:8];//發送字符串
 83                     send_n = 1;
 84                     charCount = charCount - 1;
 85                     if(charCount == 0)
 86                         state = s0;
 87                     else
 88                         state = s2;
 89                 end
 90         default : state = s0;
 91     endcase
 92     end
 93 end
 94 
 95 //調用UART_CTRL模塊
 96 UART_CTRL UartCtrl(
 97                         .clk(CLK),
 98                         .data(TxData[7:0]),
 99                         .send(send_n),
100                         .tx(TxBit),
101                         .busy(lock)
102                         );
103 //調用PulTri模塊,產生穩定的觸發信號
104 PulTri pulse(
105                 .clk(CLK),
106                 .reset_n(~reset),
107                 .start(btn),
108                 .pulse(trigger)
109                 );
110 
111 endmodule
View Code

   發送字符控制部分由兩個信號觸發,分別是trigger_n和lock,trigger_n連接按鍵信號取反后的信號(開發板上的按鍵按下為高電平),lock連接反饋busy信號。按鍵按下后觸發一次字符串的發送,首先發送換行符(\n\r),然后根據字符串字符個數的設置,循環傳送相關字符的ASCII碼,這時候狀態機的運轉靠反饋busy信號觸發。當一次字符串發送完成后,將send置為無效,這樣發送機停止運轉,反饋busy信號也就無效了,開始等待下一個trigger_n信號的觸發。通過這三個信號相互配合就能完成一個字符串的發送。

  問題到這仍然還有,因為我們這個trigger_n檢測的是按鍵電平信號,我們知道一次按鍵按下,速度再快也有幾百毫秒的時間,也就是說這個電平信號會持續上百毫秒甚至幾秒的時間,而且這個時間通常是不受控制的。這樣的觸發信號在我們這個模塊中是不能接受的。我們需要的這個觸發信號每次持續的有效時間不能超過1/9600秒,否則整個時序控制就會錯亂,發送狀態機接下來的工作會是一種無法預知的狀態。因此,我們需要改造這個觸發信號,即無論按鍵按下的時間有多長,最后得到的觸發信號的寬度是一定的,或則說我們利用的是按鍵的邊沿信號。為此,我設計了一個能產生穩定觸發信號的模塊,也上面的代碼中例化的PulTri這個模塊。這個模塊的設計代碼如下:

 1 `timescale 1ns / 1ps
 2 //////////////////////////////////////////////////////////////////////////////////
 3 // Company: 
 4 // Engineer:         lwy
 5 // 
 6 // Create Date:    23:02:53 11/12/2013 
 7 // Design Name: 
 8 // Module Name:    PulTri 
 9 // Project Name:      
10 // Target Devices: 
11 // Tool versions: 
12 // Description: 這個模塊用來產生pulsewide個時鍾寬度的脈沖(高電平)而不關心觸發信號脈沖寬度是多少,該模塊的輸出脈沖寬度固定為pulsewide個時鍾周期
13 //
14 // Port Descriptions:    clk     --  系統時鍾,他關系到最后的輸出脈沖寬度
15 //                                reset_n --    系統復位信號,低電平復位
16 //                                start      --    觸發信號端口,高電平觸發
17 //                                pulse      --    輸出脈沖,該脈沖寬度固定為
18 //                                
19 //
20 // Dependencies: 
21 //
22 // Revision: 
23 // Revision 0.01 - File Created
24 // Additional Comments: 
25 //                                pulsewide這里定義為一常數5,可以根據需要調整
26 //
27 //////////////////////////////////////////////////////////////////////////////////
28 module PulTri(
29               clk,
30               reset_n,
31               start,
32               pulse
33               );
34               
35 input clk,reset_n,start;
36 output reg pulse;
37 
38 parameter pulsewide = 50;//調整觸發脈沖的寬度,pulsewide*clk
39 
40 reg counten;
41 reg [7:0] count;
42 
43 initial
44 begin
45   counten <= 0;
46   count <= 0;
47   pulse <= 0;
48 end
49 
50  //計數器啟動標記,表示一次延時計數開始
51 always @ ( posedge clk )
52 begin
53 if ( reset_n == 1'b0 )
54   counten <= 1'b0;
55 else
56   begin
57   if ( start == 1'b1 )
58    counten <= 1'b1;
59   else if ( start == 1'b0 && count > pulsewide )
60    counten <= 1'b0;
61   end
62 end
63  
64  //延時計數器,保證延時 pulsewide 個時鍾周期
65 always @ ( posedge clk )
66 begin
67 if ( reset_n == 1'b0 )
68   count <= 0;
69 else
70   begin
71   if ( counten == 1'b0 )
72    count <= 0;
73   else if ( counten == 1'b1 && count <= pulsewide )
74    count <= count + 1;
75   else if ( counten == 1'b0 && start == 1'b0 )
76    count <= 0;
77   end
78  end
79  
80  //輸出定寬脈沖
81  always @ ( negedge clk )
82  begin
83  if ( reset_n == 'b0 || count >= pulsewide )
84   pulse <= 1'b0;
85  else if ( counten == 1'b1 )
86    pulse <= 1'b1;
87  end
88 
89 endmodule
View Code

  為了驗證它是否達到了我們預先的目的,我在modelsim中進行了仿真,得到下面的波形圖:

      

  start信號是我們的隨機觸發信號(在這個DEMO中即是按鍵信號),我們發現無論這個start信號的脈寬是多少,最后得到的pulse信號的寬度都是一定的,在上面的代碼中我們知道它的寬度為pulsewide*clk,是我們可以設定的(仿真中pulsewide為5),說明這個模塊達到了我們預先的目的。其實,這個模塊在實際的應用中是很有價值的,我們可以理解為它將一種非理想的狀況轉換成了一種接近理想的狀況。

  最后,我們的將編譯生成的bit數據流文件下載進板子中,能在PC的超級終端(或則串口調試助手)中得到下面的結果:

                    

  可見,上面的DEMO與上一篇文章中的相比已經有了很大的改觀,這是一個真正有實用價值的DEMO!如果我們像Quartus中一樣將它做成一個SOPC嵌入式IP核,將頂層模塊中的string和strLen這兩個常數改成能用軟件設置的寄存器,這樣我們就能在軟件中編程完成各種字符的發送工作,這是不是很有意義呢!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM