FPGA基础知识极简教程(6)UART通信与移位寄存器的应用

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第1张图片

博文目录

    • 写在前面
    • 正文
      • 关于UART的介绍
      • UART通信过程
      • UART、RS232以及TTL之间的关系
      • UART的使用场合
      • 有关UART的总结
      • 调试UART的技巧
      • UART的Verilog实现
        • 波特率问题
        • 发送模块
        • 接收模块
      • UART和移位寄存器之间的关系?
    • 参考资料
    • 交个朋友


写在前面

  • 相关博文1:详解移位寄存器

  • 相关博文2:uart的一些相关博客

  • 个人微信公众号: FPGA LAB

  • 个人博客首页

  • 注:学习交流使用!

相关博文1单独介绍了各种类型的移位寄存器,其中就包括串行输入并行输出移位寄存器(SIPO)以及并行输入串行输出移位寄存器 (PISO)。
移位寄存器有如下功能:

  1. 将数据延迟一定数量的时钟周期
  2. 将串行数据转换为并行数据
  3. 将并行数据转换为串行数据

第一种功能很常见,就是我们通常所说的对某某信号打几拍处理或者同步几拍等等,这是时序控制中常用的手段,如下:
FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第2张图片
这个4bit的移位寄存器可以对输入数据同步4拍!

对于后两种功能才是我今天想说的,那就是串并转换以及并串转换,其用途之一就是在UART中,下面我们将首先介绍UART相关知识最后引出PISO以及SIPO在UART中的使用情况。


正文

关于UART的介绍

UART(Universal Asynchronous Receiver/Transmitter)代表“通用异步接收器/发送器”,它是由Digital Equipment Corporation的Gordon Bell在1960年代开发的。 “通用”部分是关于数据格式和传输速度是可配置的事实。
UART能够以几种不同的模式进行通信:

  • 全双工(设备轮流发送和接收);
  • 半双工(通信双方同一时间只能单向收发);
  • 单工(仅在一个方向上,没有规定接收设备将信息发送回发送设备)。
    因为UART的通信方式可配置,所以发送和接收UART都需要以完全相同的方式进行配置,以确保成功进行通信。

基本的 UART 系统提供强大、中速、全双工通信,只有三个信号:Tx(传输的串行数据)、Rx(接收的串行数据)和接地。与其他协议(如 SPI 和I2C)不同,不需要时钟信号,因为用户向 UART 硬件提供了必要的计时信息。

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第3张图片

UART通信过程

通用异步收发器(UART)被设计为与其他UART通信,尽管它们通常本身并不直接生成通信。仅发送和接收信号。发送UART将从主板接收一个字节,然后使用其PISO(并行输入串行输出)内部移位寄存器首先发送一个“起始”位,以与接收UART交流即将传输信息的信息。然后,信息字节一次发送一次,在收到预期的位数后,再发送一个“停止”位,从而使通信线路变高。接收UART获取位流,并使用其SIPO(串行输入并行输出)移位寄存器使数据可用于其主机控制器。通过单线或其他介质进行数字信息(位)的串行传输比通过多线进行并行传输的成本低。

下图为UART通信的具体过程:
uart数据构成

空闲,无数据状态为高电平或上电。 这是电报的历史性遗产,其中将线路保持在高位以表明线路和发送器未损坏。每个字符都被构造为逻辑低起始位,数据位(可能是奇偶校验位)和一个或多个停止位。 在大多数应用程序中,最低的数据位(该图左侧的数据)首先被传输,但是也有例外(例如IBM 2741打印终端)。

起始位:起始位向接收器发出信号,通知一个新字符即将到来。

数据位:接下来的五到九位数据,取决于所使用的代码集,代表字符。 数据帧包含要传输的实际数据。如果使用奇偶校验位,它可以是 5 位,最多 8 位长。如果未使用奇偶校验位,则数据帧可以是 9 位长。在大多数情况下,数据首先以最低的位发送。

如果使用奇偶校验位,它将被放置在所有数据位之后。 通过强制逻辑高位的数量始终为偶数(对于偶数奇偶校验)或奇数(对于奇数奇偶校验),奇偶校验位提供了一种粗略的错误检测机制-如果在传输过程中某位发生翻转,则逻辑高位的数量将与所选的奇偶校验模式不匹配。 当然,如果将两位翻转,该策略就会失效,因此奇偶校验位远非防弹(安全)。 如果您非常需要无差错的通讯,建议您使用CRC。

