九、UART串口实验

UART串口实验

一、实验任务

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

二、程序设计

在这里插入图片描述
在这里插入图片描述

  • 串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下,同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
  • UART 是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receivertransmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换
    成并行数据。
  • 串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是 bps(位/秒),常用的波特率有 9600、19200、38400、57600 以及 115200 等。
  • UART 负责完成数据的串并转换
    在这里插入图片描述

设计文件

//顶层模块
module uart_loopback_top (
	input  sys_clk,
	input  sys_rst_n,
	input  recv_data_c ,
	output send_data_c
);

	//parameter define
	parameter CLK_FREQ = 50000000; //定义系统时钟频率
	parameter BPS      = 115200;   //定义串口波特率

	wire [7:0] uart_send_data;
	wire [7:0] uart_recv_data;
	wire       uart_done;
	wire       tx_busy;
	wire       uart_send_en;

	uart_recv #(
		.CLK_FREQ(CLK_FREQ),
		.BPS     (BPS     )
	) u_uart_recv (
		.sys_clk  (sys_clk    ),
		.sys_rst_n(sys_rst_n  ),
		
		.uart_rxd (recv_data_c),
		.uart_data(uart_recv_data  ),
		.uart_done(uart_done  )
	);

	uart_loop u_uart_loop (
		.sys_clk       (sys_clk     ),
		.sys_rst_n     (sys_rst_n   ),
		
		.uart_recv_data(uart_recv_data   ),
		.uart_recv_done(uart_done   ),
		.tx_busy       (tx_busy     ),
		.uart_send_en  (uart_send_en),
		.uart_send_din (uart_send_data   )
	);

	uart_send #(
		.CLK_FREQ(CLK_FREQ),
		.BPS     (BPS     )
	) u_uart_send (
		.sys_clk     (sys_clk     ),
		.sys_rst_n   (sys_rst_n   ),
		
		.uart_en     (uart_send_en),
		.uart_din    (uart_send_data   ),
		.uart_txd    (send_data_c ),
		.uart_tx_busy(tx_busy     )
	);

endmodule

