FPGA串口通信实现详解


FPGA 串口通信

基础原理

  1. 并行通信
    数据的各个位使用多条数据线同时进行传输

    传输速度快,但是占用引脚资源多
  2. 串行通信
    将数据分成一位一位的形式在一条传输线上逐个传输

FPGA 串口通信_测试

  1. 通信线路简单,占用引脚资源少,但是传输速度较慢
  2. 串行通信分类
  • 同步通信
    带时钟同步信号的数据传输,发送发和接收方在同一个时钟的控制下,同步传输数
  • 异步通信
    不带时钟同步信号的数据传输,发送方和接收方使用各自的时钟控制数据的发送和接收过程

FPGA 串口通信_数据_02

  1. 传输方向
  • 单工, 只能沿一个方向传输
  • 半双工, 数据传输可以沿两个方向,但是需要分时进行
  • 全双工,可以同时双向传输
  1. 常见串行通信接口

FPGA 串口通信_架构_03

异步串行通信UART

特点: 异步、串行

在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据

  • 协议层:通信协议(数据格式、传输速率)

UART串口通信需要两根信号线来实现,一根用于串口发送,另一个负责串口接收。

校验位: 奇偶校验

串口通信的速率使用波特率来表示,每秒传输的二进制数据的位数 bps

  • 物理层:接口类型,电平标准等
    异步串行通信的接口标准: RS232, RS422, RS485
  • RS232接口
    常见接口有DB9接口

常用就三个引脚:RXD, TXD, GND

Verilog 实现

串口接收

1. 介绍
  1. 简单介绍
    在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据
  • 空闲状态时,为高电平
  • 起始位为一个单位长度低电平,停止位为一个长度高电平
  1. 分析
  • 8位数据位
  • 1位停止位
  • 无校验位
  1. 基本思路

采集每一位中间时刻的数据作为这一位的数据 ( 也可以每一位多采几个时刻的数据,取众数 )

  1. 框图
  2. 状态机
2. 程序实现

严格按照状态机实现

程序:


`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: wkk
// Create Date: 2022/11/22 16:35:19
// Module Name: uart_rx
// Description: uart rx function
//////////////////////////////////////////////////////////////////////////////////
module uart_rx(
   input               sys_clk,
   input               sys_rst_n,
   input               uart_rx,
   output              uart_rx_valid,
   output  [7:0]       uart_rx_data
);

parameter  SYS_CLK         = 100_000_000;
// 115200
parameter  BAUD_COUNT      =  868;
parameter  BAUD_HALF_COUNT =  434;
parameter  TIME_COUNT_LEN  =  12;

localparam IDLE_STATE       = 4'd0;
localparam START_STATE      = 4'd1;
localparam RECV_STATE       = 4'd2;
localparam RECV_D0_STATE    = 4'd3;
localparam RECV_D1_STATE    = 4'd4;
localparam RECV_D2_STATE    = 4'd5;
localparam RECV_D3_STATE    = 4'd6;
localparam RECV_D4_STATE    = 4'd7;
localparam RECV_D5_STATE    = 4'd8;
localparam RECV_D6_STATE    = 4'd9;
localparam RECV_D7_STATE    = 4'd10;
localparam END_STATE        = 4'd11;

reg [3:0]   curr_state;
reg [3:0]   next_state;

reg         uart_rx_d0;
reg         uart_rx_d1;
wire        uart_rx_en;

// 开始计时
reg         time_en;
// 计时模式 0: 计数一个波特率周期 1: 计数半个波特率周期
reg         half_en;
reg         count_en;
reg         [TIME_COUNT_LEN-1:0] time_count;

reg  [7:0]  rx_data;
reg  [3:0]  rx_data_index;

// 计时模块
always @(posedge sys_clk or negedge sys_rst_n) begin
   if(!sys_rst_n || !time_en) begin
       time_count <= 0;
       count_en <= 0;
   end
   else
   if(half_en)
       if(time_count == BAUD_HALF_COUNT -1 )begin
           time_count <=0;
           count_en <= 1;
       end
       else begin
           time_count <= time_count + 1'b1;
           count_en <= 0;
       end
   else
        if(time_count == BAUD_COUNT -1 )begin
           count_en <= 1;
           time_count <= 0;
        end
        else begin
           time_count <= time_count + 1'b1;
           count_en <= 0;
       end
end

// 产生下一状态
always @(*) begin
   case( curr_state )
   IDLE_STATE: begin
       if( uart_rx_en )
           next_state = START_STATE;
       else
           next_state = IDLE_STATE;
   end
   START_STATE:
       if( count_en)
           next_state = RECV_STATE;
       else
           next_state = START_STATE;
   RECV_STATE:
       if( count_en )
           next_state = RECV_D0_STATE;
       else
           next_state = RECV_STATE;
   RECV_D0_STATE:
       if( count_en )
           next_state = RECV_D1_STATE;
       else
           next_state = RECV_D0_STATE;
   RECV_D1_STATE:
       if( count_en )
           next_state = RECV_D2_STATE;
       else
           next_state = RECV_D1_STATE;
   RECV_D2_STATE:
       if( count_en )
           next_state = RECV_D3_STATE;
       else
           next_state = RECV_D2_STATE;
   RECV_D3_STATE:
       if( count_en )
           next_state = RECV_D4_STATE;
       else
           next_state = RECV_D3_STATE;
   RECV_D4_STATE:
       if( count_en )
           next_state = RECV_D5_STATE;
       else
           next_state = RECV_D4_STATE;
   RECV_D5_STATE:
       if( count_en )
           next_state = RECV_D6_STATE;
       else
           next_state = RECV_D5_STATE;
   RECV_D6_STATE:
       if( count_en )
           next_state = RECV_D7_STATE;
       else
           next_state = RECV_D6_STATE;
   RECV_D7_STATE:
       if( count_en )
           next_state = END_STATE;
       else
           next_state = RECV_D7_STATE;
   END_STATE:
       next_state = IDLE_STATE;
   default: ;
   endcase
end

assign uart_rx_data = rx_data;
assign uart_rx_valid = (curr_state == END_STATE)?1'b1:1'b0;

// 状态输出
always @(posedge sys_clk or negedge sys_rst_n) begin
   if(!sys_rst_n) begin
      rx_data <= 7'b0;
      time_en <= 1'b0;
      half_en <= 1'b0;
      rx_data_index <= 3'b0;
   end else
   case(curr_state)
   IDLE_STATE: begin
       time_en <= 1'b0;
       half_en <= 1'b0;
       rx_data_index <= 3'b0;
   end
   START_STATE: begin
       time_en <= 1'b1;
       half_en <= 1'b1;
   end
   RECV_STATE:begin
      time_en <= 1'b1;
      half_en <= 1'b0;
   end
   RECV_D0_STATE:
       if(rx_data_index == 3'd0)begin
           rx_data[0] <= uart_rx;
           rx_data_index <= rx_data_index + 1'b1;
       end else
           rx_data[0] <= rx_data[0];
   RECV_D1_STATE:
       if(rx_data_index == 3'd1)begin
           rx_data[1] <= uart_rx;
           rx_data_index <= rx_data_index + 1'b1;
       end else
           rx_data[1] <= rx_data[1];
   RECV_D2_STATE:
       if(rx_data_index == 3'd2)begin
           rx_data[2] <= uart_rx;
           rx_data_index <= rx_data_index + 1'b1;
       end else
           rx_data[2] <= rx_data[2];
   RECV_D3_STATE:
       if(rx_data_index == 3'd3)begin
           rx_data[3] <= uart_rx;
           rx_data_index <= rx_data_index + 1'b1;
       end else
           rx_data[3] <= rx_data[3];
   RECV_D4_STATE:
       if(rx_data_index == 3'd4)begin
           rx_data[4] <= uart_rx;
           rx_data_index <= rx_data_index + 1'b1;
       end else
           rx_data[4] <= rx_data[4];
   RECV_D5_STATE:
       if(rx_data_index == 3'd5)begin
           rx_data[5] <= uart_rx;
           rx_data_index <= rx_data_index + 1'b1;
       end else
           rx_data[5] <= rx_data[5];
   RECV_D6_STATE:
       if(rx_data_index == 3'd6)begin
           rx_data[6] <= uart_rx;
           rx_data_index <= rx_data_index + 1'b1;
       end else
           rx_data[6] <= rx_data[6];
   RECV_D7_STATE:
       if(rx_data_index == 3'd7)begin
           rx_data[7] <= uart_rx;
           rx_data_index <= rx_data_index + 1'b1;
       end else
           rx_data[7] <= rx_data[7];
   END_STATE:begin
       time_en <= 1'b0;
       half_en <= 1'b0;
       rx_data_index <= 3'b0;
   end
   default: ;
   endcase
end

// catch rising edge
assign uart_rx_en = (uart_rx_d0 & !uart_rx_d1) ? 1'b1:1'b0;

always @(posedge sys_clk or negedge sys_rst_n) begin
   if(!sys_rst_n) begin
       uart_rx_d0 <= 1'b0;
       uart_rx_d1 <= 1'b0;
   end else begin
       uart_rx_d1 <= uart_rx;
       uart_rx_d0 <= uart_rx_d1;
   end
end

// update curr_state
always @(posedge sys_clk or negedge sys_rst_n) begin
   if(!sys_rst_n)
       curr_state <= IDLE_STATE;
   else
       curr_state <= next_state;
end

endmodule

testbench:


`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Engineer: wkk
// Module Name: uart_rx_tb
//////////////////////////////////////////////////////////////////////////////////

module uart_rx_tb;
reg         sys_clk;
reg         sys_rst_n;
reg         uart_rx;
wire        uart_rx_valid;
wire [7:0]  uart_rx_data;

