FPGA之UART串口实验

16 篇文章 1 订阅
本文介绍了UART串口通信的基本概念,包括同步和异步串行通信的区别,以及UART的工作原理和帧结构。实验任务是通过上位机与FPGA之间的串口数据环回传输。硬件设计中,采用USB转TTL方案简化接口,FPGA内部包含发送和接收模块。程序设计部分展示了UART接收和发送模块的VHDL代码,以及仿真结果和调试过程。
摘要由CSDN通过智能技术生成

一.UART串口简介

  • 串口通信是将数据字节分成一位一位的形式在一条数据线上逐个传送,主要特点时通信线路简单,但传输速度较慢。
  • 串口通信分类:
    • 同步串行通信:需要通信双方在同一时钟的控制下,同步传输数据;
    • 异步串行通信:指通信双方使用各自的时钟控制数据的发送和接收过程。
  • UART:是一种采用异步串行通信方式的通用异步收发传输器,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
  • UART在发送或接收过程中的一帧数据由4部分组成, 起始位数据位奇偶校验位停止位。
    • 起始位标志着一帧数据的开始
    • 停止位标志着一帧数据的结束
    • 数据位是一帧数据中的有效数据
    • 校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错(检查1的个数是奇数还是偶数)                                                                        异步串行通信数据格
  • 串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps(位/秒),常用的波特率有9600、19200、38400、57600、115200.
  • 电信号的传输过程有着不同的电平标准和接口规范,针对异步串行通信的接口标准由RS232、RS422、RS485等.
    • RS232是单端输入输出
    • RS422/485是差分输入输出
  • DB9接口定义

二.实验任务

本节实验任务是上位机通过串口调试助手发送数据给FPGA, FPGA通过串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。

三.硬件设计

  • 硬件电路

由于DB9接口类型的RS232串口占用空间较大, 很多系统已经选择USB转TTL方案, 利用CH340芯片实现USB总线转UART功能,通过Mini USB接口实现与上位机通信。

  • 开拓者开发板串口通信实验引脚分配

四.程序设计

  • 程序框图

       在编写代码之前,我们首先要确定串口通信的数据格式及波特率。在这里我们选择串口比较常用的一种模式,数据位为8位,停止位为1位,无校验位,波特率为115200bps。则传输一帧数据的时序图如下

                                                                                              串口通信时序图

  • 由系统总体框图可知, FPGA部分包括三个模块,
    • 顶层模块 (uart_top) 
    • 接收模块 (uart_recv)
    • 发送模块(uart_send)

                                                                                                          顶层模块原理图

  • 代码

    • 顶层代码定义了实验的输入输出接口 ,同时例化了发送模块和接收模块,并定义了串口相关传递参数,定义模块间的连接线型网络。

module uart_top(
    input sys_clk,   /*绯荤粺鏃堕挓*/
    input sys_rst_n,

    input  uart_rxd,
    output uart_txd
);

//parameter define
parameter CLK_FREQ=50000000;  
parameter UART_BPS=115200;

//reg define

//wire define
wire uart_en_w;
wire [7:0] uart_data_w;
wire clk_1m_W;

//**********************************************//
//************         main code      **********//
//**********************************************//

uart_recv   #(                          //涓插彛鎺ユ敹妯″潡 
     .CLK_FREQ       (CLK_FREQ ),       //璁剧疆绯荤粺鏃堕挓棰戠巼 
     .UART_BPS       (UART_BPS ))       //璁剧疆涓插彛鎺ユ敹娉㈢壒鐜
u_uart_recv(
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),

	.uart_rxd(uart_rxd),

	.uart_done(uart_en_w),   
	.uart_data(uart_data_w)
);

uart_send   #(                          //涓插彛鎺ユ敹妯″潡 
     .CLK_FREQ       (CLK_FREQ ),       //璁剧疆绯荤粺鏃堕挓棰戠巼 
    .UART_BPS       (UART_BPS ))       //璁剧疆涓插彛鎺ユ敹娉㈢壒鐜
