当前位置:   article > 正文

FPGA开发(1)——串口通信_基于fpga的串口通信设计

基于fpga的串口通信设计

1、RS232 通信协议简介
1、RS232 是 UART 的一种,没有时钟线,只有两根数据线,分别是 rx 和 tx,这两根线都是 1bit 位宽的。其中 rx 是接收数据的线,tx 是发送数据的线。
2、rx 位宽为 1bit,PC 机通过串口调试助手往 FPGA 发 8bit 数据时,FPGA 通过串口线rx 一位一位地接收,从最低位到最高位依次接收,最后在 FPGA 里面位拼接成 8 比特数据。
3、tx 位宽为 1bit,FPGA 通过串口往 PC 机发 8bit 数据时,FPGA 把 8bit 数据通过 tx线一位一位的传给 PC 机,从最低位到最高位依次发送,最后上位机通过串口助手按照RS232 协议把这一位一位的数据位拼接成 8bit 数据。
4、串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。每一帧除了中间包含 8bit 有效数据外,还在每一帧的开头都必须有一个起始位,且固定为 0;在每一帧的结束时也必须有一个停止位,且固定为 1,即最基本的帧结构(不包括校验等)有10bit。在不发送或者不接收数据的情况下,rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持高电平,如果有数据帧传输时,首先会有一个起始位,然后是 8bit 的数据位,接着有 1bit的停止位,然后 rx 和 tx 继续进入空闲状态,然后等待下一次的数据传输。如图所示为一个最基本的 RS232 帧结构。
在这里插入图片描述
5、波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是 1bit 进行传输的,所以其码元就是代表一个二进制数),每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率,常用符号“Baud”表示,其单位为“波特每秒(Bps)”。串口常见的波特率有 4800、9600、115200 等,我们选用 9600 的波特率进行串口章节的讲解。
6、比特率:每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为“每秒比特数(bps)”。比特率可由波特率计算得出,公式为:比特率=波特率 * 单个调制状态对应的二进制位数。如果使用的是 9600 的波特率,其串口的比特率为:9600Bps * 1bit= 9600bps。
7、由计算得串口发送或者接收 1bit 数据的时间为一个波特,即 1/9600 秒,如果用50MHz(周期为 20ns)的系统时钟来计数,需要计数的个数为 cnt = (1s * 10^9)ns / 9600bit)ns / 20ns ≈ 5208 个系统时钟周期,即每个 bit 数据之间的间隔要在 50MHz 的时钟频率下计数 5208 次。
8、上位机通过串口发 8bit 数据时,会自动在发 8 位有效数据前发一个波特时间的起始位,也会自动在发完 8 位有效数据后发一个停止位。同理,串口助手接收上位机发送的数据前,必须检测到一个波特时间的起始位才能开始接收数据,接收完 8bit 的数据后,再接收一个波特时间的停止位。

在这里插入图片描述
2、串口通信程序设计
实验完成PC端通过串口助手发送数据,FPGA接收数据后在发送给PC端,顶层模块框图如下图所示,主要是包含了两个模块,分别是发送和接收模块,分别编写两个模块的代码来进行仿真验证。
在这里插入图片描述
接收模块如下图所示,信号出入端口有时钟、复位和串行数据输入。数据输出端口有串行数据输出和标志位。
在这里插入图片描述
串口发送模块的时序图如下图所示,这边主要是参照野火的教程,详情可以下载野火的文档。
在这里插入图片描述
根据时序图编写我们的verilog代码,数据接收模块的代码如下所示。

module uart_rx 
#(
    parameter freq = 'd50_000_000,
    parameter baud = 'd9600
)
(
    input clk,
    input rst_n,
    input rx,
    
    output reg out_flag,
    output reg [7:0] out_data
);

parameter BAUD_MAX=freq/baud;

