文章目录
信息数据被逐位按顺序传送的通讯方式称为串行通信。串行接口(Serial Interface),简称串口,即是采用串行通信方式的扩展接口。其采用一位一位的方式顺序的传送数据,又可称串行通信接口或串行通讯接口(通常指 COM 接口)。串行接口的特点是通信线路简单,只要一对传输线就可以实现双向通信,并且可以直接利用电话线作为传输线,从而大大降低了成本,因此非常适用于远距离通信,但传送的速度较慢。
1980 年前后串口首次出现,其数据传输率是 115kbps~230kbps。初期,串口是为了实现计算机外设的连接,一般用来连接鼠标和外置 Modem 以及老式摄像头和写字板等设备,也可以应用于两台计算机(或设备)之间的互联及数据传输。目前,串口多用于工控和测量设备以及部分通信设备中。
根据通信方式的不同,串口可分成同步串行接口(Synchronous Serial Interface,SSI)和异步串行接口(Universal Asynchronous Receiver/Transmitter,UART)。SSI 常用于工业通信,UART通用于异步接收/发送。按照电气标准及协议的不同,串口包括 RS-232-C、RS-422、RS485 等。其中,RS-232-C、RS-422 与 RS-485 标准只对接口的电气特性做出规定,不涉及接插件、电缆或协议。
下面将具体介绍一下这些常用的串口。
RS-232:RS-232 是 1970 年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准,其全称为“数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准”,又被称为标准串口。RS-232 是最常用的一种串行通讯接口,采用标准 25 芯 D 型插头座(DB25),后简化为 9 芯 D 型插座(DB9),现 25 芯插头座在应用中已很少采用。RS-232 采取不平衡的传输方式,即所谓的单端通讯。由于其发送电平与接收电平仅差 2V 至 3V 左右,所以其共模抑制能力差,再加上双绞线上的分布电容,其传送距离最大为约 15 米,最高速率为 20kb/s。RS-232 是为点对点(即只用一对收、发设备)通讯而设计的,其驱动器负载为 3~7kΩ,因此其适合本地设备之间的通信。
RS-422:RS-422 标准的全称是“平衡电压数字接口电路的电气特性”,其对接口电路的特性进行了定义,采用 DB9 连接器。典型的RS-422 是四线接口,实际上还存在一根信号地线,共为 5根线。由于接收器采用高输入阻抗和发送驱动器,RS-422 比 RS232 的驱动能力更强,因此其可在相同传输线上连接多个接收节点(最多可接 10 个)。RS-422 支持点对多的双向通信,主设备(Master)一台,其余为从设备(Slave),且从设备之间不能通信。接收器的输入阻抗为 4k,故最大负载能力是 10×4k+100Ω(终接电阻)。由于采用单独的发送和接收通道,RS-422 四线接口不必控制数据方向,各装置之间的信号交换均可以通过软件方式(XON/XOFF 握手)或硬件方式(一对单独的双绞线)实现。RS-422 的最大传输距离为 1219 米,最大传输速率为 10Mb/s。其平衡双绞线的长度与传输速率成反比,只有在很短的距离下才能获得最高速率传输,一般 100 米长的双绞线上所能获得的最大传输速率仅为 1Mb/s,在 100kb/s 速率以下才可能使用规定最长的电缆长度。
RS-485:RS-485 从 RS-422 基础上发展而来,因此其许多电气规定与 RS-422 相仿,如都采用平衡传输方式、都需要在传输线上接终接电阻等。与 RS-422 相同,其最大传输距离约为 1219 米,最大传输速率为 10Mb/s,平衡双绞线的长度与传输速率成反比,只有在很短的距离下才能获得最高速率传输。RS-485 可以采用二线与四线两种连接方式:二线制可实现真正的多点双向通信,而四线制与 RS-422 一样只能实现点对多的通信,即只能有一台主(Master)设备,其余为从设备。但无论四线还是二线连接方式总线上可多接到 32 个设备,与 RS-422 相比有一定的提升。此外,RS-485与 RS-422 的共模输出电压不同,RS-485 的输出范围是-7V 至+12V,而 RS-422 是-7V至+7V。RS- 485 接收器最小输入阻抗为 12kΩ,而 RS-422 是 4kΩ。可以看出 RS-485 满足 RS-422 的所有规范,因此其可以在 RS-422 网络中应用。
CH340:由于串口(COM)不支持热插拔及传输速率较低,目前大部分新主板和便携电脑已开始取消 CH340 接口,只有工控和测量设备以及部分通信设备中还保留有串口。因此,为了使用该串口,需要使用 USB 转串口的芯片来使电脑把 USB 当串口来使用。这种类型的芯片很多,本书使用的是 CH340 芯片。CH320 是一个 USB 总线的转接芯片,用来实现 USB 转串口、USB 转 IrDA 红外或者 USB 转打印口等功能。在串口方式下,CH340 可以为计算机扩展异步串口提供常用的 MODE 联络信号,或将普通的串口设备直接升级到 USB 总线。本书的串口功能原理如下图所示,通过 USB 线将电脑与教学板上的 USB 接口相连,USB 接口的另一端与 CH340 芯片连接,CH340 芯片与 FPGA 相连。从 FPGA 的角度来看,串口其实就是两根线:输入线 USB_RXD 和输出线 USB_TXD,通过 USB_RXD 接收来自电脑过来的串口数据,通过 USB_TXD 将数据传送给电脑。其他电气特性、电平转换的工作,都可以通过 CH340 完成。

