接着上一篇,今天我們來建立一個能用於實際工程中的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
對比上一個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
發送字符控制部分由兩個信號觸發,分別是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
為了驗證它是否達到了我們預先的目的,我在modelsim中進行了仿真,得到下面的波形圖:

start信號是我們的隨機觸發信號(在這個DEMO中即是按鍵信號),我們發現無論這個start信號的脈寬是多少,最后得到的pulse信號的寬度都是一定的,在上面的代碼中我們知道它的寬度為pulsewide*clk,是我們可以設定的(仿真中pulsewide為5),說明這個模塊達到了我們預先的目的。其實,這個模塊在實際的應用中是很有價值的,我們可以理解為它將一種非理想的狀況轉換成了一種接近理想的狀況。
最后,我們的將編譯生成的bit數據流文件下載進板子中,能在PC的超級終端(或則串口調試助手)中得到下面的結果:

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