部落客福利:100G+電子設計學習資源包!
http://mp.weixin.qq.com/mp/homepage?__biz=MzU3OTczMzk5Mg==&hid=7&sn=ad5d5d0f15df84f4a92ebf72f88d4ee8&scene=18#wechat_redirect --------------------------------------------------------------------------------------------------------------------------
一、Rs232序列槽協定
序列槽通信指序列槽按位(bit)發送和接受位元組。雖然比并行通信要慢,但是其實體線路簡單并且通信距離長,可達到1200米。
實體連接配接:
A發送資料時通過tx将資料一位一位的傳輸給B的rx ,表現出來的就是tx線的高低電平,B就可以通過rx來檢測高低電平來确定資料1、0。
由于A、B之間并沒有時鐘線,不能像I2C那樣,可以通過時鐘為高時檢測資料腳的電平狀态來确定資料,那麼在序列槽協定中B應當如何确定何時采集rx端口的電平來作為資料呢?我們常常可以聽到波特率為9600或者115200等這些數值,那麼B就是通過這個數值來确定何時采集rx端口的電平。
以9600為例子,一幀資料的格式(10位):起始位、資料域(8bit)、停止位。
9600波特率 --> 9600Hz --> 1/9600(周期) --> 0.001041666666666667(秒) –-> 約 104167(ns)也就是說A的tx發送的每一個位的資料所保持的時間都必須在 104167ns 這個時間。而B也必須在這個時間内至少采集一次rx的電平狀态來得到資料。即A、B雙方都是以相同的速度去發送、采集資料。
啟動發送時,先将Tx拉低作為啟動信号,發送結束後則拉高Tx作為停止信号,空閑時Tx應為高電平狀态。
序列槽發送子產品所必須具備的兩個部分:
1、波特率的産生
采用計數分頻的使能時鐘方式産生波特率,那麼計數值應如何計算呢。
9600bps 約等于 104167ns,假如系統時鐘為 50MHz,那麼一個時鐘周期為 (1/50)*1000 = 20ns。
104167ns / 20ns = 5208次,即數系統時鐘數5208次即為104167ns。
二、FPGA 程式框圖
序列槽發送子產品的端口框圖:
輸入:Send_En:發送使能
Data_Byte[7:0]:要發送的資料
Baud_Set[2:0]:波特率選擇
Clk:系統時鐘
Rst_n:複位信号
輸出:Rs232_Tx:資料發送引腳
Tx_Done:發送完成通知信号(1:表示發送完成)
UART_state:子產品工作狀态(1:正在發送資料 0:發送完成或空閑狀态)
序列槽發送子產品詳細結構圖:
功能子產品描述:
DR_LUT:查表子產品,根據 Baud_Set[2:0] 選擇的波特率去查表得到計數分頻所需要的計數值即bps_DR[15:0]。
Div_Cnt: 計數分頻子產品,根據bps_DR[15:0] 來産生bps_clk作為tx發送資料位的節拍,即來一個bps_clk就發送一個資料位。該受en_cnt信号控制,en_cnt 為 0 時Div_Cnt子產品不計數,也就不會産生 bps_clk,也就不會發送資料。
bps_cnt:資料位計數子產品,對bps_clk進行計數,輸出bps_cnt_q[3:0],用于控制發送的資料位數,完成一幀資料長度的控制,當數到第11個bps_clk時會置高Tx_Done信号。
MUX10:10選1多路器,根據bps_cnt_q[3:0] 來輸出起始位、8位資料、停止位來設定 Rs232_Tx 信号。其實這裡應該是11選1多路器,第0個為輸出停止位信号。
MUX2_1、MUX2_2:二選一多路器,用于形成具有優先級的狀态控制機制。當Send_En信号為1時,那麼MUX2_1就會直接忽略MUX2_2 ,當Send_En信号為0時才會根據MUX2_2的選擇來控制UART_state.
整個邏輯控制流程:
1、當Send_En置高一個時鐘周期時 [MUX2_1] 輸出1到UART_state和en_cnt,此時 UART_state和en_cnt均為1。
下一個時鐘來臨之後,[MUX2_1] 取 [MUX2_2] 的狀态,由于 [MUX2_2] 取自Tx_Done信号,而Tx_Done為0,是以 [MUX2_2] 取的是en_cnt的信号,即UART_state == en_cnt == [MUX2_2] == 1。
隻要Tx_Done信号為1,則 [MUX2_2] 就會選擇輸出0,進而改變 UART_state、en_cnt信号,注意 [bsp_cnt] 子產品的clr信号也受Tx_Done控制。
2、en_cnt為1觸發 [Div_Cnt] 子產品工作,[Div_Cnt] 開始以bps_DR[15:0] 所設定的計數間隔輸出bps_clk信号。
3、bsp_cnt子產品檢測到bps_clk,開始數bps_clk個數,并輸出bps_cnt_q[3:0] 給 [MUX10] 多路器。bps由于clr信号來自Tx_Done信号,是以clr為0,不會清0計數。若bps_cnt_1[3:0]等于11,即bps_clk的個數為11,則輸出1給Tx_Done,
出現連鎖反應:
1、clr信号變為1:bsp_cnt子產品計數清零
2、[MUX2_2] 輸出0到 [MUX2_1] 再到UART_start 再到 en_cnt 導緻 [Div_Cnt] 停止輸出bsp_clk。
3、整個發送子產品也就停止發送資料。
4、MUX10:通過視訊中所寫的代碼來看,這裡應該是11選1多路器,0為Tx空閑時的狀态,即為高電平,1為起始位,2~9為要發送的資料,即Data_Byte[7:0]。10則是停止位。根據bps_cnt_q[3:0]來确定要選擇資料幀的哪一個位輸出到r_R232_Tx。
5、至此,整個邏輯部分完成。
三、代碼實作
代碼1:(代碼與視訊所寫的有點不太一樣,修改了幾句代碼是為了盡量符合上面的框圖設計)
module mytest(clk, rst_n, data_byte, send_en, baud_set, rs232_tx, tx_done, 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; // 發送完畢通知 1:發送完畢 0:正在發送
output reg uart_state; // 發送狀态 1:正在發送資料 0:空閑狀态
reg bps_clk; // 波特率時鐘
wire en_cnt; // 計數使能 1:使能 0:失能
reg[15:0] div_cnt; // 分頻計數器
reg[15:0] bps_dr; // 分頻計數最大值
reg[3:0] bps_cnt; // 波特率時鐘計數器
wire clr; // 清零信号
reg[7:0] r_data_byte_buff; // 緩沖區,用于存儲需要發送的資料,避免在發送過程中資料突然改變
localparam START_BIT = 1'b0;
localparam STOP_BIN = 1'b1;
// 序列槽工作狀态
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(tx_done) // 發送完畢
uart_state <= 1'b0;
else
uart_state <= uart_state;
end
assign en_cnt = uart_state;
// 用于啟動發送時鎖存即将要發送的資料
// 這樣就可以避免在發送的過程中資料突然改變導緻發送的資料不正确。
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
r_data_byte_buff <= 8'd0;
else if(send_en)
r_data_byte_buff <= data_byte; // 啟動發送則鎖存最新的資料
else
r_data_byte_buff <= r_data_byte_buff;
end
// 【DR_LUT】 通過查表的方式将波特率轉換為對應的分頻計數最大值
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_dr <= 16'd5207; // 9600bps
else begin
case(baud_set) // 查找表
0:bps_dr <= 16'd5207; // 9600bps
1:bps_dr <= 16'd2603; // 19200bps
2:bps_dr <= 16'd1301; // 38400bps
3:bps_dr <= 16'd0867; // 57600bps
4:bps_dr <= 16'd0433; // 115200bps
default:bps_dr <= 16'd5207; // 9600bps
endcase
end
end
// 【Div_Cnt】 計數功能
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
div_cnt <= 16'd0;
else if(en_cnt) begin
if(div_cnt == bps_dr)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end else
div_cnt <= 16'd0;
end
// 【Div_Cnt】 bps_clk 時鐘産生
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1) // 當計數器剛開始計數時就産生一個時鐘
bps_clk <= 1'b1; // 這樣就相當于啟動發送時就立即開始發送資料
else
bps_clk <= 1'b0;
end
// 【bps_cnt】 bps 計數(即發送的資料位數計數)
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_cnt <= 4'd0;
else if(clr)
bps_cnt <= 4'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
end
// 【MUX10】、【r_R232_Tx】 盡量避免組合邏輯直接輸出,輸出是有毛刺的可能會出現不太穩定的情況
// 發送資料子產品
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
rs232_tx <= STOP_BIN; // 起始位為低電平,是以空閑時為高電平即停止位
else begin
case(bps_cnt)
0:rs232_tx <= STOP_BIN; // 空閑時 bps_cnt 會一直為 0
1:rs232_tx <= START_BIT; // 起始位
2:rs232_tx <= r_data_byte_buff[0];
3:rs232_tx <= r_data_byte_buff[1];
4:rs232_tx <= r_data_byte_buff[2];
5:rs232_tx <= r_data_byte_buff[3];
6:rs232_tx <= r_data_byte_buff[4];
7:rs232_tx <= r_data_byte_buff[5];
8:rs232_tx <= r_data_byte_buff[6];
9:rs232_tx <= r_data_byte_buff[7];
10:rs232_tx <= STOP_BIN; // 停止位
default:rs232_tx <= STOP_BIN;
endcase
end
end
// 檢測一幀資料是否發送完成
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
tx_done <= 1'b0;
else if(bps_cnt == 4'd11)
tx_done <= 1'b1;
else
tx_done <= 1'b0;
end
assign clr = tx_done; // 當完成一幀資料發送之後清除 bps 計數器
endmodule
完整的時序圖
分析:
問題:tx_done、bps_cnt 會分别維持兩個時鐘周期的 1 和 11
原因:
因為當 bps_cnt 變為 11 的時候,需要等第2個時鐘周期才會被采樣到。當采樣到之後 tx_done = 1,因為 assign clr 也立即變為 1 ,而 clr 為 1 的時候也需要等第3個時鐘周期才能被 bps_cnt 采樣到變為 0,而 bps_cnt 為 0 時,需要等到第4個時鐘周期才能被 tx_done 采樣,才會變為 0 。
代碼2:(代碼與視訊所修改的方式不太一樣,修改了幾句代碼是為了盡量符合上面的框圖設計)
module mytest(clk, rst_n, data_byte, send_en, baud_set, rs232_tx, tx_done, 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 wire tx_done; // 發送完畢通知 1:發送完畢 0:正在發送
output reg uart_state; // 發送狀态 1:正在發送資料 0:空閑狀态
reg bps_clk; // 波特率時鐘
wire en_cnt; // 計數使能 1:使能 0:失能
reg[15:0] div_cnt; // 分頻計數器
reg[15:0] bps_dr; // 分頻計數最大值
reg[3:0] bps_cnt; // 波特率時鐘計數器
wire clr; // 清零信号
reg[7:0] r_data_byte_buff; // 緩沖區,用于存儲需要發送的資料,避免在發送過程中資料突然改變
localparam START_BIT = 1'b0;
localparam STOP_BIN = 1'b1;
// 序列槽工作狀态
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(tx_done) // 發送完畢
uart_state <= 1'b0;
else
uart_state <= uart_state;
end
assign en_cnt = uart_state;
// 用于啟動發送時鎖存即将要發送的資料
// 這樣就可以避免在發送的過程中資料突然改變導緻發送的資料不正确。
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
r_data_byte_buff <= 8'd0;
else if(send_en)
r_data_byte_buff <= data_byte; // 啟動發送則鎖存最新的資料
else
r_data_byte_buff <= r_data_byte_buff;
end
// 【DR_LUT】 通過查表的方式将波特率轉換為對應的分頻計數最大值
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_dr <= 16'd5207; // 9600bps
else begin
case(baud_set) // 查找表
0:bps_dr <= 16'd5207; // 9600bps
1:bps_dr <= 16'd2603; // 19200bps
2:bps_dr <= 16'd1301; // 38400bps
3:bps_dr <= 16'd0867; // 57600bps
4:bps_dr <= 16'd0433; // 115200bps
default:bps_dr <= 16'd5207; // 9600bps
endcase
end
end
// 【Div_Cnt】 計數功能
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
div_cnt <= 16'd0;
else if(en_cnt) begin
if(div_cnt == bps_dr)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end else
div_cnt <= 16'd0;
end
// 【Div_Cnt】 bps_clk 時鐘産生
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1) // 當計數器剛開始計數時就産生一個時鐘
bps_clk <= 1'b1; // 這樣就相當于啟動發送時就立即開始發送資料
else
bps_clk <= 1'b0;
end
// 【bps_cnt】 bps 計數(即發送的資料位數計數)
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_cnt <= 4'd0;
else if(clr)
bps_cnt <= 4'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
end
// 【MUX10】、【r_R232_Tx】 盡量避免組合邏輯直接輸出,輸出是有毛刺的可能會出現不太穩定的情況
// 發送資料子產品
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
rs232_tx <= STOP_BIN; // 起始位為低電平,是以空閑時為高電平即停止位
else begin
case(bps_cnt)
0:rs232_tx <= STOP_BIN; // 空閑時 bps_cnt 會一直為 0
1:rs232_tx <= START_BIT; // 起始位
2:rs232_tx <= r_data_byte_buff[0];
3:rs232_tx <= r_data_byte_buff[1];
4:rs232_tx <= r_data_byte_buff[2];
5:rs232_tx <= r_data_byte_buff[3];
6:rs232_tx <= r_data_byte_buff[4];
7:rs232_tx <= r_data_byte_buff[5];
8:rs232_tx <= r_data_byte_buff[6];
9:rs232_tx <= r_data_byte_buff[7];
10:rs232_tx <= STOP_BIN; // 停止位
default:rs232_tx <= STOP_BIN;
endcase
end
end
/*
// 檢測一幀資料是否發送完成 // 采用此種方式會導緻 tx_done、bps_cnt 會分别維持兩個時鐘周期的 1 和 11
// 因為當 bps_cnt 變為 11 的時候,需要等第2個時鐘周期才會被采樣到。當采樣到之後 tx_done = 1,而 clr 也立即變為 1 ,
// 而 clr 為 1 的時候也需要等第3個時鐘周期才能被 bps_cnt 采樣到變為 0
// 而 bps_cnt 為 0 時,需要等到第4個時鐘周期才能被 tx_done 采樣,才會變為 0
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
tx_done <= 1'b0;
else if(bps_cnt == 4'd11)
tx_done <= 1'b1;
else
tx_done <= 1'b0;
end
*/
// 【r_Tx_Done】 為了避免 tx_done 這裡采用直接指派的方式來避免出現延遲一個時鐘的現象
assign tx_done = bps_cnt == 4'd11 ? 1'b1 : 1'b0;
assign clr = tx_done; // 當完成一幀資料發送之後清除 bps 計數器
endmodule
修改後: