FPGA UART发送与接收

背景

最近刚开始接触FPGA,在大概看了基本教材学习了Verilog后就买了一块黑金的开发板,开始正儿八经的撸代码。然后就到了UART通信这一节,基本上例程都是顶层模块例化UART收发模块,作者觉得这样不能很好的理解UART的底层协议,UART发送具体是怎么发送,又怎么接收,也不想直接ctrl+ch和ctrl+v,然后作者在参考正点原子的代码后,通过状态机实现500ms发送一个字节的数据和UART接收,具体请看下文,还有请正点原子支付下广告费。

协议

如下图所示,UART通信需要三根线,一根发送TX,一根接收RX,还有一根地。总线(TX和RX)空闲时处于高电平,发送方发送数据时,先发送起始位(逻辑0),然后以地为在前、高位在后的顺序将一个字节的每一个比特位发送出去(这一字节可以是7位或者8/9位),数据发送完成后,再发送1位校验位和停止位(可以是1位或者2位,逻辑1)。接收方的RX是与发送方的TX连接在一起的,因此,接收方检测RX上是否存在下降沿,是,则准备开始接收,因为UART通信没有时钟,因此只能规定多少时间发送一个比特位来保证数据收发不会出错,这就是比特率(或者说是波特率),单位是比特每秒(bit/s),一般情况下 ,比特率使用9600和115200,。本次作者的UART通信是采用8位数据位,1位停止位,没有奇偶检验位,共10位。
FPGA UART发送与接收_第1张图片

代码编写

协议清楚后开始撸代码。

UART发送

作者的思路是用状态机进行数据发送,初始化、发送、结束,发送周期是500ms,波特率是9600,系统频率是50MHz,所以需要对时钟脉冲计数50_000_000/9600=5208.33个,取整5208。下面是代码。