u_uart_send(
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),

	.uart_data_en(uart_en_w),
	.uart_data(uart_data_w),

	.uart_txd(uart_txd)
);
endmodule

  • 串口接收模块 代码   
    • 先通过接收的信号下降沿判断数据的到来
    • 进行数据的接收寄存
    • 区分接收 和 非接受状态  rx_flag(主要通过数每一帧的数据个数是否到达停止位)
    • 通过波特率对应时钟的计数个数到达来对数据接收计数的自加
    • 数据接收的同时进行数据按位寄存
    • 每一帧数据接收完毕此时使能发送信号,并将寄存器中存储的数据传输出去
module uart_recv(
	input sys_clk,
	input sys_rst_n,

	input uart_rxd,

	output reg uart_done,    /*鎺ユ敹涓€甯ф暟鎹畬鎴愭爣蹇*/
	output reg [7:0] uart_data 
);

//parameter define
parameter CLK_FREQ=50000000;
parameter UART_BPS=9600;

localparam BPS_CNT=CLK_FREQ/UART_BPS;

//reg define

reg uart_rxd_d0;
reg uart_rxd_d1;
reg [15:0]  clk_cnt;   //绯荤粺鏃堕挓璁℃暟鍣
reg [3:0]   rx_cnt;    //鎺ユ敹鏁版嵁璁℃暟鍣
reg         rx_flag;   //鎺ユ敹杩囩▼鏍囧織淇″彿
reg [7:0]   rxdata;    //鎺ユ敹鏁版嵁瀵勫瓨鍣

//wire define
wire start_flag;

//*****************************************************
//** main code
//*****************************************************
//鎹曡幏鎺ユ敹绔彛涓嬮檷娌璧峰浣锛屽緱鍒颁竴涓椂閽熷懆鏈熺殑鑴夊啿淇″彿
assign start_flag=(~uart_rxd_d0)&uart_rxd_d1;  

//瀵筓ART鎺ュ彈绔彛鐨勬暟鎹欢鏃朵袱涓懆鏈
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
		uart_rxd_d0<=1'b0;
		uart_rxd_d1<=1'b0;
	end
	else  begin
		uart_rxd_d0<=uart_rxd;
		uart_rxd_d1<=uart_rxd_d0;		
	end
end


