FPGA串口接收Demo

串口接收Demo

简单介绍

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

FPGA串口接收Demo_第1张图片

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

FPGA串口接收Demo_第2张图片

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

FPGA串口接收Demo_第3张图片

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

框图

FPGA串口接收Demo_第4张图片

状态机

FPGA串口接收Demo_第5张图片

Verilog
`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串口接收Demo_第6张图片

总结
三段式状态机

使用三个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开发)