FPGA 串口通信
基础原理
- 并行通信
数据的各个位使用多条数据线同时进行传输
传输速度快,但是占用引脚资源多 - 串行通信
将数据分成一位一位的形式在一条传输线上逐个传输
通信线路简单,占用引脚资源少,但是传输速度较慢 - 串行通信分类
- 同步通信
带时钟同步信号的数据传输,发送发和接收方在同一个时钟的控制下,同步传输数
- 异步通信
不带时钟同步信号的数据传输,发送方和接收方使用各自的时钟控制数据的发送和接收过程
- 传输方向
- 单工, 只能沿一个方向传输
- 半双工, 数据传输可以沿两个方向,但是需要分时进行
- 全双工,可以同时双向传输
- 常见串行通信接口
异步串行通信UART
特点: 异步、串行
在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据
- 协议层:通信协议(数据格式、传输速率)
UART串口通信需要两根信号线来实现,一根用于串口发送,另一个负责串口接收。
校验位: 奇偶校验
- 串口通信的速率使用波特率来表示,每秒传输的二进制数据的位数 bps
- 物理层:接口类型,电平标准等
异步串行通信的接口标准: RS232, RS422, RS485
- RS232接口
常见接口有DB9接口
常用就三个引脚:RXD, TXD, GND
Verilog 实现
串口接收
1. 介绍
- 简单介绍在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据
- 空闲状态时,为高电平
- 起始位为一个单位长度低电平,停止位为一个长度高电平
- 分析
- 8位数据位
- 1位停止位
- 无校验位
- 基本思路
采集每一位中间时刻的数据作为这一位的数据 ( 也可以每一位多采几个时刻的数据,取众数 ) - 框图
- 状态机
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
非严格按照状态机实现( 目前使用 )
程序
`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. 介绍
- 简单介绍
在发送数据时将并行数据转换成串行数据来传输
空闲状态为高电平,发送的起始位为一个低电平,发送的停止位为一个高电平 - 分析-时序
- 框图
- 状态机
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
- 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
回环测试
测试框图
测试代码
- 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 模块
- 第一个always模块采用同步时序描述状态转移
- 第二个always模块采用组合逻辑判断状态转移条件,描述状态转移规律
- 第三个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是未更新前的,导致状态转移的错误。