parameter BAUD_COUNT      = 20;
parameter BAUD_HALF_COUNT = 10;
parameter TIME_COUNT_LEN  = 5;
uart_rx #(
    .BAUD_COUNT     (BAUD_COUNT),
    .BAUD_HALF_COUNT(BAUD_HALF_COUNT),
    .TIME_COUNT_LEN (TIME_COUNT_LEN)
)u_uart_rx(
    .sys_clk        (sys_clk),
    .sys_rst_n      (sys_rst_n),
    .uart_rx        (uart_rx),
    .uart_rx_valid  (uart_rx_valid),
    .uart_rx_data   (uart_rx_data)
);

initial begin
    sys_clk = 0;
    sys_rst_n = 0;
    uart_rx = 1;
 end

 always #5 sys_clk = !sys_clk;

 initial begin
    #10 sys_rst_n = 1;
    #30 uart_rx = 0;  // 起始位
    #200 uart_rx = 0;
    #200 uart_rx = 1;
    #200 uart_rx = 1;
    #200 uart_rx = 1;
    #200 uart_rx = 0;
    #200 uart_rx = 1;
    #200 uart_rx = 1;
    #200 uart_rx = 0;
    #200 uart_rx = 1; // 停止位
    #450
    $stop;
 end

 endmodule
 

FPGA 串口通信_测试_04

非严格按照状态机实现( 目前使用 )

程序


`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Engineer: wkk
//
// Create Date: 2023/03/15 09:37:21
// Design Name:
// Module Name: uart_rx
//
//////////////////////////////////////////////////////////////////////////////////
module uart_rx(
    input          i_clk ,
    input          i_rst_n,
    input          i_data,
    output [7:0]   o_data,
    output         o_data_valid
);

parameter       I_CLK_FREQ   =  27_000_000       ;
parameter       BAUDRATE     =  115200           ;
parameter       COUNTER_LEN  =  12               ;
localparam      COUNT_MAX    =  I_CLK_FREQ / BAUDRATE ;
reg             i_data_d0       ;
reg             i_data_d1       ;
wire            i_data_negedge_valid  ;

reg             start_rx        ; // 开始接收
reg [4:0]       bit_index       ;

reg [COUNTER_LEN-1:0]       time_counter              ;
wire                        counter_en                ;
wire                        counter_half_en           ;

reg [7:0]                   o_data_reg                ;
// 检测下降沿
assign i_data_negedge_valid = i_data_d1 & (~i_data_d0);
always @(posedge i_clk or negedge i_rst_n) begin
    if( !i_rst_n ) begin
        i_data_d0 <= 1'b1;
        i_data_d1 <= 1'b1;
    end else begin
        i_data_d0 <= i_data;
        i_data_d1 <= i_data_d0;
    end
end

// 开始信号
always @(posedge i_clk or negedge i_rst_n) begin
    if( !i_rst_n )
        start_rx <= 1'b0;
    else if(start_rx == 1'b0 && i_data_negedge_valid)
            start_rx <= 1'b1;
    else if(start_rx == 1'b1 && bit_index== 4'd9)
            start_rx <= 1'b0;
    else
        start_rx <= start_rx;
end

assign counter_half_en = (time_counter == (COUNT_MAX >> 1 ));
assign counter_en = (time_counter == COUNT_MAX-1);
// 计时器
always @(posedge i_clk or negedge i_rst_n) begin
    if( !i_rst_n )
        time_counter <= {COUNTER_LEN{1'b0}};
    else if(start_rx)
        if(time_counter == COUNT_MAX-1)
            time_counter <= {COUNTER_LEN{1'b0}};
        else
            time_counter <= time_counter+1'b1;
    else
        time_counter <= {COUNTER_LEN{1'b0}};
end
// bit_index 控制
always @(posedge i_clk or negedge i_rst_n) begin
    if( !i_rst_n )
        bit_index <= 4'b0;
    else if(start_rx)
        if(counter_en)
            bit_index <= bit_index + 4'b1;
        else
            bit_index <= bit_index;
    else
        bit_index <= 4'b0;
end

//输出
always @(posedge i_clk or negedge i_rst_n) begin
    if( !i_rst_n )
        o_data_reg <= 7'b0;
    else if( counter_half_en )
    case ( bit_index )
        4'd1:
            o_data_reg[0] <= i_data_d0;
        4'd2:
            o_data_reg[1] <= i_data_d0;
        4'd3:
            o_data_reg[2] <= i_data_d0;
        4'd4:
            o_data_reg[3] <= i_data_d0;
        4'd5:
            o_data_reg[4] <= i_data_d0;
        4'd6:
            o_data_reg[5] <= i_data_d0;
        4'd7:
            o_data_reg[6] <= i_data_d0;
        4'd8:
            o_data_reg[7] <= i_data_d0;
        default:
            o_data_reg <= o_data_reg;
    endcase
    else
        o_data_reg <= o_data_reg;
end

assign o_data_valid = (bit_index== 4'd9);
assign o_data  = o_data_reg;

endmodule

testbench


`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer: wkk
//
// Create Date: 2023/03/15 10:03:32
// Design Name:
// Module Name: usart_rx_tb
// Project Name:
//////////////////////////////////////////////////////////////////////////////////


