本帖最后由 51hei人人 于 2016-3-12 22:14 編輯
一、Rs232串口協(xié)議 串口通信指串口按位(bit)發(fā)送和接受字節(jié)。雖然比并行通信要慢,但是其物理線路簡(jiǎn)單并且通信距離 長(zhǎng),可達(dá)到1200米。 物理連接:
A發(fā)送數(shù)據(jù)時(shí)通過tx將數(shù)據(jù)一位一位的傳輸給B的rx ,表現(xiàn)出來的就是tx線的高低電平,B就可以 通過rx來檢測(cè)高低電平來確定數(shù)據(jù)1、0。 由于A、B之間并沒有時(shí)鐘線,不能像I2C那樣,可以通過時(shí)鐘為高時(shí)檢測(cè)數(shù)據(jù)腳的電平狀態(tài)來確定數(shù) 據(jù),那么在串口協(xié)議中B應(yīng)當(dāng)如何確定何時(shí)采集rx端口的電平來作為數(shù)據(jù)呢?我們常?梢月牭讲ㄌ芈蕿9600或者115200等這些數(shù)值,那么B就是通過這個(gè)數(shù)值來確定何時(shí)采集rx端口的電平。 以9600為例子,一幀數(shù)據(jù)的格式(10位):起始位、數(shù)據(jù)域(8bit)、停止位。 9600波特率 --> 9600Hz --> 1/9600(周期) --> 0.001041666666666667(秒) –-> 約 104167(ns) 也就是說A的tx發(fā)送的每一個(gè)位的數(shù)據(jù)所保持的時(shí)間都必須在 104167ns 這個(gè)時(shí)間。而B也必須在這 個(gè)時(shí)間內(nèi)至少采集一次rx的電平狀態(tài)來得到數(shù)據(jù)。即A、B雙方都是以相同的速度去發(fā)送、采集數(shù)據(jù)。 啟動(dòng)發(fā)送時(shí),先將Tx拉低作為啟動(dòng)信號(hào),發(fā)送結(jié)束后則拉高Tx作為停止信號(hào),空閑時(shí)Tx應(yīng)為高電平狀態(tài)。 串口發(fā)送模塊所必須具備的兩個(gè)部分: 1、波特率的產(chǎn)生 采用計(jì)數(shù)分頻的使能時(shí)鐘方式產(chǎn)生波特率,那么計(jì)數(shù)值應(yīng)如何計(jì)算呢。 9600bps 約等于 104167ns,假如系統(tǒng)時(shí)鐘為 50MHz,那么一個(gè)時(shí)鐘周期為 (1/50)*1000 = 20ns。 104167ns / 20ns = 5208次,即數(shù)系統(tǒng)時(shí)鐘數(shù)5208次即為104167ns。
baud_set | 波特率 | 波特率周期 | 波特率分頻計(jì)數(shù)值 | System_clk_period = 20計(jì)數(shù)值 (從0開始計(jì)算所以-1) | 0 | 9600 | 104167ns | 104167/ System_clk_period | 5208-1 | 1 | 19200 | 52083ns | 52083/ System_clk_period | 2604-1 | 2 | 38400 | 26041ns | 26041/ System_clk_period | 1302-1 | 3 | 57600 | 17361ns | 17361/ System_clk_period | 868-1 | 4 | 115200 | 8680ns | 8680/ System_clk_period | 434-1 |
2、數(shù)據(jù)發(fā)送模塊 二、FPGA 程序框圖 串口發(fā)送模塊的端口框圖: 輸入: Send_En:發(fā)送使能 Data_Byte[7:0]:要發(fā)送的數(shù)據(jù) Baud_Set[2:0]:波特率選擇 Clk:系統(tǒng)時(shí)鐘 Rst_n:復(fù)位信號(hào) 輸出: Rs232_Tx:數(shù)據(jù)發(fā)送引腳 Tx_Done:發(fā)送完成通知信號(hào)(1:表示發(fā)送完成) UART_state:模塊工作狀態(tài)(1:正在發(fā)送數(shù)據(jù) 0:發(fā)送完成或空閑狀態(tài)) 串口發(fā)送模塊詳細(xì)結(jié)構(gòu)圖: 功能模塊描述: DR_LUT:查表模塊,根據(jù) Baud_Set[2:0] 選擇的波特率去查表得到計(jì)數(shù)分頻所需要的計(jì)數(shù)值即bps_DR[15:0]。
Div_Cnt: 計(jì)數(shù)分頻模塊,根據(jù)bps_DR[15:0] 來產(chǎn)生bps_clk作為tx發(fā)送數(shù)據(jù)位的節(jié)拍,即來一個(gè)bps_clk就發(fā)送一個(gè)數(shù)據(jù)位。該受en_cnt信號(hào)控制,en_cnt 為 0 時(shí)Div_Cnt模塊不計(jì)數(shù),也就不會(huì)產(chǎn)生 bps_clk,也就不會(huì)發(fā)送數(shù)據(jù)。
bps_cnt:數(shù)據(jù)位計(jì)數(shù)模塊,對(duì)bps_clk進(jìn)行計(jì)數(shù),輸出bps_cnt_q[3:0],用于控制發(fā)送的數(shù)據(jù)位數(shù),完成一幀數(shù)據(jù)長(zhǎng)度的控制,當(dāng)數(shù)到第11個(gè)bps_clk時(shí)會(huì)置高Tx_Done信號(hào)。
MUX10:10選1多路器,根據(jù)bps_cnt_q[3:0] 來輸出起始位、8位數(shù)據(jù)、停止位來設(shè)置 Rs232_Tx 信號(hào)。其實(shí)這里應(yīng)該是11選1多路器,第0個(gè)為輸出停止位信號(hào)。
MUX2_1、MUX2_2:二選一多路器,用于形成具有優(yōu)先級(jí)的狀態(tài)控制機(jī)制。當(dāng)Send_En信號(hào)為1時(shí),那么MUX2_1就會(huì)直接忽略MUX2_2 ,當(dāng)Send_En信號(hào)為0時(shí)才會(huì)根據(jù)MUX2_2的選擇來控制UART_state. 整個(gè)邏輯控制流程:
1、當(dāng)Send_En置高一個(gè)時(shí)鐘周期時(shí) [MUX2_1] 輸出1到UART_state和en_cnt,此時(shí) UART_state和en_cnt均為1。
下一個(gè)時(shí)鐘來臨之后,[MUX2_1] 取 [MUX2_2] 的狀態(tài),由于 [MUX2_2] 取自Tx_Done信號(hào),而Tx_Done為0,所以 [MUX2_2] 取的是en_cnt的信號(hào),即UART_state == en_cnt == [MUX2_2] == 1。
只要Tx_Done信號(hào)為1,則 [MUX2_2] 就會(huì)選擇輸出0,從而改變 UART_state、en_cnt信號(hào),注意 [bsp_cnt] 模塊的clr信號(hào)也受Tx_Done控制。
2、en_cnt為1觸發(fā) [Div_Cnt] 模塊工作,[Div_Cnt] 開始以bps_DR[15:0] 所設(shè)置的計(jì)數(shù)間隔輸出bps_clk信號(hào)。
3、bsp_cnt模塊檢測(cè)到bps_clk,開始數(shù)bps_clk個(gè)數(shù),并輸出bps_cnt_q[3:0] 給 [MUX10] 多路器。bps由于clr信號(hào)來自Tx_Done信號(hào),所以clr為0,不會(huì)清0計(jì)數(shù)。若bps_cnt_1[3:0]等于11,即bps_clk的個(gè)數(shù)為11,則輸出1給Tx_Done,出現(xiàn)連鎖反應(yīng): 1、clr信號(hào)變?yōu)?:bsp_cnt模塊計(jì)數(shù)清零 2、[MUX2_2] 輸出0到 [MUX2_1] 再到UART_start 再到 en_cnt 導(dǎo)致 [Div_Cnt] 停止輸出bsp_clk。 3、整個(gè)發(fā)送模塊也就停止發(fā)送數(shù)據(jù)。
4、MUX10:通過視頻中所寫的代碼來看,這里應(yīng)該是11選1多路器,0為Tx空閑時(shí)的狀態(tài),即為高電平,1為起始位,2~9為要發(fā)送的數(shù)據(jù),即Data_Byte[7:0]。10則是停止位。根據(jù)bps_cnt_q[3:0]來確定要選擇數(shù)據(jù)幀的哪一個(gè)位輸出到r_R232_Tx。
5、至此,整個(gè)邏輯部分完成。 三、代碼實(shí)現(xiàn) 代碼1:(代碼與視頻所寫的有點(diǎn)不太一樣,修改了幾句代碼是為了盡量符合上面的框圖設(shè)計(jì)) module mytest(clk, rst_n, data_byte, send_en, baud_set, rs232_tx, tx_done, uart_state);
input clk; // 系統(tǒng)時(shí)鐘
input rst_n; // 復(fù)位
input[7:0] data_byte; // 要發(fā)送的數(shù)據(jù)
input send_en; // 啟動(dòng)發(fā)送
input[2:0] baud_set; // 波特率選擇
output reg rs232_tx;
output reg tx_done; // 發(fā)送完畢通知 1:發(fā)送完畢 0:正在發(fā)送
output reg uart_state; // 發(fā)送狀態(tài) 1:正在發(fā)送數(shù)據(jù) 0:空閑狀態(tài)
reg bps_clk; // 波特率時(shí)鐘
wire en_cnt; // 計(jì)數(shù)使能 1:使能 0:失能
reg[15:0] div_cnt; // 分頻計(jì)數(shù)器
reg[15:0] bps_dr; // 分頻計(jì)數(shù)最大值
reg[3:0] bps_cnt; // 波特率時(shí)鐘計(jì)數(shù)器
wire clr; // 清零信號(hào)
reg[7:0] r_data_byte_buff; // 緩沖區(qū),用于存儲(chǔ)需要發(fā)送的數(shù)據(jù),避免在發(fā)送過程中數(shù)據(jù)突然改變
localparam START_BIT = 1'b0;
localparam STOP_BIN = 1'b1;
// 串口工作狀態(tài)
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) // 發(fā)送完畢
uart_state <= 1'b0;
else
uart_state <= uart_state;
end
assign en_cnt = uart_state;
// 用于啟動(dòng)發(fā)送時(shí)鎖存即將要發(fā)送的數(shù)據(jù)
// 這樣就可以避免在發(fā)送的過程中數(shù)據(jù)突然改變導(dǎo)致發(fā)送的數(shù)據(jù)不正確。
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; // 啟動(dòng)發(fā)送則鎖存最新的數(shù)據(jù)
else
r_data_byte_buff <= r_data_byte_buff;
end
// 【DR_LUT】 通過查表的方式將波特率轉(zhuǎn)換為對(duì)應(yīng)的分頻計(jì)數(shù)最大值
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】 計(jì)數(shù)功能
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 時(shí)鐘產(chǎn)生
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1) // 當(dāng)計(jì)數(shù)器剛開始計(jì)數(shù)時(shí)就產(chǎn)生一個(gè)時(shí)鐘
bps_clk <= 1'b1; // 這樣就相當(dāng)于啟動(dòng)發(fā)送時(shí)就立即開始發(fā)送數(shù)據(jù)
else
bps_clk <= 1'b0;
end
// 【bps_cnt】 bps 計(jì)數(shù)(即發(fā)送的數(shù)據(jù)位數(shù)計(jì)數(shù))
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】 盡量避免組合邏輯直接輸出,輸出是有毛刺的可能會(huì)出現(xiàn)不太穩(wěn)定的情況
// 發(fā)送數(shù)據(jù)模塊
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
rs232_tx <= STOP_BIN; // 起始位為低電平,所以空閑時(shí)為高電平即停止位
else begin
case(bps_cnt)
0:rs232_tx <= STOP_BIN; // 空閑時(shí) bps_cnt 會(huì)一直為 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
// 檢測(cè)一幀數(shù)據(jù)是否發(fā)送完成
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; // 當(dāng)完成一幀數(shù)據(jù)發(fā)送之后清除 bps 計(jì)數(shù)器
endmodule 完整的時(shí)序圖 分析: 
問題:tx_done、bps_cnt 會(huì)分別維持兩個(gè)時(shí)鐘周期的 1 和 11
原因: 因?yàn)楫?dāng) bps_cnt 變?yōu)?11 的時(shí)候,需要等第2個(gè)時(shí)鐘周期才會(huì)被采樣到。當(dāng)采樣到之后 tx_done = 1,因?yàn)?assign clr 也立即變?yōu)?1 ,而 clr 為 1 的時(shí)候也需要等第3個(gè)時(shí)鐘周期才能被 bps_cnt 采樣到變?yōu)?0,而 bps_cnt 為 0 時(shí),需要等到第4個(gè)時(shí)鐘周期才能被 tx_done 采樣,才會(huì)變?yōu)?0 。 代碼2:(代碼與視頻所修改的方式不太一樣,修改了幾句代碼是為了盡量符合上面的框圖設(shè)計(jì)) module mytest(clk, rst_n, data_byte, send_en, baud_set, rs232_tx, tx_done, uart_state);
input clk; // 系統(tǒng)時(shí)鐘
input rst_n; // 復(fù)位
input[7:0] data_byte; // 要發(fā)送的數(shù)據(jù)
input send_en; // 啟動(dòng)發(fā)送
input[2:0] baud_set; // 波特率選擇
output reg rs232_tx;
output wire tx_done; // 發(fā)送完畢通知 1:發(fā)送完畢 0:正在發(fā)送
output reg uart_state; // 發(fā)送狀態(tài) 1:正在發(fā)送數(shù)據(jù) 0:空閑狀態(tài)
reg bps_clk; // 波特率時(shí)鐘
wire en_cnt; // 計(jì)數(shù)使能 1:使能 0:失能
reg[15:0] div_cnt; // 分頻計(jì)數(shù)器
reg[15:0] bps_dr; // 分頻計(jì)數(shù)最大值
reg[3:0] bps_cnt; // 波特率時(shí)鐘計(jì)數(shù)器
wire clr; // 清零信號(hào)
reg[7:0] r_data_byte_buff; // 緩沖區(qū),用于存儲(chǔ)需要發(fā)送的數(shù)據(jù),避免在發(fā)送過程中數(shù)據(jù)突然改變
localparam START_BIT = 1'b0;
localparam STOP_BIN = 1'b1;
// 串口工作狀態(tài)
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) // 發(fā)送完畢
uart_state <= 1'b0;
else
uart_state <= uart_state;
end
assign en_cnt = uart_state;
// 用于啟動(dòng)發(fā)送時(shí)鎖存即將要發(fā)送的數(shù)據(jù)
// 這樣就可以避免在發(fā)送的過程中數(shù)據(jù)突然改變導(dǎo)致發(fā)送的數(shù)據(jù)不正確。
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; // 啟動(dòng)發(fā)送則鎖存最新的數(shù)據(jù)
else
r_data_byte_buff <= r_data_byte_buff;
end
// 【DR_LUT】 通過查表的方式將波特率轉(zhuǎn)換為對(duì)應(yīng)的分頻計(jì)數(shù)最大值
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】 計(jì)數(shù)功能
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 時(shí)鐘產(chǎn)生
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1) // 當(dāng)計(jì)數(shù)器剛開始計(jì)數(shù)時(shí)就產(chǎn)生一個(gè)時(shí)鐘
bps_clk <= 1'b1; // 這樣就相當(dāng)于啟動(dòng)發(fā)送時(shí)就立即開始發(fā)送數(shù)據(jù)
else
bps_clk <= 1'b0;
end
// 【bps_cnt】 bps 計(jì)數(shù)(即發(fā)送的數(shù)據(jù)位數(shù)計(jì)數(shù))
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】 盡量避免組合邏輯直接輸出,輸出是有毛刺的可能會(huì)出現(xiàn)不太穩(wěn)定的情況
// 發(fā)送數(shù)據(jù)模塊
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
rs232_tx <= STOP_BIN; // 起始位為低電平,所以空閑時(shí)為高電平即停止位
else begin
case(bps_cnt)
0:rs232_tx <= STOP_BIN; // 空閑時(shí) bps_cnt 會(huì)一直為 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
/*
// 檢測(cè)一幀數(shù)據(jù)是否發(fā)送完成 // 采用此種方式會(huì)導(dǎo)致 tx_done、bps_cnt 會(huì)分別維持兩個(gè)時(shí)鐘周期的 1 和 11
// 因?yàn)楫?dāng) bps_cnt 變?yōu)?11 的時(shí)候,需要等第2個(gè)時(shí)鐘周期才會(huì)被采樣到。當(dāng)采樣到之后 tx_done = 1,而 clr 也立即變?yōu)?1 ,
// 而 clr 為 1 的時(shí)候也需要等第3個(gè)時(shí)鐘周期才能被 bps_cnt 采樣到變?yōu)?0
// 而 bps_cnt 為 0 時(shí),需要等到第4個(gè)時(shí)鐘周期才能被 tx_done 采樣,才會(huì)變?yōu)?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 這里采用直接賦值的方式來避免出現(xiàn)延遲一個(gè)時(shí)鐘的現(xiàn)象
assign tx_done = bps_cnt == 4'd11 ? 1'b1 : 1'b0;
assign clr = tx_done; // 當(dāng)完成一幀數(shù)據(jù)發(fā)送之后清除 bps 計(jì)數(shù)器
endmodule 修改后: 板級(jí)驗(yàn)證: 時(shí)間太晚,就不做板級(jí)實(shí)驗(yàn)了。
|