进行传输数据时,USB_RXD 和 USB_TXD 将数据字符按位传输,其串口时序如下图所示。USB_RXD 的时序由 CH340 芯片产生,FPGA 依此来接收数据。反过来,FPGA 芯片按规范产生 USB_ TXD 的时序,使得 CH340 可以正确地接收。其中,产生时序的为MASTER(主),接收数据为 SLAVE(从)。

串口时序主要包括:空闲、起始位、数据拉、校验位和停止位。下面逐一解释每个时序位的状态。
空闲:空闲状态下,数据线一直处于高电平状态。
起始位:当 MASTER 准备发送数据时,会先将数据线拉低“一段时间”,以此来告知 SLAVE做好数据传输的准备。
数据位:起始位之后是数据位,其位数由主从双方共同约定,支持 4、5、6、7、8 位等,完成约定后才能正确地传输。传输顺序是从低位开始,每个数据位传输时都会占用“一段时间”。从图 3. 4-2 中可以看出,传输从 LSB 开始,至 MSB 结束,LSB 即表示低位,MSB 表示高位。以数据 8’b00000001 为例,传输该数据时最先传送的即是最低位的“1”。
检验位:顾名思义,校验位是用于数据校验。目前简单常用的数据校验方式是奇偶校验,分为奇校验和偶校验。奇校验需要保证传输数据总共有奇数个逻辑高电平,偶校验则需保证传输数据有偶数个逻辑高电平。即“奇偶”指的是数据中(包括该校验位)1 的个数。例如:传输的数据是 0100_0011。如果校验方式是奇校验则校验位是 0,若是偶校验则校验位是 1。传输中校验位不是必须项,双方可以约定不需要校验位,或者使用奇/偶校验方式。
停止位:字符帧的最后一位是停止位。由于每台设备都有其自身时钟,在通信中两台设备间可能出现了小小的不同步,从而影响了数据传输结果。因此,MASTER 必须保证有停止位,即数据线需拉高“一段时间”。停止位不仅仅是表示传输的结束,也是一个校正时钟同步的机会,让 SLAVE 可以正确地识别下一轮数据的起始位。假如没有停止位,若校验码刚好是 0,数据连续发送时 SLAVE无法判断下一轮的起始位。对于 SLAVE 来说,数据位或校验位接收后就已经完成接收工作,在停止位无需任何操作,只需等待下一轮起始即可。
上文中提到每个数据都会传输“一段时间”,这段时间并不是随便定义的,需要传输双方做好约定,否则就不能正确地进行通信。那么“一段时间”究竟是指多长时间呢?这与波特率有关。波特率在串口通信中是一个非常重要的概念,其为模拟信号线路的速率。在串口通信中常用的波特率是 9600、19200、38400、57600、115200,代表每个码元传输的速率。在二进制数据传输中,波特率和比特率相同都为每个比特数据传输的速率,其倒数为 1bit 数据的位宽,也就是 1bit 数据持续的时间。确定了这一时间,就可用 FPGA 构造计数器实现比特周期的延时,从而实现特定波特率的数据传输。
举个例子,假设波特率为 9600,数据位为 8 位,无校验位。电脑要传送数据 8’b00110001 给 FPGA。由于波特率为 9600,每位占用时间为 1s/9600=104166ns。那么 FPGA 的
USB_RXD(图中的uart_rxd)将如下图所示进行变化。

