马上注册,看完整文章,学更多FPGA知识。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
温馨提示:明德扬2023推出了全新课程——逻辑设计基本功修炼课,降低学习FPGA门槛的同时,增加了学习的趣味性,并组织了考试赢积分活动
本案例的编号为:002700000108,如果有疑问,请按编号在下面贴子查找答案:MDY案例交流【汇总贴】_FPGA-明德扬科教 (mdy-edu.com)
本文为明德扬原创及录用文章,转载请注明出处
大家好,近期我们会连载《FPGA至简设计原理与应用》一书,有兴趣的同学可以学习,也希望大家可以对我们的书提出宝贵的意见和建议。
《FPGA至简设计原理与应用》书籍连载索引目录
http://www.fpgabbs.cn/forum.php?mod=viewthread&tid=989
读过的朋友可积极在贴后留言,书籍正式出版时,我们会从留言者中挑选20位幸运读者,幸运读者可获潘老师亲笔签名书籍一本。
注:手机浏览可能格式会乱,建议用电脑端进行浏览。
第三篇 FPGA至简设计项目实践
第四章 串口通信
本文档编号:002700000016
需要看对应的视频,请点击视频编号:000900000038
1.至简原理与应用配套的案例
2.实现通过串口调试助手发送一个8位的数据data,其可以控制开发板上的8个LED灯,data[0]~data[7]分别控制LED0~LED7灯。当数据位为0时,对应的LED灯点亮,数据为1时,对应的LED灯熄灭。
3. ALTERA入门学习案例文档
第1节 项目背景
信息数据被逐位按顺序传送的通讯方式称为串行通信。串行接口(SerialInterface),简称串口,即是采用串行通信方式的扩展接口。其采用一位一位的方式顺序的传送数据,又可称串行通信接口或串行通讯接口(通常指COM接口)。串行接口的特点是通信线路简单,只要一对传输线就可以实现双向通信,并且可以直接利用电话线作为传输线,从而大大降低了成本,因此非常适用于远距离通信,但传送的速度较慢。
1980年前后串口首次出现,其数据传输率是115kbps~230kbps。初期,串口是为了实现计算机外设的连接,一般用来连接鼠标和外置Modem以及老式摄像头和写字板等设备,也可以应用于两台计算机(或设备)之间的互联及数据传输。目前,串口多用于工控和测量设备以及部分通信设备中。
根据通信方式的不同,串口可分成同步串行接口(SynchronousSerial Interface,SSI)和异步串行接口(UniversalAsynchronous 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完成。
图3.4-1教学板串口实现架构图
进行传输数据时,USB_RXD和USB_TXD将数据字符按位传输,其串口时序如下图所示。USB_RXD的时序由CH340芯片产生,FPGA依此来接收数据。反过来,FPGA芯片按规范产生USB_TXD的时序,使得CH340可以正确地接收。其中,产生时序的为MASTER(主),接收数据为SLAVE(从)。
图3.4-2串口时序图
串口时序主要包括:空闲、起始位、数据拉、校验位和停止位。下面逐一朋友解释每个时序位的状态。
空闲:空闲状态下,数据线一直处于高电平状态。
起始位:当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(图中的rx_uart)将如下图所示进行变化。
图3.4-3带时间信息的串口时序图
使用USB线将电脑和开发板进行连接后将开发板上电,打开电脑的“设备管理器”。当出现如下图所示的界面时表示驱动程序安装成功并且已经被电脑正确地识别。
图3.4-4驱动程序成功安装且被电脑正确地识别界面 在“设备管理器”中可以查看串口号,点击“USBSerial Port(COM3)”,如下图所示。
图3.4-5在设备管理器中查看串口号
随后点击“端口设置”选择“高级”选项,如下图所示。
图3.4-6端口设置界面
下图中可见端口号的具体设置信息,其串口号为COM3。
图3.4-7高级设置界面
点击CON端口号,可以对串口号进行修改,如下图所示。
图3.4-8更改串口号
图3.4-9串口调试助手界面
串口:选择串口号,支持串口号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,下表中其所对应的是图形是“#”,因此软件会显示“#”。
图3.4-10ASCII编码图
十六进制发送:同样的,本软件支持ASCII发送和十六进制发送。若勾选十六进制发送,软件则会以十六进制形式发送。例如填写“31”后手动发送,那么FPGA收到的值为8’b00110001。如果不勾选十六进制发送,则软件以ASCII码形式发送。若同样填写“31”后手动发送,软件将首先发送ASCII码“3”所对应的十六进制值8’h33,再发送ASCII码“1”所对应的十六进制值8’h31,即FPGA将收到两个字节数据:8’h33和8’h31。
第2节 设计目标
了解了串口通信的原理后,本书将完成串口通信的设计。按照至简设计法的思路,进行设计之前首先应明确设计目标,后续设计中每一个步骤都是围绕着设计目标的实现来针对性的展开。如果没有明确设计目标就开始操作实践,最终的作品也只是东拼西凑的产物。在这种状态下的工程调试过程如果出现了问题,则需要花费大量的精力进行寻找修复。因此建议初学者在最开始学习时养成良好的工作习惯,在后续工作中会受益无穷。
本设计中串口调试助手的参数设置为:波特率9600;数据位为8位;无奇偶校验位;停止位为2比特;接收和发送都是16进制数。
本设计中,用户通过串口调试助手发送一个8位的数据data,其可以控制开发板上的8个LED灯,data[0]~data[7]分别控制LED0~LED7灯。当数据位为0时,对应的LED灯点亮,数据为1时,对应的LED灯熄灭。例如,用户发送数据data=8’b10000000后,开发板上的LED0、LED1、LED2LED3、LED4、LED5、LED6保持为亮,LED7保持为灭。这里要注意,信号的传输顺序是从低到高的,数据的最后一位“0”对应LED0,倒数第二位“0”对应LED1,倒数第三位“0”对应LED2,依次类推,第一位“1”对应LED7。
设计完成后的上板效果如下图所示。
图3.4-11串口通信实现效果图 第3节 设计实现
确定设计目标后,下面会逐步分析讲解工程的实现步骤。本书不仅分享案例,还会在操作过程中剖析设计理念及原理,同时也分享一些至简设计法的设计技巧以锻炼独立设计工程的能力。因此,建议初学者认真学习每一步。当然,在已经拥有扎实的功底、只是想要根据步骤完成设计的情况下可以跳过此部分,直接进入后续章节中的简略版操作步骤。
3.1 顶层信号
新建目录:D:\mdy_book\uart,并在此目录中新建一个名为uart.v的文件。并用GVIM打开该文件后开始编写代码。在这里再次强调,初学者一定要按照本书提供的文件路径以及文件名进行设置,避免后面出现未知错误。
根据设计目标可知,用户通过串口调试助手向FPGA发送数据,即CH340控制RX信号让其根据串口时序变化,从而将数据信号传送至FPGA。因此FPGA工程必须有一个接口信号,本书将其命名为rx_uart。
本设计中需要控制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工程信号为rx_uart。因此本工程共需要4个信号:8位信号led,时钟clk,复位rst_n和串口输入信号rx_uart。
表3.4 - 1信号和管脚关系 器件 | | | | LED0 | | | | LED1 | | | | LED2 | | | | LED3 | | | | LED4 | | | | LED5 | | | | LED6 | | | | LED7 | | | | U11 | | | | X1 | | | | K1 | | | |
将module的名称定义为uart,在顶层信号代码中将与外部相连接的输入/输出信号列出,从而实现信号与管脚的连接。已知该模块有四个信号:clk、rst_n、rx_uart和led,其具体代码如下: | module uart( clk , rst_n , rx_uart , led ); |
随后对信号的输入输出属性进行声明,指出对于FPGA来说这一信号属于输入还是输出,若为输入,声明则为input;若为输出,声明则为output。在本设计中由于clk是外部的晶振输送给FPGA的,因此在FPGA中clk为输入信号input;同样地,rst_n是外部按键给FPGA的,在FPGA中也是输入信号input;同时可知led是FPGA输出给LED灯的,是输出信号output。clk、rst_n、rx_uart三个信号的值都为0或1,用一根线表示即可,而led信号位宽为8。根据信号属性将输入输出端口定义补充完整,其代码如下: | input clk ; input rst_n ; input rx_uart; output [7:0] led ; |
3.2 信号设计
由前文可知,led信号控制了8个LED灯的亮灭,然而其亮灭控制还是取决于串口发送数据。那么就存在这样一个问题:串口发送的数据是如何告知FPGA并与led对应起来呢?下面本书就来分析一下串口数据的时序,CH340控制信号rx_uart的时序如下图所示。
图3.4-12串口时序图
从时序图中可以看出:发送8位数据data前,信号rx_uart会先变为0并持续一段时间(起启位),然后发送data[0]、data[1],以此类推直至发送完data[7],发送每位数据时都会持续一段时间,发送完毕后rx_uart会变为1并持续一段时间(结束位)。至此,CH340完成了数据的发送。可以看出每段有效信号的开始前和结束后,都会有特殊信号:有效数据开始前会有一段变0的信号,用以告 知FPGA开始传送数据;结束后会有一段变1的信号,告知FPGA此数据传送结束。 例如,CH340要发送的数据为data=8’h00110001,则rx_uart的波形如下。
3.4-13串口通信时序图
由于波特率设置为9600,考虑时间信息,每位持续的时间是1s/9600=104166ns。补充时间信息后的时序如下图所示。
图3.4-14带时间的时序图
本开发板的晶振时钟是50Mz,对应时间周期为20ns。由此可知每位数据持续时间为104166ns/20ns=5208.3个时钟周期,近似为5208个时钟周期。由于5208只是估计的大概数字,实际情况会产生一定的偏差。在实现过程中,应对数据位数进行计数,以此来判断开始位、数据位和停止位等。
图3.4-15串口通信实现架构图【这个图要去掉ns】
通过上文分析可知:本设计一共需要2个计数器,1个计数器用于计算1比特的位宽长度即5208个时钟周期,将其命名为cnt0;另一个用于计算有多少个比特,将其命名为cnt1。
首先讨论cnt0的实现,至简设计法中计数器的设计只考虑两个因素:加1条件和计数数量。由于1比特的位宽长度是5208个时钟周期,因此cnt0的计数数量是5208。
确定了计数数量后来分析一下cnt0的加1条件。为了更好理解加1条件的概念,这里以停车位来进行比喻。一般情况下对每个停车位置会进行对应编号,但是如果某个位置上放置了一块石头无法作为停车位时,该位置就不能获得对应的编号。反之则可以认为停车位编号的加1条件就是:对应位置上没有石头,其可以继续的进行编号,即assign add_cnt0 = “没有石头”。因此如果在设计中计数器一直没有阻碍地进行计数工作,就可以认为加1条件是一直有效的。根据设计目标,可以确定cnt0的加1区域为其一直工作的区域,如下图灰色区域所示。
图3.4-16计数器cnt0的加1区域
虽然确定了cnt0的加1区域,但现有设计中没有任何一个信号可以单独表示出这一区域。在这种情况下,进行对信号进行补充。因此本设计添加一个“flag_add”信号,该信号为1时表示上述灰色区域,即cnt0的加1区域。这样就可以明确计数器cnt0的加1条件为“flag_add==1”。
图3.4-17补充加1条件flag_add
确定好cnt0的加1条件和计数数量后,就开始进行代码编写,以往都是一行行的输入相应代码。但是至简设计法有一个小技巧,可以节省代码编写时间的同时在一定程度上降低了代码的出错率。至简设计法将日常代码中常用到的固定部分制作成模板,进行代码编程时可以调用相应模板后根据逻辑输入对应设计的变量将代码补充完整。这里就可以用模板编写计数器代码,感受一下这个炫酷的功能。
打开GVIM工具,在命令模式下输入“:Mdyjsq”后点击回车,调出对应模板如下图所示。
图3.4-18至简设计法调用计数器代码模板
通过上文分析可知,cnt0的加1条件是“flag_add==1”,计数数量为5208。将其填入到模板中,可以得到完整正确的计数器cnt0代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt0 <= 0; end else if(add_cnt0)begin if(end_cnt0) cnt0 <= 0; else cnt0 <= cnt0 + 1; end end
assign add_cnt0 = flag_add==1 ;
assign end_cnt0 = add_cnt0 && cnt0 == 5208-1 ; |
下面来设计计数器cnt1,该计数器用于表示数到第几比特,每个比特结束,即“end_cnt0”时,cnt1就会加1。因此cnt1的加1条件是:end_cnt0。通过设计目标中可知cnt1的计数数量为9。打开GVIM,继续调用模板,在命令模式下输入“:Mdyjsq”,点击回车。将“add_cnt1”和“end_cnt1”补充完整,得到cnt1的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt1 <= 0; end else if(add_cnt1)begin if(end_cnt1) cnt1 <= 0; else cnt1 <= cnt1 + 1; end end
assign add_cnt1 = end_cnt0; assign end_cnt1 = add_cnt1 && cnt1 == 9-1 ; |
在设计cnt0时,考虑到其加1条件增加了辅助信号flag_add,下面就来思考如何设计这一信号。通过分析可知flag_add具有两个变化点:变0和变1,这两种变化可以从功能上进行理解。工程通电后,如果PC端没有发送任何数据,则rx_uart始终保持为1,cnt0和cnt1无须计数,flag_add信号也一直保持为0。当PC端要发送数据时,rx_uart就会按串口时序产生变化,首先会发送一个开始位,即rx_uart由1变成0。FPGA接收到这一信号后,就明白PC要开始传送数据,此时cnt0和cnt1要计数了,即flag_add要变为1。
可以通过下图来辅助分析,可以看到当rx_uart为1时,flag_add为0,此时PC没有发来任何数据,cnt0和cnt1不计数。当rx_uart数第1比特,即开始位时,即通知FPGA准备好接收数据,这时rx_uart变0时,flag_add变1,cnt0和cnt1开始计数。
图3.4-19确定flag_add信号
从上图可以很容易看出:当rx_uart由1变0时,flag_add就由0变成1。其中,rx_uart信号的由1变0波动被称为下降沿,在图中可以清晰的看出此时波形是下降的。那么又如何得知下降沿的到来呢?这里就需要引入一个边沿检测电路工程来辅助设计。
3.2.1边沿检测电路设计
检测rx_uart的下降沿需要用到FPGA中的边沿检测技术。所谓边沿检测,就是检测输入信号或FPGA内部逻辑信号的跳变,即检测上升沿或下降沿。这一技术这在FPGA电路设计中被广泛应用,其电路图及各信号定义如下所示。
图3.4-20边缘检测实现原理图
图3.4-21各信号定义
可以看出中间信号trigger与触发器的信号输入端D连接,将trigger信号取反后与触发器的输出tri_ff0相与得到信号neg_edge。如果neg_edge=1就表示检测到trigger的下降沿。将触发器的输出tri_ff0取反与trigger相与后得到信号pos_edge,如果pos_edge=1则表示检测到trigger的上升沿。利用这一原理可以画出信号的波形图如下图所示。
图3.4-22边缘检测电路波形图
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,可以看到体重上升,对应信号变化即可视作迎来了一次上升沿。综上所述,rx_uart的上升沿/下降沿检测依据为这一刻的状态与上一刻的状态有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实现边沿检测电路的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin tri_ff0 <= 0; end else begin tri_ff0 <= trigger ; end end assign neg_edge = trigger==0 && tri_ff0==1; assign pos_edge = trigger==1 && tri_ff0==0;
|
3.2.2异步信号同步化
在边沿检测波形的讨论中将trigger信号视为理想的同步信号,即trigger满足D触发器的建立和保持时间,这种情况下在同步系统中实现边沿检测不是问题。但如果trigger信号不是理想的同步信号(如外部按键信号或是本工程的rx_uart信号)时,信号的变化由外部传输指令给FPGA,对于FPGA来说这些信号什么时候产生变化是完全随机的。因此,很可能出现信号在时钟上升沿发生变化,无法满足触发器的建立时间和保持时间要求,从而出现亚稳态,导致系统崩溃。详细原因可以参看至简设计法D触发器中亚稳态一节的相关内容。根据这一结论,本设计需要对输入的信号延迟两拍即先用两个触发器寄存后再进行使用。如下图所示,用2个触发器对信号进行寄存,确定了信号的稳定性,然后再进行边沿检测,满足了同步系统中实现边沿检测的需求。
图3.4-23异步信号同步化电路图
因此,在边沿检测代码设计前需要先对触发器进行设计。假设输入的信号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有效,其具体代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin tri_ff0 <= 0; tri_ff1 <= 0; tri_ff2 <= 0; end else begin tri_ff0 <= trigger ; tri_ff1 <= tri_ff0 ; tri_ff2 <= tri_ff1 ; end end assign neg_edge = tri_ff1==0 && tri_ff2==1; assign pos_edge = tri_ff1==1 && tri_ff2==0; |
综上所述,如果输入信号是异步信号,需要先对其进行同步化之后再做检测,即通过延迟两拍的方式实现信号的同步化,再通过延迟一拍的方式实现边沿检测电路。反之,如果输入信号本身就是同步信号,则没有必要进行同步化了,可以直接对其进行边沿检测。
回到本设计中,此时需要检测的是rx_uart的下降沿,并以此为条件拉高信号flag_add。由于PC端随机地给FPGA发送数据,其发送时刻是不确定的,因此本设计中rx_uart是异步信号。根据边缘检测的设计方法,此时需先将rx_uart进行同步化后再对其进行下降沿检测。其具体设计代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin rx_uart_ff0 <= 0 ; rx_uart_ff1 <= 0 ; rx_uart_ff2 <= 0 ; end else begin rx_uart_ff0 <= rx_uart ; rx_uart_ff1 <= rx_uart_ff0 ; rx_uart_ff2 <= rx_uart_ff1 ; end end |
如此一来,flag_add变1的条件就变成:rx_uart_ff1==0&& rx_uart_ff2==1。
确定flag_add变1条件后,再来讨论一下其变0的条件。当完成9比特数据的传输后,flag_add的值变为0,不再计数。因此,其变0条件为end_cnt1。根据以上分析可以得到flag_add的代码如下。 | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin flag_add<= 0 ; end else if(rx_uart_ff1==0 && rx_uart_ff2==1)begin flag_add<= 1 ; end else if(end_cnt1)begin flag_add<= 0 ; end end |
下面来设计data信号,根据串口时序可知,该信号的值来自下图中第2~第9比特的值。其中第2比特的值赋给data[0],第3比特的值赋给data[1],以此类推,第9比特的值赋给data[7]。
图3.4-24串口通信实现架构图【去掉ns】
由于每一个比特都会持续5208个时钟周期,因此必须选定一个具体时刻来将值赋给data。此处可能会有这样的疑虑:直接在每个比特计数结束的时刻,即在end_cnt0的时刻赋值不就可以了么?然而实际上,这一时刻并不可取。end_cnt0的时刻在下图中用点表示出来,此时的5208个时钟周期是理想、估算的数值,而在实际计算中很有可能出现偏差,如果在end_cnt0的时候取值,就有可能会出现错误。
图3.4-25边缘取值方法【去掉ns】
因此,最保险的做法是在中间点取值,如下图所示。在这种取值方法下,即使有较多的偏差,也不会影响到采样的正确性。
图3.4-26中间取值的方法
综上所述,本设计在cnt0计数到一半时将采到的当前rx_uart值赋给led,其中第2比特赋给led[0],第3比特赋给led[1],以此类推,第9比特赋给led[7]。将其用代码的形式表现出来则为:当add_cnt0&& cnt0==5208/2 -1时,如果cnt1==1,则将rx_uart_ff1赋给led[0]。如果cnt1==2,则将rx_uart_ff1赋给led[1],以此类推,如果cnt1==8,将rx_uart_ff1赋给led[7]。其具体代码如下所示。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin led <= 0 ; end else if(add_cnt0 && cnt0==5208/2-1)begin if(cnt1==1) led [0] <= rx_uart_ff1 ; else if(cnt1==2) led [1] <= rx_uart_ff1 ; else if(cnt1==3) led [2] <= rx_uart_ff1 ; else if(cnt1==4) led [3] <= rx_uart_ff1 ; else if(cnt1==5) led [4] <= rx_uart_ff1 ; else if(cnt1==6) led [5] <= rx_uart_ff1 ; else if(cnt1==7) led [6] <= rx_uart_ff1 ; else led [7] <= rx_uart_ff1 ; end end |
对以上代码进行优化,可简写为:
|
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin led <= 0 ; end else if(add_cnt0 && cnt0==5208/2-1 && cnt1>=1 && cnt1<9)begin led [cnt1-1] <= rx_uart_ff1 ; end end |
在进行设计时,通常是首先想到实现功能,所以会先写出第一段的优化前代码。在功能实现的前提下,再考虑有没有优化空间,从而对其进行优化得到第二段代码。设计过程中,好的代码也是这样一步步优化出来的。至此,主体程序已经完成。
3.3 信号定义
下面需要将module补充完整,首先来定义信号类型。reg和wire的判断很容易搞不清楚总会有其余的联想,比如认为reg就是寄存器,wire是线;或者认为reg的会综合成寄存器,wire不会综合成寄存器。但是这些其实和reg型还是wire型都是没有关系的,因此在信号类型判断时不需要做任何的联想,只要记住一个规则“用always实现的是reg型,其他都是wire型”就可以了。
cnt0是用always产生的信号,因此类型为reg。根据前文分析可知,该计数器计数的最大值为5208,因此需要用13根线表示,即位宽是13位。
关于信号位宽的获取,至简设计法在此分享一个非常实用的技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入,就会获得对应的信号位宽,如下图所示,将计数器的最大值5208输入,可以看出其位宽为13。
图3.4-27通过计算器获取信号位宽
综上所述,cnt0的信号定义代码如下:
cnt1也是用always产生的信号,因此类型为reg。cnt1计数的最大值为9,需要用4根线表示,即位宽是4位。编辑模式下输入“Reg4”调用至简设计法模板,补充完整后得到代码表示如下:
add_cnt0和end_cnt0都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用模板,得到代码表示如下: | wire add_cnt0 ; wire end_cnt0 ; |
add_cnt1和end_cnt1也是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可,其代码如下: | wire add_cnt1; wire end_cnt1; |
flag_add是用always方式设计的,因此类型为reg。其值是0或者1,用1根线表示即可。编辑模式下输入“Reg1”调用模板,得到代码如下:
rx_uart_ff0、rx_uart_ff1和rx_uart_ff2也都是用always方式设计的,因此类型为reg。并且其值是0或1,需要1根线表示即可。编辑模式下输入“Reg1”调用模板,得到代码表示如下:
| reg rx_uart_ff0; reg rx_uart_ff1; reg rx_uart_ff2; |
至此,整个代码的设计工作已经完成,完整的工程代码如下: | module uart( clk , rst_n , rx_uart , led );
input clk ; input rst_n ; input rx_uart; output[7:0] led ;
reg [12:0] cnt0 ; wire add_cnt0; wire end_cnt0; reg [ 3:0] cnt1 ; wire add_cnt1; wire end_cnt1;
reg rx_uart_ff0; reg rx_uart_ff1; reg rx_uart_ff2; reg flag_add ; reg [ 7:0] led ;
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt0 <= 0; end else if(add_cnt0)begin if(end_cnt0) cnt0 <= 0; else cnt0 <= cnt0 + 1; end end
assign add_cnt0 = flag_add; assign end_cnt0 = add_cnt0 && cnt0==5208-1 ;
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt1 <= 0; end else if(add_cnt1)begin if(end_cnt1) cnt1 <= 0; else cnt1 <= cnt1 + 1; end end
assign add_cnt1 = end_cnt0; assign end_cnt1 = add_cnt1 && cnt1==9-1 ;
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin rx_uart_ff0 <= 1; rx_uart_ff1 <= 1; rx_uart_ff2 <= 1; end else begin rx_uart_ff0 <= rx_uart ; rx_uart_ff1 <= rx_uart_ff0; rx_uart_ff2 <= rx_uart_ff1; end end
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin flag_add<= 0; end else if(rx_uart_ff1==0 && rx_uart_ff2==1)begin flag_add<= 1; end else if(end_cnt1)begin flag_add<= 0; end end
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin led <= 8'hff; end else if(add_cnt0 && cnt0==5208/2-1 && cnt1>0)begin led[cnt1-1] <= rx_uart_ff1; end end
endmodule |
第4节 综合工程和上板
4.1 新建工程
打开软件Quartus Ⅱ,点击“File”下拉列表中的New ProjectWzard...新建工程选项,如下图所示。
图3.4-28Quartus新建工程
然后会出现Quartus新建工程介绍,如下图所示,直接点击“Next”。
图3.4-29Quartus新建工程介绍
此时会出现的是工程文件夹、工程名、顶层模块名设置界面,如图3.4-30所示。设置目录为:D:/mdy_book/uart,工程名和顶层名为uart。这里再次强调,为了避免初学者使用过程中出现报错情况,强烈建议按照本书的工程名和文件名进行设置,设置完成后点击“Next”。
图3.4-30QUARTUS新建工程设置名称
新建工程类型设置选择“Empty project”,如下图所示,然后点击“Next”。
图3.4-31QUARTUS新建工程类型
文件添加界面如图3.4- 32所示,点击右侧的“Add”按钮,选择之前写好的“uart.v”文件,可以看到界面下方会显示出文件,随后点击“Next”。
图3.4-32QUARTUS添加文件
芯片型号选择界面如图3.4- 33所示,选择“Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”,之后点击“Next”。
图3.4-33QUARTUS选择芯片型号
图3.4- 34为QUARTUS设置工具界面,不必做任何修改,直接点击“Next”即可。
图3.4-34QUARTUS设置工具界面
下图可以看到新建工程的汇总情况,点击“Finish”,完成新建工程。
图3.4-35QUARTUS新建工程汇总界面
4.2 综合
新建工程步骤完成后,就会出现如下所示的QUARTUS界面。
图3.4-36QUARTUS新建工程后界面 点击编译按钮,可以对整个工程进行编译。编译成功的界面,如图3.4- 37所示。
图3.4-37QUARTUS编译后界面 4.3 配置管脚
下面需要对相应管脚进行配置。如下图所示,在菜单栏中选中“Assignments”,然后选择“Pin Planner”,就会弹出配置管脚的窗口。
图3.4-38QUARTUS配置管脚选项
在配置窗口最下方中的“location”一列,参考信号和管脚关系,按照表3.4- 1中最右两列配置好FPGA管脚。配置管理来源参见管脚配置环节,配置结果如图3.4- 39所示。配置完成后,关闭“PinPlanner”,软件自动会保存管脚配置信息。 表3.4 - 1信号和管脚关系
图3.4-39 QUARTUS配置管脚
4.4 再次综合再次打开“QUARTUS”软件,在菜单栏中选中“Processing”,然后选择“StartCompilation”,再次对整个工程进行编译和综合,如下图所示。
图3.4-40QUARTUS编译选项
当出现如下所示的编译成功标志时,说明编译综合成功。
图3.4-41QUARTUS编译成功标志
4.5 连接开发板
完成编译后开始进行上板调试操作,按照下图的方式,将下载器接入电脑USB接口,接上开发板电源后按下开发板下方蓝色开关,硬件连接完毕。
图3.4-42开发板连接图
4.6 上板打开QUARTUS界面,单击界面中的 ,弹出配置界面。点击“add file”添加“.sof”文件,点击“Start”,会在“Progress”出现显示进度。
图3.4-43QUARTUS界面
当进度条到100%时提示成功,即表示上板结束。
图3.4-44QUARTUS下载程序界面
4.7 串口调试
开发板连接完成后按下电源键,随后打开电脑的设备管理器,确认串口的端口号。从下图可以看出,当前串口的端口号为COM3。
图3.4-45设备管理器显示端口号
前文提供了串口调试工具下载地址,安装完成后,打开串口调试助手,其界面如下图所示。在操作界面进行设置:在串口选项中选择端口号,这里要注意端口号的选择需要与设备管理器中端口号显示一致,即为“COM3”;波特率选项选择“9600”;校验位选项选择“无校验位”;数据位选项选择“8”;停止位选项选择“2”;注意一定要勾选“十六进制显示”和“十六进制发送”两个选项:
图3.4-46串口调试助手界面
设置好串口助手后,就可以观察发送数据的现象。在发送数据栏输入相应的数据(将8个LED灯对应的8位二进制数转化为十六进制),然后点击手动发送,即可在开发板上观察到相应的现象。可以尝试发送不同的指令,看是否可以得到相应的LED灯变化效果。特别要注意的是,指令的顺序应与小灯亮的顺序相同,如果两者不同,则需检查指令顺序是否发生输入错误。
第5节 简化版步骤分享
同样这里也会分享简化版的步骤,方便复习以及反复实操。
5.1 设计实现
5.1.1顶层信号
新建目录:D:\mdy_book\uart。在该目录中,新建一个名为uart.v的文件,并用GVIM打开,开始编写代码。 确定顶层信号。本工程共需要4个信号,一个8位信号led,时钟clk,复位rst_n和串口输入信号rx_uart,工程信号和管脚关系图如下: 表3.4 - 1信号和管脚关系 器件 | | | | LED0 | | | | LED1 | | | | LED2 | | | | LED3 | | | | LED4 | | | | LED5 | | | | LED6 | | | | LED7 | | | | U11 | | | | X1 | | | | K1 | | | |
写出顶层信号代码: | module uart( clk , rst_n , rx_uart , led ); |
声明输入输出属性: | input clk ; input rst_n ; input rx_uart; output [7:0] led ; |
5.1.2信号设计
首先进行架构设计,串口时序图如下所示:
图3.4-2串口时序图
串口通信实现架构图如下:
图3.4-15串口通信实现架构图【去掉ns】 添加一个“flag_add”信号,从而满足计数器的加1条件。
图3.4-17补充加1条件flag_add[去掉ns]
设计计数器架构,对表示1比特位宽长度的计数器cnt0进行设计,其代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt0 <= 0; end else if(add_cnt0)begin if(end_cnt0) cnt0 <= 0; else cnt0 <= cnt0 + 1; end end
assign add_cnt0 = flag_add==1 ; assign end_cnt0 = add_cnt0 && cnt0 == 5208-1 ; |
计算比特个数的计数器cnt1的设计代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt1 <= 0; end else if(add_cnt1)begin if(end_cnt1) cnt1 <= 0; else cnt1 <= cnt1 + 1; end end
assign add_cnt1 = end_cnt0; assign end_cnt1 = add_cnt1 && cnt1 == 9-1 ; |
rx_uart的代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin rx_uart_ff0 <= 0 ; rx_uart_ff1 <= 0 ; rx_uart_ff2 <= 0 ; end else begin rx_uart_ff0 <= rx_uart ; rx_uart_ff1 <= rx_uart_ff0 ; rx_uart_ff2 <= rx_uart_ff1 ; end end |
flag_add的代码如下: | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin flag_add<= 0 ; end else if(rx_uart_ff1==0 && rx_uart_ff2==1)begin flag_add<= 1 ; end else if(end_cnt1)begin flag_add<= 0 ; end end |
led代码如下: | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin led <= 0 ; end else if(add_cnt0 && cnt0==5208/2-1 && cnt1>=1 && cnt1<9)begin led [cnt1-1] <= rx_uart_ff1 ; end end |
至此,主体程序完成,下面将module补充完整。
5.1.3信号定义
定义信号类型,其中cnt0的信号定义如下:
add_cnt0 和 end_cnt0 的信号定义如下: | wire add_cnt0 ; wire end_cnt0 ; |
cnt1 的信号定义如下:
add_cnt1 和 end_cnt1 的信号定义如下: | wire add_cnt1; wire end_cnt1; |
flag_add的信号定义如下:
rx_uart_ff0、rx_uart_ff1和rx_uart_ff2的信号定义如下: | reg rx_uart_ff0; reg rx_uart_ff1; reg rx_uart_ff2; |
至此,整个工程的代码的设计工作已经完成,接下来新建工程,对代码进行编译综合后上板查看现象。
5.2 综合工程和上板
5.2.1新建工程
打开软件Quartus Ⅱ,点击“File”下拉列表中的New ProjectWzard...新建工程选项。
图3.4-28Quartus新建工程
直接点击“Next”:
图3.4-29Quartus新建工程介绍
此时出现的是工程文件夹、工程名、顶层模块名设置界面,完成设置后点击“Next”:
图 3.4 - 43QUARTUS新建工程设置名称
选择“Empty project”后点击“Next”。
图3.4-31QUARTUS新建工程类型
点击右侧的“Add”按钮,选择“huxiled.v”文件完成添加后点击“Next”。
图3.4-32QUARTUS添加文件
“Device family”选项中选择“Cyclone ⅣE”,“Available devices”选项下选择“EP4CE15F23C8”,随后点击“Next”。
图3.4-33QUARTUS选择芯片型号
直接点击“Next”。
图3.4-34QUARTUS设置工具界面
点击“Finish”,完成新建工程。
图3.4-35QUARTUS新建工程汇总界面
5.2.2综合
新建工程后界面如下图所示,点击“编译”。
图3.4-36QUARTUS新建工程后界面
编译成功如下图所示。
图3.4-37QUARTUS编译后界面
5.2.3配置管脚
在菜单栏中点击“Assignments”后点击“Pin Planner”,随后会弹出配置管脚的窗口。
图3.4-38QUARTUS配置管脚选项
在配置窗口“location”根据信号和管脚关系配置管脚,配置完成关闭“Pin Planner”即可自动保存配置信息。
图3.4-39QUARTUS配置管脚
器件 | | | | LED0 | | | | LED1 | | | | LED2 | | | | LED3 | | | | LED4 | | | | LED5 | | | | LED6 | | | | LED7 | | | | U11 | | | | X1 | | | | K1 | | | |
5.2.4再次综合
打开“QUARTUS”软件,在菜单栏中选择“Processing”,点击“StartCompilation”再次进行综合。
图3.4-40QUARTUS编译选项
出现 QUARTUS 编译成功标志则表示编译成功。
图3.4-41QUARTUS编译成功标志
5.2.5连接开发板
下载器接入电脑 USB 接口,开发板接上电源后按下蓝色开关。
图3.4-42开发板连接图
5.2.6上板
打开QUARTUS界面,单击“ ”图标后弹出配置界面。点击“add file”添加“.sof”文件后点击“Start”,在“Progress”中会显示当前进度。
图3.4-43QUARTUS界面
如下图所示,进度条显示为100%,提示成功,即表示上板结束。
图3.4-44QUARTUS下载程序界面
5.2.7串口调试
开发板连接完成后,按下开发板的电源键,打开电脑的设备管理器并确认串口的端口号,如下图所示:
图3.4-45设备管理器显示端口号
下载本书提供的串口调试工具后,打开串口调试助手。对串口调试助手进行设置:选择端口号、波特率选“9600”、校验位选“无校验位”、数据位选“8”、停止位选“2”、一定要勾选“十六进制显示”和“十六进制发送”两个选项。
图3.4-46串口调试助手界面
在发送数据栏输入相应的数据(将8个LED灯对应的8位二进制数转化为十六进制)后点击手动发送,即可在开发板上观察现象。
第6节 扩展练习
至此整个串口通信的设计就分享完毕了。在学会整个设计之后可以展开思考,尝试发送不同的指令,看是否可以得到相应的LED灯变化效果。在基于原理不变的情况下多做一些尝试,这样可以更深刻的掌握案例,也欢迎有更好思路和想法的同学前往至简设计法论坛进行讨论。
|