`timescale 1ns/1ps

module uart_tx(
			input    		clk,
			input			rst_n,
			
			output	reg		tx_pin);

parameter   	CLK_FREQ		=50000000,
				BPS				=9600,
				BPS_CNT			=CLK_FREQ/BPS;
				
parameter 		IDLE  =4'd0,
				SEND  =4'd1,
				END   =4'd2;
				
reg[7:0]		data_buf;
reg[24:0]		cnt1;//500ms 计数寄存器
reg				tx_start;//计时到了置1,并开始发送
//计时500ms
always@(posedge clk,negedge rst_n)
if(!rst_n)
  begin
    cnt1<=0;  
	tx_start<=0;
  end
else  if(cnt1==25000000)
  begin 
    cnt1<=0;
	tx_start<=~tx_start;
  end
 else
	cnt1<=cnt1+1;
	
	
reg[3:0]	tx_cnt;
reg[24:0]	clk_cnt;
reg[3:0]	state;
always@(posedge clk,negedge rst_n)
if(!rst_n)
  begin
    state<=IDLE;
	data_buf<=8'h5A;//发送的数据
  end
 else 
   case(state)
     IDLE: begin
	    tx_cnt<=0;
		clk_cnt<=0;
		if(tx_start==1)//500ms发送一次数据
		   state<=SEND;
	  end
	 
	 SEND:begin
	    if(tx_cnt==4'd9&&clk_cnt==BPS_CNT/2)
		  begin
		    tx_cnt<=10;
			//data_buf<=0;
			state<=END;
		  end
		else if(clk_cnt==BPS_CNT-1)
		  begin
		    clk_cnt<=0;
			tx_cnt<=tx_cnt+1;
		  end
	    else
		  begin
		    clk_cnt<=clk_cnt+1;
			tx_cnt<=tx_cnt;
		  end
      end
	 
	 END:begin
	   if(tx_start==1)
	     begin
		   //tx_pin<=1'b1;
		   state<=END;
		 end
	else
	   state<=IDLE;
	 end
	 
	default:state<=IDLE;
  endcase
  
always@(posedge clk,negedge rst_n)
  if(!rst_n)
    tx_pin<=1'b1;
  else if(tx_start==1)
    case(tx_cnt)
	  4'd0:tx_pin<=1'b0;
	  4'd1:tx_pin<=data_buf[0];
      4'd2:tx_pin<=data_buf[1];
	  4'd3:tx_pin<=data_buf[2];
	  4'd4:tx_pin<=data_buf[3];
	  4'd5:tx_pin<=data_buf[4];
	  4'd6:tx_pin<=data_buf[5];
	  4'd7:tx_pin<=data_buf[6];
	  4'd8:tx_pin<=data_buf[7];
	  4'd9:tx_pin<=1'b1;
	default:tx_pin<=1'b1;
	endcase
	
	

				
endmodule 

UART接收

当前作者的思路是检测RX上的降沿,检测到则将将接收标志位置1,因为总线上有数据那么就存在高低电平,然后根据波特率对时钟计数,最后缓存。

module uart_rx(
    input			  clk,                  //系统时钟
    input             rst_n,                //系统复位,低电平有效
    
    input             uart_rxd,                 //UART接收端口
    output  reg       uart_done,                //接收一帧数据完成标志信号
    output  reg [7:0] uart_data                 //接收的数据
    );
    
//parameter define
parameter  CLK_FREQ = 50000000;                 //系统时钟频率
parameter  BPS      = 9600;                     //串口波特率
parameter  BPS_CNT  = CLK_FREQ/BPS;        //为得到指定波特率,
                                                //需要对系统时钟计数BPS_CNT次
//reg define
reg [1:0]  rxcheck;
reg [15:0] clk_cnt;                             //系统时钟计数器
reg [ 3:0] rx_cnt;                              //接收数据计数器
reg        rx_flag;                             //接收过程标志信号
reg [ 7:0] rxdata;                              //接收数据寄存器

//wire define
wire       start_flag;


//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
assign  start_flag = rxcheck[1]  & (~rxcheck[0] );    
always @(posedge clk or negedge rst_n) begin 
    if (!rst_n) begin 
        rxcheck<=0;         
    end
    else begin
        rxcheck[0]  <= uart_rxd;                   
        rxcheck[1]   <= rxcheck[0] ;
    end   
end

//当脉冲信号start_flag到达时,进入接收过程           
always @(posedge clk or negedge rst_n) begin         
    if (!rst_n)                                  
        rx_flag <= 1'b0;
    else begin
        if(start_flag)                          //检测到起始位
            rx_flag <= 1'b1;                    //进入接收过程,标志位rx_flag拉高
        else if((rx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
            rx_flag <= 1'b0;                    //计数到停止位中间时,停止接收过程
        else
            rx_flag <= rx_flag;
    end
end

//进入接收过程后,启动系统时钟计数器与接收数据计数器
always @(posedge clk or negedge rst_n) begin         
    if (!rst_n) begin                             
        clk_cnt <= 16'd0;                                  
        rx_cnt  <= 4'd0;
    end                                                      
    else if ( rx_flag ) begin                   //处于接收过程
            if (clk_cnt < BPS_CNT - 1) begin
                clk_cnt <= clk_cnt + 1'b1;
                rx_cnt  <= rx_cnt;
            end
            else begin
                clk_cnt <= 16'd0;             //对系统时钟计数达一个波特率周期后清零
                rx_cnt  <= rx_cnt + 1'b1;       //此时接收数据计数器加1
            end
        end
        else begin                              //接收过程结束,计数器清零
            clk_cnt <= 16'd0;
            rx_cnt  <= 4'd0;
        end
end

//根据接收数据计数器来寄存uart接收端口数据
always @(posedge clk or negedge rst_n) begin 
    if ( !rst_n)  
        rxdata <= 8'd0;                                     
    else if(rx_flag)                            //系统处于接收过程
        if (clk_cnt == BPS_CNT/2) begin         //判断系统时钟计数器计数到数据位中间
            case ( rx_cnt )
             4'd1 : rxdata[0] <= uart_rxd_d1;   //寄存数据位最低位
             4'd2 : rxdata[1] <= uart_rxd_d1;
             4'd3 : rxdata[2] <= uart_rxd_d1;
             4'd4 : rxdata[3] <= uart_rxd_d1;
             4'd5 : rxdata[4] <= uart_rxd_d1;
             4'd6 : rxdata[5] <= uart_rxd_d1;
             4'd7 : rxdata[6] <= uart_rxd_d1;
             4'd8 : rxdata[7] <= uart_rxd_d1;   //寄存数据位最高位
             default:;                                    
            endcase
        end
        else 
            rxdata <= rxdata;
    else
        rxdata <= 8'd0;
end

//数据接收完毕后给出标志信号并寄存输出接收到的数据
always @(posedge clk or negedge rst_n) begin        
    if (!rst_n) begin
        uart_data <= 8'd0;                               
        uart_done <= 1'b0;
    end
    else if(rx_cnt == 4'd9) begin               //接收数据计数器计数到停止位时           
        uart_data <= rxdata;                    //寄存输出接收到的数据
        uart_done <= 1'b1;                      //并将接收完成标志位拉高
    end
    else begin
        uart_data <= 8'd0;                                   
        uart_done <= 1'b0; 
    end    
end

endmodule

你可能感兴趣的:(FPGA UART发送与接收)