在使用开发板的串口前,需要安装 CH340 的驱动程序(下载链接:点击),下载后直接解压安装就可以进行使用。
使用 USB 线将电脑和开发板进行连接后将开发板上电,打开电脑的“设备管理器”。当出现如下图所示的界面时表示驱动程序安装成功并且已经被电脑正确地识别。

在“设备管理器”中可以查看串口号,点击“USB Serial Port(COM3)”,如下图所示。

随后点击“端口设置”选择“高级”选项,如下图所示。

下图中可见端口号的具体设置信息,其串口号为 COM3。

点击 CON 端口号,可以对串口号进行修改,如下图所示。

电脑发送数据给 FPGA 这一操作中需要使用串口调试助手(下载链接:点击),其界面如下图所示,下面对其具体参数设置进行说明。

串口:选择串口号,支持串口号 1~4。如果连接的串口号不在此范围,则需要在设备管理器中修改串口号,详细参见前文描述。
波特率:选择串口的波特率,支持 9600、19200、38400、57600、115200。该选项决定了每一位码元占用的时间。
校验位:可选择“没有检验位”、“奇校验”和“偶校验”。
数据位:设置数据位的位数,可选择 4~8 位的数据拉。
停止位:设置停止位的时间长度。可选择 1 位、1.5 位和 2 位。
打开/关闭串口:打开软件时,该串口默认是关闭状态。这里要注意有关串口使用的方法:一定要先设置好参数后,才能打开串口;反之,一定要先关闭串口后,才能关掉教学板电源和拔掉 USB线。
十六进制显示:本软件支持ASCII显示和十六进制显示。若勾选了十六进制显示,软件就会显示十六进制。例如 FPGA 发送数据8’b00110001,软件收到后会显示为十六进制,即 31。反之如果不勾选十六进制显示,则会显示 ASCII 码。如果 FPGA 依然发送8’b00110001,对照下图的ASCII表可看出其所对应的图形为“1”,因此软件会显示“1”;假设 FPGA 发送的是8’h00100011,下表中其所对应的是图形是“#”,因此软件会显示“#”。

十六进制发送:同样的,本软件支持 ASCII 发送和十六进制发送。若勾选十六进制发送,软件则会以十六进制形式发送。例如填写“31”后手动发送,那么 FPGA 收到的值为 8’b00110001。如果不勾选十六进制发送,则软件以 ASCII 码形式发送。若同样填写“31”后手动发送,软件将首先发送 ASCII 码“3”所对应的十六进制值 8’h33,再发送 ASCII 码“1”所对应的十六进制值 8’h31,即 FPGA 将收到两个字节数据:8’h33 和 8’h31。
了解了串口通信的原理后,本书将完成串口通信的设计。按照至简设计法的思路,进行设计之前首先应明确设计目标,后续设计中每一个步骤都是围绕着设计目标的实现来针对性的展开。如果没有明确设计目标就开始操作实践,最终的作品也只是东拼西凑的产物。在这种状态下的工程调试过程如果出现了问题,则需要花费大量的精力进行寻找修复。因此建议初学者在最开始学习时养成良好的工作习惯,在后续工作中会受益无穷。
本设计中串口调试助手的参数设置为:波特率 9600;数据位为 8 位;无奇偶校验位;停止位为2 比特;接收和发送都是 16 进制数。
本设计中,用户通过串口调试助手发送一个 8 位的数据 data,其可以控制开发板上的 8 个 LED灯,data[0]~data[7]分别控制LED0~LED7灯。当数据位为 0 时,对应的 LED 灯点亮,数据为 1 时,对应的 LED 灯熄灭。例如,用户发送数据 data=8’b10000000 后,开发板上的 LED0、LED1、LED2、LED3、LED4、LED5、LED6 保持为亮,LED7 保持为灭。这里要注意,信号的传输顺序是从低到高的,数据的最后一位“0”对应 LED0,倒数第二位“0”对应 LED1,倒数第三位“0”对应 LED2,依次类推,第一位“1”对应 LED7。
设计完成后的上板效果如下图所示。

