FPGA常见接口及逻辑实现(一)—— UART

一、UART简介

异步通用串行收发器,以下简称串口,应该是大部分人学习FPGA接触到的第一种接口协议,协议简单,使用场合多,方便操作,我第一次完全自己编写的模块也是UART,属于是梦开始的地方了。

协议内容很简单,空闲为高电平,第一个低电平为起始位,之后是一连串的数据位,然后是奇偶校验位,最后是一到两个高电平的结束位,速率由收发两端波特率决定,物理连接两根线。

由于UART协议的标准网上一搜一大堆,我就不多废话,直接进入代码部分了。

二、UART模块verilog实现思路

verilog的编写思路相对比较固定,一般来说,计数器和状态机就可以解决绝大部分问题,剩下的就是根据条件对各信号赋值。网上能看到很多用状态机写串口的,讲道理,多少有点小题大做了,两个计数器就可以完美解决串口模块。

唯一要解决的问题就是波特率,波特率其实就是串口收发的时钟频率,例如115200的波特率其实就是意思用115.2kHz的时钟就可以实现收发,但是一般FPGA没有那么低频的时钟,所以用高频时钟分频产生一个低频时钟去实现串口收发,分频就简单了,用输入时钟计数,计数达到需求的值时清零,产生一个类似时钟跳变的信号,然后在每次跳变沿进行相应的操作就可以了。

然后现在的问题就到了波特率计数值的计算,在网上总能看到问不同时钟频率的波特率计数怎么算的,其实最简单的理解方式就是看每个参数的单位,时钟的频率是该时钟在一秒钟内的周期数,说通俗点就是一秒钟里跳了多少次,我们以(次/秒)作为时钟频率的单位,波特率就简单了,他的单位是bps,也就是(比特/秒),本来就很容易理解,即每秒发多少个比特。用(次/秒)除以(比特/秒),得到的参数单位是(次/比特),那这两者之间的关系就很清晰了,即每以当前波特率发一个比特,当前频率的时钟需要跳多少次,正是我们需要的计数值。

总体的计数器思路有了之后,现在问题回到了串口收发,这个就很简单了,如上文所述,在每次跳变时进行串口操作即可,从起始位开始,以此收发数据位,校验位,停止位,最后结束整个过程。

还有一个需要注意的点是,串口是从低位开始发送的,接收也需要从低位开始寄存。

以下是按照思路绘制的波形图,其中baud_cnt是波特率计数,在收发过程中每个时钟周期都加一,bit_cnt是串口操作比特的计数,baud_cnt计满之后bit_cnt加一。

对于接收端,起始位开始接收操作,1到8位采样串口输入的值并寄存到对应位。

对于发送端,开始操作后,先发送一个低电平作为起始位,1到8位数据寄存器的对应位,因为是发送端,还要发送校验位和停止位,至少有一位停止位,所以最少也要计数到9。

三、UART发送模块的verilog实现

刚学习FPGA时,看到过一个说法,对于各种接口,实现起来一般都是发送端比较容易,接收端比较困难,虽然我觉得都差不多但还是先从发送端开始编写吧,而且没有发送端也不方便进行接收端的仿真。

接下来看看如何用verilog实现一个可配置参数的串口发送模块,可配置的参数包括:

输入时钟频率、串口波特率、串口停止位个数、奇偶校验模式。

模块端口声明:

module uart_tx #(
    parameter SYS_CLK_FREQ = 50_000_000,    // 输入时钟频率,单位 MHz
    parameter BAUD_RATE = 115200,           // 串口波特率,单位 bps
    parameter STOP_BIT_CNT = 1,             // 串口停止位个数
    parameter PARITY_CODE = 2               // 奇偶校验,0:无校验 1:奇校验 2:偶校验
) (
    input   wire        clk,                // 输入时钟
    input   wire        rst_n,              // 输入同步复位

    output  wire        dreq,               // 输出数据请求信号
    input   wire [7:0]  din,                // 输入待发送数据
    input   wire        tx_start,           // 输入发送启动信号

    output  wire        uart_tx             // 输出串口发送
);

此处多提一嘴,为了提高模块的复用性,要注意当前模块与其他模块交互的接口,可以看到我预留的上游模块的接口是dreq和din,典型的fifo接口,这样不管上游模块是什么,只要通过一个fifo就可以和当前的串口发送模块进行交互。这其实是一个很容易注意到的问题,但是我发现那些卖板子的机构很多代码都不注意这一点。曾经有一次我要调试一个I2C的从机,想找一个写好的主机代码用一下,看了下芯路恒和正点原子的,正点原子的首先就吓我一跳,一个bit一个bit写状态机也太逆天了,芯路恒的还好,就是有上述的问题,对上游模块的接口太复杂了,虽然命名很清晰,但是还要专门写一个针对这种接口的控制模块,很麻烦,为了避免每写一个接口模块就要写一个控制模块,还是尽量用通用接口吧。

组合逻辑:

// 波特率计数(次/比特) = 时钟频率(次/秒)/波特率(比特/秒)
    l
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值