分享

FPGA串口实现(带FIFO)

 识文广博 2016-01-23

看了CrazyBingo的书的前几章,开始写代码实践了。刚好自己手上有一个xilinx的开发板,上面有串口的资源。索性,就来实现这个串口的功能。实现串口的发送和接收。

串口的协议很简单,从网上找了个图说明下:

clip_image002

串口所用的协议时UART协议。UART协议是异步的通信协议。只用一根线完成数据的传输。协议的时序如上图所示:

在空闲状态,即没有数据传输时,线上电平保持为高电平。当开始有数据传输时,线上电平会拉低,表示开始传输数据。第一个数据位开始位,然后是数据位,数据位可以是8位,也可以使8位到4位,但是顺序是低位在前,高位在后。最后是一个高电平的停止位。这样就完成了一次数据传输。

以发送8位数据8’h28(二进制00101000)为例。

则线上的电平依次为 1->0->0->0->0->1->0->1->0->0->1。

由于是异步,没有时钟同步,所以通信的双方要约定好,传输每一位的时间,这个时间就是由波特率来决定的。

波特率是指传输每一位所用的时间,如果采用波特率256000.那么每一位传输的时间为:

1/256000 = 3.9025us。

通信是有发送和接收的,所以串口就有两根线,一根线发送数据,一根线接收数据。

以上就是串口的一些介绍,详细的介绍可以自行百度,可以了解到更多串口的细节。

以下实现一个全双工的串口接收和发送,采用FIFO对接受到的数据进行储存,然后接收外部发送信号,就将FIFO中的接收到的数据,通过串口发送。波特率为256000,数据为8位。

clip_image004

以上是结构图,画的有点挫。接收模块接收外部发送的串口数据,将数据储存到FIFO中,FIFO有外部使能信号(这里是按键按下),就将FIFO中的数据传给发送模块,发送模块在将数据发送出去。

因为是全双工模式,所以对于发送和接收各有一个波特率产生模块。用来定时数据每一位的时间。以满足串口协议的要求。

对于发送模块:

波特率程序:


module band_generate_tx
#(
parameter baud = 115200 //baud 9600-256000
)
(
input clk,
input rst_n,
input start, //start send data mode
input finish, //send data mode finish
output reg time_arr_tx //baud overflow
);
localparam cnt_16 = 15;
/*********************baud counter value calculate*********************/
reg [7:0] cnt_baud;
//calculate baud counter value
//counter value = 50_000_000 / baud /16 -1;
initial begin
case(baud)
9600 : cnt_baud = 328 -1;
14400 : cnt_baud = 217 -1;
19200 : cnt_baud = 163 -1;
28800 : cnt_baud = 109 -1;
38400 : cnt_baud = 81 -1;
56000 : cnt_baud = 56 -1;
57600 : cnt_baud = 54 -1;
115200: cnt_baud = 27 - 1;
128000: cnt_baud = 24 -1;
256000: cnt_baud = 12 -1;
default :cnt_baud = 12 -1;
endcase
end
/*********************baud counter value calculate*********************/
reg [3:0] count_16;
reg start_flag;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
start_flag <= 'd0;
else
begin
if(start)
start_flag <= 1;
else if(finish)
start_flag <= 0;
else
start_flag <= start_flag;
end
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
count_16 <= 0;
end
else
begin
if(start_flag)
begin
if(count_16 >= cnt_16)
begin
count_16 <= 0;
end
else
begin
count_16 <= count_16 + 1'b1;
end
end
end
end
reg [4:0] count_baud;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
count_baud <= 0;
time_arr_tx <= 0;
end
else
begin
if(start_flag)
begin
if(count_16 >= cnt_16)
begin
if(count_baud>=cnt_baud)
begin
count_baud <= 'd0;
end
else
count_baud <= count_baud + 1'b1;
if(count_baud == cnt_baud - 1'b1)
time_arr_tx <= 1;
else
time_arr_tx <= 0;
end
else
begin
time_arr_tx <= 0;
end
end
else
begin
count_baud <= 0;
time_arr_tx <= 0;
end
end
end
endmodule


该模块可以实现波特率从9600到256000.只需要例化模块的时候,改变波特率参数值即可。这里采用initial语句来计算产生不同的波特率需要的计数值。因为要产生串口协议波特率规定的定时时间,所以要有一个计数器,来产生这样的一个定时时间。而计数器计数是需要一个计数值的。这里采用initial来计算该计数值。

我们知道initial语句是不可综合的,但是在有些情况下,它又是可以综合的。在计算初始值的时候,是可以综合的。比如这里计算计数器的初始值。或者对寄存器的初始化。





分析一下上面这段程序。这段程序是实现波特率控制的,波特率产生不是什么时候都产生,是需要的时候才产生。在这里,就是要有发送数据的时候,才进行波特率产生。这里的start信号是外部给的串口发送数据信号,但是这样信号只会持续一个时钟周期,而后面的波特率产生是判断这个信号一直使能在进行波特率产生,那这里就有一个问题,怎么把这一个只持续一个时钟周期的信号给扩展为一直持续使能了。这个就是上面代码的功能,将一个只持续一个时钟周期的信号给扩展为一直持续使能。

当start信号有效的时候,start_flag为高,即使能,一个时钟周期后,start信号无效,但是start_flag保持使能。一旦发送数据完成后,finish信号有效到来,将start_flag无效,是关闭波特率产生。

那么问题来了,上述代码会对应怎样的一个数字电路,大家可以去想想。我想的结果是一个除法器加3个门。


波特率产生模块,主要是给发送模块提供一个波特率溢出信号,提示发送模块,该位数据发送完成,准备发下一位数据。

接下来是发送模块。采用状态机设计:


module uart_txd(
//global signal
input clk, //clk 50M
input rst_n, //reset signal, active-low
//input signal
input start, //start uart send mode
input time_arr, //baud overflow signal
input [7:0] data_in, //8-bits data to be send
//output signal
output reg finish, //send mode finish
output reg uart_txd //serial send data
);
//state
localparam idle_state = 4'd0;
localparam start_state = 4'd1;
localparam send_0_state = 4'd2;
localparam send_1_state = 4'd3;
localparam send_2_state = 4'd4;
localparam send_3_state = 4'd5;
localparam send_4_state = 4'd6;
localparam send_5_state = 4'd7;
localparam send_6_state = 4'd8;
localparam send_7_state = 4'd9;
localparam stop_state = 4'd10;
reg [3:0] state;
reg [3:0] state_next;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
state <= idle_state;
else
state <= state_next;
end
always@(*) begin
state_next = state;
finish = 0;
uart_txd = 0;
case(state)
idle_state: begin //idle state , uart_txd value is hign level.
uart_txd = 1;
if(start)
state_next = start_state;
end
start_state :
begin //send start bit. uart_txd value is 0
uart_txd = 0;
if(time_arr)
begin
state_next = send_0_state;
end
end
send_0_state :
begin
uart_txd = data_in[0];
if(time_arr)
begin
state_next = send_1_state;
end
end
send_1_state :
begin
uart_txd = data_in[1];
if(time_arr)
begin
state_next = send_2_state;
end
end
send_2_state :
begin
uart_txd = data_in[2];
if(time_arr)
begin
state_next = send_3_state;
end
end
send_3_state :
begin
uart_txd = data_in[3];
if(time_arr)
begin
state_next = send_4_state;
end
end
send_4_state :
begin
uart_txd = data_in[4];
if(time_arr)
begin
state_next = send_5_state;
end
end
send_5_state :
begin
uart_txd = data_in[5];
if(time_arr)
begin
state_next = send_6_state;
end
end
send_6_state :
begin
uart_txd = data_in[6];
if(time_arr)
begin
state_next = send_7_state;
end
end
send_7_state :
begin
uart_txd = data_in[7];
if(time_arr)
begin
state_next = stop_state;
end
end
stop_state :
begin
uart_txd = 1;
if(time_arr)
begin
state_next = idle_state;
finish = 1;
end
end
default: state_next = idle_state;
endcase
end
endmodule


代码也很简单,只要有发送使能信号,就启动状态机,发送数据,在每一个波特率溢出信号作用下,进行状态转移,以完成每一位数据的发送。这里,要注意,停止位的数据位一定要为1.这里为什么要为1,后面再解释。

接着,就用一个发送顶层模块将上述两个模块进行封装。


module uart_tx
#(
parameter baud = 256000
)
(
input clk,
input rst_n,
input start,
input [7:0] tx_data,
output uart_txd,
output finish
);
wire time_arr_tx;
band_generate_tx #(.baud(baud))
band_generate_tx_1
(
.clk(clk),
.rst_n(rst_n),
.start(start),
.finish(finish),
.time_arr_tx(time_arr_tx)
);
uart_txd uart_txd_1(
.clk(clk),
.rst_n(rst_n),
.start(start),
.time_arr(time_arr_tx),
.data_in(tx_data),
.finish(finish),
.uart_txd(uart_txd)
);
endmodule