module usart_rx_tb();
reg i_clk;
reg i_rst_n;
reg i_data;
wire  [7:0] o_data;
wire o_data_valid;

uart_rx#(
.I_CLK_FREQ(10),
.BAUDRATE  (2)
)uart_rx_inst(
    i_clk ,
    i_rst_n,
    i_data,
    o_data,
    o_data_valid
);

initial begin
    i_clk = 1'b0;
    i_rst_n = 1'b0;
    i_data  = 1'b1;
end
always #5 i_clk = ~i_clk;

initial begin
    $display("start\r\n--------------------");
    $monitor($time,"o_data_valid: %b",o_data_valid );
    #10 i_rst_n = 1'b1;
    #50 i_data = 1'b0;

    #50 i_data = 1'b1;
    #50 i_data = 1'b0;
    #50 i_data = 1'b1;
    #50 i_data = 1'b0;
    #50 i_data = 1'b1;
    #50 i_data = 1'b1;
    #50 i_data = 1'b1;
    #50 i_data = 1'b0;

    #50 i_data = 1'b1;
    #50
    //01110101
    #100;
    $stop;
end

endmodule

串口发送

1. 介绍
  1. 简单介绍
    在发送数据时将并行数据转换成串行数据来传输

空闲状态为高电平,发送的起始位为一个低电平,发送的停止位为一个高电平

  1. 分析-时序
  2. 框图
  3. 状态机
2. 程序实现

严格按照状态机实现

程序


`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: wkk
// Create Date: 2022/12/02 20:41:01
// Module Name: uart_tx
// Description: uart_tx demo
//////////////////////////////////////////////////////////////////////////////////
module uart_tx(
    input                   sys_clk     ,
    input                   sys_rst_n   ,
    input                   uart_w_en   ,
    input  wire [7:0]       uart_data   ,
    output                  uart_out
);

parameter   SYS_CLK         = 100_000_000   ;
parameter   TIME_MAX_COUNT  = 868           ;
parameter   TIME_COUNT_LEN  = 12            ;

localparam   IDLE_STATE     = 4'd0  ;
localparam   START_STATE    = 4'd1  ;
localparam   D0_STATE       = 4'd2  ;
localparam   D1_STATE       = 4'd3  ;
localparam   D2_STATE       = 4'd4  ;
localparam   D3_STATE       = 4'd5  ;
localparam   D4_STATE       = 4'd6  ;
localparam   D5_STATE       = 4'd7  ;
localparam   D6_STATE       = 4'd8  ;
localparam   D7_STATE       = 4'd9  ;
localparam   END_STATE       = 4'd10;

reg [7:0] uart_tx_data;

reg [3:0] next_state;
reg [3:0] curr_state;

reg     [TIME_COUNT_LEN-1:0]    time_counter;
wire    time_en;
reg     count_en;

reg    uart_tx_out;

// update state
always @(*) begin
    if(!sys_rst_n)
        curr_state = IDLE_STATE;
    else
        curr_state = next_state;
end


assign time_en = (time_counter == TIME_MAX_COUNT -1)? 1'b1:1'b0;
// timer
always @(posedge sys_clk or negedge sys_rst_n ) begin
     if(!sys_rst_n || count_en == 0 )
        time_counter <= 'd0;
     else if(time_counter == TIME_MAX_COUNT -1 )
        time_counter <= 'd0;
     else
        time_counter <= time_counter + 1'd1;
end

// create next state
always @(posedge sys_clk or negedge sys_rst_n ) begin
    if(!sys_rst_n) begin
        next_state <= IDLE_STATE;
    end
    else
    case(curr_state)
    IDLE_STATE :
        if(uart_w_en)
            next_state <= START_STATE;
        else
            next_state <= next_state;
    START_STATE:
        if(time_en)
            next_state <= D0_STATE;
        else
            next_state <= next_state;
    D0_STATE   :
        if(time_en)
            next_state <= D1_STATE;
        else
            next_state <= next_state;
    D1_STATE   :
        if(time_en)
            next_state <= D2_STATE;
        else
            next_state <= next_state;
    D2_STATE   :
        if(time_en)
            next_state <= D3_STATE;
        else
            next_state <= next_state;
    D3_STATE   :
        if(time_en)
            next_state <= D4_STATE;
        else
            next_state <= next_state;
    D4_STATE   :
        if(time_en)
            next_state <= D5_STATE;
        else
            next_state <= next_state;
    D5_STATE   :
        if(time_en)
            next_state <= D6_STATE;
        else
            next_state <= next_state;
    D6_STATE   :
        if(time_en)
            next_state <= D7_STATE;
        else
            next_state <= next_state;
    D7_STATE   :
        if(time_en)
            next_state <= END_STATE;
        else
            next_state <= next_state;
    END_STATE  :
        if(time_en)
            next_state <= IDLE_STATE;
        else
            next_state <= next_state;
    default:
        next_state <= IDLE_STATE;
    endcase
end

assign  uart_out = uart_tx_out;
// out
always @(posedge sys_clk or negedge sys_rst_n ) begin
    if(!sys_rst_n)begin
        uart_tx_out <= 1'b1;
        uart_tx_data <= 8'd0;
        count_en <= 1'b0;
    end
    else case(curr_state)
    IDLE_STATE :  begin
        uart_tx_out <= 1'b1;
        count_en <= 1'b0;
    end
    START_STATE:  begin
        uart_tx_out <= 1'b0;
        count_en <= 1'b1;
        uart_tx_data <= uart_data;
    end
    D0_STATE   :
        uart_tx_out <= uart_tx_data[0];
    D1_STATE   :
        uart_tx_out <= uart_tx_data[1];
    D2_STATE   :
        uart_tx_out <= uart_tx_data[2];
    D3_STATE   :
        uart_tx_out <= uart_tx_data[3];
    D4_STATE   :
        uart_tx_out <= uart_tx_data[4];
    D5_STATE   :
        uart_tx_out <= uart_tx_data[5];
    D6_STATE   :
        uart_tx_out <= uart_tx_data[6];
    D7_STATE   :
        uart_tx_out <= uart_tx_data[7];
    END_STATE  :  begin
        uart_tx_out <= 1'b1;
        count_en <= 1'b0;
    end
    default: begin
        uart_tx_out <= 1'b1;
        count_en <= 1'b0;
    end
endcase
end
endmodule

testbench

登录后复制

`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Engineer: wkk
// Create Date: 2022/12/02 20:41:01
// Module Name: uart_tx_tb
// Description: uart_tx demo testbench
//////////////////////////////////////////////////////////////////////////////////
module uart_tx_tb;
reg             sys_clk     ;
reg             sys_rst_n   ;
reg             uart_w_en   ;
reg   [7:0]     uart_data   ;
wire            uart_out    ;

