【UART】Verilog实现UART接收和发送模块

目录

 

写在前面

UART 工作原理

UART 接收部分

UART RX 模块图

UART RX 时序图

Verilog 实现  UART RX 模块

UART 发送部分

UART TX 模块图

UART TX 时序图

Verilog 实现  UART TX 模块

总结


 

写在前面

UART协议在之前的一篇博客中有介绍,直达链接如下:

【总线】一文看懂 UART 通信协议

这里只是做简单的介绍,重点在 Verilog 实现部分。

UART 工作原理

将要传输数据的UART从数据总线接收数据。数据总线用于通过另一个设备(如CPU,内存或微控制器)将数据发送到UART。数据以并行形式从数据总线传输到传输UART。在发送UART从数据总线获取并行数据后,它会添加一个起始位、一个奇偶校验位和一个停止位,从而创建数据包。接下来,数据包在Tx引脚上逐位串行输出。接收UART在其Rx引脚上逐位读取数据包。然后,接收的UART将数据转换回并行形式,并删除起始位、奇偶校验位和停止位。最后,接收UART将数据包并行传输到接收端的数据总线:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATGluZXN0LTU=,size_20,color_FFFFFF,t_70,g_se,x_16

 UART传输的数据被组织成数据包。每个数据包包含 1 个起始位、5 到 9 个数据位(取决于 UART)、一个可选的奇偶校验位以及 1 个或 2 个停止位:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATGluZXN0LTU=,size_20,color_FFFFFF,t_70,g_se,x_16

UART 接收部分

UART RX 模块图

UART 接受模块的作用是将接受到的串行数据转换成并行数据。

由于我做的实验工程是将 UART 和 RS485 共同使用,所以需要一个信号 dir 控制RS485的传输方向,如果不用 RS485 的话可以将此信号忽略。

UART 的接受模块分为六个信号,三个输入信号:时钟信号clk、复位信号(低有效)rst_n、接受串行数据信号;三个输出信号:并行数据po_data、输出并行数据的同步标志信号pi_flag、控制方向信号 dir。

54627046424e41528d41fa547e201ab9.png

UART RX 时序图

时序图清晰的描述了数据是如何变化的。

09f1fdd5cf8b4cfc8b4ea6558a03da42.png

Verilog 实现  UART RX 模块