封装的好处,是方面顶层的调用。

接着就是接受数据的模块:

首先是波特率产生:


module band_generate_rx
#(
parameter baud = 115200 //baud 9600-256000
)
(
input clk,
input rst_n,
input start,
input finish,
output reg time_arr_rx
);
localparam cnt_16 = 15;
/*********************baud counter value calculate*********************/
reg [7:0] cnt_baud;
//calculate baud counter value
//counter value = 50_000_000 / baud /16 -1;
initial begin
case(baud)
9600 : cnt_baud = 328 -1;
14400 : cnt_baud = 217 -1;
19200 : cnt_baud = 163 -1;
28800 : cnt_baud = 109 -1;
38400 : cnt_baud = 81 -1;
56000 : cnt_baud = 56 -1;
57600 : cnt_baud = 54 -1;
115200: cnt_baud = 27 - 1;
128000: cnt_baud = 24 -1;
256000: cnt_baud = 12 -1;
default :cnt_baud = 12 -1;
endcase
end
/*********************baud counter value calculate*********************/
reg [3:0] count_16;
reg start_flag;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
start_flag <= 'd0;
else
begin
if(start)
start_flag <= 1;
else if(finish)
start_flag <= 0;
else
start_flag <= start_flag;
end
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
count_16 <= 0;
end
else
begin
if(start_flag)
begin
if(count_16 >= cnt_16)
begin
count_16 <= 0;
end
else
begin
count_16 <= count_16 + 1'b1;
end
end
end
end
reg [4:0] count_baud;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
count_baud <= 0;
time_arr_rx <= 0;
end
else
begin
if(start_flag)
begin
if(count_16 >= cnt_16)
begin
if(count_baud>=cnt_baud)
begin
count_baud <= 'd0;
end
else
count_baud <= count_baud + 1'b1;
if(count_baud == cnt_baud/2 )
time_arr_rx <= 1;
else
time_arr_rx <= 0;
end
else
begin
time_arr_rx <= 0;
end
end
else
begin
count_baud <= 0;
time_arr_rx <= 0;
end
end
end
endmodule


采用和发送模块的波特率产生模块一样的结构,只是波特率的溢出信号时间不一样。发送的波特率溢出是在波特率时间的末尾,而接受的波特率溢出是在波特率时间的中间。因为在中间采集的数据才是正确有效的。

这里要明白一个东西,因为数据是一位一位传输的,所以数据改变的时间也是要注意的,不能在数据改变的时候接收数据,这样接受的数据就有可能不正确。数据变化是在波特率时间的末尾,所以接收数据要在波特率时间的中间。

下面是接收模块:


module uart_rxd(
//global signal
input clk, //clk signal .50M
input rst_n, //reset signal , active-low
//input signal
input time_arr, //baud overflow signal
input rxd, //rxd input data
output reg [7:0] rxd_data, //receive rxd 8-bits data
output start, //start receive mode
output reg finish //receive mode finish
);
/********************judge rxd falling edge*********************/
reg rxd_r ;
reg rxd_r_r ;
wire rxd_falling ;
always@( posedge clk ) begin
if( !rst_n )
begin
rxd_r <= 'b1 ;
rxd_r_r <= 'b1 ;
end
else
begin
rxd_r <= rxd ;
rxd_r_r <= rxd_r ;
end
end
assign rxd_falling = rxd_r_r & ( !rxd_r ) ;
/********************judge rxd falling edge*********************/
reg [3:0] i;
reg start_flag;
//receive 9-bits data. but the high 8-bits is real data. the last bit is start data
reg [8:0] rxd_data_reg;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
rxd_data_reg <= 'd0;
i <= 'd0;
end
else
begin
if(start_flag) //receive data
begin
if(time_arr ==1 )
begin
rxd_data_reg <= {rxd,rxd_data_reg[8:1]};
i <= i +4'd1;
end
end
else
begin
rxd_data_reg <= 'd0;
i <='d0;
end
end
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
start_flag <= 'b0;
rxd_data <= 'd0;
finish <= 'b0;
end
else
if(rxd_falling)
start_flag <= 1'b1;
else if(i >= 9 && start_flag == 1)
begin
start_flag <= 1'b0;
rxd_data <= rxd_data_reg[8:1];
finish <= 1'b1;
end
else
finish <= 1'b0;
end
//if rxd generate a falling edge,that indicate receive mode start
assign start = rxd_falling;
endmodule


这里没有采用状态机的方式设计,这里可以看出,采用状态机设计,代码便于理解和编写。

封装上述两个模块,成一个接收模块:


module uart_rx
#(
parameter baud = 256000
)
(
input clk,
input rst_n,
input uart_rxd,
output [7:0] receive_data,
output finish
);
wire time_arr_rx;
wire start;
band_generate_rx #(.baud(baud))
band_generate_rx_1 (
.clk(clk),
.rst_n(rst_n),
.start(start),
.finish(finish),
.time_arr_rx(time_arr_rx)
);
uart_rxd uart_rxd_1 (
.clk(clk),
.rst_n(rst_n),
.time_arr(time_arr_rx),
.rxd(uart_rxd),
.rxd_data(receive_data),
.start(start),
.finish(finish)
);
endmodule


然后在用一个串口顶层模块对上面发送和接受模块封装。


module uart_top
#(
parameter baud_tx = 256000,
parameter baud_rx = 256000
)
(
input clk,
input rst_n,
input uart_rxd, //input serial rxd data
input tx_start, //input start send data module signal
input [7:0] tx_data, //input 8-bits send data
output tx_finish, //output send mode finish
output rx_finish, //output receive mode finish
output uart_txd, //output serial txd data
output [7:0] receive_data //output receive 8-bits data
);
//receive module
uart_rx #(.baud(baud_rx))
uart_rx_1 (
.clk(clk),
.rst_n(rst_n),
.uart_rxd(uart_rxd),
.receive_data(receive_data),
.finish(rx_finish)
);
//send module
//when tx_start is 1, send module will send 8-bit input tx_data to the serial txd
uart_tx #(.baud(baud_tx))
uart_tx_1 (
.clk(clk),
.rst_n(rst_n),
.start(tx_start),
.tx_data(tx_data),
.uart_txd(uart_txd),
.finish(tx_finish)
);
endmodule


这样,就完成了整个的串口设计模块了。

剩下的就是FIFO模块了,这里是调用xilinx的FIFO IP核。直接拿来使用,位宽8位,深度4096.意思可以存储4096个外部发送的字节数据。


