Verilog实现UART数据接收模块

首先,我们需要明白,UART协议的定义为:接受到1个起始位为0,然后是8个数据位DATA0——7(通常是8个,即80/1),1个校验位(可以省略,要求高的时候需要,这里我们将它省略)1个停止位为1;上述说的10个位,也就是说,一个完整的UART协议包是由10个二进制数据组成。

但是我们串口工具接收和发送的数据,都是以十六进制的来表示的,接受器收到的串行数据一定是二进制的,也就是说,我们首先要把每8个二进制数,转化成相应的数据(0——28-1(255)),也就是说,每个UART协议帧,只能发送一个0——255之间的数。串口工具通常会把8位数据,分成两个4位数据,即00——FF。

那么问题来了,我们需要将接收到的单比特的RX信号,转化成8位的RXDATA数据 ,但是在什么时候转换呢?

       其次,需要解决波特率和系统时钟的关系,先来看看波特率9600的含义,表示1S内可以传输9600个比特,如果协议帧是上述的10个比特的帧,那就可以传输9600/10 = 960个帧,即1S内可以发送960个0——255之间的数据。

那么这里,可以利用系统时钟和波特率之间的关系来得到一个波特率时钟BaudClk的计数器MAXCOUNT = SYSCLK / BaudRate,每当计数器的Number到达MAXCOUNT,即可以翻转BaudClk信号,这样,我们就获得了一个用于数据采样的BaudClk时钟信号,这里我们以50MHZ的系统时钟和9600波特率为例,得到50000000/9600=5208个CLK,即每个Bit会占据5208个SYSClk,BaudRate频率即为9600HZ。

       那么问题来了,在什么时候对RXDATA进行采样呢?带着以上两个问题,我们再回到题目中去看,首先,需要实现的是对RX数据进行延迟处理,因为信号刚输入时处于亚稳态(类似于按键消抖),于是,使用一个RXREG寄存器来保存稳定的RX数据

那什么时候开始计数呢?应该是刚进入起始位的时候就开始计数,因为起始位和数据位,停止位一样,都要计数5208个Clk.

那么现在,我们得到一个标志位,和一个5208的计数器,RX在每个5208个CLK里面的数据应该是一样的,但最好的采样点,还是最中间的数据。

综上,我们大致可以得到一个期待的数据波形图如下所示:

于是,我们将问题总结一下,这个模块内我们需要解决的问题有如下几个:

1)得到稳定的输入数据,其中主要是为了明显的起始位数据标志位0(类似于按键消抖)。

解决办法为:利用三个寄存器来缓存数据。

代码为:

//使用三个寄存器来对输入信号进行消抖
reg RXReg1,RXReg2,RXReg3;
always @(posedge Clk or negedge Reset_n) begin
    if (!Reset_n) begin
        RXReg1 <= 1'b1;
        RXReg2 <= 1'b1;
        RXReg3 <= 1'b1;
    end
    else begin
        RXReg1 <= rx;
        RXReg2 <= RXReg1;
        RXReg3 <= RXReg2;
    end
end//打三个节拍用于消除亚稳态

2)状态定义。

解决办法为:定义状态IDLE(空闲),START(起始位),DATA0——78个二进制数据位),STOP(停止位)

代码为三段式状态机:

// 状态转移逻辑,根据当前状态和输入信号确定下一状态
always @(*) begin
    case (state)
        IDLE:    next_state =(RXReg3==0)? START:IDLE;
        START:   next_state = (Number==CountMax)?DATA0:START; // 转移到数据位状态
        DATA0:   next_state = (Number==CountMax)?DATA1:DATA0;
        DATA1:   next_state = (Number==CountMax)?DATA2:DATA1;
        DATA2:   next_state = (Number==CountMax)?DATA3:DATA2;
        DATA3:   next_state = (Number==CountMax)?DATA4:DATA3;
        DATA4:   next_state = (Number==CountMax)?DATA5:DATA4;
        DATA5:   next_state = (Number==CountMax)?DATA6:DATA5;
        DATA6:   next_state = (Number==CountMax)?DATA7:DATA6;
        DATA7:   next_state = (RXReg3&&(Number==CountMax)) ? STOP : DATA7; // 转移到停止位状态
        STOP:    next_state = (Number==CountMax)?IDLE:STOP; // 转移到空闲状态
        default: next_state = IDLE; // 默认转移到空闲状态
    endcase
end

// 状态更新逻辑,根据时钟信号更新当前状态
always @(posedge Clk or negedge Reset_n) begin
    if (!Reset_n) begin
        state <= IDLE;
    end
    else if (state==IDLE) begin
        state <= next_state;
    end//在没有进入计数周期的时候,随时可以进入起始标志位
    else if (state>0&&Number==CountMax) begin
        state <= next_state;
    end//在进入计数周期的时候,计数器满才能开始状态转变;
end

// 2个输出逻辑,根据当前状态和计数器输出并行数据和标志位
always @(posedge Clk or negedge Reset_n) begin
    if (!Reset_n) begin
        data <= 0;
    end
    else if (state==10) begin
        data <= rxData;
    end
    else begin
        data <= data;
    end//该句让输出的数据,空闲的时候会输出上一刻的数据,直到下一起始位到来
