FPGA 串口通信

FPGA 串口通信

文章目录

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

基础原理

  1. 并行通信

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


    传输速度快,但是占用引脚资源多

  2. 串行通信

    将数据分成一位一位的形式在一条传输线上逐个传输
    在这里插入图片描述

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

  3. 串行通信分类

    • 同步通信

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

      在这里插入图片描述

    • 异步通信

      不带时钟同步信号的数据传输,发送方和接收方使用各自的时钟控制数据的发送和接收过程
      在这里插入图片描述

  4. 传输方向

    • 单工, 只能沿一个方向传输
    • 半双工, 数据传输可以沿两个方向,但是需要分时进行
    • 全双工,可以同时双向传输
  5. 常见串行通信接口
    在这里插入图片描述

异步串行通信UART

特点: 异步、串行

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

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

    在这里插入图片描述

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

    校验位: 奇偶校验

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

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

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

    在这里插入图片描述

  • RS232接口

    常见接口有DB9接口

    在这里插入图片描述

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

Verilog 实现

串口接收

1. 介绍
  1. 简单介绍

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

  4. 框图

    在这里插入图片描述

  5. 状态机

    在这里插入图片描述

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) beginif(!sys_rst_n || !time_en) begin time_count <= 0;count_en <= 0;end else if(half_en)if(time_count == BAUD_HALF_COUNT -1 )begintime_count <=0;count_en <= 1;endelse begintime_count <= time_count + 1'b1;count_en <= 0;end else if(time_count == BAUD_COUNT -1 )begincount_en <= 1;time_count <= 0;endelse begintime_count <= time_count + 1'b1;count_en <= 0;end
end// 产生下一状态
always @(*) begincase( curr_state )IDLE_STATE: beginif( uart_rx_en )next_state = START_STATE;else next_state = IDLE_STATE;endSTART_STATE:if( count_en)next_state = RECV_STATE;elsenext_state = START_STATE;RECV_STATE:if( count_en )next_state = RECV_D0_STATE;elsenext_state = RECV_STATE;RECV_D0_STATE:if( count_en )next_state = RECV_D1_STATE;elsenext_state = RECV_D0_STATE;RECV_D1_STATE:if( count_en )next_state = RECV_D2_STATE;elsenext_state = RECV_D1_STATE;RECV_D2_STATE:if( count_en )next_state = RECV_D3_STATE;elsenext_state = RECV_D2_STATE;RECV_D3_STATE:if( count_en )next_state = RECV_D4_STATE;elsenext_state = RECV_D3_STATE;RECV_D4_STATE:if( count_en )next_state = RECV_D5_STATE;elsenext_state = RECV_D4_STATE;RECV_D5_STATE:if( count_en )next_state = RECV_D6_STATE;elsenext_state = RECV_D5_STATE;RECV_D6_STATE:if( count_en )next_state = RECV_D7_STATE;elsenext_state = RECV_D6_STATE;RECV_D7_STATE:if( count_en )next_state = END_STATE;elsenext_state = RECV_D7_STATE;END_STATE:next_state = IDLE_STATE;default: ;endcase
endassign 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) beginif(!sys_rst_n) beginrx_data <= 7'b0;time_en <= 1'b0;half_en <= 1'b0;rx_data_index <= 3'b0;end elsecase(curr_state)IDLE_STATE: begintime_en <= 1'b0;half_en <= 1'b0;rx_data_index <= 3'b0;endSTART_STATE: begintime_en <= 1'b1;half_en <= 1'b1;endRECV_STATE:begintime_en <= 1'b1;half_en <= 1'b0;end     RECV_D0_STATE:if(rx_data_index == 3'd0)beginrx_data[0] <= uart_rx;rx_data_index <= rx_data_index + 1'b1;end elserx_data[0] <= rx_data[0];      RECV_D1_STATE:if(rx_data_index == 3'd1)beginrx_data[1] <= uart_rx;rx_data_index <= rx_data_index + 1'b1;end elserx_data[1] <= rx_data[1];RECV_D2_STATE:if(rx_data_index == 3'd2)beginrx_data[2] <= uart_rx;rx_data_index <= rx_data_index + 1'b1;end elserx_data[2] <= rx_data[2];RECV_D3_STATE:if(rx_data_index == 3'd3)beginrx_data[3] <= uart_rx;rx_data_index <= rx_data_index + 1'b1;end elserx_data[3] <= rx_data[3];RECV_D4_STATE:if(rx_data_index == 3'd4)beginrx_data[4] <= uart_rx;rx_data_index <= rx_data_index + 1'b1;end elserx_data[4] <= rx_data[4];RECV_D5_STATE:if(rx_data_index == 3'd5)beginrx_data[5] <= uart_rx;rx_data_index <= rx_data_index + 1'b1;end elserx_data[5] <= rx_data[5];RECV_D6_STATE:if(rx_data_index == 3'd6)beginrx_data[6] <= uart_rx;rx_data_index <= rx_data_index + 1'b1;end elserx_data[6] <= rx_data[6];RECV_D7_STATE:if(rx_data_index == 3'd7)beginrx_data[7] <= uart_rx;rx_data_index <= rx_data_index + 1'b1;end elserx_data[7] <= rx_data[7];END_STATE:begintime_en <= 1'b0;half_en <= 1'b0;rx_data_index <= 3'b0;enddefault: ;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) beginif(!sys_rst_n) beginuart_rx_d0 <= 1'b0;uart_rx_d1 <= 1'b0;end else beginuart_rx_d1 <= uart_rx;uart_rx_d0 <= uart_rx_d1;end 
end// update curr_state
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) curr_state <= IDLE_STATE;elsecurr_state <= next_state;
endendmodule

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;endalways #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;endendmodule