uart_tx #(
    .TIME_MAX_COUNT (2),
    .TIME_COUNT_LEN (2)
)u_uart_tx(
    .sys_clk   (sys_clk  ),
    .sys_rst_n (sys_rst_n),
    .uart_w_en (uart_w_en),
    .uart_data (uart_data),
    .uart_out  (uart_out )
);
initial begin
    sys_clk             = 1'b0;
    sys_rst_n           = 1'b0;
    uart_w_en           = 1'b0;
end

always #5  sys_clk = ~sys_clk;

initial begin
    #10 sys_rst_n = 1;
    #10
    uart_data = 8'b10011101;
    uart_w_en = 1;
    #20
    uart_w_en = 0;
    #20000
    $stop;
end
endmodule

FPGA 串口通信_数据_05

  • uart_w_en 信号最少要持续2个时钟周期
  • uart_w_en 信号如果持续超过一个串口数据帧的时间长度,会重复发送
  • 非严格按照状态机实现( 目前使用 )

程序


`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Engineer: wkk
//
// Create Date: 2023/03/14 22:54:49
// Design Name:
// Module Name: uart_tx
//////////////////////////////////////////////////////////////////////////////////

module uart_tx(
    input               i_clk                   ,
    input               i_rst_n                 ,
    input   [7:0]       i_data                  ,
    input               i_data_valid            ,
    output              o_data
);
parameter    I_CLK_FREQ     = 27_000_00                   ;
parameter    BAUDRATE       = 115200                      ;
parameter    COUNTER_LEN    = 12                          ;

localparam    COUNT_MAX  = I_CLK_FREQ / BAUDRATE          ;

reg         [7:0]               i_data_reg                ;
reg                             i_data_reg_valid          ;

reg                             start_tx                  ;
reg         [3:0]               bit_num                   ;
reg                             o_data_reg                ;
reg         [COUNTER_LEN-1:0]   time_counter              ;
wire                            time_counter_en           ;


// 缓存数据
always @(posedge i_clk or negedge i_rst_n)  begin
    if(! i_rst_n ) begin
        i_data_reg <= 7'b0;
        i_data_reg_valid <= 1'b0;
    end
    else if( i_data_valid ) begin
        i_data_reg <= i_data;
        i_data_reg_valid <= 1'b1;
    end
    else begin
        i_data_reg <= i_data_reg;
        i_data_reg_valid <= 1'b0;
    end
end

always @(posedge i_clk or negedge i_rst_n)begin
     if(! i_rst_n )
        start_tx <= 1'b0;
     else if(i_data_reg_valid)
        start_tx <= 1'b1;
     else if(bit_num == 4'd9)
        start_tx <= 1'b0;
     else
        start_tx <= start_tx;
end

assign time_counter_en = (time_counter == COUNT_MAX -1) ? 1'b1 :1'b0;
always @(posedge i_clk or negedge i_rst_n) begin
    if(! i_rst_n )
        time_counter <= {COUNTER_LEN{1'b0}};
    else if( start_tx )
        if(time_counter == COUNT_MAX -1 )
            time_counter <= {COUNTER_LEN{1'b0}};
        else
            time_counter <= time_counter + 1'b1;
    else
        time_counter <= {COUNTER_LEN{1'b0}};
end

always @(posedge i_clk or negedge i_rst_n) begin
    if(! i_rst_n )
        bit_num <= 4'b0;
    else if( start_tx )
        if( time_counter_en )
            bit_num <= bit_num +1'b1;
        else
            bit_num <= bit_num;
    else
        bit_num <= 4'b0;
end

always @(posedge i_clk or negedge i_rst_n) begin
    if(! i_rst_n )
        o_data_reg <= 1'b1;
    else if( start_tx )
        case( bit_num)
           4'd0: o_data_reg <= 1'b0;
           4'd1: o_data_reg <= i_data_reg[0];
           4'd2: o_data_reg <= i_data_reg[1];
           4'd3: o_data_reg <= i_data_reg[2];
           4'd4: o_data_reg <= i_data_reg[3];
           4'd5: o_data_reg <= i_data_reg[4];
           4'd6: o_data_reg <= i_data_reg[5];
           4'd7: o_data_reg <= i_data_reg[6];
           4'd8: o_data_reg <= i_data_reg[7];
           4'd9: o_data_reg <= 1'b1;
           default: o_data_reg <= 1'b1;
        endcase
    else
        o_data_reg = 1'b1;
end
assign o_data = o_data_reg;

endmodule

testbench


`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer: wkk
//
// Create Date: 2023/03/14 23:52:48
// Design Name:
// Module Name: usart_tx_tb
// Project Name:
//////////////////////////////////////////////////////////////////////////////////

