FPGA自学笔记--串口通信实现(vivado&verilog版)

最近做了两种通信协议的实现的练习(uart,spi),此文介绍uart串口协议(串口发送)的verilog实现和testbench的编写,考虑到还有部分同学使用vhdl,vhdl版本会随后发布。在以后的系列里,还会有介绍spi协议的文章,把我自己学习中遇到的困难和正确的解决方案记录下来,仿真环境为vivado 2018.3.

一、串口通信协议(uart)

        串口作为常用的三大低速总线(UART、SPI、IIC)之一,在设计众多通信接口和调试时占有重要地位。串口(UART)全称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),主要用于数据间的串行传递,是一种全双工传输模式。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
此文目前只是实现串口发送过程,即将并行数据转化成串行数据发送给其他设备。

作为新手,我觉得串口通信主要要注意以下几点:

1.什么叫异步?异步,即意味着在数据传递的两个模块之间使用的不是同步时钟。实际上在异步串口的传输中是不需要时钟的,而是通过特定的时序来标志传输的开始(起始位--由高到低)和结束(结束位,拉高)。而我们后面讲的SPI协议就是同步方式,是通过一个sclk信号来协同主从设备。 

2.物理层,如下图所示。我们其实只用关心RXD、TXD、GND端口,其他其实不用知道太多,有兴趣的同学可以自行百度。

3.看懂uart发送的时序图。(非常重要!!!!)

二、串口发送的时序图

1.首先要了解 串口发送的数据格式,因为是异步方式,所以数据格式就显得尤为重要,就是要知道通信的起始标志和终止标志,即通信是什么时候开始,是什么时候结束。

        UART 在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如上图所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。

        起始位:起始位标志。低有效。

        数据位:一帧数据中的有效数据。注意,串口协议规定了数据位长度只能位 6 7 8 位。要想发多位数据,只能将其切分为多个过程发送。

        奇偶校验位:是用来验证数据的正确性。奇偶校验一般不使用,如果 使用,则既可以做奇校验(Odd)也可以做偶校验(Even)。其实就是发的时候如果奇校验位为1,表面数据中1的个数为奇数个,再看接受端的奇校验位是否依旧为1。如果是的话,校验成功。在一定概率上保证了传输的正确性。当然,只是概率上的传输正确。此次试验不同奇偶校验。

        停止位:停止位标志着一帧数据的结束,高有效。

        空闲时,传输 1 。

        波特率设置 : 由于是异步通信,所以没有一个统一的时钟,需要双方约定传输传输速率。常见的波特率有:300, 1200, 2400, 9600, 19200, 115200 等.即一秒发送多少位数据。

        所以我们需要实现下面的时序图。当使能信号到来时,发送起始位,数据位,停止位,一帧数据发送结束。

 三、FPGA实现。

 任务: uart_tx模块由单脉冲信号send_go使能,将data[7:0]读入uart_tx模块,发送完成后,输出单脉冲tx_done。

   模块框图:

设计文件:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/04/28 09:19:27
// Design Name: 
// Module Name: uart_tx
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//

module uart_byte_tx (
    clk,
    reset,
    send_go,      // 改成单脉冲启动信号  启动一个内部 send_en 信号
    data,
    baud_set,
    uart_tx,
    tx_done
);

    input clk;
    input reset;
    input send_go;
    input [7:0]data;
    input [2:0]baud_set;
    output reg uart_tx;
    output reg tx_done;
    //  every data remain time 
    
    //  baud_set = 0 bps = 9600  bps_max = 1000 000 000 / 9600 / 20 
    
    reg [17:0]bps_max;
    always@(*)
        case(baud_set)
            0:bps_max = 1000000000/9600/20;  // clk = 50MHZ
            0:bps_max = 1000000000/19200/20;
            0:bps_max = 1000000000/38400/20;
            0:bps_max = 1000000000/57600/20;
            0:bps_max = 1000000000/115200/20;
            default:bps_max = 1000000000/9600/20;
        endcase
        
     reg send_en;
       
     always@(posedge clk or negedge reset)
        if (!reset)
            send_en <= 0;
        else if(send_go)
            send_en <= 1;
        else if(tx_done)
            send_en <= 0;
     //  一旦开始发送,send_go 有效,就将外部的data 锁存起来,,这样防止外部数据变化而采错data【i】
    reg [7:0]r_data;
     always@(posedge clk or negedge reset)
        if (send_go)
            r_data <= data;
        else
            r_data <= r_data;
        
    wire bps_clk;
    assign bps_clk = (div_cnt == 1);
    reg [17:0]div_cnt;
    always @(posedge clk or negedge reset) begin
        if(!reset)
            div_cnt <= 0;
        else if(send_en)
        begin
            if(div_cnt == bps_max - 1)
                div_cnt <= 0;
            else
                div_cnt <= div_cnt + 1'b1;
        end
       else
            div_cnt <= 0; 
    end
    
    reg [3:0]bps_cnt;   // 11 
    always @(posedge clk or negedge reset) begin
        if(!reset)
            bps_cnt <= 0;
        else if(send_en)begin
            if(bps_clk)begin
            //if(div_cnt == 1)begin                                // 当send_en有效事  bps_cnt 才开始加   bps_cnt 不要等到最大再加一 这样会滞后
                if(bps_cnt == 12)
                    bps_cnt <= 0;                         
                else
                    bps_cnt <= bps_cnt + 1'b1;
               end
              end
         else
                bps_cnt <= 0; 
    end
    
    // send
    always @(posedge clk or negedge reset) 
        if(!reset)begin
            uart_tx <= 1'b1;
            tx_done <= 1'b0;
        end
        else begin
            case(bps_cnt)
