串口發送與接收模塊設計代碼分析
1.1Tx_Bps_Gen
Tx_Bps_Gen為發送波特率生成模塊,每當有Byte_En信號到來時,即開始產生發送一個完整字節的數據需要的完整波特率時鐘信號。
本設計,波特率支持9600bps到921600bps。例如,需要產生的波特率時鐘為9600bps,即波特率時鐘頻率為9600Hz,周期為104.17us。生成9600Hz波特率時鐘的核心思想就是對系統時鐘進行計數,這里設定系統時鐘為50MHz,則一個時鐘的周期為20ns,我們只需要對系統時鐘計數5208次,每計數5208次產生一個時鐘周期的高電平脈沖,即可實現生成9600Hz波特率時鐘的功能。相應代碼如下所示:
018 parameter system_clk = 50_000_000; /*輸入時鐘頻率設定,默認50M*/
019
020 /*根據輸入時鐘頻率計算生成各波特率時分頻計數器的計數最大值*/
021 localparam bps9600 = system_clk/9600 - 1;
022 localparam bps19200 = system_clk/19200 - 1;
023 localparam bps38400 = system_clk/38400 - 1;
024 localparam bps57600 = system_clk/57600 - 1;
025 localparam bps115200 = system_clk/115200 - 1;
026 localparam bps230400 = system_clk/230400 - 1;
027 localparam bps460800 = system_clk/460800 - 1;
028 localparam bps921600 = system_clk/921600 - 1;
029
030 reg [31:0]BPS_PARA;/*波特率分頻計數器的計數最大值*/
031
032 always@(posedge Clk or negedge Rst_n)
033 if(!Rst_n)begin
034 BPS_PARA 《= bps9600;/*復位時波特率默認為9600bps*/
035 end
036 else begin
037 case(Baud_Set)/*根據波特率控制信號選擇不同的波特率計數器計數最大值*/
038 3‘d0: BPS_PARA 《= bps9600;
039 3’d1: BPS_PARA 《= bps19200;
040 3‘d2: BPS_PARA 《= bps38400;
041 3’d3: BPS_PARA 《= bps57600;
042 3‘d4: BPS_PARA 《= bps115200;
043 3’d5: BPS_PARA 《= bps230400;
044 3‘d6: BPS_PARA 《= bps460800;
045 3’d7: BPS_PARA 《= bps921600;
046 default: BPS_PARA 《= bps9600;
047 endcase
048 end
049
050 //=========================================================
051 reg[12:0]Count;
052
053 reg n_state;
054 localparam IDEL_1 = 1‘b0,
055 SEND = 1’b1;
056
057 reg BPS_EN;
058
059 /*-------波特率時鐘生成控制邏輯--------------*/
060 always@(posedge Clk or negedge Rst_n)
061 if(!Rst_n)begin
062 BPS_EN 《= 1‘b0;
063 n_state 《= IDEL_1;
064 end
065 else begin
066 case(n_state)
067 IDEL_1:
068 if(Byte_En)begin/*檢測到字節發送使能信號,則啟動波特率生成進程,同時進入發送狀態*/
069 BPS_EN 《= 1’b1;
070 n_state 《= SEND;
071 end
072 else begin
073 n_state 《= IDEL_1;
074 BPS_EN 《= 1‘b0;
075 end
076 SEND:
077 if(Tx_Done == 1)begin/*發送完成,關閉波特率生成進程,回到空閑狀態*/
078 BPS_EN 《= 1’b0;
079 n_state 《= IDEL_1;
080 end
081 else begin
082 n_state 《= SEND;
083 BPS_EN 《= 1‘b1;
084 end
085 default:n_state 《= IDEL_1;
086 endcase
087 end
088
089 /*-------波特率時鐘生成定時器--------------*/
090 always@(posedge Clk or negedge Rst_n)
091 if(!Rst_n)
092 Count 《= 13’d0;
093 else if(BPS_EN == 1‘b0)
094 Count 《= 13’d0;
095 else begin
096 if(Count == BPS_PARA)
097 Count 《= 13‘d0;
098 else
099 Count 《= Count + 1’b1;
100 end
101
102 /*輸出數據接收采樣時鐘*/
103 //-----------------------------------------------
104 always @(posedge Clk or negedge Rst_n)
105 if(!Rst_n)
106 Bps_Clk 《= 1‘b0;
107 else if(Count== 1)
108 Bps_Clk 《= 1’b1;
109 else
110 Bps_Clk 《= 1‘b0;
第18行“parameter system_clk = 50_000_000;”,這里用一個全局參數定義了系統時鐘,暫時設定為50M,可根據實際使用的板卡上的工作時鐘進行修改。
所謂波特率生成,就是用一個定時器來定時,產生頻率與對應波特率時鐘頻率相同的時鐘信號。例如,我們使用波特率為115200bps,則我們需要產生一個頻率為115200Hz的時鐘信號。那么如何產生這樣一個115200Hz的時鐘信號呢?這里,我們首先將115200Hz時鐘信號的周期計算出來,1秒鐘為1000_000_000ns,因此波特率時鐘的周期Tb= 1000000000/115200 =8680.6ns,即115200信號的一個周期為8680.6ns,那么,我們只需要設定我們的定時器定時時間為8680.6ns,每當定時時間到,產生一個系統時鐘周期長度的高脈沖信號即可。系統時鐘頻率為50MHz,即周期為20ns,那么,我們只需要計數8680/20個系統時鐘,就可獲得8680ns的定時,即bps115200=Tb/Tclk - 1=Tb*fclk - 1=fclk/115200-1。相應的,其它波特率定時值的計算與此類似,這里小梅哥就不再一一分析。20行至28行為波特率定時器定時值的計算部分。
為了能夠通過外部控制波特率,設計中使用了一個3位的波特率選擇端口:Baud_Set。通過給此端口不同的值,就能選擇不同的波特率,此端口控制不同波特率的原理很簡單,就是一個多路選擇器,第32行至第48行即為此多路選擇器的控制代碼, Baud_Set的值與各波特率的對應關系如下:
000 :9600bps;
001 :19200bps;
010 :38400bps;
011 :57600bps;
100 :115200bps;
101 :230400bps;
110 :460800bps;
111 :921600bps;
1.2Uart_Byte_Tx
Uart_Byte_Tx為字節發送模塊,該模塊在波特率時鐘的節拍下,依照UART通信協議發送一個完整的字節的數據。當一個字節發送完畢后,Tx_Done產生一個高脈沖信號,以告知其它模塊或邏輯一個字節的數據已經傳輸完成,可以開始下一個字節的發送了。其發送一個字節數據的實現代碼如下:
33 /*計數波特率時鐘,11個波特率時鐘為一次完整的數據發送過程*/
34 always@(posedge Clk or negedge Rst_n)
35 if(!Rst_n)
36 Bps_Clk_Cnt 《= 4’b0;
37 else if(Bps_Clk_Cnt == 4‘d11)
38 Bps_Clk_Cnt 《= 4’b0;
39 else if(Bps_Clk)
40 Bps_Clk_Cnt 《= Bps_Clk_Cnt + 1‘b1;
41 else
42 Bps_Clk_Cnt 《= Bps_Clk_Cnt;
43
44 /*生成數據發送完成標志信號*/
45 always@(posedge Clk or negedge Rst_n)
46 if(!Rst_n)
47 Tx_Done 《= 1’b0;
48 else if(Bps_Clk_Cnt == 4‘d11)
49 Tx_Done 《= 1’b1;
50 else
51 Tx_Done 《= 1‘b0;
52
53 /*在開始發送起始位的時候就讀取并寄存Data_Byte,以免Data_Byte變化導致數據的丟失*/
54 always@(posedge Clk or negedge Rst_n)
55 if(!Rst_n)
56 Data = 8’d0;
57 else if(Bps_Clk & Bps_Clk_Cnt == 4‘d1)
58 Data 《= Data_Byte;
59 else
60 Data 《= Data;
61
62 /*發送數據序列機*/
63 always@(posedge Clk or negedge Rst_n)
64 if(!Rst_n)
65 Rs232_Tx 《= 1’b1;
66 else begin
67 case(Bps_Clk_Cnt)
68 4‘d1: Rs232_Tx 《= 1’b0;
69 4‘d2: Rs232_Tx 《= Data[0];
70 4’d3: Rs232_Tx 《= Data[1];
71 4‘d4: Rs232_Tx 《= Data[2];
72 4’d5: Rs232_Tx 《= Data[3];
73 4‘d6: Rs232_Tx 《= Data[4];
74 4’d7: Rs232_Tx 《= Data[5];
75 4‘d8: Rs232_Tx 《= Data[6];
76 4’d9: Rs232_Tx 《= Data[7];
77 4‘d10: Rs232_Tx 《= 1’b1;
78 default:Rs232_Tx 《= 1‘b1;
79 endcase
80 end
在UART協議中,一個完整的字節包括一位起始位、8位數據位、一位停止位即總共十位數據,那么,要想完整的實現這十位數據的發送,就需要11個波特率時鐘脈沖,如下所示:
BPS_CLK信號的第一個上升沿到來時,字節發送模塊開始發送起始位,接下來的2到9個上升沿,發送8個數據位,第10個上升沿到第11個上升沿為停止位的發送。
單個串口接收模塊中實現串口數據接收的主要代碼如下所示:
025 always @ (posedge Clk or negedge Rst_n)
026 if(!Rst_n) begin
027 Rs232_Rx0 《= 1’b0;
028 Rs232_Rx1 《= 1‘b0;
029 Rs232_Rx2 《= 1’b0;
030 Rs232_Rx3 《= 1‘b0;
031 end
032 else begin
033 Rs232_Rx0 《= Rs232_Rx;
034 Rs232_Rx1 《= Rs232_Rx0;
035 Rs232_Rx2 《= Rs232_Rx1;
036 Rs232_Rx3 《= Rs232_Rx2;
037 end
038
039 wire neg_Rs232_Rx= Rs232_Rx3 & Rs232_Rx2 & ~Rs232_Rx1 & ~Rs232_Rx0;
040
041 assign Byte_En = neg_Rs232_Rx;
042
043 /*----------計數采樣時鐘--------------*/
044 /*9倍波特率采樣時鐘,故一個完整的接收過程有90個波特率時鐘*/
045 reg[6:0]Sample_Clk_Cnt;
046 always @ (posedge Clk or negedge Rst_n)
047 if(!Rst_n)
048 Sample_Clk_Cnt 《= 7’d0;
049 else if(Sample_Clk)begin
050 if(Sample_Clk_Cnt == 7‘d89)
051 Sample_Clk_Cnt 《= 7’d0;
052 else
053 Sample_Clk_Cnt 《= Sample_Clk_Cnt + 1‘b1;
054 end
055 else
056 Sample_Clk_Cnt 《= Sample_Clk_Cnt;
057
058 reg [1:0]Start_Bit; /*起始位,這里雖然定義,但并未使用該位來判斷接收數據的正確性,即默認接收都是成功的*/
059 reg [1:0]Stop_Bit; /*停止位,這里雖然定義,但并未使用該位來判斷接收數據的正確性,即默認接收都是成功的*/
060 reg [1:0] Data_Tmp[7:0];/*此部分較為復雜,請參看說明文檔中相關解釋*/
061
062 always @ (posedge Clk or negedge Rst_n)
063 if(!Rst_n)begin
064 Data_Tmp[0] 《= 2’d0;
065 Data_Tmp[1] 《= 2‘d0;
066 Data_Tmp[2] 《= 2’d0;
067 Data_Tmp[3] 《= 2‘d0;
068 Data_Tmp[4] 《= 2’d0;
069 Data_Tmp[5] 《= 2‘d0;
070 Data_Tmp[6] 《= 2’d0;
071 Data_Tmp[7] 《= 2‘d0;
072 Start_Bit 《= 2’d0;
073 Stop_Bit 《= 2‘d0;
074 end
075 else if(Sample_Clk)begin
076 case(Sample_Clk_Cnt)
077 7’d0:
078 begin
079 Data_Tmp[0] 《= 2‘d0;
080 Data_Tmp[1] 《= 2’d0;
081 Data_Tmp[2] 《= 2‘d0;
082 Data_Tmp[3] 《= 2’d0;
083 Data_Tmp[4] 《= 2‘d0;
084 Data_Tmp[5] 《= 2’d0;
085 Data_Tmp[6] 《= 2‘d0;
086 Data_Tmp[7] 《= 2’d0;
087 Start_Bit 《= 2‘d0;
088 Stop_Bit 《= 2’d0;
089 end
090 7‘d3,7’d4,7‘d5: Start_Bit 《= Start_Bit + Rs232_Rx;
091 7’d12,7‘d13,7’d14:Data_Tmp[0] 《= Data_Tmp[0] + Rs232_Rx;
092 7‘d21,7’d22,7‘d23:Data_Tmp[1] 《= Data_Tmp[1] + Rs232_Rx;
093 7’d30,7‘d31,7’d32:Data_Tmp[2] 《= Data_Tmp[2] + Rs232_Rx;
094 7‘d39,7’d40,7‘d41:Data_Tmp[3] 《= Data_Tmp[3] + Rs232_Rx;
095 7’d48,7‘d49,7’d50:Data_Tmp[4] 《= Data_Tmp[4] + Rs232_Rx;
096 7‘d57,7’d58,7‘d59:Data_Tmp[5] 《= Data_Tmp[5] + Rs232_Rx;
097 7’d66,7‘d67,7’d68:Data_Tmp[6] 《= Data_Tmp[6] + Rs232_Rx;
098 7‘d75,7’d76,7‘d77:Data_Tmp[7] 《= Data_Tmp[7] + Rs232_Rx;
099 7’d84,7‘d85,7’d86:Stop_Bit 《= Stop_Bit + Rs232_Rx;
100 default:;
101 endcase
102 end
103 else ;
根據串口發送協議,一個字節的數據傳輸是以一個波特率周期的低電平作為起始位的,因此,成功接收UART串口數據的核心就是準確檢測起始位。由于外部串口發送過來的數據與接收系統不在同一個時鐘域,因此不能直接使用該信號的下降沿來作為檢測標志,我們需要在fpga中,采用專用的邊沿檢測電路來實現,第25行至37行通過四個移位寄存器,存儲連續四個時鐘上升沿時外部發送數據線的狀態,第39行通過比較前兩個時鐘時數據線的狀態與后兩個時鐘時數據線的狀態,來得到該數據線的準確下降沿,以此保證起始位的準確檢測。
在簡單的串口接收中,我們通常選取一位數據的中間時刻進行采樣,因為此時數據最穩定,但是在工業環境中,存在著各種干擾,在干擾存在的情況下,如果采用傳統的中間時刻采樣一次的方式,采樣結果就有可能受到干擾而出錯。為了濾除這種干擾,這里采用多次采樣求概率的方式。如下圖,將一位數據平均分成9個時間段,對位于中間的三個時間段進行采樣。然后對三個采樣結果進行統計判斷,如果某種電平狀態在三次采樣結果中占到了兩次及以上,則可以判定此電平狀態即為正確的數據電平。例如4、5、6時刻采樣結果分別為1、1、0,那么就取此位解碼結果為1,否則,若三次采樣結果為0、1、0,則解碼結果就為0。
因為采樣一位需要9個時鐘上升沿,因此,采樣一個完整的數據需要10*9,即90個時鐘上升沿,這里,采樣時鐘為波特率時鐘的9倍。產生采樣時鐘的部分代碼如下所示:
089 /*-------波特率時鐘生成定時器--------------*/
090 always@(posedge Clk or negedge Rst_n)
091 if(!Rst_n)
092 Count 《= 10‘d0;
093 else if(BPS_EN == 1’b0)
094 Count 《= 10‘d0;
095 else begin
096 if(Count == BPS_PARA)
097 Count 《= 10’d0;
098 else
099 Count 《= Count + 1‘b1;
100 end
101
102 //=====================================================
103 /*輸出數據接收采樣時鐘*/
104 always @(posedge Clk or negedge Rst_n)
105 if(!Rst_n)
106 Sample_Clk 《= 1’b0;
107 else if(Count== 1)
108 Sample_Clk 《= 1‘b1;
109 else
110 Sample_Clk 《= 1’b0;
這里,BPS_PARA的計算原理和前面Tx_Bps_Gen模塊中的BPS_PARA的計算原理一致,不過這里,因為采樣時鐘為波特率時鐘的9倍,所以,BPS_PARA為Tx_Bps_Gen模塊中的BPS_PARA的1/9。計算BPS_PARA的相關代碼如下:
018 parameter system_clk = 50_000_000; /*輸入時鐘頻率設定,默認50M*/
019
020 /*根據輸入時鐘頻率計算生成各波特率時分頻計數器的計數最大值*/
021 localparam bps9600 = system_clk/9600/9 - 1;
022 localparam bps19200 = system_clk/19200/9 - 1;
023 localparam bps38400 = system_clk/38400/9 - 1;
024 localparam bps57600 = system_clk/57600/9 - 1;
025 localparam bps115200 = system_clk/115200/9 - 1;
026 localparam bps230400 = system_clk/230400/9 - 1;
027 localparam bps460800 = system_clk/460800/9 - 1;
028 localparam bps921600 = system_clk/921600/9 - 1;
029
030 reg [31:0]BPS_PARA;/*波特率分頻計數器的計數最大值*/
031
032 always@(posedge Clk or negedge Rst_n)
033 if(!Rst_n)begin
034 BPS_PARA 《= bps9600; /*復位時波特率默認為9600bps*/
035 end
036 else begin
037 case(Baud_Set) /*根據波特率控制信號選擇不同的波特率計數器計數最大值*/
038 3‘d0: BPS_PARA 《= bps9600;
039 3’d1: BPS_PARA 《= bps19200;
040 3‘d2: BPS_PARA 《= bps38400;
041 3’d3: BPS_PARA 《= bps57600;
042 3‘d4: BPS_PARA 《= bps115200;
043 3’d5: BPS_PARA 《= bps230400;
044 3‘d6: BPS_PARA 《= bps460800;
045 3’d7: BPS_PARA 《= bps921600;
046 default: BPS_PARA 《= bps9600;/*異常情況,恢復到9600的波特率*/
047 endcase
048 end
責任編輯:lq6
-
接收模塊
+關注
關注
1文章
20瀏覽量
10456 -
時鐘信號
+關注
關注
4文章
448瀏覽量
28542 -
分頻計數器
+關注
關注
0文章
4瀏覽量
7924
原文標題:基于ZX-2型FPGA開發板的串口示波器(二)
文章出處:【微信號:gh_9d70b445f494,微信公眾號:FPGA設計論壇】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論