end
always @(posedge Clk or negedge Reset_n) begin
    if (!Reset_n) begin
        data_valid <= 0;
    end
    else if(state == STOP&&Number==0) begin
        data_valid <= 1;
    end//进入STOP状态即输出一个CLK的标志位
    else
        data_valid <= 0;
end

3)解决系统时钟和波特率的关系,是模块能适应任何系统时钟和波特率。

解决办法为:定义一个计数器,计数器的最大值就是 系统时钟/波特率,这样,在起始位到停止位之间,会调用10次该计数器,且计数到中间时,会产生一个采样标志位。

代码为:

always @(posedge Clk or negedge Reset_n ) begin
    if (!Reset_n) begin
        Number <= 0;
        CountFlag <= 0;
    end
    else if (state>0) begin
        if(Number==CountMax) begin
        Number <= 0;
        CountFlag <= 0;
        end
        else if (Number==CountMax/2) begin
        Number <= Number+1;
        CountFlag <= 1;
        end
        else begin
        Number <= Number+1;
        CountFlag <= 0;
        end
    end
end

4)并行数据需要暂存在一个8位寄存器RxData里面,何时采集数据?何时停止采集数据?

解决办法为:状态在DATA0——7且采样标志位为1的时候,就会从RxReg3里面采样数据按照状态DATA0——7依次放入RxData [0:7],未采集的数据位数应当保持,在等待期,RxData数据清零。

代码如下:

always @(posedge Clk or negedge Reset_n) begin
    if (!Reset_n) begin
        rxData <= 0;
    end
    else if( (CountFlag)&&(state>1&&state<10) ) begin
        rxData[state-2] <= RXReg3;
    end//状态在1-8里面且采样标志位时才采样
    else if (state>1) begin
        rxData <= rxData;
    end//只要状态进入起始位,rxData都应当保持或采集
    else
        rxData <= 0;
        //这里就是状态处于IDLE的时候,rxdata需要清零
end

至此,我们已经实现了完整的数据接收模块,然后对其进行仿真,仿真后的结果如下图所示(内部变量设置为黄色):

上板验证待发送模块完成后一同测试。

最终测试结果如下:

从图中可以发现,串口能正常的接收和发送数据,使用波形发送软件发送波形数据,在接收端也能正常的收到该波形。

完整代码和测试代码请联系619743936@qq.com

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 使用Verilog语言来实现UART,需要实现以下几个步骤:1.定义UART的基本参数,如波特率;2.编写UART模块,包括接收和发送模块;3.实现接收和发送模块之间的控制逻辑;4.编写驱动程序,控制UART进行数据传输。 ### 回答2: 使用Verilog语言实现UART(通用异步收发器)是一项将串行数据转换为并行数据或将并行数据转换为串行数据的重要任务。以下是使用Verilog实现UART的步骤: 1. 首先,定义UART数据宽度和波特率等参数。数据宽度指的是并行数据的位数,波特率指的是串行通信时每秒传输的比特数。 2. 创建一个有限状态机(FSM)来控制UART的发送和接收过程。该状态机可以使用状态寄存器来表示各个状态。 3. 对于发送过程,需要为数据和校验位(如奇偶校验位)创建并行数据输入端口,并定义一个控制信号来启动发送过程。 4. 在发送模块中使用一个计数器来跟踪并行数据的位数,并将其转换为串行数据。在每个时钟周期中,将相应的并行数据位发送到串行数据输出端口。 5. 对于接收过程,需要定义一个控制信号来启动接收过程,并使用一个计数器来跟踪接收到的串行数据位数。 6. 在接收模块中,使用一个移位寄存器来接收串行数据位,并在每个时钟周期中将其转换为并行数据位发送到输出端口。 7. 实现校验功能,根据校验位的设置对发送和接收数据进行校验。 8. 最后,将发送和接收模块结合在一起,实现完整的UART模块。 需要注意的是,以上只是基本的框架和思路,实际实现中可能还需要考虑其他细节,如时钟同步、数据传输协议等。 使用Verilog实现UART可以实现串行通信功能,广泛应用于各种通信领域,如网络通信、嵌入式系统和通信接口等。 ### 回答3: 使用Verilog编程语言可以很方便地实现UART(Universal Asynchronous Receiver Transmitter,通用异步收发器)。UART用于串行数据通信,可以通过该模块实现与外部设备的数据传输。 首先,在Verilog实现UART需要定义模块的输入输出端口。常见的UART端口包括时钟信号,输入数据,输出数据以及控制信号等。根据需要,可以进一步增加奇偶校验等功能。 接下来,需要实现UART的核心逻辑部分。这包括时钟分频逻辑,接收缓冲区和发送缓冲区的FIFO(First-In First-Out,先进先出)逻辑等。 对于接收端,可以设置一个有限状态机来接收和处理串行数据。根据接收缓冲区的状态,可以解析出所接收到的数据,并进行相应的处理。同时,可以设置中断信号以通知主控制器数据的到达。 对于发送端,可以设置一个有限状态机来发送数据。根据发送缓冲区的状态,可以将数据发送至串行端口,并处理相关的时序问题。 最后,需要对UART模块进行仿真和验证。可以利用Verilog的仿真工具,如ModelSim等,进行功能验证,确保UART模块的正确性。 综上所述,通过使用Verilog编程语言,并结合适当的逻辑设计,可以实现UART模块。这样,我们就能够与外部设备进行串行数据通信,实现数据的传输和交换。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值