//                0:begin uart_tx <= 1'b0;tx_done <= 1'b0;end        这样写,bps_cnt 上面是 send_en 为零的时候就 为0  用0 不科学
//                1:uart_tx <= data[0];
//                2:uart_tx <= data[1];
//                3:uart_tx <= data[2];
//                4:uart_tx <= data[3];
//                5:uart_tx <= data[4];
//                6:uart_tx <= data[5];
//                7:uart_tx <= data[6];
//                8:uart_tx <= data[7];
//                9:uart_tx <= 1'b1;
//                10:begin uart_tx <= 1'b1;tx_done <= 1'b1;end
//                default:uart_tx <= 1'b1;
                0:tx_done<=1'b0;
                1:uart_tx <= 1'b0;               
                2:uart_tx <= r_data[0];
                3:uart_tx <= r_data[1];
                4:uart_tx <= r_data[2];
                5:uart_tx <= r_data[3];
                6:uart_tx <= r_data[4];
                7:uart_tx <= r_data[5];
                8:uart_tx <= r_data[6];
                9:uart_tx <= r_data[7];
                10:uart_tx <= 1'b1;
                11:begin uart_tx <= 1'b1;end
                default:uart_tx <= 1'b1;
            endcase
        end
        // 为了保证 tx_done 只持续一个时钟周  单独写一个process
        always @(posedge clk or negedge reset) 
            if(!reset)
                tx_done <= 1'b0;
            else if ((bps_clk == 1) && (bps_cnt == 10))
                tx_done <= 1'b1;
            else
                tx_done <= 1'b0; 
endmodule

testbench文件:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/05/01 14:23:59
// Design Name: 
// Module Name: uart_byte_tx_tb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module uart_byte_tx_tb(
    );
    reg clk;
    reg reset;
    reg send_go;
    reg [7:0]data;
    wire uart_tx;
    wire tx_done;
    
uart_byte_tx uart_byte_tx (
    .clk(clk),
    .reset(reset),
    .send_go(send_go),      // 改成单脉冲启动信号  启动一个内部 send_en 信号
    .data(data),
    .baud_set(3'd4),
    .uart_tx(uart_tx),
    .tx_done(tx_done)
);
    initial clk = 1;
   always#10 clk = ~clk;
    
    initial begin
        reset = 0;
        #201;
        reset = 1;
        send_go = 1;
        data = 8'h57;
        #20
        send_go = 0;
        
        @(posedge tx_done);
        #201;
        reset = 1;
        send_go = 1;
        data = 8'h75;
        #20
        send_go = 0;
        
        @(posedge tx_done);
        #20000;
        $stop;
    end
endmodule

仿真结果: 

由上图结果我们可以看到: 当send_go信号来临时(单脉冲),至于为什么设计成单脉冲,因为单脉冲更符合模块与模块之间的握手习惯。这个主要参考B站上小梅哥的视频。现在我们继续看这个仿真结果,首先发送01010111这个数据,串口规定从低位到高位,所以加上起始标志和结束标志,就是0111010101十位数据,证明时序的正确性。每发送完一个数据,产生一个tx_done单脉冲。工程文件可以直接下载我的资源(0积分)串口通信实现(verilog带testbench文件)-嵌入式文档类资源-CSDN文库

  • 15
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 23
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

上园村蜻蜓队长

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

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

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

打赏作者

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

抵扣说明:

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

余额充值