FPGA实现串口通信(接收+发送)

 程序中使用到串口通信,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数据线操作有误,实物调试无法观测到,已修正。

#生活很苦 自己加糖# 习惯不贪心,太好的东西总是留不住

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值