下一位或两位始终处于标记(逻辑高电平,即“ 1”)状态,称为停止位。他们向接收器发信号,说明角色已完成。

由于起始位为逻辑低(0),而停止位为逻辑高(1),因此字符之间始终至少有两个保证的信号变化。

波特率:可以传输数据的近似速率(以比特/秒或 bps 表示)。更精确的定义是频率(以 bps 为单位),对应于传输一位数字数据所需的时间(以秒为单位)。 例如,对于 9600 波特系统,一位需要 1/(9600 bps) = 104.2 μs。系统实际上无法每秒传输 9600 位有意义的数据,因为开销位可能需要额外的时间,并且可能还需要一字节传输之间的延迟。

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第4张图片

数据位的采样与同步:

如果没有某种时钟机制,标准数字数据是毫无意义的。下图说明了原因:

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第5张图片

典型的数据信号只是逻辑低电平和逻辑高之间的转换电压。接收器只有在知道何时对信号进行采样时,才能将这些逻辑状态正确转换为数字数据。这可以使用单独的时钟信号轻松完成,例如,发射器更新时钟每个上升沿的数据信号,然后接收器对每个下降边缘的数据进行采样。

但是,正如名称"通用异步接收器/发射机"所暗示的那样,UART 接口不使用时钟信号来同步 Tx 和 Rx 设备。那么,接收器如何知道何时对发射机的数据信号进行采样?

发射机根据其时钟信号生成位流,然后接收器的目标是在每个比特周期的中间使用其内部时钟信号对传入数据进行采样。在位周期的中间采样不是必须的,但它是最佳的,因为接近位周期开始或结束的采样会使系统在接收机和发射机之间的时钟频率差异时不太可靠。

接收器序列从起始位的下降沿开始。这是发生关键同步过程时。接收器的内部时钟与发射机的内部时钟完全独立,换句话说,第一个下降沿可以对应于接收器时钟周期中的任何点:

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第6张图片

为了确保接收机时钟的有源边缘可以在位周期的中间附近发生,发送到接收器模块的波特速率时钟的频率远远高于实际波特速率(乘数 8 或 16,甚至 32)。

假设一个位周期对应于 16 个接收器时钟周期。在这种情况下,同步和采样可以按如下方式进行:

接收过程由起始位的下降沿启动。
接收器等待 8 个时钟周期,以便建立接近位周期中间的采样点。
然后,接收器等待 16 个时钟周期,从而将其带到第一个数据位周期的中间。
第一个数据位被采样并存储在接收寄存器中,然后模块等待另外 16 个时钟周期,然后采样第二个数据位。
此过程重复,直到采样和存储所有数据位,然后停止位的上升沿将 UART 接口返回到其空闲状态。

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第7张图片

UART、RS232以及TTL之间的关系

关于这几者之间的关系,知乎上的一个大神说的比较好(一般不理伸手党兼喷子),个人比较认同(文章后面会给出参考链接,见参考链接7):

UART:在通信和计算机科学中,Serial communication是一个通用概念,泛指所有的串行的通信协议,如RS232、USB、I2C、SPI、1-Wire、Ethernet等。

COM口和RS232

COM口是指针对串行通信协议的一种端口,是PC上异步串行通信口的简写,大部分为9针孔D型。COM口里分RS232,RS422和RS485,传输功能依次递增。由于历史原因,IBM的PC外部接口配置为RS232,成为实际上的PC界默认标准。所以,现在PC机的COM口均为RS232。若配有多个异步串行通信口,则分别称为COM1、COM2…

RS232或者RS485,它更多的是规定电气特性和各个引脚的功能定义,如 用-3V— -15V之间的任意电平表示逻辑“1” ;用+3V — +15V电平表示逻辑“0”,这里采用的是负逻辑。

TTL

TTL全名是晶体管-晶体管逻辑集成电路(Transistor-Transistor Logic),这种串行通信,对应的物理电平,始终是在0V和Vcc之间,其中常见的Vcc是5V或3.3V。TTL 高电平1是>=2.4V,低电平0是<=0.5V(对于5V或3.3V电源电压),这里是正逻辑。TTL接口在Minnow板子上如图:

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第8张图片
关系

