串口接收Demo
简单介绍
在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据
- 空闲状态时,为高电平
- 起始位为一个单位长度低电平,停止位为一个长度高电平
分析
帧格式
- 8位数据位
- 1位停止位
- 无校验位
基本思路
采集每一位中间时刻的数据作为这一位的数据 ( 也可以每一位多采几个时刻的数据,取众数 )
框图
状态机
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
总结
三段式状态机
使用三个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是未更新前的,导致状态转移的错误。