本次設計的源碼在http://download.csdn.net/detail/noticeable/9912383 下載
實驗目的:通過uart通訊協議的編寫,了解FPGA的通訊協議編寫的方法。
實驗現象:FPAG可以通過USB轉TTL工具向電腦收發數據。
相關知識點:1、uart通訊協議是什么及其內容。2、in system surce and probes(editor)調試工具的使用。
關於串行通訊:串口通訊協議是一種主機之間常用的通訊協議,通過模塊按位發送和接收字節,可以達到通訊的目的,其通訊只需要三根根數據線(GND,RXD,TXD)即可完成。其中串口通訊最重要的參數是波特率,數據位,停止位和奇偶校驗位,下面來一一講解這 些參數的作用。
(1)波特率:波特率是串口的通訊速率,常見的比特率為1200bps、4800bps、9600bps、38400bps、115200bps、256000bps、500000bps,這里bps的意思是bit/s,因此可以知道,波特率實際上是每秒可以傳輸的bit的個數,由此可知波特率與時鍾是直接掛鈎的,比如波特率位9600bps,那么時鍾就是9600hz,即每秒內產生9600個時鍾,每個時鍾周期發送1bit的數據。(這里注意:波特率越高,傳輸距離越短)
波特率分頻計數器的方法:
(2)數據位:數據位可以在通訊過程中告知另一個主機,完成一次通訊過程中真正有效的數據是多少位,完成對傳輸數據的位數的校驗。
(3)起始位、停止位:起始位位於單個數據包的第一位,停止位位於單個包的最后一位,每個包都是一個字節, 另一台主機接收到停止位時就知道信號發送已經完成了。
(4)奇偶校驗位:奇偶校驗是通訊協議中的一種簡單的檢錯方法,其有時鍾檢錯方式,:偶、奇、高和低。當然沒有校驗位也是可以的。對於偶和奇校驗的情況,串口會設置校驗位(數據位后面的一位),用一個值確保傳輸的數據有偶個或者奇個邏輯高位。例如,如果數據是011,那么對於偶校驗,校驗位為0,保證邏輯高的位數是偶數個。如果是奇校驗,校驗位為1,這樣就有3個邏輯高位。高位和低位不真正的檢查數據,簡單置位邏輯高或者邏輯低校驗。這樣使得接收設備能夠知道一個位的狀態,有機會判斷是否有噪聲干擾了通信或者是否傳輸和接收數據是否不同步。
項目設計:
本次設計發送模塊的整體結構體如下 發送時序圖
首先按照系統框圖編寫tx項目源碼:
發生時序圖
源碼程序如下
module uart_tx( clk, rst_n, send_en, baud_set, tx_done, rs232_tx, data_byte, uart_state ); input clk; input rst_n; input [7:0]data_byte;//數據發送寄存器 input send_en; input [2:0]baud_set; output reg rs232_tx; //輸出引腳 output reg tx_done; //傳輸完成的標志位 output reg uart_state; //uart_state傳送狀態 reg [15:0]div_cnt; //分頻計數器 reg bps_clk; //波特率時鍾 reg [15:0]bps_dr; //分頻計數最大值 reg [3:0]bps_cnt; //波特率計數時鍾 reg [7:0]r_data_byte;//發送緩存區 localparam start_bit=1'b0; localparam stop_bit=1'b1; //具有優先級的信號控制,產生控制信號 always@(posedge clk or negedge rst_n) if(!rst_n) uart_state<=0; else if(send_en==1'b1) uart_state<=1; else if(bps_cnt == 4'd11) //假設穿的是一組8位的數據+起始位+停止位 uart_state <= 1'b0; else uart_state<=uart_state; //發送過程中需要保證數據是穩定的,選用一個寄存器將數據先緩存起來 always@(posedge clk or negedge rst_n) if(!rst_n) r_data_byte<=8'd0; else if(send_en) r_data_byte<=data_byte; //設計查找表DR_LUT,通過查找表設置波特率
//1/波特率/20ns
always@(posedge clk or negedge rst_n) if(!rst_n) bps_dr<=16'd5207; else begin case (baud_set) 0:bps_dr<=16'd5207;//9600 1:bps_dr<=16'd2603;//19200 2:bps_dr<=16'd1301;//28400 3:bps_dr<=16'd867;//57600 4:bps_dr<=16'd433;//115200 // 5:bps_dr<=16'd5207; // 6:bps_dr<=16'd5207; // 7:bps_dr<=16'd5207; default bps_dr<=16'd5207;//9600 endcase end //分頻計數器 always@(posedge clk or negedge rst_n) if(!rst_n) div_cnt<=16'd0; else if(uart_state)begin if(div_cnt==bps_dr)//到達計數最大值時清零 div_cnt<=16'd0; else div_cnt<=div_cnt+1'b1; end else div_cnt<=16'd0; //單周期的波特率時鍾產生 always@(posedge clk or negedge rst_n) if(!rst_n) bps_clk<=1'b0; else if(div_cnt==16'd1) bps_clk<=1; else bps_clk<=0; //設計波特率計數器 always@(posedge clk or negedge rst_n) if(!rst_n) bps_cnt<=4'b0; else if (bps_cnt == 4'd11) bps_cnt<=4'b0; else if (bps_clk) bps_cnt<=bps_cnt+1'b1; else bps_cnt<=bps_cnt; //發送完成信號 always@(posedge clk or negedge rst_n) if(!rst_n) tx_done<=1'b0; else if(bps_cnt==4'd11) tx_done<=1'b1; else tx_done <=1'b0; //數據發送,即一個十選一的多路器 always@(posedge clk or negedge rst_n) if(!rst_n) rs232_tx<=1'b1; else begin case(bps_cnt) 0:rs232_tx<=1'b1; 1:rs232_tx<=start_bit;//起始位 2:rs232_tx<=r_data_byte[0]; 3:rs232_tx<=r_data_byte[1]; 4:rs232_tx<=r_data_byte[2]; 5:rs232_tx<=r_data_byte[3]; 6:rs232_tx<=r_data_byte[4]; 7:rs232_tx<=r_data_byte[5]; 8:rs232_tx<=r_data_byte[6]; 9:rs232_tx<=r_data_byte[7]; 10:rs232_tx<=stop_bit;//結束位,本次設計不設奇偶校驗位 default rs232_tx<=1'b1; endcase end endmodule
編寫testbench文件
`timescale 1ns/1ns `define clock_period 20 module uart_tx_tb; reg clk; reg rst_n; reg [7:0]data_byte; reg send_en; reg [2:0]baud_set; wire rs232_tx; wire tx_done; wire uart_state; uart_tx uart_tx0( .clk(clk), .rst_n(rst_n), .send_en(send_en), .baud_set(baud_set), .tx_done(tx_done), .rs232_tx(rs232_tx), .data_byte(data_byte), .uart_state(uart_state) ); initial clk=1; always#(`clock_period/2) clk=~clk; initial begin rst_n<=1'b0; data_byte<=8'd0; send_en<=1'd0; baud_set=3'd4; #(`clock_period*20+1) rst_n<=1'b1; #(`clock_period*50+1) data_byte<=8'haa; send_en<=1'd1; #(`clock_period) send_en<=1'd0; @(posedge tx_done)//等待傳輸完成的上升沿 #(`clock_period*500)//重新發送 data_byte<=8'h55; send_en<=1'd1; #(`clock_period) send_en<=1'd0; #(`clock_period*500) $stop; end endmodule
仿真結果如下,可以看到,每當有一個send_en 時,databyte都會將串口數據輸出出來。
將之前用到的key_filter文件添加進來,作為傳輸的控制信號。
添加IP核以便使用in system sources and probe editor 工具
將.v文件添加到file中,編寫uart_top
module uart_top(clk ,rst_n,rs232_tx,key_in,led); input key_in; input clk; input rst_n; output rs232_tx; wire send_en; wire [7:0]data_byte; output led; wire key_flag,key_state; assign send_en=key_flag&!key_state;//按鍵檢測成功且為低電平時,發生使能 uart_tx uart_tx1( .clk(clk), .rst_n(rst_n), .send_en(send_en), .baud_set(3'd0), .tx_done(), .rs232_tx(rs232_tx), .data_byte(data_byte), .uart_state(led)//將串口狀態用LED顯示出來 ); key_filter key_filter0( .clk(clk), .rst_n(rst_n), .key_in(key_in), .key_flag(key_flag), .key_state(key_state) ); ISSP ISSP0( .probe(), .source(data_byte) ); endmodule
進行引腳分配,進行板級驗證。
由於DE1-SOC上並未板載USB轉TTL模塊,這里需要自備一個,然后將模塊的RXD口連接到開發板上的GPIO_0D1口上,打開串口調試軟件並通過USB轉TTL模塊連接到FPGA上(波特率設為9600),按下KEY1后,可以看到開發板上的LEDR0快速閃爍一下,串口調試助手接收到00兩個數據
打開in system sources and probes editor,
修改格式為hex格式,然后輸入任意數據,我這里輸入66,
然后再按一下key1,可以看到串口調試軟件顯示出來66,即我們給的source值,這里發送端的設計就完成了,下面繼續按相同的方法設計接收端的協議。
UART數據接收部分:
之前已經講過了uart 發送端的時序圖,對應的,理論上接收端的時序圖如下,一般采樣在每一位數據的中點是最穩定的。
但是在工業等復雜的環境中,電磁場的干擾是很強的,所以在這里需要進行抗干擾處理,需要多次采樣求概率來進行接收,進行改進后的單bit數據接收方式示意圖
同樣編寫Verilog代碼:
module uart_rx(clk, rs232_rx, baud_set, rst_n, data_byte, rx_done ); input clk; input rs232_rx; input [2:0]baud_set; input rst_n; output reg [7:0]data_byte; output reg rx_done; reg s0_rs232_rx,s1_rs232_rx;//兩個同步寄存器 reg tmp0_rs232_rx,tmp1_rs232_rx;//數據寄存器 wire nedege; reg [15:0]bps_dr;//分頻計數器計數最大值 reg [15:0]div_cnt;//分頻計數器 reg uart_state; reg bps_clk; reg [7:0]bps_cnt; reg [2:0]r_data_byte [7:0];//前面[2:0]是每一位數據的存儲寬度,[7:0]指位寬 reg [2:0] start_bit,stop_bit; //異步信號同步處理,消除亞穩態,有疑惑的看之前的按鍵消抖部分 always@(posedge clk or negedge rst_n) if(!rst_n) begin s0_rs232_rx<=1'b0; s1_rs232_rx<=1'b0; end else begin s0_rs232_rx<=rs232_rx; s1_rs232_rx<=s0_rs232_rx; end //數據寄存 always@(posedge clk or negedge rst_n) if(!rst_n) begin tmp0_rs232_rx<=1'b0; tmp1_rs232_rx<=1'b0; end else begin tmp0_rs232_rx<=s1_rs232_rx; tmp1_rs232_rx<=tmp0_rs232_rx; end assign nedege=tmp0_rs232_rx&tmp1_rs232_rx;//下降沿檢測 //波特率設置模塊 //10^9/波特率/20ns/這里為了穩定1bit數據會采16次所以還要除16 always@(posedge clk or negedge rst_n) if(!rst_n) bps_dr<=16'd324; else begin case (baud_set) 0:bps_dr<=16'd324;//9600 1:bps_dr<=16'd162;//19200 2:bps_dr<=16'd80;//28400 3:bps_dr<=16'd53;//57600 4:bps_dr<=16'd26;//115200 default bps_dr<=16'd324;//9600 endcase end //分頻計數器 always@(posedge clk or negedge rst_n) if(!rst_n) div_cnt<=16'd0; else if(uart_state)begin if(div_cnt==bps_dr)//到達計數最大值時清零 div_cnt<=16'd0; else div_cnt<=div_cnt+1'b1; end else div_cnt<=16'd0; //單周期的波特率時鍾產生 always@(posedge clk or negedge rst_n) if(!rst_n) bps_clk<=1'b0; else if(div_cnt==16'd1) bps_clk<=1; else bps_clk<=0; //設計波特率計數器 always@(posedge clk or negedge rst_n) if(!rst_n) bps_cnt<=8'b0; else if(bps_cnt == 8'd159 | (bps_cnt == 8'd12 && (start_bit > 2)))//到12位的時候,start_bit>2說明接收錯誤,起始位不對 bps_cnt <= 8'd0;//接收完成或者開始檢測到錯誤信號,波特率時鍾停止 else if (bps_clk) bps_cnt<=bps_cnt+1'b1; else bps_cnt<=bps_cnt; //接收完成信號 always@(posedge clk or negedge rst_n) if(!rst_n) rx_done<=1'b0; else if(bps_cnt==8'd159) rx_done<=1'b1; else rx_done <=1'b0; //數據讀取,對每次采樣進行求和值 always@(posedge clk or negedge rst_n) if(!rst_n)begin start_bit=3'd0; r_data_byte[0]<=3'd0; r_data_byte[1]<=3'd0; r_data_byte[2]<=3'd0; r_data_byte[3]<=3'd0; r_data_byte[4]<=3'd0; r_data_byte[5]<=3'd0; r_data_byte[6]<=3'd0; r_data_byte[7]<=3'd0; stop_bit<=3'd0; end else if(bps_clk)begin case(bps_cnt) 0:begin start_bit = 3'd0; r_data_byte[0] <= 3'd0; r_data_byte[1] <= 3'd0; r_data_byte[2] <= 3'd0; r_data_byte[3] <= 3'd0; r_data_byte[4] <= 3'd0; r_data_byte[5] <= 3'd0; r_data_byte[6] <= 3'd0; r_data_byte[7] <= 3'd0; stop_bit = 3'd0; end 6,7,8,9,10,11:start_bit <= start_bit + s1_rs232_rx; 22,23,24,25,26,27:r_data_byte[0] <= r_data_byte[0] + s1_rs232_rx; 38,39,40,41,42,43:r_data_byte[1] <= r_data_byte[1] + s1_rs232_rx; 54,55,56,57,58,59:r_data_byte[2] <= r_data_byte[2] + s1_rs232_rx; 70,71,72,73,74,75:r_data_byte[3] <= r_data_byte[3] + s1_rs232_rx; 86,87,88,89,90,91:r_data_byte[4] <= r_data_byte[4] + s1_rs232_rx; 102,103,104,105,106,107:r_data_byte[5] <= r_data_byte[5] + s1_rs232_rx; 118,119,120,121,122,123:r_data_byte[6] <= r_data_byte[6] + s1_rs232_rx; 134,135,136,137,138,139:r_data_byte[7] <= r_data_byte[7] + s1_rs232_rx; 150,151,152,153,154,155:stop_bit <= stop_bit + s1_rs232_rx; default: begin start_bit = start_bit; r_data_byte[0] <= r_data_byte[0]; r_data_byte[1] <= r_data_byte[1]; r_data_byte[2] <= r_data_byte[2]; r_data_byte[3] <= r_data_byte[3]; r_data_byte[4] <= r_data_byte[4]; r_data_byte[5] <= r_data_byte[5]; r_data_byte[6] <= r_data_byte[6]; r_data_byte[7] <= r_data_byte[7]; stop_bit = stop_bit; end endcase end always@(posedge clk or negedge rst_n)//數據提取 if(!rst_n) data_byte <= 8'd0; else if(bps_cnt == 8'd159)begin data_byte[0] <= r_data_byte[0][2]; data_byte[1] <= r_data_byte [1][2]; data_byte[2] <= r_data_byte[2][2]; data_byte[3] <= r_data_byte[3][2]; data_byte[4] <= r_data_byte[4][2]; data_byte[5] <= r_data_byte[5][2]; data_byte[6] <= r_data_byte[6][2]; data_byte[7] <= r_data_byte[7][2]; end //控制邏輯 always@(posedge clk or negedge rst_n) if(!rst_n) uart_state <= 1'b0; else if(nedege) uart_state <= 1'b1; else if(rx_done || (bps_cnt == 8'd12 && (start_bit > 2))) //接收完成或者到12位的時候,start_bit>2說明接收錯誤,起始位不對 uart_state <= 1'b0;//關閉傳輸狀態位 else uart_state <= uart_state; endmodule
編寫testbench並添加路徑,因為testbench中調用了uart_rx,所以在添加路徑的時候需要將uart.v文件添加到路徑中去
`timescale 1ns/1ns `define clock_period 20 module uart_rx_tb; reg rst_n; reg clk; reg rs232_rx; wire rs232_tx; reg [2:0]baud_set; wire rx_done; wire tx_done; wire [7:0]data_byte_r; reg [7:0]data_byte_t; reg send_en; wire uart_state; uart_rx uart_rx1(.clk(clk), .rs232_rx(rs232_tx), //用輸入值作為讀取值 .baud_set(baud_set), .rst_n(rst_n), .data_byte(data_byte_r), .rx_done(rx_done) ); uart_tx uart_tx2( .clk(clk), .rst_n(rst_n), .send_en(send_en), .baud_set(baud_set), .tx_done(tx_done), .rs232_tx(rs232_tx), .data_byte(data_byte_t), .uart_state(uart_state)//將串口狀態用LED顯示出來 ); initial clk=1; always#(`clock_period/2) clk=~clk; initial begin rst_n<=1'b0; data_byte_t<=8'd0; send_en<=1'd0; baud_set=3'd4; #(`clock_period*20+1) rst_n<=1'b1; #(`clock_period*50+1) data_byte_t<=8'haa; send_en<=1'd1; #(`clock_period) send_en<=1'd0; @(posedge tx_done)//等待傳輸完成的上升沿 #(`clock_period*5000)//重新發送 data_byte_t<=8'h55; send_en<=1'd1; #(`clock_period) send_en<=1'd0; #(`clock_period*500) $stop; end endmodule
將uart_rx設為頂層文件,編譯后進行仿真,仿真圖形如下:
新建ISSP IP核,添加8個探針
在uart_top中將rs_232_rx添加進來
module uart_top(clk ,rst_n,rs232_rx,rs232_tx,key_in,led); input key_in; input clk; input rst_n; input rs232_rx; output rs232_tx; wire send_en; wire [7:0]data_byte_t; reg [7:0]data_byte_r; wire [7:0]data_rx; output led; wire key_flag,key_state; wire rx_done; assign send_en=key_flag&!key_state;//按鍵檢測成功且為低電平時,發生使能 uart_tx uart_tx1( .clk(clk), .rst_n(rst_n), .send_en(send_en), .baud_set(3'd0), .tx_done(), .rs232_tx(rs232_tx), .data_byte(data_byte_t), .uart_state(led)//將串口狀態用LED顯示出來 ); uart_rx uart_rx2(.clk(clk), .rs232_rx(rs232_rx), //用輸入值作為讀取值 .baud_set(3'd0), .rst_n(rst_n), .data_byte(data_rx),//接收到的數據傳遞給data_rx; .rx_done(rx_done) ); key_filter key_filter0( .clk(clk), .rst_n(rst_n), .key_in(key_in), .key_flag(key_flag), .key_state(key_state) ); ISSP ISSP( .probe(),//調用一個ISSP 發送數據 .source(data_byte_t) ); ISSP1 ISSP2( .probe(data_byte_r),//調用一個ISSP 接收數據 .source() ); //應為data_rx可能沒有接收成功,為錯誤量,所以需要一個中間量來進行緩沖 always@(posedge clk or negedge rst_n) if(!rst_n) data_byte_r <= 8'd0; else if(rx_done) data_byte_r<= data_rx; else data_byte_r<= data_byte_r; endmodule
分配RX引腳給GPIO0_D0,然后將程序燒寫到FPGA中,將GPIO0-D0與GPIO0_D1連着直接用杜邦線連接,打開ISSP工具,將兩者都設置為hex顯示,且將第二個ISSP工具設置為循環掃描,在第一個ISSP中輸入數據,按下按鍵KEY1后,看到第二個ISSP 有相應的數據變化。
此時說明整個協議編寫完成 了可以完成通訊了