module test_uart_fifo(
input clk,
input rst_n,
input key,
input rx_finish,
input [7:0] rx_data,
input tx_finish,
//input start,
output reg tx_start,
output [7:0] tx_data
);
//wire start;
//wire full;
wire empty;
reg rd_en;
key_button key_button_1 (
.clk(clk),
.rst_n(rst_n),
.key(key),
.key_down(start)
);
fifo_uart fifo_uart_1 (
.clk(clk), // input clk
.rst(rst_n), // input rst
.din(rx_data), // input [7 : 0] din
.wr_en(rx_finish), // input wr_en
.rd_en(rd_en), // input rd_en
.dout(tx_data), // output [7 : 0] dout
.full(), // output full
.empty(empty) // output empty
);
localparam idle_state = 0;
localparam send_state = 1;
reg start_flag;
reg state;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
state <= idle_state;
tx_start <= 0;
rd_en <= 0;
end
else
begin
case(state)
idle_state:
begin
if(start_flag)
begin
tx_start <= 1;
state <= send_state;
rd_en <= 1;
end
else
begin
tx_start <= 0;
state <= idle_state;
rd_en <= 0;
end
end
send_state:
begin
tx_start <= 0;
rd_en <= 0;
if(tx_finish)
state <= idle_state;
end
endcase
end
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
start_flag <= 0;
else
begin
if(start)
start_flag <= 1;
else if(empty)
start_flag <= 0;
end
end
endmodule


其中key_button模块,是检测按键下降沿的。我这里是设定我按下按键,就将FIFO的所有数据发送出去。而fifo_uart模块,是例化FIFO的IP。

最终的顶层代码:


module test_uart(
input clk,
input rst_n,
input uart_rxd,
input key,
//input start,
output uart_txd,
output [7:0] LED
);
wire tx_start;
wire rx_finish;
wire tx_finish;
wire [7:0] tx_data;
wire [7:0] receive_data;
uart_top #(.baud_tx(256000),
.baud_rx(256000))
uart_top_1 (
.clk(clk),
.rst_n(rst_n),
.uart_rxd(uart_rxd),
.tx_start(tx_start),
.tx_data(tx_data),
.tx_finish(tx_finish),
.rx_finish(rx_finish),
.uart_txd(uart_txd),
.receive_data(receive_data)
);
test_uart_fifo test_uart_fifo_1 (
.clk(clk),
.rst_n(rst_n),
.key(key),
//.start(start),
.rx_finish(rx_finish),
.rx_data(receive_data),
.tx_finish(tx_finish),
.tx_start(tx_start),
.tx_data(tx_data)
);
assign LED = ~receive_data;
endmodule


代码也很简单,就将两个模块连接起来。

综合,分配管脚,然后布局布线,最后下载。使用串口猎人,用来发送数据和接收数据。

串口测试成功了。

最终效果如下,很酷吧。。发送的数据是Crazybingo写的书的自序的一部分。

clip_image006

发送数据后,按下开发板按键,就将发送的数据发回。使用串口猎人捕获,即可得到数据。

在实现这个功能时候,有遇到一下问题:

发送单个数据,接收单个数据正确,但是发送一串数据,接收就只能接受到最后一个数据,而不是发送的一串数据。

这个问题,我可折腾了好久。最开始以为是FIFO没有正常工作,写testbench仿真,发现还真的是有这个问题。FIFO的复位信号弄反了。这个系统是设定的低电平复位,而FIFO设定的高电平复位,所以接收数据不对。将复位信号更正后,发现还是有问题。在仿真FIFO,发现FIFO是正常工作的。那出现的问题,肯定就是我的串口模块的问题。

各种写testbench代码仿真,仿真后,发现,接收模块是没有问题的,那问题就应该是发送模块的问题。通过仿真,发现发送的时序是对的,确实将每个数据按照规定的时序发送出去。但是发现发完一个数据后,只隔了一个系统时钟周期,就马上发送第二个数据。这里,就猜想,是不是数据发送太快,数据发送完,要在等一个波特率时间在发送下一个数据。百度下,发现,原来,发送的停止位的数据是要为1的。而我写程序的时候,以为停止位是0或1都没有关系,就给了个0.将这里改正,发现,程序对了。能实现发一串数据,然后FPGA在回发一串数据了。

所以要注意,发送的时候,停止位的数据一定要为高电平!!!!!!!!!!!!!!!!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多