UART更多关注规定编码格式的标准,如波特率(baud rate)、帧格式和波特率误差等等。RS232和TTL更多是电平标准和电压,他们在软件协议层面是一样的,如对于同样传输0b01010101来说,RS232和TTL的时序对比:

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第9张图片
如何分辨究竟是TTL还是RS232呢?一般来说,由SOC芯片引脚直接引出的一般是TTL,其高低电平不需要任何转换,可以由芯片之间驱动,节省费用;而中间接有转换芯片的可能就是RS232了,可以根据电路图的芯片型号google即可。

另一个原则是RS232通常出现在传统的PC和服务器领域,TTL通常用于嵌入式设备。

UART的使用场合

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第10张图片
为了正确操作,必须将发送和接收UART设置为相同的位速度,字符长度,奇偶校验和停止位。 接收UART可能会检测到一些不匹配的设置,并为主机系统设置一个“ framing error”标志位。 在特殊情况下,接收UART将产生不稳定的残缺字符流,并将其传输到主机系统。

与连接到调制解调器的个人计算机一起使用的典型串行端口使用8个数据位,无奇偶校验和1个停止位。 对于这种配置,每秒ASCII字符数等于比特率除以10。

一些成本非常低的家用计算机或嵌入式系统省去了UART,并使用CPU采样输入端口的状态或直接操纵输出端口进行数据传输。 虽然占用大量CPU(因为CPU时序很关键),但因此可以省去UART芯片,从而节省了金钱和空间。 该技术称为位敲打。

有关UART的总结

  • UART,中文翻译为通用异步收发器,因此通信双方是异步的,这也就意味着发送器和接收器不共享同一个时钟,各自产生自己的时钟。

  • 发送设备与接收设备要以同样的速率发送或接收数据,速率可以分为以下几种:

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第11张图片

  • 通信双方使用同样的帧结构,这样才能保证异步通信的正确性。

  • UART帧的构成:

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第12张图片

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第13张图片

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第14张图片

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第15张图片

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第16张图片

调试UART的技巧

通常在调试UART时通常很难知道您的计算机串行端口是否不工作或它是否是您的FPGA代码。 这是一个技巧:将9针电缆和跳线针2和3放在一起。 您可以使用回形针或任何方便的工具。 引脚2和3是TX和RX。 如果将它们连接在一起,则计算机会收到您从计算机发送的任何命令。 您已创建串行环回!

打开终端程序,尝试连接到串行电缆。 敲击键盘上的任意键。 波特率与其他无关紧要,因为这是回送。 如果您在终端上看到收到的发送字符,则说明您的串行电缆有效! 如果不是,则电缆或串行程序有问题。

UART的Verilog实现

看了很多版本的UART收发实现,代码设计风格不一样,但其实原理都是一致的,例如当发送数据时,肯定需要按照波特率的速率发送串行数据,那这个波特率我们是先利用系统时钟分频得到波特率时钟,用这个波特率时钟发送串行数据呢?还是直接利用系统时钟,通过计数的方式,计数从多少到多少发送一个串行数据,之后计数从多少到多少发送第二个数据呢?

波特率问题

我想都是可以的,例如博客:
波特率产生
这是我之前参考互联网上资料写的一个波特率产生的模块,用到了分频的思想!
假如我们约定uart的波特率为115200bps,我们使用的系统时钟为2MHz,那么2MHz要多少分频可以达到115200bps呢?
如下计算 :
2000_000/115200=17.361111111111111111111111111111

可以采用17分频的方法来产生这样的波特率。
尽管有一点误差,但完全不影响,uart发送完一帧数据之后距离发送下一帧数据之间还是有一定间隔的,不影响下一帧数据的发送,至于接收,更是不在话下!也就是说波特率具有一定的容错范围,引用知乎大佬的一段内容:

波特率是有一定的容错范围的,例如,STM32配置成115200波特率,每10ms发送一个30字节的字符串,串口芯片用的CH340,上位机波特率设置成113000-121000也可以接收,无乱码,差不多正负2000的波特率,这容错范围也太大了,当然如果发送频率太快,数据量太大,误码率肯定会大大增加,所以还是建议通信双方使用同样的波特率以减少误差。