在这里插入图片描述

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

程序

`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) beginif( !i_rst_n ) begini_data_d0 <= 1'b1;i_data_d1 <= 1'b1;end else begini_data_d0 <= i_data;i_data_d1 <= i_data_d0;end
end// 开始信号
always @(posedge i_clk or negedge i_rst_n) beginif( !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;elsestart_rx <= start_rx;
endassign 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) beginif( !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}};elsetime_counter <= time_counter+1'b1;elsetime_counter <= {COUNTER_LEN{1'b0}};
end
// bit_index 控制
always @(posedge i_clk or negedge i_rst_n) beginif( !i_rst_n ) bit_index <= 4'b0;else if(start_rx) if(counter_en)bit_index <= bit_index + 4'b1;elsebit_index <= bit_index;elsebit_index <= 4'b0;
end//输出
always @(posedge i_clk or negedge i_rst_n) beginif( !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;endcaseelseo_data_reg <= o_data_reg;
endassign 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 begini_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;
endendmodule

串口发送

1. 介绍
  1. 简单介绍

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

    在这里插入图片描述

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

  2. 分析-时序

    在这里插入图片描述

  3. 框图

    在这里插入图片描述

  4. 状态机

    在这里插入图片描述

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 @(*) beginif(!sys_rst_n) curr_state = IDLE_STATE;elsecurr_state = next_state;
endassign time_en = (time_counter == TIME_MAX_COUNT -1)? 1'b1:1'b0;
// timer
always @(posedge sys_clk or negedge sys_rst_n ) beginif(!sys_rst_n || count_en == 0 ) time_counter <= 'd0;else if(time_counter == TIME_MAX_COUNT -1 )time_counter <= 'd0;elsetime_counter <= time_counter + 1'd1;
end// create next state
always @(posedge sys_clk or negedge sys_rst_n ) beginif(!sys_rst_n) beginnext_state <= IDLE_STATE;endelse case(curr_state)IDLE_STATE :if(uart_w_en)next_state <= START_STATE;elsenext_state <= next_state;START_STATE:if(time_en)next_state <= D0_STATE;elsenext_state <= next_state;D0_STATE   :if(time_en)next_state <= D1_STATE;elsenext_state <= next_state;D1_STATE   :if(time_en)next_state <= D2_STATE;elsenext_state <= next_state;D2_STATE   :if(time_en)next_state <= D3_STATE;elsenext_state <= next_state;D3_STATE   :if(time_en)next_state <= D4_STATE;elsenext_state <= next_state;D4_STATE   :if(time_en)next_state <= D5_STATE;elsenext_state <= next_state;D5_STATE   :if(time_en)next_state <= D6_STATE;elsenext_state <= next_state;D6_STATE   :if(time_en)next_state <= D7_STATE;elsenext_state <= next_state;D7_STATE   :if(time_en)next_state <= END_STATE;elsenext_state <= next_state;END_STATE  :if(time_en)next_state <= IDLE_STATE;elsenext_state <= next_state;default:next_state <= IDLE_STATE;endcase
endassign  uart_out = uart_tx_out;
// out
always @(posedge sys_clk or negedge sys_rst_n ) beginif(!sys_rst_n)beginuart_tx_out <= 1'b1;uart_tx_data <= 8'd0;count_en <= 1'b0;   endelse case(curr_state)IDLE_STATE :  begin                      uart_tx_out <= 1'b1;   count_en <= 1'b0;   endSTART_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: beginuart_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 beginsys_clk             = 1'b0;sys_rst_n           = 1'b0;uart_w_en           = 1'b0;
endalways #5  sys_clk = ~sys_clk;initial begin#10 sys_rst_n = 1;#10uart_data = 8'b10011101;uart_w_en = 1;#20uart_w_en = 0;#20000$stop;
end
endmodule

在这里插入图片描述

  • 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)  beginif(! i_rst_n ) begini_data_reg <= 7'b0;i_data_reg_valid <= 1'b0;end else if( i_data_valid ) begini_data_reg <= i_data;i_data_reg_valid <= 1'b1;endelse begini_data_reg <= i_data_reg;i_data_reg_valid <= 1'b0;end
endalways @(posedge i_clk or negedge i_rst_n)beginif(! 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;elsestart_tx <= start_tx;
endassign time_counter_en = (time_counter == COUNT_MAX -1) ? 1'b1 :1'b0;
always @(posedge i_clk or negedge i_rst_n) beginif(! 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}};elsetime_counter <= time_counter + 1'b1;elsetime_counter <= {COUNTER_LEN{1'b0}};
endalways @(posedge i_clk or negedge i_rst_n) beginif(! i_rst_n )bit_num <= 4'b0;else if( start_tx )if( time_counter_en )bit_num <= bit_num +1'b1;elsebit_num <= bit_num;elsebit_num <= 4'b0;
endalways @(posedge i_clk or negedge i_rst_n) beginif(! 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;endcaseelseo_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 begini_clk = 1'b0;i_rst_n = 1'b0;i_data_valid = 1'b0;
endalways #10 i_clk = ~i_clk;initial begin#20;i_rst_n = 1'b1;#20;i_data = 8'b10110001;i_data_valid= 1'b1;#20i_data_valid = 1'b0;#500;i_data = 8'b11111111;i_data_valid= 1'b1;#20i_data_valid = 1'b0;//$monitor($time,"\to_data: %b",o_data);#100;$stop;   
endendmodule

回环测试

测试框图

在这里插入图片描述

测试代码
  • 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  begini_clk = 1'b0;i_rst_n  = 1'b0;
    endalways #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;
    endendmodule
    
  • 实测结果

    在这里插入图片描述

米联客参考代码

串口接收

`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)beginif(bps_start_en && baud_div < BAUD_DIV)	baud_div <= baud_div + 1'b1;else baud_div <= 13'd0;
endreg [12:0] samp_cnt = 0;
always@(posedge clk_i)beginif(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)beginif(!uart_rx_int&&bps_start_en==1'b0) beginbps_start_en <= 1'b1;RX_S <= START;endelse if(rx_start_fail)beginbps_start_en <= 1'b0;endelse if(bps_en)begincase(RX_S)START:RX_S <= BIT0; //RX bit0BIT0: RX_S <= BIT1; //RX bit1BIT1: RX_S <= BIT2; //RX bit2BIT2: RX_S <= BIT3; //RX bit3BIT3: RX_S <= BIT4; //RX bit4BIT4: RX_S <= BIT5; //RX bit5BIT5: RX_S <= BIT6; //RX bit6BIT6: RX_S <= BIT7; //RX bit7BIT7: RX_S <= STOP; //RX STOPSTOP: 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)beginif(samp_en)begincap_cnt <= cap_cnt + 1'b1;rx_tmp <= uart_rx_i_r[4] ? rx_tmp + 1'b1 :  rx_tmp - 1'b1;endelse if(bps_en) begin //每次波特率时钟使能,重新设置rx_tmp初值为15rx_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)beginap_tmp_r  <= cap_tmp;ap_tmp_r1 <= ap_tmp_r;cap_en_r  <= cap_en;
endalways@(posedge clk_i)beginif(cap_en&&bps_start_en)begincap_r <= (rx_tmp > 5'd15) ? 1 : 0;    end else if(!bps_start_en)begincap_r <= 1'b1;end
end//以下状态机里面保存好数据
reg [7:0] rx = 8'd0;
reg start_bit = 1'b1;
always@(posedge clk_i)beginif(cap_en_r)begincase(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)beginif(bps_start_en && baud_div < BAUD_DIV)	baud_div <= baud_div + 1'b1;else baud_div <= 13'd0;
endreg [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) beginbps_start_en <= 1'b1;tx_cnt <= 4'd0;uart_tx_data_r <= {1'b1,uart_tx_data_i[7:0],1'b0};endelse 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)beginuart_tx_data_r <= {uart_tx_data_r[0],uart_tx_data_r[9:1]};tx_cnt <= tx_cnt + 1'b1;endelse if(bps_en)beginbps_start_en <= 1'd0;end
end   endmodule

状态机总结

三段式状态机

使用三个always 模块

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

对应代码结构

第一段

always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) curr_state <= IDLE_STATE;elsecurr_state <= next_state;
end

