FPGA 串口收发

系列文章目录

FPGA Verilog 串口发送
使用modelsim进行Verilog仿真(包含testbench编写)


前言

前面的文章实现了简单的串口发送,本来设计是继续进行串口接收的学习的,但是FPGA串口接收的学习不方便观察现象,所以采用串口收发的形式,将上位机发送的byte再返回回去。
PS:在写代码的过程中明显感觉到在逻辑电路中我失去了逻辑,调试过程十分痛苦,整个实现历时估摸着得有个两三天吧。最后测试每330个字符有一个误码。
串口条件:

  1. 波特率:115200;
  2. 无校验位;
  3. 数据位8位;
  4. 停止位;

一、串口收发

串口收发的时序倒比较简单,采用状态机的方式实现。

1.串口发送

这个在前面已经有做过了,上面目录里面有。发送总共有4个状态,分别为空闲状态(记作S_IDLE)、起始状态(S_START)、发送状态(S_SEND_BYTE) 以及结束状态(S_STOP)。发送的状态机代码如下。我们初学时主要关注状态机之间的状态转换:
首先,在默认状态下为S_IDLE,当有信号要发送时,即tx_data_valid为高电平时,进入S_STRAT状态。注意,在S_IDLE状态时,对时序没有要求,但是从S_START时,就必须对时钟严格要求了,本文从进入S_START后进行计数(记作cnt_bit),每计434个数作为一个串口传输bit。同时进入S_START时,输出信号线需要有一个下降沿,并在此状态下保持低电平。当cnt_bit计满434后,进入到S_SEND_BYTE状态,从此刻开始每434个cnt_bit的时间发送一个bit,发8个bit后,进入到S_STOP,计434个cnt_bit后转入S_IDLE,此时意味着结束。大概可以意思如下图所示。
在这里插入图片描述

module uart_tx
#(
	parameter CLK_FRE = 50,      //clock frequency(Mhz)
	parameter BAUD_RATE = 115200 //serial baud rate
)
(
	input                        clk,              //clock input
	input                        rst_n,            //asynchronous reset input, low active 
	input[7:0]                   tx_data,          //data to send
	input                        tx_data_valid,    //data to be sent is valid
	output reg                   tx_data_ready,    //send ready
	output                       tx_pin           //serial data output

);
//calculates the clock cycle for baud rate 
localparam                       CYCLE = CLK_FRE * 1000000 / BAUD_RATE;
//state machine code
localparam                       S_IDLE       = 0;
localparam                       S_START      = 1;//start bit
localparam                       S_SEND_BYTE  = 2;//data bits
localparam                       S_STOP       = 3;//stop bit
//reg [7:0] tx_data;
reg[2:0]                         state;
reg[15:0]                        cycle_cnt; //baud counter
reg[2:0]                         bit_cnt;//bit counter
reg[7:0]                         tx_data_latch; //latch data to send
reg                              tx_reg; //serial data output
assign tx_pin = tx_reg;

initial
begin
	tx_reg<=1;
//	tx_data<=8'ha5;
end