链接见参考资料11!

如果使用计数的方式,其实也存在波特率问题,同样加入系统时钟为2MHz,波特率为115200bps,那么根据:

2000_000/115200=17.361111111111111111111111111111

我们肯定也是计数17个发送位数据,和前面先生成波特率时钟信号无二差别!

那下面的内容,我结合两种写法,给出自己能够理解的设计方式:


发送模块波特率产生模块

我拒绝花里胡哨的操作,因为我这个脑子想到博文:波特率产生提供的第二种方法太费劲,因此,我直接进行分配,产生波特率时钟!
系统时钟为2MHz,波特率为115200bps,因此进行17分频(占空比为1:17):

`timescale 1ns / 1ps
module baud_gen(
	input clk,
	input enable,
	output BaudTick
    );

	reg BaudTick_mid = 0;
	assign BaudTick = BaudTick_mid;
	reg [4:0] baud_count = 0;
	
	always@(posedge clk) begin
		if(enable) begin
			if(baud_count < 16) begin
				baud_count <= baud_count + 1;
				BaudTick_mid <= 0;
			end
			else if(baud_count == 16) begin
				baud_count <= 0;
				BaudTick_mid <= 1;
			end
			else ;		
		end
		else begin
			baud_count <= 0;
			BaudTick_mid <= 0;
		end	
	end		
endmodule


对这个模块进行仿真,仿真文件为:

`timescale 1ns / 1ps
module baud_gen_tb(
    );

	
	reg enable;
	reg clk;
	wire Baud_Tick;
	
	initial begin
		clk = 0;
		forever 
			# 250 clk = ~clk;
	
	end
	initial enable = 1;
	
	baud_gen u0(
	.clk(clk),
	.enable(enable),
	.BaudTick(Baud_Tick)
	);
	

endmodule

仿真波形为:
FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第17张图片
符合预期!


发送模块

对于发送模块的设计,发送8位数据,采用偶校验方式,算上起始位与结束位,总共11位。
发送一帧数据的发送模块设计如下:

`timescale 1ns / 1ps
module uart_tran(
	//input 
	input clk,
	input rst_n,
	input [7:0] data_in, //data needed to be transmitted
	input trig, //transmit data when the posedge trig

	//output
	output busy, //busy is asserted when the data is transmitting
	output reg tx //output serial data


    );
	parameter IDLE = 0, START_BIT = 1, BIT1 = 2, BIT2 = 3, BIT3 = 4, BIT4 = 5;

	parameter BIT5 = 6, BIT6 = 7, BIT7 = 8, BIT8 = 9, POLARITY = 10, STOP_BIT = 11;

	wire BaudTick;

	reg trig_r, trig_posedge;
	//get the posedge of the trig
	always@(posedge clk or negedge rst_n) begin
		if(~rst_n) begin
			trig_r <= 0;
			trig_posedge <= 0;
		end
		else begin
			trig_r <= trig;
			trig_posedge <= (~trig_r)&trig;
		end
	end

	//generate odd or even polarity bit
	wire odd_bit;   //奇校验位 = ~偶校验位
	wire even_bit;  //偶校验位 = 各位异或
	wire polarity_bit = even_bit;  //偶校验
	// wire POLARITY_BIT = odd_bit;   //奇校验

	assign even_bit = ^data_in; //even_bit = data_in[0] ^ data_in[1] ^ .....
	assign odd_bit = ~even_bit;

	assign busy = (cur_state == IDLE) ? 0 : 1 ; //busy is dessertted when the current state is IDLE

	// reg [10 : 0] data_in_buf;
	// always @ (posedge clk)
	// begin
 //   		if(!rst_n)
 //       		data_in_buf <= 11'b0;
 //   		else if(trig_posedge & (~busy))    //只读取一次数据,一帧数据发送过程中,改变输入无效
 //      		data_in_buf <= {1'b1, polarity_bit, data_in[7:0], 1'b0};   //数据帧拼接
	// end



	//state variable define
	reg [3:0] cur_state, nxt_state = IDLE;
	always@(posedge clk or negedge rst_n) begin
		if(~rst_n) begin
			cur_state <= IDLE;
		end
		else begin
			cur_state <= nxt_state;
		end
	end

	always@(*) begin
		case(cur_state)
			IDLE: begin
				if(trig_posedge) nxt_state = START_BIT;
			end
			START_BIT: begin
				if(BaudTick) nxt_state = BIT1;
			end
			BIT1: begin
				if(BaudTick) nxt_state = BIT2;
			end
			BIT2: begin
				if(BaudTick) nxt_state = BIT3;
			end
			BIT3: begin
				if(BaudTick) nxt_state = BIT4;
			end
			BIT4: begin
				if(BaudTick) nxt_state = BIT5;
			end
			BIT5: begin
				if(BaudTick) nxt_state = BIT6;
			end
			BIT6: begin
				if(BaudTick) nxt_state = BIT7;
			end
			BIT7: begin
				if(BaudTick) nxt_state = BIT8;
			end
			BIT8: begin
				if(BaudTick) nxt_state = POLARITY;
			end
			POLARITY: begin
				if(BaudTick) nxt_state = STOP_BIT;
			end
			STOP_BIT: begin
				if(BaudTick) nxt_state = IDLE;
			end
			default: begin
				if(BaudTick) nxt_state = IDLE;
			end
		endcase
	end

	reg tx_mid = 1;
	always@(*) begin
		case(cur_state)
			IDLE: begin
				if(trig_posedge) tx_mid = 1;
			end
			START_BIT: begin
				if(BaudTick) tx_mid = 0;
			end
			BIT1: begin
				if(BaudTick) tx_mid = data_in[0];
			end
			BIT2: begin
				if(BaudTick) tx_mid = data_in[1];
			end
			BIT3: begin
				if(BaudTick) tx_mid = data_in[2];
			end
			BIT4: begin
				if(BaudTick) tx_mid = data_in[3];
			end
			BIT5: begin
				if(BaudTick) tx_mid = data_in[4];
			end
			BIT6: begin
				if(BaudTick) tx_mid = data_in[5];
			end
			BIT7: begin
				if(BaudTick) tx_mid = data_in[6];
			end
			BIT8: begin
				if(BaudTick) tx_mid = data_in[7];
			end
			POLARITY: begin
				if(BaudTick) tx_mid = polarity_bit;
			end
			STOP_BIT: begin
				if(BaudTick) tx_mid = 1;
			end
			default: begin
				if(BaudTick) tx_mid = 1;
			end
		endcase
	end


	always@(posedge clk or negedge rst_n) begin
		if(~rst_n) begin
			tx <= 1;
		end
		else begin
			tx <= tx_mid;
		end
	end


	baud_gen inst_baud_gen(
		.clk(clk),
		.enable(1),
		.BaudTick(BaudTick)
		);

endmodule

测试文件:

`timescale 1ns / 1ps
module uart_tran_tb(
    );

	
	//input 
	reg clk;
	reg rst_n;
	reg [7:0] data_in; //data needed to be transmitted
	reg trig; //transmit data when the posedge trig

	//output
	wire busy; //busy is asserted when the data is transmitting
	wire tx;//output serial data
	
	initial begin
		clk = 0;
		forever 
			# 250 clk = ~clk;
	
	end

	initial begin
		rst_n = 0;
		trig = 0;
		#1000 rst_n = 1;
		trig = 1;
		data_in = 8'b1011_0011;
		#1000
		@(negedge clk) trig = 0;

	end



	
	uart_tran inst_uart_tran(
		.clk(clk),
		.rst_n(rst_n),
		.data_in(data_in),
		.trig(trig),
		.busy(busy),
		.tx(tx)
		);
	

endmodule

测试波形图如下:
FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第18张图片
可见,符合预期!

接收模块

下面给出接收一帧数据的设计!
对于串口接收数据,通常采用过采样的方式采样数据,对串口接收数据采样多少次合适呢?
我们都知道在本博文一开始设计串口数据发送的时候,由于波特率为115200bps,也就是一位数据8.68us,但我们的时钟周期为0.5us,也就是时钟频率为2MHz,那么需要17个周期发送一个数据。
那么问题 来了,我们接收的时候多少个时钟接收一个数据呢?
为了保持我们的约定,我们也是17个时钟接收一个数据,也就是我们所谓的过采样系数为17,或者说我们对一个数据采样17次,之后取中间数据作为我们接收的串行值,这就比较保险。