第二段

always @(*) begincase( curr_state )// ....endcase
end

第三段

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

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

always 的执行是并行的

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

  • 第一段的内容: curr_state <– next_state

    将下一状态变为当前状态,状态更新

  • 第二段的内容:需要根据curr_state的值结合其他条件,得出下一状态

  • 第一段改变curr_state的值,第二段需要使用curr_state的值,并且两者是并行执行的,会形成冲突,可能使得第二段使用的curr_state是未更新前的,导致状态转移的错误。

查看全文

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.dgrt.cn/a/2190121.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章:

在这里插入图片描述

FPGA 串口通信

FPGA 串口通信 文章目录FPGA 串口通信基础原理异步串行通信UARTVerilog 实现串口接收1. 介绍2. 程序实现严格按照状态机实现非严格按照状态机实现( 目前使用 )串口发送1. 介绍2. 程序实现严格按照状态机实现非严格按照状态机实现( 目前使用 &……

Matlab安装勾选产品说明

在安装Matlab2023a的时候,需要勾选所需的产品(组件),一共是112个。
其中很多都不是必须的,可以视个人需求进行选择,这里提供了112个产品的描述和详情链接。
组件名称英文描述中文描述MATLAB科学计算语言S……

(排序4)堆排序与冒泡排序

堆排序
堆排序的话其实就是把数组看成一颗完全二叉树,因为我们事先就知道数组的话可以等价于一颗完全二叉树,然后再把数组看成完全二叉树的基础之上,在给这个完全二叉树进行建堆,比如说我需要升序排列的话那我应该要建一个大根堆……

如何使用图吧工具箱进行CPU和显卡双烤

如何使用图吧工具箱进行双烤1. 概述2. 用图吧工具箱进行CPU和显卡的压力测试2.1 CPU 压力测试2.2 显卡压力测试2.3 在AIDA64中查看CPU和显卡的温度结束语1. 概述
图吧工具箱是一款功能强大的硬件检测工具合集,且开源、免费、绿色; 集成了硬件检测、评分……

arduino多任务的实现:光强检测+程序计时+控灯

多任务可以同时执行多个函数,无须等待传统的delay似的排队操作。受到某位大神的笔记启发和实践后,整理成适用于个人的项目。总体流程:声明任务、添加任务、创建任务、激活任务。 整体代码(删除了一些内容):……

Springboot异常统一处理,并保存异常日志到数据库中

一、为什么要进行统一异常处理
如果发生了异常我们应该让接口可以返回统一的结果。有好的展示给接口调用方。方便我们对异常进行记录,和错误排查。我们可能对某些异常比较关注,比如说我们监控某个IP或者用户一天发送短信的数量,当超出一定数……

【蓝桥杯】DFS正确入门方式 | DFS + 递归与递推习题课(上) | 一节课教你爆搜!——学习笔记

目录
同系列文章——传送门
【蓝桥杯】DFS深度优先练习题——基础入门模板(1)_小卢先冲的博客-CSDN博客第一题:递归实现指数型枚举、第二题:全排列问题、第三题:组合的输出https://blog.csdn.net/weixin_61082895/ar……

RecycleView小结

RecycleView四级缓存
一级缓存:用于存放当前屏幕可显示区域的ViewHolder,目的是为了方便更新数据,以及对View操作时更加快捷二级缓存:用于缓存最近滑动出屏幕的ViewHolder,目的是为了当用户将该View滑出屏幕外时又突然……

大数据项目实战之数据仓库:数仓数据同步策略——第3章 数仓环境准备

文章目录第3章 数仓环境准备3.1 Hive安装部署3.2 Hive元数据配置到MySQL3.2.1 拷贝驱动3.2.2 配置Metastore到MySQL3.3 启动Hive3.3.1 初始化元数据库3.3.2 启动Hive客户端第3章 数仓环境准备
3.1 Hive安装部署
1)把apache-hive-3.1.2-bin.tar.gz上传到linux的/op……

