本文简单介绍Zynq中的UART控制器,重点放在编程所需了解的知识。很多功能使用库函数可以快速配置,因此没必要仔细了解每个寄存器是干什么的这种问题,应把精力放在UART的特性、工作原理和可实现功能方面。
UART控制器
Zynq中的UART控制器是一个全双工异步收发器,支持各种可编程的波特率和I/O信号格式,能够自动生成奇偶校验,支持多主检测模式。
UART操作由配置和模式寄存器控制。采用独立的Rx和Tx数据路径,每个路径包含一个64字节的FIFO。控制器对Tx和Rx FIFO中的数据进行串并转换操作,还有一个模式开关,支持RxD和TxD信号的各种回环配置。FIFO的中断标志支持轮询处理或中断驱动处理两种方式。程序中我们通过Rx和Tx的数据端口寄存器来读写数据字节。
Zynq内部有两个UART控制器,UART0和UART1,都支持如下特性:
- 可编程波特率发生器;
- 64字节的接收和发送FIFO;
- 可编程的串口协议,6、7、8位的数据位;1、1.5、2位的停止位;奇校验、偶校验、无校验等;
- 奇偶校验检测、帧错误检测、溢出错误检测;
- 生成换行符;
- 生成中断;
- RxD和TxD模式,使用模式开关选择Normal、echo或诊断回环;
- 将UART0和UART1回环;
- 调制解调控制信号CTS、RTS、DSR、DTR、RI和DCD,仅在EMIO接口上可用。
UART波特率发生器
UART的模块框图如下所示:
波特率发生器为接收机和发射机提供波特率时钟。下表给出了一些串口时钟、波特率和错误率之间的关系。
发送FIFO与数据流
我们通过向TxFIFO寄存器写值来把数据加载到TxFIFO中。TxFIFO有空标志,FIFO中有数据时此标志无效。当TxFIFO进入完全中断状态(TFULL)时,则表明FIFO已满。此时再执行写操作,会触发溢出,数据不会加载到TxFIFO中。TxFIFO几乎满标志(TNFULL)表示FIFO中只有一个字节的空闲空间。我们可以设置门限触发器(TTRIG),当FIFO中的字节达到此值时会触发TTRIG(图示见下小节)。
传输模块从TxFIFO中移出并行数据,将其加载到发射机移位寄存器中,实现串行化。在波特率时钟的下降沿传输数据,先传LSB,如下图:
接收FIFO与数据捕获
RxFIFO存储接收器串行移位寄存器收到的数据。RxFIFO也有与TxFIFO功能类似的空标志、完全中断状态RFUL、门限触发器RTRIG。几种标志和中断的示意如下图:
接收时,UART不断地对RxD信号进行过采样,当采样检测到一个由高到低的转换时,便将其视作起始位的开始。在波特率时钟周期的一半时,再进行三次采样,如果仍然时低电平,则认为这是一个有效的起始位。示意如下,下图是16倍的过采样:
确定了一个有效的起始位后,重新同步接收器的波特率时钟,以便在每一位的中心位置对传入的RxD信号进行采样(防止滑码),示意如下图。如果您编写过Verilog的UART收发程序,对这个处理方法应该不会感到陌生。
当确定了一个串行数据位的值后,将其转移到接收移位寄存器中。当组装好一个完整的字符后,移位寄存器中的内容被推送到RxFIFO中。
接收器错误检测
UART控制器提供了四种错误检测机制。
- 奇偶检验错误。每接收一个字符时,接收器根据UART计算接收数据位的奇偶校验值,将它与接收到的奇偶检验位进行比较。如果值不同,则奇偶校验错误标志位置1,并产生中断。
- 帧错误。当接收器在帧末没有接收到有效的停止位时,帧错误标志位置1,产生中断。
- 溢出错误。当接收到一个字符时,UART控制器检测RxFIFO是否有空间。如果有则将该字符写入RxFIFO;如果RxFIFO已满则等待;如果又检测到了下一个数据的起始位,且RxFIFO仍然是满的,那么等待的数据将丢失,同时溢出标志位置1,产生中断。
- 超时机制。接收器有一个10位的递减计数器,每当在RxD上收到一个新的起始位或者程序向专门的标志位写入1重置时,计数器都会重新加载并倒数。当计数器减到0时认定发生超时,相应标志位置1(此时程序中应重置计数器),产生中断。
上面介绍的4种中断都不是必须的,我们可以屏蔽这些中断,也可以禁用超时机制。
I/O模式选择
模式开关控制着RxD和TxD信号在控制器内部的路由。有4种工作模式,如下图所示:
下面简单介绍下各模式的功能:
- 正常模式:这是标准的UART工作模式,TxD和RxD有独立的路径。
- 自动echo模式:Echo模式仍然会接收RxD上的数据,但模式开关将数据路由到接收器和TxD引脚,控制器不能用发射机来发射数据。
- 本地回环模式:这个模式内部信号不与RxD和TxD连接,发射的数据被回环到接收器中。
- 远程回环模式:此模式将RxD与TxD连接,控制器无法在TxD上发送任何内容,也不能在RxD上接收任何内容。
程序设计方法
如果我们的需求只是用串口打印一些调试信息,那么直接使用printf或xil_printf函数就足够了,无需对串口做任何配置。如果我们需要用UART完成某些特定功能,那么就有必要细致了解UART的程序设计方法。本文只简单描述一下设计思路,后面的文章给出实例。
1. UART启动
UART的启动顺序如下:
- 复位控制器;
- 配置I/O信号路由。Rx/Tx可以路由到MIO或EMIO,只有EMIO可以使用调制解调控制信号;
- 配置UART参考时钟UART_Ref_Clk;
- 配置控制器功能;
- 配置中断,我们可通过中断来管理RxFIFO和TxFIFO;
- 配置调制解调控制(可选);
- 管理发送和接收的数据,可以采用轮询或中断驱动处理两种方式。
2. 配置控制器函数
控制器功能主要配置字符帧、波特率、FIFO触发器等级、Rx超时机制,并启用控制器。重置控制器后必须要
配置所有这些参数。步骤如下:
- 配置UART字符帧,数据位长度、校验位、停止位、I/O模式等;
- 配置波特率;
- 设置RxFIFO触发器等级,可以选择启用或禁用该功能;
- 使能控制器;
- 配置接收器的超时机制,可以选择启用或禁用该功能。
3. 发送数据
程序中我们可以使用轮询和中断两种方式控制到TxFIFO和RxFIFO的数据流。这两个FIFO大小为64个字节,因此当TxFIFO的空标志有效时,我们可以直接向其写入64个字节,无需检查TxFIFO的状态。实际上当发送器处于活跃状态时,可写入的字节数要超过64个字节,因为控制器同时也在移出数据,将其串行化转移到TxD信号上。
使用轮询方法的数据发送流程如下:
- 检查TxFIFO是否为空;
- 向TxFIFO写入数据,可写入64个字节;
- 写入更多数据。我们可以等TxFIFO空了后再写64个字节,即执行第2步;也可以检测TxFIFO是否有空间,即不停的读取TFUL标志和写单个字节的数据。
使用中断方法的数据发送流程如下:
- 禁用TxFIFO空中断;
- 向TxFIFO写64个字节的数据;
- 检测TxFIFO是否有空间,不停的读取TFUL标志和写单个字节的数据;
- 重复步骤2-3,直到TxFIFO已满;
- 使能TxFIFO空中断;
- 等待,直到TxFIFO为空,然后从步骤1重新开始;
4. 接收数据
使用轮询方法的数据接收流程如下:
- 等待,直到RxFIFO中的数据数量达到触发等级;
- 从RxFIFO中读取数据;
- 重复步骤2直到FIFO空;
- 发生Rx超时中断时将其重置。
使用中断方法的数据接收流程如下:
- 使能中断;
- 等待,直到RxFIFO中的数据数量达到触发等级或者发生超时;
- 从RxFIFO中读取数据;
- 重复步骤2-3直到FIFO空;
- 清除中断标志。
UART的I/O接口
UART的RxD和TxD信号可以路由到多组MIO管脚之一或EMIO接口。所有调制解调流控制信号只能路由到EMIO接口,再MIO管脚上不可用。下表给出了所有UART信号:
我们在Vivado中搭建硬件环境时,也要选择使用UART0还是UART1,以及它们的信号路由到的MIO管脚。