1.简介
UART 是一种比较过时的,但在航天等领域常用的接口协议;UART 协议最大的优点就是成本极低,但是缺点也显而易见——UART 是异步时钟,这就导致它容易因为时钟的不同步而产生错误。UART 的异步时钟很容易让它在数据收发的过程中产生错位,毕竟有谁会专门搞个时钟芯片给 UART 做极度精准的时钟源呢,如果真是这样,UART 可一点也不便宜,同步时钟的协议里有很多比专门搞个时钟芯片这种方式更低廉、优质的方案。
2.UART格式
UART 的数据格式也比较简单,它往往由起始位、7~9bit 数据位、截止位和校验位(可选)组成,一般来说大部分人都会采用 8bit 即一个字节长度的数据位。校验位属于最原始的校验方式——即大多数人初学校验就接触的奇偶校验,这种方式太过于不靠谱了,以至于在现代计算机中完全没有人会去使用这种校验方式,因此在很多设计中往往不会使用校验位。如果真的很想去做数据校验来确保数据的正确性,往往会采取对数据上层做处理的方式,比如在上层对多个 UART 数据组包、做 CRC 校验等。
UART 在不传输数据的情况下会一直保持高电平,而它的起始判别则是由高电平转低电平拉低这个动作做依据的。相比之下截止位的判别就不太一样了,截止位通常有一个高电平的保持时间,在标准的 UART 协议中,截止位的保持时间可选为 0.5、1 和 1.5 三种 UART 时钟长度(1UART 时钟长度即发送一个字节所需要的时间)。但截止位一般来说并不是用来判断数据是否传输完毕所用的,截止位的作用是让 UART 传输双方能有一个规范的传输标准,让双方不至于传数据时让两组数据之间间隔过短,让波特率时钟能够有足够的时间恢复,尽可能避免由于 UART 异步所带来的时钟偏移或数据错位。回到截止位的判别,截止位的判断往往更多地依赖于数据位的数量,在什么都要提前约定好(图 1)的 UART 中,这种方式既简单直接又行之有效。

图 1 某串口助手截图
当然了,UART 在数据位这里也有点讲究。中国人的阅读习惯通常是从左至右,但是 UART 的阅读习惯恰恰相反,UART 在收发的时候都是从低位发送至高位。比如说大写字母 A 的 ASCII 码为:01000001,那么在 UART 收发大写字母 A 时,其顺序为:10000010。因此在写 UART 的代码时,都应当注意一下 UART 的收发顺序。
3.FPGA实现
说到 UART 的 FPGA 实现就不得不提到 FPGA 的一个关键思想——状态机。作为 FPGA 工程的一个基础入门级项目,UART 将状态机演绎地很不错。对于 UART 这种项目来说,状态机是至关重要的,状态机可以将工程的每一步都表达出来,形成清晰的工程运行链路和运行条件,不仅方便人的阅读,也方便了写代码时的思路构建。
以 UART 为例,按照我的理解,其状态机可以由图 2 和图 3 表示。

图 2 RX 模块状态转移图

图 3 TX 模块状态转移图
图 2 和图 3 中的条件均为我 UART 代码中状态转移的条件,状态转移图可以帮助我们更好地构建状态机,也能更清晰地理解代码的每一块内容,同时方便我们检查状态机是否存在逻辑上的问题。值得一提的是,由于在设计中引入了 FIFO 做中间的数据缓存,因此状态转移图中也体现了 AXIS 流接口方便好用的握手机制:ready 和 valid。
FIFO 是个很好用的数据缓冲区,FIFO 可以用于异步时钟之间,方便数据收发的时钟同步,也可以用在同步时钟里做高效的数据缓存。FIFO 顾名思义即 first in first out(先入先出),其数据总是像队列一样,先进入的数据先出来,如果想自己写 FIFO 的话,可以使用双指针队列,这样就是一个完善的,拥有判空判满机制的数据队列了。一般来说没有人会自己写 FIFO,大家往往会选择使用 Alinx 提供的 AXI4 stream data FIFO(或其他类似的 FIFO 的 IP)。
FIFO 拥有自己严格的数据管理规则,总的来说就是:满时不可写,空时不可读。体现在 AXIS 流接口上:FIFO 满时,给上游写模块的 ready 信号会失效(低电平),FIFO 空时,给下游读模块的 valid 信号会失效。而 FIFO 在决定何时读写上也有自己的判断机制:在给上游写模块 ready 信号有效且上游给 FIFO 的 valid 信号有效时,FIFO 会写入数据;在给下游读模块 valid 信号有效且下游读模块给 FIFO 的 ready 信号有效时会读出数据。
在准备好 RX、TX 和 FIFO 模块以后,我们还需要波特率生成器模块,波特率生成器是一个比较简单的计数模块,在计数达到一定值的时候输出一个使能信号,rx 和 tx 模块的相关部分就是使用这个使能进行工作的,这样就可以完成在特定时间发送、接收数据的工作,利用使能代替创建一个新时钟还能减少资源占用,降低异步时钟管理的风险。一般来说,这个使能都在波特率时钟计数的中间位置,这样可以将时钟错位导致的数据错位所需时间拉到最大,降低错误的可能性。波特率时钟生成器计数则遵循以下公式:
division = freq / (baudrate * oversampling)其中 division 为分频系数(即计数值),freq 为系统时钟频率(计算时以 Hz 为单位),baudrate 为波特率,oversampling 为过采样数(不过采样即为 1)。过采样是为了确保传输的正确性,在干扰较大或者传输距离较长的情况下推荐使用,UART 的过采样数一般为 16,其原理就是对同一个波特率时钟下的数据进行多次采样,互相比对来确保传输的准确性。
4.目前可进行的优化
UART 顶层可以封多种协议进行传输,这样可以大大提高 UART 传输的正确性。
在 UART 的 FPGA 工程中,我个人认为应该放置两个 FIFO,FIFO 分别与 RX 和 TX 模块连接后,各留出一个接口用于系统交互,这样可以大大提高 UART 代码的复用性,若能将其封装为 IP,可以在后续任何能用到的地方及时导入使用。
值得一提的是在系统时钟产生部分(差分转单端时钟的地方)可以使用 clock_wizard 产生时钟信号,在产生时钟信号的同时,clock_wizard 在稳定运行几个时钟周期以后会输出一个 locked 信号,这个信号可以作为系统的复位信号,使整个系统在开启时进行复位,保证系统的正常运行。
默认评论
Halo系统提供的评论