Spring Boot基础学习之(十):修改员工的信息

注意:spring boot专栏是一个新手项目,博文顺序则是功能实现的流程,如果有看不懂的内容可以到前面系列去了解。 本次项目所有能够使用的静态资源可以免费进行下载
静态资源
在本篇代码DAO层将通过Java文件去实现,在这里就不连接数……

vscode开发常用的工具栏选项,查看源码技巧以及【vscode常用的快捷键】

一、开发常用的工具栏选项
1、当前打开的文件快速在左侧资源树中定位: 其实打开了当前的文件已经有在左侧资源树木定位了,只是颜色比较浅 2、打开太多文件的时候,可以关闭 3、设置查看当前类或文件的结构 OUTLINE
相当于idea 查看当前类或接……

数据要素化条件之一:原始性

随着技术的发展,计算机不仅成为人类处理信息的工具,而且逐渐地具有自主处理数据的能力,出现了替代人工的数据智能技术。数据智能的大规模使用需要关于同一分析对象或同一问题的、来源于不同数据源的海量数据。这种数据必须是针对特定对象的记……

【面试题 高逼格利用 类实现加法】编写代码, 实现多线程数组求和.

编写代码, 实现多线程数组求和.关键1. 数组的初始化关键2. 奇偶的相加import java.util.Random;public class Thread_2533 {public static void main(String[] args) throws InterruptedException {// 记录开始时间long start System.currentTimeMillis();// 1. 给定一个很长的……

一个python训练

美国:28:麻省理工学院,斯坦福大学,哈佛大学,加州理工学院,芝加哥大学,普林斯顿大学,宾夕法尼亚大学,耶鲁大学,康奈尔大学,哥伦比亚大学,密歇根大学安娜堡分校,约翰霍普金斯大学,西北大学,加州大学伯克利分校,纽约大学,加州大学洛杉矶分校,杜克大学,卡内基梅隆大学,加州大学圣地……

Mybatis03学习笔记

目录 使用注解开发
设置事务自动提交
mybatis运行原理
注解CRUD
lombok使用(偷懒神器,大神都不建议使用)
复杂查询环境(多对一)
复杂查询环境(一对多)
动态sql环境搭建
动态sql常用标签……

设置或取得c# NumericUpDown 编辑框值的方法,(注意:不是Value值)

本人在C#开发中使用到了NumericUpDown控件,但是发现该控件不能直接控制显示值,经研究得到下面的解决办法
NumericUpDown由于是由多个控件组合而来的控件,其中包含一个类似TextBox的控件,若想取得或改变其中的值要使用如下方法
N……

使用NPOI 技术 的SetColumnWidth 精确控制列宽不能成功的解决办法(C#)

在使用NPOI技术开发自动操作EXCEL软件时遇到不能精确设置列宽的问题。

ISheet sheet1 hssfworkbook.CreateSheet("Sheet1");
sheet1.SetColumnWidth(0, 50 * 256); // 在EXCEL文档中实际列宽为49.29
sheet1.SetColumnWidth(1, 100 * 256); // 在EXCEL文……

Mysql 数据库zip版安装时basedir datadir 路径设置问题,避免转义符的影响

本人在开发Mysql数据库自动安装程序时遇到个很奇怪的问题,其中my.ini的basedir 的路径设置是下面这样的:
basedir d:\测试\test\mysql
但是在使用mysqld安装mysql服务时老是启动不了,报1067错误,后来查看window事件发现一个独特……

java stream sorted排序 考虑null值

项目里使用到排序, java里没有像C# 里的linq,只有stream,查找stream.sorted源码看到有个
Comparator.nullsLast
然后看了一下实现,果然是能够处理null值的排序,如:minPriceList.stream().sorted(Comparator.comparing(l -> l.g……

spring @EnableConfigurationProperties 实现原理

查看DataSourceAutoConfiguration源码,发现如下代码: Configuration ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) EnableConfigurationProperties(DataSourceProperties.class) Import({ DataSourcePoolMetadataProvidersCon……

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注