基于Verilog开发的UART驱动

1. 概念

        UART 的英文全称是 Universal Asynchronous Receiver/Transmitter ,即通用异步收发器,串口是串行接口的简称。

        如上图所示,uart有两根线,一根rx,用于FPGA接受PC的指令;另一根tx,用于输出FPGA的指令。

2.协议

        串口的协议如上图所示,分为如下几个状态:空闲,其实,发送数据,发送校验位(可选),发送停止位;在空闲状态下信号为高;起始位标志着数据开始发送,FPGA检测到(发送)一个低电平;数据位为协议传输的内容;奇偶校验位是可选的;奇校验的功能是判断数据为中1的个数,保证连上校验位中1的个数是奇数个;偶校验就是保证1的个数为偶数个;停止位就是将信号拉至高电平。

        串口是一种慢速协议,常用的波特率有:9600,19200,38400,57600,115200等。波特率的概念就是每秒发送的字节数(bit/s);这里举一个例子,波特率为9600,也就是9600bit/s,也就是一秒发送1200byte(1200个字节);

3.模块划分

在常见的开发板教程中,会存在同时发送多个数据出现乱码的情况,这里会作为一个小问题点解决这个问题。模块整体划分如下:晶振的50mhz时钟先进入pll产生一个50mhz的更优质的时钟。随经过复位产生模块与uart时钟匹配的复位信号,将该时钟与复位信号一起传入uart_rx和uart_tx模块。

uart_rx模块解析传入的数据后将其交给用户,同时产生一个标志该数据可用的valid信号;用户也可产生一个数据经由tx模块发送到串口线上;这里采用类似于AXI写法的风格来编写代码。

4.部分代码解析

驱动模块顶层,上述模块划分部分的接口可以从下面的代码段中看清楚;为了输出给用户使用,这里把串口的时钟和复位一起输出出去;此外,顶层驱动代码就是按照上述框图例化;

(
	input								i_clk			,
	input								i_rst			,
						
	input								i_uart_rx		,
	output								o_uart_tx		,
	
	input	[P_UART_DATA_WIDTH - 1 : 0]	i_user_tx_data	,
	input								i_user_tx_valid	,
	output								o_user_tx_ready	,
	
	output	[P_UART_DATA_WIDTH - 1 : 0]	o_user_rx_data	,
	output								o_user_rx_valid	,
	
	output                             o_user_clk  ,
	output                             o_user_rst  
);

时钟分频模块,在上一篇博客中已经提过,这里不再提及;

复位产生模块,由于不同模块之间产生的时钟不同。例如50mhz的时钟持续一个周期的复位信号在9600波特率下极有可能检测不到,这里做了一个复位产生模块;也就是直接在所在时钟域按照输入位数打拍;uart_tx模块,该模块是串口的发送模块,用户产生一个数据,在tx模块准备好了之后发送出去。ready信号表示tx模块当前是否准备完毕,在数据开始发送时拉低,在计数器计数到数据发送或者校验位结束时拉高;比较麻烦的点是在于校验位的计算;如果事先知道发送的数据位数,这里可以直接按位异或即可得到结果,然而这里数据位数时使用参数化的,因此需要时序逻辑解决;具体解析在注释后边;

always@(posedge i_clk,posedge i_rst)
begin
	if(i_rst)
		ro_user_tx_ready <= 'd1;
	else if(r_cnt == 3 + P_UART_DATA_WIDTH - 3 && P_UART_CHECK == 0)
		ro_user_tx_ready <= 'd1;
	else if(r_cnt == 3 + P_UART_DATA_WIDTH - 2 && P_UART_CHECK > 0)
		ro_user_tx_ready <= 'd1;
	else if(w_tx_active)
		ro_user_tx_ready <= 'd0;
	else 
		ro_user_tx_ready <= ro_user_tx_ready;
end 

