1. 引言
UART即通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),相信常和硬件打交道的同学都不会陌生,在学习过程中最常见的就是使用在开发板和PC或另一台开发板的通信。作为一种简单的通信协议,UART在实际使用中非常广泛。如果是学习Verilog HDL或者FPGA的新手,UART也是一个必不可少的入门例程。
这里本人对UART进行一次简单的实现,并且加入了LED和拨码开关作为外设实现对UART收发控制,可以直接在FPGA开发板上观察实验结果。因为这里讲解的定位是入门,实现最基础的功能,如果对各位小伙伴有帮助,还请赏个脸点个赞,如有不足,欢迎评论指出ଘ(੭ˊᵕˋ)੭
文章目录
2. 设计思路
既然前面说到UART是一种非常简单的通信传输协议,那我们这里就以此为设计目标,由目标去引导设计流程,循序渐进,一直到最后的实现。
2.1 接口选择
假设我们希望两台设备之间进行通信,需要一次传输一个byte,也就是8-bit,那么利用8跟线一次将数据全部发送当然是最简单不过的,但实际上由于我们对数据传输的速率要求并不高(本实验中默认波特率115200),而在实际情况下芯片的IO数量是非常稀缺的资源,为了节省IO,像这样的低速接口普遍采用串行传输的方式将8-bit数据放在一根线上依次发送(如UART/IIC/SPI)以此来选择牺牲速度去节省IO资源(但实际上有些高速接口也用串行传输如SERDES,但完全是两种东西这里不讲了)。
好了这里我们就妥协一下,选择串行的方式,将8-bit数据依次放在一根线上,一个一个依次发送好了。既然如此,如果要实现两台设备间的数据收发,首先想到的就是一根线发送,一根线接收,另一台设备也是如此,让发送对接收,接收对发送。我们最常见的UART模块设备通常就是这样使用两条线实现通信,分别是用来发送数据的TXD,和用来接收数据的RXD(实际上一根线也可以进行数据收发,如IIC的SDA)。
2.2 通信协议
前面我们打算将8-bit数据依次放在一根线上面进行发送,但紧接着我们不得不面临两个问题:
- 假如发送方开始发送数据,则开始将TXD的电平按数据依次翻转。但当连续发送两个相同的电平时(如11或00),接收方如何得知发来的是一个、两个甚至更多呢?
- 如何得知什么时刻开始发送数据的呢。假如TXD在没有数据需要发送时,默认为高电平闲置状态,那么当需要发送1的时候,接收方如何得知此时是仍在闲置状态,还是已经开始发送1了呢。
而UART就为了解决各种问题而制定了一系列规则。其中面对问题1,协议指明了单个电平需要持续的时间,让接收方知道发送的是1个bit,而不是2个或更多,这个时间对应的频率就是比特率/bps(由于一个符号用1-bit表示,所以也可以叫波特率)。面对问题2,协议对将要传输的8-bit开头加1-bit起始位,末尾加1-bit停止位,也就是将数据封装为一帧。在没有数据需要发送时,TXD处于闲置状态,默认为高电平,当有数据需要发送时,先传输1-bit低电平,这样接收方由闲置状态感知到电平变化,明白要开始传输数据了,这1-bit就是起始位。起始位发送完毕后,紧接着是8-bit数据位依次发送,这里采用的是LSB FIRST,也就是并串转换时先发送低位,最后发送高位。8-bit数据全部发送完毕后,紧接着发送1-bit高电平,将TXD重新拉回闲置状态,这一bit就是停止位。这样这两个问题是不是就解决了呢,我们赶紧动手写代码去试一下~
3. 模块设计
动手开始写代码之前,我们再回顾一下前面的内容,先把图纸画好。
- 功能:
通过拨码开关选择要发送的值,系统将拨码开关的状态读出来,并令UART发送出去;当UART接收到数据时,就将接收数据结果显示到LED。 - 接口:
时钟和复位作为全局输入信号
8-bit拨码开关和8-bitLED作为面向用户的输入输出
TXD和RXD作为UART发送和接收的接口 - 模块:
UART部分主要是两部分逻辑:i)波特率产生和采样逻辑 ii)发送和接收逻辑。对于波特率,实际上就是产生一个目标频率的脉冲信号,而对于发送和接收逻辑,实际上就是将这个波特率脉冲作为采样脉冲,将要发送的8-bit数据拆成8个1-bit放在TXD上依次发送,或者是对RXD采样,再拼成8-bit数据。
这样,UART部分的逻辑就被我们分作两组模块,分别是i)发送逻辑和发送波特率 ii)接收逻辑和接收波特率。
4. Verilog HDL逻辑设计
4.1 顶层模块
设计如上图所示的模块结构。顶层模块FPGA_Top实现UART模块的例化,并连接用户接口SW和LED。需要发送数据时,transmit模块获取波特率脉冲,并以此将SW的8-bit数据封装为一帧后通过TXD依次发送出来。在receive模块检测到RXD从空闲的高电平状态拉低,也就是检测到起始位,则开始按波特率脉冲对RXD进行采样,完成串并转换后传入LED显示。
4.2 UART模块
为了实现UART的功能,我们首先要考虑的就是,如何将一个8-bit的数据放在TXD上发送出去。前面已经讲到,为了让接收方能够正确识别,需要对数据约定波特率,并封帧发送。波特率就是利用时钟分频得到一个目标频率的脉冲,封帧就是在数据首位各加1-bit,接下来按位依次发送。这样再看,逻辑是不是就清晰许多了呢。
为了得到固定频率的脉冲,我们设计一个模块进行时钟分频;为了实现数据的最终发送,我们设计一个模块进行封帧和并串转换。接收部分亦然。
更进一步,当需要通过UART发送数据时,transmit模块会请求波特率发生器模块开始工作,紧接着产生所需频率的脉冲,transmit模块对要发送的8-bit数据进行封帧,紧接着进行并串转换,完成数据的发送。而当外界向UART发送数据时,会将处于高电平闲置的RXD拉低,一旦receive检测到RXD从闲置状态拉低,则判定当前为起始位,紧接着请求波特率发生器模块按所需频率产生采样脉冲,将数据按脉冲一一接收,完毕后掐头去尾,得到所需要的8-bit数据。
4.2.1 波特率发生器模块
-
功能和接口
波特率发生器模块负则输出一个固定频率的脉冲,要设计这样的时序逻辑电路,时钟和复位当然必不可少。为了使输出的脉冲相位可控,我们添加一个en信号,当en信号使能时,模块才开始工作。为了使输出的脉冲频率可调,我们添加一个分频控制信号prescale。 -
设计思路
波特率发生器模块产生一个需要频率的脉冲,类似于分频器,实际上其逻辑就是用计数器对输入参考时钟进行计数,当计到N时,这段时间对应的频率就是所需要的采样脉冲频率。
其中,