给出设计文件:

`timescale 1ns / 1ps
/////////////////////////////////////
// Engineer: Reborn Lee
/////////////////////////////////////


module uart_rec(
	input clk,
	input rst_n,
	input rx,
	output reg [7:0] rx_data_out,
	output reg rx_data_out_ready,
	output reg err_check,
	output reg err_frame

    );

	wire odd_bit;   //奇校验位 = ~偶校验位
	wire even_bit;  //偶校验位 = 各位异或
	wire POLARITY_BIT;   //本地计算的奇偶校验

	assign even_bit = ^rx_data_out;     //一元约简,= data_in[0] ^ data_in[1] ^ .....
	assign odd_bit = ~even_bit;
	assign POLARITY_BIT = even_bit;  //偶校验



	// the negedge of rx
	reg rx_r, rx_rr, rx_negedge;
	always@(posedge clk or negedge rst_n) begin
		if(~rst_n) begin
			rx_r <= 1;
			rx_rr <= 1;
			rx_negedge <= 0;
		end
		else begin
			rx_r <= rx;
			rx_rr <= rx_r;
			rx_negedge <= rx_r&(~rx);
		end
	end

	reg receive;
	always @(posedge clk or negedge rst_n) begin
		if (~rst_n) begin
			// reset
			receive <= 0;
		end
		else if (rx_negedge) begin
			receive <= 1;
		end
		else if(rx_state == 4'b0010 && sampleNow) begin
			receive <= 0;
		end
		else begin
			;
		end
	end


	reg [4:0] OversamplingCnt = 0;

	always @(posedge clk) begin
		// OversamplingCnt <= (receive == 0) ? 1'd0 : OversamplingCnt + 1'd1;
		OversamplingCnt <= (receive == 1 && OversamplingCnt <= 15) ? OversamplingCnt + 1 : 0;
	end
		
		
	wire sampleNow = OversamplingCnt == 8;

	reg [3:0] rx_state = 4'b0000;
	always @(posedge clk)
		case(rx_state)
			4'b0000: if(receive) begin
						rx_state <= 4'b0001;  
					 end
			4'b0001: if(sampleNow) rx_state <= 4'b1000;  // sync start bit to sampleNow
			4'b1000: if(sampleNow) rx_state <= 4'b1001;  // bit 0
			4'b1001: if(sampleNow) rx_state <= 4'b1010;  // bit 1
			4'b1010: if(sampleNow) rx_state <= 4'b1011;  // bit 2
			4'b1011: if(sampleNow) rx_state <= 4'b1100;  // bit 3
			4'b1100: if(sampleNow) rx_state <= 4'b1101;  // bit 4
			4'b1101: if(sampleNow) rx_state <= 4'b1110;  // bit 5
			4'b1110: if(sampleNow) rx_state <= 4'b1111;  // bit 6
			4'b1111: if(sampleNow) rx_state <= 4'b0100;  // bit 7
			4'b0100: if(sampleNow) rx_state <= 4'b0010;  // polarity bit
			4'b0010: if(sampleNow) rx_state <= 4'b0000;  // stop bit
			default: rx_state <= 4'b0000;
		endcase


	always @(posedge clk)
		if(sampleNow && rx_state[3]) rx_data_out <= {rx_rr, rx_data_out[7:1]};

	always @(posedge clk) begin
		rx_data_out_ready <= (sampleNow && rx_state==4'b0100);  // make sure a stop bit is received
	end


//校验错误:奇偶校验不一致
always @ (posedge clk)
begin
    if(!rst_n)
        err_check <= 0;
    else if(rx_state == 4'b0100 && sampleNow)
    begin
        if(POLARITY_BIT != rx_rr)      //奇偶校验正确
            err_check <= 1;         //锁存     
    end
end

//帧错误:停止位不为1
always @ (posedge clk)
begin
    if(!rst_n)
        err_frame <= 0;
    else if(rx_state == 4'b0010 && sampleNow)
    begin
        if(rx_rr != 1)        //停止位
            err_frame <= 1;
    end
end
endmodule

我认为这里有必要解释一些小细节,关于里面的时序关系的解释如下:
我们认为接收数据帧的起点是数据帧的下降沿,这是因为数据帧的第一个数据是起始数据,而起始数据为0:

	// the negedge of rx
	reg rx_r, rx_rr, rx_negedge;
	always@(posedge clk or negedge rst_n) begin
		if(~rst_n) begin
			rx_r <= 1;
			rx_rr <= 1;
			rx_negedge <= 0;
		end
		else begin
			rx_r <= rx;
			rx_rr <= rx_r;
			rx_negedge <= rx_r&(~rx);
		end
	end

这是下降沿标志相对于数据起点来说已经延迟了1个时钟,而接收数据有效标志receive,检测到下降沿的时候,置1,如下:

reg receive;
	always @(posedge clk or negedge rst_n) begin
		if (~rst_n) begin
			// reset
			receive <= 0;
		end
		else if (rx_negedge) begin
			receive <= 1;
		end
		else if(rx_state == 4'b0010 && sampleNow) begin
			receive <= 0;
		end
		else begin
			;
		end
	end

因此,其相对于数据起点,延迟了二拍!
而receive为1的时候我们开始计数,如下:

reg [4:0] OversamplingCnt = 0;

	always @(posedge clk) begin
		// OversamplingCnt <= (receive == 0) ? 1'd0 : OversamplingCnt + 1'd1;
		OversamplingCnt <= (receive == 1 && OversamplingCnt <= 15) ? OversamplingCnt + 1 : 0;
	end

我们取计数的大致中间位置的采样值为接收数据值。
为了保持数据与计数值保持时序上一致,也即是计数为0的时候,数据开始!
我们的处理是用对数据延迟两拍作为实际要采样的对象!
我们来提前看看效果:
FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第19张图片

测试文件:

`timescale 1ns / 1ps
 