//褰撹剦鍐蹭俊鍙穝tart_flag鍒拌揪鏃讹紝杩涜鎺ユ敹杩囩▼
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
		rx_flag<=1'b0;
	end
	else  begin
		if (start_flag) begin
			rx_flag<=1'b1;
		end
		else if ((rx_cnt==4'd9)&&(clk_cnt==BPS_CNT/2) )begin
			rx_flag<=1'b0;
		end
		else begin
			rx_flag<=rx_flag;
		end
	end
end

//杩涘叆鎺ユ敹杩囩▼鍚庯紝鍚姩绯荤粺鏃堕挓璁℃暟鍣ㄤ笌鎺ユ敹鏁版嵁璁℃暟鍣
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_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;;
		end
	end
	else begin
        clk_cnt<=16'd0;
        rx_cnt <= 4'd0;		
	end
end

//鏍规嵁鎺ユ敹鏁版嵁璁℃暟鍣ㄦ潵瀵勫瓨UART鎺ユ敹绔彛鏁版嵁
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
		rxdata<=8'd0;
	end
	else if (rx_flag) begin
		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 begin
		rxdata<=rxdata;	
	    end
	end
	else begin
		rxdata<=8'd0;
	end
end

//鏁版嵁鎺ユ敹瀹屾瘯鍚庣粰鍑烘爣蹇椾俊鍙峰苟瀵勫瓨鍣ㄨ緭鍑烘帴鏀跺埌鐨勬暟鎹
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_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
  • 串口发送模块代码
module uart_send(
	input sys_clk,
	input sys_rst_n,

	input uart_data_en,
	input [7:0] uart_data,

	output reg uart_txd
);

//parameter define
parameter CLK_FREQ=50000000;
parameter UART_BPS=9600;

localparam BPS_CNT=CLK_FREQ/UART_BPS;

//reg define
reg uart_en_d0;
reg uart_en_d1;
reg [15:0] clk_cnt;
reg [3:0]  tx_cnt;   //鍙戦€佹暟鎹鏁板櫒
reg tx_flag;
reg [7:0] tx_data;
//wire define 
wire en_flag;
//*****************************************************
//**               main code                         **//
//*****************************************************
//鎹曡幏uart_en涓婂崌娌匡紝寰楀埌涓€涓椂閽熷懆鏈熺殑鑴夊啿淇″彿

assign en_flag=(~uart_en_d1)&uart_en_d0;

//瀵瑰彂閫佷娇鑳戒俊鍙穟art_en寤惰繜涓や釜鏃堕挓鍛ㄦ湡 
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
		uart_en_d0<=1'b0;
		uart_en_d1<=1'b0;
	end
	else  begin
		uart_en_d0<=uart_data_en;
		uart_en_d1<=uart_en_d0;			
	end
end
//褰撹剦鍐蹭俊鍙積n_flag鍒拌揪鏃瀵勫瓨寰呭彂閫佺殑鏁版嵁锛屽苟杩涘叆鍙戦€佽繃绋
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
    	tx_flag<=1'b0;
    	tx_data<=8'd0;
	end
	else  if(en_flag) begin
		tx_flag<=1'b1;
    	tx_data<=uart_data;		
	end
	else begin
		if ((tx_cnt==4'd9)&&(clk_cnt==BPS_CNT/2)) begin //璁℃暟鍒板仠姝綅涓棿鏃讹紝鍋滄鍙戦€佽繃绋
			tx_flag<=1'b0;
    	    tx_data<=8'd0;
		end
		else begin
			tx_flag<=tx_flag;
    	    tx_data<=tx_data;
		end
	end
end

//杩涘叆鍙戦€佽繃绋嬪悗锛屽惎鍔ㄧ郴缁熸椂閽熻鏁板櫒涓庡彂閫佹暟鎹鏁板櫒
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
		clk_cnt<=16'd0;
		tx_cnt <=4'd0;
	end
	else if(tx_flag)  begin
	    if (clk_cnt<BPS_CNT-1) begin
		   clk_cnt<=clk_cnt+1'b1;
		   tx_cnt   <= tx_cnt; 	    	
	    end	
	    else begin
            clk_cnt  <= 16'd0;               //瀵圭郴缁熸椂閽熻鏁拌揪涓€涓尝鐗圭巼鍛ㄦ湡鍚庢竻闆
            tx_cnt   <= tx_cnt + 1'b1;       //姝ゆ椂鍙戦€佹暟鎹鏁板櫒鍔	    	
	    end
	end
	else begin            
		clk_cnt<=16'd0;
		tx_cnt <=4'd0;		
	end
end

always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
       uart_txd<=1'b1;
	end
	else  if(tx_flag) begin
    	case(tx_cnt)
			4'd0: uart_txd<=1'b0;			     
			4'd1: uart_txd  <= tx_data[0];   //鏁版嵁浣嶆渶浣庝綅		
			4'd2: uart_txd  <= tx_data[1];
			4'd3: uart_txd  <= tx_data[2]; 
	        4'd4: uart_txd  <= tx_data[3]; 		   
			4'd5: uart_txd  <= tx_data[4]; 
	        4'd6: uart_txd  <= tx_data[5]; 
			4'd7: uart_txd  <= tx_data[6]; 
	        4'd8: uart_txd  <= tx_data[7]; 
	        4'd9: uart_txd<=1'b1;        
	        default:;
	    endcase		
	end
   else begin
	 		uart_txd<=1'b1; 		
	 			end	
end
endmodule
  • 仿真结果

 

  • 调试问题总结

(PS:开始仿真的时候接收的 数据计数等于9后,仍然不清零,我修改了大于等于9,依然 不清零,所以定位到是后面的数周期的参数写错了,所以出现的无法清零问题;第二个问题是发送数据 是对的,但是接收一直显示的是00,后来检查数据流发现,是我在例化接收模块的时候 ,未将数据端口引出 ,因此发送模块无法拿到接收模块接收到的数据,因此数据一直为00,因此重新例化模块后,能发送接收到的数据了。)

  • 安装了sublime  可以进行语法检查了,另外写了一些快捷脚本例如:

例1:

直接生成

 

例2:

自己也可以写一些状态机方面的,按照自己的风格,以后有什么需要再添加吧。

 

今天依旧是学习的一天,加油!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值