看了CrazyBingo的書的前幾章,開始寫代碼實踐了,。剛好自己手上有一個xilinx的開發(fā)板,上面有串口的資源,。索性,就來實現(xiàn)這個串口的功能,。實現(xiàn)串口的發(fā)送和接收,。 串口的協(xié)議很簡單,從網(wǎng)上找了個圖說明下: 串口所用的協(xié)議時UART協(xié)議,。UART協(xié)議是異步的通信協(xié)議,。只用一根線完成數(shù)據(jù)的傳輸。協(xié)議的時序如上圖所示: 在空閑狀態(tài),,即沒有數(shù)據(jù)傳輸時,線上電平保持為高電平,。當(dāng)開始有數(shù)據(jù)傳輸時,,線上電平會拉低,表示開始傳輸數(shù)據(jù),。第一個數(shù)據(jù)位開始位,,然后是數(shù)據(jù)位,數(shù)據(jù)位可以是8位,,也可以使8位到4位,,但是順序是低位在前,高位在后,。最后是一個高電平的停止位,。這樣就完成了一次數(shù)據(jù)傳輸。 以發(fā)送8位數(shù)據(jù)8’h28(二進(jìn)制00101000)為例,。 則線上的電平依次為 1->0->0->0->0->1->0->1->0->0->1,。 由于是異步,沒有時鐘同步,,所以通信的雙方要約定好,,傳輸每一位的時間,這個時間就是由波特率來決定的,。 波特率是指傳輸每一位所用的時間,,如果采用波特率256000.那么每一位傳輸?shù)臅r間為: 1/256000 = 3.9025us。 通信是有發(fā)送和接收的,,所以串口就有兩根線,,一根線發(fā)送數(shù)據(jù),一根線接收數(shù)據(jù)。 以上就是串口的一些介紹,,詳細(xì)的介紹可以自行百度,,可以了解到更多串口的細(xì)節(jié)。 以下實現(xiàn)一個全雙工的串口接收和發(fā)送,,采用FIFO對接受到的數(shù)據(jù)進(jìn)行儲存,,然后接收外部發(fā)送信號,就將FIFO中的接收到的數(shù)據(jù),,通過串口發(fā)送,。波特率為256000,數(shù)據(jù)為8位,。 以上是結(jié)構(gòu)圖,,畫的有點挫。接收模塊接收外部發(fā)送的串口數(shù)據(jù),,將數(shù)據(jù)儲存到FIFO中,,F(xiàn)IFO有外部使能信號(這里是按鍵按下),就將FIFO中的數(shù)據(jù)傳給發(fā)送模塊,發(fā)送模塊在將數(shù)據(jù)發(fā)送出去,。 因為是全雙工模式,,所以對于發(fā)送和接收各有一個波特率產(chǎn)生模塊。用來定時數(shù)據(jù)每一位的時間,。以滿足串口協(xié)議的要求,。 對于發(fā)送模塊: 波特率程序:
module band_generate_tx #( parameter baud = 115200 //baud 9600-256000 ) ( input clk, input rst_n, input start, //start send data mode input finish, //send data mode finish output reg time_arr_tx //baud overflow ); localparam cnt_16 = 15; /*********************baud counter value calculate*********************/ reg [7:0] cnt_baud; //calculate baud counter value //counter value = 50_000_000 / baud /16 -1; initial begin case(baud) 9600 : cnt_baud = 328 -1; 14400 : cnt_baud = 217 -1; 19200 : cnt_baud = 163 -1; 28800 : cnt_baud = 109 -1; 38400 : cnt_baud = 81 -1; 56000 : cnt_baud = 56 -1; 57600 : cnt_baud = 54 -1; 115200: cnt_baud = 27 - 1; 128000: cnt_baud = 24 -1; 256000: cnt_baud = 12 -1; default :cnt_baud = 12 -1; endcase end /*********************baud counter value calculate*********************/ reg [3:0] count_16; reg start_flag; always@(posedge clk or negedge rst_n) begin if(!rst_n) start_flag <= 'd0; else begin if(start) start_flag <= 1; else if(finish) start_flag <= 0; else start_flag <= start_flag; end end always@(posedge clk or negedge rst_n) begin if(!rst_n) begin count_16 <= 0; end else begin if(start_flag) begin if(count_16 >= cnt_16) begin count_16 <= 0; end else begin count_16 <= count_16 + 1'b1; end end end end reg [4:0] count_baud; always@(posedge clk or negedge rst_n) begin if(!rst_n) begin count_baud <= 0; time_arr_tx <= 0; end else begin if(start_flag) begin if(count_16 >= cnt_16) begin if(count_baud>=cnt_baud) begin count_baud <= 'd0; end else count_baud <= count_baud + 1'b1; if(count_baud == cnt_baud - 1'b1) time_arr_tx <= 1; else time_arr_tx <= 0; end else begin time_arr_tx <= 0; end end else begin count_baud <= 0; time_arr_tx <= 0; end end end endmodule
該模塊可以實現(xiàn)波特率從9600到256000.只需要例化模塊的時候,改變波特率參數(shù)值即可,。這里采用initial語句來計算產(chǎn)生不同的波特率需要的計數(shù)值,。因為要產(chǎn)生串口協(xié)議波特率規(guī)定的定時時間,所以要有一個計數(shù)器,,來產(chǎn)生這樣的一個定時時間,。而計數(shù)器計數(shù)是需要一個計數(shù)值的。這里采用initial來計算該計數(shù)值,。 我們知道initial語句是不可綜合的,,但是在有些情況下,它又是可以綜合的,。在計算初始值的時候,,是可以綜合的。比如這里計算計數(shù)器的初始值,?;蛘邔拇嫫鞯某跏蓟?
分析一下上面這段程序,。這段程序是實現(xiàn)波特率控制的,,波特率產(chǎn)生不是什么時候都產(chǎn)生,,是需要的時候才產(chǎn)生。在這里,,就是要有發(fā)送數(shù)據(jù)的時候,,才進(jìn)行波特率產(chǎn)生。這里的start信號是外部給的串口發(fā)送數(shù)據(jù)信號,,但是這樣信號只會持續(xù)一個時鐘周期,,而后面的波特率產(chǎn)生是判斷這個信號一直使能在進(jìn)行波特率產(chǎn)生,那這里就有一個問題,,怎么把這一個只持續(xù)一個時鐘周期的信號給擴展為一直持續(xù)使能了,。這個就是上面代碼的功能,將一個只持續(xù)一個時鐘周期的信號給擴展為一直持續(xù)使能,。 當(dāng)start信號有效的時候,,start_flag為高,即使能,,一個時鐘周期后,,start信號無效,但是start_flag保持使能,。一旦發(fā)送數(shù)據(jù)完成后,,finish信號有效到來,將start_flag無效,,是關(guān)閉波特率產(chǎn)生,。 那么問題來了,,上述代碼會對應(yīng)怎樣的一個數(shù)字電路,,大家可以去想想。我想的結(jié)果是一個除法器加3個門,。
波特率產(chǎn)生模塊,,主要是給發(fā)送模塊提供一個波特率溢出信號,提示發(fā)送模塊,,該位數(shù)據(jù)發(fā)送完成,,準(zhǔn)備發(fā)下一位數(shù)據(jù)。 接下來是發(fā)送模塊,。采用狀態(tài)機設(shè)計:
module uart_txd( //global signal input clk, //clk 50M input rst_n, //reset signal, active-low //input signal input start, //start uart send mode input time_arr, //baud overflow signal input [7:0] data_in, //8-bits data to be send //output signal output reg finish, //send mode finish output reg uart_txd //serial send data ); //state localparam idle_state = 4'd0; localparam start_state = 4'd1; localparam send_0_state = 4'd2; localparam send_1_state = 4'd3; localparam send_2_state = 4'd4; localparam send_3_state = 4'd5; localparam send_4_state = 4'd6; localparam send_5_state = 4'd7; localparam send_6_state = 4'd8; localparam send_7_state = 4'd9; localparam stop_state = 4'd10; reg [3:0] state; reg [3:0] state_next; always@(posedge clk or negedge rst_n) begin if(!rst_n) state <= idle_state; else state <= state_next; end always@(*) begin state_next = state; finish = 0; uart_txd = 0; case(state) idle_state: begin //idle state , uart_txd value is hign level. uart_txd = 1; if(start) state_next = start_state; end start_state : begin //send start bit. uart_txd value is 0 uart_txd = 0; if(time_arr) begin state_next = send_0_state; end end send_0_state : begin uart_txd = data_in[0]; if(time_arr) begin state_next = send_1_state; end end send_1_state : begin uart_txd = data_in[1]; if(time_arr) begin state_next = send_2_state; end end send_2_state : begin uart_txd = data_in[2]; if(time_arr) begin state_next = send_3_state; end end send_3_state : begin uart_txd = data_in[3]; if(time_arr) begin state_next = send_4_state; end end send_4_state : begin uart_txd = data_in[4]; if(time_arr) begin state_next = send_5_state; end end send_5_state : begin uart_txd = data_in[5]; if(time_arr) begin state_next = send_6_state; end end send_6_state : begin uart_txd = data_in[6]; if(time_arr) begin state_next = send_7_state; end end send_7_state : begin uart_txd = data_in[7]; if(time_arr) begin state_next = stop_state; end end stop_state : begin uart_txd = 1; if(time_arr) begin state_next = idle_state; finish = 1; end end default: state_next = idle_state; endcase end endmodule
代碼也很簡單,,只要有發(fā)送使能信號,,就啟動狀態(tài)機,,發(fā)送數(shù)據(jù),在每一個波特率溢出信號作用下,,進(jìn)行狀態(tài)轉(zhuǎn)移,,以完成每一位數(shù)據(jù)的發(fā)送。這里,要注意,,停止位的數(shù)據(jù)位一定要為1.這里為什么要為1,,后面再解釋。 接著,,就用一個發(fā)送頂層模塊將上述兩個模塊進(jìn)行封裝,。
module uart_tx #( parameter baud = 256000 ) ( input clk, input rst_n, input start, input [7:0] tx_data, output uart_txd, output finish ); wire time_arr_tx; band_generate_tx #(.baud(baud)) band_generate_tx_1 ( .clk(clk), .rst_n(rst_n), .start(start), .finish(finish), .time_arr_tx(time_arr_tx) ); uart_txd uart_txd_1( .clk(clk), .rst_n(rst_n), .start(start), .time_arr(time_arr_tx), .data_in(tx_data), .finish(finish), .uart_txd(uart_txd) ); endmodule
封裝的好處,是方面頂層的調(diào)用,。 接著就是接受數(shù)據(jù)的模塊: 首先是波特率產(chǎn)生:
module band_generate_rx #( parameter baud = 115200 //baud 9600-256000 ) ( input clk, input rst_n, input start, input finish, output reg time_arr_rx ); localparam cnt_16 = 15; /*********************baud counter value calculate*********************/ reg [7:0] cnt_baud; //calculate baud counter value //counter value = 50_000_000 / baud /16 -1; initial begin case(baud) 9600 : cnt_baud = 328 -1; 14400 : cnt_baud = 217 -1; 19200 : cnt_baud = 163 -1; 28800 : cnt_baud = 109 -1; 38400 : cnt_baud = 81 -1; 56000 : cnt_baud = 56 -1; 57600 : cnt_baud = 54 -1; 115200: cnt_baud = 27 - 1; 128000: cnt_baud = 24 -1; 256000: cnt_baud = 12 -1; default :cnt_baud = 12 -1; endcase end /*********************baud counter value calculate*********************/ reg [3:0] count_16; reg start_flag; always@(posedge clk or negedge rst_n) begin if(!rst_n) start_flag <= 'd0; else begin if(start) start_flag <= 1; else if(finish) start_flag <= 0; else start_flag <= start_flag; end end always@(posedge clk or negedge rst_n) begin if(!rst_n) begin count_16 <= 0; end else begin if(start_flag) begin if(count_16 >= cnt_16) begin count_16 <= 0; end else begin count_16 <= count_16 + 1'b1; end end end end reg [4:0] count_baud; always@(posedge clk or negedge rst_n) begin if(!rst_n) begin count_baud <= 0; time_arr_rx <= 0; end else begin if(start_flag) begin if(count_16 >= cnt_16) begin if(count_baud>=cnt_baud) begin count_baud <= 'd0; end else count_baud <= count_baud + 1'b1; if(count_baud == cnt_baud/2 ) time_arr_rx <= 1; else time_arr_rx <= 0; end else begin time_arr_rx <= 0; end end else begin count_baud <= 0; time_arr_rx <= 0; end end end endmodule
采用和發(fā)送模塊的波特率產(chǎn)生模塊一樣的結(jié)構(gòu),只是波特率的溢出信號時間不一樣,。發(fā)送的波特率溢出是在波特率時間的末尾,而接受的波特率溢出是在波特率時間的中間,。因為在中間采集的數(shù)據(jù)才是正確有效的,。 這里要明白一個東西,,因為數(shù)據(jù)是一位一位傳輸?shù)?,所以?shù)據(jù)改變的時間也是要注意的,,不能在數(shù)據(jù)改變的時候接收數(shù)據(jù),這樣接受的數(shù)據(jù)就有可能不正確,。數(shù)據(jù)變化是在波特率時間的末尾,,所以接收數(shù)據(jù)要在波特率時間的中間。 下面是接收模塊:
module uart_rxd( //global signal input clk, //clk signal .50M input rst_n, //reset signal , active-low //input signal input time_arr, //baud overflow signal input rxd, //rxd input data output reg [7:0] rxd_data, //receive rxd 8-bits data output start, //start receive mode output reg finish //receive mode finish ); /********************judge rxd falling edge*********************/ reg rxd_r ; reg rxd_r_r ; wire rxd_falling ; always@( posedge clk ) begin if( !rst_n ) begin rxd_r <= 'b1 ; rxd_r_r <= 'b1 ; end else begin rxd_r <= rxd ; rxd_r_r <= rxd_r ; end end assign rxd_falling = rxd_r_r & ( !rxd_r ) ; /********************judge rxd falling edge*********************/ reg [3:0] i; reg start_flag; //receive 9-bits data. but the high 8-bits is real data. the last bit is start data reg [8:0] rxd_data_reg; always@(posedge clk or negedge rst_n) begin if(!rst_n) begin rxd_data_reg <= 'd0; i <= 'd0; end else begin if(start_flag) //receive data begin if(time_arr ==1 ) begin rxd_data_reg <= {rxd,rxd_data_reg[8:1]}; i <= i +4'd1; end end else begin rxd_data_reg <= 'd0; i <='d0; end end end always@(posedge clk or negedge rst_n) begin if(!rst_n) begin start_flag <= 'b0; rxd_data <= 'd0; finish <= 'b0; end else if(rxd_falling) start_flag <= 1'b1; else if(i >= 9 && start_flag == 1) begin start_flag <= 1'b0; rxd_data <= rxd_data_reg[8:1]; finish <= 1'b1; end else finish <= 1'b0; end //if rxd generate a falling edge,that indicate receive mode start assign start = rxd_falling; endmodule
這里沒有采用狀態(tài)機的方式設(shè)計,,這里可以看出,,采用狀態(tài)機設(shè)計,代碼便于理解和編寫,。 封裝上述兩個模塊,,成一個接收模塊:
module uart_rx #( parameter baud = 256000 ) ( input clk, input rst_n, input uart_rxd, output [7:0] receive_data, output finish ); wire time_arr_rx; wire start; band_generate_rx #(.baud(baud)) band_generate_rx_1 ( .clk(clk), .rst_n(rst_n), .start(start), .finish(finish), .time_arr_rx(time_arr_rx) ); uart_rxd uart_rxd_1 ( .clk(clk), .rst_n(rst_n), .time_arr(time_arr_rx), .rxd(uart_rxd), .rxd_data(receive_data), .start(start), .finish(finish) ); endmodule
然后在用一個串口頂層模塊對上面發(fā)送和接受模塊封裝,。
module uart_top #( parameter baud_tx = 256000, parameter baud_rx = 256000 ) ( input clk, input rst_n, input uart_rxd, //input serial rxd data input tx_start, //input start send data module signal input [7:0] tx_data, //input 8-bits send data output tx_finish, //output send mode finish output rx_finish, //output receive mode finish output uart_txd, //output serial txd data output [7:0] receive_data //output receive 8-bits data ); //receive module uart_rx #(.baud(baud_rx)) uart_rx_1 ( .clk(clk), .rst_n(rst_n), .uart_rxd(uart_rxd), .receive_data(receive_data), .finish(rx_finish) ); //send module //when tx_start is 1, send module will send 8-bit input tx_data to the serial txd uart_tx #(.baud(baud_tx)) uart_tx_1 ( .clk(clk), .rst_n(rst_n), .start(tx_start), .tx_data(tx_data), .uart_txd(uart_txd), .finish(tx_finish) ); endmodule
這樣,,就完成了整個的串口設(shè)計模塊了。 剩下的就是FIFO模塊了,,這里是調(diào)用xilinx的FIFO IP核,。直接拿來使用,位寬8位,,深度4096.意思可以存儲4096個外部發(fā)送的字節(jié)數(shù)據(jù),。
module test_uart_fifo( input clk, input rst_n, input key, input rx_finish, input [7:0] rx_data, input tx_finish, //input start, output reg tx_start, output [7:0] tx_data ); //wire start; //wire full; wire empty; reg rd_en; key_button key_button_1 ( .clk(clk), .rst_n(rst_n), .key(key), .key_down(start) ); fifo_uart fifo_uart_1 ( .clk(clk), // input clk .rst(rst_n), // input rst .din(rx_data), // input [7 : 0] din .wr_en(rx_finish), // input wr_en .rd_en(rd_en), // input rd_en .dout(tx_data), // output [7 : 0] dout .full(), // output full .empty(empty) // output empty ); localparam idle_state = 0; localparam send_state = 1; reg start_flag; reg state; always@(posedge clk or negedge rst_n) begin if(!rst_n) begin state <= idle_state; tx_start <= 0; rd_en <= 0; end else begin case(state) idle_state: begin if(start_flag) begin tx_start <= 1; state <= send_state; rd_en <= 1; end else begin tx_start <= 0; state <= idle_state; rd_en <= 0; end end send_state: begin tx_start <= 0; rd_en <= 0; if(tx_finish) state <= idle_state; end endcase end end always@(posedge clk or negedge rst_n) begin if(!rst_n) start_flag <= 0; else begin if(start) start_flag <= 1; else if(empty) start_flag <= 0; end end endmodule
其中key_button模塊,,是檢測按鍵下降沿的。我這里是設(shè)定我按下按鍵,,就將FIFO的所有數(shù)據(jù)發(fā)送出去,。而fifo_uart模塊,是例化FIFO的IP,。 最終的頂層代碼:
module test_uart( input clk, input rst_n, input uart_rxd, input key, //input start, output uart_txd, output [7:0] LED ); wire tx_start; wire rx_finish; wire tx_finish; wire [7:0] tx_data; wire [7:0] receive_data; uart_top #(.baud_tx(256000), .baud_rx(256000)) uart_top_1 ( .clk(clk), .rst_n(rst_n), .uart_rxd(uart_rxd), .tx_start(tx_start), .tx_data(tx_data), .tx_finish(tx_finish), .rx_finish(rx_finish), .uart_txd(uart_txd), .receive_data(receive_data) ); test_uart_fifo test_uart_fifo_1 ( .clk(clk), .rst_n(rst_n), .key(key), //.start(start), .rx_finish(rx_finish), .rx_data(receive_data), .tx_finish(tx_finish), .tx_start(tx_start), .tx_data(tx_data) ); assign LED = ~receive_data; endmodule
代碼也很簡單,,就將兩個模塊連接起來。 綜合,,分配管腳,,然后布局布線,最后下載,。使用串口獵人,,用來發(fā)送數(shù)據(jù)和接收數(shù)據(jù)。 串口測試成功了,。 最終效果如下,,很酷吧。,。發(fā)送的數(shù)據(jù)是Crazybingo寫的書的自序的一部分,。 發(fā)送數(shù)據(jù)后,按下開發(fā)板按鍵,,就將發(fā)送的數(shù)據(jù)發(fā)回,。使用串口獵人捕獲,即可得到數(shù)據(jù),。 在實現(xiàn)這個功能時候,,有遇到一下問題: 發(fā)送單個數(shù)據(jù),接收單個數(shù)據(jù)正確,,但是發(fā)送一串?dāng)?shù)據(jù),,接收就只能接受到最后一個數(shù)據(jù),,而不是發(fā)送的一串?dāng)?shù)據(jù),。 這個問題,我可折騰了好久,。最開始以為是FIFO沒有正常工作,,寫testbench仿真,發(fā)現(xiàn)還真的是有這個問題,。FIFO的復(fù)位信號弄反了,。這個系統(tǒng)是設(shè)定的低電平復(fù)位,而FIFO設(shè)定的高電平復(fù)位,,所以接收數(shù)據(jù)不對,。將復(fù)位信號更正后,,發(fā)現(xiàn)還是有問題。在仿真FIFO,,發(fā)現(xiàn)FIFO是正常工作的,。那出現(xiàn)的問題,肯定就是我的串口模塊的問題,。 各種寫testbench代碼仿真,,仿真后,發(fā)現(xiàn),,接收模塊是沒有問題的,,那問題就應(yīng)該是發(fā)送模塊的問題。通過仿真,,發(fā)現(xiàn)發(fā)送的時序是對的,,確實將每個數(shù)據(jù)按照規(guī)定的時序發(fā)送出去。但是發(fā)現(xiàn)發(fā)完一個數(shù)據(jù)后,,只隔了一個系統(tǒng)時鐘周期,,就馬上發(fā)送第二個數(shù)據(jù)。這里,,就猜想,,是不是數(shù)據(jù)發(fā)送太快,數(shù)據(jù)發(fā)送完,,要在等一個波特率時間在發(fā)送下一個數(shù)據(jù),。百度下,發(fā)現(xiàn),,原來,,發(fā)送的停止位的數(shù)據(jù)是要為1的。而我寫程序的時候,,以為停止位是0或1都沒有關(guān)系,,就給了個0.將這里改正,發(fā)現(xiàn),,程序?qū)α?。能實現(xiàn)發(fā)一串?dāng)?shù)據(jù),然后FPGA在回發(fā)一串?dāng)?shù)據(jù)了,。 所以要注意,,發(fā)送的時候,停止位的數(shù)據(jù)一定要為高電平?。,。。,。,。,。。,。,。。,。,。。,。,。?/p> |
|