reg rx_reg1;
reg rx_reg2;
reg rx_reg3;
reg start_flag;
reg work_en;
reg [3:0]  bit_cnt;
reg bit_flag;
reg [12:0] baud_cnt;
reg [7:0] rx_data;
reg rx_flag;

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        rx_reg1<=1'b1;
    end
    else begin
        rx_reg1<=rx;
    end
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        rx_reg2<=1'b1;
    end
    else begin
        rx_reg2<=rx_reg1;
    end
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        rx_reg3<=1'b1;
    end
    else begin
        rx_reg3<=rx_reg2;
    end
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        start_flag<=1'b0;
    end
    else if(rx_reg2==1'b0 && rx_reg3==1'b1 && work_en==1'b0)begin
      start_flag<=1'b1;
    end
    else start_flag<=1'b0;
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
      work_en<=1'b0;
    end
    else if(start_flag==1'b1)begin
      work_en<=1'b1;
    end
    else if(bit_cnt==4'd8 && bit_flag)begin
        work_en<=1'b0;
    end
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
      baud_cnt<=13'd0;
    end
    else if(baud_cnt == BAUD_MAX-1'b1 || work_en==1'b0)begin
      baud_cnt<=13'd0;
    end
    else baud_cnt<=baud_cnt+1'b1;
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
      bit_flag<=1'b0;
    end
    else if(baud_cnt==BAUD_MAX/2'd2-1'b1)begin
      bit_flag<=1'b1;
    end
    else bit_flag<=1'b0;
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        bit_cnt<=4'd0;
    end
    else if(bit_flag==1'b1 & bit_cnt==4'd8)begin
      bit_cnt<=4'd0;
    end
    else if( bit_flag==1'b1)begin
      bit_cnt<=bit_cnt+1'b1;
    end
    else bit_cnt<=bit_cnt;
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
      rx_data<=8'd0;
    end
    else if(bit_flag==1'b1 && bit_cnt>=4'd1 && bit_cnt<=4'd8)begin
        rx_data<={rx_reg3,rx_data[7:1]};
    end
    else rx_data<=rx_data;
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        rx_flag<=1'b0;
    end
    else if(bit_flag==1'b1 & bit_cnt==4'd8)begin
      rx_flag<=1'b1;
    end
    else rx_flag<=1'b0;
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
      out_data<=8'd0;
    end
    else if(rx_flag==1'b1)begin
      out_data<=rx_data;
    end
    else out_data<=out_data;
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
      out_flag<=1'b0;
    end
    else if(rx_flag==1'b1)begin
      out_flag<=rx_flag;
    end
    else out_flag<=1'b0;
end

endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150

编写串口接收模块的仿真测试代码如下。

//~ `New testbench
`timescale  1ns / 1ps

module tb_uart_rx;

// uart_rx Parameters
parameter PERIOD    = 10          ;
parameter freq      = 'd50_000_0;
parameter baud      = 'd9600      ;
parameter BAUD_MAX  = freq/baud   ;

// uart_rx Inputs
reg   clk                                  = 0 ;
reg   rst_n                                = 0 ;
reg   rx                                   = 0 ;

// uart_rx Outputs
wire  out_flag                             ;
wire  [7:0]  out_data                      ;


initial
begin
    forever #(PERIOD/2)  clk=~clk;
end

initial
begin
    #(PERIOD*2) rst_n  =  1;
end

uart_rx #(
    .freq     ( freq     ),
    .baud     ( baud     )
)
 u_uart_rx (
    .clk                     ( clk             ),
    .rst_n                   ( rst_n           ),
    .rx                      ( rx              ),

    .out_flag                ( out_flag        ),
    .out_data                ( out_data  [7:0] )
);

initial
begin
    #(PERIOD*10);
    rx_bit(8'd0);
    rx_bit(8'd1);
    rx_bit(8'd2);
    rx_bit(8'd3);
end

task rx_bit(
    input [7:0] data
);
integer i;

for(i=0;i<10;i=i+1)begin
  case(i)
    0:rx<=1'b0;
    1:rx<=data[0];
    2:rx<=data[1];
    3:rx<=data[2];
    4:rx<=data[3];
    5:rx<=data[4];
    6:rx<=data[5];
    7:rx<=data[6];
    8:rx<=data[7];
    9:rx<=1'b1;
  endcase
    #(PERIOD*52);
end

endtask

endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

最后的仿真结果图如下图所示,可以看到仿真结果接收模块接收8位的串行数据,接收完一个字节后,输出一个标志位信号和8位长度的数据。
在这里插入图片描述
接下来是发送模块,发送模块主要包括时钟、复位、输入的并行数据、标志位、输出的串行数据。
在这里插入图片描述
时序图如下图所示,具体代码讲解可以参照野火文档。
在这里插入图片描述
串口发送模块的代码如下图所示。

module uart_tx
#(
    parameter freq = 'd50_000_000,
    parameter baud = 'd9600
)
(
    input clk,
    input rst_n,
    input [7:0] in_data,
    input in_flag,
    
     output reg tx    
);

parameter BAUD_MAX=freq/baud;

reg work_en;
reg bit_flag;
reg [3:0] bit_cnt;
reg [12:0] baud_cnt;

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        work_en<=1'b0;
    end
    else if(in_flag==1'b1)begin
      work_en<=1'b1;
    end
    else if(bit_cnt==4'd9 && bit_flag==1'b1)begin
      work_en<=1'b0;
    end
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
      baud_cnt<=13'd0;
    end
    else if(baud_cnt == BAUD_MAX-1'b1 || work_en==1'b0)begin
      baud_cnt<=13'd0;
    end
    else baud_cnt<=baud_cnt+1'b1;
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
      bit_flag<=1'b0;
    end
    else if(baud_cnt==13'd1)begin
        bit_flag<=1'b1;
    end
    else bit_flag<=1'b0;
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
      bit_cnt<=4'd0;
    end
    else if(bit_cnt==4'd9 && bit_flag==1'b1)begin
      bit_cnt<=4'd0;
    end
    else if(bit_flag==1'b1)begin
      bit_cnt<=bit_cnt+1'b1;
    end
    else bit_cnt<=bit_cnt;
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        tx<=1'b1;
    end
    else if(bit_flag==1'b1)begin
      case(bit_cnt)
        4'd0:tx<=1'b0;
        4'd1:tx<=in_data[0];
        4'd2:tx<=in_data[1];
        4'd3:tx<=in_data[2];
        4'd4:tx<=in_data[3];
        4'd5:tx<=in_data[4];
        4'd6:tx<=in_data[5];
        4'd7:tx<=in_data[6];
        4'd8:tx<=in_data[7];
        4'd9:tx<=1'b1;
        default:tx<=1'b1;
      endcase
    end
end

endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

串口发送模块的测试代码如下图所示。

`timescale  1ns / 1ps

module tb_uart_tx;

// uart_tx Parameters
parameter PERIOD    = 10          ;
parameter freq      = 'd50_000_0;
parameter baud      = 'd9600      ;
parameter BAUD_MAX  = freq/baud   ;

// uart_tx Inputs
reg   clk                                  = 0 ;
reg   rst_n                                = 0 ;
reg   [7:0]  in_data                       = 0 ;
reg   in_flag                              = 0 ;

// uart_tx Outputs
wire  tx                                   ;


initial
begin
    forever #(PERIOD/2)  clk=~clk;
end

initial
begin
    #(PERIOD*2) rst_n  =  1;
end

uart_tx #(
    .freq     ( freq     ),
    .baud     ( baud     ))
 u_uart_tx (
    .clk                     ( clk            ),
    .rst_n                   ( rst_n          ),
    .in_data                 ( in_data  [7:0] ),
    .in_flag                 ( in_flag        ),

    .tx                      ( tx             )
);

initial
begin
     in_data<=8'd0;
     in_flag<=1'b0;
     #(PERIOD*10)

     in_data<=8'd0;
     in_flag<=1'b1;
     #(PERIOD)
     in_flag<=1'b0;
     #(PERIOD*52*10)
     
     in_data<=8'd1;
     in_flag<=1'b1;
     #(PERIOD)
     in_flag<=1'b0;
     #(PERIOD*52*10)

     in_data<=8'd2;
     in_flag<=1'b1;
     #(PERIOD)
     in_flag<=1'b0;
     #(PERIOD*52*10)

     in_data<=8'd3;
     in_flag<=1'b1;
     #(PERIOD)
     in_flag<=1'b0;

end

endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

串口发送模块的仿真图如下图所示,可以看到发送模块接收到并行数据和标志位后将数据转换为串行数据通过tx端口发送回PC端口。
在这里插入图片描述
3、上板验证
这边打开串口助手,设置号波特率、数据位、停止位等参数后打开串口。发送数据后在上面屏幕看到从FPGA端发送回来的数据,串口通信成功。
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/455862
推荐阅读
相关标签
  

闽ICP备14008679号