always@(posedge i_clk,posedge i_rst)
begin
	if(i_rst)
		r_cnt <= 'd0;
	else if(r_cnt == 3 + P_UART_DATA_WIDTH - 3 && P_UART_CHECK == 0)//起始使  + 数据使 + 校验使 + 停止使 - 停止使 - 校验使 - 1
		r_cnt <= 'd0;
	else if(r_cnt == 3 + P_UART_DATA_WIDTH - 2 && P_UART_CHECK > 0)
		r_cnt <= 'd0;
	else if(!ro_user_tx_ready)
		r_cnt <= r_cnt + 1;
	else 
		r_cnt <= r_cnt;
end


always@(posedge i_clk,posedge i_rst)
begin
	if(i_rst)
		r_tx_data <= 'd0;
	else if(w_tx_active)
		r_tx_data <= i_user_tx_data;
	else if(!ro_user_tx_ready)
		r_tx_data <= r_tx_data >> 1;//0101 0101 发鿁最后一位即叿
	else 
		r_tx_data <= r_tx_data;
end

always@(posedge i_clk,posedge i_rst)
begin
	if(i_rst)
		ro_uart_tx <= 'd1;
	else if(w_tx_active)//起始使
		ro_uart_tx <= 'd0;
	else if(r_cnt == 3 + P_UART_DATA_WIDTH - 3 && P_UART_CHECK > 0)
		ro_uart_tx <= P_UART_CHECK == 1 ?  ~r_tx_check : r_tx_check ;
	else if(r_cnt >= 3 + P_UART_DATA_WIDTH - 3 && P_UART_CHECK == 0)//没有校验 ,直接停止位
		ro_uart_tx <= 'd1;
	else if(r_cnt >= 3 + P_UART_DATA_WIDTH - 2 && P_UART_CHECK >  0)//有校骿 ,晚丿拍直接停止位
		ro_uart_tx <= 'd1;
	else if(!ro_user_tx_ready)//数据使
		ro_uart_tx <= r_tx_data[0];
	else 
		ro_uart_tx <= 'd1;
end

always@(posedge i_clk,posedge i_rst)
begin
	if(i_rst)
		r_tx_check <= 'd0;
	else if(r_cnt == 1 + P_UART_DATA_WIDTH - 1)//计数到数据位发鿁结板
		r_tx_check <= 'd0;
	else 										//uart_tx 在奇校验旿 = _ r_tx_check 偶校验时 = r_tx_check;
		r_tx_check <= r_tx_check ^ r_tx_data[0];// 奇校骿 0000 0001  r_tx_check = 1 , uart_tx = 0         0000 0011 r_tx_check = 0  uart_tx=1 	
end	

 uart_rx模块,由于串口是从低位先发送,然后再发送高位的,这里使用移位的操作,也就是舍弃掉之前数据的最低位,然后将rx数据接到最高位,如下图所示;

always@(posedge i_clk,posedge i_rst)
begin
	if(i_rst)
		ro_user_rx_data <= 'd0;
	else if(r_cnt >= 1 && r_cnt <= P_UART_DATA_WIDTH)
		ro_user_rx_data <= {i_uart_rx,ro_user_rx_data[P_UART_DATA_WIDTH - 1 : 1]};
	else 
		ro_user_rx_data <= ro_user_rx_data;
end 

其他模块,在做数据回环实验的时候,连续发送数据时tx发送数据较慢,通常使用FIFO将rx的数据缓存,如下图所示;rx数据接收完成后以valid作为写使能写入fifo;当tx模块空闲且fifo不为空时,用户从fifo里面读出数据并通过tx发送;

uart_fifo uart_fifo_u0 (
  .clk		(wo_user_clk	)	,     
  .srst		(wo_user_rst	)	,   
  .din		(w_user_rx_data	)	,     
  .wr_en	(w_user_rx_valid)	, 
  .rd_en	(r_fifo_rden)		, 
  .dout		(w_user_tx_data	)	,   
  .full		()			,   
  .empty	(w_user_empty)  
); 

5.仿真

时钟分频

产生复位

无校验位 tx

无校验rx

有校验位tx奇校验;

有校验位tx偶校验;

有校验rx;

6.计划

        下周更新SPI协议。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值