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的值结合其他条件,得出下一状态文章来源:https://www.toymoban.com/news/detail-408069.html
-
第一段改变curr_state的值,第二段需要使用curr_state的值,并且两者是并行执行的,会形成冲突,可能使得第二段使用的curr_state是未更新前的,导致状态转移的错误。文章来源地址https://www.toymoban.com/news/detail-408069.html
到了这里,关于FPGA 串口通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!