module uart_rec_tb(
    );
	reg clk;
	reg rst_n;
	reg rx;
	wire [7:0] rx_data_out;
	wire rx_data_out_ready;
	wire err_check;
	wire err_frame;
 
	
	parameter DATA0 = 11'b11010101010; //从低位开始发送
	reg [10:0] data_in;
	
	initial begin
		clk = 0;
		forever 
			#250 clk = ~clk;
	end

	initial begin
		rst_n = 0;
		data_in = DATA0;
		rx = 1;
		#2000
		rst_n = 1;

		for(integer i = 0; i <= 10; i = i + 1) begin
			#8500
				rx = data_in[i];
		end
		
		#8680 rx = 1;
		#20000 $finish;
	
	end
	
	uart_rec inst_uart_sec(
		.clk(clk),
		.rst_n(rst_n),
		.rx(rx),
		.rx_data_out(rx_data_out),
		.rx_data_out_ready(rx_data_out_ready),
		.err_check(err_check),
		.err_frame(err_frame)
		);
	

endmodule

仿真波形:

FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第20张图片
由于我们发送的数据为01010101,因此对于偶校验来说,校验位应该为0,但是我们在仿真文件中给出校验位为1,测试一下能不能检测到校验位出错!
FPGA基础知识极简教程(6)UART通信与移位寄存器的应用_第21张图片
好了,暂时就到这里吧。

UART和移位寄存器之间的关系?

为什么说UART中使用了移位寄存器呢?
可以看如下代码:
接受模块

always @(posedge clk)
		if(sampleNow && rx_state[3]) rx_data_out <= {rx_rr, rx_data_out[7:1]};

接受模块,将串行数据转换为并行数据!
这里并不是一个周期将移位一次,而是有条件的!因此,可以看做是移位寄存器的变体版本!

发送模块,我已经把它写成状态机了,这里就不说了,其实可以写成别的形式,和接收模块类似!
不再多说!


参考资料

  • 参考资料1
  • 参考资料2
  • 参考资料3
  • 参考资料4
  • 参考资料5
  • 参考资料6
  • 参考资料7
  • 参考资料8
  • 参考资料9
  • 参考资料10
  • 参考资料11

交个朋友

不得不说,这篇博客用了我两天时间,当然睡觉居多,但也已经花费很多精力了,但完成这篇博客并更深刻理解了UART还是很值得的!如果你喜欢,也请给点个赞吧(在看)(关注)!创作不易,这里感谢了。

  • 个人微信公众号:FPGA LAB

  • 知乎:李锐博恩

  • FPGA/IC技术交流2020

你可能感兴趣的:(#,数字设计基础教程)