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位。
在这里插入图片描述

代码编写

协议清楚后开始撸代码。

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实现发送接收功能,可按以下步骤进行: ### 了解UART协议 串口UART)全称通用异步收发传输器,主要用于数据间的串行传递,是一种全双工传输模式。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据[^3]。 ### 设计工程文件 包含接收程序(如uart_rx)、发送程序(如uart_tx)以及顶层程序(如uart_top),并进行相应的仿真验证,如tb_uart_rx、tb_uart_tx、tb_uart_top [^2]。 ### 考虑关键点 - **UART串口通信格式**:明确数据传输的格式,如起始位、数据位、停止位等。 - **波特率**:确定数据传输的速率,确保发送端和接收端波特率一致 [^2]。 ### 程序设计 - **时序图**:设计接收时序图和发送时序图,确保数据在正确的时间点进行发送接收 [^2]。 - **原理图**:包括系统框图和RTL原理图,清晰展示系统架构和逻辑关系 [^2]。 - **程序编写**:使用Verilog HDL语言编写发送接收程序。以下是一个简单的Verilog示例代码框架: ```verilog // 发送模块示例 module uart_tx ( input wire clk, input wire rst_n, input wire [7:0] data_in, input wire send_en, output reg tx_out, output reg send_done ); // 这里添加发送逻辑代码 endmodule // 接收模块示例 module uart_rx ( input wire clk, input wire rst_n, input wire rx_in, output reg [7:0] data_out, output reg recv_done ); // 这里添加接收逻辑代码 endmodule // 顶层模块示例 module uart_top ( input wire clk, input wire rst_n, input wire [7:0] data_in, input wire send_en, input wire rx_in, output wire tx_out, output reg [7:0] data_out, output reg send_done, output reg recv_done ); // 实例化发送接收模块 uart_tx uart_tx_inst ( .clk(clk), .rst_n(rst_n), .data_in(data_in), .send_en(send_en), .tx_out(tx_out), .send_done(send_done) ); uart_rx uart_rx_inst ( .clk(clk), .rst_n(rst_n), .rx_in(rx_in), .data_out(data_out), .recv_done(recv_done) ); endmodule ``` ### 下载验证 - **管脚约束和时序约束**:对FPGA的引脚进行约束,确保信号连接正确,并进行时序约束以保证系统稳定运行 [^2]。 - **下载bit文件验证**:将生成的bit文件下载到FPGA开发板上,进行实际的发送接收测试 [^2]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值