确定设计目标后,下面会逐步分析讲解工程的实现步骤。本书不仅分享案例,还会在操作过程中剖析设计理念及原理,同时也分享一些至简设计法的设计技巧以锻炼独立设计工程的能力。因此,建议初学者认真学习每一步。当然,在已经拥有扎实的功底、只是想要根据步骤完成设计的情况下可以跳过此部分,直接进入后续章节中的简略版操作步骤。
新建目录:
D:\mdy_book\mdyBookUart,并在此目录中新建一个名为mdyBookUart.v的文件。并用GVIM打开该文件后开始编写代码。在这里再次强调,初学者一定要按照本书提供的文件路径以及文件名进行设置,避免后面出现未知错误。根据设计目标可知,用户通过串口调试助手向 FPGA 发送数据,即 CH340 控制 RX 信号让其根据串口时序变化,从而将数据信号传送至 FPGA。因此 FPGA 工程必须有一个接口信号,本书将其命名为 uart_rxd。
本设计中需要控制 8 个 LED 灯的亮灭,按照之前章节中信号与灯的对应方法,需要 8 个信号来
进行控制:led0,led1,led2,led3,led4,led5,led6,led7。
然而本设计是采用串口指令控制小灯,且 8 个信号数目过于繁多。因此,可以选择使用一个 8 比特的信号,将其命名为 led。既然如此,那之间几个章节中的工程可以用这种方法吗?答案是肯定的,可以将led0,led1,led2,led3四个信号转换为一个 4 比特的信号 led。虽然设计内容与需求不同,但是至简设计法一定会分享最合适最简单的方法。
硬件电路的连接关系如下表所示。本设计中采用 8 比特的信号 led 来控制 8 个 LED灯,工程的时钟管脚为 G1,对应 FPGA 工程信号为
clk;复位管脚为AB12,对应FPGA工程信号为rst_n;串口输入管脚为D6,对应FPGA工程信号为uart_rxd。因此本工程共需要 4 个信号:8 位信号led,时钟clk,复位rst_n和串口输入信号uart_rxd。

将
module的名称定义为mdyBookUart,在顶层信号代码中将与外部相连接的输入/输出信号列出,从而实现信号与管脚的连接。已知该模块有四个信号:clk、rst_n、uart_rxd和led,其具体代码如下:

随后对信号的输入输出属性进行声明,指出对于
FPGA来说这一信号属于输入还是输出,若为输入,声明则为input;若为输出,声明则为output。在本设计中由于clk是外部的晶振输送给FPGA的,因此在FPGA中clk为输入信号input;同样地,rst_n是外部按键FPGA的,在FPGA中也是输入信号input;同时可知led是FPGA输出给LED灯的,是输出信号output。clk、rst_n、uart_rxd三个信号的值都为 0 或 1,用一根线表示即可,而 led 信号位宽为 8。根据信号属性将输入输出端口定义补充完整,其代码如下:

由前文可知,
led信号控制了 8 个LED灯的亮灭,然而其亮灭控制还是取决于串口发送数据。那么就存在这样一个问题:串口发送的数据是如何告知FPGA并与led对应起来呢?下面本书就来分析一下串口数据的时序,CH340控制信号uart_rxd的时序如下图所示。

从时序图中可以看出:发送 8 位数据
data前,信号uart_rxd会先变为 0 并持续一段时间(起启位),然后发送data[0]、data[1],以此类推直至发送完data[7],发送每位数据时都会持续一段时间,发送完毕后uart_rxd会变为 1 并持续一段时间(结束位)。至此,CH340 完成了数据的发送。可以看出每段有效信号的开始前和结束后,都会有特殊信号:有效数据开始前会有一段变 0 的信号,用以
告知 FPGA 开始传送数据;结束后会有一段变 1 的信号,告知 FPGA 此数据传送结束。
例如,CH340 要发送的数据为data=8’h00110001,则 uart_rxd 的波形如下。

由于波特率设置为 9600,考虑时间信息,每位持续的时间是
1s/9600=104166ns。补充时间信息后的时序如下图所示。

本开发板的晶振时钟是 50Mz,对应时间周期为 20ns。由此可知每位数据持续时间为 104166ns/20ns =5208.3 个时钟周期,近似为 5208 个时钟周期。由于 5208 只是估计的大概数字,实际情况会产生一定的偏差。在实现过程中,应对数据位数进行计数,以此来判断开始位、数据位和停止位等。

