使用verilog设计uart串口

5 篇文章 0 订阅
4 篇文章 4 订阅

测试环境

操作系统:Windows10

综合仿真:Vivado 2018.3

芯片验证:Zynq7010

 

串口时序

作为调试交互接口,串口优势非常明显,虽然网上有很多成熟的IP,但作为学习,用Verilog重新写个电路还是很有必要。

我们日常常用的串口配置一般为:

         起始位:1bit

         数据位:8bit

         停止位:1bit

         校验位:无

         所以今天实现的电路就以这个配置来,固定1起始位8数据位1停止位无校验位。

串口线分为TX和RX,通信时使用交叉线连接,并且必须共地。下图为串口的TX线时序,SCLK为参考时钟。

         串口数据是异步传输,因此上图SCLK在实际通信当中不存在,因此通信双方需要约定好这个SCLK速度,才能保证数据通信的正确性,这个约定的时钟就是波特率,baud一般常用有115200、9600等待,115200表示1秒钟之内发送115200位的数据,为了增加实用性,本次设计的电路有一个分频寄存器。

         这里以TX发送线为例结合上图简单讲解一下时序。TX线在无数据传输时处于高电平,当TX从高跳变为低时意味着数据开始传输(这个低电平就是起始位),然后根据约定的波特率,从第0个数据位开始传输,当第7个数据位传输完后拉高TX,意味这数据传输完成(这个高电平就是停止位)。

 

电路实现

模块接口

         clock:输入时钟,我这里直接接外部50M有源晶振

         reset:串口复位,低电平复位串口

         division:分频系数,根据分频系数确定串口baud率

         rdata和tdata:接收和发送数据线

         rfinish和tfinish:接收和发送完成触发线

         ttrigger:发送数据触发线,触发后会发tdata的8位数据通tx发送出去

         tx和rx:接外部引脚

 

参数约定

         根据我们的配置,每次我们发送一字节的数据就需要10位,所以本次发送和接收电路都使用状态机实现,共10种状态,约定参数如下:

 

接收电路实现

         接收电路需要根据波特率来读取RX线,因为RX线每次信号在一个串口时钟中部是最稳定的,如下图:

         因此我们的逻辑处理时钟必须是串口时钟的2倍才能确定中间位置,再结合division分频系数,可实现如下电路, 此处rfcount_clear在被半个串口时钟周期会发出一个高电平脉冲:

         有了串口时钟,我们还需要设计一个接收状态机,方便数据接收,如下图, 因为总共是10种状态, 按照常规来说4位足够, 但由于要中位采样, 所以这里使用5位, 最低位的跳变正好可以用来触发采样:

         状态确定后我们即可根据状态来采样RX线上的电平了, 此时使用移位寄存器实现:

         处理一些其它的信号,注释都写得很纤细:

         以上就是整个串口的接收电路,起始可以看到我并没有使用STATE_IDLE态,因为考虑到连续传输数据,如果把完整的停止位接收下来,会导致rtrigger信号异常。

 

发送电路的实现

         发送电路相较于接收电路来说简单一些,可完全遵从10种状态来实现,思路和接收电路思路差不多,最大的差别是发送电路是把数据往TX线上送,因此可以使用一个电路选择器来实现:

 

波特率计算

         根据我们上边的讲解,我们生成的时钟必须是串口时钟的两倍,因此即便我们输入的是50M的时钟并且不分频,最大的波特率也只能达到25000000,所以可以认为输入时钟进行了二分频,因此计算公式如下:

baud = clock/2/(division+1)     =>     division = clock/2/baud - 1;

例如clock为50MHz, baud为115200,则 division = 50000000/2/115200 - 1 约等于 216

 

完整代码

