原创 【连载】【FPGA黑金开发板】Verilog HDL那些事儿--串口模块(十一)

2010-10-7 16:33 1695 6 6 分类: FPGA/CPLD

声明:本文为原创作品,版权归akuei2及黑金动力社区(http://www.heijin.org)共同所有,如需转载,请注明出处http://www.cnblogs.com/kingst/

3.4 实验十:串口模块

单片机?串口?这些已经是众所周知的组合了吧。但是有一点你是否明白过串口传输的细小部分呢?我们先抛开硬件接口不谈(基本上没有什么好谈),在传统的串口实验。我们只是在串口的表面上,对单片机的寄存器进行配置和查询,来实现串口的操作~

实际上你知不知道,串口在传输的期间,到底发生了什么事情?使用Verilog HDL对串口建模,你会很底层的窥探到它。

串口传输数据都是一帧数据(11位)

位作用

0

起始位

1~7

数据位

9

校验位

10

停止位

在串口的总线上“高电平”是默认的状态,当一帧数据的开始传输必须先拉低电平,这就是第0位的作用。第0位过后就是8个数据位,这八个数据位才是一帧数据中最有意义的东西。最后的校验位或者停止位,基本上是没有重要意义,作用如同命名般一样。

串口传输还有另一个重要参数就是“波特率”。很多朋友都误解“波特率”是串口传输的传输速度,这样的理解基本上是无误。但是在微观上“波特率”就是“一个位的周期”,换句话说就是“一个位所逗留的时间”。

常用的波特率有9600 bps 和 115200 bps ( bit per second )。“9600 bps” 表示每秒可以传输9600位。但是经过公式计算“一个位的周期”就会暴露出来。

一个位的周期 = 1 / bps

= 1/ 9600

= 0.000104166666666667

从上述的公式,我们明白一个事实 9600 bps ,一位数据占用 0.000104166666666667s 时间。如果是一帧11位的数据,就需要

0.000104166666666667 x 11 = 0.00114583333333334

那么一秒钟内可以传输

1 / 0.00114583333333334 = 872.727272727268

872.727272727268 个帧数据。

当然这只是在数字上计算出来而已,但是实际上还有许多看不见的延迟因数。

实验十之一:串口接收模块

关系到串口接收,基本上就是“采样”的操作。当你明白“bps”的意义后,对于串口的接收时非常的简单。

实验十之一是要建立如上图的 rx_module.v 。Rx_module.v 是一个组合模块,主要是包含 detect_module.v , bps_module.v 和 rx_control_module.v,2个功能模块,和1个组合模块。

detect_module.v 的输入是连结无理引脚rx,它主要检测一帧数据的第0位,也就是起始位,然后产生一个高脉冲经 H2L_Sig 给 rx_control_module.v ,以表示一帧数据接收工作已经开始。

rx_bps_module.v 是产生波特率定时的功能模块。换一句话说,它是配置波特率的模块。

当rx_control_module.v 拉高 Count_Sig, bps_module.v 经 BPS_CLK 对 rx_control_module.v 产生定时。

rx_control_module.v 是核心控制模块。对串口的配置主要是1帧11位的数据,重视八位数据位,无视起始位,校验位和结束位。当RX_En_Sig 拉高,这个模块就开始工作,他将采集来自 RX_Pin_In 的数据,当完成一帧数据接收的时候,就会产生一个高脉冲给 RX_Done_Sig。

说道“采集”,它到底是如何实现采集的呢?

首先,你猜猜看,在什么时候数据是最稳定?如上图所示,数据采集都是在“每位数据的中间”进行着。在上图中 RX_Pin_In 输入一帧数据,当 detect_module.v 检测到低电平(起始位),rx_control_module.v 和 rx_bps_module.v 就产生定时(与RX_Pin_In的波特率是一致),然而rx_bps_module.v 产生的定时是在每个位时间的中间。

在第0位数据,采取忽略的态度,然后接下来的8位数据位都被采集,最后校验位和停止位,却是采取了忽略的操作。有一点你必须好好注意,串口传输数据“从最低位开始,到最高位结束”。

上述的内容,主要是 rx_module.v 的内容。对于全双工的串口来说 rx_module.v 必须与 tx_module.v 保持独立关系,所以 RX_En_Sig 和 RX_Done_Sig 都是被必须的。

实验十之一源码:

detect_module.v

嗯!detect_module.v 这个功能模块,估计也非常眼熟了吧,而该功能模块是为了检查电平由高变低。当检测到电平又高变低,在第31行就会输出高脉冲。

rx_bps_module.v

9600 bps 传输速度使一位数据的周期是 0.000104166666666667s 。以20Mhz时钟频率要得到上述的定时需要:

N = 0.000104166666666667 / ( 1 / 20Mhz )

= 2083

如果从零开始算起 2083 - 1 亦即 2082 个计数(20行)。然而,采集数据要求“在周期的中间”,那么结果是 2082 / 2 ,结果等于 1041(29行)。基本上 rx_bps_module.v 只有在 Count_Sig 拉高的时候(22行),模块才会开始计数。

rx_control_module.v

在37~61行,是 rx_control_module.v 的核心控制功能。在37行,表示了如果 RX_En_Sig 如果没有拉高,这个模块是不会工作。(在26行定义了 isCount 标志寄存器,为了使告知 rx_bps_module.v 输出 bps 定时)当 rx_control_module.v 模块被使能,该模块就会处于就绪状态,一旦 detect_module.v 检查到又高变低的电平变化(41行),会使步骤i进入第0位采集,然而 isCount 标志寄存器同时也会被设置为逻辑1, rx_bps_module.v 便会开始产生波特率的定时。

我们知道 rx_bps_module.v 产生的定时是在“每位数据的中间”。在43~44行,第一次的定时采集时第0位数据(起始位),保持忽略态度。在46~47行,定时采集的是八位数据位,每一位数据位会依低位到最高位储存入 rData 寄存器。

49~53行,是最后两位的定时采集(校验位,停留位),同时采取忽略的态度。当进入55~59行,这表示一帧数据的采集工作已经结束。最后会产生一个完成信号的高脉冲,同时间 isCount会被设置为逻辑0,亦即停止rx_bps_module.v 的操作。

至于65~67行, isCount标志寄存器是 Count_Sig 输出的驱动器,rData 寄存器是 RX_Data 输出的驱动器,最后 isDone 标志寄存器是 RX_Done_Sig 输出的寄存器。

rx_module.v

实验十之一说明:

当rx_module.v 组合模块,组织一切以后只留出,输入RX_Pin_In 和RX_En_Sig, 输出RX_Data 和RX_Done_Sig,只要知道 rx_module.v 基本的操作, 那么这个模块就很容易的控制。

实验十之一最难理解的地方就是“定时采集”那部分了,在前面之所以我说大部分的同学对“波特率”有所误解,关系也就是关于这一部分。如果从另一个角度去思考的话,如果一个位所占的时间越短,那么在1秒以内,可以传输的位就越多,传输速率当然就会越高。

在试验中, rx_bps_module.v 的是“配置波特率”,然而 rx_control_module.v 的工作则对 “数据位位宽”“奇偶校验”“停止位位宽”作出配置。但是在普遍的应用上“1位起始位,8位数据位,1位校验位,和1位停止位”已经成为流行。当然 波特率的速度是 9600 bps 还是 115200 bps 这是看距离而定。

说说一点而外的话吧,波特率越低,虽然传输速度慢,但是支持的距离就越长。波特率越高,虽然在短时间内数据的传输量高,但是支持的距离却越短。可以把,距离和波特率看成正反比吧

完成后的扩展图:

实验十之一结论:

“定时采集”的部分,比较难搞明白,但是深思熟虑过后,你会发现这样的写法很有逻辑。

实验十之一演示:

在这一章,如上图 rx_module_demo ,建立了一个控制模块,对rx_module.v 的调用。

一开始 control_module.v 会拉高 RX_En_Sig 使能 rx_module.v 。当有一阵数据经 RX_Pin_In 传入, rx_module.v 就会接收然后将输出输出致 RX_Data , 再产生一个高脉冲给 RX_Done_Sig。当 control_module.v 接收到 RX_Done_Sig 的高脉冲,就会将 RX_Data 的“前四位”输出致 4 位LED资源。

实验十之一演示源码:

control_module.v

在22~27行就是这个控制模块的全部了。 一开始的时候(27行)就将 isEn设置为逻辑1, 这个标志寄存器在32行驱动着 RX_En_Sig 。换句话说,此时的 rx_module.v 已经进入就绪状态,那么同样的 control_module.v 等待着 RX_Done_Sig 的通知(25行)。一旦一帧数据接收完毕, RX_Done_Sig 就会产生高脉冲 , 然后 rData就会被赋予 RX_Data 的值,同时间 isEn被设置为逻辑0(26行)。在下一瞬间, control_module.v 再一次设置 isEn 为逻辑1,做好接收下一组数据的准备。

rx_module_demo.v

这是实验十之一演示的组合模块,没有什么特别的。连线关系基本上和“图形”一样。

在 45行,对输出进行“载减”致前四位。

实验十之一演示说明:

这个演示主要是在“串口调试助手”以 “十六进制”发送,如:输出 0F ,那么四位LED就会亮了起来,输出 0A ,第四位和第二位LED亮了起来。

完成后扩展图:

实验十之一演示结论:

在设计组合模块的时候,细心的要求,要求非常高。此外这个演示,不过是演示 rx_module.v 是如何调用而已。

实验十之二:串口发送模块

经实验十之一之后,基本上掌握“波特率”的概念了吧?在这一章实验“波特率”依然是保持很重要的角色。

上图是 tx_module.v 组合模块。与rx_module.v比较tx_module.v 少了 detect_module.v,因为在串口发送一帧数据的时候,根本不需要到它。

tx_bps_module.v 同样是作为“定时”的功能。当 TX_En_Sig 拉低电平的时候,它是处于随眠的状态。一旦TX_En_Sig 拉高电平,那么 tx_bps_module.v 就开始计数。然后定时产生一个高脉冲经 BPS_CLK 给 tx_control_module.v。

tx_control_module.v 控制模块是最为中心的一部分,当 TX_En_Sig 拉高电平,同时间 tx_bps_module.v 也会开始计数。tx_control_module.v 将 TX_Data 的值,按 tx_bps_module.v 产生的定时,有节奏的往 TX_Pin_Out 发送。当一帧数据发送完毕后,就产生一个 TX_Done_Sig 的高脉冲。

tx_module.v 和 rx_module.v比较不同的是: rx_module.v 是“定时采集”,然而tx_module.v是“定时发送”。假设我配置的波特率为9600 bps,那么每隔 0.000104166666666667 这个模块就会产生一个高脉冲给 tx_control_module.v,该控制模块会按这个节拍将数据一位一位的数据发送出去。

一帧数据有11位,那么 tx_bps_module.v 需要产生12次定时。

实验十之一源码:

tx_bps_module.v

tx_bps_module.v 是配置为 9600 bps 的波特率(20行),基本上和 rx_bps_module.v没有分别。但是有一点值得注意的是,它现在的工作是“定时发送”。然而有一点可能会使读者混乱 ...

我们知道 BPS_CLK 的产生是在“一位数据的中间”,如果这个模式在“定时采集”的作用上很容易理解,但是换做“定时发送”的模式又有什么不同呢?

左图有3个“定时发送”的产生。每个“定时发送”是在计数 12'd1041 发生。读者尝试数数看,两个“定时发送”的之间到底相差了多少个计数?没错,是12'd2082个计数。这下明白怎么一回事了吧!上一个定时的产生与下一个定时产生的之间才是重点,也就是说“一位数据的周期"定义在两个定时的之间。

tx_control_module.v

比起 rx_control_module.v , tx_control_module.v 相对比较简单。当 TX_En_Sig 拉高(32行)该控制模块就会开始工作了(同时间 tx_bps_module.v 也会开始计数)。每当 tx_bps_module.v 产生一个定时的高脉冲 BPS_CLK, tx_control_module.v 都会发送一位数据。

第0位数据是起始位,所以 rTX寄存器 被赋值位逻辑0(36行)。接下来的八位都是数据位,tx_control_module.v 从 TX_Data 按最低位到最高位将数据赋值给 rTX寄存器(39行)。然而最后两位数据都则是校验位和停止位,如果没有什么特别需求,就随便填上逻辑1就行了(41~45行)。最后产生一个 TX_Done_Sig 的高脉冲(47~51行)。

在58行 TX_Done_Sig的输出是被 isDone 这个标志寄存器驱动着,然而 TX_Pin_Out 的输出是由 rTX这个寄存器驱动。

tx_module.v

在组合模块 tx_module.v 中的连线,基本上和“图形”是一模一样。但是有一点比较特别的是在 24行,Count_Sig 输入是由 TX_En_Sig 直接连线。

实验十之二说明:

发送模块 tx_module.v 基本上与 rx_module.v 比较起来建模跟简单,只要好好的掌握“定时采集”和“定时发送”的区别即可。

tx_bps_module.v 依然是扮演着配置“波特率”的角色。tx_control_module.v 的功能是对一字节数据,包装后以一帧的格式发送出去。至于要不要加入“校验位”,设置不同“数据位位宽”,或者设置不同的“停留位位宽”,视目的而定。普通的应用下有1个起始位,8个数据位,1个校验位和1个停止位。如果没有什么特别的要求,校验位可以随便填。

完成后的扩展图:

实验十之二结论:

tx_module.v 可以视为是一个独立的模块,为了更好的调用它,设计的时候留下 TX_En_Sig 和 TX_Done_Sig 对往后的调用非常有用。

实验十之二演示:

上图是实验十之二演示的组合模块。在 tx_module_demo.v 中的 control_module.v 主要是每秒往 tx_module.v 发送 0x31 的数据。

一开始 control_module.v 往 TX_Data 输出数据,然后拉高TX_Done_Sig 使 tx_module.v 开始工作。当 tx_module.v 发往一帧数据以后,就会对 TX_Done_Sig 产生一个高脉冲,以示发送完毕。

实验十之二演示源码:

control_module.v

第17行是1秒的定义常量,在23~29是1秒的定时器。control_module.v 主要是每秒发送 0x31的数据,换句话说就是每秒设置一次 isEn标志寄存器。当isEn被设置后,tx_module.v 就会开始工作,发完一帧数据位 TX_Done_Sig 会产生高脉冲。这使得(42行),control_module.v 会重新赋值 rData寄存器 然后复位 isEn标志寄存器。直到下一秒的到来,isEn标志寄存器会再一次被设置。

tx_module_demo.v

该组合模块比较简单,没有什么特别的。

实验十之二演示说明:

在实验十之二演示中,“串口调试助手”会一直显示 1 ... 如果以“十六进制”显示的话,结果会是 "31",每一次发送的时间间隔是 1秒。

完成的扩展图:

实验十之二演示结论:

这个演示是演示该 tx_module.v 如何调用。

实验十说明:

在前面我也说过了,串口是全双工执行的。在建模的时候,理应考虑“发送”和“接收”是独立的模块,或者说“发送模块”和“接收模块”是并行操作着。

无论是“发送模块”还是“接收模块”,xx_bps_module.v 控制着波特率的配置,然而 xx_control_module.v 控制着一帧数据的配置。

此外,为了要很好的调用该模块,Done_Sig 都扮演着重要的角色。还记的传统的串口实验吗?在单片机上,我们都是使用“中断”或者“查询”的方式来得知各个模块的工作状态。但是实验十的建模涉及刚好是两中方法的中间,或者应该称为“触发”更为贴切。

在过去的实验八我曾说过。在微观上,每个模块都是独立执行着。但是有些模块比较特殊,平常在时钟的上升沿它们都是处于就绪状态。一旦有“触发”的信号,它就会马上工作。在这一章实验 Done_Sig 可以视为一种“完成反馈”的“触发信号”。

实验十结论:

实验十的“低级建模”比较简单,将一个串口模块,分为两个模块,亦即“发送模块”和“接收模块”。然而每个模块又分为“小模块”,每个“小模块”都有自己的功能。

如 xx_bps_module.v 的工作就是产生定时,xx_control_module.v 是针对一帧信息数据格式的配置。最后在组合模块上,留下“xx_En_Sig”和 “xx_Done_Sig”以方便调用。

实际上,实验十已经涉及许多有关“仿顺序操作”的基本知识了(以往的实验同样也涉及一点),因为“仿顺序操作”的“建模构造”会有标志性的 Start_Sig 和 Done_Sig。

关于更多信息,下一章就会开始讲解了。

总结:

嗯!当读者看到这里,你已经掌握最基本的“低级建模”的建模思路了。这一章笔记,其中最为重要的就是“图形”和“准则”。因为“低级建模”存在的缺点,“图形”和“准则”都可以很有效的去克服,其外还产生“意想不到的效果”。这个“意想不到的效果”到底是什么?笔者就不说什么了,因为那种感觉真的不是文字可以表达得到 ...... 笔者估计读者也渐渐感受了其中“不可思议”的感觉吧?

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
6
关闭 站长推荐上一条 /3 下一条