通过上文分析可知:本设计一共需要 2 个计数器,1 个计数器用于计算 1 比特的位宽长度即 5208 个时钟周期,将其命名为 cnt0;另一个用于计算有多少个比特,将其命名为 cnt1。首先讨论 cnt0 的实现,至简设计法中计数器的设计只考虑两个因素:加 1 条件和计数数量。由于 1 比特的位宽长度是 5208 个时钟周期,因此 cnt0 的计数数量是 5208。
确定了计数数量后来分析一下 cnt0 的加 1 条件。为了更好理解加 1 条件的概念,这里以停车位来进行比喻。一般情况下对每个停车位置会进行对应编号,但是如果某个位置上放置了一块石头无法作为停车位时,该位置就不能获得对应的编号。反之则可以认为停车位编号的加 1 条件就是:对应位置上没有石头,其可以继续的进行编号,即 assign add_cnt0 = “没有石头”。因此如果在设计中计数器一直没有阻碍地进行计数工作,就可以认为加 1 条件是一直有效的。根据设计目标,可以确定 cnt0 的加 1 区域为其一直工作的区域,如下图灰色区域所示。

虽然确定了 cnt0 的加 1 区域,但现有设计中没有任何一个信号可以单独表示出这一区域。在这种情况下,进行对信号进行补充。因此本设计添加一个“
flag_add”信号,该信号为 1 时表示上述灰色区域,即 cnt0 的加 1 区域。这样就可以明确计数器 cnt0 的加 1 条件为“flag_add==1“,

确定好 cnt0 的加 1 条件和计数数量后,就开始进行代码编写,以往都是一行行的输入相应代码。但是至简设计法有一个小技巧,可以节省代码编写时间的同时在一定程度上降低了代码的出错率。至简设计法将日常代码中常用到的固定部分制作成模板,进行代码编程时可以调用相应模板后根据逻辑输入对应设计的变量将代码补充完整。这里就可以用模板编写计数器代码,感受一下这个炫酷的功能。
打开 GVIM 工具,在命令模式下输入“:Mdyjsq”后点击回车,调出对应模板如下图所示。

通过上文分析可知,cnt0 的加 1 条件是“flag_add==1”,计数数量为 5208。将其填入到模板
中,可以得到完整正确的计数器 cnt0 代码如下:

下面来设计计数器
cnt1,该计数器用于表示数到第几比特,每个比特结束,即“end_cnt0”时,cnt1就会加 1。因此cnt1的加 1 条件是:end_cnt0。通过设计目标中可知cnt1的计数数量为 9。打开GVIM,继续调用模板,在命令模式下输入“:Mdyjsq”,点击回车。将“add_cnt1”和“end_cnt1”补充完整,得到cnt1的代码为:

在设计
cnt0时,考虑到其加 1 条件增加了辅助信号flag_add,下面就来思考如何设计这一信号。通过分析可知flag_add具有两个变化点:变 0 和变 1,这两种变化可以从功能上进行理解。工程通电后,如果 PC 端没有发送任何数据,则uart_rxd始终保持为 1,cnt0和cnt1无须计数,flag_add信号也一直保持为 0。当 PC 端要发送数据时,uart_rxd就会按串口时序产生变化,首先会发送一个开始位,即uart_rxd由 1 变成 0。FPGA 接收到这一信号后,就明白 PC 要开始传送数据,此时 cnt0和 cnt1 要计数了,即flag_add要变为 1。
可以通过下图来辅助分析,可以看到当
uart_rxd为 1 时,flag_add为 0,此时 PC 没有发来任何数据,cnt0和cnt1不计数。当uart_rxd数第 1 比特,即开始位时,即通知FPGA准备好接收数据,这时uart_rxd变 0 时,flag_add变 1,cnt0和cnt1开始计数。

从上图可以很容易看出:当
uart_rxd由 1 变 0 时,flag_add就由 0 变成 1。其中,uart_rxd信号的由 1 变 0 波动被称为下降沿,在图中可以清晰的看出此时波形是下降的。那么又如何得知下降沿的到来呢?这里就需要引入一个边沿检测电路工程来辅助设计。
检测
uart_rxd的下降沿需要用到 FPGA 中的边沿检测技术。所谓边沿检测,就是检测输入信号或 FPGA 内部逻辑信号的跳变,即检测上升沿或下降沿。这一技术这在 FPGA 电路设计中被广泛应用,其电路图及各信号定义如下所示。


