FPGA-UART接口實現
前言
UART接口協議是一種比較簡單、非常常用的一種接口協議,使用它的場景很常見,是我們學習FPGA一定要會的接口協議;
UART協議
通用異步收發器(Universal Asynchronous Receiver/Transmitter),通常稱作UART,是一種串行、異步、全雙工的通信協議,在嵌入式領域應用的非常廣泛。其數據通信格式如下圖:
LSB:
least significant bit 表示二進制數據的最低位。
MSB :
most significant bit 表示二進制數據的最高位。
起始位:
每開始一次通信時發送方先發出一個邏輯”0”的信號(低電平),表示傳輸字符的開始。因為總線空閑時為高電平所以開始一次通信時先發送一個明顯區別於空閑狀態的信號即低電平。
數據位:
起始位之后就是我們所要傳輸的數據,數據位可以是5、6、7、8,9位等,構成一個字符(一般都是8位)。先發送最低位,最后發送最高位,使用低電平表示‘0’高電平表示‘1’完成數據位的傳輸。
奇偶校驗位:
數據位加上這一位后,使得“1”的位數應為偶數(偶校驗)或奇數(奇校驗),以此來校驗數據傳送的正確性。
停止位:
它是一個字符數據的結束標志。可以是1位、1.5位、2位的高電平。 由於數據是在傳輸線上定時的,並且每一個設備有其自己的時鍾,很可能在通信中兩台設備之間出現了小小的不同步。因此停止位不僅僅是表示傳輸的結束,並且提供計算機校正時鍾的機會。停止位個數越多,數據傳輸越穩定,但是數據傳輸速度也越慢。
空閑位:
UART協議規定,當總線處於空閑狀態時信號線的狀態為‘1’即高電平,表示當前線路上沒有數據傳輸。
UART協議實現(verilog)
為了更加簡單的描述串口協議,我們這里使用常用的1位起始位,1位停止位,8位數據位,無奇偶校驗位,波特率9600(本次波特率以參數形式提供,如需測試其他波特率,計算后更改即可)。
UART發送端代碼如下
module uart_rx
#(
parameter max_baud = 5208,
parameter max_samp = 2604
)
(
input wire sclk ,
input wire rst_n ,
input wire rx ,
output reg rx_data_en ,
output reg[7:0] rx_data
);
reg rx1 ;//延時一個時鍾
reg rx2 ;//延時兩個時鍾
reg rx_reg ;//延時三個時鍾
reg rx_en ;//計數標志
reg[12:0] cnt_baud ;//波特率
reg bit_flag ;
reg[3:0] bit_cnt ;
reg rx_data_en_tmp ;
reg[7:0] rx_data_tmp ;
//消除亞穩態
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx1 <= 0;
else
rx1 <= rx;
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx2 <= 0;
else
rx2 <= rx1;
//邊沿檢測
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_reg <= 0;
else
rx_reg <= rx2;
//產生計數標志
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_en <= 0;
else if(rx2 == 0 && rx_reg == 1)
rx_en <= 1;
else if(bit_cnt==9)
rx_en <= 0;
//波特率產生
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
cnt_baud <= 0;
else if(cnt_baud==max_baud)
cnt_baud <= 0;
else if(rx_en==0)
cnt_baud <= 0;
else if(rx_en==1)
cnt_baud <= cnt_baud + 1'b1;
//采樣產生bit標志
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
bit_flag <= 0;
else if(cnt_baud==max_samp)
bit_flag <= 1;
else
bit_flag <= 0;
//bit計數
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
bit_cnt <= 0;
else if(bit_cnt==9)
bit_cnt <= 0;
else if(bit_flag==1)
bit_cnt <= bit_cnt + 1'b1;
//接收數據
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data_tmp <= 0;
else if(bit_flag==1 && bit_cnt!=0)
rx_data_tmp <= {rx_reg,rx_data_tmp[7:1]}; //右移
//傳輸完標志
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data_en_tmp <= 0;
else if(bit_cnt==8 && bit_flag==1)
rx_data_en_tmp <= 1;
else
rx_data_en_tmp <= 0;
//接收數據輸出
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data <= 0;
else if(rx_data_en_tmp==1)
rx_data <= rx_data_tmp;
//接收數據標志輸出
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data_en <= 0;
else
rx_data_en <= rx_data_en_tmp;
endmodule
UART接收端代碼如下
module uart_rx
#(
parameter max_baud = 5208,
parameter max_samp = 2604
)
(
input wire sclk , //50MHZ
input wire rst_n ,
input wire rx ,
output reg rx_data_en ,
output reg[7:0] rx_data
);
reg rx1 ;//延時一個時鍾
reg rx2 ;//延時兩個時鍾
reg rx_reg ;//延時三個時鍾
reg rx_en ;//計數標志
reg[12:0] cnt_baud ;//波特率
reg bit_flag ;
reg[3:0] bit_cnt ;
reg rx_data_en_tmp ;
reg[7:0] rx_data_tmp ;
//消除亞穩態
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx1 <= 0;
else
rx1 <= rx;
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx2 <= 0;
else
rx2 <= rx1;
//邊沿檢測
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_reg <= 0;
else
rx_reg <= rx2;
//產生計數標志
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_en <= 0;
else if(rx2 == 0 && rx_reg == 1)
rx_en <= 1;
else if(bit_cnt==9)
rx_en <= 0;
//波特率產生
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
cnt_baud <= 0;
else if(cnt_baud==max_baud)
cnt_baud <= 0;
else if(rx_en==0)
cnt_baud <= 0;
else if(rx_en==1)
cnt_baud <= cnt_baud + 1'b1;
//采樣產生bit標志
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
bit_flag <= 0;
else if(cnt_baud==max_samp)
bit_flag <= 1;
else
bit_flag <= 0;
//bit計數
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
bit_cnt <= 0;
else if(bit_cnt==9)
bit_cnt <= 0;
else if(bit_flag==1)
bit_cnt <= bit_cnt + 1'b1;
//接收數據
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data_tmp <= 0;
else if(bit_flag==1 && bit_cnt!=0)
rx_data_tmp <= {rx_reg,rx_data_tmp[7:1]}; //右移
//傳輸完標志
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data_en_tmp <= 0;
else if(bit_cnt==8 && bit_flag==1)
rx_data_en_tmp <= 1;
else
rx_data_en_tmp <= 0;
//接收數據輸出
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data <= 0;
else if(rx_data_en_tmp==1)
rx_data <= rx_data_tmp;
//接收數據標志輸出
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data_en <= 0;
else
rx_data_en <= rx_data_en_tmp;
endmodule
再寫一個頂層文件將發送端和接收端例化(當然,使用時也可以直接使用發送代碼和接收代碼),代碼如下
module uart_top(
input wire sclk ,//50MHZ
input wire rst_n ,
input wire rx_data ,
output wire tx_data
);
wire rx_flag ;
wire[7:0] data_net ;
uart_rx
#(
.max_baud (5208-1 ), //波特率 50M/9600 ~5208
.max_samp (2604-1 ) //在中間采樣
)
uart_rx_inst(
.sclk (sclk ),
.rst_n (rst_n ),
.rx (rx_data ),
.rx_data_en (rx_flag ),
.rx_data (data_net )
);
uart_tx
#(
.max_baud (5208-1 ), //波特率 50M/9600 ~5208
.max_samp (2604-1 ) //在中間采樣
)
uart_tx_inst(
.sclk (sclk ), //50MHZ
.rst_n (rst_n ),
.tx_data_en (rx_flag ),
.tx_data (data_net ),
.uart_tx (tx_data )
);
endmodule
這里實現UART的方式是通過計數器的方式,當然也可以通過其他的方式實現(比如狀態機等),主要還是要理解UART協議,再理解的基礎上,實現起來就會比較輕松。
仿真
給對應top寫一個testbench(仿真激勵),如下:
`timescale 1ns/1ns
module tb_uart_top() ;
reg sclk ;
reg rst_n ;
reg rx_data ;
wire tx_data ;
initial
begin
sclk = 0 ;
rst_n <= 0 ;
#20
rst_n <= 1 ;
end
always #5 sclk = ~sclk ; //50MHZ
initial
begin
rx_data <= 1 ;
#200
rx_byte() ;
end
// defparam uart_top_inst.uart_rx_inst.max_baud = 51;
// defparam uart_top_inst.uart_rx_inst.max_samp = 25;
// defparam uart_top_inst.uart_tx_inst.max_baud = 51;
// defparam uart_top_inst.uart_tx_inst.max_samp = 25;
task rx_bit(
input [7:0] data
);
integer i;
for(i=0;i<10;i=i+1)
begin
case(i)
0:rx_data <= 0 ;
1:rx_data <= data[0] ;
2:rx_data <= data[1] ;
3:rx_data <= data[2] ;
4:rx_data <= data[3] ;
5:rx_data <= data[4] ;
6:rx_data <= data[5] ;
7:rx_data <= data[6] ;
8:rx_data <= data[7] ;
9:rx_data <= 1'b1 ;
endcase
#53000;
end
endtask
task rx_byte();
integer j;
for(j=0;j<24;j=j+1)
rx_bit(j);
endtask
uart_top uart_top_inst(
.sclk (sclk ),
.rst_n (rst_n ),
.rx_data (rx_data ),
.tx_data (tx_data )
);
endmodule
這里通過task發送0-23到UART接收端,接收端接收到數據再通過發送端發送出去。
截取其中仿真波形如下:

以發送23(8‘h17)為例,從圖上可以看出接收的二進制數為8‘b11101000,因為在仿真時(testbench)我們先發送的低位,所以需要將接收的數據倒置,即8’b00010111=8’h17,轉化為十進制即為23。
仿真結果正確。
實際上本段代碼可以直接拿來使用,下板測試通過。
若有相關問題可以互相討論