module usart_tx_tb();
reg i_clk                   ;
reg i_rst_n                 ;
reg [7:0] i_data            ;
reg i_data_valid            ;
wire o_data                 ;

uart_tx # (
    .I_CLK         (20),
    .BAUDRATE      (10)
)uart_tx_inst(
   i_clk                   ,
   i_rst_n                 ,
   i_data                  ,
   i_data_valid            ,
   o_data
);

initial begin
    i_clk = 1'b0;
    i_rst_n = 1'b0;
    i_data_valid = 1'b0;
end

always #10 i_clk = ~i_clk;

initial begin
    #20;
    i_rst_n = 1'b1;
    #20;
    i_data = 8'b10110001;
    i_data_valid= 1'b1;
    #20
    i_data_valid = 1'b0;
    #500;
    i_data = 8'b11111111;
    i_data_valid= 1'b1;
    #20
    i_data_valid = 1'b0;

    //$monitor($time,"\to_data: %b",o_data);
    #100;
    $stop;
end


endmodule

回环测试

测试框图

FPGA 串口通信_后端_06

测试代码
  • verilog
`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer: wkk
//
// Create Date: 2023/03/15 15:35:05
// Design Name:
// Module Name: usart_demo
//
/////////////////////////////////////////////////////////////////////////////////
module usart_demo(
    input       i_clk           ,
    input       i_rst_n         ,
    input       i_data          ,
    output      o_data
);

parameter I_CLK_FREQ =  100_000_000   ;
parameter BAUDRATE   =  115200       ;

wire  [7:0]         data;
wire                data_valid;


uart_rx#(
     .I_CLK_FREQ(I_CLK_FREQ),
     .BAUDRATE(BAUDRATE)
)uart_rx_inst(
   .i_clk            (i_clk),
   .i_rst_n          (i_rst_n),
   .i_data           (i_data),
   .o_data           (data),
   .o_data_valid     (data_valid)
);

uart_tx#(
    .I_CLK_FREQ(I_CLK_FREQ),
    .BAUDRATE(BAUDRATE)
)uart_tx_inst(
    .i_clk                   (i_clk),
    .i_rst_n                 (i_rst_n),
    .i_data                  (data),
    .i_data_valid            (data_valid),
    .o_data                  (o_data)
);

endmodule
  • testbench
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:  wkk
//
// Create Date: 2023/03/15 15:43:27
// Design Name:
// Module Name: usart_demo_tb
//
//////////////////////////////////////////////////////////////////////////////////


module usart_demo_tb();
reg     i_clk    ;
reg     i_rst_n  ;
reg     i_data   ;
wire    o_data   ;

usart_demo usart_demo_inst(
     i_clk           ,
     i_rst_n         ,
     i_data          ,
     o_data
);

initial  begin
    i_clk = 1'b0;
    i_rst_n  = 1'b0;
end

always #5 i_clk = ~i_clk;

initial  begin
    #10 i_rst_n = 1'b1;
    #20 i_data  = 1'b0;

    #50 i_data  = 1'b1;
    #50 i_data  = 1'b1;
    #50 i_data  = 1'b0;
    #50 i_data  = 1'b1;
    #50 i_data  = 1'b0;
    #50 i_data  = 1'b0;
    #50 i_data  = 1'b1;
    #50 i_data  = 1'b1;

    #50 i_data  = 1'b1;

    #100 $stop;
end

endmodule
  • 实测结果

米联客参考代码

串口接收


`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////
/*
Company : Liyang Milian Electronic Technology Co., Ltd.
Brand: 米联客(msxbo)
Technical forum:uisrc.com
taobao: osrc.taobao.com
Create Date: 2019/02/27 22:09:55
Module Name: uart_rx_path
Description:
The serial port receiving module has a baud rate of 9600. It does 8 samplings in
each sampling cycle and has good anti-interference ability.
Copyright: Copyright (c) msxbo
Revision: 1.0
Signal description:
1) _i input
2) _o output
3) _n activ low
4) _dg debug signal
5) _r delay or register
6) _s state mechine
*/
////////////////////////////////////////////////////////////////////////////////

