FPGA串口的完整设计包括波特率生成,单个数据的接收与发送,一帧数据的接收与发送,我把内容分散来进行

目录

一、波特率生成程序

1.1发送数据波特率生成

1.2接收数据波特率生成

二、单个数据发送程序


sofaRPC通讯使用 fpba01通讯说明_fpga开发

如上图,串口数据传输流程如下:

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       

sofaRPC通讯使用 fpba01通讯说明_sofaRPC通讯使用_02

=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时

sofaRPC通讯使用 fpba01通讯说明_数据_03

 所以,我们在计数器==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

对代码进行仿真 ,得到下图:

sofaRPC通讯使用 fpba01通讯说明_sofaRPC通讯使用_04

首先确认一下波特率是否正确,确实是8680ns,波特率正确

sofaRPC通讯使用 fpba01通讯说明_数据_05

然后看输入输出, 输入为8'h55 即8'd0101_01010,输出TX为 1_0101_0101_0,即结束位_8'h55_起始位

sofaRPC通讯使用 fpba01通讯说明_数据_06