/**
 * 说明: clock接入后会二分频,串口固定使用1起始位,1结束位,8数据位,无校验位
 * 波特率计算公式:baud = clock/2/(division+1)     =>     division = clock/2/baud - 1;
 * 例如clock为50MHz, baud为115200,则 division = 50000000/2/115200 - 1 约等于 216
 * time: 2021-02-20
 * edior: huxiang hello
*/
module uart(
    input wire            clock,        // 时钟输入
    input wire            reset,        // 复位(低电平有效)
    input wire [31:0]     division,     // 分频系数
    output wire [7:0]     rdata,        // 读数据
    input wire [7:0]      tdate,        // 发数据
    output wire           rfinish,      // 收到数据触发线 (高电平触发)
    output wire           tfinish,      // 发送完成触发线 (高电平触发)
    input wire            ttrigger,     // 发送数据更新触发线 (高电平触发)
    output reg            tx,           // 串口tx
    input wire            rx            // 串口rx
    );
    
    // ----------------------------- 公用状态符号 ----------------------------------
    localparam      STATE_START = 4'd0;     // 起始
    localparam      STATE_BIT0  = 4'd1;     // 第0位
    localparam      STATE_BIT1  = 4'd2;     // 第1位
    localparam      STATE_BIT2  = 4'd3;     // 第2位
    localparam      STATE_BIT3  = 4'd4;     // 第3位
    localparam      STATE_BIT4  = 4'd5;     // 第4位
    localparam      STATE_BIT5  = 4'd6;     // 第5位
    localparam      STATE_BIT6  = 4'd7;     // 第6位
    localparam      STATE_BIT7  = 4'd8;     // 第7位
    localparam      STATE_STOP  = 4'd9;     // 结束
    localparam      STATE_IDLE  = 4'd10;    // 空闲
    
    // ----------------------------- 发送部分逻辑 ----------------------------------
    // 处理分频计数
    reg [32:0]      tfcount;             // 分频计数 - 最后一位特殊用法,实现clock二分频
    wire            tfcount_clear;       // tfcount清0信号, 高电平脉冲
    assign tfcount_clear = (tfcount == {division, 1'b1}) ? 1'b1 : 1'b0;
    always @(negedge clock)              // 考虑到clock线延迟, 所以使用下降沿触发
    begin
        if(!reset || tfcount_clear || ttrigger)
            tfcount <= 33'd0;
        else
            tfcount <= tfcount + 33'd1;
    end
    // 处理发送状态
    reg [3:0]       tstate;             // 发送状态机
    wire tx_idle;                       // tx线空闲
    assign tx_idle = (tstate == STATE_IDLE) ? 1'b1 : 1'b0;
    always @(posedge clock)
    begin
        if(!reset)
            tstate <= STATE_IDLE;       // 默认处于空闲状态
        else if(tfcount_clear && !tx_idle)
            tstate <= tstate + 4'd1;    // 处于发送态
        else if(ttrigger)
            tstate <= STATE_START;      // 切换到起始态
        else;
    end
    // 处理tx线
    always @(*)     // 这里会综合成多路选择器
    case(tstate)
        STATE_START: tx = 1'b0;
        STATE_BIT0:  tx = tdate[0];
        STATE_BIT1:  tx = tdate[1];
        STATE_BIT2:  tx = tdate[2];
        STATE_BIT3:  tx = tdate[3];
        STATE_BIT4:  tx = tdate[4];
        STATE_BIT5:  tx = tdate[5];
        STATE_BIT6:  tx = tdate[6];
        STATE_BIT7:  tx = tdate[7];
        STATE_STOP:  tx = 1'b1;
        STATE_IDLE:  tx = 1'b1;
        default:     tx = 1'b1;
    endcase
    // 处理tfinish
    reg [3:0]       old_tstate;
    always @(posedge clock) old_tstate <= tstate;
    assign tfinish = (tx_idle && old_tstate==STATE_STOP) ? 1'b1 : 1'b0;
    
    // ----------------------------- 接收部分逻辑 ----------------------------------
    // 未确保连续接收正确,接收部分处于STATE_STOP状态时就会发出读数据完成信号
    // 处理分频计数
    reg [31:0]      rfcount;             // 分频计数
    wire            rfcount_clear;       // rfcount清0信号, 高电平脉冲
    assign rfcount_clear = (rfcount == division) ? 1'b1 : 1'b0;
    reg            rtrigger;             // 开始读信号 (高触发)
    always @(negedge clock)              // 考虑到clock线延迟, 所以使用下降沿触发
    begin
        if(!reset || rfcount_clear || rtrigger)
            rfcount <= 32'd0;
        else
            rfcount <= rfcount + 32'd1;
    end
    // 处理接收状态
    reg [4:0]       rstate;             // 接收状态机 - 最后一位特殊用法, 中间采样
    wire  rx_idle;                      // rx空闲时为高电平
    assign rx_idle = (rstate[4:1] == STATE_STOP) ? 1'b1 : 1'b0;
    always @(posedge clock)
    begin
        if(!reset)
            rstate <= {STATE_STOP,1'b0}; // 默认处于停止状态
        else if(rfcount_clear && !rx_idle)
            rstate <= rstate + 4'd1;    // 处于接收态
        else if(rtrigger)
            rstate <= {STATE_START,1'b0};// 切换到起始态
        else;
    end
    // 从rx采样电平
    reg [7:0] rdata_mov;
    always @(posedge rstate[0] or negedge reset)    // 综合成移位寄存器
    begin
        if(!reset)
            rdata_mov <= 8'd0;
        else
            rdata_mov <= {rx, rdata_mov[7:1]};
    end
    // 数据位连接到rdata
    assign rdata = rdata_mov;
    // 处理rfinish
    reg [3:0]       old_rstate;
    always @(posedge clock) old_rstate <= rstate[4:1];
    assign rfinish = (rx_idle && old_rstate==STATE_BIT7) ? 1'b1 : 1'b0;
    // 生成开始读触发信号
    always @(posedge clock) rtrigger <= (rx_idle && !rx) ? 1'b1 : 1'b0;// rx线在空闲状态被拉低意味着开始
    
endmodule

 

  • 1
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值