module uart_rx(
input clk_i,
input uart_rx_i,
output [7:0] uart_rx_data_o,
output uart_rx_done
 );

parameter [12:0] BAUD_DIV     = 14'd5207;//波特率时钟,9600bps,50Mhz/9600 - 1'b1=5207
parameter [12:0] BAUD_DIV_CAP = (BAUD_DIV/8 - 1'b1);//8次采样滤波去毛刺

reg [12:0] baud_div = 0;	//波特率设置计数器
reg bps_start_en = 0;		//波特率启动标志

always@(posedge clk_i)begin
    if(bps_start_en && baud_div < BAUD_DIV)
        baud_div <= baud_div + 1'b1;
    else
        baud_div <= 13'd0;
end

reg [12:0] samp_cnt = 0;
always@(posedge clk_i)begin
    if(bps_start_en && samp_cnt < BAUD_DIV_CAP)
        samp_cnt <= samp_cnt + 1'b1;
    else
        samp_cnt <= 13'd0;
end

//数据接收缓存器
reg [4:0] uart_rx_i_r=5'b11111;
always@(posedge clk_i)
	uart_rx_i_r<={uart_rx_i_r[3:0],uart_rx_i};
//数据接收缓存器,当连续接收到五个低电平时,即uart_rx_int=0时,作为接收到起始信号
wire uart_rx_int=uart_rx_i_r[4] | uart_rx_i_r[3] | uart_rx_i_r[2] | uart_rx_i_r[1] | uart_rx_i_r[0];

parameter START = 4'd0;
parameter BIT0  = 4'd1;
parameter BIT1  = 4'd2;
parameter BIT2  = 4'd3;
parameter BIT3  = 4'd4;
parameter BIT4  = 4'd5;
parameter BIT5  = 4'd6;
parameter BIT6  = 4'd7;
parameter BIT7  = 4'd8;
parameter STOP  = 4'd9;

reg [3:0] RX_S = 4'd0;
wire bps_en = (baud_div == BAUD_DIV);
wire rx_start_fail;
always@(posedge clk_i)begin
    if(!uart_rx_int&&bps_start_en==1'b0) begin
        bps_start_en <= 1'b1;
        RX_S <= START;
    end
    else if(rx_start_fail)begin
        bps_start_en <= 1'b0;
    end
    else if(bps_en)begin
        case(RX_S)
            START:RX_S <= BIT0; //RX bit0
            BIT0: RX_S <= BIT1; //RX bit1
            BIT1: RX_S <= BIT2; //RX bit2
            BIT2: RX_S <= BIT3; //RX bit3
            BIT3: RX_S <= BIT4; //RX bit4
            BIT4: RX_S <= BIT5; //RX bit5
            BIT5: RX_S <= BIT6; //RX bit6
            BIT6: RX_S <= BIT7; //RX bit7
            BIT7: RX_S <= STOP; //RX STOP
            STOP: bps_start_en <= 1'b0;
            default: RX_S <= STOP;
        endcase
    end
end

//滤波采样,在每个波特率周期采样,samp_en一个周期内出现8次,rx_tmp初值,15为中间值,如果采样为1则增加,否则减少
reg [4:0] rx_tmp = 5'd15;
reg [4:0] cap_cnt = 4'd0;
wire samp_en = (samp_cnt == BAUD_DIV_CAP);//采样使能

always@(posedge clk_i)begin
    if(samp_en)begin
        cap_cnt <= cap_cnt + 1'b1;
        rx_tmp <= uart_rx_i_r[4] ? rx_tmp + 1'b1 :  rx_tmp - 1'b1;
    end
    else if(bps_en) begin //每次波特率时钟使能,重新设置rx_tmp初值为15
        rx_tmp <= 5'd15;
        cap_cnt <= 4'd0;
    end
end
//当采样7次取值,大于16为采样1,小于16为采样0
reg cap_r = 1'b0;
wire cap_tmp = (cap_cnt == 3'd7);
reg ap_tmp_r = 1'b0;
reg ap_tmp_r1 = 1'b0;
wire cap_en = (!ap_tmp_r1&&ap_tmp_r);
reg cap_en_r = 1'b0;
always@(posedge clk_i)begin
    ap_tmp_r  <= cap_tmp;
    ap_tmp_r1 <= ap_tmp_r;
    cap_en_r  <= cap_en;
end


always@(posedge clk_i)begin
    if(cap_en&&bps_start_en)begin
        cap_r <= (rx_tmp > 5'd15) ? 1 : 0;
    end
    else if(!bps_start_en)begin
        cap_r <= 1'b1;
    end
end

//以下状态机里面保存好数据
reg [7:0] rx = 8'd0;
reg start_bit = 1'b1;
always@(posedge clk_i)begin
    if(cap_en_r)begin
        case(RX_S)
            BIT0: rx[0] <= cap_r;
            BIT1: rx[1] <= cap_r;
            BIT2: rx[2] <= cap_r;
            BIT3: rx[3] <= cap_r;
            BIT4: rx[4] <= cap_r;
            BIT5: rx[5] <= cap_r;
            BIT6: rx[6] <= cap_r;
            BIT7: rx[7] <= cap_r;
            default: rx <= rx;
        endcase
    end
end

assign rx_start_fail = (RX_S == START)&&cap_en_r&&(cap_r == 1'b1);
assign uart_rx_done = (RX_S == STOP)&& cap_en;
assign uart_rx_data_o = rx;

endmodule

串口发送


`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
/*
Company : Liyang Milian Electronic Technology Co., Ltd.
Brand: 米联客(msxbo)
Technical forum:uisrc.com
taobao: osrc.taobao.com
Create Date: 2019/02/27 22:09:55
Module Name: uart_tx_path
Description:
The baud rate of this serial port is 9600
Copyright: Copyright (c) msxbo
Revision: 1.0
Signal description:
1) _i input
2) _o output
3) _n activ low
4) _dg debug signal
5) _r delay or register
6) _s state mechine
*/
////////////////////////////////////////////////////////////////////////////////

module uart_tx(
	input clk_i,
	input [7:0] uart_tx_data_i,	//待发送数据
	input uart_tx_en_i,			//发送发送使能信号
	output uart_tx_o,
	output uart_busy
);

parameter [12:0] BAUD_DIV     = 14'd5207;//波特率时钟,9600bps,50Mhz/9600 - 1'b1=5207
//波特率发生器,实际就是分配器
reg bps_start_en = 1'b0;
reg [12:0] baud_div = 13'd0;

assign uart_busy = bps_start_en;

always@(posedge clk_i)begin
    if(bps_start_en && baud_div < BAUD_DIV)
        baud_div <= baud_div + 1'b1;
    else
        baud_div <= 13'd0;
end

reg [9:0] uart_tx_data_r = 10'h3ff;
wire bps_en = (baud_div == BAUD_DIV);
reg [3:0] tx_cnt = 4'd0;
assign uart_tx_o = uart_tx_data_r[0];
always@(posedge clk_i)begin
//首先当发送使能有效,寄存数据
    if(uart_tx_en_i) begin
        bps_start_en <= 1'b1;
        tx_cnt <= 4'd0;
        uart_tx_data_r <= {1'b1,uart_tx_data_i[7:0],1'b0};
    end
    else if(!bps_start_en)begin//当bps_start_en为0让状态机处于停止状态
        uart_tx_data_r <= 10'h3ff;
        tx_cnt <= 4'd0;
    end
// 通过移位发送数据
    if(bps_en && tx_cnt < 4'd9)begin
         uart_tx_data_r <= {uart_tx_data_r[0],uart_tx_data_r[9:1]};
         tx_cnt <= tx_cnt + 1'b1;
    end
    else if(bps_en)begin
         bps_start_en <= 1'd0;
    end
end

endmodule

状态机总结

三段式状态机

使用三个always 模块

  1. 第一个always模块采用同步时序描述状态转移
  2. 第二个always模块采用组合逻辑判断状态转移条件,描述状态转移规律
  3. 第三个always模块描述状态输出(可以使用组合电路输出,也可以使用时序电路输出)

对应代码结构

第一段


always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        curr_state <= IDLE_STATE;
    else
        curr_state <= next_state;
end

第二段


always @(*) begin
    case( curr_state )
       // ....
    endcase
end

第三段


always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
	 //...
    end else begin
	//...
    end
end

第二段不使用同步时序逻辑的原因

always 的执行是并行的

倘若使用同步时序逻辑,则:

  • 第一段的内容: curr_state <-- next_state
    将下一状态变为当前状态,状态更新
  • 第二段的内容:需要根据curr_state的值结合其他条件,得出下一状态
  • 第一段改变curr_state的值,第二段需要使用curr_state的值,并且两者是并行执行的,会形成冲突,可能使得第二段使用的curr_state是未更新前的,导致状态转移的错误。

免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删

QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空