程序中使用到串口通信,mcu串口不够用了,使用fpga扩展出一路串口供使用,特此记录。
功能要求:
1、串口波特率自定义
2、串口同时支持发送及接收
3、串口接收到的数据传输至mcu解析,mcu将要发送的数据送至fpga发出
4、未使用到校验位,仅预留(未作)
实现思路:
串口通信过程中状态明确,选择三段式状态机进行实现
状态包含:IDLE(空闲)START(起始)DATA(数据移位)CHECK(校验)STOP(停止)
模块分为3部分,接收部分,发送部分,顶层模块
实现代码:
接收模块
/*
将rx引脚上的串口数据送至data_o[7:0]寄存器,data_en高有效
*/
module uart_rx
#(parameter BPS_WID = 9,
parameter BPS_CNT = 434) //115200
(
input sys_clk,
input rst_n,
input rx,
output reg[7:0]data_o,
output data_en
);
localparam IDLE = 5'b00000,
START= 5'b00001,
DATA = 5'b00010,
CHECK= 5'b00100,
STOP = 5'b01000,
WAIT = 5'b10000; //校验错误时进入
/*******************************************************
RX_module
*******************************************************/
wire rx_str_n,rx_bit;
reg [2:0]rx_r;
reg [BPS_WID-1:0] baud_cnt;
assign rx_bit = rx_r[1];
// assign clk_bps = baud_cnt < BPS_CNT/2;
//消除亚稳态,产生起始下降沿
assign rx_str_n = (!rx_r[1])&(rx_r[2]);
always @(posedge sys_clk) begin
rx_r <= {rx_r[1:0],rx};
end
//--1
(*noprune*)reg [4:0]cur_st,nxt_st;
reg [7:0]rx_data;
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n) cur_st <= IDLE;
else cur_st <= nxt_st;
end
//--2
reg [3:0]bit_cnt;
always @(*) begin
case(cur_st)
IDLE :begin
if(rx_str_n) nxt_st = START;
else nxt_st = IDLE;
end
START :begin
if(baud_cnt == BPS_CNT/2) nxt_st = DATA;
else nxt_st = START;
end
DATA :begin
if(bit_cnt < 'd9) nxt_st = DATA;
else nxt_st = CHECK;
end
CHECK :begin
if(baud_cnt == BPS_CNT/2) nxt_st = STOP;
else nxt_st = CHECK;
end
STOP :
if(baud_cnt == BPS_CNT/2) nxt_st = IDLE;
else nxt_st = STOP;
WAIT : nxt_st <= IDLE;
default : nxt_st <= IDLE;
endcase
end
//--3
always @(posedge sys_clk) begin
if((nxt_st == START)||(nxt_st == DATA)||
(nxt_st == CHECK)||(nxt_st == STOP))begin
if(baud_cnt < BPS_CNT - 'd1)
baud_cnt <= baud_cnt + 'd1;
else
baud_cnt <= 'd0;
end
end
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)begin
rx_data <= 'd0;
bit_cnt <= 'd1;
end
else if((baud_cnt == BPS_CNT/2)&&(nxt_st == DATA)&&(bit_cnt < 'd9))begin
rx_data[bit_cnt - 'd1] <= rx_bit;
bit_cnt <= bit_cnt + 'd1;
end
else if(nxt_st == STOP)begin
data_o <= rx_data;
end
else if(nxt_st == START)begin
rx_data <= 'd0;
bit_cnt <= 'd1;
end
end
//--数据有效信号
wire back_end;
reg [1:0]en_dely;
assign back_end = (cur_st==STOP);
assign data_en = en_dely[1]&(!en_dely[0]);
always @(posedge sys_clk) begin
en_dely <= {en_dely[0],back_end};
end
endmodule
发送模块
/*
将data_i[7:0]寄存器的数据以串口格式发送到tx引脚上,
data_en高时,data_i[7:0]数据有效
busy: 1:模块忙 0:无操作
*/
module uart_tx
#(parameter BPS_WID = 9,
parameter BPS_CNT = 434) //115200
(
input sys_clk ,
input rst_n ,
input [7:0]data_i ,
input data_en ,
output reg tx ,
output reg busy //1:busy 0:no busy
);
localparam IDLE = 5'b00000,
START= 5'b00001,
DATA = 5'b00010,
CHECK= 5'b00100,
STOP = 5'b01000;
/*******************************************************
TX_module
*******************************************************/
reg [BPS_WID-1:0] baud_cnt;
reg [7:0]tx_data;
reg str_p,par_data = 1'b1;
//预存数据
always @(posedge sys_clk) begin
if(data_en &(!busy))begin
tx_data <= data_i;
str_p <= 'd1;
end
else
str_p <= 'd0;
end
//--1
(*noprune*)reg [4:0]cur_st,nxt_st;
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n) cur_st <= IDLE;
else cur_st <= nxt_st;
end
//--2
reg [3:0]bit_cnt;
always @(*) begin
case(cur_st)
IDLE :begin
if(str_p) nxt_st = START;
else nxt_st = IDLE;
end
START :begin
if(baud_cnt < BPS_CNT - 'd1) nxt_st = START;
else nxt_st = DATA;
end
DATA :begin
if(bit_cnt < 'd9) nxt_st = DATA;
else nxt_st = CHECK;
end
CHECK :begin
if(baud_cnt < BPS_CNT - 'd1) nxt_st = CHECK;
else nxt_st = STOP;
end
STOP :begin
if(baud_cnt < BPS_CNT - 'd1) nxt_st = STOP;
else nxt_st = IDLE;
end
default : nxt_st <= IDLE;
endcase
end
//--3
always @(posedge sys_clk) begin
if((nxt_st == START)||(nxt_st == DATA)||
(nxt_st == CHECK)||(nxt_st == STOP))begin
if(baud_cnt < BPS_CNT - 'd1)
baud_cnt <= baud_cnt + 'd1;
else
baud_cnt <= 'd0;
end
else
baud_cnt <= 'd0;
end
always @(posedge sys_clk) begin
case(nxt_st)
IDLE : tx <= 1'b1;
START : tx <= 1'b0;
DATA : if(bit_cnt > 'd0) tx <= tx_data[bit_cnt - 'd1];
CHECK : tx <= par_data;
STOP : tx <= 1'b1;
default : tx <= 1'b1;
endcase
end
always @(posedge sys_clk) begin
if(cur_st == IDLE) busy <= 'd0;
else busy <= 'd1;
end
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)begin
bit_cnt <= 'd0;
end
else if((baud_cnt == BPS_CNT-'d1)&&(nxt_st == DATA)&&(bit_cnt < 'd9))begin
bit_cnt <= bit_cnt + 'd1;
end
else if(nxt_st == START)begin
bit_cnt <= 'd0;
end
end
endmodule
顶层测试模块
功能描述:fpga将串口接收模块收到的数据“+1”后,送至串口发送模块,发送至电脑串口助手
例如:串口助手发送0x11,接收到0x12 (0x11+0x01)
module uart_mt(
input sys_clk,
input rst_n,
// input [7:0]tx_data,
// output [7:0]rx_data,
input rx,
output tx,
output tx_busy
);
localparam BAUD_WID = 11;
localparam BAUD_CNT = 1302; //38400
//----------------
wire [7:0]rx_d;
wire rx_e;
//----------------
wire tx_e;
reg [3:0]dely_cnt;
reg [7:0]rx_data,tx_data;
reg tx_str;
always @(posedge sys_clk) begin
if(rx_e) begin
rx_data <= rx_d;
tx_str <= 1'd1;
dely_cnt <= 'd0;
end
else if((tx_str)&&(dely_cnt < 'd5))
dely_cnt <= dely_cnt + 'd1;
else if(dely_cnt == 'd5)
tx_str <= 'd0;
end
always @(posedge sys_clk) begin
tx_data <= rx_data + 'd1;
end
assign tx_e = (dely_cnt == 'd4);
uart_tx #(
.BPS_WID (BAUD_WID ),
.BPS_CNT (BAUD_CNT )
)u1_uart_tx(
.sys_clk (sys_clk ),
.rst_n (rst_n ),
.data_i (tx_data ),
.data_en (tx_e ),
.tx (tx ),
.busy () //1:busy 0:no busy
);
uart_rx #(
.BPS_WID (BAUD_WID ),
.BPS_CNT (BAUD_CNT )
)u1_uart_rx(
.sys_clk (sys_clk ),
.rst_n (rst_n ),
.rx (rx ),
.data_o (rx_d ),
.data_en (rx_e )
);
endmodule
功能验证:
数据收发验证
signaltap II实物调试波形
串口助手界面
波特率更改验证
波特率:38400
波特率:115200
总结:
如若需要实现连续接收,则可搭配ram或fifo,对数据进行保存。
如需实现连续发送,则将数据写入ram中,持续监测busy信号,再no busy时将数据读出,送入发送模块即可。
至此,串口收发均验证完成,数据收发正常,此次功能简单,未进行仿真验证,但有signtap波形图可以看出功能符合预期,后续有时间再增加modelsim仿真波形图
欢迎大家互相交流
补充:TB测试代码
`timescale 1 ns/ 1 ps
module uart_tb();
localparam BAUD_WID = 11;
localparam BAUD_CNT = 434; //115200
parameter SYS_CLK = 50; //系统时钟频率,单位Mhz
parameter SYS_PRE = 1000/(SYS_CLK*2); //时钟周期,单位ns
parameter UART_BPS = 115200; //串口波特率
parameter BPS_CNT = 1_000_000_000/UART_BPS;//用于串口仿真的时延
wire tx_pin,rx_en;
wire [7:0]rx_data;
reg [7:0]tx_reg,tx_data;
reg rst_n,clk,rx_pin,tx_en;
always #SYS_PRE clk = ~clk; //产生时钟
initial begin
#0;
clk = 0;
rst_n = 1;
rx_pin = 1;
tx_en = 0;
tx_reg = 8'h69;
#10;
rst_n = 0;
#10;
rst_n = 1;
//起始信号:(下降沿)
#BPS_CNT; rx_pin = 1;
#BPS_CNT; rx_pin = 0;
//数据信号:0:7
#BPS_CNT; rx_pin = tx_reg[0];
#BPS_CNT; rx_pin = tx_reg[1];
#BPS_CNT; rx_pin = tx_reg[2];
#BPS_CNT; rx_pin = tx_reg[3];
#BPS_CNT; rx_pin = tx_reg[4];
#BPS_CNT; rx_pin = tx_reg[5];
#BPS_CNT; rx_pin = tx_reg[6];
#BPS_CNT; rx_pin = tx_reg[7];
//串口结束信号
#BPS_CNT; rx_pin = 1;
//起始信号:(下降沿)
#BPS_CNT; rx_pin = 1;
#BPS_CNT; rx_pin = 0;
//数据信号:0:7
#BPS_CNT; rx_pin = tx_reg[0];
#BPS_CNT; rx_pin = tx_reg[1];
#BPS_CNT; rx_pin = tx_reg[2];
#BPS_CNT; rx_pin = tx_reg[3];
#BPS_CNT; rx_pin = tx_reg[4];
#BPS_CNT; rx_pin = tx_reg[5];
#BPS_CNT; rx_pin = tx_reg[6];
#BPS_CNT; rx_pin = tx_reg[7];
//串口结束信号
#BPS_CNT; rx_pin = 1;
#60; tx_data = 8'hdb;
#30; tx_en = 1;
#30; tx_en = 0;
end
uart_tx #(
.BPS_WID (BAUD_WID ),
.BPS_CNT (BAUD_CNT ),
.UART_SET (16'h1811 )
)u1_uart_tx(
.clk (clk ),
.rst_n (rst_n ),
.data_i (tx_data ),
.data_en (tx_en ),
.tx (tx_pin ),
.busy () //1:busy 0:no busy
);
uart_rx #(
.BPS_WID (BAUD_WID ),
.BPS_CNT (BAUD_CNT ),
.UART_SET (16'h1811 )
)u1_uart_rx(
.clk (clk ),
.rst_n (rst_n ),
.rx (rx_pin ),
.data_o (rx_data ),
.data_en (rx_en )
);
endmodule
补充:modelsim仿真图
接收数据
测试数据为:0x69
符合预期
发送数据
测试数据为:0xdb
波形数据为0b 1101 1011,即(0xdb),符合预期
注:之前的代码发送模块发送数据时第一位数据发送前2个clk对TX数据线操作有误,实物调试无法观测到,已修正。
#生活很苦 自己加糖# 习惯不贪心,太好的东西总是留不住