更新一篇博客吧,寫一寫關於這周自己對串口通信的理解。也會分享一下自己寫的代碼,
先簡單的介紹一下一些概念:
並行通信是指數據的各個位用多條數據線同時進行傳輸
串行通信是將數據分成一位一位的形式在一條傳輸線上逐個傳輸
同步通信:帶時鐘同步信號的數據傳輸;發送方和接收方在同一時鐘的控制下,同步傳輸數據。
異步通信:不帶時鐘同步信號的數據傳輸。發送方與接收方使用各自的時鐘控制數據的發送和接收過程。
單工 :數據只能沿一個方向傳輸
半雙工:數據傳輸可以沿兩個方向,但需要分時進行
全雙工:數據可以同時進行雙向傳輸
下面是串口通信的過程:
串口通信是全雙工,有Tx,Rx兩條線。Tx是發送,Rx是接收。
波特率:每秒鐘傳送數據位的數目來表示,9600,就是1秒,可以傳送9600bit個數據(0,1)
首先先介紹一下FPGA接收電腦串口助手發過來的數據過程,
首先,如果電腦還沒有發數據之前,Rx這條線上一直是高電平,處於空閒狀態,如果一旦電腦發數據過來, 這條線就會拉低,持續Period時間,這個Period時間就是一位數據需要的時間,這個時間計算:
例如:如果系統時鐘頻率clk=50000000hz,波特率baud=9600 ,Period=(50000000/9600)*20ns;
我們檢測Rx如果發生下降沿,我們就開始用一個計數器進行計時,如果記到Period這個時間到了,我們進入下個狀態,準備接收第1bit數據,如果電腦發過來的數據是1。1的ASCII碼就是:49,49對應的二進制爲:0011 0001,電腦會在Rx這條線上,先發低位數據,然後是高位數據,所以就是發過來的數據就是:1000_1100。然後FPGA接收數據時,最好在每位中間時刻去保存數據,這個時候的數據比較穩定。按照這樣一共接收八位數據。最後一位就是停止位。電腦會發過來1bit的停止位,爲高電平。我們一定要在1bit數據之前結束接收狀態。(給一個建議,在一半period那個時刻,就結束)。
//串口助手設置:波特率9600 無奇偶校驗位,8位數據位,一個停止位
//time:2019.11.13.22.41
module uart_rx(
input clk ,
input rst_n ,
input uart_rx,
output reg uart_rx_done,
output reg [7:0] data
);
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 9600; //波特率
localparam PERIOD = CLK_FREQ/UART_BPS;
//接收的數據
reg [7:0] rx_data;
reg rx1,rx2;
wire start_bit;
reg start_flag;
reg [15:0] cnt0;
wire add_cnt0;
wire end_cnt0;
reg [3:0] cnt1;
wire add_cnt1;
wire end_cnt1;
//下降沿檢測
assign start_bit=(rx2)&(~rx1);
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx1<=1'b0;
rx2<=1'b0;
end
else begin
rx1<=uart_rx;
rx2<=rx1;
end
end
//開始標誌位
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
start_flag<=0;
end
else if(start_bit) begin
start_flag<=1;
end
else if(end_cnt1) begin
start_flag<=0;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(end_cnt1) begin
cnt0 <= 0;
end
else if(end_cnt0) begin
cnt0 <= 0;
end
else if(add_cnt0)begin
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = start_flag;
assign end_cnt0 = add_cnt0 && cnt0==PERIOD-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(end_cnt1) begin
cnt1 <= 0;
end
else if(add_cnt1)begin
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = end_cnt0 ;
assign end_cnt1 = (cnt0==((PERIOD-1)/2))&& (cnt1==10-1) ;
//數據接收
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_data<=8'd0;
end
else if(start_flag) begin
if(cnt0== PERIOD/2)begin
case(cnt1)
4'd1:rx_data[0]<=rx2;
4'd2:rx_data[1]<=rx2;
4'd3:rx_data[2]<=rx2;
4'd4:rx_data[3]<=rx2;
4'd5:rx_data[4]<=rx2;
4'd6:rx_data[5]<=rx2;
4'd7:rx_data[6]<=rx2;
4'd8:rx_data[7]<=rx2;
default:rx_data<=rx_data;
endcase
end
else begin
rx_data<=rx_data;
end
end
else begin
rx_data<=8'd0;
end
end
//數據接收
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data<=0;
end
else if(end_cnt1)begin
data<=rx_data;
end
end
//接收完成標誌
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
uart_rx_done<=0;
end
else if(end_cnt1)begin
uart_rx_done<=1;
end
else begin
uart_rx_done<=0;
end
end
endmodule
發送代碼:
//串口助手設置:波特率9600 無奇偶校驗位,8位數據位,一個停止位
//time:2019.11.13.22.41
module uart_tx(
input clk ,
input rst_n ,
output reg uart_tx,
input [7:0] data,
input tx_start
);
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 9600; //波特率
localparam PERIOD = CLK_FREQ/UART_BPS;
reg [7:0] tx_data; //發送的數據
reg start_tx_flag; //發送數據標誌位
//記算一位數據需要多長時間PERIOD
reg [15:0] cnt0;
wire add_cnt0;
wire end_cnt0;
//發送幾個數據
reg [3:0] cnt1;
wire add_cnt1;
wire end_cnt1;
//發送標誌位
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
start_tx_flag<=0;
tx_data<=0;
end
else if(tx_start) begin
start_tx_flag<=1;
tx_data<=data; //把發送的數據存到這裏來
end
else if(end_cnt1) begin
start_tx_flag<=0;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(end_cnt1) begin
cnt0 <= 0;
end
else if(end_cnt0) begin
cnt0 <= 0;
end
else if(add_cnt0)begin
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = start_tx_flag;
assign end_cnt0 = add_cnt0 && cnt0==PERIOD-1; //一位時間
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(end_cnt1) begin
cnt1 <= 0;
end
else if(add_cnt1)begin
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = end_cnt0 ;
assign end_cnt1 = (cnt0==((PERIOD-1)/2))&& (cnt1==10-1); //發送10位,包括停止位,空閒位
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
uart_tx<=1; //空閒狀態
end
else if(start_tx_flag) begin
if(cnt0==0)begin
case(cnt1)
4'd0:uart_tx<=0; //起始位
4'd1:uart_tx<=tx_data[0];
4'd2:uart_tx<=tx_data[1];
4'd3:uart_tx<=tx_data[2];
4'd4:uart_tx<=tx_data[3];
4'd5:uart_tx<=tx_data[4];
4'd6:uart_tx<=tx_data[5];
4'd7:uart_tx<=tx_data[6];
4'd8:uart_tx<=tx_data[7];
4'd9:uart_tx<=1; //停止位
default:;
endcase
end
end
end
endmodule
頂層模塊:
//注意:
//串口助手設置:波特率9600 無奇偶校驗位,8位數據位,一個停止位
//time:2019.11.13.22.41
module uart(
input clk ,
input rst_n ,
input uart_rx ,
output uart_tx ,
output wire [7:0] seg_sel,
output wire [7:0] segment
);
wire [7:0] data;
wire uart_rx_done;
//數碼管
seg_num seg_disp_uut(
.clk (clk),
.rst_n (rst_n),
.seg_sel (seg_sel),
.segment (segment),
.data (data)
);
//FPGA接收串口助手發來的數據
uart_rx uart_rx_uut(
.clk (clk),
.rst_n (rst_n),
.uart_rx (uart_rx),
.uart_rx_done(uart_rx_done),
.data (data)
);
//FPGA把接收的數據發送到串口助手上
uart_tx uart_tx_uut(
.clk (clk),
.rst_n (rst_n),
.uart_tx (uart_tx),
.data (data),
.tx_start(uart_rx_done)
);
endmodule