//从上位机接收数据模块
module uart_recv (
	input            sys_clk,
	input            sys_rst_n,
	input            uart_rxd ,  //数据通道
	output reg [7:0] uart_data,
	output reg       uart_done
);
	parameter CLK_FREQ = 50000000;       //时钟频率50MHZ
	parameter BPS      = 115200;
	parameter BPS_CNT  = CLK_FREQ / BPS; //传输一位需要的周期数
	//localparameter 本地参数不可传递 parameter 可以在模块之间传递参数

	reg       rx_flag;
	reg [15:0] clk_cnt;
	reg [7:0] rx_data; //先将数据保存到寄存器中,当接收完成后再output
	reg [3:0] rx_cnt;

	wire start_flag;
	reg  uart_rxd_d0;
	reg  uart_rxd_d1;

	//抓取数据通道的下降沿
	assign start_flag = ~uart_rxd_d0 && uart_rxd_d1 ;
	always @(posedge sys_clk or posedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			uart_rxd_d0 <= 1'd0;
			uart_rxd_d1 <= 1'd0;
		end
		else begin
			uart_rxd_d0 <= uart_rxd ;
			uart_rxd_d1 <= uart_rxd_d0;
		end
	end

	//开始接收信号
	always @(posedge sys_clk or posedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			rx_flag <= 1'd0;
		end
		else begin
			if (start_flag)
				rx_flag <= 1'd1;
			//停止位并不需要完整的一个BPS_CNT,所以取其一半作为判断条件
			else if (rx_cnt == 4'd9 && (clk_cnt == BPS_CNT / 2)) begin
				rx_flag <= 1'd0;
			end
			else
				rx_flag <= rx_flag;
		end
	end

	//时钟计数器
	always @(posedge sys_clk or negedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			clk_cnt <= 16'd0;
		end
		else if (rx_flag) begin
			if (clk_cnt == BPS_CNT-1) begin
				clk_cnt <= 16'd0;  //每数434个时钟周期接收一个bit
			end
			else
				clk_cnt <= clk_cnt + 16'd1;
		end
		else begin
			clk_cnt <= 16'd0;
		end
	end

	//接收数据计数器
	always @(posedge sys_clk or posedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			rx_cnt <= 4'd0;
		end
		else if (rx_flag) begin
			if (clk_cnt == BPS_CNT-1)
				rx_cnt <= rx_cnt + 1'd1;
			else
				rx_cnt <= rx_cnt;
		end
		else begin
			rx_cnt <= 4'd0;
		end
	end

	//接收数据
	always @(posedge sys_clk or posedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			rx_data <= 8'd0;
		end
		else if (rx_flag) begin
			if (clk_cnt == BPS_CNT/2) begin
				//避免出现亚稳态,就等到稳定之后,可以选择捕获后的第二个寄存器来表示
				case(rx_cnt)
					4'd1    : rx_data[0] <= uart_rxd_d1;
					4'd2    : rx_data[1] <= uart_rxd_d1;
					4'd3    : rx_data[2] <= uart_rxd_d1;
					4'd4    : rx_data[3] <= uart_rxd_d1;
					4'd5    : rx_data[4] <= uart_rxd_d1;
					4'd6    : rx_data[5] <= uart_rxd_d1;
					4'd7    : rx_data[6] <= uart_rxd_d1;
					4'd8    : rx_data[7] <= uart_rxd_d1;
					default : ;
				endcase
			end
			else
				rx_data <= rx_data;
		end
		else begin
			rx_data <= 8'd0;
		end
	end

	//数据接收完,进行output并给出done信号
	always @(posedge sys_clk or posedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			uart_data <= 8'd0;
			uart_done <= 1'd0;
		end
		else if (rx_cnt == 4'd9) begin
			uart_data <= rx_data;
			uart_done <= 1'd1;
		end
		else begin
			uart_data <= 8'd0;
			uart_done <= 1'd0;
		end
	end

endmodule

//环回模块,对数据进行转发
module uart_loop (
	input            sys_clk,
	input            sys_rst_n,
	input      [7:0] uart_recv_data,
	input            uart_recv_done,
	input            tx_busy,
	output reg       uart_send_en,
	output reg [7:0] uart_send_din
);

	reg tx_ready;

	wire en_flag;
	reg  uart_recv_done_d0;
	reg  uart_recv_done_d1;

	//抓取发送使能信号的上升沿
	assign en_flag = uart_recv_done_d0 && ~uart_recv_done_d1 ;
	always @(posedge sys_clk or posedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			uart_recv_done_d0 <= 1'd0;
			uart_recv_done_d1 <= 1'd0;
		end
		else begin
			uart_recv_done_d0 <= uart_recv_done ;
			uart_recv_done_d1 <= uart_recv_done_d0;
		end
	end

	//传给发送模块
	always @(posedge sys_clk or negedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			uart_send_en  <= 1'd0;
			uart_send_din <= 8'd0;
			tx_ready      <= 1'd0;
		end
		else begin
			//这个捕获到的上升沿信号en_flag是一瞬间的
			if (en_flag) begin
				uart_send_en  <= 1'd0;
				tx_ready      <= 1'd1; //这个信号很有必要,接收完进入准备状态
				uart_send_din <= uart_recv_data;
			end
			//当发送模块不处于发送状态且环回模块准备好发送时,才可以发送
			else if (~tx_busy && tx_ready) begin
				tx_ready     <= 1'd0; //只发送一次即可,已经发送了就需要复位准备下次发送
				uart_send_en <= 1'd1;
			end
		end

	end
endmodule

//从开发板将数据发送给上位机
//注意这里的发送模块是将数据发送给上位机
//发送的数据是来自环回模块传递来的数据
module uart_send (
	input            sys_clk,
	input            sys_rst_n,
	input            uart_en,      //发送使能信号
	input      [7:0] uart_din,     //并行的数据
	output reg       uart_txd,     //将串行数据传给上位机
	output           uart_tx_busy
);
	parameter CLK_FREQ = 50000000;       //时钟频率50MHZ
	parameter BPS      = 115200;
	parameter BPS_CNT  = CLK_FREQ / BPS; //传输一位需要的周期数
	//localparameter 本地参数不可传递 parameter 可以在模块之间传递参数

	reg        tx_flag;
	reg [15:0] clk_cnt;
	reg [ 7:0] tx_data; //先将数据保存到寄存器中
	reg [ 3:0] tx_cnt;

	wire en_flag;
	reg  uart_en_d0;
	reg  uart_en_d1;

	//抓取发送使能信号的上升沿
	assign en_flag = uart_en_d0 && ~uart_en_d1 ;
	always @(posedge sys_clk or posedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			uart_en_d0 <= 1'd0;
			uart_en_d1 <= 1'd0;
		end
		else begin
			uart_en_d0 <= uart_en ;
			uart_en_d1 <= uart_en_d0;
		end
	end

	//开始发送信号
	assign uart_tx_busy = tx_flag;
	always @(posedge sys_clk or posedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			tx_flag <= 1'd0;
			tx_data <= 8'd0;
		end
		else begin
			if (en_flag) begin
				tx_flag <= 1'd1;
				tx_data <= uart_din;
			end
			//停止位并不需要完整的一个BPS_CNT
			//发送完一次数据,清零
			else if (tx_cnt == 4'd9 && (clk_cnt == (BPS_CNT - BPS_CNT/16) ) ) begin
				tx_flag <= 1'd0;
				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
			// reset
			clk_cnt <= 16'd0;
		end
		else if (tx_flag) begin
			if (clk_cnt == BPS_CNT-1) begin
				clk_cnt <= 16'd0;  //每数434个时钟周期接收一个bit
			end
			else
				clk_cnt <= clk_cnt + 16'd1;
		end
		else begin
			clk_cnt <= 16'd0;
		end
	end

	//接收数据计数器
	always @(posedge sys_clk or posedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			tx_cnt <= 4'd0;
		end
		else if (tx_flag) begin
			if (clk_cnt == BPS_CNT-1)
				tx_cnt <= tx_cnt + 1'd1;
			else
				tx_cnt <= tx_cnt;
		end
		else begin
			tx_cnt <= 4'd0;
		end
	end

	//发送数据,并转串
	always @(posedge sys_clk or posedge sys_rst_n) begin
		if (!sys_rst_n) begin
			// reset
			uart_txd <= 1'd1; //当开始接收前信号处于高电平
		end
		else if (tx_flag) begin
			case(tx_cnt)
				4'd0    : uart_txd <= 1'd0 ; //第一个低电平,开始接收
				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'd1 ; //最后接收完,拉高
				default : ;
			endcase
		end
		else begin
			uart_txd <= 1'd1;
		end
	end


endmodule

tb文件

module tb_uart_loopback_top (); 

    //激励信号一般定义为 reg型
    reg                sys_clk        ;
    reg                sys_rst_n    ;
    reg recv_data_c;

    initial begin
        sys_clk         = 1'b0;
        sys_rst_n       = 1'b0;
        #100
            sys_rst_n = 1'b1;
        #8680 recv_data_c = 1'b0;
        #8680 recv_data_c = 1'b1;
        #8680 recv_data_c = 1'b1;
        #8680 recv_data_c = 1'b0;
        #8680 recv_data_c = 1'b0;
        #8680 recv_data_c = 1'b1;
        #8680 recv_data_c = 1'b0;
        #8680 recv_data_c = 1'b1;
        #8680 recv_data_c = 1'b0;
        #8680 recv_data_c = 1'b1;

    end

    always #10 sys_clk = ~sys_clk;

    uart_loopback_top u_uart_loopback_top (
        .sys_clk   (sys_clk   ),
        .sys_rst_n (sys_rst_n ),
        .recv_data_c (recv_data_c ),
        .send_data_c (send_data_c )
    );

endmodule

仿真波形

在这里插入图片描述

上板验证

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值