`timescale 1ns / 1ps
//
// Company: 
// Engineer: Linest-5
// Create Date: 2022/04/14
// Design Name: 
// Module Name: UART_RX
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: UART 接收模块,将串行的数据转成并行的数据接收
// Dependencies: 
// Revision:
// Additional Comments:
// 
//


module UART_RX #(
	parameter	BAUD_RATE = 'd9600,					//波特率
	parameter	CLK_FREQ  = 'd250000000,			//时钟周期
	parameter	BAUD_CNT_MAX = CLK_FREQ/BAUD_RATE
	)
	(
	input	wire		  clk,						//接收到的数据
	input	wire		  rst_n,					//复位信号,低电平有效
	input	wire		  rx,						//输入的串行数据		
	output	reg   [7:0]	  po_data,					//输出的并行数据
	output	reg           po_flag					//并行数据输出的同步标志信号
	);			
				
	reg					  rx_reg1;					//打三拍,稳定数据
	reg 				  rx_reg2;		
	reg 				  rx_reg3;		
	reg 				  start_flag;				//数据稳定信号
	reg 				  work_en;					//开始提取数据有效信号
	reg 				  bit_flag;					//数据提取标志信号
	reg   [3:0]			  bit_cnt;					//数据位计数信号
	reg   [12:0]		  baud_cnt;					//比特计数信号,一个数据需要多少个时钟周期
	reg   [7:0]			  rx_data; 					//串转并数据
	reg 				  rx_flag;					//串转并完成信号


	//消除亚稳态
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			rx_reg1 <= 'd1;
		end
		else begin
			rx_reg1 <= rx;
		end
	end

	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			rx_reg2 <= 'd1;
		end
		else begin
			rx_reg2 <= rx_reg1;
		end
	end

	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			rx_reg3 <= 'd1;
		end
		else begin
			rx_reg3 <= rx_reg2;
		end
	end
	//下降沿检测
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			start_flag <= 'd0;
		end
		else if ((rx_reg2 == 0) && (rx_reg3 == 1) && (work_en == 'd0)) begin 
			start_flag <= 'd1;
		end
		else begin
			start_flag <= 'd0;
		end
	end
	//开始数据计数信号
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			work_en <= 'd0;
		end
		else if (start_flag == 1) begin
			work_en <= 'd1;
		end
		else if ((bit_flag == 1) && (bit_cnt == 'd8)) begin
			work_en <= 'd0;
		end
	end
	//波特计数信号,用时钟频率除以波特率即每个数据需要的时钟周期数
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			baud_cnt <= 'd0;
		end
		else if ((baud_cnt == BAUD_CNT_MAX-1) || (work_en == 'd0)) begin
			baud_cnt <= 'd0;
		end
		else begin
			baud_cnt <= baud_cnt + 'd1;
		end
	end
	//数据中间提取最稳定
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			bit_flag <= 'd0;
		end
		else if (baud_cnt == BAUD_CNT_MAX/2-1) begin
			bit_flag <= 'd1;
		end
		else begin
			bit_flag <= 'd0;
		end
	end
	//数据位计数信号,只去有效的数据位,起始位和结束位不要
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			bit_cnt <= 'd0;
		end
		else if ((bit_flag == 'd1) && (bit_cnt == 'd8)) begin
			bit_cnt <= 'd0;
		end
		else if (bit_flag == 'd1) begin
			bit_cnt <= bit_cnt + 'd1;
		end
	end
	//将提取的数据串转并
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			rx_data <= 'd0;
		end
		else if ((bit_flag == 'd1) && (bit_cnt >= 'd1) && (bit_cnt <= 'd8)) begin
			rx_data <= {rx_reg3,rx_data[7:1]};
		end
	end
	//完成最后一个数据的拼接之后, 就把此信号拉高 
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			rx_flag <= 'd0;
		end
		else if ((bit_flag == 'd1) && (bit_cnt == 'd8)) begin
			rx_flag <= 'd1;
		end
		else begin
			rx_flag <= 'd0;
		end
	end
	//并行数据输出
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			po_data <= 'd0;
		end
		else if (rx_flag == 'd1) begin
			po_data <= rx_data;
		end
		else begin
			po_data <= po_data;
		end
	end
	//并行数据输出标志信号
    always @(posedge clk or negedge rst_n) begin
        if (rst_n == 'd0) begin
            po_flag <= 'd0;
        end
        else begin
            po_flag <= rx_flag;
        end
    end
	
endmodule

UART 发送部分

UART TX 模块图

输入信号:时钟信号clk、复位信号rst_n、输入并行数据、输入并行数据的同步标志信号

输出信号:发送串行数据tx、数据发送完成标志信号tx_done、方向控制信号dir、发哦是那个端空闲标志信号tx_ready

7e05b53b55ec45d68afd3ffd42e14539.png

UART TX 时序图

014a900e3d75424295a0c1e8d51f3015.png

Verilog 实现  UART TX 模块

`timescale 1ns / 1ps
//
// Company: 
// Engineer: Linest-5
// Create Date: 2022/04/15
// Design Name: 
// Module Name: UART_TX
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: UART 发送模块,将接收到的并行数据转成串行数据并发送
// Dependencies: 
// Revision:
// Additional Comments:
// 
//

