FPGA串口的完整设计包括波特率生成,单个数据的接收与发送,一帧数据的接收与发送,我把内容分散来进行
目录
一、波特率生成程序
1.1发送数据波特率生成
1.2接收数据波特率生成
二、单个数据发送程序
如上图,串口数据传输流程如下:
1、平时数据线处于“空闭”状态(1状态)。
2、当要发送或者接收数据时,UART改变TX、RX数据线的状态(变为0状态)并维持1位的时间,这样,接收方检测到开始位后,就开始一位一位地检测数据线的状态,得到所传输的数据。
3、UART一帧中可以有5、6、7或8位数据,发送方一位一位地改变数据线的状态,将它们发送出去,首先发送最低位。
4、如果使用奇偶校验功能,UART在发送完数据位后,还要发送1个校验位。有两种校验方法,即奇校验和偶校验(数据位连同校验位中“1”的数目分别等于奇数或偶数)。
5、发送停止位,数据线恢复到“空闭”状态(1状态)。停止位的长度有三种:1位、1.5位、2位。
一、波特率生成程序
1.1发送数据波特率生成
传输数据之前,UART之间要约定数据的传输速率(即每位所占时间,其倒数成为波特率)、数据的帧格式(即有多少个数据位、是否使用校验位、是奇校验还是偶校验、有几位停止位)。因此,我们首先需要确定波特率,根据波特率确定位传送时间宽度Td。
Td=波特率的倒数
波特率就是每秒钟传输的数据位数。
串口典型的传输波特率600bps,1200bps,2400bps,4800bps,9600bps,19200bps,38400bps,57600bps,115200bps。
在这里我们用115200bps的传输波特率:
Td=1115200
=8680×10^9 s = 8680ns因此用计数器构造一个8680ns的clk
PLL产生的clk为25MHZ,T=40ns
8680=40×217 217=8’b1101 1001 需要一个8位的计数器
module TX_baud(
input clk_25m,
input reset,
input TX_start,//开始发送数据标志位
output TX_baud_en//发送数据波特率使能
);
reg [7:0] TX_baud_cnt;
always@(posedge clk_25m)begin
if(reset)begin
TX_baud_en<=1'd0;
TX_baud_cnt<=8'd0;
end
else if(TX_start)begin
if(TX_baud_cnt==8'd216)begin
TX_baud_en<=1'd1;
TX_baud_cnt<=8'd0;
end
else begin
TX_baud_en<=1'd0;
TX_baud_cnt<=TX_baud_cnt+8'd1;
end
end
else begin
TX_baud_en<=1'd0;
TX_baud_cnt<=8'd0;
end
end
endmodule
1.2接收数据波特率生成
接收数据时,波特率需要和发送数据时的波特率一样,为了采集到稳定的数据,采集数据时应该采中间的数据,所以串口接收数据部分的波特率生成程序的计数器应该修改为发送波特率的一半
发送数据时计数器==8'd216时,输出一次波特率使能信号
接收数据时应该计数器==8'd108,输出一次波特率使能信号
但是接收数据的使能周期依然与发送数据相同,还是计数器==8'd216时
所以,我们在计数器==8'd216时清零计数器,计数器==8'd108,输出一次波特率使能信号
module RX_baud(
input clk_25m,
input reset,
input RX_start,//开始接收数据标志位
output RX_baud_en//发送接收波特率使能
);
reg [7:0] RX_baud_cnt;
always@(posedge clk_25m)begin
if(reset)begin
RX_baud_en<=1'd0;
RX_baud_cnt<=8'd0;
end
else if(RX_start)begin
if(RX_baud_cnt==8'd216)begin
RX_baud_en<=1'd0;
RX_baud_cnt<=8'd0;
end
else if(RX_baud_cnt==8'd108)begin
RX_baud_en<=1'd1;
RX_baud_cnt<=RX_baud_cnt+8'd1;
end
else begin
RX_baud_en<=1'd0;
RX_baud_cnt<=RX_baud_cnt+8'd1;
end
end
else begin
RX_baud_en<=1'd0;
RX_baud_cnt<=8'd0;
end
end
endmodule
这样,我们就生成了数据发送的波特率,这里需要特别注意的是,有一个开始发送数据、开始接收数据的标志位,也就是TX、RX数据线的状态1变为0
二、单个数据发送程序
数据发送程序输入的是将要传输的8位数据 [7:0] din,输出为8位数据合成的串行数据TX
我们定义开始位为0,结束位为1,此处不加校验位,加上8位数据,串行TX一共是10位
输入数据使能信号din_en,输出开始发送数据标志位TX_start给波特率生成程序
同时,需要设置寄存器: 记数据发送个数 (10个) tx_num;记发送所有位数据(10个)data_send
数据发送的步骤:
(1)产生开始发送的信号,当数据使能=1时开始发送,当数据个数=10时停止发送 (tx_num==4'd9&&baud_clk)当10个数据发送完成,且badu_clk为高电平,保证把停止位发送完了之后波特率停止
(2)计数数据个数,在开始发送之后,来一个baud时钟,计数一次
(3)把开始发送的信号打一拍产生Tx_start_dly
(4)开始发送数据,(din_en && Tx_start_dly == 1'b0)保证上个数据已经发送完成
构成data_send<={stop_bit,din,start_bit};
来一个baud时钟,移位一次,data_send<={1'd0,data_send[9:1]}
(5)把并行数据拼成串行TX
不传输时,TX高电平,发送时TX<=data_send[0];
module data_TX(
input clk_25m,
input reset,
input TX_baud_en,//发送数据波特率使能
input [7:0] din,//8位数据
input din_en,//数据使能
output reg TX_start,//开始发送数据标志位
output reg TX//串行数据
);
parameter start_bit=1'd0;//开始位=0
parameter stop_bit=1'd1;//结束位=1
reg [3:0] tx_num; //数据发送个数
reg [9:0] data_send; //组合起来的10个数据(开始+8位数据+结束)
/*产生开始发送数据标志位TX_start*/
always@(posedge clk_25m)begin
if(reset)begin
TX_start<=1'd0;
end
else if(din_en==1)begin//当开始发送数据时,允许发送信号
TX_start<=1'd1;
end
else if(tx_num==4'd9&&TX_baud_en)begin//当10个数据发送完成,且TX_baud_en为高电平,保证把停止位发送完了之后波特率停止
TX_start<=1'd0;
end
else begin
TX_start<=1'd0;
end
end
/*发送数据计数tx_num*/
always@(posedge clk_25m)begin
if(reset)begin
tx_num<=4'd0;
end
else if(TX_start)begin//当开始产生波特率时
if(TX_baud_en)begin
tx_num<=tx_num+4'd1; //一个baud时钟记一个数据
end
else begin
tx_num<=tx_num;
end
end
else begin
tx_num<=4'd0;
end
end
/* 给tx_start打一拍 */
reg TX_start_dly;
always@(posedge clk_25m)begin
if(reset)begin
TX_start_dly<=1'd0;
end
else begin
TX_start_dly<=TX_start;
end
end
/*输出data_send(发送数据)*/
always@(posedge clk_25m)begin
if(reset)begin
data_send<=10'd0;
end
else if(din_en&&TX_start_dly==0)begin//数据使能=1且tx_start_dly=0,保证上个数据已经发送完了
data_send<={stop_bit,din,start_bit};
end
else if(TX_baud_en)begin
data_send<={1'd0,data_send[9:1]};//向右移位,高位补0
end
else begin
data_send<=data_send;
end
end
/*合成串行数据*/
always@(posedge clk_25m)begin
if(reset)begin
TX=1'd1;//TX空闲的时候为高电平
end
else if(TX_start)begin
TX<=data_send[0];
end
else begin
TX=1'd1;
end
end
endmodule
这里需要特别注意的是,
(1)TX空闲的时候为高电平,不传输数据的时候,TX=1'd1
(2)data_send的组成是data_send<={stop_bit,din,start_bit},起始位在最低位
(3)data_send从低位开始发送数据,将已经发送的位直接清零:data_send<={1'd0,data_send[9:1]};
再编写一下仿真文件:
module TB_uart_top(
);
reg clk_25m=0;
reg reset;
reg [7:0]din;
reg din_en;
wire TX ;
uart_top inst_uart_top(
.clk_25m (clk_25m) ,
.reset (reset) ,
.din (din) ,
.din_en (din_en) ,
.TX (TX)
);
initial begin
clk_25m=0;
reset=1;
din_en=0;
din=8'h55;
#1000;
reset=0;
din_en=1;
#30;
din_en=0;
end
always #20 clk_25m=~clk_25m;
endmodule
对代码进行仿真 ,得到下图:
首先确认一下波特率是否正确,确实是8680ns,波特率正确
然后看输入输出, 输入为8'h55 即8'd0101_01010,输出TX为 1_0101_0101_0,即结束位_8'h55_起始位