可以看出中间信号
trigger与触发器的信号输入端 D 连接,将trigger信号取反后与触发器的输出tri_ff0相与得到信号neg_edge。如果neg_edge=1就表示检测到trigger的下降沿。将触发器的输出tri_ff0取反与trigger相与后得到信号pos_edge,如果pos_edge=1则表示检测到trigger的上升沿。
利用这一原理可以画出信号的波形图如下图所示。

tri_ff0是触发器的输出,因此tri_ff0的信号与trigger信号只是相差了一个时钟周期。这里也可以这样理解:每个时钟上升沿看到的tri_ff0值实际上是上一个时钟看到的trigger信号值,即tri_ff0的值是trigger在上一时刻的值。
以记录的体重值为例来帮助理解,假设第一天的体重是 49kg,第二天是 50kg,第三天是 50kg,第四天为 49kg,可以看到体重值在发生变化。此时
tigger信号为当天记录的体重值,而tri_ff0信号为前一天的体重值,即第二天的tigger值为当天体重 50kg,tri_ff0值为前一天体重 49kg,第三天的tigger值为当天体重 50kg,tri_ff0值为前一天体重 50kg,以此类推。在生活中将当天的体重和前一天进行对比就可以得知体重的变化,通过两个信号的对比也能得知信号的变化。比如第二天的tigger值 50kg,此时tri_ff0为 49kg,50 大于 49,可以看到体重上升,对应信号变化即可视作迎来了一次上升沿。综上所述,uart_rxd的上升沿/下降沿检测依据为这一刻的状态与上一刻的状态有 0 到 1 者 1 到 0 的变化。
从图 3.4- 22 可以看出,第 3 个时钟的上升沿处
trigger值为 0,而tri_ff0值为 1,即trigger的值发生了从 1 到 0 的变化,即迎来下降沿,此时neg_edge为 1。反之,当neg_edge的值为 1,就表示检测到了trigger的下降沿。同理,在第 7 个时钟的上升沿处trigger值为 1,而tri_ff0值为 0,此时pos_edge的值为 1,表示检测到了trigger的上升沿。
综上所述,可以得出Verilog实现边沿检测电路的代码如下:

在边沿检测波形的讨论中将
trigger信号视为理想的同步信号,即trigger满足 D 触发器的建立和保持时间,这种情况下在同步系统中实现边沿检测不是问题。但如果trigger信号不是理想的同步信号(如外部按键信号或是本工程的uart_rxd信号)时,信号的变化由外部传输指令给 FPGA,对于 FPGA 来说这些信号什么时候产生变化是完全随机的。因此,很可能出现信号在时钟上升沿发生变化,无法满足触发器的建立时间和保持时间要求,从而出现亚稳态,导致系统崩溃。详细原因可以参看至简设计法 D 触发器中亚稳态一节的相关内容。根据这一结论,本设计需要对输入的信号延迟两拍即先用两个触发器寄存后再进行使用。如下图所示,用 2 个触发器对信号进行寄存,确定了信号的稳定性,然后再进行边沿检测,满足了同步系统中实现边沿检测的需求。

因此,在边沿检测代码设计前需要先对触发器进行设计。假设输入的信号
trigger不是同步信号,则该信号需要用 2 个触发器进行寄存,得到信号tri_ff0和tri_ff1。需要特别注意的是,在第一个触发器阶段,信号依旧存在亚稳态的情况,因此tri_ff0绝对不可以作为条件使用,只能以信号tri_ff1作为条件。得到同步信号后用寄存器寄存,得到信号tri_ff2,根据tri_ff1和tri_ff2就可以得到边沿检测结
果。即当tri_ff1==1且tri_ff2==0时,上升沿的 pos_edge 有效;当tri_ff1==0且tri_ff2==1时,下降沿的neg_edge有效,其具体代码如下:

综上所述,如果输入信号是异步信号,需要先对其进行同步化之后再做检测,即通过延迟两拍的方式实现信号的同步化,再通过延迟一拍的方式实现边沿检测电路。反之,如果输入信号本身就是同步信号,则没有必要进行同步化了,可以直接对其进行边沿检测。
回到本设计中,此时需要检测的是
uart_rxd的下降沿,并以此为条件拉高信号flag_add。由于PC 端随机地给 FPGA 发送数据,其发送时刻是不确定的,因此本设计中uart_rxd是异步信号。根据边缘检测的设计方法,此时需先将 uart_rxd 进行同步化后再对其进行下降沿检测。其具体设计代码如下:

如此一来,
flag_add变 1 的条件就变成:uart_rxd_ff10 && uart_rxd_ff21。确定flag_add变 1 条件后,再来讨论一下其变 0 的条件。当完成 9 比特数据的传输后,flag_add的值变为 0,不再计数。因此,其变 0 条件为end_cnt1。根据以上分析可以得到flag_add的代码
如下。

下面来设计
data信号,根据串口时序可知,该信号的值来自下图中第 2~第 9 比特的值。其中第 2 比特的值赋给data[0],第 3 比特的值赋给data[1],以此类推,第 9 比特的值赋给data[7]。

由于每一个比特都会持续 5208 个时钟周期,因此必须选定一个具体时刻来将值赋给 data。此处可能会有这样的疑虑:直接在每个比特计数结束的时刻,即在
end_cnt0的时刻赋值不就可以了么?
然而实际上,这一时刻并不可取。end_cnt0的时刻在下图中用点表示出来,此时的 5208 个时钟周期是理想、估算的数值,而在实际计算中很有可能出现偏差,如果在end_cnt0的时候取值,就有可能会出现错误。

因此,最保险的做法是在中间点取值,如下图所示。在这种取值方法下,即使有较多的偏差,也不会影响到采样的正确性。

综上所述,本设计在
cnt0计数到一半时将采到的当前uart_rxd值赋给led,其中第 2 比特赋给led[0],第 3 比特赋给led[1],以此类推,第 9 比特赋给led[7]。将其用代码的形式表现出来则为:当add_cnt0 && cnt0==5208/2 -1时,如果cnt1==1,则将uart_rxd_ff1赋给led[0]。如果cnt1==2,则将uart_rxd赋给led[1],以此类推,如果cnt1==8,将uart_rxd_ff1赋给 led[7]。其具体代码如下所示。

对以上代码进行优化,可简写为:

在进行设计时,通常是首先想到实现功能,所以会先写出第一段的优化前代码。在功能实现的前提下,再考虑有没有优化空间,从而对其进行优化得到第二段代码。设计过程中,好的代码也是这样一步步优化出来的。至此,主体程序已经完成。
下面需要将 module 补充完整,首先来定义信号类型。reg 和 wire 的判断很容易搞不清楚总会有其余的联想,比如认为 reg 就是寄存器,wire 是线;或者认为 reg 的会综合成寄存器,wire 不会综合成寄存器。但是这些其实和 reg 型还是 wire 型都是没有关系的,因此在信号类型判断时不需要做任何的联想,只要记住一个规则“用 always 实现的是 reg 型,其他都是 wire 型”就可以了。
cnt0 是用 always 产生的信号,因此类型为 reg。根据前文分析可知,该计数器计数的最大值为5208,因此需要用 13 根线表示,即位宽是 13 位。
关于信号位宽的获取,至简设计法在此分享一个非常实用的技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入,就会获得对应的信号位宽,如下图所示,将计数器的最大值 5208 输入,可以看出其位宽为 13。

综上所述,cnt0 的信号定义代码如下:

cnt1 也是用 always 产生的信号,因此类型为 reg。cnt1 计数的最大值为 9,需要用 4 根线表示,即位宽是 4 位。编辑模式下输入“Reg4”调用至简设计法模板,补充完整后得到代码表示如下:

add_cnt0 和 end_cnt0 都是用 assign 方式设计的,因此类型为 wire。其值是 0 或者 1,用 1 根线表示即可。编辑模式下输入“Wire1”调用模板,得到代码表示如下:

add_cnt1 和 end_cnt1 也是用 assign 方式设计的,因此类型为 wire。其值是 0 或者 1,用 1 根线表示即可,其代码如下:

flag_add 是用 always 方式设计的,因此类型为 reg。其值是 0 或者 1,用 1 根线表示即可。编辑模式下输入“Reg1”调用模板,得到代码如下:

uart_rxd_ff0、uart_rxd_ff1和uart_rxd_ff2也都是用 always 方式设计的,因此类型为 reg。并且其值是 0 或 1,需要 1 根线表示即可。编辑模式下输入“Reg1”调用模板,得到代码表示如下:

至此,整个代码的设计工作已经完成,完整的工程代码如下:



打开软件 Quartus Ⅱ,点击“File”下拉列表中的 New Project Wzard…新建工程选项,如下图所示。

然后会出现 Quartus 新建工程介绍,如下图所示,直接点击“Next”。

此时会出现的是工程文件夹、工程名、顶层模块名设置界面,如图 3.4-30 所示。设置目录为:
D:/mdy_book/mdyBookUart,工程名和顶层名为mdyBookUart。这里再次强调,为了避免初学者使用过程中出现报错情况,强烈建议按照本书的工程名和文件名进行设置,设置完成后点击“Next”。注:由于版本持续优化,图中 uart 可能为 mdyBookUart

新建工程类型设置选择“Empty project”,如下图所示,然后点击“Next”。

文件添加界面如图 3.4- 32 所示,点击右侧的“Add”按钮,选择之前写好的
“mdyBookUart.v”文件,可以看到界面下方会显示出文件,随后点击“Next”。注:由于版本持续优化,图中uart.v可能为mdyBookUart.v

芯片型号选择界面如图 3.4- 33 所示,选择“
Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”,之后点击“Next”。

图 3.4- 34 为
QUARTUS设置工具界面,不必做任何修改,直接点击“Next”即可。

下图可以看到新建工程的汇总情况,点击“
Finish”,完成新建工程。

新建工程步骤完成后,就会出现如下所示的
QUARTUS界面。

点击编译按钮,可以对整个工程进行编译。编译成功的界面,如图 3.4- 37 所示。

下面需要对相应管脚进行配置。如下图所示,在菜单栏中选中“
Assignments”,然后选择“Pin Planner”,就会弹出配置管脚的窗口。

在配置窗口最下方中的“
location”一列,参考表 3.2-2 信号和管脚关系,按照表 3.4- 1 中最右两列配置好 FPGA 管脚。配置管理来源参见管脚配置环节,配置结果如图 3.4- 39 所示。配置完成后,关闭“Pin Planner”,软件自动会保存管脚配置信息。



再次打开“
QUARTUS”软件,在菜单栏中选中“Processing”,然后选择“Start Compilation”,再次对整个工程进行编译和综合,如下图所示。

当出现图 3.1-70
QUARTUS编译成功标志时,说明编译综合成功。

完成编译后开始进行上板调试操作,按照下图的方式,将下载器接入电脑 USB 接口,接上开发板电源后按下开发板下方蓝色开关,硬件连接完毕。

打开
QUARTUS界面,单击界面中的 ,弹出配置界面。点击“add file”添加“.sof”文件,点击“Start”,会在“Progress”出现显示进度。

当进度条到 100%时提示成功,即表示上板结束。

开发板连接完成后按下电源键,随后打开电脑的设备管理器,确认串口的端口号。从下图可以看出,当前串口的端口号为 COM3。

前文提供了串口调试工具下载地址,安装完成后,打开串口调试助手,其界面如下图所示。在操作界面进行设置:在串口选项中选择端口号,这里要注意端口号的选择需要与设备管理器中端口号显示一致,即为“COM3”;波特率选项选择“9600”;校验位选项选择“无校验位”;数据位选项选择“8”;停止位选项选择“2”;注意一定要勾选“十六进制显示”和“十六进制发送”两个选项:

设置好串口助手后,就可以观察发送数据的现象。在发送数据栏输入相应的数据(将 8 个 LED灯对应的 8 位二进制数转化为十六进制),然后点击手动发送,即可在开发板上观察到相应的现象。可以尝试发送不同的指令,看是否可以得到相应的 LED 灯变化效果。特别要注意的是,指令的顺序应与小灯亮的顺序相同,如果两者不同,则需检查指令顺序是否发生输入错误。
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195
我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正
我一直在尝试使用nanoc用于生成静态网站。我需要组织一个复杂的排列页面,我想让我的内容保持干燥。包含或合并的概念在nanoc系统中如何运作?我已阅读文档,但似乎找不到我想要的内容。例如:我如何获取两个部分内容项并将它们合并到一个新的内容项中。在staticmatic您可以在您的页面中执行以下操作。=partial('partials/shared/navigation')类似的约定在nanoc中如何运作? 最佳答案 这里是nanoc的作者。在nanoc中,部分是布局。因此,您可以拥有layouts/partials/shared/