FPGA 串口通信

FPGA 串口通信

文章目录

    • FPGA 串口通信
      • 基础原理
      • 异步串行通信UART
      • Verilog 实现
        • 串口接收
          • 1. 介绍
          • 2. 程序实现
            • 严格按照状态机实现
            • 非严格按照状态机实现( 目前使用 )
        • 串口发送
          • 1. 介绍
          • 2. 程序实现
            • 严格按照状态机实现
            • 非严格按照状态机实现( 目前使用 )
        • 回环测试
          • 测试框图
          • 测试代码
      • 米联客参考代码
        • 串口接收
        • 串口发送
      • 状态机总结
        • 三段式状态机

基础原理

  1. 并行通信

    数据的各个位使用多条数据线同时进行传输

    FPGA 串口通信_第1张图片
    传输速度快,但是占用引脚资源多

  2. 串行通信

    将数据分成一位一位的形式在一条传输线上逐个传输
    FPGA 串口通信_第2张图片

    通信线路简单,占用引脚资源少,但是传输速度较慢

  3. 串行通信分类

    • 同步通信

      带时钟同步信号的数据传输,发送发和接收方在同一个时钟的控制下,同步传输数

      FPGA 串口通信_第3张图片

    • 异步通信

      不带时钟同步信号的数据传输,发送方和接收方使用各自的时钟控制数据的发送和接收过程
      FPGA 串口通信_第4张图片

  4. 传输方向

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

异步串行通信UART

特点: 异步、串行

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

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

    FPGA 串口通信_第6张图片

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

    校验位: 奇偶校验

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

  • 物理层:接口类型,电平标准等

    异步串行通信的接口标准: RS232, RS422, RS485

    FPGA 串口通信_第7张图片

  • RS232接口

    常见接口有DB9接口

    FPGA 串口通信_第8张图片

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

Verilog 实现

串口接收

1. 介绍
  1. 简单介绍

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

    FPGA 串口通信_第9张图片

    • 空闲状态时,为高电平
    • 起始位为一个单位长度低电平,停止位为一个长度高电平
  2. 分析

    FPGA 串口通信_第10张图片

    • 8位数据位
    • 1位停止位
    • 无校验位
  3. 基本思路

    FPGA 串口通信_第11张图片

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

  4. 框图

    FPGA 串口通信_第12张图片

  5. 状态机

    FPGA 串口通信_第13张图片

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 串口通信_第14张图片

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

程序

`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. 简单介绍

    在发送数据时将并行数据转换成串行数据来传输

    FPGA 串口通信_第15张图片

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

  2. 分析-时序

    FPGA 串口通信_第16张图片

  3. 框图

    FPGA 串口通信_第17张图片

  4. 状态机

    FPGA 串口通信_第18张图片

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 串口通信_第19张图片

  • 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 串口通信_第20张图片

测试代码
  • 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
    
  • 实测结果

    FPGA 串口通信_第21张图片

米联客参考代码

串口接收

`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是未更新前的,导致状态转移的错误。

你可能感兴趣的:(FPGA,fpga开发,verilog,串口通信,uart)