打印
[VHDL]

玩转VHDL009-异步串口UART

[复制链接]
1390|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
ucx|  楼主 | 2017-10-16 17:31 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 ucx 于 2017-10-16 17:33 编辑

异步串口UART
串口综述
串口通信虽然古老,但目前在工业控制和计算机通信中仍很常用。串口在工业控制中常有把串口通过光纤传送的远端的需求,实现这一功能的设备业界常称之为串口猫。串口猫通常不关心串口的波特率和具体的格式,采用高速采样的方式把一路或多路串口复接成一路高速光信号,接收端比特同步后解复用出串口数据。这种高速采样的方式,计划在后续介绍高速信号的比特同步后再讲解。

在低速信道传送较高速串口时(比如在64K信道传送56K波特率串口),或者串通信终端需要实现UART协议。串口通信中,以字节B[7..0]为单位。异步串口UART通信时通常采用10个比特,有时也增加一个奇偶校验位使用11个比特。早期的串口通信协议也有把奇偶校验位用作命令控制使用。现在串口通信多采用10位方式,通信错误校验交由上层完成。为顺应潮流,也为清晰起见,本帖以10位串口通信方式介绍UART的收发FPGA实现。

RS232RS485/RS422对串口10的电平大小规定不尽相同,不属于本帖讨论范围,读者可查阅相关标准或串口芯片手册。

UART规定串口在空闲时发送常1,有一个字节数据B[7..0]需要发送时,发送10比特顺序依次是:起始B0B1..B6B7终止,其中起始位为0,终止位为1。例如要发送数据0x35那么实际发送10个比特依次为0 1 0 1 0 1 1 0 0 1

串口波特率的实现
通常,系统时钟速率远远高于串口速率,所以可以系统时钟频率除以波特率后取整的分频比R。然后用系统时钟按照模R计数,那么模R计数器的溢出率即为串口波特率。然而,有时我们想设计的更完美,希望计数器的溢出率和串口速率在标称值上完全相同。也即希望系统时钟可以被波特率整除,然而这在实际应用中常是不现实的。我们知道,串口常用波特率为300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 43000, 56000, 57600115200。我们不考虑43000这个波特率,那么其他所有波特率均可以整除64512000。这一点在003帖分频器中已作介绍,并举例系统时钟为80M时如何实现等效64.512M时钟。在本帖我们采用另一个常用时钟65.536M来实现64.512M时钟。

因为65.536M÷64×63=64.512M,所以用一个6位计数器每个时钟+1计数,扣除在计数器全0的时刻即实现等效64.512M时钟的clk_en

         signal share     : std_vector(0 to 5);         -- 0 ~ 63

signal clk_en    :std_logic;

       Process(clock) begin

                if rising_edge(clock) then

                         share <= share + 1;

                         clk_en <= b_or(share);

                end if;

       End process;
串口接收比特同步
无论异步通信还是同步通信,在信号接收时都需要比特同步。区别于同步通信的异步通信,在一次通信开始前要发送前导表示通信的开始。UART的起始比特就完成此功能,以太网通信的7个字节0xAA前导码和一个字节0xAB表征的SOF具有同样的功能。串口接收引脚RXD在空闲1状态跳变为0表示一次串口通信的开始。更一般地说,RXD从1到0的时刻总是表征一个比特的开始。
然后,计算出64.512M时钟与串口波特率的比值R。R可由配合FPGA工作的CPU计算所得,或者由004帖提到的RT_Divider产生。此时,分母为波特率,分子为64.512M。考虑到串口波特率总是100的整数倍,可以把分子和分母同时除以100处理,那么设置波特率均按除以100处理。即,当设置波特率为96时表征波特率为9600。
现在定义一个串口接收比特计数器cn_rFrq,当RXD从1跳变到0时清零cn_rFrq,否则计数器cn_rFrq以上一节提到的clk_en节拍模R计数。试想一下,在cn_rFrq在计数到R/2时采样RXD总能正确读取接收到的每个串口比特。当然,要去除RXD从1跳变到0时RXD刚好计数到采样时刻的特殊情况。UART正常发送时,最多9个比特就会出现一次1到0的跳变。即RXD上连0或连1的个数不超过9个,当本地时钟和串口采用的时钟频率相对误差小于5%(这是很容易满足的)条件下,上述机制保证每个比特均能正确采样。用VHDL语言表述上述过程如下:
signal cn_rFrq,nBaud       :std_vector(23 downto 0);
signal RXD_rg                    :std_vector(0 to 1);
nBaud = R-1;
Process(clock) Begin
         if rising_edge(clock)then
1.               ShiftLeft(RXD_rg,RXD, clk_en);
2.               RstIncDec(cn_rFrq,clk_en and (NegDiff(RXD_rg) or cn_rFrq >= nBaud), clk_en);
3.               rx_smp <=clk_en and cn_rFrq=SHR(nBaud, 1) and not NegDiff(RXD_rg);
         end if;
End process;
进程中使用clk_en作为使能是为了实现64.512M时钟的效果,这一点已经多次论述。行2NegDiff(RXD_rg)函数在RXD_rg(0)= '1'RXD_rg(1)= '0'时返回真,此时正对应RXD的下降沿附近。行2中用cn_rFrq >= nBaud作为cn_rFrq的一个清零条件,即实现了模R计数。行3cn_rFrq=SHR(nBaud, 1)表征cn_rFrq在计数到R/2时刻,and not NegDiff(RXD_rg)是为了扣除上述提到的特殊情况,防止起始比特被采样两次。而在NegDiff(RXD_rg)之前任一时刻分频计数器cn_rFrq达到R/2产生采样脉冲rx_smp按照下文的逻辑读到的串口接收比特值为1,仍为空闲态,不影响UART判断逻辑。