module UART_TX(

    input 			clk,					//系统时钟
    input 			rst_n,					//系统复位,低电平有效
    input 			valid,					//数据有效信号
    input [7:0] 	pi_data,				//输入的并行数据

    output 			tx,						//并转串的输出数据
    output 			tx_done, 				//并转串的输出数据发送完成标志信号
    output	reg		dir=1,					//控制max3485的dir使能信号
    output	reg 	ready,					//tx模块可以接收数据信号
    output	reg 	ena 					//使能信号
    );

    reg   [7:0] 	pi_data_reg;			//输入数据寄存
    reg 			work_en;				//开始提取数据有效信号
    reg   [17:0] 	baud_cnt;				//波特计数信号,每个数据需要的波特数
    reg   [3:0] 	bit_cnt;				//数据位计数信号
    reg 			bit_flag;				//数据提取标志信号
    reg 			tx_reg;					//发送数据寄存
    reg 			tx_done_reg;			//发送数据完成标志信号

    //参数定义
    parameter	CLK_FREQ 	 = 'd50000000;				
    parameter	BAUD_RATE 	 = 'd9600;
    parameter	BAUD_CNT_MAX = CLK_FREQ/BAUD_RATE;

	//检测输入数据的到来,并将数据寄存
    always @(posedge clk or negedge rst_n) begin
        if (rst_n == 'd0) begin
            pi_data_reg <= 'd0;
        end
        else if (valid == 'd1) begin
            pi_data_reg <= pi_data ;
        end
        else begin
            pi_data_reg <= pi_data_reg;
        end
    end
	//工作使能,在数据标志信号为高时拉高,在发送数据完成信号为高时拉低
   	always @(posedge clk or negedge rst_n) begin
   	    if (rst_n == 'd0) begin
   	        work_en <= 'd0;
   	    end
   	    else if (valid == 'd1) begin 
   	        work_en <= 'd1;
   	    end
   	    else if (tx_done == 'd1) begin
   	        work_en <= 'd0;
   	    end
   	end
	//根据不同的波特率,对每个数据需要波特数进行计数
    always @(posedge clk or negedge rst_n) begin
        if(rst_n == 'd0)begin
            baud_cnt <= 'd0;
        end
        else if(work_en == 'd1 && (baud_cnt == BAUD_CNT_MAX - 'd1))begin
			baud_cnt <= 'd0 ;
        end
        else if (work_en == 'd1) begin
            baud_cnt <= baud_cnt + 'd1 ; 
        end
        else begin
            baud_cnt <= 'd0;
        end
    end
	//比特标志信号拉高,在每一个波特计数为1时拉高,相当于对数据的提取标志信号
    always @(posedge clk or negedge rst_n) begin
        if (rst_n == 'd0) begin
            bit_flag <= 'd0;
        end
        else if (baud_cnt == 'd1) begin
            bit_flag <= 'd1;
        end
        else begin
            bit_flag <= 'd0;
        end
    end
    //比特计数信号,在对每一个数据为进行计数
    always @(posedge clk or negedge rst_n) begin
        if(rst_n == 'd0)begin
            bit_cnt <= 'd0;
        end
        else if ((work_en == 'd1) && (bit_flag == 'd1) && (bit_cnt == 'd11)) begin
			bit_cnt <= 'd0;                       
        end
        else if ((work_en == 'd1) && (bit_flag == 'd1)) begin
            bit_cnt <= bit_cnt + 'd1;
        end
        else if (work_en == 'd0) begin
            bit_cnt <= 'd0;
        end
    end
	//对并行数据映射到串行数据
    always @(posedge clk or negedge rst_n) begin
        if (rst_n == 'd0) begin
            tx_reg <= 'd0;
        end
        else begin
            case (bit_cnt)
                4'd1: tx_reg <= 'd0;
                4'd2: tx_reg <= pi_data_reg[0];
                4'd3: tx_reg <= pi_data_reg[1];
                4'd4: tx_reg <= pi_data_reg[2];
                4'd5: tx_reg <= pi_data_reg[3];
                4'd6: tx_reg <= pi_data_reg[4];
                4'd7: tx_reg <= pi_data_reg[5];
                4'd8: tx_reg <= pi_data_reg[6];
                4'd9: tx_reg <= pi_data_reg[7];
                4'd10:tx_reg <= 'd1;
                4'd11:tx_reg <= 'd1;
                default :tx_reg <= 'd1; 
            endcase
        end
    end
	//发送数据完成标志信号
    always @(posedge clk or negedge rst_n) begin
        if (rst_n == 'd0) begin
            tx_done_reg <= 'd0;
        end
        else if ((bit_flag == 'd1) && (bit_cnt == 'd10)) begin
            tx_done_reg <= 'd1;
        end
        else begin
            tx_done_reg <= 'd0;
        end
    end
    //在tx模块发送数据完成时或者空闲状态时拉高,拉高表示可以接收上游传来的数据,在数据并转串时拉低
    always @(posedge clk or negedge rst_n) begin
    	if (rst_n == 'd0) begin
    		ready <= 'd1;
    	end
    	else if (tx_done == 'd1) begin
    		ready <= 'd1;
    	end
    	else if (valid == 'd1) begin
    		ready <= 'd0;
    	end
    end
    //Bram的使能信号
    always @(posedge clk or negedge rst_n) begin
    	if (rst_n) begin
    		ena <= 'd0;
    	end
    	else begin
    		ena <= tx_done;
    	end
    end
    
    assign tx = tx_reg;
    assign tx_done = tx_done_reg;

endmodule

总结

UART 的时序并不复杂,最重要的是理解比特率的概念,以及在计数当中如何设定,这两个模块在实际的工程中可以正常跑通。

 

 

  • 11
    点赞
  • 160
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
以下是一个DMA+UART发送接收Verilog代码示例: ```verilog module dma_uart ( input clk, // 时钟信号 input rst, // 复位信号 input [7:0] tx_data, // 待发送数据 output reg tx_busy, // 发送忙标志 output reg [7:0] rx_data, // 接收到的数据 input rx, // 接收数据信号 output reg rx_ready, // 接收就绪标志 input dma_start, // DMA启动信号 input [31:0] dma_src_addr, // DMA源地址 output [31:0] dma_dest_addr, // DMA目的地址 input [7:0] dma_length // DMA数据长度 ); // 内部寄存器定义 reg [7:0] tx_byte_cnt; reg [7:0] rx_byte_cnt; reg [31:0] dma_addr; reg [7:0] dma_cnt; reg [31:0] dma_data; // UART发送状态机 always @ (posedge clk) begin if (rst) begin tx_busy <= 0; tx_byte_cnt <= 0; end else if (tx_busy) begin case (tx_byte_cnt) 0: begin // 发送起始位 tx <= 0; tx_byte_cnt <= tx_byte_cnt + 1; end 9: begin // 发送停止位 tx <= 1; tx_busy <= 0; end default: begin // 发送数据位 tx <= tx_data[tx_byte_cnt-1]; tx_byte_cnt <= tx_byte_cnt + 1; end endcase end end // UART接收状态机 always @ (posedge clk) begin if (rst) begin rx_ready <= 0; rx_byte_cnt <= 0; end else if (rx_ready) begin case (rx_byte_cnt) 0: begin // 接收起始位 rx_byte_cnt <= rx_byte_cnt + 1; end 9: begin // 接收停止位 rx_ready <= 0; end default: begin // 接收数据位 rx_data[rx_byte_cnt-1] <= rx; rx_byte_cnt <= rx_byte_cnt + 1; end endcase end else if (rx) begin // 接收到起始位 rx_ready <= 1; rx_byte_cnt <= 0; end end // DMA控制状态机 always @ (posedge clk) begin // 状态1 - 等待启动信号 if (!tx_busy && !rx_ready && dma_start) begin dma_addr <= dma_src_addr; dma_dest_addr <= 0; // 设置DMA目的地址为UART发送寄存器地址 dma_cnt <= dma_length; tx_busy <= 1; end // 状态2 - 数据传输中 else if (tx_busy && dma_cnt > 0) begin dma_data <= mem[dma_addr]; dma_dest_addr <= 1; // 设置DMA目的地址为UART发送寄存器地址 dma_addr <= dma_addr + 1; dma_cnt <= dma_cnt - 1; end // 状态3 - 数据传输完成 else if (tx_busy && dma_cnt == 0) begin tx_busy <= 0; end // 状态4 - 接收到数据 else if (rx_ready) begin // 设置DMA目的地址为接收到的数据寄存器地址 dma_dest_addr <= 2; end end endmodule ``` 在这个示例中,DMA控制器等待启动信号,并将DMA源地址、目的地址和数据长度设置为输入信号。一旦启动,控制器将开始将数据从DMA源地址传输到UART发送寄存器。同时,UART接收状态机等待接收到起始位,一旦接收到,它就开始将数据位存储到接收到的数据寄存器中。请注意,这只是一个简单的示例,实际上的DMA和UART控制器需要更多的控制逻辑和状态。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linest-5

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值