always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
		state <= S_IDLE;
	else
	case(state)
		S_IDLE:
			if(tx_data_valid == 1'b1)//触发状态
			begin
				tx_data_latch<=tx_data;
				tx_data_ready<=0;
				state <= S_START;
			end
			else
				state <= S_IDLE;
		S_START:
			if(cycle_cnt == CYCLE - 1)
				state <= S_SEND_BYTE;
			else
				state <= S_START;
		S_SEND_BYTE:
			if(cycle_cnt == CYCLE - 1  && bit_cnt == 3'd7)
				state <= S_STOP;
			else
				state <= S_SEND_BYTE;
		S_STOP:
			if(cycle_cnt == CYCLE - 1)
				begin
				state <= S_IDLE;
				tx_data_ready<=1;
				end
			else
				state <= S_STOP;
		default:
			state <= S_IDLE;
	endcase
end
/****************cnt计数****************/
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		cycle_cnt<=0;
	else
		if(state==S_IDLE)
			cycle_cnt<=0;
		else
			if(state>=S_START && cycle_cnt<CYCLE-1)
				cycle_cnt<=cycle_cnt+1;
			else 
				cycle_cnt<=0;
end
/******************bit计数****************/
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		bit_cnt<=0;
	else
		if(bit_cnt==3'd7 && cycle_cnt==CYCLE-1)
			bit_cnt<=0;
		else
			if(state==S_SEND_BYTE && cycle_cnt==CYCLE-1)
				bit_cnt<=bit_cnt+1;
end
always@(posedge clk or negedge rst_n)
begin
	case(state)
		S_IDLE:
			tx_reg<=1;
		S_START:
			tx_reg<=0;
		S_SEND_BYTE:
			tx_reg<=tx_data_latch[bit_cnt];
		S_STOP:
			tx_reg<=1;
		default:
			tx_reg<=1;
	endcase
end
endmodule

2.串口接收

与串口发送类似,不过发送是主动的,接收是被动的,最主要的是检测接收线的起始信号,即下降沿。下降沿的判断很有意思,如下代码块所示。

always @(posedge clk) begin
    rx_rxd_r <= rx_rxd;
    rx_rxd_rr <= rx_rxd_r;
end
//falling, 检测rx_rxd下降沿
assign falling = ~rx_rxd_r & rx_rxd_rr;

检测实际上是利用同一个线上面在相邻两个时钟周期的电平不同来实现的。
检测到下降沿之后,进入到S_START状态,然后后面的状态就和发送一样了,只不过发送变成了接收。

module uart_rx
#(
    parameter           CLK_FRE    =   50  ,//时钟频率(Mhz)
    parameter           BAUD_RATE   =   115200 //波特率(bps)
)
(
    input               clk         ,
    input               rst_n       ,
    input               rx_rxd      ,//接收端口
	 input               rx_data_valid,
    output   reg [7:0]  rx_data     ,//接收1字节数据
	 output reg rx_data_ready
);

localparam  CYCLE = CLK_FRE * 1000000 / BAUD_RATE;
reg         rx_rxd_r        ;
reg         rx_rxd_rr       ;
wire        falling         ;
reg [20:0]  cycle_cnt        ;

//state, 状态机
localparam S_IDLE      =       0;
localparam S_START     =       1;
localparam S_Receive = 2;
localparam S_STOP      =      3;
reg [2:0] state;
reg [3:0] bit_cnt;
//rx_rxd_r, rx_rxd_rr, 打节拍
always @(posedge clk) begin
    rx_rxd_r <= rx_rxd;
    rx_rxd_rr <= rx_rxd_r;
end
//falling, 检测rx_rxd下降沿
assign falling = ~rx_rxd_r & rx_rxd_rr;

always@(posedge clk or negedge rst_n) 
begin
   if(!rst_n) 
	begin
       state <= S_IDLE;
       rx_data_ready <= 1'b0;
    end
    else 
	 begin
        case(state)
            S_IDLE:
	begin
	if(rx_data_valid==0)
		rx_data_ready<=0; 		
                if(falling)
		begin
                    state <= S_START;
			
			rx_data<=7'd0;
		end
	end
            S_START:
                if(cycle_cnt == CYCLE-1)
                    state <= S_Receive;
            S_Receive:
		if(cycle_cnt ==0)
						rx_data[bit_cnt]<=rx_rxd;
					else
						if(cycle_cnt == CYCLE-1 && bit_cnt == 3'd7)
							state<=S_STOP;
            S_STOP:
                if(cycle_cnt == CYCLE-1) 
					 begin
                    state <= S_IDLE;
						rx_data_ready<=1;
						end
            default:
				begin
	if(rx_data_valid==0)
		rx_data_ready<=0;
                state <= S_IDLE;
					 rx_data_ready<=0;
				end					 
        endcase
    end
end
/*******cycle_cnt, 波特率计数器*******/
always @(posedge clk or negedge rst_n) 
begin
    if(!rst_n)
        cycle_cnt <= 'd0;
	else
		if(state==S_IDLE)
			cycle_cnt<=0;
		else
			if(state>=S_START && cycle_cnt <CYCLE-1)
				cycle_cnt<=cycle_cnt+1;
			else
				cycle_cnt<=0;  
end
/*******bit_cnt, 波特率计数器*******/
always @(posedge clk or negedge rst_n) 
begin
	if(!rst_n)
		bit_cnt<=0;
	else
		if(state!=S_Receive)
			bit_cnt<=0;
		else
			if(bit_cnt==7 && cycle_cnt==CYCLE-1)
				bit_cnt<=0;
			else
			if(cycle_cnt==CYCLE-1)
				bit_cnt<=bit_cnt+1;
end
endmodule

3.发送与接收的连接

发送与接收的连接很重要,因为是要实现收到一个就转发一个,所以怎么样只发送一次是需要考虑的。类似于STM32单片机接收中断中,在中断服务函数执行完成后清除中断标志位,退出中断,否则将一直卡在中断服务函数中或者重复进入。
在程序中引入几个信号来实现,先放一张图
在这里插入图片描述

图中,发射模块多了rx_data_ready和rx_data_valid,同样地,接收模块多了tx_data_ready和tx_data_valid两个信号线。
连接:
rx_data_ready<---->tx_data_valid
tx_data_ready<---->rx_data_valid
作用:首先当接收进入到S_STOP时,认为一个byte已经接收完成,此时接收模块拉高rx_data_ready,告诉发送模块我有一个byte已经收到,可以发送;随后发送模块将8bits的数据进行锁存,再拉低tx_data_ready告诉接收模块已经收到数据,最后接收模块把rx_data_ready拉低,防止发送模块重复触发工作。

二、仿真

1.使用quartusII仿真

在软件中将模块转化为RTL模型,再使用原理图进行仿真,原理图在上面已经给出。但是该软件只能仿真100us内的情况,所以只能对这段时间内的波形进行仿真,最后选择放弃,改用modelsim进行仿真。

2.使用modelsim进行仿真

testbench如下

`timescale 1 ns/ 1 ns

module uart_TR_tb;
localparam CLK_FRE = 50;      //clock frequency(Mhz)
localparam BAUD_RATE = 115200; //serial baud rate
	reg clk,rstn,rx_rxd;
	wire [7:0]rx_data;
	initial
	begin
		rstn=0;
		rx_rxd=0;
		#10 rstn=1;
		clk=0;
		forever #10 clk=~clk;
		
	end
initial
begin
#17361 rx_rxd=~rx_rxd;
#17361 rx_rxd=~rx_rxd;
#17361 rx_rxd=~rx_rxd;
#17361 rx_rxd=~rx_rxd;
#17361 rx_rxd=~rx_rxd;
#17361 rx_rxd=~rx_rxd;
end
uart_tx
#(
    	.CLK_FRE    (CLK_FRE  ),//时钟频率(Mhz)
    	.BAUD_RATE  (BAUD_RATE ) //波特率(bps)
)
uart_tx_inst
(
	.clk(clk),              //clock input
	.rst_n(rstn),            //asynchronous reset input, low active 
	.tx_data(rx_data[7:0]),          //data to send
	.tx_data_valid(tx_data_valid),    //  input
	.tx_data_ready(rx_data_valid),    //send ready  output
	.tx_pin(tx_pin)           //serial data output
);
uart_rx
#(
    	.CLK_FRE       (CLK_FRE       ),//时钟频率(Mhz)
   	.BAUD_RATE      (BAUD_RATE      ) //波特率(bps)
)
uart_rx_inst
(
    	.clk            (clk            ),
    	.rst_n          (rstn          ),
	.rx_data_valid(rx_data_valid),
    	.rx_rxd         (rx_rxd       ),//接收端口
    	.rx_data        (rx_data[7:0] ),//接收1字节数据
    	.rx_data_ready  (tx_data_valid) //1字节数据接收完成 output
);
endmodule

仿真结果如下图所示。
在这里插入图片描述
实际测试结果如下图
在这里插入图片描述

总结

提示:这里对文章进行总结:
实现了串口的收发,经过测试,连续发送330byte回传无误,但是第331就出问题了。可能还是对时序的控制不够精确吧,后面可以尝试使用SPI进行通信。

  • 2
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值