相关帖子

沙发
ucx|  楼主 | 2017-10-16 17:33 | 只看该作者

串口字节接收恢复
在串口综述中提到,串口数据总是以0开始,紧接着是8个比特的数据,最后以1结束。也就是说,起始比特的前一个比特恒定为1,即起始比特总是发生在前一个比特为1当前比特为0的情况。由此,定义一个比特计数器rSnrSn9表征串口空闲或已完成上一次接收的状态。rSn < 9为正在逐个比特接收状态。

rSn9且接收到的前一比特为1当前比特为0rSn=0。若rSn < 9则每接收一个比特rSn加一计数。那么,当rSn=8时的采样时刻rx_smp刚好接收到停止比特,那么先前接收到的8个比特即为所需字节。另外,为了使系统上电时,UART处于空闲状态,特定义rSn初始态为rSn9的空闲态,在Quartus II中用power_up_level=high描述。这是为了去除系统上电后就发生的一次多余的串口接收事件。VHDL精确表述为:

attribute altera_attribute                                  :string;

signal rSn                                                         :std_vector(3 downto 0);

attribute altera_attribute of rSn : signal is"power_up_level=high";

signal r_rg                                                       :std_vector(0 to 7);

signal b_rx                                                      :std_logic;

Process(clock) Begin

         if rising_edge(clock)then

                   if rx_smp ='1' then

1.                        b_rx<= RXD_rg(RXD_rg'left);

2.                        ShiftRight(r_rg,b_rx);

3.                        RstIncDec(rSn,rSn>=9 and r_rg(r_rg'left) and not b_rx, rSn < 9);

4.                        LoadValue(q_rx,r_rg, b_rx and rSn=8);

5.                        rx_err<=  not b_rx and rSn=8;

                   end if;

6.               upd_rx <=rx_smp and b_rx and rSn=8;

         end if;

End process;

1b_rx为当前接收到的比特值。

2:右移b_rx使先接收到的放到低位。

3r_rg(r_rg'left)是相对于b_rx的前一比特值,rSn清零的三个条件是rSn9的空闲态,前一比特为1,当前比特为0

4:在rSn=8且当前比特b_rx为停止位,正确接收,把以为寄存器r_rg的值装载到输出q_rx中。

5:在rSn=8b_rx=0,没有正确接收到停止位,发生一次错误。

6upd_rx输出用来表征发生一次串口接收事件,用于更新串口接收。

q_rxupd_rx交由上层处理,UART接收处理完毕。

串口发送
为简化上层设计,串口发送通常设置缓存功能。本帖为突出UART逻辑,只设置一个字节的缓存。发送对上层接口描述为:

         d_tx                   :in std_vector(7 downto 0);

         t_en                   :in std_logic;

         TXD,t_busy      : out std_logic;

端口d_tx时待发送的数据,t_en为一个时钟周期的高电平脉冲时启动发送,t_busy用来指示发送忙闲状态。t_busyt_en后被置高,开始发送终止比特时置低。上层在闲忙指示为0的状态使能t_en保证正确发送,若在t_busy=0时使能t_en则有可能会被丢弃。TXD时发送到串口的信号。

发送使用的信号定义为:

signal cn_tFrq  : std_vector(23downto 0);发送波特率计数器

signal t_data   : std_vector(0to 7); 用作一个字节的缓存。

signal tx_rg      :std_vector(0 to 8); 发送移位寄存器。

signal t_wait    : std_logic;指示缓存等待发送。

signal tx_smp  : std_logic; 依据波特率产生的发送比特使能脉冲。

signal tSn          :std_vector(0 to 3);发送比特计数器。

Process(clock) Begin

         if rising_edge(clock)then

         1.      RstIncDec(cn_tFrq, clk_en andcn_tFrq>=baud_tx, clk_en);

         2.      tx_smp <= clk_en and cn_tFrq=0;

         3.      LoadValue(t_data, d_tx, t_en);

         4.      SetReset(t_wait, t_en, t_smp andtSn>=9);

         5.      ShiftRight(tx_rg, '1', tx_smp);

         6.      LoadValue(tx_rg, t_data & '0',  tx_smp and tx_wait and tSn>=9);

         7.      RstIncDec(tSn, tx_smp and tx_wait andtSn>=9, tx_smp and tSn<9);

                   8.   t_busy <= t_en or t_wait ortSn<9;

         end if;

End process;

9.  TXD <=tx_rg(tx_rg'right);

12:再解释就过于啰嗦了。

3t_en装载数据d_tx到缓存t_data

4t_wait用来等待起始比特的开始时刻到来。

5:右移tx_rg,被移入的是1,所以不用显式发送终止比特。

6:装载起始比特和t_datatx_rg

7tSn>=9表征发送完毕或空闲。

8:在发送终止比特之前t_busy始终被置高电平。

9:万事大吉。

其他
为了避免系统在上电时,串口发送一个无用的字节,在定义tx_rg之后还要增加一句
attribute altera_attribute of tx_rg : signal is"power_up_level=high";另外在电路板上对TXD引脚应做弱上拉处理。由于串口是低速信号,RXD引脚会有跳弹现象,为使系统稳定应增加跳弹处理单元。

关于UART,您还有什么疑问吗?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

ucx

28

主题

85

帖子

5

粉丝