首页资源分类FPGA/CPLDAltera > 4《HELLO FPGA》- 项目实战篇

4《HELLO FPGA》- 项目实战篇

已有 445185个资源

下载专区

文档信息举报收藏

标    签:《HELLOFPGA》

分    享:

文档简介

4《HELLO FPGA》- 项目实战篇

文档预览

封面 前言 为什么要学项目实战篇:前面的篇章多为理论知识,而这一篇是结合开发板实物,从理论上 升到实践,将前面的基础知识运用到实际的工程项目当中。 项目实战篇包含哪些内容:我们例举三人表决器、数字时钟、多终端点歌系统、数字示波器 这四个实际的工程项目,手把手带领大家从分析工程、分解工程、到最终实现工程。通过逐个解 决工程中的实际问题,来学习原汁原味的 FPGA 设计。本篇一改传统教程里逐个讲解外设的方 法,巧妙的将所有外设功能放在实际项目当中讲解,使读者真正意义上做到了现学现用,活学活 用。 目录 第一章 三人表决器设计分析 ......................................................................................................... 1 §1.1 逻辑代数基础..................................................................................................................3 §1.2 组合电路基础 .................................................................................................................5 1.2.1 组合电路的分析与设计 ...........................................................................................5 1.2.2 三人表决器的理论实战...........................................................................................5 §1.3 Verilog 基础 ....................................................................................................................6 1.3.1 结构化描述方式 ......................................................................................................7 1.3.2 数据流描述方式 ......................................................................................................8 1.3.3 行为级描述方式 ......................................................................................................9 §1.4 Quartus 基础 ................................................................................................................ 11 1.4.1 新建工程 ................................................................................................................ 12 1.4.2 输入设计 ............................................................................................................... 14 1.4.3 查看 RTL...............................................................................................................16 §1.5 ModelSim 仿真 .............................................................................................................17 第二章 三人表决器设计实战 .......................................................................................................23 §2.1 LED 外设...................................................................................................................... 25 2.1.1 功能概述 ............................................................................................................... 25 2.1.2 设计说明 .............................................................................................................. 25 2.1.3 源码解析 .............................................................................................................. 27 2.1.4 板级调试 .............................................................................................................. 29 §2.2 按键外设 ...................................................................................................................... 31 2.2.1 功能概述 ............................................................................................................... 31 2.2.2 设计说明 .............................................................................................................. 33 2.2.3 源码解析 .............................................................................................................. 33 2.2.4 板级调试 .............................................................................................................. 34 §2.3 数码管外设.................................................................................................................. 35 2.3.1 功能概述 .............................................................................................................. 35 2.3.2 设计说明 .............................................................................................................. 37 2.3.3 源码解析 .............................................................................................................. 37 2.3.4 板级调试 .............................................................................................................. 39 §2.4 用外设来实现三人表决器 ........................................................................................... 39 2.4.1 功能概述 .............................................................................................................. 39 2.4.2 设计说明 .............................................................................................................. 40 2.4.3 源码解析 .............................................................................................................. 40 2.4.4 板级调试 .............................................................................................................. 42 第三章 数字时钟设计分析 ...........................................................................................................43 §3.1 锁存器和触发器 .......................................................................................................... 45 3.1.1 锁存器................................................................................................................... 45 3.1.2 触发器 .................................................................................................................. 48 §3.2 寄存器和计数器 .......................................................................................................... 49 3.2.1 寄存器 .................................................................................................................. 49 3.2.2 计数器 .................................................................................................................. 50 §3.3 数字时钟的理论实战 .................................................................................................. 52 第四章 数字时钟设计实战 ...........................................................................................................55 §4.1 LED 进阶...................................................................................................................... 57 4.1.1 功能概述 ............................................................................................................... 57 4.1.2 设计说明 .............................................................................................................. 58 4.1.3 源码解析 .............................................................................................................. 59 4.1.4 板级调试 .............................................................................................................. 64 §4.2 按键进阶 ..................................................................................................................... 65 4.2.1 功能概述 .............................................................................................................. 65 4.2.2 设计说明 .............................................................................................................. 66 4.2.3 源码解析 .............................................................................................................. 67 4.2.4 板级调试 .............................................................................................................. 70 §4.3 数码管进阶.................................................................................................................. 70 4.3.1 功能概述 .............................................................................................................. 70 4.3.2 设计说明 ............................................................................................................... 71 4.3.3 源码解析 .............................................................................................................. 72 4.3.4 板级调试 .............................................................................................................. 75 §4.4 蜂鸣器外设 ................................................................................................................. 75 4.4.1 功能概述 .............................................................................................................. 75 4.4.2 设计说明 .............................................................................................................. 77 4.4.3 源码解析 .............................................................................................................. 77 4.4.4 板级调试 .............................................................................................................. 80 §4.5 用外设来实现数字时钟............................................................................................... 80 4.5.1 功能概述 .............................................................................................................. 80 4.5.2 设计说明 .............................................................................................................. 80 4.5.3 源码解析 ...............................................................................................................81 4.5.4 板级调试 .............................................................................................................. 87 第五章 多终端点歌系统设计分析............................................................................................... 89 §5.1 UART 控制原理 ............................................................................................................91 5.1.1 UART 基本概述 .....................................................................................................91 5.1.2 UART 通信协议.................................................................................................... 94 §5.2 UART 实际应用........................................................................................................... 96 5.2.1 功能概述 .............................................................................................................. 96 5.2.2 设计说明 .............................................................................................................. 96 5.2.3 源码解析 .............................................................................................................. 97 5.2.4 板级调试 .............................................................................................................104 §5.3 红外控制原理.............................................................................................................105 5.3.1 红外基本概述 ......................................................................................................105 5.3.2 红外通信协议 .....................................................................................................108 §5.4 红外实际应用............................................................................................................. 110 5.4.1 功能概述 ............................................................................................................. 110 5.4.2 设计说明 ............................................................................................................. 110 5.4.3 源码解析 ..............................................................................................................111 5.4.4 板级调试 ............................................................................................................. 118 §5.5 PS/2 控制原理 ............................................................................................................ 118 5.5.1 PS/2 基本概述 ..................................................................................................... 118 5.5.2 PS/2 通信协议..................................................................................................... 119 §5.6 PS/2 实际应用 ............................................................................................................125 5.6.1 功能概述 .............................................................................................................125 5.6.2 设计说明 .............................................................................................................125 5.6.3 源码解析 .............................................................................................................126 5.6.4 板级调试 .............................................................................................................129 第六章 多终端点歌系统设计实战.............................................................................................. 131 §6.1 功能概述.....................................................................................................................133 §6.2 设计说明 ....................................................................................................................133 §6.3 源码解析 ....................................................................................................................134 §6.4 板级调试 ....................................................................................................................136 第七章 数字示波器设计分析 .....................................................................................................137 §7.1 VGA 控制原理 ............................................................................................................139 7.1.1 VGA 基本概述......................................................................................................139 7.1.2 VGA 通信协议 .....................................................................................................142 §7.2 VGA 实际应用 ............................................................................................................143 7.2.1 功能概述 .............................................................................................................143 7.2.2 设计说明 .............................................................................................................143 7.2.3 源码解析 .............................................................................................................144 7.2.4 板级调试 .............................................................................................................149 §7.3 AD 控制原理 ...............................................................................................................149 7.3.1 AD 基本概述 ........................................................................................................149 7.3.2 AD 通信协议........................................................................................................150 §7.4 AD 实际应用 ............................................................................................................... 151 7.4.1 功能概述 ............................................................................................................. 151 7.4.2 设计说明 ............................................................................................................. 151 7.4.3 源码解析 .............................................................................................................152 7.4.4 板级调试 .............................................................................................................158 §7.5 DA 控制原理 ...............................................................................................................159 7.5.1 DA 基本概述 ........................................................................................................159 7.5.2 DA 通信协议........................................................................................................ 161 §7.6 DA 实际应用 ...............................................................................................................163 7.6.1 功能概述 .............................................................................................................163 7.6.2 设计说明 .............................................................................................................163 7.6.3 源码解析 .............................................................................................................164 7.6.4 板级调试 ............................................................................................................. 171 第八章 数字示波器设计实战 .....................................................................................................173 §8.1 功能概述.....................................................................................................................175 §8.2 设计说明 ....................................................................................................................175 §8.3 源码解析 ....................................................................................................................176 §8.4 板级调试 ....................................................................................................................194 版权声明 .......................................................................................................................................197 三人表决器设计分析 第一章 三人表决器设计分析 说到三人表决器,我相信大家都不陌生,因为我们在日常生活中会经常使用到它,就比如说, 班级里面竞选班长,我们就是通过这一原理来投票竞选的。对于一个 FPGA 初学者来说,虽然 三人表决器它的原理很简单,但是,我们想要使用 FPGA 来实现它,其实并不是那么容易的。 为什么不容易呢?下面我们给大家分析分析,大家看如图 1.1 所示,这是一个实现三人表决器的 整个工程框架图。 基础知识学习 数字电路基础知识 FPGA基础知识 逻辑代数基础 组合电路基础 Verilog基础 Quartus基础 勤能补拙 融会贯通 完成三人表决器 功能概述 设计说明 源码解析 板级调试 LED外设 按键外设 数码管外设 基础外设学习 图 1.1 实现三人表决器的工程框架图 通过该图我们可以看出,想要实现三人表决器,首先我们得具备数字电路基础知识,因为数 字电路是基础,我们只有学会了数字电路,我们才知道怎么样能够实现三人表决器。我们知道了 怎么样能够实现三人表决器以后,接下来我们就需要学习 Verilog 硬件描述语言,因为学习 Verilog 硬件描述语言,可以将我们数字电路中学到的组合电路知识描述成 Verilog 代码。当然, 只有这些基础知识是不够的,我们还需要学习 Quartus II 软件,因为 Quartrus II 软件可以将我 们的 Verilog 代码综合成实际的硬件电路运行至我们的 FPGA 中。到了这里,我们已经实现了三 人表决器了吗?答案是没有的,从这个框架图中我们可以看到,我们只完成了一半的任务,我们 的三人表决器核心的电路是有了,但是我们的三人表决器电路它没有输入设备,也没有输出设, 我们的三人表决器是空有一身功夫没处用。为了能够让我们的三人表决器能够正常使用,我们还 需要学习 LED 外设、按键外设,以及我们的数码管外设,学习了这三个外设以后,我们就可以 将这三个外设合理的应用到我们的三人表决器上,最终完成三人表决器的设计,实现三人表决器 的功能。 §1.1 逻辑代数基础 通过上面的分析,想必大家对整个工程框架有了一定的了解,如果你在开始学习我们的《项 4 项目实战篇 §1 目实战篇》前,已经看过学习过我们的《数字电路篇》、《硬件基础语法篇》,以及我们的《软件 工具篇》,那么你现在实现起三人表决器来那是轻而易举的事情。如果你没有学习,或者说,学 习过,时间太久生疏了,那么在这里也是不用担心的,我们接下来就会带领大家再将我们的基础 知识复习一遍。 首先我们从数字电路基础知识出发,用数字电路基础知识设计出三人表决器的函数表达式 及逻辑电路图,来带大家回顾数字电路的基础知识;其次,我们用三人表决器的函数表达式及逻 辑电路图,写出其相对应的 Verilog 代码,来带领大家了解 Verilog 基础知识;最后,我们用三 人表决器的 Verilog 代码,在 Quartus 中实现三人表决器的逻辑功能,来带领大家掌握 Quartus 基础知识,其整个基础学习框架图,如图 1.2 所示。 基础知识学习 数字电路基础知识 FPGA基础知识 逻辑代数基础 组合电路基础 Quartus基础 Verilog基础 勤能补拙 融会贯通 图 1.2 三人表决器的总体分析学习框架图 下面我们就给大家复习数字电路的第一部分逻辑代数基础知识。在逻辑代数中有三种基本 的逻辑运算,它们分别是:与、或、非。这三种基本的逻辑运算可以组合成任意的复杂逻辑运算。 我们在实际应用中为了减少逻辑运算的数目,使数字电路的设计方便,我们通常还使用其他四种 组合运算,它们分别是:与非、或非、异或、同或。下面我们就对这些逻辑运算进行介绍。为了 能让大家更好的对比学习这些逻辑运算,我将它们制作成了表格,如表 1.1 所示。 表 1.1 常见的门电路几种表示方法 与 输出 (AND) 输入 00 0 01 0 10 0 11 1 或 (OR) 0 1 1 1 非 与非 (NOT) (NAND) 或非 (NOR) 1 1 1 1 1 0 0 1 0 0 0 0 异或 (XOR) 0 1 1 0 同或 (XNOR) 1 0 0 1 国内 & ≥1 =1 & ≥1 =1 = 电 路 符 号 国外 通过表格我们可以知道这些逻辑运算的逻辑符号、逻辑表达式和真值表。由于国外的电路符 Zircon Opto-Electronic Technology CO.,Ltd. §1 三人表决器设计分析 5 合和我们国内的电路符号稍微有点不同,所以这里我们将这两种符号都列了出来,方便读者对比 学习。看完了这个表格,相信大家对这些逻辑运算都有了一定的了解。看完了逻辑代数基础,接 下来我们再来看下组合电路基础。 §1.2 组合电路基础 1.2.1 组合电路的分析与设计 说完了逻辑代数基础知识,下面我们就来讲解数字电路的第二部分组合电路基础知识。组合 电路的基本问题是逻辑分析和逻辑设计,逻辑分析,是根据给定的组合电路逻辑图,确定其逻辑 功能,即找出输出与输入之间的逻辑关系。例如在推敲某个逻辑电路的设计思想;或者评价某个 逻辑电路技术指标的合理性;或者进行产品仿制、维修和改进时,分析的过程是很重要的。下面 我们给出组合电路的分析方法流程图,如图 1.3 所示。 组合逻辑 电路 逻辑表达式 化简 变换 最简表达式 真值表 图 1.3 组合电路的分析方法 逻辑功能 说完了逻辑分析,接下来我们再来看下逻辑设计,逻辑设计或称逻辑综合是逻辑分析的逆过 程,即根据给定的逻辑功能要求,确定一个能实现这种功能的最简逻辑电路。下面我们给出组合 电路的设计方法流程图,如图 1.4 所示。 实际逻辑 问题 真值表 化简 逻辑表达式 变换 最简(或者最 合理)表达式 图 1.4 组合逻辑电路的设计方法 逻辑图 1.2.2 三人表决器的理论实战 复习完了组合电路的分析与设计,接下来我们就可以动手去设计我们的三人表决器电路了。 首先我们根据实际逻辑问题建立真值表,这里我们设 A、B、C 分别代表参加表决的三个输入变 量,L 为表决结果。规定变量 A=1、B=1、C=1 表示赞成,反之表示不赞成;L=1 表示多数赞成, 即通过,反之表示不通过。故可列出真值表,如表 1.2 所示。 http://www.fpga.gs/ 6 项目实战篇 §1 表 1.2 三人表决器真值表 A B C L 0 0 0 0 0 0 1 0 0 1 0 0 0 1 1 1 1 0 0 0 1 0 1 1 1 1 0 1 1 1 1 1 接下来我们便可以通过真值表写出函数表达式,我们将 L=1 的结果分别取出并相或即可组 成函数表达式,L  ABC  ABC  ABC  ABC ,得出了函数表达式我们需要将函数表达式进 行化简,我们可以用公式法化简也可以用卡诺图法化简,由于的函数表达式比较简单,这里我们 就利用公式化进行化简,我们利用并项法 A A1 将上述函数表达式进行化简, L  (A  A)BC  A(B  B)C  AB(C  C) ,最后化简得出 L  BC  AC  AB 。 根据最后化简的函数表达式,我们画出三人表决器的逻辑图,逻辑图如图 1.5 所示。 A B & C & ≥1 L & 图 1.5 三人表决器的逻辑图 至此,我们的三人表决器就设计完成了,接下来我们就可以根据三人表决器的真值表、函数 表达式,以及逻辑电路图来写出其相对应的 Verilog 代码。 §1.3 Verilog基础 在写出三人表决器的 Verilog 代码之前,我们先来复习一下 Verilog 的基础知识。用 Verilog 描述的数字逻辑电路也被称为 HDL 建模。模块是 Verilog 的基本描述单位,用于描述每个设计 的功能或结构,以及与其他模块通信的外部接口。在 Verilog 中大约使用 100 个预定义的关键词 定义该语言的结构,Verilog 使用一个或多个模块对数字电路建模,模块代表硬件上的逻辑实体, 其范围可以从简单的门到整个大的系统,比如一个计数器、一个存储子系统、一个微处理器等。 一个模块可以包括整个设计模块或者设计模型的一部分,模块的定义总是以关键词 module 开 始,以关键词 endmodule 结尾,它的一般语法结构如代码 1.1 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §1 三人表决器设计分析 7 代码 1.1 一般的 Verilog 语法结构 1 module 模块名 2( 3 端口名 1,端口名 2,端口名 3,… 4 ); 5 端口类型说明(input,output,inout); 6 参数定义(可选); 7 数据类型定义(wire,reg 等); 8 9 //----------以上为描述接口说明部分----------// 10 //----------以下为描述逻辑功能部分----------// 11 12 实例引用低层次模块和基本门级元件; 13 连续赋值语句(assign); 14 过程赋值语句(initial 和 always) 15 功能描述语句; 16 任务和函数; 17 18 endmodule “模块名”是模块唯一的标示符,括号中以逗号分隔列出的端口名是该模块的输入、输出。 接着是模块的说明部分,“端口类型说明”为 input(输入)、output(输出)、inout(双向端口)三者之 一,凡是在模块名后面圆括号中出现的端口名都必须明确地说明其端口类型。“参数定义”是将 常量用符号常量代替,以增加程序的可读性和可修改性,它是一个可选择的语句,用不到可以省 略。“数据类型定义”部分用来指定模块内所用的数据对象是寄存器类型(reg 等)还是连线类型 (wire 等)。接下来,要描述模块实现的逻辑功能。通常可以使用三种不同描述方式: (1) 是使用实例化低层次模块的方法,即调用其他已定义过的低层次模块对整个电路的功 能进行描述,或者直接调用 Verilog 内部预先定义的基本门级元件描述电路的结构,通 常将这种方法称为结构化描述方式; (2) 是使用连续赋值语句(assign)对电路的逻辑功能进行描述,通常称之为数据流描述方式, 该方式特别便于对组合逻辑电路建模; (3) 是使用过程块语句结构(always)和比较抽象的高级程序语句对电路的逻辑功能进行 描述,通常称为行为描述方式。 我们可以选用这几种方式中的任一一种,或者混合使用几种方式描述电路的逻辑功能,也就 是说,在一个模块中可以包含连续赋值语句、always 语句和结构型描述方式,并且这些描述方 式在程序中排列的先后顺序是任意的。复习完了 Verilog 的基础知识,接下来我们就可以利用这 三种不同描述方式来描述我们的三人表决器。 1.3.1 结构化描述方式 结构化描述是根据三人表决器的逻辑图编写而成。为了方便读者对照比较,我便将三人表决 器的逻辑图复制到此处,如图 1.6 所示。 http://www.fpga.gs/ 8 项目实战篇 §1 A B & C & ≥1 L & 图 1.6 三人表决器的逻辑图 下面我们给出结构化描述方式的 Verilog 代码,如代码 1.2 所示。 代码 1.2 结构化描述方式 1 module A4_Vote1 //模块名 A4_Vote1,即模块的开始,方法 1 2( 3 //输入端口 4 A,B,C, 5 //输出端口 6 L 7 ); 8 9 input A; //模块的输入端口 A 10 input B; 11 input C; 12 output L; //模块的输入端口 B //模块的输入端口 C //模块的输出端口 L 13 14 wire AB,BC,AC; //内部信号声明 AB,BC,AC 15 16 and U1(AB,A,B); //与门(A,B 信号进入)(A 与 B 信号即 AB 输出) 17 and U2(BC,B,C); 18 and U3(AC,A,C); //与门 同上 //与门 同上 19 20 or U4(L,AB,BC,AC); //或门 同上 21 22 endmodule //模块的结束 由于代码比较简单,并且我们在代码中也给出了详细的注释,所以我们这里就不在进一步进 行讲解了。如果你对结构化描述方法还不太理解,那么你可以参考我们的《硬件语法篇》,该篇 中有对结构化描述方法详细的讲解。下面我们给出一些关于结构化描述方式的 Verilog 拓展小知 识:Verilog HDL 内置 26 个基本元件,其中 14 个是门级元件,12 个为开关级元件,我们只介绍 了 7 个基本门,and(与)、nand(与非)、or(或)、nor(或非)、not(非)、xor(异或)、nxor (同或)。 1.3.2 数据流描述方式 Zircon Opto-Electronic Technology CO.,Ltd. §1 三人表决器设计分析 9 数据流描述方式是根据三人表决器的逻辑函数编写而成。为了方便读者对照比较,同样我便 将三人表决器的逻辑函数复制到此处,逻辑函数如下所示。 L  BC  AC  AB 下面我们给出数据流描述方式的 Verilog 代码,如代码 1.3 所示。 代码 1.3 数据流描述方式 1 module A4_Vote2 //模块名 A4_Vote2,即模块的开始 2( 3 //输出端口 4 A,B,C, 5 //输入端口 6 L 7 ); 8 9 input A; //模块的输入端口 A 10 input B; //模块的输入端口 B 11 input C; 12 output L; //模块的输入端口 C //模块的输出端口 L 13 14 assign L = (B && C) || (A && C) || (A && B); 15 16 endmodule //模块的结束 由于代码比较简单,并且代码中也给出了详细的注释看,所以我们同样也不在进一步进行讲 解。如果你对数据流描述方法还不太理解,那么你可以参考我们的《硬件语法篇》,该篇中有对 数据流描述方法详细的讲解。下面我们给出一些关于数据流描述方式的 Verilog 拓展小知识: (1) input、output 和 inout 默认类型是 wire 型,连续赋值 assign 语句是针对线网变量的一 种赋值语句,线网变量一般对应到 FPGA 中的一段连线,而连线的值时会随着它的驱 动源的变化而不停变化的,因此也就称之为连续赋值语句。对应于实际的数字电路,线 网类型实际上就对应着硬件的连线,起到连接作用。线网数据类型包括很多个子类型, wire 是线网中最常用的一种数据类型,wire 型数据常用来表示用以 assign 关键字指定 的组合逻辑信号。 (2) Verilog HDL 中绝大多数运算操作符都是可综合的:+(加)、-(减)、!(逻辑非)、~ (取反)、&(与)、~&(与非)、|(或)、~|(或非)、^(异或)、~^(同或)、*(乘)、 /(除)、%(取模)、<<(逻辑左移)、>>(逻辑右移)、<(小于)、<=(小等于)、>(大 于)、>=(大等于)、==(等于)、!(逻辑不等于)、&&(逻辑与)、||(逻辑或)。 1.3.3 行为级描述方式 行为级描述方式是根据三人表决器的真值表编写而成。为了方便读者对照比较,同样我便将 三人表决器的真值表复制到此处,如表 1.2 所示。 http://www.fpga.gs/ 10 项目实战篇 §1 表 1.3 三人表决器真值表 A B C L 0 0 0 0 0 0 1 0 0 1 0 0 0 1 1 1 1 0 0 0 1 0 1 1 1 1 0 1 1 1 1 1 下面我们给出行为级描述方式的 Verilog 代码,如代码 1.4 所示。 代码 1.4 行为级描述方式 1 module A4_Vote3 //模块名 A4_Vote3,即模块的开始 2( 3 //输入端口 4 A,B,C, 5 //输出端口 6 L 7 ); 8 9 input A; 10 input B; 11 input C; //模块的输入端口 A //模块的输入端口 B //模块的输入端口 C 12 output reg L; //模块的输出端口 L 13 14 always @ (A,C,B) //always 在组合逻辑中的用法 15 begin //always @ (A,B,C)解析:只要 A,B,C 16 case({A,B,C}) //其中有一个信号有变化便会执行 begin 中的 case 语句 17 3'b000: L = 1'b0; //也可以写成 always @ (*),与 18 3'b001: L = 1'b0; //always @ (A,B,C)功能相同 19 3'b010: L = 1'b0; //{A,B,C}解析:把 A,B,C 三条线合成一条总线 20 3'b011: L = 1'b1; //举例说明:{1'b1,1'b0}=2'b10 21 3'b100: L = 1'b0; 22 3'b101: L = 1'b1; 23 3'b110: L = 1'b1; 24 3'b111: L = 1'b1; 25 default:L = 1'bx; //不要省略 26 endcase //case 语句的结束 27 end //begin 语句的结束 28 29 endmodule //module 语句的结束 Zircon Opto-Electronic Technology CO.,Ltd. §1 三人表决器设计分析 11 如果你对行为级描述方法还不太理解,那么你可以参考我们的《硬件语法篇》,该篇中有对 行为级描述方法详细的讲解。下面我们给出一些关于行为级描述方式的 Verilog 拓展小知识: always 和 assign 相对来说,还是 assign 比较容易理解,这里我将 always @ (A,B,C)详细 讲解一下,@是事件等待语句,意思是 always 不断循环等待 A、B 和 C 三个敏感变量变化,不 管 A、B 和 C 是从高变低,还是从低变高,都将会执行 always 下面的 begin……end 中的语句。 如果 A、B 和 C 都没有变化,那么 always 也将不往下执行,将一直循环等待。新标准可以这样 书写 always @ (*),其解释如下:always @ (*)是个组合逻辑电路的描述方式;是为了防止你在 设计时考虑不周全带来一些操作失误,所以敏感表用*(表示全部的敏感变量),只要有任何输入 信号变化,其输出立即发送变化。 串行语句的执行思路是顺序执行的,一般高级编程语言中的语句执行方式都是顺序执行的, C 语言就是如此,顺序执行的语句更容易帮助我们来表达我们的设计思想,尤其是使描述时序逻 辑变得容易。所以,虽然 FPGA 的设计思想都是并行的,module 中仅支持并行语句的调用,但 是为了方便设计者表达自己的思想,Verilog 中的一些并行语句中的字语句体允许是顺序执行的。 而关键字 begin...end 就被赋予此使命。begin...end 块必须包含至少一个声明语句。在 begin...end 中间的语句都是顺序执行的语句。使用方法如下: case(……) ……: begin …… end ……: begin …… end ……: begin …… end default:……; endcase always @ (posedge clk) begin …… …… …… end if(……) begin …… …… …… end case 语句是一种多分支选择语句,if 语句只有两个分支可供选择,而实际问题中常常需要 用到多分支选择,Verilog 语言提供的 case 语句直接处理多分支选择。至此,三人表决器的 Verilog 代码我们就写完了,接下来我们就可以使用 Quartus II 软件综合三人表决器 Verilog 代码。 §1.4 Quartus基础 想要利用 Quartus II 软件综合三人表决器 Verilog 代码,我们需要完成下面的 Quartus II 软 件的使用流程,如图 1.7 所示。 打开软件 新建工程 选择器件 输入设计 编译工程 查看RTL 图 1.7 Quartus II 软件设计流程图 分配管脚 配置工程 下载程序 http://www.fpga.gs/ 12 项目实战篇 §1 从图 1.7 中我们可以知道,打开 Quartus II 软件第一件事就是新建一个工程,在 Quartus II 软件中新建工程还是比较简单的,Quartus II 软件提供了一个新建工程向导,我们只需要跟着向 导一步步走,就可以完成整个工程的建立。工程建立完毕以后,我们需要新建一个 Verilog 顶层 文件,然后将三人表决器的 Verilog 代码输入到新建的 Verilog 顶层文件中,输入完成以后,我 们就可以编译,这时,Quartus II 软件会检查你输入的代码,如果代码出现错误,那么 Quartus II 软件将会在控制台中给出错误信息,如果代码没有错误,那么 Quartus II 软件将会显示编译完 成,编译完成后,我们还需要给工程分配管脚、配置工程。最后,我们通过下载工具将编译生成 的.sof 文件下载至开发板完成整个开发流程。在这里,我们只是简单的介绍了一下上述的流程图, 目的是为了让大家对每个流程做到心中有数,接下来我们将会对每个流程进行详细的操作演示。 1.4.1 新建工程 双击计算机桌面上的 Quartus II 13.1 (64-bit)图标(如果安装的是 32 位程序应选择 Quartus II 13.1 (32-bit)图标),打开 Quaruts II 软件。Quartus II 软件主界面如图 1.8 所示,第一次打开软 件,通常默认由菜单栏、工具栏、工程文件导航窗口、编译流程窗口、主编辑窗口以及各种输出 打印窗口组成。 图 1.8 Quartus II 软件主界面 下面要新建一个工程,在这之前建议在硬盘中专门建立一个文件夹用于存储自己的 Quartus II 工程,这个工程目录的路径名应该只有字母、数字和下划线,以字母为首字符,且不要包含中 文和其他符号。在菜单栏上选择【File】→【New Project Wizard】,首先弹出了“Introduction” 页面,单击【Next】按钮进入“Directory,Name,Top-Level Entity”页面,如图 1.9 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §1 三人表决器设计分析 13 图 1.9 新建工程向导的文件名和路径设置 在该页面中,我们需要选择一个文件夹用于存储后续工程生成的所有相关文件,如这里将本 实例的工程存储在了一个名叫“A4_Vote1”的文件夹下,“What is the name of this project”下 输入工程名,“What is the name of the top-level design entity for this project”下输入工程顶层 设计文件的名字。通常建议工程名和工程顶层文件保持一致,如这里统一命名为“A4_Vote1”。 设置完毕后,我们单击【Next】按钮,进入新建工程向导中的第二个页面,在第二个页面中,我 们可以将已经设计好的工程文件添加至我们工程中,由于我们这里是一个全新的工程,所以并没 有什么文件可以添加,我们直接点击【Next】按钮进入下一个页面,如图 1.10 所示页面。 http://www.fpga.gs/ 14 项目实战篇 §1 图 1.10 器件选择页面 在该页面中,我们在“Family”中选择“Cyclone IV E”系列,然后在“Available devices” 中选择具体型号“EP4CE10F17C8”。由于我们开发板主芯片用的“EP4CE10F17C8”,所以我 们这里要选择和开发板主芯片一样的型号。接着我们单击【Next】按钮进入下一个页面。如图 1.11 所示。 图 1.11 EDA 工具设置页面 在该页面中,由于我们只用到仿真工具,所以我们将“Simulation”设置为“Modelsim-Altera”, “Verilog HDL”即可。完成这个页面的配置后,可用单击【Next】按钮继续进入下一个页面查 看并核对页面设置的结果,也可以直接单击【Finish】按钮完成工程创建。 1.4.2 输入设计 工程创建完成以后,下面我们就来创建 Verilog 顶层文件,我们找到菜单栏【File】→【New】 按钮并点击,则弹出如图 1.12 所示的新建文件窗口。 Zircon Opto-Electronic Technology CO.,Ltd. §1 三人表决器设计分析 15 图 1.12 新建 Verilog 文件窗口 在该窗口中,我们选择“Verilog HDL File”并单击【OK】按钮完成 Verilog 文件创建。在 创建好的 Verilog 文件中,我们就可以将三人表决器 Verilog 代码输入至我们的 Verilog 文件中。 这里需要我们注意的是,前面三种描述方式描述的 Verilog 都是可以使用的,这里我们就以结构 化描述方式 Verilog 代码为例,如图 1.13 所示。 图 1.13 三人表决器的 Verilog 代码 代码 输 入 完成 以 后 ,接 下 来我 们 就 可以 编 译 工程 了 ,我 们 找 到 【 Processing 】→ 【 Start http://www.fpga.gs/ 16 项目实战篇 §1 Compilation】按钮并点击,这时,Quartus II 软件会自动编译我们的工程,我们可以通过 Quartus II 软件界面中的右下角来查看编译进度。在编译过程中,我们可以通过信息打印窗口来查看错误 (Error)和警告(Warning)信息,如图 1.14 所示。 图 1.14 信息提示窗口 1.4.3 查看 RTL 编译完成以后,下面我们就可以查看 RTL 了,在查看 RTL 之前,我们先来简单的介绍一下 什么是 RTL?RTL(Register Transfer Level,即寄存器传输级),是用来描述专用的数字电路, 抽象层次相对较低。虽然行为综合可以综合算法级别的代码,但实际上,大多数的设计团队只使 用 RTL 综合工具,这是因为他们必须学会像硬件工程师一样思考,才能编写高效,可综合的 RTL 代码。在综合结束后,设计者经常会希望看到综合后的原理图,以分析综合结果是否与所设想中 的设计一致,这样就会用到“RTL Viewer”和“Technology Map Viewer”这两个工具。 (1) RTL Viewer:是编译后的结果,反映的是模块之间的连接,显示的图形都是调用标准 单元的结果,跟工艺库,FPGA 类型,都没有关系。 (2) Technology Map Viewer:是已经映射到 FPGA 器件的,可以直接看到内部门电路的 连接。 如果只是想看下代码的 RTL 结构是什么样的,就看“RTL Viewer”,如果想看映射到 FPGA 板子上是什么结构,就看“Technology Map Viewer”。这里我们在 Quarttus II 软件菜单栏中找 到【Tools】→【Nelist Viewers】→【RTL Viewer】按钮并点击打开,弹出如图 1.15 所示页面; Zircon Opto-Electronic Technology CO.,Ltd. §1 三人表决器设计分析 17 图 1.15 RTL Viewer 窗口 从该图中我们可以看到,这就是我们使用 Verilog 描述的三人表决器电路,由于我们是根据 三人表决器逻辑电路来描述的,所以 RTL Viewer 中的电路和我们三人表决器的电路是一样的。 因此,我们可以确定我们的 Verilog 代码是正确无误的。 查看完了 RTL 视图,接下来我们就需要给工程分配管脚了,这里需要注意的是,由于我们 的三人表决器现在只是一个功能模块,并没有使用按键、LED 和数码管外设,所以我们没有管 脚可以分配。接下来的步骤我们也不需要再进行了,即使我们进行下去,下载至 A4 开发板上也 是看不到任何效果的。为了能够验证我们的三人表决器模块,接下来我们就通过 ModelSim 仿真 软件来对三人表决器模块进行仿真验证。 §1.5 ModelSim仿真 首先我们找到菜单栏中的【Processing】→【Start】→【Start Test Bench Template Writer】 按钮并点击,这时,Quartus II 软件会自动创建一个仿真文件。我们可以在工程路径下的 \simulation\modelsim 文件夹中找到该仿真文件,如图 1.16 所示。 图 1.16 找到 A4_Vote1.vt 仿真文件 在该图中我们可以看到,我们的仿真文件名叫 A4_Vote1.vt ,我们可以直接用记事本打开 “A4_Vote1.vt”并进行修改,也可以将该文件拖到 Quartus II 窗口中进行修改,也可以用 Quartus II 菜单栏中的【File】→【Open】选中“A4_Vote1.vt ”进行打开并修改。我们将仿真文件代码 修改如代码 1.5 所示。 http://www.fpga.gs/ 18 项目实战篇 §1 代码 1.5 修改完成后的仿真文件代码 1 `timescale 1 ps/ 1 ps 2 module A4_Vote1_vlg_tst(); 3 // constants 4 // general purpose registers 5 reg eachvec; 6 // test vector input registers 7 reg A; 8 reg B; 9 reg C; 10 // wires 11 wire L; 12 13 // assign statements (if any) 14 A4_Vote1 i1 ( 15 // port map - connection between master ports and signals/registers 16 .A(A), 17 .B(B), 18 .C(C), 19 .L(L) 20 ); 21 initial 22 begin 23 //监控 Y 信号的变化,如果其值发生变化,马上打印出来 24 $monitor($time,"Y vaule = %b\n",L); 25 26 //激励信号产生 27 A = 1'b0;B = 1'b0;C = 1'b0; 28 //等待 1000ps = 1ns 29 #1000;A = 1'b0;B = 1'b0;C = 1'b1; 30 //等待 1000ps 31 #1000;A = 1'b0;B = 1'b1;C = 1'b0; 32 //等待 1000ps 33 #1000;A = 1'b0;B = 1'b1;C = 1'b1; 34 //等待 1000ps 35 #1000;A = 1'b1;B = 1'b0;C = 1'b0; 36 //等待 1000ps 37 #1000;A = 1'b1;B = 1'b0;C = 1'b1; 38 //等待 1000ps 39 #1000;A = 1'b1;B = 1'b1;C = 1'b0; 40 //等待 1000ps 41 #1000;A = 1'b1;B = 1'b1;C = 1'b1; 42 43 #1000; Zircon Opto-Electronic Technology CO.,Ltd. §1 三人表决器设计分析 19 44 $stop; 45 end 46 endmodule 完成仿真文件代码编写以后,接下来我们返回到 Quartus II 软件中对仿真进行设置,我们找 到菜单栏中的【Assigement】→【Settings】按钮并点击,在弹出的窗口中选择“Category”下 的【EDA Tool Setting】→【Simulation】,如图 1.17 所示。 图 1.17 仿真设置页面 在该页面中,我们选中“Compile Test bench”后,单击后面的【Test Benches】按钮则弹 出如图 1.18 所示页面。 http://www.fpga.gs/ 20 项目实战篇 §1 图 1.18 添加仿真文件 在“Test Benches”窗口(上面的窗口)中,我们单击【New】按钮,接着弹出图 1.18 所 示的“New Test Bench Settings”窗口(下面的窗口)。在此窗口中,我们根据 A4_Vote1.vt 仿 真文件输入“Test bench name”和 “Top level module in test bench”的名称。接着我们还要 在“Test bench and simulation files”中将仿真文件 A4_Vote1.vt 添加进来,如图 1.19 所示。 图 1.19 已经选中仿真文件 在该窗口中,我们点击【Add】将仿真文件 A4_Vote1.vt 添加至该窗口中,接下来我们就可 以一路点击【OK】完成仿真配置。完成仿真配置以后,下面我们就可以进行仿真了。我们找到 Zircon Opto-Electronic Technology CO.,Ltd. §1 三人表决器设计分析 21 菜单栏中的【Tools】→【Run Simulation Tool】→【RTL Simulation】按钮并点击,随后“ModelsimAltera”便启动,如图 1.20 所示。这是“Modelsim-Altera”软件的工作界面。 图 1.20 ModelSim 仿真界面 下面我们给出仿真波形的特写,如图 1.21 所示。 图 1.21 ModelSim 仿真波形 另外,我们也可以查看打印的信息,如图 1.22 所示。 http://www.fpga.gs/ 22 项目实战篇 §1 图 1.22 ModelSim 信息打印窗口 从仿真波形窗口和信息打印窗口中我们可以看出,当 ABC 为 000,001,010,100 时,L 均为 0,当 ABC 为 011,101,110,111 的,L 为 1。因此,我们可以确定,我们的三人表决器电路功能也 是正确的。至此,我们就完成了三人表决器的仿真。 Zircon Opto-Electronic Technology CO.,Ltd. 三人表决器设计实战 第二章 三人表决器设计实战 通过第一章节的学习,我们设计出了三人表决器的真值表、函数表达式和逻辑电路图,写出 了三人表决器的 Verilog 代码,完成了 Quartus II 软件工程创建和代码综合,以及实现了 MdelSim 仿真验证,唯一遗憾的是我们没能将程序下载至 A4 开发板,之所以我们没有将程序下载至 A4 开发板,这是因为我们还没有学习如何使用 LED、按键和数码管这三个外设。为了弥补这一遗 憾,我们将会在本章节中学习如何使用 LED、按键和数码管这三个外设,以及学习如何使用这 三个外设完成三人表决器设计。如图 2.1 所示,这是我们三人表决器总体设计学习框图。 基础外设学习 LED外设 按键外设 数码管外设 功能概述 设计说明 源码解析 板级调试 完成三人表决器 图 2.1 三人表决器总体设计学习框图 从该图中我们可以看出,首先我们需要学习的是 LED 外设,紧接着我们还要学习按键和数 码管外设,每一个外设我们都会从功能概述、设计说明、源码解析和板级调试,这四个方面进行 学习。当这三个外设学习完以后,最终我们就能够完成三人表决器的设计,在 A4 开发板上实现 三人表决器的功能。 §2.1 LED外设 2.1.1 功能概述 LED 外设可以说是我们所有外设中最简单的一个外设了,虽然 LED 外设操作简单,但是 LED 的却能起到大作用。通过 LED 外设的学习,我们可以很直观的了解到如何使用 Verilog 硬 件描述语言控制 LED 亮灭这一过程。不仅仅如此,我们还可以通过控制 LED 获得以下四条信 息: (1) 检测 LED 是否能够正常工作; (2) 检测 FPGA 是否能够正常工作; (3) 程序下载完成后,检测 FPGA 是否能够正常运行; (4) 将程序固话到 EPCS 中,检测 FPGA 是否能够正常加载; 2.1.2 设计说明 在这里第一步新建工程和第二步设计输入我们就不在进行详细介绍了,如果还有不是很熟 悉的读者可以参考前面 1.4 Quartus 基础章节,新建工程,命名为“A4_Led1”,把这个工程放 26 项目实战篇 §2 在专门的文件夹下,新建 Verilog 源文件,命名为“A4_Led1.v”,输入设计代码,综合编译并验 证设计的正确性。 完成代码设计后,可以选择菜单栏【Assignments】→【Pin Planner】对工程的 I/O 引脚进 行分配,“Pin Planner”界面如图 2.2 所示。如果“Node Name”一列里没有出现顶层文件的信 号,那么建议先对整个工程执行一次编译。 图 2.2 Pin Planner 管脚分配图 这里我们将会提供 FPGA 的管脚分配 pdf 文件和 tcl 文件,通过原理图也可以找到 LED 指 示灯所连接的 I/O 引脚,将它们对应的引脚号输入到对应信号名的“Location”中,如图 2.3 所 示。 图 2.3 LED 外设管脚分配完成图 分配好管脚以后,我们需要对整个工程进行一次全编译。编译完成后会在主界面中弹出工程 的编译报告,如图 2.4 所示,该报告的左侧罗列了各个阶段的各种设计相关报告索引,右侧则显 示具体内容,默认的“Flow Summary”报告中则给出了该设计的最主要的一些资源利用情况。 Zircon Opto-Electronic Technology CO.,Ltd. §2 三人表决器设计实战 27 图 2.4 编译完成后的报告总结图 2.1.3 源码解析 本实例代码中设计了 8 个输出端口,分别对应的是 LED0~LED7,也即是开发板上 LED 的 D1~D8,只需要给输出端口赋值 1(即高电平),赋值 0(即低电平),便能实现开发板 LED 的亮 和灭,功能框如图 2.5 所示。 LED0 FPGA芯片 …… LED7 LED外设 图 2.5 LED 的逻辑设计功能框图 下面我们给出该实例的 Verilog 代码,如代码 2.1 所示。 代码 2.1 LED 外设的 Verilog 代码 1 module A4_Led1 //方法 1,模块的开始 2( 3 //输出端口 4 LED0,LED1,LED2,LED3,LED4,LED5,LED6,LED7 5 ); 6 7 output LED0,LED1,LED2,LED3,LED4,LED5,LED6,LED7; 8 9 assign LED0 = 1'b1; //给 LED0 端口赋值 1'b0,相信有不少读者会对 1'b0 感兴趣 10 assign LED1 = 1'b0; //为什么要写 1'b0,不直接写 0, 11 assign LED2 = 1'b0; //1'b0 是指用的 1 位二进制,而 0 则等于 32'd0, 12 assign LED3 = 1'b0; //只有数字没有进制默认就为十进制,并且位宽默认为 32 位, 13 assign LED4 = 1'b1; //尽管 1'b0 和 32'd0 结果是相同的, 14 assign LED5 = 1'b0; //但是用十进制表示的话会浪费资源, http://www.fpga.gs/ 28 项目实战篇 §2 15 assign LED6 = 1'b0; //因为我们的资源是有限的, 16 assign LED7 = 1'b0; //所以节约资源从细节做起,养成良好的编程习惯。 17 18 endmodule //模块的结束 19 20 //module A4_Led1 //方法 2,模块的开始 21 //( 22 // LED //输出端口的声明 23 //); 24 // output [7:0] LED; 25 // //LED[0]=1'b1,LED[1]=1'b0,LED[2]=1'b0,LED[3]=1'b0, 26 // //LED[4]=1'b1,LED[5]=1'b0,LED[6]=1'b0,LED[7]=1'b0, 27 // assign LED = 8'b0001_0001; 28 // 29 //endmodule //模块的结束 30 // 31 //这里要注意的是,不同的方法,管脚分配名称也是不一样的 以上两种方法实现的功能相同,代码相对比较简单,注释给出了详细说明,这里我就不多做 解释,有一点要强调说明,不同的方法,在引脚分配时是不同的,比如方法 1 中引脚分配名是 LED0,而方法 2 中引脚分配却是 LED[0],这两个还是有区别的。看到这里,也许有的读者就会 有这么一个疑问:怎么才能分辨给 LED 赋值 1’b0 亮,还是给 1’b1 亮。这里可不是软件能控制了 的,这里要看硬件上的设计,要看原理图才能知道,A4 开发板上的 LED 原理图,如图 2.6 所 示。 图 2.6 LED 的原理图 从该图中我们可以看出,LED 的一端接到 VCC3V3 电源上面,当 LEDX 输出为高电平时, 没有电流流过 LED,LED 不亮;当 LEDX 输出为低电平时,有电流流过 LED,LED 点亮。因 此,可以通过程序控制 FPGA 上与 LEDX 管脚相连的 IO 的输出电平来使 LED 点亮和熄灭。(这 里的 X 代表 1、2、3、4、5、6、7、8)。 Zircon Opto-Electronic Technology CO.,Ltd. §2 三人表决器设计实战 29 2.1.4 板级调试 由于我们这里可以直接上板调试并验证设计的正确性,就不在进行仿真调试。直接单击工具 栏上的【Programmer】按钮或者选择菜单栏【Tools】→【Programmer】,进入下载界面,如图 2.7 所示。 图 2.7 Programmer 下载窗口界面 连接好“USB Blaster”(PC 和开发板),连接好电源,按下电源开关上电。然后确认设备管 理器中通用串行总线控制器下是否有“Altera USB-Blaster”设备,如果没有则检查“USBBlaster”设备是否正确安装驱动,然后在“Programmer”下确认 Quartus II 是否识别了“USB Blaster”下载器。若没有识别,则按照如图 2.8 所示,单击左上角的【Hardware Setup】按钮 建立连接。 http://www.fpga.gs/ 30 项目实战篇 §2 图 2.8 USB-Blaster 设备连接图 前 面 的 步 骤 都 确 定 好 , 我 们 就 可 以 点 击 【 add File 】, 找 到 “ output_files ” 目 录 下 的 “A4_Led1.sof”,将下载文件添加进来即可,如图 2.9 所示,直接单击右侧的【Start】按钮就可 以启动下载操作,观察右上角的“Progress”是否会从 0 变化到 100%。 图 2.9 设备连接完成图 当我们点击 Start 按钮,将 A4_Led1.sof 文件下载到 A4 开发板上后,我们可以看到 A4 开 发板上的 D1 和 D5 是熄灭的,其余的 LED 都是点亮的,如图 2.10 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §2 三人表决器设计实战 31 §2.2 按键外设 图 2.10 LED 外设板级调试图 2.2.1 功能概述 按键检测处理是 FPGA 嵌入式设计学习开发的基本功,必须很好地学习掌握按键处理技术。 在 FPGA 为主控的应用系统中,按键主要有两种形式:独立式按键和矩阵式按键。独立式按键 的每个按键都单独接到 FPGA 的一个 I/O 口上,通过判断按键端口的电位即可识别按键操作; 而矩阵式按键通过行列交叉按键编码进行识别。我们 A4 开发板上使用的便是独立式按键,其原 理图,如图 2.11 所示。 http://www.fpga.gs/ 32 项目实战篇 §2 图 2.11 A4 开发板上的按键原理图 从该图中可以看出,如果我们按下按键,那么按键就会接通并连接到高电平 VCC3V3,如 果我们没有按下,那么按键就会断开并接地,其实现原理主要是通过轻触按键内部的金属弹片受 力弹动来实现接通和断开的。从上面的原理图中我们还可以看到,我们不仅仅只有轻触按键,我 们还高大上的触摸按键。触摸式按键根据它们不同的工作原理可分为两大类:电阻式触摸按键与 电容式感应按键。电阻式的触摸按键原理非常类似于触摸屏技术,需要在多块导电薄膜上面按照 按键的位置印制成,因此这种按键需要在设备表面贴一张触摸薄膜。电阻式触摸屏一直由于其低 廉的价格而深受厂商的喜爱,但是由于导电薄膜的耐用性较低,并且也会降低透光性,因此已经 被越来越多的厂家所抛弃。电容式触摸按键主要是为了克服电阻式触摸按键的耐用性所提出的, 电容式触摸按键的结构与电阻式相似,其原理是利用人体的感应电容来检测是否有手指存在,在 没有手指按下时,按键上由于分布电容的存在,因此按键对地存在一定的静态电容,当人的手指 按下或者接近按键时,人体的寄生电容将耦合到这个静态电容上,使按键的最终电容值变大,该 变化的电容信号再输入到单片机进行信号转换,将变化的电容量转换成某种电信号的变化量,再 由一定的算法来检测和判断这个变化量的程度,当这个变化量超过一定的域值时就认为手指按 下。在没有手指按下时,各按键对地有一个等效的静态电容,当有手指按下时,人体电容就叠加 到按键上,使按键对地的电容增加,这样获得的电容再输入到芯片中进行处理。简单来说,就是 一个 IC 控制的电路,该电路包括一个能放置在任何介质面板上的简单阻性环形电极组件,按键 的操作面板可以是一整块普通绝缘体,不需要在面板上挖孔,人手接近面板和下面的电极片形成 电容,靠侦测电容量的变化来感应。温度,静电,水,灰尘等外界因素一般不会影响,整个面板 没有按键的存在,便于清洁,让产品在外观上更加高档美观,由于按键没有接点,使用寿命也是 非常的长久,一般来说是半永久性。我们 A4 开发板上所采用的触摸按键芯片是 TTP226,TTP226 是一款接触板检测(touch pad detector)IC,提供 8 个接触键。这里我们只用到了 4 个,另外的 4 个按键被实体按键取代,对于 TTP226 这个芯片我就不多费口舌拉,想要进一步了解的读者可 Zircon Opto-Electronic Technology CO.,Ltd. §2 三人表决器设计实战 33 以查阅其数据手册。 下面我将利用按键与 LED 联调,实现按键点灯,让大家深刻的感受一下实体按键和触摸按 键的不同之处及不同体验,当然,我们还可以利用触摸按键的先天优势,来实现滑动效果,这里 实体按键是望尘莫及的。 2.2.2 设计说明 新建工程,命名为“A4_Key1”,把这个工程放在专门的文件夹下,其他设置参考 1.3 Quartus 基础章节。新建 Verilog 源文件,命名为“A4_Key1.v”,输入设计代码,综合编译后进行引脚分 配,本例程的引脚分配如图 2.12 所示。 图 2.12 按键外设管脚分配完成图 接下来,对工程进行全编译,不仅要让刚刚添加的引脚分配生效,也要生成可以下载到 FPGA 芯片中的配置文件。 2.2.3 源码解析 本实例代码中设计了 8 个输入端口,分别对应的是触摸按键 KEY1、KEY2、KEY3、KEY4 和实体按键 KEY5、KEY6、KEY7、KEY8。设计了 8 个输出端口,分别对应的是开发板上 LED 的 D1~D8,我们将开发板上的按键通过 FPGA 直接连接到 LED 上,便可实现按下按键点亮 LED 功能。功能框图如图 2.13 所示。 按键外设 KEY0 …… KEY7 FPGA芯片 LED0 …… LED7 LED外设 图 2.13 按键的逻辑设计功能框图 下面我们给出该实例的 Verilog 代码,如代码 2.2 所示。 http://www.fpga.gs/ 34 项目实战篇 §2 代码 2.2 按键外设的 Verilog 代码 1 module A4_Key1 //方法 1 2( 3 //输入端口 4 KEY0,KEY1,KEY2,KEY3,KEY4,KEY5,KEY6,KEY7, 5 //输出端口 6 LED0,LED1,LED2,LED3,LED4,LED5,LED6,LED7 7 ); 8 9 input KEY0,KEY1,KEY2,KEY3,KEY4,KEY5,KEY6,KEY7; 10 output LED0,LED1,LED2,LED3,LED4,LED5,LED6,LED7; //对应开发板上的 KEY //对应开发板上的 LED 11 12 assign LED0 = KEY0; //触摸按键 1 控制 D1 13 assign LED1 = KEY1; //触摸按键 2 控制 D2 14 assign LED2 = KEY2; //触摸按键 3 控制 D3 15 assign LED3 = KEY3; //触摸按键 4 控制 D4 16 assign LED4 = KEY4; //实体按键 1 控制 D5 17 assign LED5 = KEY5; //实体按键 2 控制 D6 18 assign LED6 = KEY6; //实体按键 3 控制 D7 19 assign LED7 = KEY7; //实体按键 4 控制 D8 20 21 endmodule //模块的结束 22 23 //module A4_Mini_Key //方法 2 24 //( 25 // input [7:0] KEY, //输入端口声明 26 // output [7:0] LED //输出端口声明 27 //); 28 // 29 // assign LED = KEY; //8 个按键控制 8 个 LED 30 // 31 //endmodule //模块的结束 这里也可以和 LED 代码一样,也可以用两种方法实现。这里有一点要重点提示一下,我们 根据原理图可以得出按键悬空的时候是 0,按下按键后按键的值就为 1,因此,在没有按下按键 时,所有的按键都为 0,又因为 LED 低电平有效,所以 LED 都是点亮的。想要让按键以相反的 状态工作,那么只需要在每个按键前面进行取反即可。 2.2.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Key1.sof 文件下载到开发板中,接着按下实体按键, 对应的 LED 将会熄灭,松开按键,对应的 LED 将会点亮,我们按下了触摸的第四个按键和实体 的第一个按键,其相对应的 D4 和 D5 也都熄灭,如图 2.14 所示, Zircon Opto-Electronic Technology CO.,Ltd. §2 三人表决器设计实战 35 图 2.14 按键外设板级调试图 当所有的按键都按下,对应的所有的 LED 将会熄灭,这里触摸按键与实体按键相比较会更 有趣,触摸按键可以实现实体按键实现不了的滑键效果,你可以左滑,也可以右滑,摩擦摩擦, 魔鬼的步伐。 §2.3 数码管外设 2.3.1 功能概述 说完了按键外设,接下来我们再来看下数码管外设,数码管是一种价格便宜、使用简单的半 导体发光器件,在电器特别是家电领域应用极为广泛,我们可以通过对其不同的管脚输入相对的 电流,变可以使其发亮,从而能够显示时间、日期和温度等。数码管可分为七段数码管和八段数 码管,区别在于八段数码管比七段数码管多一个用于显示小数点的发光二极管。所谓八段数码管 其实就是将八个发光二极管,a、b、c、d、e、f、g、dp 八段按一定的方式排列起来,利用不同 的组合,来显示不同的阿拉伯数字,如图 2.15 所示。 http://www.fpga.gs/ 36 项目实战篇 §2 g f COM a b a f g b e c d DP e d COM c DP 图 2.15 七段数字显示器及发光段组合图 (a)显示器 (b)段组合图 数码管按各发光二极管电极的连接方式分为共阳数码管和共阴数码管两种,如图 2.16 所示。 COM a b c d e f g DP a b c d e f g DP 图 2.16 半导体数字显示器的内部接法 COM (a)共阳极接法 (b)共阴极接法 从图中我们可以看出,将八段数码管中的每个二极管的阴极并联在一起,组成公共阴极端。 将八段数码管中的每个二极管的阳极并联在一起,组成公共阳极端。如果我们将共阴极管脚接地, 那么此时不管哪个管脚输入高电平,对应发光二极管就被点亮。如果我们将共阳极接高电平,那 么此时不管哪个管脚输入低电平,对应发光二极管就被点亮。我们的 A4 开发板采用的是共阴极 连接方式,原理图如图 2.17 所示。 图 2.17 A4 开发板上的数码管原理图 Zircon Opto-Electronic Technology CO.,Ltd. §2 三人表决器设计实战 37 从该图中可以看出,我们将 6 个数码管的 a~g 及小数点 dp 管脚并联在一起连接到 FPGA 中,作为数码管数据输入端,我们将 6 个数码管的公共端连接到 FPGA 中,作为数码管片选端, 虽然不是名副其实的“片选”,但是还真达到了异曲同工之妙。 简单的介绍完了数码管,下面我们再来看下数码管例程将要实现一个怎样的功能,该例程主 要实现的功能是让 6 个数码管同时显示一个数字。为了方便大家编写代码,我们将共阴极的数 码管编码表,总结如表 2.1 所示。 表 2.1 数码管编码表 数字 编码 数字 编码 数字 编码 数字 编码 0 3FH 1 06H 2 5BH 3 4FH 4 66H 5 6DH 6 7DH 7 07H 8 7FH 9 6FH A 77H B 7CH C 39H D 5EH E 79H F 71H 2.3.2 设计说明 新建工程,命名为“A4_Segled1”,把这个工程放在专门的文件夹下,其他设置参考 1.4 Quartus 基础章节。新建 Verilog 源文件,命名为“A4_Segled1.v”,输入设计代码,综合编译 后进行引脚分配,本例程的引脚分配如图 2.18 所示。 图 2.18 数码管外设管脚分配完成图 接下来,对工程进行全编译,不仅要让刚刚添加的引脚分配生效,也要生成可以下载到 FPGA 芯片中的配置文件。 2.3.3 源码解析 本 实 例 代 码 中 设 计 了 8 个 输 出 端 口 SEG_DATAa , SEG_DATAb , SEG_DATAc , SEG_DATAd,SEG_DATAe, SEG_DATAf, SEG_DATAg,SEG_DATADP 分别对应的是 数码管的 a,b,c,d,e,f,g,DP 八个数据位,即控制数码管显示什么数字。设计了 6 个输 出端口 SEG_EN1,SEG_EN2,SEG_EN 3,SEG_EN 4,SEG_EN 5,SEG_EN 6,分别对 应的是六个数码管上的片选端,即控制数码管的亮和灭。我们通过编写相应代码下载到 FPGA 中,FPGA 管脚输出数据直接到数码管上,便可实现点亮数码管功能。功能框图 2.19 所示。 http://www.fpga.gs/ 38 FPGA芯片 项目实战篇 SEG_DATA[6…0] …… SEG_EN[5…0] §2 数码管外设 图 2.19 数码管的逻辑设计功能框图 下面我们给出该实例的 Verilog 代码,如代码 2.3 代码 2.2 所示。 代码 2.3 数码管外设的 Verilog 代码 1 module A4_Segled1 //模块的开始 2( 3 //数码管数据引脚 4 SEG_DATAa,SEG_DATAb,SEG_DATAc,SEG_DATAd,SEG_DATAe, 5 SEG_DATAf,SEG_DATAg,SEG_DATADP, 6 //数码管使能引脚 7 SEG_EN1,SEG_EN2,SEG_EN3,SEG_EN4,SEG_EN5,SEG_EN6 8 ); 9 10 //将数码管数据位和数码管使能位声明为输出默认类型 wire 11 output SEG_DATAa,SEG_DATAb,SEG_DATAc,SEG_DATAd,SEG_DATAe, 12 SEG_DATAf,SEG_DATAg,SEG_DATADP; 13 output SEG_EN1,SEG_EN2,SEG_EN3,SEG_EN4,SEG_EN5,SEG_EN6; 14 //ouput [6:0] SEG_DATA; SEG_DATA[0]等价于 SEG_DATAa …… 15 SEG_DATA[7]等价于 SEG_DATAg 16 //ouput [5:0] SEG_EN; SEG_EN[0]等价于 SEG_EN1 …… SEG_EN[5]等价于 SEG_EN6 17 18 //数码管显示 0~F 对应段选输出 19 parameter SEG_NUM0 = 8'hbf, //数字 0 20 SEG_NUM1 = 8'h86, //数字 1 21 SEG_NUM2 = 8'hdb, //数字 2 22 SEG_NUM3 = 8'hcf, //数字 3 23 SEG_NUM4 = 8'he6, //数字 4 24 SEG_NUM5 = 8'hed, //数字 5 25 SEG_NUM6 = 8'hfd, //数字 6 26 SEG_NUM7 = 8'h87, //数字 7 27 SEG_NUM8 = 8'hff, //数字 8 28 SEG_NUM9 = 8'hef, //数字 9 29 SEG_NUMA = 8'hf7, //数字 A 30 SEG_NUMB = 8'hfc, //数字 B 31 SEG_NUMC = 8'hb9, //数字 C 32 SEG_NUMD = 8'hde, //数字 D 33 SEG_NUME = 8'hf9, //数字 E Zircon Opto-Electronic Technology CO.,Ltd. §2 三人表决器设计实战 39 34 SEG_NUMF = 8'hf1; //数字 F 35 36 //给数码管数据位赋值,也可以这样写 SEG_DATA[7:0] = SEG_NUM8; 37 这样写是有前提的,要这样声明 output [7:0] SEG_DATA; 38 assign {SEG_DATADP,SEG_DATAg,SEG_DATAf,SEG_DATAe, 39 SEG_DATAd,SEG_DATAc,SEG_DATAb,SEG_DATAa} = SEG_NUM8; 40 //给数码管使能位赋值也可以这样写 SEG[5:0] = 6'b000000; 同上 41 assign {SEG_EN6,SEG_EN5,SEG_EN4,SEG_EN3,SEG_EN2,SEG_EN1} = 6'b000000; 42 endmodule //模块的结束 这里需要我们注意的是: {SEG_EN 6,SEG_EN 5,SEG_EN 4,SEG_EN 3,SEG_EN 2,SEG_EN 1} = 6'b000000; {SEG_EN 1,SEG_EN 2,SEG_EN 3,SEG_EN 4,SEG_EN 5,SEG_EN 6} = 6'b000000; 以上两句如果都赋值 6'b000000 是没有区别的,如果都赋值 6'b100000;区别就呈现出来了。 第一个句子是把 SEG_EN 6 给赋值为 1,而第二个句子是 SEG_EN 1 给赋值为 1。 2.3.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Segled1.sof 文件下载到开发板中,接着我们就能看 到数码管将会被点亮,显示 8.,如图 2.20 所示。 图 2.20 数码管外设板级调试图 §2.4 用外设来实现三人表决器 2.4.1 功能概述 前边基础越牢固,后面学起来就越轻松,因为它们是有一个进阶关系的。首先我们要知道如 http://www.fpga.gs/ 40 项目实战篇 §2 何利用 FPGA 实现三人表决电路,第二,我们要会控制外设 LED、按键和数码管,然后我们只 要将其拼合在一起,就是一个实际应用小工程。做项目思路也是如此,当碰到一个大项目,我们 会将这个大项目分成若干个小模块,然后实现一个个小模块,最终拼合成大工程。我们这里是用 按键作为输入进行人机交互实现投票功能,当按下按键其相对应的 LED 将会点亮,表示该按键 进行了投票。FPGA 实现三人表决电路,数码管是用来显示投票的总票数。 2.4.2 设计说明 新建工程,命名为“A4_Vote4”,把这个工程放在专门的文件夹下,其他设置参考 1.4 Quartus 基础章节。新建 Verilog 源文件,命名为“A4_Vote4.v”,输入设计代码,综合编译后进行引脚 分配,本例程的引脚分配如图 2.21 所示。 图 2.21 外设的管脚分配图 接下来,对工程进行全编译,不仅要让刚刚添加的引脚分配生效,也要生成可以下载到 FPGA 芯片中的配置文件。 2.4.3 源码解析 本实例代码中设计了 2 个输出总线为 SEG_DATA 和 SEG_EN,其中 SEG_DATA 包含 7 个输出端口 SEG_DATAa,SEG_DATAb,SEG_DATAc, SEG_DATAd,SEG_DATAe, SEG_DATAf, SEG_DATAg,分别对应的是数码管的 a,b,c,d,e,f,g 七个数据位,即 控制数码管显示什么数字。SEG_EN 包含 6 个输出端口 SEG_EN1,SEG_EN2,SEG_EN3, SEG_EN4,SEG_EN5,SEG_EN6,分别对应的是六个数码管上的片选端,即控制数码管的亮 和灭,这里我们省略了小数点 DP 数据位。代码中我们还设计了 3 个输入端口 KEY1、KEY2、 KEY3 和 3 个输出端口 LED1、LED2、LED3。我们通过编写相应代码下载到 FPGA 中,FPGA 管脚输出数据直接到数码管上,便可实现点亮数码管功能。功能框图如图 2.22 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §2 三人表决器设计实战 41 KEY1 LED1 按键外设 KEY2 KEY3 FPGA芯片实现三人 表决功能电路 LED2 LED3 LED外设 SEG_EN[5...0] SEG_DATA[6...0] 数码管外设 图 2.22 三人表决器的逻辑功能框图 下面我们给出该实例的 Verilog 代码,如代码 2.4 代码 2.2 所示。 代码 2.4 三人表决器的 Verilog 代码 1 module A4_Vote4 //模块名 A4_Vote4,即模块的开始 2( 3 //输入端口 4 KEY1,KEY2,KEY3, 5 //输出端口 6 LED1,LED2,LED3,SEG_DATA,SEG_EN 7 ); 8 9 input KEY1,KEY2,KEY3; //按键 10 output LED1,LED2,LED3; //LED 11 output [5:0] SEG_EN; 12 output reg [6:0] SEG_DATA; //数码管使能管脚 //数码管数据管脚 13 14 parameter SEG_NUM0 = 7'h3f, //数字 0 15 SEG_NUM1 = 7'h06, //数字 1 16 SEG_NUM2 = 7'h5b, //数字 2 17 SEG_NUM3 = 7'h4f; //数字 3 18 19 always @ (*) //组合电路,实现三人表决器电路(行为描述) 20 begin 21 case({KEY3,KEY2,KEY1}) //检测按键是否按下,按下为 1,悬空为 0 22 3'b000 : SEG_DATA = SEG_NUM0;//当有 0 个按键按下时,数码管就显示数字 0 23 3'b001 : SEG_DATA = SEG_NUM1;//当有 1 个按键按下时,数码管就显示数字 1 24 3'b010 : SEG_DATA = SEG_NUM1;//当有 1 个按键按下时,数码管就显示数字 1 25 3'b011 : SEG_DATA = SEG_NUM2;//当有 2 个按键按下时,数码管就显示数字 2 26 3'b100 : SEG_DATA = SEG_NUM1;//当有 1 个按键按下时,数码管就显示数字 1 27 3'b101 : SEG_DATA = SEG_NUM2;//当有 2 个按键按下时,数码管就显示数字 2 28 3'b110 : SEG_DATA = SEG_NUM2;//当有 2 个按键按下时,数码管就显示数字 2 29 3'b111 : SEG_DATA = SEG_NUM3;//当有 3 个按键按下时,数码管就显示数字 3 http://www.fpga.gs/ 42 项目实战篇 §2 30 default: SEG_DATA = SEG_NUM0; 31 endcase 32 end //case 语句的结束 //begin 语句的结束 2.4.4 板级调试 最后,我们将 Quartus 编译生成的 A4_ Vote4.sof 文件下载到开发板中,接着我们会发现 开发板上的 SEG6 数码管将会点亮显示 0,这是因为没有按键按下,当我们将三个按键全部按 键,可以看到 LED 灯将全亮,数码管也同时显示 3,如图 2.23 所示。 图 2.23 三人表决器板级调试图 Zircon Opto-Electronic Technology CO.,Ltd. 数字时钟设计分析 第三章 数字时钟设计分析 通过前两章的学习,我们完成了三人表决器项目,在学习三人表决器的项目中,不知道大家 有没有发现这么一个问题,那就是我们学的所有的外设都是用的组合电路来实现的,并没有涉及 到时序电路,这主要是因为,时序电路比组合电路会稍微难上一点,我们只有掌握了组合电路, 我们才能够进一步进阶学习时序电路。老规矩,在开始设计我们的数字时钟项目之前,我们先来 看下我们的数字时钟项目划分为哪几个知识点,如图 3.1 所示。 组合逻辑基础 时序逻辑基础 锁存器和触发器 寄存器和计数器 勤能补拙 融会贯通 完成多功能数 字时钟电路 LED进阶 功能概述 设计说明 源码解析 板级调试 按键进阶 数码管进阶 蜂鸣器外设 图 3.1 实现数字时钟的工程框架图 基础外设 进阶学习 通过该框架图我们可以看出,首先我们应该从时序电路基础入手,从而学习锁存器、触发器、 寄存器和计数器这四个最基本的时序电路。对于这部分知识,如果你在之前已经看过了我们的 《数字电路篇》,那么我们相信这些知识你都已经很好的掌握了,当然如果你学习的太久,对这 部分知识有所遗忘,那么你也无需担心,我们将会带领大家进行一个简单的复习,帮助大家巩固 复习这部分知识。当我们学习完了时序电路基础以后,我们就可以进一步学习我们的 LED、按 键、数码管和蜂鸣器这四个外设了,这里我们需要注意的是,我们学习的 LED、按键和数码管这 三个外设,不在是前面那样简单的使用组合电路控制了。这里的这三个外设我们再原有的基础上 增加了难度,将会使用时序电路来控制它们。当我们熟练的能够使用时序电路来控制我们的外设 以后,我们就可以将以上所学的知识进行一个结合,最终实现我们整个数字电路时钟项目。 §3.1 锁存器和触发器 3.1.1 锁存器 通过上面的分析,想必大家对整个项目框架有了一定的了解,下面我们就来学习整个框架的 第一部分时序逻辑基础知识,如图 3.2 所示。 46 项目实战篇 §3 组合逻辑基础 时序逻辑基础 锁存器和触发器 寄存器和计数器 勤能补拙 融会贯通 图 3.2 数字时钟总体分析学习框架图 首先我们要说的是锁存器,锁存器(Latch)是一种对输入脉冲电平敏感的存储电路,它具 有记忆功能,它只在输入脉冲的高电平(或低电平)期间对输入信号敏感并改变状态。在数字电 路中可以记录二进制数字信号“0”和“1”。这里我们就以 D 锁存器为例进行讲解,所谓的 D 锁 存器,就是能够将输入端的单路数据 D 存入到锁存器中的电路,下面我们给出 D 锁存器的电路 图,如图 3.3 所示。 C RD & & Q & Q & D SD 控制门电路 RS锁存器 图 3.3 D 锁存器的电路图 从 D 锁存器的电路图中我们可以看出,该电路主要是由两个部分组成,第一个部分是由 G1 、 G2 两个与非门组成的 RS 锁存器,第二个部分是由 G3 、 G4 两个与非门组成的控制门电路。 C 为控制信号,它被加到了 G3 、 G4 两个与非门的输入端上,用来控制激励信号的输入。 下面我们来分析一下 D 锁存器的工作原理,当控制端 C  0 时,根据与非门的逻辑规律, 无论此时 D 等于什么, RD 和 SD 都同时等于 1,根据由与非门组成的 RS 锁存器的逻辑规律, RD 和 SD 都同时等于 1 的话,锁存器的输出端 Q 将维持原状态不变。那么,当控制端 C  1时, 如果此时 D  0 , SD 就等于 1, RD 就等于 0,根据 RS 锁存器的逻辑规律,电路的结果就为 0 状态;如果 D  1,那么 RD 就等于 1, SD 也就等于 0,锁存器的结果就为 1 状态,也就是说, 此时锁存器的状态是由激励输入端 D 来确定的,并且 D 等于什么,锁存器的状态就是什么,这 就是我们前面所说的,将单路数据 D 存入到锁存器之中。 根据上面的讲解,我们已经知道了 D 锁存器的逻辑功能了。下面我们就根据逻辑功能,来 写出它的特性表,如表 3.1 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §3 数字时钟设计分析 47 表 3.1 D 锁存器的特性表 0 X 0 0 0 X 1 1 1 0 0 0 1 0 1 0 1 1 0 1 1 1 1 1 从 D 锁存器的特性表中我们可以看出,如果 D  0 ,那么 Qn1 就等于 0,如果 D  1,那 么 Qn1 就等于 1。我们根据这里的特性表便可以写出它的特性方程,还是按照老方法,将输出等 于 1 的项提出来后,组成一个与或式,那么这里我们就不一步步的化简了,直接给出化简后的特 性方程: Qn1  D 接下来我们再来画出它的状态图,如图 3.4 所示。 D=1 D=0 0 1 D=1 D=0 图 3.4 D 锁存器的状态图 从状态图中我们可以看出,当 D 在 0 状态时,也就是输入 D  0 时,输出 Qn1 将保持 0 状 态,当输入 D  1时,那么输出 Qn1 将从 0 变为 1。 下面我们画出它的波形图,如图 3.5 所示。 锁存 Q跟随D 锁存 Q跟随D 锁存 图 3.5 D 锁存器的波形图 http://www.fpga.gs/ 48 项目实战篇 §3 从 D 锁存器的波形图图中我们可以看出,D 是锁存器的输入信号,C 是锁存器的控制信号, Q 是锁存器的输出信号,当控制信号 C 为高电平时,输出信号 Q 将跟随输入信号 D 的变化而变 化,大家看虚线内,Q 的波形等于 D 的波形。当控制信号 C 从高电平变为低电平时,输入信号 D 的状态将会决定锁存器将要锁存的状态。大家可以看, C 由高变低的那两条虚线内,所对应 的输入信号 D 为低电平,那么输出信号 Q 也将会锁存低电平。最后面的那两条虚线也同理,D 为高电平, Q 锁存高电平,至此关于 D 锁存器的内容我们就复习啦。 3.1.2 触发器 说完了锁存器,接下来我们再来说一下触发器,触发器(Flip-Flop)是一种对脉冲边沿敏感 的存储单元电路,它也具有记忆功能,它只在触发脉冲的上升沿(或下降沿)瞬间改变其状态。 在数字电路中可以记录二进制数字信号“0”和“1”。这里我们就以 D 触发器为例进行讲解,下 面我们给出 D 触发器的电路图,如图 3.6 所示。 F1 F2 D Q1 Q2 Q CLK 1 1 CLK 2 1 Q CLK 1 1 图 3.6 D 触发器的电路图 从 D 触发器的电路图中我们可以看出,该电路是由两个相同的 D 锁存器以及两个非门连接 而成的,图中的 F1 和 F 2 就是 D 锁存器的电路符号, F1 为主锁存器, F 2 为从锁存器,由于 主锁存器的输出信号 Q1 就是从锁存器的输入信号,因而造成了两个锁存器的主从关系,这两个 锁存器的控制信号都由外部时钟信号 CLK 提供。 下面我们来分析 D 触发器是如何工作的,并且看看它是否能够解决空翻的现象。当 CLK  0 时, CLK 经过非门后直接 F1 的控制信号,那么此时 F1 的控制信号为 1, F1 被选通,处于工 作状态,如果现在输入信号 D 为 1 的话,它经过 F1 , F1 的输出 Q1 将为 1,这里的 Q1 ,不仅 是 F1 的输出信号,也同时是 F 2 的输入信号,不过现在 F 2 的控制信号为 0, F 2 被封锁了, 处于保持状态,输入信号 D 没有办法直接改变输出 Q 的状态,这是前半拍的工作情况,也就是 说,输入信号先存入主锁存器中,而不直接影响输出 Q 的状态。下面我们再来看后半拍,外部的 控制信号 CLK 由 0 变为 1 了,这个 1,经过非门后直接作为 F1 的控制信号,那么此时 F1 的控 制信号为 0,主锁存器 F1 就被封锁了,它的输出 Q1 将保持在当前的状态,即使现在输入信号 D 再发生改变,Q1 的值也不再受影响了。而 F 2 的控制信号 CLK 此时为 1, F 2 处于工作状态, Q1 将会作为 F 2 这个从锁存器的输入信号,直接影响到输出信号 Q 的状态。Q1 为 1,那么根据 D 锁存器的逻辑规律,输出的 Q 将为 1,Q 非将为 0。这就是后半拍的工作情况,在后半拍里我 们才能实现整个电路状态的改变,因此从上面的分析中我们就可以看出,在 CLK 信号由 0 变为 1,这样的一个变化周期内,触发器的输出状态只可能改变一次,这样就克服了锁存器中存在的 Zircon Opto-Electronic Technology CO.,Ltd. §3 数字时钟设计分析 49 空翻现象。 通过 D 锁存器和 D 触发器的学习,细心的读者应该已经发现了,D 锁存器与 D 触发器的逻 辑功能其实是相同的,只不过它们的触发方式有所不同。接下来我们通过将 D 触发器的波形图 与前面 D 锁存器的波形图进行比较,来看一看,它们的触发方式不同在哪里,如图 3.7 所示。 电平触发 边沿触发 主 选通 从 保持 保持 选通 选通 保持 有效 翻转 空翻 空翻 图 3.7 D 锁存器和 D 触发器的波形对比图 大家先看 D 触发器的波形图,D 触发器是在控制信号 CLK 为 0 时,才会接收输入信号 D 的值,并将这个值锁存起来,当控制信号 CLK 变为 1 时,输出信号 Q 才会被改变。那么 D 触发 器,其实就是在 CLK 这个时钟信号由 0 变为 1 的这个边沿进行触发的,通常我们就将这种触发 方式称为边沿触发,通过这种边沿触发方式的 D 触发器我们也将它称为边沿 D 触发器。 D 锁存器的触发方式是电平触发,和我们刚刚讲的边沿触发是有所不同的。这种不同是由于 锁存器和触发器的电路结构不同,而造成的。这里需要注意的是,由于 D 锁存器的功能和 D 触 发器的功能是一样的,所以在编写代码时很容易把 D 锁存器当成 D 触发器来使用,这种情况我 们是应该要极力避免的。至此关于触发器的基础知识内容就已经讲完啦,在这里我们需要注意的 是,在时序电路中,边沿触发器就是时序电路的核心知识,如果不懂边沿触发器,那么也就看不 懂时序电路,下面我们讲解的寄存器和计数器其实都是用这些边沿触发器组合而成的。 §3.2 寄存器和计数器 3.2.1 寄存器 说完了触发器,接下来我们就说一说寄存器,其实我们只要搞明白、弄清楚触发器,寄存器 也是分分钟就能够学会的。为什么会这样说呢,因为寄存器就是由多个触发器组成的,我们知道 能够存储一位二进制码的时序电路叫做触发器,其实寄存器就是能够存储多位二进制数码的时 序电路。为了让大家能够进一步了解和明白寄存器,下面我们举例进行说明,我们给出由四位 D 触发器组成的寄存器电路图,如图 3.8 所示。 http://www.fpga.gs/ 50 Q0 Q0 F0 Q 1D C1 项目实战篇 Q1 Q1 F1 Q 1D C1 Q2 Q2 F2 Q 1D C1 §3 Q3 Q3 F3 Q 1D C1 1 D0 CP D1 D2 D3 图 3.8 由四位 D 触发器组成的寄存器电路图 通过电路图我们可以看出, F0 、 F1 、 F2 、 F3 是 4 个边沿 D 触发器的电路符号,图中绿色 的小三角则表示它们的触发方式是边沿触发,这个就是寄存器的基本结构,下面我们就来看一看 寄存器的工作原理。 如果此时我们给电路中的 CP 端一个脉冲的话,那么此刻 D0 、 D1 、 D2 、 D3 的值就将被 储存在寄存器里了。直到 CP 端下一个脉冲的上升沿到来时,储存的值才有可能被改变。根据这 个特性我们可以列出该电路的特性表。如表 3.2 所示。 表 3.2 由四位 D 触发器组成的寄存器特性表 时钟 输入 输出 工作模式 CP D0、D1、D2、D3 Q0、Q1、Q2、Q3 ↑ D0、D1、D2、D3 D0、D1、D2、D3 数码寄存 1 X、X、X、X 保持 数据保持 0 X、X、X、X 保持 数据保持 从特性表中我们可以看出,这种寄存器有两种工作状态,分别是寄存和保持。当时钟信号的 上升沿到来时,电路将会触发,D0、D1、D2、D3 的值将被寄存,直到下一个时钟信号的上升 沿到来之前,无论我们怎么改变输入信号的值,输出信号的值也不会改变。这就是基本寄存器的 逻辑规律,非常的容易理解。 3.2.2 计数器 最后我们在来说一下计数器,计数器也是今后我们在 FPGA 设计中,最常用到的一种时序 电路,所谓计数器就是一种能统计输入脉冲个数的时序电路,也就是说,我可以通过输出的结果 看出来你一共来了多少次脉冲。这样的特性我们不仅可以直接用于计数,还可以用于定时,分频 等多种功能。为了让大家能够进一步了解和明白计数器,这里我们同寄存器一样举例进行说明, 下面我们给出计数器的电路结构图,如图 3.9 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §3 数字时钟设计分析 51 CLK G1 =1 G4 G2 & =1 G3 & =1 G5 F1 DQ Q Q1 F2 DQ Q Q2 F3 DQ Q Q3 F4 DQ Q Q4 图 3.9 由四位 D 触发器组成的计数器电路图 这里的电路结构图还是稍微有点复杂的,我们并不能像看寄存器一样很直观的看出计数器 的逻辑功能,大家看计数器的结构,它是由 F1 、 F2 、 F3 和 F4 这四个边沿 D 触发器,并加上三 个异或门和二个与门组成的。下面我们来通过分析了解一下这个电路是如何被用来计数的。 我们假设现在电路的初始状态是 0000,也就是输出端口 Q1Q2Q3Q4  0000 ,下面我们来 看下这四个 D 触发器此时的输入信号是什么,我们先看 F1 ,F1 的输入信号 D ,是由 Q1 反馈回 来的,我们已知此时的 Q 是 0,那么 Q1 就是 1,这个 1 反馈给输入信号 D ,此时 F1 的输入信 号就是 1,下面再看 F2 , F2 的输入信号,是 Q1 和 Q2 的值经过一个异或门之后得到的,我们已 经此时的 Q1 是 0,Q2 也是 0,那么这两个 0,经过异或门,根据异或门的逻辑规律,相同出 0, 相异出 1,这里异或门的输出就是 0 了, F2 的输入信号也是 0。下面再看 F3 , F3 的输入信号, 是由 Q1 和 Q2 经过一个与门之后的输出值,与 Q3 的值相异或得到的,我们看,Q1 和 Q2 都是 0, 两个 0 相与,输出肯定还是 0,这个输出的 0,再与 Q3 的值相异或,根据异或门的规律,两个 输入现在都是 0,那么异或门的输出也是 0,F3 的输入此时就是 0,讲完了 F3 ,我们再来看 F4 , F4 的输入信号是由, Q1 、 Q2 和 Q3 经过一个与门之后,得到的输出值,再与 Q4 的值相异或得 到的, Q1 、 Q2 和 Q3 此时都是 0,它们相与之后,得到的输出值也还是 0,那么这个 0 再与 Q4 的值相异或,根据异或门的逻辑规律,两个输入值都是 0,那么异或门输出也是 0, F4 的输入此 时就是 0。 到此,我们已经分析出了 F1 、 F2 、 F3 和 F4 ,这四个触发器此时的输入值了,下面我们就 可以根据 D 触发器的逻辑规律知道下一刻电路的输出值了,那么,现在我们给 CLK 端口一个上 升沿,也就是 CLK 由 0 变为 1 了,那么四个边沿 D 触发器将会同时触发,当 CLK 这个时钟信 号的上升沿到来时,D 触发器的输入值将会被锁存,根据逻辑规律,下一刻 4 个 D 触发器的输 出值就分别为 1,0,0,0。这里如果我们把 Q4 的值当做二进制数的最高位,把 Q1 的值当做二进制 数的最低位,那么现在计数器所输出的值,就是二进制数 0001,也就是十进制的 1。计数器,接 http://www.fpga.gs/ 52 项目实战篇 §3 收到了第一个时钟信号的上升沿,那么计数器就输出二进制数 0001,依次类推,如果第二个时 钟信号的上升沿到来时,大家可以试着自己推一下,这个时候计数器将会输出二进制数 0010, 也就是十进制数 2,每当电路多到来一个时钟上升沿,计数器就会作加 1 运算。当电路计到第十 六个脉冲时,电路状态将由 1111 又变为 0000,完成一个循环周期,所以该电路也称为模 16 同 步加法计数器。所谓同步就是指该电路中的四个边沿 D 型触发器共用一个时钟脉冲 CLK ,当时 钟上升沿到来时,它们能够同时触发,这就叫同步,既然有同步,那肯定也有异步,所谓的异步 就是它们不是共用一个时钟脉冲,且触发不是同时发生的。 那么,讲到这个,大家应该已经了解了计数器的工作原理了,下面我们根据上面的分析,画 出模 16 同步加法计数器的特性表,如表 3.3 所示。 表 3.3 模 16 同步加法计数器的特性表 0 1 2 3 4 5 …… 14 15 0000 0001 0010 0011 0100 0101 …… 1110 1111 0001 0010 0011 0100 0101 0110 …… 1111 0000 由于该特性表过于啰嗦,这里我们就省略了一部分内容,由于模 16 同步加法计数器的特性 表比较容易理解,这里我们就不在进一步讲解,下面我们给出模 16 同步加法计数器的状态图, 如图 3.10 所示。 0 CLK1 1 CLK2 2 CLK3 3 CLK16 15 14 CLK15 图 3.10 模 16 同步加法计数器的状态图 §3.3 数字时钟的理论实战 说完了计数器,下面我们就趁热打铁简单的说一说数字时钟的一个实现原理。我们知道数字 时钟最基本的功能就是显示时、分、秒三个单位,这里我们就以单位秒为主进行讲解,当我们知 道了秒是如何实现的以后,我们再想实现单位时和分也是非常容易的。 一个两位数的单位秒,我们可以从 0 一直累加计数到 59,然后再返回 0 循环进行累加,刚 Zircon Opto-Electronic Technology CO.,Ltd. §3 数字时钟设计分析 53 刚我们说过一个可以从 0 到 15 循环累加的计数器,叫做模 16 计数器,那么我们这里从 0 到 59 循环累加,其实就是一个模 60 计数器,模 60 计数器的电路结构相比之前的模 16 是有点复杂 的,如果我们想用门电路直接搭一个的话,还是有点麻烦的,所以一般情况,我们不选择直接用 模 60 计数器来实现,而是选择用模 6 计数器加上模 10 计数器的方法实现,为什么要选择模 10 和模 6 呢,一个两位的秒,是分为个位和十位的,个位无非是要显示 0 到 9 这 10 个数,十位无 非是要显示 0 到 5 这 6 个数,我们可以将十位和个位分开来进行计数,它的个位数字现在每间 隔 1 秒变化一次,变化到 9 的时候,它下一刻将会归 0,然后重新计数,而十位的数字每间隔 10 秒变化一次,变化到 5 的时候,它的下一刻也将会归 0,然后重新计数,这就是两位数单位秒的 一种实现方法,下面我给出两位数单位秒的电路示意图,如图 3.11 所示。 10秒 一个上升沿 DDD123 模6 Q1 Q2 计数器 Q3 CLK 1秒 一个上升沿 DDDD1234 模10 Q1 Q2 计数器 Q3 Q4 CLK 显示译码器 显示译码器 十位 0~5 个位 0~9 图 3.11 两位数单位秒的电路示意图 从电路示意图中我们可以看出,我们先将个位的数码管和十位的数码管分别与模 10 和模 6 计数器对应,然后,我们将这两个计数器的输出信号引出,连接到相应的译码器上,再通过译码 器译码,将最终的数字显示在数码管上。这就是一个两位数秒表的电路结构了,这里我们要注意 的是,图中的两个计数器,它们的时钟信号不是共用的,模 10 计数器的时钟端,信号的上升沿 是需要间隔 1 秒来一次,而模 6 计数器的时钟端,信号的上升沿是需要间隔 10 秒来一次的,具 体怎么产生我们需要的时钟信号,这里就不具体讲了,我们将会在后面给大家进行讲解。当然实 现这种电路的方法不仅仅只有这一种,我们还可以通过当模 10 计数器从 9 变成 0 的时候输出一 个信号给模 6 计数器,让模 6 计数器工作一次来完成整个电路功能。通过上面的讲解与分析, 大家有没有感觉到,想要实现一个数字时钟并不是想象中的那么难,我们只要实现了单位秒,后 面的单位分和时完全可以依葫芦画瓢,使用同样的方法来实现。至此,我们整个框架的第一部分 时序逻辑基础知识就复习完了,下面我们就来学习整个框架的第二部分外设进阶知识。 http://www.fpga.gs/ 数字时钟设计实战 第四章 数字时钟设计实战 在上一章节的学习中,我们主要讲解了数字时钟整个工程框架图的第一部分时序电路基础 知识,在这部分基础知识中,我们完成了数字时钟设计分析,知道了如何利用理论知识来完成数 字时钟设计。接下来我们将由理论转为实战,来学习整个工程框架图的第二部分外设进阶知识, 如图 4.1 所示。 LED进阶 基础外设 进阶学习 按键进阶 数码管进阶 功能概述 设计说明 源码解析 板级调试 完成多功能 数字时钟电路 蜂鸣器外设 图 4.1 数字时钟总体设计学习框架图 从该图中我们可以看出,首先我们需要学习的是 LED 外设,进阶这我们还要学习按键、数 码管和蜂鸣器外设,每一个外设我们与讲解三人表决器时一样,也会从功能概述、设计说明、源 码解析和板级调试,这四个方面进行学习。当这四个外设学习完以后,最终我们就能够将这四个 外设相结合完成数字时钟项目的设计,并且在 A4 开发板上实现上数字时钟的功能。 §4.1 LED进阶 4.1.1 功能概述 下面我们就来讲解第一个 LED 外设进阶,在 LED 外设进阶中,我们将利用 LED 外设实现 闪烁效果。虽然是从点灯变成了闪灯,只是一步之遥,但是从点灯到闪灯却有着本质的变化,在 这里要引入一个时钟的概念,在 FPGA 的设计中,90%以上的功能都是基于时钟来完成的。下 面我们就来简单的介绍一下时钟是如何产生的。 晶体振荡器简称晶振,晶振可以不用任何外围电路,只要加电就可以输出时钟,使用起来十 分方便。晶振在整个数字电路中就好比一个人的心脏一样,提供 FPGA 系统的时钟源, 也就是产 生高低电平的周期(产生一个高电平和一个低电平为一个周期,)一般说来此频率越高,FPGA 在 单位时间里处理的速度越快。晶振分为无源晶体与有源晶振,无源晶体(crystal)是有 2 个引脚的 无极性元件,需要借助于时钟电路才能产生振荡信号,自身无法振荡起来;有源晶振则叫做 oscillator(振荡器)。有源晶振有 4 只引脚,是一个完整的振荡器,其中除了石英晶体外,还有 晶体管和阻容元件,因此体积较大。相对于无源晶体,有源晶振的缺陷是其信号电平是固定的, 需要选择好合适输出电平,灵活性较差,而且价格高。对于时序要求敏感的应用,个人认为还是 有源的晶振好,因为可以选用比较精密的晶振,甚至是高档的温度补偿晶振。无源晶体相对于晶 58 项目实战篇 §4 振而言其缺陷是信号质量较差,通常需要精确匹配外围电路(用于信号匹配的电容、电感、电阻 等),更换不同频率的晶体时,周边配置电路需要做相应的调整。建议采用精度较高的石英晶体, 尽可能不要采用精度低的陶瓷晶体,如图 4.2 所示为各种类型的晶振。 图 4.2 各种类型的晶振 无源晶体输出正弦波,有源晶振输出正弦波或方波。如果有源晶振把整形电路做在有源晶振 里面了的话,输出就是方波,但很多时候在示波器上看到的还是波形不太好的正弦波,这是由于 示波器的带宽不够,好像有源晶振 20MHz,如果用 40MHz 或 60MHz 的示波器测量,显示的是 正弦波,这是由于方波的傅里叶分解为基频和奇次谐波的叠加,带宽不够的话,就只剩下基频 20MHz 和 60MHz 的谐波,所以显示正弦波。完美的再现方波需要至少 10 倍的带宽,5 倍的带 宽只能算是勉强,所以需要至少 100M 的示波器。FPGA 所用的晶振必须是有源的,因为 FPGA 内部没有无源晶体的启动电路。FPGA 的晶振一般要接入全局时钟引脚,尽管和接在普通 IO 也 可以实现功能,但是全局时钟引脚的电路是经过优化的,如果不用全局时钟引脚,系统时序根本 得不到保障,主控时钟脚如果是树的躯体那么其他管脚可能只能位于某片树叶的位置,时钟是要 游遍整个电路结构的,所以一般情况下晶振要接入到全局时钟引脚。 4.1.2 设计说明 在这个实例中,我们将用到 A4 开发板上的一个 LED 指示灯,首先我们在 FPGA 内部产生 一个分频计数器,对 FPGA 的输入 50MHz 时钟进行分频,然后我们将得到的分频信号(50%占 空比的方波)输出给 LED 指示灯,实现 1s 让他闪烁一次,如图 4.3 所示。 图 4.3 分频计数之 LED 闪烁 知道了整个工程是如何设计的之后,接下来我们就可以新建工程了,我们将该工程命名为 “A4_Led2”,把这个工程放在专门的文件夹下,其他设置参考 1.4 Quartus 基础章节。新建 Verilog 源文件,命名为“A4_Led2.v”,输入设计代码,综合编译后进行引脚分配,本例程的引 Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 59 脚分配如图 4.4 所示。 图 4.4 LED 外设管脚分配完成图 接下来,对工程进行全编译,不仅要让刚刚添加的引脚分配生效,也要生成可以下载到 FPGA 芯片中的配置文件。 4.1.3 源码解析 大家看,如图 4.5 所示,这就是按键的逻辑设计功能框图。 CLK_50M RST_N 27位的循环计数器用来定时 1s 判断时间1s并根据判断结果 来改变LED亮灭状态 LED 图 4.5 LED 的逻辑设计功能框图 从该图中我们可以看出,本实例代码中设计了 2 个输入端口和 1 个输出端口,其中 2 个输 入端口分别对应的是开发板上的复位按键和时钟管脚。1 个输出端口对应的是开发板的 D1,该 代码使用 FPGA 外部时钟不停地对 27bit 的计数器 time_cnt 做循环计数,然后将这个计数器 time_cnt 的值与我们设定的 SET_TIME_1S(50_000_000 也即是 1s)做比较,当达到我们设 定的值后,D1 将会翻转状态实现闪烁,同时计数器也会得到清零,从 0 开始计数。下面我们给 出该实例的 Verilog 代码,如代码 4.1 所示。 代码 4.1 LED 进阶外设的 Verilog 代码 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : A4_Led2.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : LED 闪烁 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 module A4_Led2 8( 9 //输入端口 10 CLK_50M,RST_N, 11 //输出端口 12 LED1 http://www.fpga.gs/ 60 项目实战篇 §4 13 ); 14 15 //--------------------------------------------------------------------------16 //-- 外部端口声明 17 //--------------------------------------------------------------------------- 18 input CLK_50M; //时钟的端口,开发板用的 50M 晶振 19 input RST_N; //复位的端口,低电平复位 20 output LED1; //对应开发板上的 LED 21 22 //--------------------------------------------------------------------------23 //-- 内部端口声明 24 //--------------------------------------------------------------------------- 25 reg [26:0] time_cnt; //用来控制 LED 闪烁频率的定时计数器 26 reg [26:0] time_cnt_n; //time_cnt 的下一个状态 27 reg led_reg; //用来控制 LED 亮灭的显示寄存器 28 reg led_reg_n; //led_reg 的下一个状态 29 30 31 //设置定时器的时间为 1s,计算方法为 (1*10^6)us / (1/50)us 50MHz 为开发板晶振 32 parameter SET_TIME_1S = 27'd50_000_000; 33 34 //--------------------------------------------------------------------------35 //-- 逻辑功能实现 36 //--------------------------------------------------------------------------37 //时序电路,用来给 time_cnt 寄存器赋值 38 always @ (posedge CLK_50M or negedge RST_N) 39 begin 40 if(!RST_N) 41 time_cnt <= 27'h0; //判断复位 //初始化 time_cnt 值 42 else 43 time_cnt <= time_cnt_n; //用来给 time_cnt 赋值 44 end 45 46 //组合电路,实现 1s 的定时计数器 47 always @ (*) 48 begin 49 if(time_cnt == SET_TIME_1S) 50 time_cnt_n = 27'h0; //判断 1s 时间 //如果到达 1s,定时计数器将会被清零 51 else 52 time_cnt_n = time_cnt + 27'h1;//如果未到 1s,定时计数器将会继续累加 53 end 54 55 //--------------------------------------------------------------------------56 //时序电路,用来给 led_reg 寄存器赋值 Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 61 57 always @ (posedge CLK_50M or negedge RST_N) 58 begin 59 if(!RST_N) 60 led_reg <= 1'b0; //判断复位 //初始化 led_reg 值 61 else 62 led_reg <= led_reg_n; //用来给 led_reg 赋值 63 end 64 65 //组合电路,判断时间,控制 LED 的亮或灭 66 always @ (*) 67 begin 68 if(time_cnt == SET_TIME_1S) //判断 1s 时间 69 led_reg_n = ~led_reg; //如果到达 1s,显示寄存器将会改变 LED 的状态 70 else 71 led_reg_n = led_reg; //如果未到 1s,显示寄存器将会将保持 LED 的原状态 72 end 73 74 assign LED1 = led_reg; //最后,将显示寄存器的值赋值给端口 LED1 75 76 endmodule 当读者第一次看到有关时序的代码,第一印象便是代码好多,相比之下组合电路的代码总是 短短几行。其实代码多并不代表代码难理解,而代码少也不代表代码容易理解。首先要说明的是 为什么定义了一个 27 位 time_cnt 寄存器(当然 26 位才是最好的选择,这里笔者是想要说明以 下问题)。通过观察程序寄存器最大运行到 50000000,经计算 26 位寄存器就能装得下 50000000 这个大数目,怎么计算的呢,一个一位寄存器能表示两个值 0 和 1,那一个 2 位寄存器能表示 4 个值 00,01,0,11,几位寄存器就是 2 的几次方,而我们这里的 27 位寄存器可以存放 2 的 27 次 方=134217728,比 50000000 大好多,所以 27 位也绰绰有余,同样 30 位也是可以的,但是在 能实现需求的情况下,资源少性价比就高了,使用 30 位肯定比使用 26 位浪费的资源多了,浪 费是可耻的,工程大了就会察觉资源的可贵,要养成一个良好的习惯,从声明寄存器开始。 说完了这个 time_cnt 寄存器,接下来我们在来看下参数定义,这个 50000000 是如何得到 的,我们的晶振是 50MHz,也就是说,我们的时钟一个周期变化就是 20ns,如果我们想要 1 秒 钟变化一次,那么我们只需要让时钟运行个 50000000 次,也就得到了我们想要的 1 秒。 下面也就是本程序中最核心的,也是用法相对比较多的 always 模块。always 模块敏感表可 以为电平、沿信号(上升沿)posedge、(下降沿)negedge;前面我们都把 always 模块用在组 合逻辑电路中,如下代码结构如下: 1 always @ (*) 2 begin 3 //具体逻辑 4 end //always @ (A,B) 也可以 always @ (A or B) always 后若有沿信号(上升沿 posedge,下降沿 negedge)声明,则多为时序逻辑,其基本代 http://www.fpga.gs/ 62 项目实战篇 §4 码结构如下: 1 always @ (posedge CLK_50M) //单个沿触发的时序逻辑 2 begin 3 //具体逻辑 4 end 5 6 always @ (posedge CLK_50M or negedge RST_N) //多个沿触发的时序逻辑 7 begin 8 //具体逻辑 9 end 下面我对这两个组合和时序代码结构用语言进行描述一下,组合逻辑电路中 always 前面已 经介绍过了,不过这里为了方便读者对比,我在写一遍。always @ (A,B)详细讲解一下,@是事 件等待语句,意思是一直等待 A 和 B 二个敏感变量变化,不管 A 和 B 是从高变低,还是从低变 高,都将会执行 always 下面的 begin……end 中的语句。如果 A 和 B 都没有变化,那么 always 也将不往下执行,将一直循环等待。新标准可以这样书写 always @ (*),其解释如下:always @ (*)是个组合逻辑电路的描述方式;是为了防止你在设计时考虑不周全带来一些操作失误,所 以敏感表用*(表示全部的敏感变量,这里表示 A 和 B),只要有任何输入信号变化,其输出立即 发送变化。 而时序逻辑电路中 always @ (posedge CLK_50M or negedge RST_N):@也同样是时间 等待语句,只是这里一直等待的是 CLK_50M 和 RST_N 二个敏感变量的上升沿和下降沿的变 化,如果有一个 CLK_50M 的上升沿到来,那么便会执行 always 下面的 begin……end 中的语 句。同样的如果有一个 RST_N 的下降沿到来,也同样会执行 always 下面的 begin……end 中 的语句。如果两个信号都没有到来,那么将不会执行 begin……end 中的语句,一直等待信号的 到来。 细心的读者一定会发现时序电路中的赋值是<=,而组合逻辑电路中的赋值是=,是不是我在 编写代码的时候小手一抖,多打了一个符号呢,不是这样的,在 Verilog 中有这么两个概念。阻 塞赋值操作符用等号(即= )表示,非阻塞赋值操作符用小于等于号 (即 <= )表示。阻塞和非阻塞 赋值的语言结构,在 Verilog 语言中相对来说还是比较最难理解的。在很多时候,用"="或者是 "<="实际上对应的是不同的硬件电路,在这里我就不多做解释,1、是一句两句是解释不清楚它 们之间的关系,2、是有可能越解释读者反而越凌乱。想要进一步了解什么是阻塞和非阻塞可以 参考《硬件语法篇》。 在这里,只要按照我这种代码风格来写,是很轻松就能够知道什么时候用阻塞什么时候用非 阻塞,因为我是组合和时序电路分开写的,这样写有什么好处呢,1、是时序电路用非阻塞赋值 (<=),组合电路用阻塞赋值(=),是不是一下就能分清楚了。2、是当工程很大时,分析起来 很有优势,因为时序逻辑部分的写法几乎是完全一样的,这时就可以将主要精力放在分析组合逻 辑上面。3、就是代码容易解读,别人读你写的代码更容易理解。 有优点就有缺点,我把一个计数器分成了组合逻辑和时序逻辑两部分来写,这样就会使代码 变多,你可能会说组合电路能实现计数器吗?如果只是实现一次加的运算,那组合电路是可以的, 这里借助寄存器来把每一次加 1 的结果保存下来,下一次加 1 就是累加的了。这就是第一次看时 Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 63 序电路代码多的原因所在。为了能够让大家有更深的体会和理解,下面我们就将上面的这个代码 改写成另一种风格,如代码 4.2 所示代码。 代码 4.2 LED 进阶外设其他风格的 Verilog 代码 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : A4_Led3.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : 同样使 LED 闪烁和之前不同的代码风格 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 module A4_Led3 8( 9 //输入端口 10 CLK_50M,RST_N, 11 //输出端口 12 LED1 13 ); 14 15 //--------------------------------------------------------------------------16 //-- 外部端口声明 17 //--------------------------------------------------------------------------- 18 input CLK_50M; //时钟的端口,开发板用的 50M 晶振 19 input RST_N; //复位的端口,低电平复位 20 output LED1; //对应开发板上的 LED 21 22 //--------------------------------------------------------------------------23 //-- 内部端口声明 24 //--------------------------------------------------------------------------- 25 reg led_reg; //定义显示寄存器 26 reg [26:0] time_cnt; //定义定时寄存器 27 28 29 //设置定时器的时间为 1s,计算方法为 (1*10^9)ns / (1/50)ns 50MHz 为开发板晶振 30 parameter SET_TIME_1S = 27'd50_000_000; 31 32 //--------------------------------------------------------------------------33 //-- 逻辑功能实现 34 //--------------------------------------------------------------------------- 35 always @ (posedge CLK_50M or negedge RST_N) 36 begin 37 if(!RST_N) //判断复位 38 begin 39 led_reg <= 1'b0; //初始化 led_reg 值 40 end http://www.fpga.gs/ 64 项目实战篇 §4 41 else if (time_cnt == SET_TIME_1S) //判断 1s 时间 42 begin 43 led_reg <= ~led_reg; //如果到达 1s,显示寄存器将会被取反 44 time_cnt <= 1'b0; //如果到达 1s,定时计数器将会被清零 45 end 46 else time_cnt <= time_cnt + 1'b1; //如果未到 1s,定时计数器将会继续累加 47 end 48 49 assign LED1 = led_reg; //最后,将显示寄存器的值赋值给端口 LED1 50 51 endmodule 这个代码和我们之前的代码相比较,虽然代码从多变少了,但是这种写法却带来了很多问题。 对于这些问题,我们就不一一列举了,想要进一步了解的可以查看我们的《硬件语法篇》中的讲 解。这里我们就只说一说这些问题中最严重的一个,那就是它完全是根据软件编程思想编写而成 的,通过前面的学习我们知道评价硬件描述语言写的好坏的标准和其他软件编程语言的标准是 完全不同的,我们在学习 Verilog 硬件描述语言的时候,一定要摒弃软件编程的一些固有思路, 学会用硬件的方式去解决问题。时刻提醒自己正在设计的是一个电路,而不是一行行空洞的代码。 这里关于代码风格我就不多费口舌了,代码在屏幕上,RTL 在心中,这便是学习 Verilog 的最高 境界。我们的代码风格不一定是最好的,但我们会尽量做到最好,因为我们知道初学者学习 Verilog 时模仿居多,一个好的代码风格能提高效率避免很多不必要的错误,一个好的代码风格, 代码阅读起来都是很舒畅的。希望读者们能养成一个好代码风格习惯。 4.1.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Led2.sof 文件下载到开发板中,接着我们可以看到 开发板上的发光二极管 D1,亮 1 秒后就会灭,灭 1 秒后就会亮的循环下去,如图 4.6 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 65 §4.2 按键进阶 图 4.6 LED 进阶板级调试图 4.2.1 功能概述 通过 LED 外设小试身手之后,相信大家对时序电路有了一个全新的认识。接下来我们将继 续讲解第二个外设按键。在按键外设中,我们将会利用按键外设实现按下按键点亮 LED。当然, 这里的实现效果和我们之前讲解的效果是一样,不过,不同的是我们在实现的效果的同时对我们 的按键进行了消抖。下面我们就来简单的介绍一下什么是消抖。从前面的学习,我们知道按键按 键开关是各种电子设备不可或缺的人机接口。在实际应用中,我们很大一部分的按键是机械按键, 在机械按键的触点闭合和断开时,都会产生抖动。 键按下 前沿抖动 键稳定 图 4.7 按键抖动波形图 后沿抖动 如图 4.7 所示,在按键被按下的短暂一瞬间,往往会产生几毫秒的抖动,在这时候若采集信 号,势必导致误操作,甚至系统崩溃;同样,在释放按键的那一刻,硬件上会相应的产生抖动, http://www.fpga.gs/ 66 项目实战篇 §4 会产生同样的后果。因此,为了保证系统能正确识别按键操作,就必须对按键的抖动进行处理。 说的直白点,所谓消抖,就是不去检测那个抖动的波形,而是去检测稳定的波形,这样就能得到 稳定的结果。那怎么能得到那个稳定的波形呢?这里的方法有很多,对于硬件消抖来说。一般是 用电容或者触发器等电路进行消抖,这里我们就以 RS 触发器消抖硬件电路为例进行一个简单的 讲解,大家看,如图 4.8 所示。 VCC(+5V) 4.7K & A B & 4.7K VCC(+5V) 图 4.8 RS 触发器消抖硬件电路 从该图中我们可以看出,两个“与非”门构成一个 RS 触发器。当按键未按下时,输出为 1; 当键按下时,输出为 0。此时即使用按键的机械性能,使按键因弹性抖动而产生瞬时断开(抖动跳 开 B),中要按键不返回原始状态 A,双稳态电路的状态不改变,输出保持为 0,不会产生抖动 的波形。也就是说,即使 B 点的电压波形是抖动的,但经双稳态电路之后,其输出为正规的矩形 波。这一点通过分析 RS 触发器的工作过程很容易得到验证。 由于我们这个 A4 开发板没有使用硬件消抖电路,所以我们只能使用软件消抖来解决我们的 按键抖动问题了。对于软件消抖来说,我们可以采用延迟消抖,当我们检测到按下的信号后,我 们并不立刻就执行,而是延时一段时间,然后再测按键是不是被按下,如果是按下的信号,那么 说明确实是按下去了,否则就是没按下去或者发生其他的意外产生的毛刺。理论上按键抖动时间 为 5ms-10ms,这里我们就用 20ms 的延迟来实现按键消抖并点亮 LED。 4.2.2 设计说明 新建工程,命名为“A4_Key2”,把这个工程放在专门的文件夹下,其他设置参考 1.4 Quartus 基础章节。新建 Verilog 源文件,命名为“A4_Key2.v”,输入设计代码,综合编译后进行引脚分 配,本例程的引脚分配,如图 4.9 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 67 图 4.9 按键进阶外设管脚分配完成图 接下来,对工程进行全编译,不仅要让刚刚添加的引脚分配生效,也要生成可以下载到 FPGA 芯片中的配置文件。 4.2.3 源码解析 大家看,如图 4.10 所示,这就是按键的逻辑设计功能框图。 CLK_50M RST_N KEY[7…0] 先判断按键是否按下,当按 键按下后,计数器才会开始 计数,计到20 ms后,才会读 入按键 消抖后的按键 用于控制LED的亮灭 LED[7…0] 图 4.10 按键进阶的逻辑设计功能框图 从该图中我们可以看出,该程序中我们设计了 10 个输入端口和 8 个输出端口,10 个输入端 口分别对应着我们的时钟、复位以及 8 个按键,8 个输出端口对应着我们的 8 个 LED。该程序 首先判断按键是否按下,如果按键有按下,那么计数器便开始计数,然后将这个计数器 time_cnt 的值与我们设定的 SET_TIME_20MS 值做比较,当达到我们设定的值后,我们便将按键的值读 取进来,然后用于控制 LED 的亮灭。如果没有到达我们设定的值,那么我们便不会读取按键的 值,LED 也将保持原状态不变。下面我们给出该实例的 Verilog 代码,如代码 4.3 所示。 代码 4.3 按键进阶外设的 Verilog 代码 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : A4_Ked2.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : 按键消抖 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 module A4_Key2 8( http://www.fpga.gs/ 68 项目实战篇 §4 9 //输入端口 10 CLK_50M,RST_N,KEY, 11 //输出端口 12 LED 13 ); 14 15 //--------------------------------------------------------------------------- 16 //-- 外部端口声明 17 //--------------------------------------------------------------------------- 18 input 19 input CLK_50M; RST_N; //时钟的端口,开发板用的 50MHz 晶振 //复位的端口,低电平复位 20 input [ 7:0] KEY; //对应开发板上的 KEY 21 output [ 7:0] LED; //对应开发板上的 LED 22 23 //--------------------------------------------------------------------------- 24 //-- 内部端口声明 25 //--------------------------------------------------------------------------- 26 reg [20:0] time_cnt; //用来计数按键延迟的定时计数器 27 reg [20:0] time_cnt_n; //time_cnt 的下一个状态 28 reg [ 7:0] key_in_r; //用来接收按键信号的寄存器 29 reg 30 reg 31 wire [ 7:0] [ 7:0] key_out; key_out_n; key_press; //消抖完成输出按键 //key_out 的下一个状态 //检测按键有没有变化 32 33 //设置定时器的时间为 20ms,计算方法为 (20*10^3)us / (1/50)us 50MHz 为开发板晶振 34 parameter SET_TIME_20MS = 21'd1_000_000; 35 36 //--------------------------------------------------------------------------- 37 //-- 逻辑功能实现 38 //--------------------------------------------------------------------------- 39 //时序电路,用来 key_in_r 寄存器赋值 40 always @ (posedge CLK_50M, negedge RST_N) 41 begin 42 if(!RST_N) 43 key_in_r <= 8'h00; //判断复位 //初始化 key_in_r 值 44 else 45 key_in_r <= KEY; //将按键的值赋值给 key_in_r 46 end 47 48 assign key_press = key_in_r ^ KEY; //检测按键有没有变化 49 50 //时序电路,用来给 time_cnt 寄存器赋值 51 always @ (posedge CLK_50M, negedge RST_N) 52 begin Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 69 53 if(!RST_N) //判断复位 54 time_cnt <= 21'h0; //初始化 time_cnt 值 55 else 56 time_cnt <= time_cnt_n; //用来给 time_cnt 赋值 57 end 58 59 //组合电路,实现 20ms 的定时计数器 60 always @ (*) 61 begin 62 if(time_cnt == SET_TIME_20MS || key_press) //判断按键有没有变化、时间有没有到 63 time_cnt_n = 21'h0; //如果条件成立,那么定时计数器将会被清零 64 else 65 time_cnt_n = time_cnt + 1'b1; //如果条件不成立,那么定时计数器将会继续累加 66 end 67 68 //时序电路,用来 key_out 寄存器赋值 69 always @ (posedge CLK_50M, negedge RST_N) 70 begin 71 if(!RST_N) //判断复位 72 key_out <= 8'h00; //初始化 key_out 值 73 else 74 key_out <= key_out_n; //用来给 key_out 赋值 75 end 76 77 //组合电路,每 20ms 接收一次按键的值 78 always @ (*) 79 begin 80 if(time_cnt == SET_TIME_20MS) //判断 20ms 时间 81 key_out_n = key_in_r; //如果到达 20ms,接收一次按键的值 82 else 83 key_out_n = key_out; //如果未到 20ms,保持原状态不变 84 end 85 86 assign LED = key_out; //将消抖的按键值赋值给 LED 87 88 endmodule 下面我们就来简单的介绍一下该代码,第 1 至 34 行是端口声明和参数定义,第 39 至 48 行 是用来检测按键有没有变化,大家来第 48 行,这一段程序便是该程序的核心知识点,这里我们 假设按键默认状态是(0000_0000),也就是没有一个按键按下,这时如果 KEY1 按键按下,那 么我们的按键的值会立刻变成 (0000_0001),由于我们的 key_in_r 它需要经过时钟的上升沿才 会触发,所以这时它的值仍是 8 个 0(0000_0000),由于 key_in_r 等于(0000_0000),而我 们的 KEY 等于(0000_0001),所以它们相异或的 key_press 便会等于(0000_0001),也就是 输入信号有了变化,按键被按下,这时候我们就可以进行计数了。如果计数没有到达 20ms,输 http://www.fpga.gs/ 70 项目实战篇 §4 入信号又发生了变化,这时,我们的计数器会立刻清零然后重新计数,直到按键一直稳定到达 20ms 以后,我们的按键才会有效。否则都将认为是按键抖动,直接过滤掉。第 51 至 66 行是用 来实现 20ms 的定时计数器,这和我们的 LED 进阶中的代码是一样的。只不过把 1s 改成了 20ms。 第 69 至 84 行是用来接收稳定后的按键值,第 86 行是按键控制 LED 实现 LED 亮或灭。 4.2.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Key2.sof 文件下载到开发板中,接着我们按下实体 按键,与其相对应的 LED 将会熄灭,松开按键后,与其相对应的 LED 将会点亮,如图 4.11 所 示,我们同时按下触摸的第三个和第四个,实体的第一个和第二个,其相对应的 D3、D4、D5、 D6 也都熄灭。 §4.3 数码管进阶 图 4.11 按键进阶板级调试图 4.3.1 功能概述 讲完了按键外设,接下来我们再来看一看我们的第三个数码管外设,通过前面数码管的学习 我们知道,我们 A4 开发板上的数码管的所有数据管脚是共用的,原理图如图 4.12 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 71 图 4.12 数码管的原理图 通过该图我们可以看出,我们的 6 个数码管的所有的数据管脚全都连接在了一起,我们只 要给数据管脚赋值,所有的数码管都会显示一样数值,如果我们想让每个数码显示不同的值,比 如让数码管显示 012345,那么我们应该用什么方法实现呢?下面我们就来为大家简单的进行一 个讲解:当 FPGA 输出数据给数码管时,所有数码管都会接收到相同的数据,但究竟是哪个数 码管会显示出该数据,这其实是取决于 FPGA 对位选通 COM 端电路的控制,如果我们将需要 显示的数码管的选通控制打开,那么该数码管就会显示 FPGA 输出的数据,如果我们将选通控 制关闭,那么该数码管也就不会显示。因此,我们就可以通过分时轮流控制各个数码管的 COM 端,使各个数码管轮流受控显示,在轮流显示过程中,每位数码管的点亮时间为 1~2ms,由于 人的视觉暂留现象及发光二极管的余辉效应,所以我们就可以完成在数码管上同时显示六位不 同的数字。尽管实际上各位数码管并非同时点亮,但只要扫描的速度足够快,给人的印象就是一 组稳定的显示数据,不会有闪烁感。比如:六个数码管要显示“012345” 数字。第一个数码管 SEG1 加低电平,其余 SEG2、SEG3、SEG4、SEG5、SEG6 高电平,同时数码管输入 0 对应 的数据;然后第二个数码管 SEG2 加低电平,其余 SEG1、SEG3 、SEG4 、SEG5、SEG6 高 电平,同时数码管输入 1 对应的数据;然后以此类推,周而复始重复上述过程,六个数码管就显 示“012345”数字。 4.3.2 设计说明 新建工程,命名为“A4_Segled2”,把这个工程放在专门的文件夹下,其他设置参考 1.4 Quartus 基础章节。新建 Verilog 源文件,命名为“A4_Segled2.v”,输入设计代码,综合编译 后进行引脚分配,本例程的引脚分配,如图 4.13 所示。 http://www.fpga.gs/ 72 项目实战篇 §4 图 4.13 数码管外设管脚分配完成图 接下来,对工程进行全编译,不仅要让刚刚添加的引脚分配生效,也要生成可以下载到 FPGA 芯片中的配置文件。 4.3.3 源码解析 大家看,如图 4.14 所示,这是数码管的逻辑设计功能框图。 CLK_50M RST_N 16位的循环计数 器 用来定时10ms 判断时间10ms并 根据判断结果来 递增循环3位的计 数器 利用3位计数器的 值来分时控制数 码管的亮和灭 SEG_EN 利用3位计数器 的值来分时控 制数码管显示 的数据 SEG_DATA 图 4.14 数码管的逻辑设计功能框图 从该图中我们可以看出,本实例代码中有 2 个输入端口和 2 个输出端口,2 个输入端口分别 是时钟和复位,2 个输出端口分别是数码管的使能端口和数据端口。该代码使用 FPGA 外部时 钟不停地对 16 位的计数器 time_cnt 做循环计数,然后将这个计数器 time_cnt 的值与我们设定 的 SET_TIME_10MS(50_000 也即是 10ms)做比较,当达到我们设定的值后,3 位的计数器 将会加 1,同时 16 位计数器也会得到清零,从 0 开始计数,直至 3 位计数器下一次加 1,也就是 说,3 位计数器每 10ms 将会执行 1 次,依次循环计数。最后,我们在根据 3 位计数器相应的值 来控制数码管的 SEG_EN 端口分时选中,在单独选中 SEG_EN 端口时,它所对应的显示数据 也将进行译码后送往 SEG_DATA 段选输出信号,虽然是分时分别地选中 6 个数码管位,但是 由于人眼的视觉暂留效果,实际上看上去好像是同时点亮所有数码管。下面我们给出该实例的 Verilog 代码,如代码 4.4 所示。 代码 4.4 数码管进阶外设的 Verilog 代码 1 //--------------------------------------------------------------------------2 //-- 文件名 : A4_Segled2.v Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 73 3 //-- 作者 : ZIRCON 4 //-- 描述 : 动态数码管显示,数码管显示 012345 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 module A4_Segled2 8 ( 9 //输入端口 10 CLK_50M,RST_N, 11 //输出端口 12 SEG_DATA,SEG_EN 13 ); 14 15 //--------------------------------------------------------------------------- 16 //-- 外部端口声明 17 //--------------------------------------------------------------------------- 18 input 19 input CLK_50M; RST_N; //时钟的端口,开发板用的 50M 晶振 //复位的端口,低电平复位 20 output reg [ 5:0] SEG_EN; //数码管使能端口 21 output reg [ 7:0] SEG_DATA; //数码管数据端口(查看管脚分配文档或者原理图) 22 23 //--------------------------------------------------------------------------- 24 //-- 内部端口声明 25 //--------------------------------------------------------------------------- 26 reg [15:0] time_cnt; //用来控制数码管闪烁频率的定时计数器 27 reg [15:0] time_cnt_n; //time_cnt 的下一个状态 28 reg [ 2:0] led_cnt; //用来控制数码管亮灭及显示数据的显示计数器 29 reg [ 2:0] led_cnt_n; //led_cnt 的下一个状态 30 31 //设置定时器的时间为 10ms,计算方法为 (10*10^3)us / (1/50)us 50MHz 为开发板晶振 32 parameter SET_TIME_10MS = 16'd500_000; 33 34 //--------------------------------------------------------------------------- 35 //-- 逻辑功能实现 36 //--------------------------------------------------------------------------- 37 //时序电路,用来给 time_cnt 寄存器赋值 38 always @ (posedge CLK_50M or negedge RST_N) 39 begin 40 if(!RST_N) //判断复位 41 time_cnt <= 16'h0; //初始化 time_cnt 值 42 else 43 time_cnt <= time_cnt_n; //用来给 time_cnt 赋值 44 end 45 46 //组合电路,实现 10ms 的定时计数器 http://www.fpga.gs/ 74 项目实战篇 §4 47 always @ (*) 48 begin 49 if(time_cnt == SET_TIME_10MS) 50 time_cnt_n = 16'h0; //判断 10ms 时间 //如果到达 10ms,定时计数器将会被清零 51 else 52 time_cnt_n = time_cnt + 27'h1; //如果未到 10ms,定时计数器将会继续累加 53 end 54 55 //时序电路,用来给 led_cnt 寄存器赋值 56 always @ (posedge CLK_50M or negedge RST_N) 57 begin 58 if(!RST_N) //判断复位 59 led_cnt <= 3'h0; //初始化 led_cnt 值 60 else 61 led_cnt <= led_cnt_n; //用来给 led_cnt 赋值 62 end 63 64 //组合电路,判断时间,实现控制显示计数器累加 65 always @ (*) 66 begin 67 if(time_cnt == SET_TIME_10MS) 68 led_cnt_n = led_cnt + 1'h1; //判断 10ms 时间 //如果到达 10ms,计数器进行累加 69 else 70 led_cnt_n = led_cnt; //如果未到 10ms,计数器保持不变 71 end 72 73 //组合电路,实现数码管的数字显示 74 always @ (*) 75 begin 76 case (led_cnt) 77 3'b000 : SEG_DATA = 8'b00111111; //当计数器为 0 时,数码管将会显示 "0" 78 3'b001 : SEG_DATA = 8'b00000110; //当计数器为 1 时,数码管将会显示 "1" 79 3'b010 : SEG_DATA = 8'b01011011; //当计数器为 2 时,数码管将会显示 "2" 80 3'b011 : SEG_DATA = 8'b01001111; //当计数器为 3 时,数码管将会显示 "3" 81 3'b100 : SEG_DATA = 8'b01100110; //当计数器为 4 时,数码管将会显示 "4" 82 3'b101 : SEG_DATA = 8'b01101101; //当计数器为 5 时,数码管将会显示 "5" 83 default: SEG_DATA = 8'b10111111; 84 endcase 85 end 86 87 //组合电路,控制数码管亮灭 88 always @ (*) 89 begin 90 case (led_cnt) Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 75 91 3'b000 : SEG_EN = 6'b111110; //当计数器为 0 时,数码管 SEG1 显示 92 3'b001 : SEG_EN = 6'b111101; //当计数器为 1 时,数码管 SEG2 显示 93 3'b010 : SEG_EN = 6'b111011; //当计数器为 2 时,数码管 SEG3 显示 94 3'b011 : SEG_EN = 6'b110111; //当计数器为 3 时,数码管 SEG4 显示 95 3'b100 : SEG_EN = 6'b101111; //当计数器为 4 时,数码管 SEG5 显示 96 3'b101 : SEG_EN = 6'b011111; //当计数器为 5 时,数码管 SEG6 显示 97 default: SEG_EN = 6'b111111; 98 endcase 99 end 101 102 endmodule 下面我们就来简单的介绍一下该代码,第 1 至 32 行是端口声明和参数定义,第 38 至 53 行 是 10ms 的定时计数器,第 56 至 71 行是控制数码管显示的加法计数器,第 74 至 85 行是用来 给数码管的数据端口赋值的,第 88 至 99 行是用来给数码管的使能端口赋值的。由于该代码比 较简单,也没有什么比较难理解的地方,所以我们这里就简单的介绍了一下并没有进一步详细讲 解。 4.3.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Segled2.sof 文件下载到开发板中,接着我们就能看 到数码管将会被点亮,显示 0.1.2.3.4.5.,如图 4.15 所示。 §4.4 蜂鸣器外设 4.4.1 功能概述 图 4.15 数码管进阶板级调试图 http://www.fpga.gs/ 76 项目实战篇 §4 讲完了数码管外设,接下来我们再来看一看我们的最后一个蜂鸣器外设,蜂鸣器是一种最简 单的发声元器件,它广泛应用于计算机、打印机、复印机、报警器、电话机等电子产品中作为报 警或发声提醒装置。比如计算机在刚开启时,通常主板上会发出一声较短的尖锐的滴……的鸣叫 声,提示用户主板自检通过,可以正常进行后面的启动;总而言之,可别小看了这颗区区几角钱 的小家伙,关键时刻还挺有用的。可以毫不夸张地说,蜂鸣器也算是一种人机交互的手段。蜂鸣 器主要分为电磁式蜂鸣器和压电式蜂鸣器两种类型,如图 4.16 所示。 图 4.16 各种类型的蜂鸣器 电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,振荡器产 生的音频信号电流通过电磁线圈,使电磁线圈产生磁场,振动膜片在电磁线圈和磁铁的相互作用 下,周期性振动发声。压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外 壳等组成。多谐振荡器由晶体管或集成电路构成,当接通电源后(1.5~15V 直流工作电压),多 谐振荡器起振,输出 1.5~2.5kHZ 的音频信号,阻抗匹配器推动压电蜂鸣片发声。 我们的 A4 开发板采用的是 3.3V 电磁式蜂鸣器。蜂鸣器发声原理是电流通过电磁线圈,使 电磁线圈产生磁场来驱动振动膜发声的,因此需要一定的电流才能驱动它。一般的电磁式蜂鸣器 的驱动电流为 30-100mA,直接使用 FPGA 的 IO 引脚驱动的话,声音会不够响亮,因此我们 A4 开发板上增加一个电流放大的电路,通过三极管 8550 来驱动蜂鸣器,原理图如图 4.17 所示。 图 4.17 蜂鸣器的原理图 从该图中我们可以看出,三极管的发射极接到 VCC(+3.3V)电源上面,蜂鸣器的正极接 到三极管的集电极,三极管的基级经过限流电阻 R28 后由 FPGA 的 I/O 口(BEEP)控制。当 输出高电平时,三极管 Q7 截止,没有电流流过线圈,蜂鸣器不发声;当 BEEP 口为低电平时, 三极管导通,这样蜂鸣器的电流形成回路,发出声音。因此,我们可以通过程序控制 FPGA 上 与 BEEP 管脚相连的 IO 的输出电平来使蜂鸣器发出声音和关闭。在实际编程中,我们改变 BEEP 引脚输出波形的频率,就可以调整控制蜂鸣器音调,产生各种不同音色、音调的声音。另外,我 们改变 BEEP 输出电平的高低电平占空比,则可以控制蜂鸣器的声音大小,这些我们都可以通 Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 77 过编程来实现。 最后我们再来介绍一下,我们将利用蜂鸣器外设实现一个怎样的功能。我们使用按键作为输 入,使用蜂鸣器作为输出,当我们按下 KEY1 按键的时候,我们的蜂鸣器就会发出中音 dao,当 我们按下 KEY2 按键的时候,我们的蜂鸣器就会发出中音 rui。依次类推,直至我们按下 KEY8 按键的时候,我们的蜂鸣器就会发出高音 do。因此我们便能够实现一个简易的电子琴,使用 8 个按键弹出各种不同的音调。 4.4.2 设计说明 新建工程,命名为 A4_Beep,把这个工程放在专门的文件夹下,其他设置参考 1.4 Quartus 基础章节。新建 Verilog 源文件,命名为 A4_Beep.v,输入设计代码,综合编译后进行引脚分配, 本例程的引脚分配如图 4.18 所示。 图 4.18 蜂鸣器外设管脚分配图 接下来,对工程进行全编译,不仅要让刚刚添加的引脚分配生效,也要生成可以下载到 FPGA 芯片中的配置文件。 4.4.3 源码解析 大家看,如图 4.19 所示,这是蜂鸣器的逻辑设计功能框图。 CLK_50M RST_N KEY 根据按键来选择不同的计 数值 根据选择的计数值利用16 位循环计数器进行计数判 断 根据判断的结果来改变蜂 鸣器的状态 BEEP 图 4.19 蜂鸣器的逻辑设计功能框图 从该图中我们可以看出,本实例代码中有 3 个输入端口和 1 个输出端口,3 个输入端口分别 是时钟、复位和按键,1 个输出端口是蜂鸣器端口。该代码可以使用按键来选择不同大小的计数 值,选择的计数值用于 16 位循环计数器中,如果 16 位循环计数器与选择的计数值相等,那么 16 位循环计数器将会清零并重新开始计数,同时蜂鸣器也将会改变状态,否则蜂鸣器将保持原 状态。下面我们给出该实例的 Verilog 代码,如代码 4.5 所示。 代码 4.5 数码管进阶外设的 Verilog 代码 http://www.fpga.gs/ 78 项目实战篇 §4 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : A4_Beep.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : 用按键可以弹奏 do,re,mi,fa,so,la,si,do 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 module A4_Beep 8( 9 //输入端口 10 CLK_50M,RST_N,KEY, 11 //输出端口 12 BEEP 13 ); 14 15 //--------------------------------------------------------------------------- 16 //-- 外部端口声明 17 //--------------------------------------------------------------------------- 18 input CLK_50M; //时钟的端口,开发板用的 50MHz 晶振 19 input RST_N; //复位的端口,低电平复位 20 input [ 7:0] KEY; //按键端口 21 output BEEP; //蜂鸣器端口 22 23 //--------------------------------------------------------------------------- 24 //-- 内部端口声明 25 //--------------------------------------------------------------------------- 26 reg [15:0] time_cnt; //用来控制蜂鸣器发声频率的定时计数器 27 reg 28 reg 29 reg 30 reg [15:0] [15:0] time_cnt_n; freq; beep_reg; beep_reg_n; //time_cnt 的下一个状态 //各种音调的分频值 //用来控制蜂鸣器发声的寄存器 //beep_reg 的下一个状态 31 32 //--------------------------------------------------------------------------- 33 //-- 逻辑功能实现 34 //--------------------------------------------------------------------------- 35 //时序电路,用来给 time_cnt 寄存器赋值 36 always @ (posedge CLK_50M or negedge RST_N) 37 begin 38 if(!RST_N) //判断复位 39 time_cnt <= 16'b0; //初始化 time_cnt 值 40 else 41 time_cnt <= time_cnt_n; //用来给 time_cnt 赋值 42 end 43 44 //组合电路,判断频率,让定时器累加 Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 79 45 always @ (*) 46 begin 47 if(time_cnt == freq) 48 time_cnt_n = 16'b0; //判断分频值 //定时器清零操作 49 else 50 time_cnt_n = time_cnt + 1'b1; //定时器累加操作 51 52 end 53 54 //时序电路,用来给 beep_reg 寄存器赋值 55 always @ (posedge CLK_50M or negedge RST_N) 56 begin 57 if(!RST_N) //判断复位 58 beep_reg <= 1'b0; //初始化 beep_reg 值 59 else 60 beep_reg <= beep_reg_n; //用来给 beep_reg 赋值 61 end 62 63 //组合电路,判断频率,使蜂鸣器发声 64 always @ (*) 65 begin 66 if(time_cnt == freq) 67 beep_reg_n = ~beep_reg; //判断分频值 //改变蜂鸣器的状态 68 else 69 beep_reg_n = beep_reg; //蜂鸣器的状态保持不变 70 end 71 72 //组合电路,按键选择分频值来实现蜂鸣器发出不同声音 73 //中音 do 的频率为 523.3hz,freq = 50 * 10^6 / (523 * 2) = 47774 74 always @ (*) 75 begin 76 case(KEY) 77 8'b00000001: freq = 16'd47774; //中音 1 的频率值 523.3Hz 78 8'b00000010: freq = 16'd42568; //中音 2 的频率值 587.3Hz 79 8'b00000100: freq = 16'd37919; //中音 3 的频率值 659.3Hz 80 8'b00001000: freq = 16'd35791; //中音 4 的频率值 698.5Hz 81 8'b00010000: freq = 16'd31888; //中音 5 的频率值 784Hz 82 8'b00100000: freq = 16'd28409; //中音 6 的频率值 880Hz 83 8'b01000000: freq = 16'd25309; //中音 7 的频率值 987.8Hz 84 8'b10000000: freq = 16'd23889; //高音 1 的频率值 1046.5Hz 85 default : freq = 16'd0; 86 endcase 87 end 88 http://www.fpga.gs/ 80 项目实战篇 §4 89 assign BEEP = beep_reg; 90 91 endmodule //最后,将寄存器的值赋值给端口 BEEP 从该代码中我们可以看出,它和我们的 LED 进阶代码很相似,唯一不同的是,LED 进阶代 码中的计数器是给了固定的计数值或固定的时间,让计数器循环自动计数并清零,而这里的计数 器的计数值或时间是不固定的,它可以任由按键修改。由于该代码比较简单,没有什么难点,并 且我们代码中给出的注释也很详细,所以我们这里就不再进一步进行讲解了。 4.4.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Beep.sof 文件下载到开发板中,接着我们按下按键 就能听到蜂鸣器发出的声音,由于图片无法表达声音,这里我们就不在给出开发板截图。 §4.5 用外设来实现数字时钟 4.5.1 功能概述 当我们把 LED、按键、数码管和蜂鸣器这四个外设学习完了以后,接下来我们就需要将我 们所学的这四个外设有机的相结合,最终实现一个复杂的数字时钟项目。当然这里也是一样的, 前面的基础越牢固,我们后面学习起来也就越轻松。如果你对前面的知识还不是很理解,没有很 好的掌握,那么我们这里建议大家先不要急于往下学习,先把基础知识给学习好了,掌握了,然 后再接着往下学习。 下面我们就来简单的介绍一下,我们利用这四个外设将实现一个怎样功能的数字时钟。我们 的数码管能够实现:时显示、分显示和秒显示。我们的按键能够在显示过程对时、分、秒进行调 整,KEY5 按键用于控制数字时钟的时调整,KEY6 按键用于控制数字时钟的分调整,KEY7 按 键用于控制数字时钟的秒调整,KEY8 按键用于控制数字时钟的暂停和开始。我们的蜂鸣器能够 根据按下不同的按键发出不同的音调,以便提示用户按键已经按下。我们的 LED 则根据秒数据 来进行一个闪烁显示,秒数据进行了改变,我们的 LED 也就跟随着改变。这就是我们整个数字 时钟所实现的功能。 4.5.2 设计说明 新建工程,命名为“A4_Clock_Top1”,把这个工程放在专门的文件夹下,其他设置参考 1.4 Quartus 基础章节。新建 Verilog 源文件,命名为“A4_Clock_Top1.v”,输入设计代码,综合编 译后进行引脚分配,本例程的引脚分配如图 4.20 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 81 图 4.20 数字时钟项目的管脚分配图 接下来,对工程进行全编译,不仅要让刚刚添加的引脚分配生效,也要生成可以下载到 FPGA 芯片中的配置文件。 4.5.3 源码解析 大家看,这就是数字时钟的框架结构图,如图 4.21 所示。 图 4.21 数字时钟项目的框架结构图 从该图中我们可以看出,这里我们将数字时钟分成了 4 个模块,它们分别是 Key_Module、 Counter_Module、Segled_Module 和 Beep_Module。第一个模块 Key_Module 就是我们的按 键模块,该模块主要是用来给按键消抖的,第二个模块 Counter_Module 就是我们的时钟计时模 块,该模块用来计算时、分、秒的数据,我们将计算出的时、分、秒数据分别送给数码管模块和 LED 进行显示,这里需要我们注意的是,由于 LED 显示比较简单,所以我们就没有再另外给出 一个模块,我们这里将 LED 写在了该模块中。第三个模块是 Segled_Module 就是我们的数码 管模块,该模块用来点亮数码管进行时、分、秒显示。第四个模块是 Beep_Module 就是我们的 蜂鸣器模块,该模块用来控制蜂鸣器实现简易的电子琴,根据不同的按键弹出各种不同的音调。 在这四个模块中,我们的按键模块、数码管模块,以及蜂鸣器模块我们都是学过的,这里的代码 和我们之前的学习的代码基本上是一致的,下面我们就主要来看下 Counter_Module 时钟计时 模块,如代码 4.6 所示。 代码 4.6 数码时钟项目的 Verilog 代码 http://www.fpga.gs/ 82 项目实战篇 §4 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : Counter_Module.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : 时钟计时模块 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 module Counter_Module 8 ( 9 //输入端口 10 CLK_50M,RST_N,key_out,LED, 11 //输出端口 12 hours2_data,hours1_data,minutes2_data,minutes1_data,seconds2_data, 13 seconds1_data 14 ); 15 16 //--------------------------------------------------------------------------- 17 //-- 外部端口声明 18 //--------------------------------------------------------------------------- 19 input CLK_50M; //时钟的端口,开发板用的 50MHz 晶振 20 input RST_N; //复位的端口,低电平复位 21 input [ 7:0] key_out; 22 output [ 7:0] LED; 23 output hours2_data; 24 output hours1_data; //按键端口 //LED 端口 //时钟高 4 位数据 //时钟低 4 位数据 25 output minutes2_data; //分钟高 4 位数据 26 output minutes1_data; //分钟低 4 位数据 27 output 28 output seconds2_data; seconds1_data; //秒钟高 4 位数据 //秒钟低 4 位数据 29 30 //--------------------------------------------------------------------------- 31 //-- 内部端口声明 32 //--------------------------------------------------------------------------- 33 reg [26:0] time_seconds; //秒钟低位计数器 34 reg 35 reg 36 reg 37 reg [26:0] [ 3:0] [ 3:0] [ 3:0] time_seconds_n; seconds1_data; seconds1_data_n; seconds2_data; //time_seconds 的下一个状态 //秒钟低位数据寄存器 1 //seconds1_data 的下一个状态 //秒钟高位数据寄存器 2 38 reg [ 3:0] seconds2_data_n; //seconds2_data 的下一个状态 39 reg [ 3:0] minutes1_data; //分钟低位数据寄存器 40 reg 41 reg 42 reg 43 reg [ 3:0] [ 3:0] [ 3:0] [ 3:0] minutes1_data_n; minutes2_data; minutes2_data_n; hours1_data; //minutes1_data 的下一个状态 //分钟高位数据寄存器 //minutes1_data 的下一个状态 //时钟低位数据寄存器 44 reg [ 3:0] hours1_data_n; //hours1_data 一个状态 Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 83 45 reg [ 3:0] hours2_data; //时钟高位数据寄存器 46 reg 47 reg 48 reg [ 3:0] hours2_data_n; stop_reg; stop_reg_n; //hours2_data 一个状态 //控制时钟的开始和暂停 //stop_reg 的下一个状态 49 50 //设置定时器的时间为 1s,计算方法为 (1*10^9) / (1/50) 50MHZ 为开发板晶振 51 parameter SEC_TIME_1S = 27'd50_000_000; 52 53 //--------------------------------------------------------------------------- 54 //-- 逻辑功能实现 55 //--------------------------------------------------------------------------- 56 //时序电路,用来给 stop_reg 寄存器赋值 57 always @ (posedge CLK_50M or negedge RST_N) 58 begin 59 if(!RST_N) 60 stop_reg <= 1'b0; //判断复位 //初始化 stop_reg 值 61 else 62 stop_reg <= stop_reg_n; //用来给 stop_reg 赋值 63 end 64 65 //组合电路,用于控制数字时钟的暂停与开始 66 always @ (*) 67 begin 68 if(key_out[7]) //判断 KEY8 有没有按下 69 stop_reg_n = ~stop_reg; //当按键按下时,改变 stop_reg 寄存器的状态 70 else 71 stop_reg_n = stop_reg; //当按键没有按下时,stop_reg 保持原状态不变 72 end 73 //--------------------------------------------------------------------------- 74 //时序电路,用来给 time_seconds 寄存器赋值 75 always @ (posedge CLK_50M or negedge RST_N) 76 begin 77 if(!RST_N) //判断复位 78 time_seconds <= 1'b0; //初始化 time_seconds 值 79 else 80 time_seconds <= time_seconds_n; //用来给 time_seconds 赋值 81 end 82 83 //组合电路,实现 1s 的定时计数器 84 always @ (*) 85 begin 86 if(time_seconds == SEC_TIME_1S) //判断 1s 时间 87 time_seconds_n = 1'b0; //如果到达 1s,定时计数器将会被清零 88 else if(stop_reg) //判断有没有按下暂停 http://www.fpga.gs/ 84 项目实战篇 §4 89 time_seconds_n = time_seconds + 1'b1; //如果没有暂停,定时计数器将会继续累加 90 else 91 time_seconds_n = time_seconds; //否则,定时计数器将会保持不变 92 end 93 //--------------------------------------------------------------------------- 94 //时序电路,用来给 seconds1_data 寄存器赋值 95 always @ (posedge CLK_50M or negedge RST_N) 96 begin 97 if(!RST_N) 98 seconds1_data <= 1'b0; //判断复位 //初始化 seconds1_data 值 99 else 100 seconds1_data <= seconds1_data_n; //用来给 seconds1_data 赋值 101 end 102 103 //组合电路,用来控制秒数个位的进位和清零 104 always @ (*) 105 begin 106 if(time_seconds == SEC_TIME_1S | key_out[6] == 1'b1) //判断条件 107 seconds1_data_n = seconds1_data + 1'b1;//如果条件成立,seconds1_data 将会加 1 108 else if(seconds1_data == 4'd10) //判断 seconds1_data 有没有达到 10s 109 seconds1_data_n = 1'b0; //如果 seconds1_data 到达 10s,seconds1_data 将会被清零 110 else 111 seconds1_data_n = seconds1_data; //否则 seconds1_data 将会保持不变 112 end 113 //--------------------------------------------------------------------------- 114 //时序电路,用来给 seconds2_data 寄存器赋值 115 always @ (posedge CLK_50M or negedge RST_N) 116 begin 117 if(!RST_N) 118 seconds2_data <= 4'd0; //判断复位 //初始化 seconds2_data 值 119 else 120 seconds2_data <= seconds2_data_n; //用来给 seconds2_data 赋值 121 end 122 123 //组合电路,用来控制秒数十位的进位和清零 124 always @ (*) 125 begin 126 if(seconds1_data == 4'd10) //判断 seconds1_data 有没有达到 10s 127 seconds2_data_n = seconds2_data + 1'b1;//如果条件成立,seconds2_data 将会加 1 128 else if(seconds2_data == 4'd6) //判断 seconds2_data 有没有达到 60s 129 seconds2_data_n = 1'b0; //如果 seconds2_data 到达 60s,seconds2_data 将会被清零 130 else 131 seconds2_data_n = seconds2_data; //否则 seconds2_data 将会保持不变 132 end Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 85 133 //--------------------------------------------------------------------------- 134 //时序电路,用来给 minutes1_data 寄存器赋值 135 always @ (posedge CLK_50M or negedge RST_N) 136 begin 137 if(!RST_N) //判断复位 138 minutes1_data <= 4'd0; //初始化 minutes1_data 值 139 else 140 minutes1_data <= minutes1_data_n; //用来给 minutes1_data 赋值 141 end 142 143 //组合电路,用来控制分数个位的进位和清零 144 always @ (*) 145 begin 146 if(seconds2_data == 4'd6 | key_out[5] == 1'b1) //判断按键 KEY6 和判断 1m 时间 147 minutes1_data_n = minutes1_data + 1'b1;//如果条件成立,minutes1_data 将会加 1 148 else if(minutes1_data == 4'd10) //判断 minutes1_data 有没有达到 10m 149 minutes1_data_n = 1'b0; //如果 minutes1_data 达到 10m,minutes1_data 将会被清零 150 else 151 minutes1_data_n = minutes1_data; //否则 minutes1_data 将会保持不变 152 end 153 //--------------------------------------------------------------------------- 154 //时序电路,用来给 minutes2_data 寄存器赋值 155 always @ (posedge CLK_50M or negedge RST_N) 156 begin 157 if(!RST_N) //判断复位 158 minutes2_data <= 4'd0; //初始化 minutes2_data 值 159 else 160 minutes2_data <= minutes2_data_n; //用来给 minutes2_data 赋值 161 end 162 163 //组合电路,用来控制分数十位的进位和清零 164 always @ (*) 165 begin 166 if(minutes1_data == 4'd10) //判断 minutes1_data 有没有达到 10m 167 minutes2_data_n = minutes2_data + 1'b1;//如果条件成立,minutes2_data 将会加 1 168 else if(minutes2_data == 4'd6) //判断 minutes2_data 有没有达到 60m 169 minutes2_data_n = 1'b0; //如果 minutes2_data 达到 10m,minutes2_data 将会被清零 170 else 171 minutes2_data_n = minutes2_data; //否则 minutes2_data 将会保持不变 172 end 173 //--------------------------------------------------------------------------- 174 //时序电路,用来给 hours1_data 寄存器赋值 175 always @ (posedge CLK_50M or negedge RST_N) 176 begin http://www.fpga.gs/ 86 项目实战篇 §4 177 if(!RST_N) //判断复位 178 hours1_data <= 4'd2; //初始化 hours1_data 值 179 else 180 hours1_data <= hours1_data_n; //用来给 hours1_data 赋值 181 end 182 183 //组合电路,用来控制时数个位的进位和清零 184 always @ (*) 185 begin 186 if(minutes2_data == 4'd6 | key_out[4] == 1'b1) //判断按键 KEY5 和判断 1h 时间 187 hours1_data_n = hours1_data + 1'b1; //如果条件成立,hours1_data 将会加 1 188 else if((hours2_data == 4'd0 || hours2_data == 4'd1) && hours1_data == 4'd10 || (hours2_data == 4'd2 && hours1_data == 4'd4)) 189 hours1_data_n = 1'b0; //如果条件成立,hours1_data 将会被清零 190 else 191 hours1_data_n = hours1_data; //否则 hours1_data 将会保持不变 192 end 193 //--------------------------------------------------------------------------- 194 //时序电路,用来给 hours2_data 寄存器赋值 195 always @ (posedge CLK_50M or negedge RST_N) 196 begin 197 if(!RST_N) 198 hours2_data <= 1'b1; //判断复位 //初始化 hours2_data 值 199 else 200 hours2_data <= hours2_data_n; //用来给 hours2_data 赋值 201 end 202 203 //组合电路,用来控制时数十位的进位和清零 204 always @ (*) 205 begin 206 if((hours2_data == 4'd0 || hours2_data == 4'd1) && hours1_data == 4'd10 || (hours2_data == 4'd2 && hours1_data == 4'd4)) 207 hours2_data_n = hours2_data + 1'b1; //如果条件成立,hours1_data 将会被清零 208 else if(hours2_data == 4'd3) 209 hours2_data_n = 1'b0; //如果 hours2_data 等于 3,hours2_data 将会被清零 210 else 211 hours2_data_n = hours2_data; //否则 hours2_data 将会保持不变 212 end 213 //--------------------------------------------------------------------------- 214 assign LED = {seconds2_data,seconds1_data}; //将秒的数据赋值给 LED 端口 215 216 endmodule 下面我们就来简单的介绍一下该代码,第 1 至 51 行是端口声明和参数定义;第 57 至 72 行 Zircon Opto-Electronic Technology CO.,Ltd. §4 数字时钟设计实战 87 通过判断 KEY8 来实现开始或者暂停的功能;第 75 至 92 行是用来实现 1s 的定时器;第 95 至 112 行根据 1s 的定时器来实现单位秒个位的进位和清零功能,同时也可以通过判断 KEY7 来实 现。第 115 至 132 行根据单位秒的个位来实现单位秒十位的进位和清零功能。第 135 至 152 行 根据单位秒的十位来实现单位分个位的进位和清零功能,同时也可以通过判断 KEY6 来实现。 第 155 至 172 行根据单位分的个位来实现单位分十位的进位和清零功能。第 175 至 192 行根据 单位分的十位来实现单位时个位的进位和清零功能,同时也可以通过判断 KEY5 来实现。第 195 至 212 行根据单位时的个位来实现单位时十位的进位和清零功能。第 214 行将秒的低 4 位和秒 的高 4 位赋值给了 LED 进行显示。 通过代码的讲解和分析,我们可以知道,这里使用的是进位累加计时方法来实现的数字时钟, 我们先实现了秒单位的计数,然后我们再根据秒这个单位计数分别来实现单位分和单位时。当然 实现数字时钟的方法不只这一种,我们还使用前面所讲的方法来实现,就是分别定义 1s 定时器、 10s 定时器、1m 定时器、10m 定时器、1h 定时器和 10h 定时器这么多的定时器,每个定时器控 制一个数码管进行显示,因此就可以构成一个数字时钟。对于这个方法我们这里就不再给出源代 码了,感兴趣的朋友可以自己尝试设计。这里需要我们注意的是,我们在提供的项目代码中,有 一个叫 A4_Clock_Top2 的项目,该项目和我们的 A4_Clock_Top1 所实现的功能是一样的,不 过 , 它 们 在 按 键 的 处 理 方 面 稍 有 不 同 , A4_Clock_Top1 按 下 按 键 可 以 持 续 累 加 , 而 A4_Clock_Top2 按下按键并不会累加,它只有在按下按键并松开按键以后才会加 1,对于 A4_Clock_Top2 的代码我们就不再进行讲解了,感兴趣的朋友可以自己尝试分析。 4.5.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Clock_Top1.sof 文件下载到开发板中,接着我们就 能看到数码管将会被点亮,显示 12.00.00,我们只要按下 KEY8,数字时钟就会开始运行,再按 KEY8,数字时钟就会暂停,我们可以按下 KEY5 来进行调整时,按下 KEY6 调整分,按下 KEY7 来调整秒,不仅只有 KEY5,6,7,8 按下会有音调,触摸按键 1,2,3,4 按下也同样会有音 调,触摸按键没有添加其他的功能,如果你有什么更好的创意都可以利用触摸按键来实现。如果 想要复位那么只需要按下复位键即可,数字时钟运行如图 4.22 所示。 http://www.fpga.gs/ 88 项目实战篇 §4 图 4.22 数字时钟项目板级调试图 Zircon Opto-Electronic Technology CO.,Ltd. 多终端点歌系统设计分析 第五章 多终端点歌系统设计分析 学习完了数字时钟项目,接下来我们将继续学习第三个项目,也就是我们的多终端点歌系统。 什么是多终端点歌系统呢?想必大家和朋友聚会的时候都去过 KTV 唱歌,在 KTV 的包厢里,我 们可以在 KTV 点歌台上使用触摸屏来点歌换歌,也可以在 KTV 四周的墙壁上使用墙上的按键 进行点歌换歌,当然,我们还可以使用遥控器进行一个点歌换歌。其实这就是一个典型的多终端 点歌系统。 我们这里设计的多终端点歌系统并没有 KTV 包厢里的点歌系统复杂,我们设计的是一个简 易的多终端点歌系统,我们通过 A4 开发板上的 UART、红外和 PS/2 这三个外设实现了对蜂鸣 器的一个发声控制。通过这个系统的学习,我们可以让大家了解到 KTV 包厢里的多终端点歌系 统的工作原理,同时也可以让大家知道更多的人机交互方式。在之前的学习中,我们是通过 A4 开发板上的按键控制蜂鸣器来进行一个人机交互,而在接下来的学习中,我们可以使用计算机通 过 UART 来控制蜂鸣器,我们也可以通过红外遥控器来控制蜂鸣器,我们还可以通过 PS/2 键盘 来控制蜂鸣器。在开始设计我们的多终端点歌系统项目之前,我们还是先来看下我们的多终端点 歌系统项目划分为哪几个知识点,如图 5.1 所示。 Uart控制原理 Uart实际应用 高级外设学习 红外控制原理 红外实际应用 多终端点歌系统 PS/2控制原理 PS/2实际应用 图 5.1 实现多终端点歌系统项目的工程框架图 通过该框架图我们可以看出,我们现在学习的外设已经不再是之前的 LED、按键、数码管 和蜂鸣器这些基础外设了,我们现在学习的外设是 UART、红外和 PS/2 这些高级外设。在这些 高级外设的学习过程中,我们并没有采用之前的介绍方法,我们这里是将基本原理和实际应用分 开进行讲解,也许有的朋友就会问,为什么要分开进行讲解呢?这是因为我们的高级外设要比我 们的基础外设复杂的多,在我们基础外设的学习中,我们知道 LED、按键、蜂鸣器和数码管这四 个外设,它们没有控制条件,时序方面也是没有要求,我们只要给它们高电平或低电平就能够控 制它们实现各种效果。在我们高级外设的学习中,如果我们要想控制它们,那么我们就必须要遵 循它们的通信协议,如果我们不去遵循这个协议,那么我们就不能使用它们完成通信。当我们依 次学习完了这三个高级外设之后,我们就可以将这三个外设相结合,最终实现我们的多终端点歌 系统。 §5.1 UART控制原理 5.1.1 UART 基本概述 92 项目实战篇 §5 首先我们介绍的是 UART 外设,说到 UART,我们先来给大家说明一下什么是 UART, UART 中文名字叫通用异步收发器,英文名字叫 Universal Asynchronous Receiver/Transmitter, 它们的首字母就是 UART。这个通用异步收发器它其实就是把一些并行的数据转换成串行的数 据发送出去,同时也可以将串行的数据转换成并行的数据给接收进来。一般来说,和 UART 成 对出现的总是 RS232,RS232 是什么呢?RS232 也就是我们计算机上的串口,它的全称是 EIARS-232 C(简称 232,或者是 RS232)。其中 EIA(Electronic Industry Association)代表美国电 子工业协会,RS 是 Recommended Standard 的缩写,代表推荐标准,232 是标识符,C 表示 修改次数,它被广泛用于计算机串行接口外设连接。如果你的计算机上还有串口的话,那么你就 可以在主机箱后面看到 RS232 的接口,如图 5.2 所示。 12 3 4 5 6789 54321 9876 公头针脚排列 母头针脚排列 图 5.2 RS232 接口 从图中我们可以看到,它总共有 9 个管脚。如果你在以前就使用过串口的话,那么你就会知 道,它其实是有 25 个管脚的,随着设备的不断改进,现在 DB25 针很少看到了,代替它的是 DB9 的接口,因此现在我们也都把 RS232 接口叫做 DB9,DB9 所用到的管脚比 DB25 有所变化, 如表 5.1 所示。 表 5.1 RS232 接口管脚表 引脚 简写 功能说明 1 CD 载波侦测(Carrier Detect) 2 RXD 接收数据(Receive) 3 TXD 发送数据(Transmit) 4 DTR 数据终端准备(Data Terminal Ready) 5 GND 地址(Ground) 6 DSR 数据准备好(Data Set Ready) 7 RTS 请求发送(Request To Send) 8 CTS 清除发送(Clear To Send) 9 RI 振铃指示(Ring Indicator) 从该表中我们可以看出,它一共有 9 个引脚,在这 9 个引脚中,最重要的 3 个引脚是: 引脚 2: RxD (Receive Data ,Input 接收数据)、引脚 3: TxD (Transmit Data ,Output 发送数据)和 引脚 5: GND (地)。我们仅使用这 3 个引脚就可以进行数据的发送和接收了。如果我们大家玩过 单片机调试过串口的话,那么我们就会知道,其实我们只要将 2(RXD)、3(TXD)进行一个连 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 93 接,我们就可以使用了,剩下的管脚我们都是用于流控,一般我们是用不到。 说完了这个 RS232 管脚接口,下面我们还有一个关键点需要给大家说明。那就是 RS232 的电平逻辑,我们要知道 RS232 它遵循的是一个负逻辑,什么意思,就是逻辑“1”的话是负电 压,并且电压为-3V~-15V,逻辑“0”的话是正电压,并且电压为+3V~+15V,正好与我们传统 的 TTL 电平相反,传统的 TTL 电平 5V 为逻辑“1”,0V 为逻辑“0”。虽然-3V~-15V 和+3V~+15V 这么大的一个电压范围可以提高噪声容限,但是大家想想看,把这个传输线的电压搞这么高本身 来说是不适合应用的,特别是我们在这个低功耗电路大肆盛行的年代,我们还用这种接口去通信, 肯定是显得不经济的。当然这只是一方面,还有其他一些方面,比如说接口太多庞大等,最终导 致 RS232 这个接口慢慢的被淘汰掉了。我们从电脑上就可以看的出,记得以前每台电脑都会带 有 RS232 接口,现在我们已经很少能够在电脑上看到 RS232 接口了。没了 RS232 接口,我们 怎么实现串口通信呢?自古以来两种方法:第一种方法就是:我们通过 USB 转 RS232 接口, 如图 5.3 所示。 图 5.3 USB 转 RS232 接口 我们可以从图片中看到这种线种类很多,这种线主要起到了电平与协议转换的一个作用。如 果我们想要使用这种线,那么我们的开发板上必须要使用 RS232 接口,由于我们的 FPGA 芯片 不支持 RS232 这个-3~-15V,所以我们的开发板上还需要使用一个 RS232 电平转换芯片。也 就是说,我们 FPGA 首先输出 TTL 电平,然后经过开发板上的 RS232 电平进行一个转换,转 换成 RS232 接口电平,转换完成的 RS232 接口电平在经过 USB 转 RS232 接口,最终传输到 我们的计算机上。这就是第一种方法,这种方法大家听起来似乎理所当然,但实际上这种方法却 是画蛇添足。为什么这么说呢,因为本身来说,开发板上出来的电平就是 TTL 电平,然后再去 转换成 RS232 电平,转换完成的 RS232 电平又通过 USB 转 RS232 接口,将 RS232 接口电 平转换成计算机所识别的 USB 协议,转来转去可以说非常复杂。因此第二种方法是当下比较流 行的一个方法,我们已经抛弃了 RS232 接口和它的电平逻辑,虽然我们直接用 TTL 电平,但是 我们仍保留了 RS232 通信协议,也就是说,虽然我们还是亲切的把它称为 UART,但是实际上 它接口的电平已经完全是我们的 TTL 电平,并且传输在我们的 USB 总线上。我们 A4 开发板上 便是采用的这种方法,使用的是 CP2102 转换芯片,原理如图 5.4 所示。 http://www.fpga.gs/ 94 项目实战篇 §5 图 5.4 A4 开发板上的 UART 原理图 从该图中我们可以看出,这个转换芯片 CP2102 的作用就是把我们的 USB 协议转换成 TTL 电平的 UART。通过这种方法,我们就能够简化设计、增加系统的可靠性和减少系统功耗,从而 避免了转换到电平范围非常高的-3V~-15V 和+3V~+15V。此外,我们还可以从该图中看出,这 个 USB 接口我们还用作了供电,也就是说,我们 A4 开发板上的 USB 电缆,它不但充当了电源 线,而且还充当了 UART 线。这里我们需要说明的是,当 CP2102 与主机连接后,我们必须根 据操作系统选择相对应的驱动程序,当驱动程序安装完成之后,端口设备会产生“CP2102 USB to UART Bridge Controller(COM×)”的新端口(×随计算机的配置而异),只有出现了该端口, 我们才可以正常使用它。 5.1.2 UART 通信协议 说完了 UART 基本概述,接下来我们就来讲解 UART 通信协议。UART 作为异步串行通信 接口,也就是说,它的数据传输不需要时钟,只要两条信号线分别进行数据收发。既然没有时钟, 那么它们如何来保证数据收发的准确性?很简单,收发双发只需要约定好数据传输的速度和帧 格式,简单的讲就是约定好一个数据位传输的时间和一个数据的长短。 UART 的数据传输速度是用波特率来描述的,亦即每秒钟传输的数据位,例如 1000 波特率 表示每秒钟传输 1000 比特的数据, 或者说每个数据位持续 1 毫秒。波特率不是随意的,必须服 从一定的标准,如果希望设计 123456 波特率的 RS-232 接口,这是不行的。常用的串行传输速 率值包括以下几种:1200 波特率,9600 波特.率 ,38400 波特率,115200 波特率等。在 115200 波特传输速度下,每位数据持续 (1/115200) = 8.68us。如果传输 8 位数据,共持续 8 x 8.68us = 69.44us。但是每个字节的传输又要求额外的“开始位”和“停止位(1 位)”,所以实际上需要 花费 10 x 8.68us = 86.8us 的时间。因此最大的有效数据传输率只能达到 11.5KBytes 每秒。 说完了 UART 数据传输速度,下面我们再来看下 UART 的帧格式,如图 5.5 所示, Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 95 一个字符帧 0 1 1 起 LSB 始 位 有效数据位 MSB 奇 偶 检 验 停 止 位 空 闲 位 位 图 5.5 UART 的数据传输格式 通过该图我们可以看出,它是由 1 个起始位(必须为 0)、8 个数据位(用户数据)、1 个奇 偶校验位(用于简单地纠错以保证传输可靠性)和 1 或 2 个停止位(必须为 1)组成。除了奇偶 校验位,其他 3 个部分都是必须的。通过这个 UART 帧格式图我们还能够看出,UART 的通信 是十分简单的,它是以低电平作为起始位,高电平作为停止位,中间可传输 5~8 比特数据和 1 比特奇偶校验位,奇偶校验位的有无和数据比特的长度由通信双方约定。发送端发送数据时先发 一低电平,然后发送 8 比特数据,之后马上把信号拉高,从而完成一帧数据传送。接收端接收到 低电平时开始计数,然后接收 8 比特信息位后如果检测到高电平即认为已接收完一帧数据,继 续等待下一帧起始信号低电平的到来,若接收完 8 比特数据后没有检测到高电平则认为这不是 一帧有效数据,将其丢弃,继续等待起始信号。收发可同时进行,互不干扰。一帧数据传输完毕 后可以继续传输下一帧数据,也可以继续保持为高电平,两帧之间保持高电平,持续时间可以任 意长。 为了让读者能进一步理解,这里举例说明,来看看 0x55 是如何传输的,如图 5.6 所示 图 5.6 0x55 的二进制表示为:01010101 但是由于先发送的是最低有效位,所以发送序列是这样的: 1-0-1-0-1-0-1-0。下面是另外 一个例子来说明传输速率的重要性,如图 5.7 所示。 Line is idle Star bit Bit 0 Bit 7 Stop bit Line is idle again 图 5.7 传输的数据为 0xC4,你能看出来吗? http://www.fpga.gs/ 96 项目实战篇 §5 从该图中我们很难看出来所传输的数据,这也说明了事先知道传输的速率对于接收端有多 么重要。至此,我们就完成了 UART 基本原理的讲解,接下来我们将由原理转换为实战,通过 UART 具体工程来对 UART 的实际应用进行一个讲解。 §5.2 UART实际应用 5.2.1 功能概述 首先我们先来说下我们的工程将要实现一个怎样的功能:我们使用计算机通过 UART 向我 们的 A4 开发板发送 0~9 这 10 个数字,然后我们根据这 10 个不同的数字来操控我们的蜂鸣器 发出不同的音调。当我们发送 1 的时候,我们的蜂鸣器会连续发出中音 dao 的音调。当我们发 送 2 的时候,我们的蜂鸣器会连续发出中音 rui 的音调。当我们发送 8 的时候,我们的蜂鸣器会 连续发出高音 dao 的音调,当我们发送 9 的时候,我们的蜂鸣器会连续发出高音 rui 的音调。最 后当我们发送 0 的时候,我们的蜂鸣器会停止发出声音。 5.2.2 设计说明 根据上面的功能概述,我们可以设计出如图 5.8 所示的功能模块。 图 5.8 UART 外设的框架结构图 从该图中我们可以看出,该程序主要分成了 3 个模块,它们分别是 Uart_Bps_Module、 Uart_Rx_Module 和 Beep_Module 。 Uart_Bps_Module 用 于 生 成 波 特 率 提 供 给 Uart_Rx_Module 模块使用的。Uart_Rx_Module 是用于控制我们的串口外设来接收计算机发送 的 0~9 十个数字。Beep_Module 根据我们接收到的不同的数字来驱动蜂鸣器发出不同的音调。 根据这个功能模块我们还可以看到,我们主要使用了 CLK_50M、RST_N、UART_RX 和 BEEP 这 4 个管脚,下面我们对这 4 个管脚进行一个分配,如图 5.9 所示。 图 5.9 UART 外设的管脚分配图 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 97 这里我们需要说明的是,我们程序中不仅给出了 UART 的接收模块,而且还给出了 UART 的发送模块,由于本实例中没有使用到发送模块,所以我们就没有对发送模块进行管脚分配,并 且我们还将发送模块的代码给注释掉了。 5.2.3 源码解析 介绍完了设计,接下来我们就来对源码进行一个分析。首先我们分析的是 Uart_Bps_Module 模块代码,如代码 5.1 所示。 代码 5.1 Uart_Bps_Module.v 代码 1 module Uart_Bps_Module 2( 3 //输入端口 4 CLK_50M,RST_N,bps_start, 5 //输出端口 6 bps_flag 7 ); 8 9 //--------------------------------------------------------------------------10 //-- 外部端口声明 11 //--------------------------------------------------------------------------- 12 input CLK_50M; //时钟的端口,开发板用的 50M 晶振 13 input RST_N; //复位的端口,低电平复位 14 input bps_start; //接收和发送端口的波特率时钟启动信号 15 output bps_flag; //接收数据位的中间采样点,发送数据的数据改变点 16 17 //--------------------------------------------------------------------------18 //-- 内部端口声明 19 //--------------------------------------------------------------------------- 20 reg [12:0] time_cnt; //分频计数 21 reg [12:0] time_cnt_n; //time_cnt 的下一个状态 22 reg bps_flag; //接收数据位的中间采样点,发送数据的数据改变点 23 reg bps_flag_n; //bps_flag 的下一个状态 24 25 //计算方式为,波特率为 115200 ,1/115200 每一位的周期是 8.68us, 8.68 / (1 /50) = 434 26 parameter BPS_PARA = 9'd434; //波特率为 115200 时的分频计数值 27 parameter BPS_PARA_2 = 8'd217; //波特率为 115200 时的分频计数值的一半 28 29 //--------------------------------------------------------------------------30 //-- 逻辑功能实现 31 //--------------------------------------------------------------------------32 //时序电路,用来给 time_cnt 寄存器赋值 33 always @ (posedge CLK_50M or negedge RST_N) 34 begin http://www.fpga.gs/ 98 项目实战篇 §5 35 if(!RST_N) //判断复位 36 time_cnt <= 13'b0; //初始化 time_cnt 值 37 else 38 time_cnt <= time_cnt_n; //用来给 time_cnt 赋值 39 end 40 41 //组合电路,1 位数据需要 8.68us,实现 8.68us 的定时器 42 always @ (*) 43 begin 44 if((time_cnt == BPS_PARA) || (!bps_start)) 45 time_cnt_n = 1'b0; //波特率计数清零 46 else 47 time_cnt_n = time_cnt + 1'b1; //波特率时钟计数启动 48 end 49 50 //时序电路,用来给 bps_flag 寄存器赋值 51 always @ (posedge CLK_50M or negedge RST_N) 52 begin 53 if(!RST_N) //判断复位 54 bps_flag <= 1'b0; //初始化 bps_flag 值 55 else 56 bps_flag <= bps_flag_n; //用来给 bps_flag 赋值 57 end 58 59 //组合电路,判断接收数据位的中间采样点,发送数据的数据改变点 60 always @ (*) 61 begin 62 if(time_cnt == BPS_PARA_2) 63 bps_flag_n = 1'b1; //判断时间有没有到达 4.43us //如果达到,将采样标志位置 1 送出 64 else 65 bps_flag_n = 1'b0; //如果没有达到,将采样完成标志位置 0 送出 66 end 67 68 endmodule 从该代码中我们可以看出,该程序很简单,它主要是根据串口收发模块的控制,产生固定周 期的定时指示信号。接下来我们再来看下 Uart_Rx_Modul 模块代码,如代码 5.2 所示。 代码 5.2 Uart_Rx_Module.v 代码 1 module Uart_Rx_Module 2 ( 3 //输人端口 4 CLK_50M,RST_N,UART_RX,rx_bps_flag, 5 //输出端口 6 out_rx_data,rx_bps_start Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 99 7 ); 8 9 //--------------------------------------------------------------------------- 10 //-- 外部端口声明 11 //--------------------------------------------------------------------------- 12 input CLK_50M; //时钟的端口,开发板用的 50M 晶振 13 input RST_N; //复位的端口,低电平复位 14 input UART_RX; 15 input rx_bps_flag; 16 output rx_bps_start; 17 output [ 7:0] out_rx_data; //FPGA 的接收端口,串口 CP2102 的发送端口 //接收数据位的中间采样点 //接收端口的波特率时钟启动信号 //从 UART_RX 数据线中解析完后的数据 18 19 //--------------------------------------------------------------------------- 20 //-- 内部端口声明 21 //--------------------------------------------------------------------------- 22 reg 23 wire [ 1:0] [ 1:0] detect_edge; detect_edge_n; //记录 UART 的开始脉冲,即第一个下降沿 //detect_edge 的下一个状态 24 reg negedge_reg; //下降沿标志 25 wire negedge_reg_n; //negedge_reg 的下一个状态 26 reg rx_bps_start; //接收端口的波特率时钟启动信号 27 reg 28 reg 29 reg 30 reg [ 3:0] [ 3:0] [ 7:0] rx_bps_start_n; bit_cnt; bit_cnt_n; shift_data; //rx_bps_start 的下一个状态 //用来记录接收数据位 //bit_cnt 的下一个状态 //接收串行数据流中用到的移位寄存器 31 reg [ 7:0] shift_data_n; //shift_data 的下一个状态 32 reg [ 7:0] out_rx_data; //从 UART_RX 数据线中解析完后的数据 33 reg [ 7:0] out_rx_data_n; //out_rx_data 的下一个状态 34 35 //--------------------------------------------------------------------------- 36 //-- 逻辑功能实现 37 //--------------------------------------------------------------------------- 38 //时序电路,用来给 detect_edge 寄存器赋值 39 always @ (posedge CLK_50M or negedge RST_N) 40 begin 41 if(!RST_N) 42 detect_edge <= 2'b11; //判断复位 //初始化 detect_edge 值 43 else 44 detect_edge <= detect_edge_n; //用来给 detect_edge 赋值 45 end 46 47 //组合电路,用来接收 UART_RX 信号 48 assign detect_edge_n = {detect_edge[0], UART_RX}; 49 50 //时序电路,用来给 negedge_reg 寄存器赋值 http://www.fpga.gs/ 100 项目实战篇 §5 51 always @ (posedge CLK_50M or negedge RST_N) 52 begin 53 if(!RST_N) 54 negedge_reg <= 1'b0; //判断复位 //初始化 negedge_reg 值 55 else 56 negedge_reg <= negedge_reg_n; //用来给 negedge_reg 赋值 57 end 58 59 //组合电路,判断下降沿,如果下降沿到来,negedge_reg_n 就为 1 60 assign negedge_reg_n = (detect_edge == 2'b10) ? 1'b1 : 1'b0; 61 62 //时序电路,用来给 rx_bps_start 寄存器赋值 63 always @ (posedge CLK_50M or negedge RST_N) 64 begin 65 if(!RST_N) 66 rx_bps_start <= 1'b0; //判断复位 //初始化 rx_bps_start 值 67 else 68 rx_bps_start <= rx_bps_start_n; //用来给 rx_bps_start 赋值 69 end 70 71 //组合电路,如果下降沿到来,那么将启动波特率计数器 72 always @ (*) 73 begin 74 if(negedge_reg) //判断下降沿 75 rx_bps_start_n = 1'b1; //如果下降沿到来,将 rx_bps_start_n 置 1 76 else if(bit_cnt == 4'd9) //判断数据有没有接收完成 77 rx_bps_start_n = 1'b0; //如果数据接收完成,将 rx_bps_start_n 置 0 78 else 79 rx_bps_start_n = rx_bps_start; //否则,将保持不变 80 end 81 82 //时序电路,用来给 bit_cnt 寄存器赋值 83 always @ (posedge CLK_50M or negedge RST_N) 84 begin 85 if(!RST_N) 86 bit_cnt <= 4'b0; //判断复位 //初始化 bit_cnt 值 87 else 88 bit_cnt <= bit_cnt_n; //用来给 bit_cnt 赋值 89 end 90 91 //组合电路,如果到达数据位的中间采样点,那么接收数据位计数器加 1,采样下一个数据位 92 always @ (*) 93 begin 94 if(rx_bps_flag) //判断有没有达到数据位的中间采样点 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 101 95 bit_cnt_n = bit_cnt + 1'b1; //如果到达,那么接收数据位计数器加 1, 96 else if(bit_cnt == 4'd9) 97 bit_cnt_n = 1'b0; //判断数据有没有接收完成 //如果数据接收完成,接收数据位计数器清零 98 else 99 bit_cnt_n = bit_cnt; //否则,将保持不变 100 end 101 102 //时序电路,用来给 shift_data 寄存器赋值 103 always @ (posedge CLK_50M or negedge RST_N) 104 begin 105 if(!RST_N) //判断复位 106 shift_data <= 8'b0; //初始化 shift_data 值 107 else 108 shift_data <= shift_data_n; //用来给 shift_data 赋值 109 end 110 111 //组合电路,采样每个数据位的中心时间点,每采样一个便开始启动移位寄存器记录数据 112 always @ (*) 113 begin 114 if(rx_bps_flag) //判断有没有达到数据位的中间采样点 115 shift_data_n = {UART_RX,shift_data[7:1]}; //如果到达,那么接收 UART_RX 中的数据 116 else 117 shift_data_n = shift_data; //否则,将保持不变 118 end 119 120 //时序电路,用来给 out_rx_data 寄存器赋值 121 always @ (posedge CLK_50M or negedge RST_N) 122 begin 123 if(!RST_N) 124 out_rx_data <= 8'b0; //判断复位 //初始化 out_rx_data 值 125 else 126 out_rx_data <= out_rx_data_n; //用来给 out_rx_data 赋值 127 end 128 129 //组合电路,如果数据接收完成,那么将移位寄存器中的数据输出 130 always @ (*) 131 begin 132 if(bit_cnt == 4'd9) //判断数据有没有接收完成 133 out_rx_data_n = shift_data; //如果接收完成,那么将移位寄存器中的数据输出 134 else 135 out_rx_data_n = out_rx_data; //否则,将保持不变 136 end 137 138 endmodule http://www.fpga.gs/ 102 项目实战篇 §5 从代码中我们可以看出,该程序不断采集 UART_RX 信号,当检测到其下降沿后,启动内 部的波特率计数使能信号 rx_bps_start,同时在波特率采样信号 rx_bps_flag 的控制下,去采集 8bit 的有效数据。当数据 采集完毕,则发送指示信号 tx_start_flag 并送出采 集到的数据 out_rx_data 给蜂鸣器模块。介绍完了 UART 的接收模块,下面我们再来看下蜂鸣器模块代码, 如代码 5.3 所示。 代码 5.3 Beep_Module.v 代码 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : Beep_Module.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : 蜂鸣器发声模块 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 module Beep_Module 8 ( 9 //输入端口 10 CLK_50M,RST_N,KEY, 11 //输出端口 12 BEEP 13 ); 14 15 //--------------------------------------------------------------------------- 16 //-- 外部端口声明 17 //--------------------------------------------------------------------------- 18 input CLK_50M; //时钟的端口,开发板用的 50MHz 晶振 19 input RST_N; //复位的端口,低电平复位 20 input [ 7:0] KEY; //按键端口 21 output BEEP; //蜂鸣器端口 22 23 //--------------------------------------------------------------------------24 //-- 内部端口声明 25 //--------------------------------------------------------------------------- 26 reg [15:0] time_cnt; //用来控制蜂鸣器发声频率的定时计数器 27 reg [15:0] time_cnt_n; //time_cnt 的下一个状态 28 reg [15:0] freq; //各种音调的分频值 29 reg [15:0] freq_n; //各种音调的分频值 30 reg beep_reg; //用来控制蜂鸣器发声的寄存器 31 reg beep_reg_n; //beep_reg 的下一个状态 32 33 //--------------------------------------------------------------------------34 //-- 逻辑功能实现 35 //--------------------------------------------------------------------------36 //时序电路,用来给 time_cnt 寄存器赋值 37 always @ (posedge CLK_50M or negedge RST_N) Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 38 begin 39 if(!RST_N) 40 time_cnt <= 16'b0; //判断复位 //初始化 time_cnt 值 41 else 42 time_cnt <= time_cnt_n; //用来给 time_cnt 赋值 43 end 44 45 //组合电路,判断频率,让定时器累加 46 always @ (*) 47 begin 48 if(time_cnt == freq) //判断分频值 49 time_cnt_n = 16'b0; //定时器清零操作 50 else 51 time_cnt_n = time_cnt + 1'b1; //定时器累加操作 52 53 end 54 55 //时序电路,用来给 beep_reg 寄存器赋值 56 always @ (posedge CLK_50M or negedge RST_N) 57 begin 58 if(!RST_N) 59 beep_reg <= 1'b0; //判断复位 //初始化 beep_reg 值 60 else 61 beep_reg <= beep_reg_n; //用来给 beep_reg 赋值 62 end 63 64 //组合电路,判断频率,使蜂鸣器发声 65 always @ (*) 66 begin 67 if(time_cnt == freq) //判断分频值 68 beep_reg_n = ~beep_reg; //改变蜂鸣器的状态 69 else 70 beep_reg_n = beep_reg; //蜂鸣器的状态保持不变 71 end 72 73 //时序电路,用来给 beep_reg 寄存器赋值 74 always @ (posedge CLK_50M or negedge RST_N) 75 begin 76 if(!RST_N) //判断复位 77 freq <= 16'b0; //初始化 beep_reg 值 78 else 79 freq <= freq_n; //用来给 beep_reg 赋值 80 end 81 http://www.fpga.gs/ 103 104 项目实战篇 §5 82 //组合电路,按键选择分频值来实现蜂鸣器发出不同声音 83 //中音 do 的频率为 523.3hz,freq = 50 * 10^6 / (523 * 2) = 47774 84 always @ (*) 85 begin 86 case(KEY) 87 8'h30: freq_n = 16'd0; //没有声音 88 8'h31: freq_n = 16'd47774; //中音 1 的频率值 262Hz 89 8'h32: freq_n = 16'd42568; //中音 2 的频率值 587.3Hz 90 8'h33: freq_n = 16'd37919; //中音 3 的频率值 659.3Hz 91 8'h34: freq_n = 16'd35791; //中音 4 的频率值 698.5Hz 92 8'h35: freq_n = 16'd31888; //中音 5 的频率值 784Hz 93 8'h36: freq_n = 16'd28409; //中音 6 的频率值 880Hz 94 8'h37: freq_n = 16'd25309; //中音 7 的频率值 987.8Hz 95 8'h38: freq_n = 16'd23889; //高音 1 的频率值 1046.5Hz 96 8'h39: freq_n = 16'd21276; //高音 2 的频率值 1175Hz 97 default : freq_n = freq; 98 endcase 99 end 100 101 assign BEEP = beep_reg; //最后,将寄存器的值赋值给端口 BEEP 102 103 endmodule 从蜂鸣器模块中我们可以看出,该代码和之前的代码基本一致,唯一改动的地方就是我们的 按键接收部分,我们将按键接收部分修改成了串口发送的 0-9 这 10 个数字,由于串口发送过来 的数字 1 对应的是 ASCII 码表中的 1,我们通过查看 ASCII 码可以知道 1 对应的就是十六进制的 31。因此我们代码中使用的是 31,如果数据为 31,那么我们就执行中音 dao 的发声操作。 5.2.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Uart_Top.sof 文件下载到开发板中,接着我们打开 串口工具 SecureCRT,然后我们发送数字 1 按下回车之后,我们便会听到 A4 开发板上的蜂鸣 器一直连续发出中音 dao 的声音。如果这时我们在发送数字 2 按下回车之后,A4 开发板上的蜂 鸣器会立刻发出中音 rui 的声音。依次类推,如果你不想让蜂鸣器发出声音,那么你只需要发送 数字 0 即可实现。如图 5.10 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 105 §5.3 红外控制原理 图 5.10 UART 外设的板级调试图 5.3.1 红外基本概述 介绍完了 UART 外设,接下来我们就来介绍第二个红外外设。首先我们来给大家说明一下 什么是红外,如图 5.11 所示。 图 5.11 电磁波 从该图中我们可以看到,整个电磁波谱包括了无线电波、红外线、紫外线以及 X 射线等。它 们的波长不同,其中波长在 400~760ns 之间就是一般的可见光,可见光由七种颜色不一的光组 http://www.fpga.gs/ 106 项目实战篇 §5 成,即红、橙、黄、绿、蓝、靛、紫。颜色不同,波长也不同:波长最长的是红色光,接下来是 橙、黄、绿、蓝、靛、紫。也就是说紫色光波长最短。红外光又叫红外线,波长比可见光要长的 电磁波,波长为 1 毫米到 770 纳米之间,光谱上面在红色光的外侧。 我们知道了红外光之后,接下来我们再来说下红外光是如何进行通信的,我们都知道红外遥 控器已被广泛使用在各种类型的家电产品上,就比如说我们的电视机是最早用到我们的遥控器 的,现在我们的空调等等。这些遥控器都是利用 950nm 近红外波段的红外线作为传递信息的媒 体。一般的通用红外遥控系统,如图 5.12 所示。 红外遥控器 一体化红外接收头 键盘 编码和调制 光电放大 解调 图 5.12 通用红外遥控系统 通过该图我们可以看出,它是由两大部分组成,一部分是发射,也就是我们的红外遥控器, 另一部分是接收。也就是我们 A4 开发板上的一体化红外接收头。通常情况下,发送端采用脉冲 宽度调试(PWM)或者是脉冲位置调试(PPM)两种方式,将二进制数字信号调制成某一频率 的脉冲序列,并驱动红外发射管以光脉冲的形式发送出去;接收端将接收到的光脉冲转换成电信 号,再经过放大、滤波等处理后送给解调电路进行解调,还原以二进制数字信号后输出,简而言 之,红外通信的实质就是对二进制数字信号进行调制与解调,以便利用红外进行传输。发射部分 主要包括键盘、红外编码芯片、LED 红外发送器;接收部分主要包括光电转换放大器电路、红外 解码芯片。下面我们就来介绍一下我们所使用的红外遥控器,如图 5.13 所示。 图 5.13 A4 开发板上所使用的红外遥控器 通过该图我们可以看出,我们所使用的红外遥控器,它是一个带电池的手持装置。它采用 UPD6122 控制芯片 NEC 编码格式,频率 38K,尺寸 90*40*12mm,发射距离标准 8 米,CR2025 电池供电,按键作用力 200-350g,按键正常寿命大于 20 万次。它设计成尽可能的减少功耗, 经受震动,以及信号尽可能的远。当没有遥控按钮按下时,它处于几乎不消耗电能的低功耗待机 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 107 模式,而当按钮按下时,它会马上唤醒发射相应红外命令。如图 5.14 所示。这就是一个发射过 程。 图 5.14 红外遥控器的发射过程 通过该图我们可以看出,该图左边是调制信号通过驱动放大由红外 LED 发射;该图右边是 信号通过接收器检测输出。在串行通信里,我们经常谈及‘marks’和‘spaces’标记。‘spaces’ 是个默认信号,是指发射管关闭状态,在‘spaces’期间,红外光不被发射。反之在‘marks’ 状态期间,红外光以特定的频率脉冲形式发射。在消费类电子产品里,脉冲频率普遍采用 30KHz 到 60KHz 这个频段。在接收端,一个‘space’信号以高电平的重现方式输出。反之一个‘mark’ 信号便是以低电平方式重现。这里需要我们注意的是,这里的‘marks’和‘spaces’不是我们 需要发送的状态‘1’和‘0’。‘marks’和‘spaces’以及 1 和 0 之间的真正关系取决于被应用 的协议。关于协议部分我们将会在下面进行讲解。说完了发射装置,下面我们再来介绍一下一体 化红外接收头,如图 5.15 所示。 图 5.15 A4 开发板上所使用的一体化红外接收头 红外接收头通常被厂家集成在一个元件中,成为一体化红外接收头。内部电路包括红外监测 二极管,放大器,限副器,带通滤波器,积分电路,比较器等,如图 5.16 所示。 图 5.16 一体化红外接收头内部电路 通过该图我们可以看出,红外监测二极管监测到红外信号,然后把信号送到放大器和限幅器, 限幅器把脉冲幅度控制在一定的水平,而不论红外发射器和接收器的距离远近。交流信号进入带 通滤波器,带通滤波器可以通过 30khz 到 60khz 的负载波,通过解调电路和积分电路进入比较 http://www.fpga.gs/ 108 项目实战篇 §5 器,比较器输出高低电平,还原出发射端的信号波形。注意输出的高低电平和发射端是反相的, 这样的目的是为了提高接收的灵敏度。红外接收头的种类很多,引脚定义也不相同,一般都有三 个引脚,包括供电脚,接地和信号输出脚。我们 A4 开发板上使用的是 IRM-56384 通用一体化 红外接收头,因为一体化红外接收头已经将低噪音放大器、限幅器、带通滤波器、解调器等都集 成在了一起,所以电路连接起来也非常简单,我们 A4 开发板的红外连接,如图 5.17 所示。 图 5.17 A4 开发板上的红外连接原理图 5.3.2 红外通信协议 在同一个遥控电路中通常要使用实现不同的遥控功能或区分不同的机器类型,这样就要求 信号按一定的编码传送,编码则会由编码芯片或电路完成。对应于编码芯片通常会有相配对的解 码芯片或包含解码模块的应用芯片。在实际的产品设计中,编码芯片不一定能完成我们要求的功 能,这时我们就需要了解所使用的编码芯片到底是如何编码的。只有知道编码方式,我们才可以 定制解码方案。由于我们的红外遥控器采用的 NEC 编码格式,所以我们这里就以 NEC 协议组 成为例进行讲解说明。NEC 协议采用的是脉宽调制(PWM)的串行码,以脉宽为 0.565ms、间 隔 0.56ms、周期为 1.125ms 的组合表示二进制的“0”;以脉宽为 0.565ms、间隔 1.685ms、周 期为 2.25ms 的组合表示二进制的“1”,如图 5.18 所示。 “” “” 图 5.18 NEC 协议中的 0 与 1 的波形 从图中我们可以看到,“0”和“1”均以 0.565ms 的低电平开始,不同的是高电平的宽度不 同,“0”为 0.56ms,“1”为 1.68ms,所以必须根据高电平的宽度区别“0”和“1”。当按下遥 控器的按键时,遥控器将发出一串二进制代码,我们称它为一帧数据,一帧数据编码格式如图 5.19 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 109 引导码 用户码 用户码 数据码 数据反码 停止位 图 5.19 一帧数据编码格式 从该图中我们可以看出,一帧数据主要包括了引导码、用户码、数据码和数据码反码。编码 总共 32 位。引导码是告诉接收头准备接收红外遥控码,用户码用于识别不同的遥控芯片。数据 码是用于我们发送的红外遥控器数据,数据反码是数据码反相后的编码,编码时可用于对数据的 纠错。注意:第二段的用户码也可以在遥控应用电路中被设置成第一段用户码的反码。遥控器发 射代码时,均是低位在前。高位在后。其中引导码高电平为 9ms,低电平为 4.5ms,当接收到此 码时,表示一帧数据的开始。经 38KHz 的载频进行二次调制以提高发射效率,达到降低电源功 耗的目的。然后再通过红外发射二极管产生红外线向空间发射。编码数据,载波,发射,接收解 码。这里要注意的一点就是这个协议在被红外遥控器发送出去之前,经过了一次反向器,反向器 的作用是 1 变为 0,0 变为 1,那么上面的整个协议则电平反过来接收。9ms 本来是高电平的, 那么将会变成低电平,以此类推如图 5.20 所示。 发射数据 载波 红外发射 引导码 起始码 接收解码 图 5.20 红外接收解码波形图 NEC 协议按键输出有二种方式:一种是每次按键都输出完整的一帧数据,波形如图 5.21 所 示: Tf Tf =108 ms 455KHz晶振 图 5.21 单一按键波形图 http://www.fpga.gs/ 110 项目实战篇 §5 另一种是按下相同的按键后每发送完整的一帧数据后,再发送重复码,再到按键被松开。波 形如图 5.22 所示。 Tf =108 ms 455KHz晶振 Tf Tf 图 5.22 连续按键波形图 重复码的具体参数如图 5.23 所示。 §5.4 红外实际应用 图 5.23 重复码的具体参数波形图 5.4.1 功能概述 首先我们先来说下我们的工程将要实现一个怎样的功能:我们使用红外遥控器向我们的 A4 开发板的红外接收头上发送 0~9 这 10 个数字,然后我们根据这 10 个不同的数字来操控我们的 蜂鸣器发出不同的音调。当我们发送 1 的时候,我们的蜂鸣器会连续发出中音 dao 的音调。当 我们发送 2 的时候,我们的蜂鸣器会连续发出中音 rui 的音调。依次类推,当我们发送 0 的时候, 我们的蜂鸣器会停止发出声音。我们可以看到这个功能和我们的 UART 基本上是一样的。我们 只是将由 UART 通信方式改成了红外通信方式。 5.4.2 设计说明 根据上面的功能概述,我们可以设计出如图 5.24 所示的功能模块。 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 111 图 5.24 红外外设的框架结构图 从 该 图 中 我 们 可 以 看 出 , 该 程 序 主 要 分 成 了 2 个 模 块 , 它 们 分 别 是 Ir_Module 和 Beep_Module。Ir_Module 是用于解析我们红外遥控器向红外接收头上发送的 0~9 十个数字。 Beep_Module 根据我们接收到的不同的数字来驱动蜂鸣器发出不同的音调。根据这个功能模块 我们还可以看到,我们主要使用了 CLK_50M、RST_N、IR_DATA 和 BEEP 这 4 个管脚,下面 我们对这 4 个管脚进行一个分配,如图 5.25 所示。 图 5.25 红外外设的管脚分配图 5.4.3 源码解析 介绍完了设计,接下来我们就来对源码进行一个分析。由于我们这里的蜂鸣器模块和我们串 口的蜂鸣器模块基本上是一样的,所以我们这里就不再进行介绍了,我们这里就主要介绍红外模 块代码,如代码 5.4 所示。 代码 5.4 Ir_Module.v 代码 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : Ir_Module.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : 红外模块 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 8 /*Timing control. 9 `define HEAD_HIGH 24'h6_DDD0 // 9.000ms @ 50MHz, standard is 24'h6_DDD0. 10 `define HEAD_LOW 24'h3_6EE8 // 4.500ms @ 50MHz, standard is 24'h3_6EE8 11 `define BIT_0_HIGH 24'h6D60 // 0.560ms @ 50MHz, standard is 24'h6D60 12 `define BIT_0_LOW 24'h6E5A // 0.565ms @ 50MHz, standard is 24'h6E5A 13 `define BIT_1_HIGH 24'h6D60 // 0.560ms @ 50MHz, standard is 24'h6D60 14 `define BIT_1_LOW 24'h1_4A14 // 1.685ms @ 50MHz, standard is 24'h1_4A14 15 `define REP_HEAD_HIGH 24'h6_DDD0 // 9.000ms @ 50MHz, standard is 24'h6_DDD0. 16 `define REP_HEAD_LOW 24'h1_B774 // 2.250ms @ 50MHz, standard is 24'h1_B774 17 `define REP_BIT_HIGH 24'h6D60 // 0.560ms @ 50MHz, standard is 24'h6D60 18 `define REP_BIT_LOW 24'hF4240 // 20.00ms @ 50MHz, standard is 24'hF4240 */ 19 20 //high_time 为红外引导码 9ms 低电平,(high_time[23:14] == `HEAD_HIGH) 21 //24'h6_DDD0 = (0110_11)01_1101_1101_0000 的[23:14]是(01_1011) = 10'h1B 22 //1B+14 个 0=6C000 X 20ns = 8.85ms 23 `define HEAD_HIGH 10'h1B //(0110_11)01_1101_1101_0000 约 8.85ms http://www.fpga.gs/ 112 项目实战篇 §5 24 `define HEAD_LOW 10'hD //(0011_01)10_1110_1110_1000 约 4.26ms 25 `define BIT_0_HIGH 10'h1 //(0000_01)10_1101_0110_0000 约 0.33ms 26 `define BIT_0_LOW 10'h1 //(0000_01)10_1110_0101_1010 约 0.33ms 27 `define BIT_0_LOW2 10'h2 //(0000_10)00_0000_0000_0000 约 0.66ms 28 `define BIT_1_HIGH 10'h1 //(0000_01)10_1101_0110_0000 约 0.33ms 29 `define BIT_1_LOW 10'h5 //(0001_01)00_1010_0001_0100 约 1.64ms 30 `define BIT_1_LOW2 10'h4 //(0001_00)00_0000_0000_0000 约 1.31ms 31 `define REP_HEAD_HIGH 10'h1B //(0110_11)01_1101_1101_0000 约 8.85ms 32 `define REP_HEAD_LOW 10'h6 //(0001_10)11_0111_0111_0100 约 1.97ms 33 `define REP_BIT_HIGH 10'h1 //(0000_01)10_1101_0110_0000 约 0.33ms 34 `define REP_BIT_LOW 10'h3D //(1111_01)00_0010_0100_0000 约 19.99ms 35 36 module Ir_Module 37 ( 38 //输入端口 39 CLK_50M,RST_N,IR_DATA, 40 //输出端口 41 o_ir_data 42 ); 43 44 //--------------------------------------------------------------------------- 45 //-- 外部端口声明 46 //--------------------------------------------------------------------------- 47 input CLK_50M; //系统时钟 48 input RST_N; //系统复位 49 input IR_DATA; //红外输入管脚 50 output [ 7:0] o_ir_data; //从红外读出的数据 51 52 //--------------------------------------------------------------------------- 53 //-- 内部端口声明 54 //--------------------------------------------------------------------------- 55 reg [ 3:0] ir_fsm_cs; //状态机的当前状态 56 reg [ 3:0] ir_fsm_ns; //状态机的下一个状态 57 reg 58 reg 59 reg 60 reg [23:0] [23:0] [23:0] [23:0] time_cnt; time_cnt_n; low_time; low_time_n; //计时器 //time_cnt 的下一个状态 //低电平计时器(实际是高电平) //low_time 的下一个状态 61 reg [23:0] high_time; //高电平计时器(实际是低电平) 62 reg [23:0] high_time_n; //high_time 的下一个状态 63 reg 64 reg 65 reg 66 wire [ 7:0] [ 7:0] [ 1:0] [ 1:0] bit_cnt; bit_cnt_n; detect_edge; detect_edge_n; //用来记录 8 位串行红外数据组成一个字节 //bit_cnt 的下一个状态 //检测边沿寄存器 //detect_edge 的下一个状态 67 reg [31:0] ir_data; //从红外读出的数据 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 113 68 reg [31:0] ir_data_n; //ir_data 的下一个状态 69 reg 70 reg 71 reg [31:0] [31:0] ir_data_reg; ir_data_reg_n; posedge_reg; //红外数据的缓存寄存器 //ir_data_reg 的下一个状态 //检测上升沿 72 wire posedge_reg_n; //posedge_reg 的下一个状态 73 wire head_code; //红外引导码 74 wire bit_0_code; //逻辑 0(实际逻辑 1) 75 wire 76 wire 77 wire bit_1_code; rep_head_code; rep_bit_code; //逻辑 1(实际逻辑 0) //重复引导码 //重复码 78 79 parameter FSM_IDLE = 4'h0; //空闲状态 80 parameter FSM_DATA = 4'h1; //串行数据接收状态 81 parameter 82 parameter 83 parameter FSM_DATA_END FSM_REP_BIT FSM_REP_BIT_END = 4'h2; //数据接收完成状态 = 4'h3; //处理重复码状态 = 4'h4; //重复码处理完成状态 84 85 //时序电路,用来给 detect_edge 寄存器赋值 86 always @ (posedge CLK_50M or negedge RST_N) 87 begin 88 if(!RST_N) 89 detect_edge <= 2'h0; //判断复位 //初始化 detect_edge 值 90 else 91 detect_edge <= detect_edge_n; //用来给 detect_edge 赋值 92 end 93 94 //组合电路,检测上升沿 95 assign detect_edge_n = {detect_edge[0] , {~IR_DATA}};//将红外信号取反并接收 96 97 //时序电路,用来给 posedge_reg 寄存器赋值 98 always @ (posedge CLK_50M or negedge RST_N) 99 begin 100 if(!RST_N) //判断复位 101 posedge_reg <= 1'h0; //初始化 posedge_reg 值 102 else 103 posedge_reg <= posedge_reg_n; //用来给 posedge_reg 赋值 104 end 105 106 //组合电路,判断上升沿,如果 detect_edge 等于 01,posedge_reg_n 就置 1 107 assign posedge_reg_n = (detect_edge == 2'b01) ? 1'b1 : 1'b0; 108 109 //时序电路,用来给 time_cnt 寄存器赋值 110 always @ (posedge CLK_50M or negedge RST_N) 111 begin http://www.fpga.gs/ 114 项目实战篇 §5 112 if(!RST_N) //判断复位 113 time_cnt <= 24'h0; //初始化 time_cnt 值 114 else 115 time_cnt <= time_cnt_n; //用来给 time_cnt 赋值 116 end 117 118 //组合电路,计数器用于记录高电平或者低电平的脉冲宽度 119 always @ (*) 120 begin 121 if(detect_edge[0] != detect_edge[1])//判断电平变化 122 time_cnt_n = 24'h0; //如果红外信号发生变化,time_cnt_n 就从 0 开始计数 123 else 124 time_cnt_n = time_cnt + 24'h1; //否则,time_cnt 就加 1 125 end 126 127 //时序电路,用来给 high_time 寄存器赋值 128 always @ (posedge CLK_50M or negedge RST_N) 129 begin 130 if(!RST_N) //判断复位 131 high_time <= 24'h0; //初始化 high_time 值 132 else 133 high_time <= high_time_n; //用来给 high_time 赋值 134 end 135 136 //组合电路,实际记录的是 IR_DATA 上的低电平宽度,因为上面对 IR_DATA 做了一次取反操作 137 always @ (*) 138 begin 139 if(detect_edge == 2'b10) 140 high_time_n = time_cnt; //判断下降沿 //如果判断为下降沿,则开始计数 141 else 142 high_time_n = high_time; //否则保持不变 143 end 144 145 //时序电路,用来给 low_time 寄存器赋值 146 always @ (posedge CLK_50M or negedge RST_N) 147 begin 148 if(!RST_N) //判断复位 149 low_time <= 24'h0; //初始化 low_time 值 150 else 151 low_time <= low_time_n; //用来给 low_time 赋值 152 end 153 154 //组合电路,实际记录的是 IR_DATA 上的高电平宽度,因为上面对 IR_DATA 做了一次取反操作 155 always @ (*) Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 115 156 begin 157 if(IR_DATA) 158 low_time_n = time_cnt; //判断高电平 //如果判断为高电平,则开始计数 159 else 160 low_time_n = low_time; //当 IR_DATA 变成 0 时就保持不变 161 end 162 163 //低电平至少 8.85ms,高电平至少 4.26ms,就被认为是引导码,head_code 就为 1 164 assign head_code = (high_time[23:14] == `HEAD_HIGH) && (low_time[23:14] == `HEAD_LOW) && posedge_reg; 165 //低电平至少 0.33ms,高电平至少 0.33ms 或者 0.66ms,被认为是逻辑"0",bit_0_code 就为 1 166 assign bit_0_code = (high_time[23:14] == `BIT_0_HIGH) && ((low_time[23:14] == `BIT_0_LOW) || (low_time[23:14] == `BIT_0_LOW2)) && posedge_reg; 167 //低电平至少 0.33ms,高电平至少 1.31ms 或者 1.66ms,被认为是逻辑"1",bit_1_code 就为 0 168 assign bit_1_code = (high_time[23:14] == `BIT_1_HIGH) && ((low_time[23:14] == `BIT_1_LOW) || (low_time[23:14] == `BIT_1_LOW2)) && posedge_reg; 169 //重复引导码 170 assign rep_head_code = (high_time[23:14] == `REP_HEAD_HIGH) && (low_time[23:14] == `REP_HEAD_LOW) && posedge_reg; 171 //重复码 172 assign rep_bit_code = (high_time[23:14] == `REP_BIT_HIGH) && (low_time[23:14] == `REP_BIT_LOW) && posedge_reg; 173 174 //时序电路,用来给 bit_cnt 赋值的 175 always @ (posedge CLK_50M or negedge RST_N) 176 begin 177 if(!RST_N) 178 bit_cnt <= 8'h0; //判断复位 //初始化 bit_cnt 179 else 180 bit_cnt <= bit_cnt_n; //用来给 bit_cnt 赋值 181 end 182 183 //组合电路,用来记录 8 位串行红外数据组成一个字节 184 always @ (*) 185 begin 186 if(ir_fsm_cs != FSM_DATA) 187 bit_cnt_n = 8'h0; //判断状态机当前状态是否在接收数据状态 //如果不等于,bit_cnt_n 则清零 188 else if((ir_fsm_cs == FSM_DATA) && posedge_reg)//判断状态机当前状态是否在接收数据 状态以及是否在上升沿 189 bit_cnt_n = bit_cnt + 8'h1; //如果条件成立,则记录 8 位串行红外数据 190 else 181 bit_cnt_n = bit_cnt; //否则保持不变 192 end 193 http://www.fpga.gs/ 116 项目实战篇 §5 194 //时序电路,用来给 ir_fsm_cs 赋值的 195 always @ (posedge CLK_50M or negedge RST_N) 196 begin 197 if(!RST_N) //判断复位 198 ir_fsm_cs <= FSM_IDLE; //初始化 ir_fsm_cs 的值 199 else 200 ir_fsm_cs <= ir_fsm_ns; //用来给 ir_fsm_cs 赋值 201 end 202 203 //组合电路,状态机的控制核心 204 always @ (*) 205 begin 206 case(ir_fsm_cs) //判断当前的状态 207 208 FSM_IDLE: 209 if(head_code) //收到引导码后 210 ir_fsm_ns = FSM_DATA; //进入串行接收状态 211 else if(rep_head_code) //收到重复码后 213 ir_fsm_ns = FSM_REP_BIT; //进入处理重复码状态 213 else 214 ir_fsm_ns = ir_fsm_cs; //否则保持不变 215 216 FSM_DATA: 217 if(bit_cnt == 8'h20) //接收 4 个字节(地址码,地址反码,命令码,命令反码) 218 ir_fsm_ns = FSM_DATA_END; //接收完毕后,进入数据完成状态 219 else if(rep_head_code || head_code || rep_bit_code) //判断重复码 220 ir_fsm_ns = FSM_IDLE; //进入空闲状态 221 else 222 ir_fsm_ns = ir_fsm_cs; //否则保持不变 223 224 FSM_DATA_END: 225 ir_fsm_ns = FSM_IDLE; //进入空闲状态 226 227 FSM_REP_BIT: 228 if(rep_bit_code) //判断重复码 229 ir_fsm_ns = FSM_REP_BIT_END;//进入重复码处理完成状态 230 else if(rep_head_code || head_code || bit_0_code || bit_1_code)//判断条件 231 ir_fsm_ns = FSM_IDLE; //进入空闲状态 232 else 233 ir_fsm_ns = ir_fsm_cs; //否则保持不变 234 235 FSM_REP_BIT_END: 236 ir_fsm_ns = FSM_IDLE; //进入空闲状态 237 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 238 default:ir_fsm_ns = FSM_IDLE; //进入空闲状态 239 endcase 240 end 241 242 //时序电路,用来给 ir_data_reg 赋值的 243 always @ (posedge CLK_50M or negedge RST_N) 244 begin 245 if(!RST_N) 246 ir_data_reg <= 32'h0; //判断复位 //初始化 ir_data_reg 247 else 248 ir_data_reg <= ir_data_reg_n; //用来给 ir_data_reg 赋值的 249 end 250 251 //组合电路,记录接收到的串行码 32bit,每接收一位,判断是 0 还是 1 后移位保存。 252 always @ (*) 253 begin 254 if(ir_fsm_cs == FSM_IDLE) //判断状态机的状态 255 ir_data_reg_n = 32'hFFFF; 256 else if((ir_fsm_cs == FSM_DATA) && (bit_1_code)) //判断位 1 257 ir_data_reg_n = {ir_data_reg[30:0] , 1'h1}; 258 else if((ir_fsm_cs == FSM_DATA) && (bit_0_code)) //判断位 0 259 ir_data_reg_n = {ir_data_reg[30:0] , 1'h0}; 260 else 261 ir_data_reg_n = ir_data_reg; //否则保持不变 262 end 263 264 //时序电路,用来给 ir_data 赋值的 265 always @ (posedge CLK_50M or negedge RST_N) 267 begin 268 if(!RST_N) //判断复位 269 ir_data <= 32'h0; //初始化 ir_data 270 else 271 ir_data <= ir_data_n; //用来给 ir_data 赋值 272 end 273 274 //组合电路,解码完成的状态就可以读取值了 275 always @ (*) 276 begin 277 if(ir_fsm_ns == FSM_DATA_END) //判断状态机的状态 278 ir_data_n = ir_data_reg; //解码完成的状态就可以读取值了 279 else 280 ir_data_n = ir_data; //否则保持不变 281 end 282 http://www.fpga.gs/ 117 118 项目实战篇 §5 283 assign o_ir_data = {ir_data[8],ir_data[9],ir_data[10],ir_data[11],ir_data[12], ir_data[13],ir_data[14],ir_data[15]}; 284 285 endmodule 从代码中我们可以看出,我们根据前面讲解的红外通信协议,我们定义了引导码的高电平和 低电平,位 0 的高电平和低电平,位 1 的高电平和低电平,还有重复码等等。该程序首先不断检 测采集红外信号 IR_DATA,当检测到上升沿之后,那么我们就启动程序内部的定时器,如果 IR_DATA 是高电平,那么我们就使用 low_time 这个定时器来定时来记录 IR_DATA 高电平宽 度,如果 IR_DATA 是低电平,那么我们就使用 high_time 这个定时器来记录 IR_DATA 的低电 平宽度,这两个定时器记录的电平宽度将和我们前面定义的引导码、位 0 和位 1 等进行比较,如 果符合,那么启动状态机,状态机将根据不同的状态接收 4 个字节(用户码,用户反码,数据码, 数据反码),接收完毕后,我们就可以将数据发送给我们的蜂鸣器进行判断。 5.4.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Ir_Top.sof 文件下载到开发板中,接着我们使用红 外遥控器按下数字 1,我们便会听到 A4 开发板上的蜂鸣器一直连续发出中音 dao 的声音。如果 这时我们按下数字 2,A4 开发板上的蜂鸣器会立刻发出中音 rui 的声音。依次类推,如果你不想 让蜂鸣器发出声音,那么你只需要按下红外遥控器的 0 即可实现。 §5.5 PS/2控制原理 5.5.1 PS/2 基本概述 介绍完了红外外设,接下来我们就来介绍第三个 PS/2 外设。PS/2 是在较早之前,用于鼠 标、键盘等设备。一般情况下,PS/2 接口的鼠标为绿色,键盘为紫色。PS/2 原是“personal 2” 的意思,“个人系统 2”,是 IBM 公司在上个世纪 80 年代推出的一种个人电脑。以前完全开放的 PC 标准让 IBM 觉得利益受了损失。所以 IBM 设计了 PS/2 这种电脑,目的是重新定义 PC 标 准,不再采用开放标准的方式。在这种电脑上 IBM 使用了新型 MCA 总线,新的 OS/2 操作系 统。PS/2 电脑上使用的键盘鼠标接口就是现在的 PS/2 接口。因为标准不开放,PS/2 电脑在市 场中失败了,只有 PS/2 接口一直沿用到今天。PS/2 键盘与主机的连接有 5Pin 和 6Pin 两种方 式,5Pin 的接口主要是用于 AT 键盘,目前已经基本淘汰。PS/2 鼠标只有 6Pin 一种方式,如图 5.26 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 119 外设端 1 4 3 25 主机端 1 3 4 2 5 5脚的DIN(PS/2) 1、时钟(CLOCK) 2、数据(DATA) 3、未实现,保留 4、电源地(GND) 5、电源+5V(VCC) (插头) (插座) 外设端 5 3 1 6 4 2 (插头) 主机端 6 4 2 5 3 1 (插座) 图 5.26 PS/2 接口管脚 6脚的mini-DIN(PS/2) 1、数据(DATA) 2、未实现,保留 3、电源地(GND) 4、电源+5V(VCC) 5、时钟(CLOCK) 6、未实现,保留 从图中我们可以看到,在这么多的管脚中,其实只有四个管脚是有意义的。它们分别是 Clock (时钟脚)、Data 数据脚、+5V(电源脚)和 Ground(电源地)。主机提供+5V,并且键盘/鼠 标的地线连接到主机的电源地上。数据和时钟都是集电极开路(OC)的,因此任何你连接到 PS/2 鼠标、键盘或主机的设备在时钟和数据线上都要有一个大的上拉电阻(一般取 10KΩ)。置“0” 就把线拉低,置“1”就让线上浮成高电平。我们 A4 开发板中的 PS/2 接口就是如此,大家看, 如图 5.27 所示。 图 5.27 A4 开发板上的 PS/2 连接原理图 从该图中我们可以看到,CLK 与 DAT 两个管脚都上拉了 10K 的电阻。 5.5.2 PS/2 通信协议 介绍完了 PS/2 基本概念,下面我们再来看下 PS/2 的通信协议,PS/2 鼠标和键盘履行一种 双向同步串行协议。通信的两端通过 Clock(时钟脚)同步,并通过 Data(数据脚)交换数据。 键盘/鼠标可以发送数据到主机,而主机也可以发送数据到设备,但主机总是在总线上有优先权, 它可以在任何时候抑制来自于键盘/鼠标的通信,只要把时钟拉低即可。该协议采用的短帧格式, http://www.fpga.gs/ 120 项目实战篇 §5 每一数据帧包含 11~12 个位,如表 5.2 所示。 表 5.2 PS/2 的数据帧格式 1 个起始位 总是逻辑 0 8 个数据位 低位在前 1 个奇偶校验位 奇校验 1 个停止位 总是逻辑 1 1 个应答位 仅用在主机对设备的通信中 表中,如果数据位中1的个数为偶数,校验位就为1;如果数据位中1的个数为奇数,校验 位就为 0;总之,数据位中 1 的个数加上校验位中 1 的个数总为奇数,因此总进行奇校验。在一 帧数据的通信过程中,主机在时钟的下降沿读取由外设发来的数据,外设在时钟的上升沿读取主 机发来的数据,无论是主机端发送信息还是外设端发送信息,同步时钟都是由外设来产生,一般 两设备间传输数据的同步时钟最大频率为 33KHz,大多数 PS/2 设备工作在 10~20 KHz。推荐 值在 15 KHz 左右,也就是说,Clock(时钟脚)高、低电平的持续时间都为 40us。另外我们需 要注意的是,外设每接收到主机发来的一帧数据,都需要紧随该帧的停止位发送一个应答位数据, 然后外设还要发一帧应答数据,表示外设已完整地接受到了主机的命令。而主机每收到外设发来 的一帧数据则不需要发送这个应答位,也不需要另外发送应答帧。说完了 PS/2 的数据帧格式, 下面我们就来看下设备到主机的通信过程。 当键盘或鼠标要发送信息时,它首先检测时钟线是否为高,如果时钟线不是高电平,那么主 机则禁止外设发送,外设需要把发送的信息存储起来等待总线空闲时再发送数据(键盘有 16 字 节的缓冲区,而鼠标的缓冲区仅存储最后一个要发送的数据包)。如果时钟线为高,那么表明总 线空闲,外设可以发送数据。外设发送数据的时序图如图 5.28 所示。 CLOCK DATA START DATA0 DATA1 DATA2 DATA3 DATA4 DATA5 DATA6 DATA7 PARITY STO P 图 5.28 外设发送数据到主机的时序图 从该时序图中我们可以看出,外设在时钟高电平时期将数据放到数据线上,主机在接收到时 钟下降沿后到数据线上读取数据。为了更清楚地理解这一过程。我们将其详细步骤描述如下: (1) 外设检测时钟线,如果时钟线为低,系统禁止外设发送;这时,我们延迟等待主机释放 时钟线。如果时钟线为高,接着向下执行。 (2) 外设检测数据线,如果数据线为低,则主机准备发送数据,外设改变状态,准备接收主 机的发送信息,如果数据线为高,接着向下执行。 (3) 外设将数据线拉低,发送起始位,5~25us 后开始产生时钟进行数据发送。 (4) 外设在发送过程中的前十个时钟周期里以固定的时间间隔定期地在时钟的高电平时期 检测时钟线.如果外设发现时钟线已被主机强行拉低,则该发送过程被终止。 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 121 (5) 外设最后一次检测时钟线发生在外设产生了第 10 个时钟上升沿 5us 以后。 (6) 主机在时钟的下降沿到数据线上读取数据,在收到一个完整的信息后,将时钟线拉低禁 止外设的下一个发送过程以便处理收到的信息。 (7) 主机释放时钟线(将时钟线置高)表示允许下一个外设发送过程。 该通信过程中的具体参数如下图 5.29 所示。 T2 … clk 1st T4 2st T1 T3 10st 11st T5 data … T6 参数 T1 T2 T3 描述 从数据的发送到时钟的下降沿 时钟的低电平持续时间 从时钟的上升沿到数据的发送 最小值 5 30 5 典型值 12 40 12 T4 时钟的高电平持续时间 30 40 第11个下降沿后,主机拉低时钟线,不允许 T5 0 50 下一次外设发送的禁止时间 T6 外设发送数据过程的总时间 - - 图 5.29 外设发送数据到主机的具体参数图 最大值 25 50 T4-5 50 - 2 单位 us us us us us ms 说完了设备到主机的通信,下面我们再来看下主机到设备的通信过程。在这一通信过程中, 同步时钟仍然是由外设产生的,主机发送数据前要通知外设让其产生同步时钟。为了做到这一点, 主机首先拉低时钟线以禁止外设的发送,该时钟低电平至少要维持 60us;其次主机拉低数据线, 以这两个低电平通知外设开始产生同步时钟;然后主机释放时钟线(步释放数据线),等待外设 产生的时钟将时钟线拉低,这个低电平是第一个时钟脉冲,因为此时数据线为低,这个低电平将 作为起始位被发送出去。主机在时钟的低电平时将数据放到数据线上,时钟上升沿将数据锁定在 数据线上,外设在时钟上升沿后 5~25us 内采样数据线,将数据读入。这一点与外设到主机的通 信过程不一样的,该过程的时序图如图 5.30 示。 CLOCK DATA START DATA0 DATA1 DATA2 DATA3 DATA4 DATA5 DATA6 DATA7 PARITY STOP AC K 图 5.30 主机发送数据到外设的时序图 从该时序图中我们可以看出,主机在时钟的低电平时期将数据放到数据线上,外设在发送了 时钟的上升沿后到数据线上取。为了更清楚地理解这一过程。我们将其详细步骤描述如下: (1) 主机检测外设是否正处于发送过程。若正在发送且已经发送了第 10 个时钟,则主机必 http://www.fpga.gs/ 122 项目实战篇 §5 须接收外设发送的信息;若正在进行的发送过程没有超过第 10 个时钟,则主机强行通 过将时钟线拉低终止该数据发送过程。 (2) 主机将时钟线拉低至少 60us,然后主机拉低数据线,以此低电平作为发送起始位通知 外设主机有数据要发送。 (3) 主机释放时钟线(将时钟线置为高),然后等待外设将时钟线拉低 (4) 主机根据待发数据位是 0 或 1 将数据线拉低或置高。 (5) 主机等待外设将时钟线置高,时钟线被拉高后则等待外设将时钟线拉低。 (6) 重复步骤 4-5,直到发送完奇校验位。 (7) 主机释放数据线,该高电平即作为停止位。 (8) 主机等待外设将数据线拉低(外设发给主机的握手应答位)。 (9) 主机等待外设将时钟线拉低。 (10) 主机等待外设释放时钟线和数据线。 这里需要我们注意的是,主机在时钟的低电平时期将数据放在数据线上,而应答位则是在时 钟的高电平时期放到数据线上的。该通信过程中的具体参数如下图 5.31 所示。 clk T9 data 参数 T7 T8 T9 T10 T11 T12 T7 1st T8 T10 … 2st 10st T5 Start bit bit0 … parity stop bit T11 描述 时钟的低电平持续时间 时钟的高电平持续时间 从禁止外设发送到主机 开始发送 外设采集数据的时间 从主机将起始位放到数 据线上到外设产生时钟 主机发送数据的时间 最小值 30 30 60 30 - T12 典型值 40 40 - 40 - - - 最大值 50 50 50 15 2 图 5.31 主机发送数据到外设的具体参数图 11st ACK 单位 us us us us ms ms 在主机发送的通信过程中,外设检测到主机有数据要发送时,即为主机产生同步时钟。所以 这一通信过程事实上可以分割成两部分,分别由主机和外设来做,如图 5.32 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 123 (a) (b) clock HOS T DEVICE data clock data START DATA0 DATA1 DATA2 DATA3 DATA4 DATA5 DATA6 DATA7 PARITY STOP ACK 图 5.32 主机发送数据到外设的时序图 从该图中我们可以看出,其中(a)部分是由主机完成的,(b)部分是由外设完成的。(a)部分 表示从主机将时钟线拉低到外设开始产生时钟所需的时间,该时间不得超过 15ms。(b)部分是从 外设发送同步时钟开始的主机的发送过程所需要的时间,该时间不能超过 2ms。该发送过程完 成后,主机将拉低时钟线禁止外设发送以便其进行一些必要的处理动作,然后主机将释放时钟线。 由于主机发送的命令需要外设的应答,该应答数据必须在主机释放时钟线后的 20ms 内开始发 送,否则主机将产生一个错误。至此,PS/2 的通信协议我们就讲解完了,最后我们再给大家补 充说明一下 PS/2 键盘和 PS/2 鼠标的数据包格式。 首先我们介绍的是 PS/2 键盘的数据包格式。PS/2 键盘的状态每改变一次,键盘至少会发 出三个字节的数据包,在有键按下时会向主机发送该键的通码,当按键释放时发送断码。例如: 键 A 的通码为 0x1C,键 A 的断码为:0xF0,0x1C,因此当要传送键 A 时,键盘发送的数据包 的代码是 0x1C,0xF0,0x1C。每个按键被分配了唯一的通码和断码。这样主机通过查找唯一的 扫描码就可以断定是哪个按键。每个键一整套的通断码组成了“扫描码集”。现在有三套标准的 扫描码集,分别是第一套、第二套和第三套。所有现代的键盘默认使用的是第二套扫描码。这里 需要注意的是没有一个简单的公式可以计算出扫描码,如果你想要知道某特定按键的通码和断 码,那么你将不得不查表获得。在这里,我们已经把第二套扫描码集中所有的通码和断码做成了 表格,如下表所示。 KEY A B C D E F G H I J 通码 1C 32 21 23 24 2B 34 33 43 3B 断码 F0 1C F0 32 F0 21 F0 23 F0 24 F0 2B F0 34 F0 33 F0 43 F0 3B 表 5.3 PS/2 键盘的第二套扫描码集 KEY 通码 断码 KEY 9 46 F0 46 [ ` 0E F0 0E INSERT - 4E F0 4E HOME = 55 F0 55 PG UP \ 5D F0 5D DELETE BKSP 66 F0 66 END SPACE 29 F0 29 PG DN TAB 0D F0 0D U ARROW CAPS 58 F0 58 L ARROW L SHFT 12 F0 12 D ARROW 通码 54 E0 70 E0 6C E0 7D E0 71 E0 69 E0 7A E0 75 E0 6B E0 72 断码 F0 54 E0 F0 70 E0 F0 6C E0 F0 7D E0 F0 71 E0 F0 69 E0 F0 7A E0 F0 75 E0 F0 6B E0 F0 72 http://www.fpga.gs/ 124 项目实战篇 §5 K 42 F0 42 L CTRL 14 F0 14 R ARROW E0 74 E0 F0 74 L 4B F0 4B L GUI E0 1F E0 F0 1F NUM 77 F0 77 M 3A F0 3A L ALT 11 F0 11 KP / E0 4A E0 F0 4A N 31 F0 31 R SHFT 59 F0 59 KP * 7C F0 7C O 44 F0 44 R CTRL E0 14 E0 F0 14 KP - 7B F0 7B P 4D F0 4D R GUI E0 27 E0 F0 27 KP + 79 F0 79 Q 15 F0 15 R ALT E0 11 E0 F0 11 KP EN E0 5A E0 F0 5A R 2D F0 2D APPS E0 2F E0 F0 2F KP 71 F0 71 S 1B F0 1B ENTER 5A F0 5A KP 0 70 F0 70 T 2C F0 2C ESC 76 F0 76 KP 1 69 F0 69 U 3C F0 3C F1 05 F0 05 KP 2 72 F0 72 V 2A F0 2A F2 06 F0 06 KP 3 7A F0 7A W 1D F0 1D F3 04 F0 04 KP 4 6B F0 6B X 22 F0 22 F4 0C F0 0C KP 5 73 F0 73 Y 35 F0 35 F5 03 F0 03 KP 6 74 F0 74 Z 1A F0 1A F6 0B F0 0B KP 7 6C F0 6C 0 45 F0 45 F7 83 F0 83 KP 8 75 F0 75 1 16 F0 16 F8 0A F0 0A KP 9 7D F0 7D 2 1E F0 1E F9 01 F0 01 ] 58 F0 58 3 26 F0 26 F10 09 F0 09 ; 4C F0 4C 4 25 F0 25 F11 78 F0 78 ' 52 F0 52 5 2E F0 2E F12 07 F0 07 , 41 F0 41 E0 F0 PRNT E0 12 6 36 F0 36 7C E0 . SCRN E0 7C F0 12 49 F0 49 7 3D F0 3D SCROLL 7E F0,7E / 4A F0 4A E1 14 77 8 3E F0 3E PAUSE E1 F0 14 -NONE- F0 77 介绍完了 PS/2 键盘的数据包格式,下面我们再来介绍一下 PS/2 鼠标的数据包格式,如表 5.4 所示。 表 5.4 PS/2 鼠标发送的数据包格式 Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 Byte1 Y overflow X overflow Y sign bit X sign bit Always 1 Middle Right Left Byte2 X Movement Byte3 Y Movement Byte4 Z Movement 通过该图我们可以看出,鼠标发送的数据包格式总共有 4 个 Byte,下面我们就对这 4 个 Byte 进行一个介绍:Byte1 中的 Bit0、Bit1、Bit2 分别表示左、右、中键的状态,状态值 0 表示 释放,状态值 1 表示按下。Bit3 总是 1,Bit4 和 Bit5 是 X 轴和 Y 轴的一个正位移、负位移标志 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 125 位。如果是正位移,X 轴和 Y 轴的位移计数器值就会增加。如果是负位移,X 轴和 Y 轴的位移 计数器值就会减少。Bit6 和 Bit7 是 X 轴和 Y 轴的一个溢出标志位。X 轴和 Y 轴的位移计数器可 表示的值的范围是-255 到+255,如果超过了范围,相应的溢出标识位就会被设置,并且在复位 前,位移计数器不会增减。Byte2 和 Byte3 分别是 X 轴和 Y 轴的位移计数器 Byte4 是 Z 轴的位 移计数器。这个数据包由带滚轮的三键三维鼠标产生。若是不带滚轮的三键鼠标,产生的数据包没 有 Byte4。 §5.6 PS/2实际应用 5.6.1 功能概述 首先我们先来说下我们的工程将要实现一个怎样的功能:我们使用 PS/2 键盘向我们的 A4 开发板的 PS/2 接口上发送 0~9 这 10 个数字,然后我们根据这 10 个不同的数字来操控我们的 蜂鸣器发出不同的音调。当我们发送 1 的时候,我们的蜂鸣器会连续发出中音 dao 的音调。当 我们发送 2 的时候,我们的蜂鸣器会连续发出中音 rui 的音调。依次类推,当我们发送 0 的时候, 我们的蜂鸣器会停止发出声音。其实我们可以看到这个功能和我们的串口、红外基本也是一样的。 我们只是将串口通信方式或者是红外的通信方式改成了 PS/2 通信方式。 5.6.2 设计说明 根据上面的功能概述,我们可以设计出如图 5.33 所示的功能模块。 图 5.33 PS/2 外设的框架结构图 从该图中我们可以看出,该程序主要分成了 2 个模块,它们分别是 Ps2_Module 和 Beep_Module。Ps2_Module 是用于解析我们 PS/2 键盘向 PS/2 接口上发送的 0~9 这十个数 字。Beep_Module 根据我们接收到的不同的数字来驱动蜂鸣器发出不同的音调。根据这个功能 模块我们还可以看到,我们主要使用了 CLK_50M、RST_N、PS2_CLK、PS2_DATA 和 BEEP 这 5 个管脚,下面我们对这 5 个管脚进行一个分配,如图 5.34 所示。 http://www.fpga.gs/ 126 项目实战篇 §5 图 5.34 PS/2 外设的管脚分配图 5.6.3 源码解析 介绍完了设计,接下来我们就来对源码进行一个分析。由于我们这里的蜂鸣器模块和我们 UART、红外的蜂鸣器模块基本上是一样的,所以我们这里就不再进行介绍了,我们这里就主要 介绍 Ps2 模块代码,如代码 5.5 所示。 代码 5.5 Ps2_Module.v 代码 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : Ps2_Module.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : PS2 的功能模块 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 module Ps2_Module 8 ( 9 //输入端口 10 CLK_50M,RST_N,PS2_CLK,PS2_DATA, 11 //输出端口 12 o_ps2_data 13 ); 14 15 //--------------------------------------------------------------------------16 //-- 外部端口声明 17 //--------------------------------------------------------------------------- 18 input CLK_50M; //时钟的端口,开发板用的 50M 晶振 19 input RST_N; //复位的端口,低电平复位 20 input PS2_CLK; //PS2 的时钟端口 21 input PS2_DATA; //PS2 的数据端口 22 output reg [15:0] o_ps2_data; //从 PS2 数据线中解析完后的数据。 23 24 //--------------------------------------------------------------------------25 //-- 内部端口声明 26 //--------------------------------------------------------------------------- 27 reg [ 1:0] detect_edge; //记录 PS2 的开始脉冲,即第一个下降沿 28 wire [ 1:0] detect_edge_n; //detect_edge 的下一个状态 29 reg [ 3:0] bit_cnt; //记录 PS2 的时钟个数,即数据位数 30 reg [ 3:0] bit_cnt_n; //bit_cnt 的下一个状态 Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 127 31 reg [10:0] bit_shift; //数据移位寄存器,用于读出 PS2 线上数据 32 reg 33 reg 34 reg [10:0] [39:0] [39:0] bit_shift_n; data_shift; data_shift_n; //bit_shift 的下一个状态 //数据移位寄存器,用于记录数据帧数 //data_shift 的下一个状态 35 reg [15:0] o_ps2_data_n; //o_ps2_data 的下一个状态 36 reg negedge_reg; //下降沿标志 37 wire negedge_reg_n; //negedge_reg 的下一个状态 38 39 //--------------------------------------------------------------------------- 40 //-- 逻辑功能实现 41 //--------------------------------------------------------------------------- 42 //时序电路,用来给 detect_edge 寄存器赋值 43 always @ (posedge CLK_50M or negedge RST_N) 44 begin 45 if(!RST_N) 46 detect_edge <= 2'b11; //判断复位 //初始化 detect_edge 值 47 else 48 detect_edge <= detect_edge_n; //用来给 detect_edge 赋值 49 end 50 51 //组合电路,检测下降沿 52 assign detect_edge_n = {detect_edge[0] , PS2_CLK}; //接收 PS2 的时钟信号 53 54 //时序电路,用来给 negedge_reg 寄存器赋值 55 always @ (posedge CLK_50M or negedge RST_N) 56 begin 57 if(!RST_N) 58 negedge_reg <= 1'b0; //判断复位 //初始化 negedge_reg 值 59 else 60 negedge_reg <= negedge_reg_n; //用来给 negedge_reg 赋值 61 end 62 63 //组合电路,判断下降沿,如果 detect_edge 等于 10,negedge_reg_n 就置 1 64 assign negedge_reg_n = (detect_edge == 2'b10) ? 1'b1 : 1'b0; 65 66 //时序电路,用来给 bit_cnt 寄存器赋值 67 always @ (posedge CLK_50M or negedge RST_N) 68 begin 69 if(!RST_N) //判断复位 70 bit_cnt <= 4'b0; //初始化 bit_cnt 值 71 else 72 bit_cnt <= bit_cnt_n; //用来给 bit_cnt 赋值 73 end 74 http://www.fpga.gs/ 128 项目实战篇 §5 75 //组合电路,判断下降沿,并记录下降沿个数 76 always @ (*) 77 begin 78 if(bit_cnt == 4'd11) //判断时钟个数 79 bit_cnt_n = 4'b0; //如果等于 11,bit_cnt_n 就置 0 80 else if(negedge_reg) //判断下降沿 81 bit_cnt_n = bit_cnt + 4'b1; //如果下降沿到来,bit_cnt_n 就加 1 82 else 83 bit_cnt_n = bit_cnt; //否则,保持不变 84 end 85 86 //时序电路,用来给 bit_shift 寄存器赋值 87 always @ (posedge CLK_50M or negedge RST_N) 88 begin 89 if(!RST_N) 90 bit_shift <= 11'b0; //判断复位 //初始化 bit_shift 值 91 else 92 bit_shift <= bit_shift_n; //用来给 bit_shift 赋值 93 end 94 95 //组合电路,读出 PS2 线上数据并放到移位寄存器中 96 always @ (*) 97 begin 98 if(negedge_reg) //判断下降沿 99 bit_shift_n = {PS2_DATA , bit_shift[10:1]};//如果下降沿到来,就读取 PS2 线上的值 100 else 101 bit_shift_n = bit_shift; //否则,保持不变 102 end 103 104 //时序电路,用来给 data_shift 寄存器赋值 105 always @ (posedge CLK_50M or negedge RST_N) 106 begin 107 if(!RST_N) //判断复位 108 data_shift <= 40'b0; //初始化 data_shift 值 109 else 110 data_shift <= data_shift_n; //用来给 data_shift 赋值 111 end 112 113 //组合电路,将字节拼合成帧 114 always @ (*) 115 begin 116 if(bit_cnt == 4'd11) //判断时钟个数 117 data_shift_n = {data_shift[31:0] , bit_shift[8:1]}; 118 else Zircon Opto-Electronic Technology CO.,Ltd. §5 多终端点歌系统设计分析 129 119 data_shift_n = data_shift; //否则,保持不变 120 end 121 122 //时序电路,用来给 o_ps2_data 寄存器赋值 123 always @ (posedge CLK_50M or negedge RST_N) 124 begin 125 if(!RST_N) 126 o_ps2_data <= 16'b0; 127 else 128 o_ps2_data <= o_ps2_data_n; 129 end //判断复位 //初始化 o_ps2_data 值 //用来给 o_ps2_data 赋值 130 131 //组合电路,用来判断通码和断码,并将数据输出 132 always @ (*) 133 begin 134 if((data_shift_n[15:8] == 8'hF0) && (data_shift_n[23:16] == 8'hE0)) 135 o_ps2_data_n = {8'hE0 , data_shift_n[7:0]};//如果相等就将 E0 和数据位输出 136 else if((data_shift_n[15:8] == 8'hF0) && (data_shift_n[23:16] != 8'hE0)) 137 o_ps2_data_n = {8'h0, data_shift_n[7:0]}; //如果通码不等于 E0,就将数据位输出 138 else 139 o_ps2_data_n = o_ps2_data; 140 end //否则,保持不变 141 142 endmodule 从代码中我们可以看出,我们根据前面讲解的 PS2 通信协议,首先我们先利用本地的时钟 去采样 PS/2 的时钟,并用本地两位寄存器 detect_edge 去存储它。然后我们就可以通过判断 detect_edge == 2’b01 则是上升沿,detect_edge == 2’b10 则是下降沿。相信大家对这段程序 已经是比较熟悉了,因为它在我们的 UART 和红外中都有出现。当我们能够判断下降沿后,我 们就可以根据下降沿通过我们的 bit_shift 移位寄存器来接收我们的 11 位数据,接收完了 11 位数 据以后,我们将其中的 8 位有效数据存入到我们的 data_shift 移位寄存器中,到了这里程序还没 有结束,通过我们前面的学习我们知道键盘至少会发出三个字节的数据包,因此,我们还需要继 续接收键盘发来的数据,直至接收完所有的数据。最后我们在根据 data_shift 中的数据判断出通 码,然后将通码数据传输给我们的蜂鸣器模块。 5.6.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Ps2_Top.sof 文件下载到开发板中,接着我们使用 PS/2 键盘按下数字 1,我们便会听到 A4 开发板上的蜂鸣器一直连续发出中音 dao 的声音。如 果这时我们按下数字 2,A4 开发板上的蜂鸣器会立刻发出中音 rui 的声音。依次类推,如果你不 想让蜂鸣器发出声音,那么你只需要按下 PS/2 键盘的 0 即可实现。 http://www.fpga.gs/ 多终端点歌系统设计实战 第六章 多终端点歌系统设计实战 §6.1 功能概述 当我们把 UART、红外和 PS/2 这三个外设学习完了以后,接下来我们同前面数字时钟项目 一样,也是把我们之前所学的三个高级外设合并在一起,最终实现一个复杂的多终端点歌系统项 目。这里我们需要注意的是,如果你在学习我们 UART、红外和 PS/2 的时候,你没有能够很好 的掌握,或者说你还是不清楚这些高级外设是如何控制的,那么我们这里建议大家还是不要急于 往下学习。我们只有将这些高级外设给搞明白、弄清楚了之后,我们在接下来的学习过程中将会 是非常轻松的,否则,我们在接下来的学习过程中将会越学越乱,最后乱成一团。 下面我们就来简单的介绍一下,我们利用这三个高级外设将实现一个怎样功能的多终端点 歌系统。在前面的单个外设学习中,我们可以利用 UART 发送 0~9 十个数字来控制我们的蜂鸣 器发出不同的音调,也可以利用红外来完成,当然,还可以利用 PS/2 来完成。在我们的多终端 点歌系统中,我们则实现了三个外设同时可以发送 0~9 十个数字,来控制我们的蜂鸣器发出不 同的音调。 §6.2 设计说明 根据上面的功能概述,我们可以设计出如图 6.1 所示的功能模块。 图 6.1 多终端点歌系统项目的框架结构图 从该图中我们可以看出,该程序主要分成了 5 个模块,它们分别是 Uart_Bps_Module、 Uart_Rx_Module、Ir_Module、Ps2_Module 和 Beep_Module。Uart_Bps_Module 用于生成 波特率提供给 Uart_Rx_Module 模块使用的。Uart_Rx_Module 是用于控制我们的串口外设来 接收计算机发送的 0~9 十个数字。Ir_Module 是用于解析我们红外遥控器向红外接收头上发送 的 0~9 十个数字。Ps2_Module 是用于解析我们 PS/2 键盘向 PS/2 接口上发送的 0~9 这十个数 134 项目实战篇 §6 字。Beep_Module 根据我们接收到的不同的数字来驱动蜂鸣器发出不同的音调。根据这个功能 模块我们还可以看到,我们主要使用了 CLK_50M、RST_N、UART_RX、IR_DATA、PS2_CLK、 PS2_DATA 和 BEEP 管脚,下面我们对这些管脚进行一个管脚分配,如图 6.2 所示。 图 6.2 多终端点歌系统项目的管脚分配图 §6.3 源码解析 介绍完了设计,接下来我们就来对源码进行一个分析。不管是 UART,还是红外和 PS/2, 相信大家对这几个模块已经非常熟悉了,因为我们前面才刚刚讲过,这里我们就不再对这些模块 进行介绍了,我们主要介绍一下我们在这些模块中都修改了哪些部分。首先我们修改的第一个部 分就是我们为 UART、红外和 PS/2 都添加了一个输出完成标志位,它们分别是 uart_finish、 ir_finish 和 ps2_finish,这里我们就以 UART 为例,如代码 6.1 所示。 代码 6.1 Uart_Rx_Module.v 代码 1 //时序电路,用来给 out_rx_data 寄存器赋值 2 always @ (posedge CLK_50M or negedge RST_N) 3 begin 4 if(!RST_N) //判断复位 5 uart_finish <= 1'b0; //初始化 uart_finish 值 6 else 7 uart_finish <= uart_finish_n; //用来给 uart_finish 赋值 8 end 9 10 //组合电路,如果数据接收完成,那么将 uart_finish 标志位置 1 11 always @ (*) 12 begin 13 if(bit_cnt == 4'd9) //判断数据有没有接收完成 14 uart_finish_n = 1'b1; //如果接收完成,那么将 uart_finish 标志位置 1 15 else 16 uart_finish_n = 1'b0; //否则,将 uart_finish 标志位置 0 17 end 从该代码中我们可以看出,代码很简单,当 UART 数据接收完成后,我们就将 uart_finish 置 1,否则就置 0。红外和 PS/2 也是同理,这里我们就不再进一步给出代码了。我们修改的第 二个部分就是我们的蜂鸣器模块,我们在蜂鸣器模块中添加了一个判断,用来判断 UART、红外 和 PS/2 这三个外设中哪个外设完成了数据接收,如果完成了数据接收,那么我们就将该外设中 Zircon Opto-Electronic Technology CO.,Ltd. §6 多终端点歌系统设计实战 135 的数据赋值给 temp_data,如代码 6.2 所示。 代码 6.2 Beep_Module.v 代码 1 //时序电路,用来给 temp_data 寄存器赋值 2 always @ (posedge CLK_50M or negedge RST_N) 3 begin 4 if(!RST_N) 5 temp_data <= 8'b0; //判断复位 //初始化 temp_data 值 6 else 7 temp_data <= temp_data_n; //用来给 temp_data 赋值 8 end 9 10 //时序电路,用来接收串口、红外和 PS/2 数据 11 always @ (*) 12 begin 13 if(uart_finish) 14 temp_data_n = in_rx_data; 15 else if(ir_finish) 16 temp_data_n = in_ir_data; 17 else if(ps2_finish) 18 temp_data_n = in_ps2_data; //判断串口数据有没有接收完毕 //如果接收完毕,那么将串口的数据赋值给 temp_data //判断红外数据有没有接收完毕 //如果接收完毕,那么将红外的数据赋值给 temp_data //判断 PS2 数据有没有接收完毕 //如果接收完毕,那么将 PS2 的数据赋值给 temp_data 19 else 20 temp_data_n = temp_data; //否则,将保持不变 21 end 从该代码中我们可以看出,在 uart_finish、ir_finish 和 ps2_finish 这三个信号中,只要有一 个信号被置 1,那么我们就会将该外设所接收到的数据赋值给 temp_data。我们修改的第三个部 分仍然是我们的蜂鸣器模块,我们将之前判断一个外设的输入数据修改成了判断三个外设的输 入数据,如代码 6.3 所示。 代码 6.3 Beep_Module.v 代码 1 //组合电路,按键选择分频值来实现蜂鸣器发出不同声音 2 //中音 do 的频率为 523.3hz,freq = 50 * 10^6 / (523 * 2) = 47774 3 always @ (*) 4 begin 5 case(temp_data) 6 8'h16: freq_n = 16'd0; //判断红外的值,没有声音 7 8'h0C: freq_n = 16'd47774; //中音 1 的频率值 262Hz 8 8'h18: freq_n = 16'd42568; //中音 2 的频率值 587.3Hz 9 8'h5E: freq_n = 16'd37919; //中音 3 的频率值 659.3Hz 10 8'h08: freq_n = 16'd35791; //中音 4 的频率值 698.5Hz 11 8'h1C: freq_n = 16'd31888; //中音 5 的频率值 784Hz 12 8'h5A: freq_n = 16'd28409; //中音 6 的频率值 880Hz 13 8'h42: freq_n = 16'd25309; //中音 7 的频率值 987.8Hz 14 8'h52: freq_n = 16'd23889; //高音 1 的频率值 1046.5Hz http://www.fpga.gs/ 136 项目实战篇 §6 15 8'h4A: freq_n = 16'd21276; 16 8'h70: freq_n = 16'd0; 17 8'h69: freq_n = 16'd47774; 18 8'h72: freq_n = 16'd42568; 19 8'h7A: freq_n = 16'd37919; 20 8'h6B: freq_n = 16'd35791; 21 8'h73: freq_n = 16'd31888; 22 8'h74: freq_n = 16'd28409; 23 8'h6C: freq_n = 16'd25309; 24 8'h75: freq_n = 16'd23889; 25 8'h7D: freq_n = 16'd21276; 26 8'h30: freq_n = 16'd0; 27 8'h31: freq_n = 16'd47774; 28 8'h32: freq_n = 16'd42568; 29 8'h33: freq_n = 16'd37919; 30 8'h34: freq_n = 16'd35791; 31 8'h35: freq_n = 16'd31888; 32 8'h36: freq_n = 16'd28409; 33 8'h37: freq_n = 16'd25309; 34 8'h38: freq_n = 16'd23889; 35 8'h39: freq_n = 16'd21276; 36 default: freq_n = freq; 37 endcase 38 end //高音 2 的频率值 1175Hz //判断 PS 的值,没有声音 //中音 1 的频率值 262Hz //中音 2 的频率值 587.3Hz //中音 3 的频率值 659.3Hz //中音 4 的频率值 698.5Hz //中音 5 的频率值 784Hz //中音 6 的频率值 880Hz //中音 7 的频率值 987.8Hz //高音 1 的频率值 1046.5Hz //高音 2 的频率值 1175Hz //判断串口的值,没有声音 //中音 1 的频率值 262Hz //中音 2 的频率值 587.3Hz //中音 3 的频率值 659.3Hz //中音 4 的频率值 698.5Hz //中音 5 的频率值 784Hz //中音 6 的频率值 880Hz //中音 7 的频率值 987.8Hz //高音 1 的频率值 1046.5Hz //高音 2 的频率值 1175Hz §6.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Mod_Top.sof 文件下载到开发板中,接着我们先用 串口进行控制发送 0-9,我们可以听到蜂鸣器进行了发声操作,然后我们再用红外控制发送 09,我们也可以听到蜂鸣器进行了发声操作,最后我们再用 PS/2 键盘发送 0-9,我们还可以听 到蜂鸣器仍然进行了发声操作。 Zircon Opto-Electronic Technology CO.,Ltd. 数字示波器设计分析 第七章 数字示波器设计分析 学习完了多终端点歌系统项目,接下来我们将继续学习最后一个项目,也就是我们的数字示 波器。说到数字示波器,学过电子的朋友想必都不陌生,数字示波器是一种用途十分广泛的电子 测量仪器。它能把肉眼看不见的电信号变换成看得见的图像,通过模拟转换器把被测电压转换为 数字信息。利用数字示波器能观察各种不同信号幅度随时间变化的波形曲线,还可以用它测试各 种不同的电量,如电压、电流、频率、相位差、调幅度等等。在开始设计我们的数字示波器项目 之前,我们还是先来看下我们的数字示波器项目划分为哪几个知识点,如图 7.1 所示。 VGA控制原理 VGA实际应用 高级外设学习 AD控制原理 AD实际应用 简易数字示波器 DA控制原理 DA实际应用 图 7.1 实现数字示波器项目的工程框架图 通过该框架图我们可以看出,该框架图和我们多终端点歌系统的框架图是类似的,唯一不同 的是我们之前学习的是 UART、红外和 PS/2 这三个外设变成了 VGA、AD 和 DA。当我们依次 学习完了这三个高级外设之后,我们就可以将这三个外设相结合,最终实现我们的简易数字示波 器。 §7.1 VGA控制原理 7.1.1 VGA 基本概述 首先我们介绍的是 VGA 的基本概述。下面我们就来说下什么是 VGA,VGA 的英文全称是 Video Graphics Array,中文名字叫视频图形阵列,是 IBM 在 1987 年随 PS/2 机一起推出的一 种视频传输标准,具有分辨率高、显示速率快、颜色丰富等优点,不支持热插拔,不支持音频传 输,在彩色显示器领域得到了广泛的应用。这个标准对于现今的个人电脑市场已经十分过时,我 们现在有更牛的 HDMI 和 DVI 等,即使如此,VGA 仍然是最多制造商所共同支持的一个标准, 个人电脑在加载自己的独特驱动程序之前,都必须支持 VGA 的标准。这也说明它在显示标准中 的重要性和兼容性。知道了什么是 VGA 后,接下来我们再来简单的介绍一下 VGA 接口管脚定 义,如图 7.2 所示。 140 项目实战篇 §7 543 21 10 9 8 7 6 15 14 13 12 11 图 7.2 VGA 接口 从该图中我们可以看出,VGA 接口是一种 D 型接口,采用非对称分布连接方式,共有 15 针,分成 3 排,每排 5 个孔,是显卡上应用最为广泛的接口类型,绝大多数显卡都带有此种接 口。如果你没有见过该接口,那么你可以看一下你的主机箱后面。其对应接口定义如下表所示。 表 7.1 VGA 接口管脚表 管脚 定义 管脚 定义 1 红基色(Red) 9 保留(各家定义不同) 2 绿基色(Green) 10 数字地 3 蓝基色(Blue) 11 地址码 4 地址码(ID Bit) 12 地址码 5 自测试(各家定义不同) 13 行同步 6 红地 14 场同步 7 绿地 15 地址码(各家定义不同) 8 蓝地 在这 15 个管脚中,其中比较重要的是 3 根 RGB 彩色分量信号和 2 根扫描同步信号 HSYNC 和 VSYNC。这里我们需要注意的是:VGA 显示器上每一个像素点可以有多种颜色,由三基色 信号 R、G、B 组合构成,如果每个像素点采用 3 位二进制数表示(R、G、B 信号各一位),则 总共可以显示成 2*2*2=8 种颜色,每个像素点采用 8 位二进制数表示(R、G、B 信号各为 3、 3、2)则总共可以显示成 8*8*4=256 种颜色。如果 VGA 显示真彩色 BMP 图像,则要 R、G、 B 三个分量各 8 位,即 24 位表示一个像素值,很多情况下还采用 32 位表示一个像素值。为了 节省显存的存储空间,可采用高彩色图像,即每个像素值由 16 位表示,R、G、B 三个分量分别 使用 5 位、6 位、5 位,比真彩色图像数据量减少一半,同时又能满足显示效果。介绍完了 VGA 的接口管脚定义,下面我们再来简单的介绍一下 VGA 应用原理,如图 7.3 所示。 扫描时序 显 数字图像输出 VGA控制器 示 器 颜色信号 VGA_controller DAC 图 7.3 VGA 应用原理框图 从图中我们可以看出,如果我们想要显示一个图像,那么我们首先要有这个图像,当我们有 Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 141 了图像以后,我们就可以通过 VGA 控制器来产生相对应的 VGA 时序将图像数据送出,送出去 之后呢,我们要知道,这个显示器它是不认识数字信号的,它只认识模拟信号,因此呢我们还需 要一个 DA 来将数字信号转换成显示器所识别的模拟信号。在我们的 A4 开发板中我们并没有使 用 DA 来进行模数转换,我们使用的是 R-2R 电阻网络分流模拟 DA 来实现的,如图 7.4 所示。 图 7.4 A4 开发板上 VGA 连接原理图 从该图中可以看出,我们使用了大量的电阻,为什么要使用这么多的电阻呢?下面我们就给 大家进行一个详细的讲解,首先我们来想一下,比如说我们想要显示深红色,那么这个深红色需 要用什么样的一个电压来代替呢,这其实就需要用到 VGA 接口协议规定,VGA 接口协议规定 VGA_R、VGA_G、VGA_B 分别为红绿蓝三基色模拟电压,该模拟电压范围为 0V~0.714V,其 中 0V 代表无色,0.714V 代表满色。也就是说,如果我们想要显示纯红,红到不能在红,那么我 们只需要给 VGA_R 管脚送 0.714V 电压即可,如果我们想要显示一半红,那么我们只需要给 VGA_R 管脚送 0.357V 电压就可以了。看到这里问题就来了,我们怎么样才能把电压变成 0.714V 呢?这时候我们就需要 R-2R 电阻网络分压,将 3.3V 进行一个分压改变,然后调整到 0.714V, 如图 7.5 所示。 0.714V电压输出 3.3V X 75 图 7.5 R-2R 电阻网络分压原理 从该图中我们可以看出,首先我们通过一个 75 欧姆的电阻接地,然后我们再通过一个 X 欧 http://www.fpga.gs/ 142 项目实战篇 §7 姆的电阻接到 3.3V 上,最后我们只需要通过更改 X 的欧姆值就可以完成整个 R-2R 电阻网络分 压。如果我们的 VGA_R 信号是 1 位,那么该 X 的值就可以通过(X+75)/3.3=75/0.714 这个公 式计算得出 X=271.6。如果我们的 VGA_R 是 3 位的,那么该 X 的值就可以通过:(X+75) /3.3=75/0.714 和 Rx//2Rx//4Rx=X 两个公式计算得出 Rx=475.3。因此我们选择 500 欧姆,1K 欧姆,2K 欧姆的电阻作为电阻网络。看到这里,相信大家能够明白为什么我们需要使用大量的 电阻了,我们是通过电阻将 VGA 的数字信号转换成模拟信号给 VGA 进行显示。 7.1.2 VGA 通信协议 大家看这个就是 VGA 通信协议,如图 7.6 所示。 p VS o HS … q r s … b a c d e 图 7.6 VGA 的通信时序图 从 VGA 的时序图中,我们可以看出,帧时序和行时序都产生了四个部分,帧时序的四个部 分分别是:同步脉冲(Sync o)、显示后沿(Back porch p)、显示时序段(Display interval q)和显示 前沿(Front porch r)。其中同步脉冲(Sync o)、显示后沿(Back porch p)和显示前沿(Front porch r)是消隐区,RGB 信号无效,屏幕不显示数据。显示时序段(Display interval q)是有效数据区。 行时序的四个部分分别是:同步脉冲(Sync a)、显示后沿(Back porch b)、显示时序段(Display interval c)和显示前沿(Front porch d)。其中同步脉冲(Sync a)、显示后沿(Back porch b)和显示 前沿(Front porch d)是消隐区,RGB 信号无效,屏幕不显示数据。显示时序段(Display interval c)是有效数据区。下面我们给出常用的分辨率及参数,如表 7.2 所示。 显示模式 640x480@60 640x480@75 800x600@60 800x600@60 1024x768@60 1024x768@75 1280x1024@60 1280x800@60 1440x900@60 表 7.2 VGA 常用刷新率时序表 时钟 行时序(像素数) (MHz) a b c deo 25.175 96 48 640 16 800 2 31.5 64 120 640 16 840 3 40.0 128 88 800 40 1056 4 49.5 80 160 800 16 1056 3 65 136 160 1024 24 1344 6 78.8 176 176 1024 16 1312 3 108.0 112 248 1280 48 1688 3 83.46 136 200 1280 64 1680 3 106.47 152 232 1440 80 1904 3 帧时序(行数) pq r s 33 480 10 525 16 480 1 500 23 600 1 628 21 600 1 625 29 768 3 806 28 768 1 800 38 1024 1 1066 24 800 1 828 28 900 1 932 Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 143 通过该表我们可以看出,不同的分辨率,它的时序是不一样的。下面我们以 800*600@60Hz 分辨率为例,给大家讲解一下 VGA 时序:HSYNC 是用来控制“行时序”, HSYNC 的 a 是拉 低的 128 个列像素,b 是拉高的 88 个列像素,c 是拉高的 800 个列像素,而 d 是拉高的 40 个 列像素,e 总共有 1056 个列像素。VSYNC 是用来控制“帧时序”, VSYNC 的 o 是拉低的 4 个行像素,p 是拉高的 23 个行像素,q 是拉高的 600 个行像素,而 r 是拉高的 1 个行像素,s 总 共有 628 个行像素。每帧对应 628 个行周期(628=4+23+600+1),每显示行包括 1056 点时钟, 由此可知,时钟频率:628*1056*60 约 40MHz。 §7.2 VGA实际应用 7.2.1 功能概述 首先我们先来说下我们的工程将要实现一个怎样的功能:我们的 VGA 工程主要实现的功能 是让 VGA 显示分辨率为 800*600 的四种不同颜色的彩条,这四种颜色分别是红、黄、蓝、绿。 7.2.2 设计说明 根据上面的功能概述,我们可以设计出如图 7.7 所示的功能模块。 图 7.7 VGA 外设的框架结构图 从该图中我们可以看出,该程序主要分成了 2 个模块,它们分别是 PLL_Module 和 Vga_Module。PLL_Module 模块是用于生成我们的 40M 时钟,这里的 PLL 是一个 IP 核,如果 还有不会创建 PLL IP 核的朋友,那么你可以参考我们的《软件工具篇》中的内容。我们《软件 工具篇》中有详细介绍如何创建 PLL IP 核的教程。Vga_Module 模块用于生成我们的 VGA 时 序,并且提供我们 VGA 显示的图像数据。根据这个功能模块我们还可以看到,我们主要使用了 CLK_50M、RST_N、VGA_HSYNC、VGA_VSYNC 和 VGA_DATA 管脚,下面我们对这些管 脚进行一个管脚分配,如图 7.8 所示。 http://www.fpga.gs/ 144 项目实战篇 §7 图 7.8 VGA 外设的管脚分配图 7.2.3 源码解析 由于 PLL IP 核使用比较简单,所以这里我们就不再进一步进行介绍了,我们这里主要介绍 的是 Vga 模块,如代码 7.1 所示。 代码 7.1 Vga_Module.v 代码 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : Vga_Module.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : VGA 显示彩条 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 8 //--------------------------------------------------------------------------- 9 //-- VGA 800*600 @ 60 10 //-- VGA_DATA[7:0] red2,red1,red0,green2,green1,green0,blue1,blue0 11 //-- VGA CLOCK 40MHz. 12 //--------------------------------------------------------------------------- 13 14 //--------------------------------------------------------------------------- 15 //-- Horizonal timing information 16 //-- Sync pluse 128 a 17 //-- back porch 88 b 18 //-- active 800 c 19 //-- front porch 40 d 20 //-- All line 1056 e 21 //-- Vertical timing information 22 //-- sync pluse 4 o 23 //-- back porch 23 p 24 //-- active time 600 q 25 //-- front porch 1 r 26 //-- All lines 628 s 27 //--------------------------------------------------------------------------- 28 29 //--------------------------------------------------------------------------- 30 //-- Horizonal timing information 31 `define HSYNC_A 16'd128 // 128 32 `define HSYNC_B 16'd216 // 128 + 88 33 `define HSYNC_C 16'd1016 // 128 + 88 + 800 34 `define HSYNC_D 16'd1056 // 128 + 88 + 800 + 40 35 //-- Vertical timing information 36 `define VSYNC_O 16'd4 // 4 37 `define VSYNC_P 16'd27 // 4 + 23 Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 145 38 `define VSYNC_Q 16'd627 // 4 + 23 + 600 39 `define VSYNC_R 16'd628 // 4 + 23 + 600 + 1 40 //--------------------------------------------------------------------------- 41 42 module Vga_Module 43 ( 44 //输入端口 45 CLK_50M,RST_N, 46 //输出端口 47 VSYNC,HSYNC,VGA_DATA 48 ); 49 50 //--------------------------------------------------------------------------- 51 //-- 外部端口声明 52 //--------------------------------------------------------------------------- 53 input 54 input CLK_50M; RST_N; //时钟的端口,开发板用的 50M 晶振 //复位的端口,低电平复位 55 output VSYNC; //VGA 垂直同步端口 56 output HSYNC; //VGA 水平同步端口 57 output [ 7:0] VGA_DATA; //VGA 数据端口 58 59 //--------------------------------------------------------------------------- 60 //-- 内部端口声明 61 //--------------------------------------------------------------------------- 62 reg [15:0] hsync_cnt; //水平扫描计数器 63 reg [15:0] hsync_cnt_n; //hsync_cnt 的下一个状态 64 reg 65 reg 66 reg 67 reg [15:0] [15:0] [ 7:0] [ 7:0] vsync_cnt; vsync_cnt_n; VGA_DATA; VGA_DATA_N; //垂直扫描计数器 //vsync_cnt 的下一个状态 //RGB 端口总线 //VGA_DATA 的下一个状态 68 reg VSYNC; //垂直同步端口 69 reg VSYNC_N; //VSYNC 的下一个状态 70 reg HSYNC; //水平同步端口 71 reg 72 reg 73 reg HSYNC_N; vga_data_en; vga_data_en_n; //HSYNC 的下一个状态 //RGB 传输使能信号 //vga_data_en 的下一个状态 74 75 //时序电路,用来给 hsync_cnt 寄存器赋值 76 always @ (posedge CLK_50M or negedge RST_N) 77 begin 78 if(!RST_N) 79 hsync_cnt <= 16'b0; //判断复位 //初始化 hsync_cnt 值 80 else 81 hsync_cnt <= hsync_cnt_n; //用来给 hsync_cnt 赋值 http://www.fpga.gs/ 146 项目实战篇 §7 82 end 83 84 //组合电路,水平扫描 85 always @ (*) 86 begin 87 if(hsync_cnt == `HSYNC_D) //判断水平扫描时序 88 hsync_cnt_n = 16'b0; //如果水平扫描完毕,计数器将会被清零 89 else 90 hsync_cnt_n = hsync_cnt + 1'b1; //如果水平没有扫描完毕,计数器继续累加 91 end 92 93 //时序电路,用来给 vsync_cnt 寄存器赋值 94 always @ (posedge CLK_50M or negedge RST_N) 95 begin 96 if(!RST_N) 97 vsync_cnt <= 16'b0; //判断复位 //给行扫描赋值 98 else 99 vsync_cnt <= vsync_cnt_n; //给行扫描赋值 100 end 101 102 //组合电路,垂直扫描 103 always @ (*) 104 begin 105 if((vsync_cnt == `VSYNC_R) && (hsync_cnt == `HSYNC_D))//判断垂直扫描时序 106 vsync_cnt_n = 16'b0; //如果垂直扫描完毕,计数器将会被清零 107 else if(hsync_cnt == `HSYNC_D) //判断水平扫描时序 108 vsync_cnt_n = vsync_cnt + 1'b1; //如果水平扫描完毕,计数器继续累加 109 else 110 vsync_cnt_n = vsync_cnt; //否则,计数器将保持不变 111 end 112 113 //时序电路,用来给 HSYNC 寄存器赋值 114 always @ (posedge CLK_50M or negedge RST_N) 115 begin 116 if(!RST_N) 117 HSYNC <= 1'b0; //判断复位 //初始化 HSYNC 值 118 else 119 HSYNC <= HSYNC_N; //用来给 HSYNC 赋值 120 end 121 122 //组合电路,将 HSYNC_A 区域置 0,HSYNC_B+HSYNC_C+HSYNC_D 置 1 123 always @ (*) 124 begin 125 if(hsync_cnt < `HSYNC_A) //判断水平扫描时序 Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 126 HSYNC_N = 1'b0; //如果在 HSYNC_A 区域,那么置 0 127 else 128 HSYNC_N = 1'b1; //如果不在 HSYNC_A 区域,那么置 1 129 end 130 131 //时序电路,用来给 VSYNC 寄存器赋值 132 always @ (posedge CLK_50M or negedge RST_N) 133 begin 134 if(!RST_N) 135 VSYNC <= 1'b0; //判断复位 //初始化 VSYNC 值 136 else 137 VSYNC <= VSYNC_N; //用来给 VSYNC 赋值 138 end 139 140 //组合电路,将 VSYNC_A 区域置 0,VSYNC_P+VSYNC_Q+VSYNC_R 置 1 141 always @ (*) 142 begin 143 if(vsync_cnt < `VSYNC_O) 144 VSYNC_N = 1'b0; //判断水平扫描时序 //如果在 VSYNC_O 区域,那么置 0 145 else 146 VSYNC_N = 1'b1; //如果不在 VSYNC_O 区域,那么置 1 147 end 148 149 //时序电路,用来给 vga_data_en 寄存器赋值 150 always @ (posedge CLK_50M or negedge RST_N) 151 begin 152 if(!RST_N) 153 vga_data_en <= 1'b0; //判断复位 //初始化 vga_data_en 值 154 else 155 vga_data_en <= vga_data_en_n; //用来给 vga_data_en 赋值 156 end 157 158 //组合电路,判断显示有效区(列像素>216&&列像素<1017&&行像素>27&&行像素<627) 159 always @ (*) 160 begin 161 if((hsync_cnt > `HSYNC_B && hsync_cnt <`HSYNC_C) && 162 (vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)) 163 vga_data_en_n = 1'b1; //如果在显示区域就给使能数据信号置 1 164 else 165 vga_data_en_n = 1'b0; //如果不在显示区域就给使能数据信号置 0 166 end 167 168 169 //时序电路,用来给 VGA_DATA 寄存器赋值 http://www.fpga.gs/ 147 148 项目实战篇 §7 170 always @ (posedge CLK_50M or negedge RST_N) 171 begin 172 if(!RST_N) 173 VGA_DATA <= 8'h0; //判断复位 //初始化 VGA_DATA 值 174 else 175 VGA_DATA <= VGA_DATA_N; //用来给 VGA_DATA 赋值 176 end 177 178 //组合电路,显示彩条,判断区域并根据数据填充颜色 179 always @ (*) 180 begin 181 if(vga_data_en) //判断数据使能 182 begin 183 if( vsync_cnt >= `VSYNC_P ) //判断屏幕位置 184 begin 185 if((hsync_cnt > `HSYNC_B) && (hsync_cnt <= `HSYNC_B + 10'd300))//判断位置 186 VGA_DATA_N = 8'hE0; //红色 187 else if((hsync_cnt > `HSYNC_B + 10'd300) && (hsync_cnt <= `HSYNC_B + 10'd400)) //判断屏幕位置 188 VGA_DATA_N = 8'h03; //黄色 189 else if((hsync_cnt > `HSYNC_B + 10'd400) && (hsync_cnt <= `HSYNC_B + 10'd500)) //判断屏幕位置 190 VGA_DATA_N = 8'hFC; //蓝色 191 else if((hsync_cnt > `HSYNC_B + 10'd500) && (hsync_cnt <= `HSYNC_B + 10'd800)) //判断屏幕位置 192 VGA_DATA_N = 8'h1C; //绿色 193 else 194 VGA_DATA_N = 8'h0; //黑色 195 end 196 else 197 VGA_DATA_N = 8'h0; //黑色 198 end 199 else 200 VGA_DATA_N = 8'h0; //黑色 201 end 202 endmodule 下面我们就来简单的介绍一下该代码,代码第 1 至 73 行是端口定义和参数声明,我们定义 了 800*600@60 分辨率所用到的参数。代码第 76 至 91 行是用来生成水平扫描计数器 hsync_cnt 的。代码第 94 至 1111 行是用来生成垂直扫描计数器 vsync_cnt 的。代码第 114 至 129 行是用 来判断 VGA 水平同步端口 HSYNC。代码 132 至 147 行是用来判断 VGA 垂直同步端口 VSYNC。 代码第 150 至 166 行是用来判断显示有效区的。代码第 170 至 201 行是用来根据判断的不同区 域进行数据填充。 Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 149 7.2.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Vga_Top.sof 文件下载到开发板中,接着我们使用 VGA 线将 VGA 连接到我们的显示器上,这时我们可以看到显示器上出现了四种不同颜色的彩 条,如图 7.9 所示。 §7.3 AD控制原理 图 7.9 VGA 外设的板级调试图 7.3.1 AD 基本概述 介绍完了 VGA 外设,接下来我们就来介绍第二个 AD 外设。说到 AD,我们先来说下什么 是 AD,AD 的全称 Analog to Digital,简称 AD,中文名称是模拟转数字,其实我们从这个中文 名称中就可以知道了它的功能,它的功能就是将时间连续、幅值也连续的模拟量转换为时间离散、 幅值也离散的数字信号。由于我们的 A4 开发板上所采用的是 TLC549 AD 芯片,所以接下来我 们主要介绍的是 TLC549 这个 AD 芯片。TLC549 是美国德州仪器公司生产低价位、高性能的 8 位串行模数转换芯片,它以 8 位开关电容逐次逼近的方法实现 A/D 转换,其内部具有 4MHz 的 系统时钟,转换速度小于 17us,它允许的最大转换速率为 40000 次/s,电源为 3V 至 6V,能方 便地采用三线串行接口方式与各种微处理进行连接,如图 7.10 所示是 TLC549 的引脚图。 REF+ 1 ANALOGIN 2 REF- 3 GND 4 8 Vcc 7 I/O CLOCK 6 DATA OUT 5 CS 图 7.10 AD TLC549 的引脚图 从该图中我们可以看出,它有八个管脚,分别如表 7.3 所示。 http://www.fpga.gs/ 150 项目实战篇 §7 表 7.3 AD TLC549 接口管脚表 管脚 名称 描述 1 REF+ 正基准电压输入端 2 ANALOG IN 模拟信号输入端 3 REF- 负基准电压输入端 4 GND 接地端 5 CS 片选信号 6 DATA OUT 转换结果数据串行输出端 7 I/O CLOCK 外接时钟输入端 8 VCC 系统电源输入端 这八个管脚在我们 A4 开发板上的连接如图 7.11 所示。 图 7.11 A4 开发板上 AD TLC549 连接原理图 从该图中我们可以看出,TLC549 这个芯片的正基准电压输入端 REF+和系统电源 VCC 都 接的是 5V 电压,负基准电压输入端 REF-和接下端 GND 都接的地,外接时钟输入端 I/O CLOCK、 转换结果数据串行输出端 DATA OUT 和片选信号 CS 都接在我们的 FPGA I/O 管脚上。模拟信 号输入端 ANALOG IN 接在了我们 A4 开发板的 SMA 头上。 7.3.2 AD 通信协议 介绍完了 AD 基本概述,接下来我们再来看下 AD 的通信协议。由于 TLC549 它内部具有 4MHz 的系统时钟,并且该时钟与 I/O CLOCK 是独立工作的,所以我们无需特殊的速度或相位 匹配,其工作时序如图 7.12 所示。 图 7.12 AD TLC549 时序图 Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 151 从该图中我们可以看出: (1) 当 CS 为高时,转换结果数据串行输出端 DATA OUT 处于高阻状态,此时 I/O CLOCK 也不起作用。 (2) 当 CS 为低时,AD 前一次转换的数据 A 的最高位 A7 立即出现在数据线 DATA OUT 上,其余的 7 位数据在 I/O CLOCK 的下降沿依次由时钟同步输出。因此可在 I/O CLOCK 的上升沿读取数据;这里我们需要注意的有两点:第一点是:当 CS 变为低电平到 I/O CLOCK 第一个时钟到来至少要 1.4us。第二点是:I/O CLOCK 不能超过 1.1MHz。 (3) 读完 8 位数据后,AD 开始转换这一次转换的采样数据 B,以便下一次读取。转换时片 选信号 CS 必须置高电平,每次转换的时间不超过 17us,开始于 CS 变为低电平后 I/O CLOCK 的第 8 个下降沿,没有转换完成标志信号;也没有启动控制端,只要读取前一 次数据后马上就可以开始新的 AD 转换,转换完成后就进入保持状态。 §7.4 AD实际应用 7.4.1 功能概述 首先我们先来说下我们的工程将要实现一个怎样的功能:我们的 AD 工程主要实现的功能是 将输入的模拟信号进行模数转换,将转换的数字信号送给数码管显示,数码管显示的是对应的模 拟电压值。也就是说,如果我们使用跳线将我们的 AD 模拟信号输入端 ANALOG IN 跳到 0V 电 压上,那么我们的数码管将显示 0.0,如果我们使用跳线将我们的 AD 模拟信号输入端 ANALOG IN 跳到 3.3V 电压上,那么我们的数码管将显示 3.3。 7.4.2 设计说明 根据上面的功能概述,我们可以设计出如图 7.13 所示的功能模块。 图 7.13 AD 外设的框架结构图 从该图中我 们可以看 出, 该程序主要 分成了 2 个 模块,它们 分别是 Ad_Module 和 Segled_Module。Ad_Module 是用于控制我们的 Ad 外设进行模数转换的。Segled_Module 用 于显示我们 Ad 模块转换完成的数据。根据这个功能模块我们还可以看到,我们主要使用了 CLK_50M、RST_N、AD_DATA、AD_CLK、AD_CS、SEG_DATA 和 SEG_EN 管脚,下面 http://www.fpga.gs/ 152 项目实战篇 §7 我们对这些管脚进行一个管脚分配,如图 7.14 所示。 图 7.14 AD 外设的管脚分配图 7.4.3 源码解析 介绍完了设计,接下来我们就来对源码进行一个分析。数码管这个模块,相信大家已经很熟 悉了,我们已经用过很多次了,这里我们就不再进行介绍了。下面我们主要介绍的是 AD 模块代 码,如代码 7.2 所示。 代码 7.2 Ad_Module.v 代码 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : Ad_Module.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : AD 模块 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 8 `define AD_CLK_TIME 10'd45 //1.1M, 909ns,909 / (1 / 50M) = 45 =0x2D 9 `define AD_CLK_TIME_HALF 10'd22 //909ns / 2 = 454.5ns 45 / 2 = 22 10 11 module Ad_Module 12 ( 13 //Input 14 CLK_50M,RST_N, 15 //Output 16 AD_CS,AD_CLK,AD_DATA,o_vol_int,o_vol_dec 17 ); 18 19 //--------------------------------------------------------------------------20 //-- 外部端口声明 21 //--------------------------------------------------------------------------- Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 153 22 input CLK_50M; //时钟的端口,开发板用的 50M 晶振 23 input 24 input 25 output RST_N; AD_DATA; AD_CS; //复位的端口,低电平复位 //AD 数据端口 //AD 片选端口 26 output AD_CLK; //AD 时钟端口,最大不超过 1.1MHz 27 output [ 3:0] o_vol_int; //从 A/D 转换芯片输出的电压值的整数部分; 28 output [ 3:0] o_vol_dec; //从 A/D 转换芯片输出的电压值的小数部分; 29 30 //--------------------------------------------------------------------------- 31 //-- 内部端口声明 32 //--------------------------------------------------------------------------- 33 reg AD_CS; //AD 片选信号端口 34 reg AD_CS_N; //AD_CS 的下一个状态 35 reg 36 reg AD_CLK; AD_CLK_N; //AD 时钟,最大不超过 1.1MHz //AD_CLK 的下一个状态 37 38 reg [ 2:0] ad_fsm_cs; //状态机的当前状态 39 reg [ 2:0] ad_fsm_ns; //状态机的下一个状态 40 41 reg [ 3:0] o_vol_int; //从 A/D 转换芯片输出的电压值的整数部分; 42 wire 43 reg 44 wire [ 3:0] [ 3:0] [ 3:0] o_vol_int_n; o_vol_dec; o_vol_dec_n; //o_vol_int 的下一个状态 //从 A/D 转换芯片输出的电压值的小数部分; //o_vol_dec 的下一个状态 45 46 reg [ 5:0] time_cnt; //用于记录一个时钟所用时间的定时器 47 reg [ 5:0] time_cnt_n; //time_cnt 的下一个状态 48 reg 49 reg [ 5:0] [ 5:0] bit_cnt; bit_cnt_n; //用来记录时钟周期个数的计数器 //bit_cnt 的下一个状态 50 51 reg [ 7:0] data_out; //用来保存稳定的 AD 数据 52 reg [ 7:0] data_out_n; //data_out 的下一个状态 53 reg [ 7:0] ad_data_reg; //用于保存数据的移位寄存器 54 reg [ 7:0] ad_data_reg_n; //ad_data_reg_n 的下一个状态 55 56 wire 57 wire [11:0] [11:0] mid1; mid2; //数据转换电压的整数部分 //数据转换电压的小数部分 58 59 parameter FSM_IDLE = 3'h0; //状态机的初始状态; 60 parameter FSM_READY = 3'h1; //满足 CS 有效时的第一个 1.4us 的延时状态 61 parameter 62 parameter 63 parameter FSM_DATA = 3'h2; //读取 8 个数据状态 FSM_WAIT_CONV = 3'h3; //等待转换状态,等待 17us; FSM_END = 3'h4; //结束的状态 64 65 //--------------------------------------------------------------------------- http://www.fpga.gs/ 154 项目实战篇 §7 66 //-- 逻辑功能实现 67 //--------------------------------------------------------------------------- 68 //时序电路,用来给 ad_fsm_cs 寄存器赋值 69 always @ (posedge CLK_50M or negedge RST_N) 70 begin 71 if(!RST_N) //判断复位 72 ad_fsm_cs <= 1'b0; //初始化 ad_fsm_cs 值 73 else 74 ad_fsm_cs <= ad_fsm_ns; //用来给 ad_fsm_ns 赋值 75 end 76 77 //组合电路,用来实现状态机 78 always @ (*) 79 begin 80 case(ad_fsm_cs) //判断状态机的当前状态 81 FSM_IDLE: 82 //3 x 0.909us = 2.727us 用于初始化延时 83 if((bit_cnt == 6'd2 ) && (time_cnt == `AD_CLK_TIME)) 84 ad_fsm_ns = FSM_READY; //如果空闲状态完成就进入延时状态 85 else 86 ad_fsm_ns = ad_fsm_cs; //否则保持原状态不变 87 FSM_READY: 88 //2 x 0.909us = 1.818us 用于延迟 1.4us 89 if((bit_cnt == 6'd1 ) && (time_cnt == `AD_CLK_TIME)) 90 ad_fsm_ns = FSM_DATA; //如果延时状态完成就进入读取数据状态 91 else 92 ad_fsm_ns = ad_fsm_cs; //否则保持原状态不变 93 FSM_DATA: 94 //读取数据 8 位,1~8 个时钟脉冲 95 if((bit_cnt == 6'd8 ) && (time_cnt == `AD_CLK_TIME)) 96 ad_fsm_ns = FSM_WAIT_CONV;//如果读取数据状态完成就进入等待状态 97 else 98 ad_fsm_ns = ad_fsm_cs; //否则保持原状态不变 99 FSM_WAIT_CONV: 100 //19 x 0.909us = 17.271us 用于延迟 17us 101 if((bit_cnt == 6'd18) && (time_cnt == `AD_CLK_TIME)) 102 ad_fsm_ns = FSM_END; //如果等待状态完成就进入读取状态 103 else 104 ad_fsm_ns = ad_fsm_cs; //否则保持原状态不变 105 FSM_END: 106 ad_fsm_ns = FSM_READY; //完成一次数据转换,进入下一次转换 107 default:ad_fsm_ns = FSM_IDLE; 108 endcase 109 end Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 155 110 111 //时序电路,用来给 time_cnt 寄存器赋值 112 always @ (posedge CLK_50M or negedge RST_N) 113 begin 114 if(!RST_N) //判断复位 115 time_cnt <= 6'h0; //初始化 time_cnt 值 116 else 117 time_cnt <= time_cnt_n; //用来给 time_cnt 赋值 118 end 119 120 //组合电路,实现 0.909us 的定时计数器 121 always @ (*) 122 begin 123 if(time_cnt == `AD_CLK_TIME) //判断 0.909us 时间 124 time_cnt_n = 6'h0; //如果到达 0.909us,定时器清零 125 else 126 time_cnt_n = time_cnt + 6'h1; //如果未到 0.909us,定时器继续加 1 127 end 128 129 //时序电路,用来给 bit_cnt 寄存器赋值 130 always @ (posedge CLK_50M or negedge RST_N) 131 begin 132 if(!RST_N) 133 bit_cnt <= 6'h0; //判断复位 //初始化 bit_cnt 值 134 else 135 bit_cnt <= bit_cnt_n; //用来给 bit_cnt 赋值 136 end 137 138 //组合电路,用来记录时钟周期个数的计数器 139 always @ (*) 140 begin 141 if(ad_fsm_cs != ad_fsm_ns) //判断状态机的当前状态 142 bit_cnt_n = 6'h0; //如果当前的状态不等于下一个状态,计时器就清零 143 else if(time_cnt == `AD_CLK_TIME_HALF)//判断 0.4545us 时间 144 bit_cnt_n = bit_cnt + 6'h1; //如果到达 0.4545us,计数器就加 1 145 else 146 bit_cnt_n = bit_cnt; //否则计数器保持不变 147 end 148 149 //时序电路,用来给 AD_CLK 寄存器赋值 150 always @ (posedge CLK_50M or negedge RST_N) 151 begin 152 if(!RST_N) //判断复位 153 AD_CLK <= 1'h0; //初始化 AD_CLK 值 http://www.fpga.gs/ 156 项目实战篇 §7 154 else 155 AD_CLK <= AD_CLK_N; //用来给 AD_CLK 赋值 156 end 157 158 //组合电路,用来生成 AD 的时钟波形 159 always @ (*) 160 begin 161 if(ad_fsm_cs != FSM_DATA) //判断状态机的当前状态 162 AD_CLK_N = 1'h0; //如果当前的状态不等于读取数据状态,AD_CLK_N 就置 0 163 else if(time_cnt == `AD_CLK_TIME_HALF)//判断 0.4545us 时间 164 AD_CLK_N = 1'h1; //如果到达 0.4545us,ADC_CLK_N 就置 1 165 else if(time_cnt == `AD_CLK_TIME)//判断 0.909us 时间 166 AD_CLK_N = 1'h0; //如果到达 0.909us,AD_CLK_N 就置 0 167 else 168 AD_CLK_N = AD_CLK; //否则保持不变 169 end 170 171 //时序电路,用来给 AD_CS 寄存器赋值 172 always @ (posedge CLK_50M or negedge RST_N) 173 begin 174 if(!RST_N) 175 AD_CS <= 1'h0; //判断复位 //初始化 AD_CS 值 176 else 177 AD_CS <= AD_CS_N; //用来给 AD_CS 赋值 178 end 179 180 //组合电路,用来生成 AD 的片选波形 181 always @ (*) 182 begin 183 if((ad_fsm_cs == FSM_DATA) || (ad_fsm_cs == FSM_READY))//判断状态机的当前状态 184 AD_CS_N = 1'h0;//如果当前状态等于读取数据状态或等于延时 1.4us 状态,AD_CS_N 就置 0 185 else 186 AD_CS_N = 1'h1;//如果当前状态不等于读取数据状态或不等于延时 1.4us 状态,AD_CS_N 就置 1 187 end 188 189 //时序电路,用来给 ad_data_reg 寄存器赋值 190 always @ (posedge CLK_50M or negedge RST_N) 191 begin 192 if(!RST_N) //判断复位 193 ad_data_reg <= 8'h0; //初始化 ad_data_reg 值 194 else 195 ad_data_reg <= ad_data_reg_n; //用来给 ad_data_reg 赋值 196 end 197 Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 157 198 //组合电路,将 AD 线上的数据保存到移位寄存器中 199 always @(*) 200 begin 201 if((ad_fsm_cs == FSM_DATA) && (!AD_CLK) && (AD_CLK_N))//判断每一个时钟的上升沿 202 ad_data_reg_n = {ad_data_reg[6:0],AD_DATA};//将数据存入移位寄存器中,高位优先 203 else 204 ad_data_reg_n = ad_data_reg; //否则保持不变 205 end 206 207 //时序电路,用来给 data_out 寄存器赋值 208 always @ (posedge CLK_50M or negedge RST_N) 209 begin 210 if(!RST_N) //判断复位 211 data_out <= 8'h0; //初始化 data_out 值 212 else 213 data_out <= data_out_n; //用来给 data_out 赋值 214 end 215 216 //组合电路,将移位寄存器中的数据存入 data_out 中,可用于输出 217 always @ (*) 218 begin 219 if(ad_fsm_cs == FSM_END) //判断复位 220 data_out_n = ad_data_reg; //初始化 data_out 值 221 else 222 data_out_n = data_out; //用来给 data_out 赋值 223 end 224 225 //时序电路,用来给 o_vol_int 寄存器赋值 226 always @ (posedge CLK_50M or negedge RST_N) 227 begin 228 if(!RST_N) //判断复位 229 o_vol_int <= 4'h0; //初始化 o_vol_int 值 230 else 231 o_vol_int <= o_vol_int_n; //用来给 o_vol_int 赋值 232 end 233 234 //时序电路,用来给 o_vol_dec 寄存器赋值 235 always @ (posedge CLK_50M or negedge RST_N) 236 begin 237 if(!RST_N) 238 o_vol_dec <= 4'h0; //判断复位 //初始化 o_vol_dec 值 239 else 240 o_vol_dec <= o_vol_dec_n; //用来给 o_vol_dec 赋值 241 end http://www.fpga.gs/ 158 项目实战篇 §7 242 243 //将 AD 的 8 位数据转换成数码管显示电压 244 assign o_vol_int_n = mid1[11:8]; 245 assign o_vol_dec_n = mid2[11:8]; //整数部分 //小数部分 246 247 // AD 的电压计算公式:AD_DATA / FF = voltage / 5 (5V 为 ADC 的参考电压) 248 // voltage = 5 * AD_DATA / FF 249 // voltage = 5 * AD_DATA >> 8 250 251 // voltage(整数部分) = ((AD_DATA << 2) + AD_DATA) >> 8 252 assign mid1 = {2'h0,data_out[7:0],2'h0} + {4'h0,data_out[7:0] }; 253 // voltage(小数部分) = ((5 * AD_DATA & 0XFF) * 10 ) >> 8 254 assign mid2 = {1'h0, mid1[7:0],3'h0} + {3'h0, mid1[7:0],1'h0}; 255 256 endmodule 下面我们就来简单的介绍一下该代码,代码第 1 至 63 行是端口声明和参数定义,第 69 至 109 是一个状态机,这个状态机就是围绕着我们之前讲解的 TLC549 时序图来编写的,它先等待 2.727us 用于初始化延时,紧接着就是我们在时序图中要注意的 1.4us 的延迟。延迟完了以后, 就是我们的 8 个 CLOCK 时钟用来读取我们的 8 位数据。读完了数据以后我们需要等待 17us 完 成一次数据转换。第 112 至 127 行是 0.909us 的定时计数器 time_cnt,它是用来对系统时钟计 数,用于产生 AD 模块的低频时钟信号,这里需要我们注意的是,它不是一个简单的系统时钟分 频信号,因为分频信号是无完全掌握的,而我们要对 AD_CLK 每一次的 0 或 1 变化要完全掌握。 第 130 至 147 是用来记录时钟周期个数的计数器 bit_cnt,它能够对 AD_CLK 的周期进行计数, 并且可以控制在状态机中每个状态中停留的时间。第 150 至 169 行是我们的 AD_CLK 时钟波形 的产生,它主要是通过判断状态机和 time_cnt 来产生。第 172 至 187 是我们的 AD_CS 片信号 的产生,它主要是通过判断状态机的状态来产生的。第 190 至 205 是用来读取 AD 转换完成的 数据,将读取的数据依次存入我们的移位寄存器中。第 208 至 254 是用来将移位寄存器中的数 据转换成数码管显示的电压输出给我们的数码管进行显示。这里我们需要注意的是,我们没有使 用取模和求余运算符将读到的数据转换成电压,我们使用的是 FPGA 最善长的移动操作。大家 知道取模和求余会生成一个庞大的电路,电路运行极慢,而使用移动操作,可以提高运行的速度, 并且能够节省很多资源。 7.4.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Ad_Top.sof 文件下载到开发板中,接着我们使用跳 线将我们的 AD 模拟信号输入端 ANALOG IN 跳到 3.3V 电压上,我们可以看到我们的 A4 开发 板数码管上显示了 3.3,如图 7.15 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 159 图 7.15 AD 外设的板级调试图 如果我们使用跳线将我们的 AD 模拟信号输入端 ANALOG IN 跳到 5.0V 电压上,结果数码 管上没有显示 5.0 而是显示 4.9。这是因为参考电压在波动,有误差所导致的。 §7.5 DA控制原理 7.5.1 DA 基本概述 介绍完了 AD 外设,接下来我们就来介绍最后一个 DA 外设。下面我们就来说下什么是 DA, DA 的全称 Digital to Analog,简称 DA,中文名称是数字转模拟,正好与我们前面讲解的 AD 相对应。它的功能就是将时间离散、幅值也离散的数字信号转换为时间连续、幅值也连续的模拟 信号。学过我们数字电路篇的朋友我们都知道,在数字信号中,它是只有高电平 1 和低电平 0 的, 一般来说我们高电平是 TTL 电平也就是 3.3V,如果我们想用数字信号生成正弦波,那么显然是 不可能的,生成方波还是有可能的。我们想要生成正弦波怎么办呢?我们必须通过把这个数字信 号转换成模拟信号,使用模拟信号来生成正弦波。由于我们的 A4 开发板上所采用的是 TLC5615 DA 芯片,所以接下来我们主要介绍的是 TLC5615 这个 DA 芯片。TLC5615 是美国德州仪器公 司生产的 10 位串行数模转换芯片,它的输出为电压型,最大输出电压是基准电压值的两倍,上 电时内部自动复位;它的功耗低,最大功耗为 1.75mW;它的转换速率块,更新率为 1.21MHz; 它采用串行总线接口,兼容 SPI 通信,能进行多片级联使用。性能比早期电流型输出的 DA 转 换器要好。如图 7.16 所示是 TLC5615 的引脚图。 http://www.fpga.gs/ 160 项目实战篇 §7 DIN 1 SCLK 2 CS 3 DOUT 4 8 VDD 7 OUT 6 REFIN 5 AGND 图 7.16 DA TLC5615 的引脚图 从该图中我们可以看出,它有八个管脚,分别如表 7.4 所示。 表 7.4 DA TLC5615 接口管脚表 管脚 名称 描述 1 DIN 串行二进制输入端口 2 SCLK 串行时钟输入端 3 CS 片选信号 4 DOUT 用于级联的串行数据输出 5 AGND 模拟地 6 REFIN 基准电压输入端 7 OUT DA 模拟电压输出端 8 VDD 电源电压输入端 在这八个管脚中,我们只需要通过 3 根串行总线就可以完成 10 位数据的串行输入,如图 7.17 所示。 图 7.17 A4 开发板上 DA TLC5615 连接原理图 从该图中我们可以看出,TLC5615 这个芯片的基准电压输入端 REF+接的是 2.5V,我们是 将 5V 通过电阻进行分压分到了 2.5V。系统电源 VCC 接的是 5V 电压,模拟地 AGND 接的是 地,DA 模拟电压输出端 OUT 接在了我们 A4 开发板的 SMA 头上,剩下的四个管脚串行二进制 输入端 DIN、串行时钟输入端 SCLK、片选信号 CS 和用于级联的串行数据输出 DOUT 都接在 我们的 FPGA I/O 管脚上。这里我们需要说明的是 DOUT,虽然它接到了我们的 FPGA I/O 管脚 上,但是我们一般是用不到它的。TLC5615 它有两种工作模式,如图 7.18 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 161 图 7.18 DA TLC5615 工作模式 从图中我们可以看出,第一种工作模式是 12 位的,它主要分为 10 位的有效位和 2 位的填 充位,这 2 位填充位数据可以任意,我们一般使用的是这种模式。第二种工作模式是 16 位的, 它主要分为高 4 位虚拟位,10 位有效位以及低 2 位填充位,这种模式一般用于级联方式,我们 可以将本片的 DOUT 接到下一片的 DIN。 7.5.2 DA 通信协议 介绍完了 DA 基本概述,接下来我们再来看下 DA 的通信协议。在开始讲解 DA 的通信协议 之前,我们先来看下 DA 的内部功能框图,如图 7.19 所示。 图 7.19 DA TLC5615 内部功能框图 从这个功能框图我们可以看出,它主要由以下五部分组成, (1) 一个 16 位移位寄存器,用来接收串行移入的二进制数,并且有一个级联的数据输出端 DOUT。 (2) 并行输入输出的 10 位 DAC 寄存器,为 10 位 DAC 电路提供待转换的二进制数据。 (3) 电压跟随器为参考电压端 REFIN 提供很高的输入阻抗,大约 10M 欧姆。 (4) ×2 电路提供最大值为 2 倍于 REFIN 的输出。 (5) 上电复位电路和控制电路。 http://www.fpga.gs/ 162 项目实战篇 §7 说完了 DA 的功能框图,接下来我们回到它的工作时序上,它的工作时序如图 7.20 所示。 图 7.20 DA TLC5615 通信时序图 从该图中我们可以看出:当 CS 为低电平时,在每一个 SCLK 时钟的上升沿,我们将串行二 进制输入端口 DIN 输入的数据,一位一位的移入到 16 位移位寄存器中。这时候我们要知道的是, 二进制最高位首先被移入进去,当 DIN 中的所有数据移入完了以后,片选信号 CS 将会被拉高, 这时 16 位移位寄存器中的 10 位有效数据将会被锁存于并行输入输出的 10 位 DAC 寄存器中, 为 10 位 DAC 电路提供待转换的二进制数据进行转换。这里需要我们注意的是,CS 的上升和下 降都必须发生在 SCLK 为低电平期间,并且当 CS 为高电平时,串行输入数据不能被移入 16 位 移位寄存器中。说完了 DA 的通信协议,最后我们再来补充说明一下 DA 的串行时钟和更新速 率,我们可以在 DA 的数据手册中找到如图 7.21 所示。 图 7.21 DA TLC5615 串行时钟和更新速率 通过该图我们可以看出,f(SCLK)max 可以通过 1/tw(CH)+ tw(CL)这个公式进行计算的,这 个公式中的 tw(CH)和 tw(CL)就是我们时序图中 SCLK 高电平和低电平持续的时间,通过查找 TLC5615 的芯片手册我们可以知道它们都是 25ns。因此,f(SCLK)max 计算得出最大约为 14MHz。我们知道 TLC5615 它有两种工作方式,第一种是 12 位,第二种是 16 位,我们这里就 按照手册中的 16 位来进行计算,因此我们就可以通过第二个公式计算出 TLC5615 转换一次需 要 16x(25+25)+20 = 820ns,然后我们再代入第一个公式可以计算出 f(SCLK)max = 1/820ns = 1.21MHz,当然这 1.21Mhz 只是理想情况下的,根据上图中我们还可以知道,However,DAC setting time to 10 bit of 12.5us limits the updata rate to 80Khz for full-scale input setp transitions, 这什么意思,它就是我们最后虽然可以已 1.21MHz 的速率去更新这个 DA,但是对于 DA 来说, Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 163 信号从一个高变成一个低的陡变过程,它需要一定的建立时间,也就是上面所说的 12.5us,这 个 12.5us 是非常长的,理想情况下它从一个电压跳到另一个电压需要 80KHz。实际上严格计算 的话,它只能达到 75KHz 左右,也就是 1/(820ns+12.5us)。如果你学过《信号与系统》或者是 《数字信号处理》的话,那么你就知道想要生成一个完整周期信号,至少需要 fmax 的两倍,这 意味着我们想要生成一个正弦波,它能够看见,至少需要 2 个点,这样的话我们的 TLC5615 的 极限速度也就是 75KHz/2=37.5KHz。 §7.6 DA实际应用 7.6.1 功能概述 首先我们先来说下我们的工程将要实现一个怎样的功能:我们的 DA 工程主要实现的功能是 生成一个频率为 1KHz 的正弦波。我们先将正弦波的数据保存在 ROM 中,然后我们通过调用 ROM 中的数据送给我们的 DA 模块进行数模转换,转换完成的模拟信号我们直接通过 A4 开发 板上的 SMA 头进行了输出。这里需要我们注意的是,我们在 A4 开发板上是看不到任何效果的, 我们需要使用示波器测量我们的 DA 输出管脚,然后才能看到输出的 1KHz 的正弦波形。 7.6.2 设计说明 根据上面的功能概述,我们可以设计出如图 7.22 所示的功能模块。 图 7.22 DA 外设的框架结构图 从该图中我们可以看出,该程序主要分成了 2 个模块,它们分别是 Da_Data_Module 和 Da_Module。Da_Data_Module 是用来将 ROM 中的数据读出并送给我们的 DA 模块进行数模 转换。Da_Module 是用于控制我们的 DA 外设进行数模转换的。根据这个功能模块我们还可以 看到,我们主要使用了 CLK_50M、RST_N、DA_DIN、DA_CS 和 DA_CS 管脚,下面我们对 这些管脚进行一个管脚分配,如图 7.23 所示。 图 7.23 DA 外设的管脚分配图 http://www.fpga.gs/ 164 项目实战篇 §7 7.6.3 源码解析 介绍完了设计,接下来我们就来对源码进行一个分析。首先我们介绍的是 Da_Data_Module 模块代码,如代码 7.3 所示。 代码 7.3 Da_Data_Module.v 代码 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : Da_Data_Module.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : DA 数据生成模块 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 module Da_Data_Module 8 ( 9 //输入端口 10 CLK_50M,RST_N, 11 //输出端口 12 da_data,da_start 13 ); 14 15 //--------------------------------------------------------------------------16 //-- 外部端口声明 17 //--------------------------------------------------------------------------- 18 input CLK_50M; //时钟端口,开发板用的 50M 晶振 19 input RST_N; //复位端口,低电平复位 20 output [9:0] da_data; //从 ROM 中读出的 DA 数据 21 output da_start; //DA 模块的开始标志位 22 23 //--------------------------------------------------------------------------- 24 //-- 内部端口声明 25 //--------------------------------------------------------------------------- 26 reg [7:0] time_cnt; //计数器 27 reg [7:0] time_cnt_n; //time_cnt 的下一个状态 28 reg [9:0] rom_addr; //rom 的地址端口 29 reg [9:0] rom_addr_n; //rom_addr 的下一个状态 30 reg da_start; //DA 模块的开始标志位 31 reg da_start_n; //da_start 的下一个状态 32 33 //--------------------------------------------------------------------------34 //-- 逻辑功能实现 35 //--------------------------------------------------------------------------- 36 //例化 ROM 模块 37 ROM ROM_Init 38 ( Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 165 39 .address (rom_addr ), //rom 的地址端口 40 .clock (CLK_50M ), //rom 的时钟端口 41 .q (da_data[9:2] ) //rom 的数据端口 42 ); 43 44 //时序电路,用来给 time_cnt 寄存器赋值 45 always @ (posedge CLK_50M or negedge RST_N) 46 begin 47 if(!RST_N) 48 time_cnt <= 8'h0; //判断复位 //初始化 time_cnt 值 49 else 50 time_cnt <= time_cnt_n; //用来给 time_cnt 赋值 51 end 52 53 //1KHz 的正弦波,周期为 1ms,即 1ms 送出一个完整的 512 个点的波形 54 //每个点的时间是 1 / 512 * 1000000(ns) 我们采用开发板上的 50Mhz 晶振系统周期为 20ns 55 //FPGA 内部的计数器值为 1 / 512 * 1000000(ns) / 20 = 97 56 always @ (*) 57 begin 58 if(time_cnt == 8'd96) //判断计数器是否到 97,从 0 开始即 96 59 time_cnt_n = 8'h0; //如果到 97 就将 time_cnt_n 置 0 60 else 61 time_cnt_n = time_cnt + 8'h1; //否则,time_cnt_n 加 1 62 end 63 64 //时序电路,用来给 rom_addr 寄存器赋值 65 always @ (posedge CLK_50M or negedge RST_N) 66 begin 67 if(!RST_N) 68 rom_addr <= 10'h0; //判断复位 //初始化 time_cnt 值 69 else 70 rom_addr <= rom_addr_n; //用来给 time_cnt 赋值 71 end 72 73 //组合电路,发送一个完整的 512 个点的波形,我们的 mif 文件中有 1024 个点 74 always @ (*) 75 begin 76 if(time_cnt == 8'd96) //判断计数器是否到 97,从 0 开始即 96 77 rom_addr_n = rom_addr + 10'h2;//如果到 97,rom_addr_n 加 2,1024 / 2 = 512 78 else 79 rom_addr_n = rom_addr; //否则保持不变 80 end 81 82 //时序电路,用来给 da_start 寄存器赋值 http://www.fpga.gs/ 166 项目实战篇 §7 83 always @ (posedge CLK_50M or negedge RST_N) 84 begin 85 if(!RST_N) 86 da_start <= 1'h0; //判断复位 //初始化 da_start 值 87 else 88 da_start <= da_start_n; //用来给 da_start 赋值 89 end 90 91 //组合电路,生成 DA 工作开始标识 92 always @ (*) 93 begin 94 if(time_cnt == 8'h0) //判断计数器的值 95 da_start_n = 1'h1; //如果计数器为 0,标识为 1 96 else 97 da_start_n = 1'h0; //否则计数器则为 0 98 end 99 100 assign da_data[1:0] = 2'h0; //给 da_data 低两位赋值 101 102 endmodule 下面我们就来简单的介绍一下该代码,代码第 1 至 31 行是端口声明和参数定义。第 37 至 42 行是 ROM IP 核的例化。如果还有不会创建 ROM IP 核的朋友,那么你可以参考我们的《软 件工具篇》中的内容。我们《软件工具篇》中有详细介绍如何创建 ROM IP 核的教程。第 45 至 62 是按一定的速率将数据从 ROM 中读出来送到 DA 模块中,这个速率决定了波形的频率,我 们这里设置的是 1KHz。第 65 至 80 是用来生成 ROM 的地址端口。第 83 至 98 行是用来启动 DA 模块的。介绍完了 Da_Data_Module 模块代码,下面我们就来看下 Da_Module 模块代码。 如代码 7.4 所示。 代码 7.4 Da_Module.v 代码 1 //--------------------------------------------------------------------------- 2 //-- 文件名 : Da_Module.v 3 //-- 作者 : ZIRCON 4 //-- 描述 : DA 模块 5 //-- 修订历史 : 2014-1-1 6 //--------------------------------------------------------------------------- 7 module Da_Module 8 ( 9 //输入端口 10 CLK_50M,RST_N, 11 //TLC5615 输出管脚 12 DA_CLK,DA_DIN,DA_CS, 13 //用户逻辑输入与输出 14 DA_DATA,send_start,send_finish Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 167 15 ); 16 17 //--------------------------------------------------------------------------- 18 //-- 外部端口声明 19 //--------------------------------------------------------------------------- 20 21 input CLK_50M; //时钟的端口,开发板用的 50M 晶振 22 input 23 output reg 24 output 25 output reg RST_N; DA_CLK; DA_DIN; DA_CS; //复位的端口,低电平复位 //DA 时钟端口 //DA 数据输出端口 //DA 片选端口 26 input [ 9:0] DA_DATA; //DA 数据的输入 27 input send_start; //DA 工作开始标识 28 output send_finish; //DA 工作完成标识 29 30 //--------------------------------------------------------------------------- 31 //-- 内部端口声明 32 //--------------------------------------------------------------------------- 33 reg [ 3:0] FSM_CS; //状态机的当前状态 34 reg [ 3:0] FSM_NS; //状态机的下一个状态 35 reg 36 reg 37 reg 38 reg [ 3:0] [ 3:0] [11:0] [11:0] bit_cnt; bit_cnt_n; shift_reg; shift_reg_n; //用来记录数据发送个数的计数器 //bit_cnt 的下一个状态 //移位寄存器,将最高位数据移给 DA_DIN //shift_reg 的下一个状态 39 reg [ 3:0] time_cnt; //用于记录时钟个数的计数器 40 reg [ 3:0] time_cnt_n; //time_cnt 的下一个状态 41 reg 42 reg DA_CLK_N; DA_CS_N; //DA_CLK 的下一个状态 //DA_CS 的下一个状态 43 44 parameter FSM_IDLE = 4'h0; //状态机的空闲状态 45 parameter FSM_READY = 4'h1; //状态机的准备状态,将 CS 拉低 46 parameter FSM_SEND = 4'h2; //状态机的发送状态,发送 12 个数据 47 parameter FSM_FINISH = 4'h4; //状态的完成状态,将 CS 拉高 48 49 //--------------------------------------------------------------------------- 50 //-- 逻辑功能实现 51 //--------------------------------------------------------------------------- 52 //时序电路,用来给 time_cnt 寄存器赋值 53 always @ (posedge CLK_50M or negedge RST_N) 54 begin 55 if(!RST_N) 56 time_cnt <= 4'h0; //判断复位 //初始化 time_cnt 值 57 else 58 time_cnt <= time_cnt_n; //用来给 time_cnt 赋值 http://www.fpga.gs/ 168 项目实战篇 §7 59 end 60 61 //组合电路,用于记录时钟个数的计数器 62 always @ (*) 63 begin 64 if(FSM_CS != FSM_NS) //判断状态机的当前状态 65 time_cnt_n = 4'h0; //如果当前的状态不等于下一个状态,计数器就清零 66 else if(DA_CLK != DA_CLK_N) //判断时钟的当前状态 67 time_cnt_n = 4'h0; //如果当前的时钟不等于下一个时钟状态,计数器清零 68 else 69 time_cnt_n = time_cnt + 4'h1;//否则计数器就加 1 70 end 71 72 //时序电路,用来给 bit_cnt 寄存器赋值 73 always @ (posedge CLK_50M or negedge RST_N) 74 begin 75 if(!RST_N) //判断复位 76 bit_cnt <= 4'h0; //初始化 bit_cnt 值 77 else 78 bit_cnt <= bit_cnt_n; //用来给 bit_cnt 赋值 79 end 80 81 //组合电路,用来记录数据发送个数的计数器 82 always @ (*) 83 begin 84 if(FSM_CS == FSM_FINISH) //判断状态机的当前状态 85 bit_cnt_n = 4'h0; //如果当前的状态不等于完成状态,bit_cnt_n 就置 0 86 else if(DA_CLK && (!DA_CLK_N)) //判断时钟的当前状态 87 bit_cnt_n = bit_cnt + 4'h1;//如果当前的时钟等于下一个时钟取非,bit_cnt_n 就加 1 88 else 89 bit_cnt_n = bit_cnt; //否则保持不变 90 end 91 92 //时序电路,用来给 shift_reg 寄存器赋值 93 always @ (posedge CLK_50M or negedge RST_N) 94 begin 95 if(!RST_N) //判断复位 96 shift_reg <= 12'h0; //初始化 shift_reg 值 97 else 98 shift_reg <= shift_reg_n; //用来给 shift_reg 赋值 99 end 100 101 //组合电路,移位寄存器,将 DA_DATA 的数据依次移给 DA_DIN 102 always @ (*) Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 169 103 begin 104 if(send_start) //判断开始标识 105 shift_reg_n = {DA_DATA,2'h0};//如果标志为 1,则将 DA_DATA 的数据赋值给移位寄存器 106 else if(DA_CLK && (time_cnt == 4'h0))//判断 DA_CLK 的状态 107 shift_reg_n = {shift_reg[10:0] , 1'h0};//如果 DA_CLK 为高,移位寄存器开始移位 108 else 109 shift_reg_n = shift_reg; //否则保持不变 110 end 111 112 //--------------------------------------------------------------------------- 113 //-- 状态机 114 //--------------------------------------------------------------------------- 115 //时序电路,用来给 FSM_CS 寄存器赋值 116 always @ (posedge CLK_50M or negedge RST_N) 117 begin 118 if(!RST_N) 119 FSM_CS <= FSM_IDLE; //判断复位 //初始化 FSM_CS 值 120 else 121 FSM_CS <= FSM_NS; //用来给 FSM_CS 赋值 122 end 123 124 //组合电路,用来实现状态机 125 always @ (*) 126 begin 127 case(FSM_CS) //判断状态机的当前状态 128 FSM_IDLE: 129 if(send_start) //判断开始标识 130 FSM_NS = FSM_READY; //如果标识符为 1,则进入准备状态 131 else 132 FSM_NS = FSM_CS; //否则保持原状态不变 133 134 FSM_READY: 135 if(time_cnt == 4'h1) //等待两个时钟 136 FSM_NS = FSM_SEND; //两个时钟到了便进入发送状态 137 else 138 FSM_NS = FSM_CS; //否则保持原状态不变 139 140 FSM_SEND: 141 if((bit_cnt == 4'hC)&&(!DA_CLK))//发送数据 12 个 142 FSM_NS = FSM_FINISH; //发送完成进入完成状态 143 else 144 FSM_NS = FSM_CS; //否则保持原状态不变 145 146 FSM_FINISH: http://www.fpga.gs/ 170 项目实战篇 §7 147 if(time_cnt == 4'h2) //等待三个时钟 148 FSM_NS = FSM_IDLE; //完成一次数据转换,进入下一次转换 149 else 150 FSM_NS = FSM_CS; //否则保持原状态不变 151 152 default:FSM_NS = FSM_IDLE; 153 endcase 154 end 155 156 //时序电路,用来给 DA_CS 寄存器赋值 157 always @ (posedge CLK_50M or negedge RST_N) 158 begin 159 if(!RST_N) //判断复位 160 DA_CS <= 1'h1; //初始化 DA_CS 值 161 else 162 DA_CS <= DA_CS_N; //用来给 DA_CS 赋值 163 end 164 165 //组合电路,用来生成 DA 的片选波形 166 always @ (*) 167 begin 168 if(FSM_CS == FSM_READY) //判断状态机的当前状态 169 DA_CS_N = 1'h0; //如果当前的状态等于准备状态,DA_CS_N 就置 0 170 else if((FSM_CS == FSM_FINISH) && (time_cnt == 4'h1))//判断状态机的当前状态 171 DA_CS_N = 1'h1; //如果当前的状态等于完成状态并且时钟计数器等于 1,DA_CS_N 就置 1 172 else 173 DA_CS_N = DA_CS; //否则保持不变 174 end 175 176 //时序电路,用来给 DA_CLK 寄存器赋值 177 always @ (posedge CLK_50M or negedge RST_N) 178 begin 179 if(!RST_N) //判断复位 180 DA_CLK <= 1'h0; //初始化 DA_CLK 值 181 else 182 DA_CLK <= DA_CLK_N; //用来给 DA_CLK 赋值 183 end 184 185 //组合电路,用来生成 DA 的时钟波形 186 always @ (*) 187 begin 188 if((FSM_CS == FSM_SEND) && (!DA_CLK) && (time_cnt == 4'h1))//判断状态机当前状态 189 DA_CLK_N = 1'h1; //如果符合上述条件,每两个时钟就会生成一个高电平的 DA_CLK 190 else if((FSM_CS == FSM_SEND) && (DA_CLK) && (time_cnt == 4'h1))//判断当前状态 Zircon Opto-Electronic Technology CO.,Ltd. §7 数字示波器设计分析 171 191 DA_CLK_N = 1'h0; //如果符合上述条件,每两个时钟就会生成一个低电平的 DA_CLK 192 else 193 DA_CLK_N = DA_CLK; //否则保持不变 194 end 195 196 assign DA_DIN = shift_reg[11]; //将移位寄存器的最高位赋值给 DA_DIN 197 assign send_finish = (FSM_CS == FSM_IDLE); //标识发送完成 198 199 endmodule 下面我们就来简单的介绍一下该代码,代码第 1 至 47 行是端口声明和参数定义。第 53 至 70 行是用于记录时钟个数的计数器。第 73 至 90 是用于记录数据发送个数的计数器。第 93 至 110 是一个移位寄存器,用于 DA 数据的发送。第 116 至 154 是一个状态机,这个状态机就是围 绕着我们之前讲解的 TLC5615 时序图来编写的,它先等待 send_start 启动信号,当 send_start 启动信号为高是,状态机才会开始工作,进入准备状态,在准备状态中等待两个时钟后,状态机 进入发送数据状态,在该状态中我们将发送 12 位的数据。发送完了数据以后,我们需要等待三 个时钟完成一次数据转换。第 157 至 174 是用来生成 DA 的片选信号。第 177 至 194 是用来生 成 DA 的时钟信号。 7.6.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Da_Top.sof 文件下载到开发板中,接着我们使用示 波器测量我们的 DA 输出管脚,如图 7.24 所示。 图 7.24 DA 外设的板级调试图 我们可以在示波器中看到,我们测量的正弦波确实是 1KHz。 http://www.fpga.gs/ 数字示波器设计实战 第八章 数字示波器设计实战 §8.1 功能概述 当我们把 VGA、AD 和 DA 这三个外设学习完了以后,接下来我们同前面多终端点歌系统项 目一样,也是把我们之前所学的三个高级外设合并在一起,最终实现一个复杂的数字示波器项目。 这里我们需要注意的是,如果你在学习我们 VGA、AD 和 DA 的时候,你没有能够很好的掌握, 或者说你还是不清楚这些高级外设是如何控制的,那么我们这里建议大家还是不要急于往下学 习。我们只有将这些高级外设给搞明白、弄清楚了之后,我们在接下来的学习过程中将会是非常 轻松的,否则,我们在接下来的学习过程中将会非常痛苦。 下面我们就来简单的介绍一下,我们利用这三个高级外设将实现一个怎样功能的数字示波 器。首先我们使用了 DA 进行了一个数模转换,生成了 500Hz 的正弦波、锯齿波和三角波。然 后我们将 DA 的输出连接到 AD 的输入,这时我们就可以使用 AD 进行了一个模数转换,将 DA 生成的正弦波、锯齿波和三角波模拟信号转换为数字信号,AD 转换完成的数字信号,我们通过 了两个双口 RAM 进行存储,第一个双口 RAM 中的数字信号,我们使用 VGA 进行波形显示, 第二个 RAM 中的数字信号,我们使用 FFT 进行快速傅氏变换求出该波形的频率和峰峰值,然 后我们再将频率和峰峰值传输给 VGA 进行一个显示。 §8.2 设计说明 根据上面的功能概述,我们可以设计出如图 8.1 所示的功能模块。 图 8.1 数字示波器项目的框架结构图 从该图中我们可以看出,该程序主要分成了 6 个模块,它们分别是 PLL_Module 、 Da_Data_Module、Da_Module 、Ad_Module、Data_Process 和 Vga_Module。PLL_Module 用于提供 40MHz 时钟给 VGA 模块使用。Da_Data_Module 模块中有着三个 ROM,这三个 176 项目实战篇 §8 ROM 分别存储着正弦波、锯齿波和三角波的数据,我们根据拨码开关选择其中的一个 ROM 将 其数据读出传输给我们的 Da 模块进行数模转换。Da_Module 用于将正弦波、锯齿波和三角波 的数据转换成模拟信号输出。Ad_Module 用于接收 Da 转换的模拟信号并转换成数字信号,转 换完成的数字信号将送给我们的 Data_process 模块。Data_Process 主要是处理 Ad 转换完成 的数据。该模块中有着两个双口 RAM 和 FFT 控制模块。Vga_Module 主要是用来显示频率、 峰峰值和波形。根据这个功能模块我们还可以看到,我们主要使用了 CLK_50M、RST_N、 AD_DATA、SWITCH、AD_CLK、AD_CS、DA_CLK、DA_CS、DA_DIN、VGA_HSYNC、 VGA_VSYNC 和 VGA_DATA 管脚,下面我们对这些管脚进行一个管脚分配,如图 8.2 所示。 图 8.2 数字示波器项目的管脚分配图 §8.3 源码解析 介绍完了设计,接下来我们就来对源码进行一个分析。由于该项目代码太过庞大,我们这里 就不在一一给出代码进行详解,我们这里主要对项目中的重点、难点进行详细讲解说明。首先我 们讲解的第一个重点是 DA 数据生成模块,在之前的学习中我们是生成一个波形,而这里我们生 成了三个波形,如代码 8.1 所示。 代码 8.1 Da_Data_Module.v 代码 1 //例化正弦波 ROM 模块 2 ROM_SIN ROM_SIN_Init 3( 4 .address (rom_addr ), //ROM 地址 5 .clock (CLK_50M ), //ROM 时钟 6 .q (da_data_sin ) //从 ROM 中读出的正弦波数据 7 ); 8 9 //例化三角波 ROM 模块 Zircon Opto-Electronic Technology CO.,Ltd. §8 数字示波器设计实战 177 10 ROM_TRIANGLE ROM_TRIANGLE_Init 11 ( 12 .address (rom_addr 13 .clock (CLK_50M ), //ROM 地址 ), //ROM 时钟 14 .q (da_data_triangle ) //从 ROM 中读出的三角波数据 15 ); 16 17 //例化锯齿波 ROM 模块 18 ROM_SAWTOOTH ROM_SAWTOOTH_Init 19 ( 20 .address (rom_addr ), //ROM 地址 21 .clock (CLK_50M ), //ROM 时钟 22 .q (da_data_swatooth ) //从 ROM 中读出的锯齿波数据 23 ); 24 25 //拨码开关选择波形 26 always @ (posedge CLK_50M) 27 begin 28 case(SWITCH) 29 2'b00 : da_data <= {da_data_sin ,2'h0}; //拨码开关选择正弦波 30 2'b01 : da_data <= {da_data_swatooth ,2'h0}; //拨码开关选择锯齿波 31 2'b10 : da_data <= {da_data_triangle ,2'h0}; //拨码开关选择三角波 32 2'b11 : da_data <= {da_data_sin ,2'h0}; //拨码开关选择正弦波 33 default : da_data <= {da_data_sin ,2'h0}; //拨码开关选择正弦波 34 endcase 35 end 通过该代码我们可以看出,之前的代码我们是使用了一个 ROM 模块来存储正弦波波形,而 这里我们又多添加了两个 ROM 模块用来存储三角波和锯齿波,我们只需要通过拨码开关来选择 不同的波形数据,就可以实现三种波形的显示效果。说完了第一个重点,接下来我们再来看第二 个重点,第二个重点就是我们的数据处理模块 Data_Process,这个模块可以说是我们整个项目 的核心模块,如代码 8.2 所示。 代码 8.2 Data_Process.v 代码 1 module Data_Process 2 ( 3 //输入端口 4 CLK_50M,CLK_40M,RST_N,AD_CS,in_ad_data,vga_x, 5 //输出端口 6 vga_freq,vga_fengzhi,ad_to_vga_data 7 ); 8 9 //--------------------------------------------------------------------------10 //-- 外部端口声明 http://www.fpga.gs/ 178 项目实战篇 §8 11 //--------------------------------------------------------------------------- 12 input 13 input 14 input CLK_50M; CLK_40M; RST_N; //时钟端口,开发板用的 50M 晶振 //PLL 生成的 40M 时钟 //复位端口,低电平复位 15 input AD_CS; //AD 片选信号端口 16 input [ 7:0] in_ad_data; //AD 模数转换完成的数据输出 17 input [15:0] vga_x; //VGA 的 x 坐标 18 output [31:0] vga_freq; 19 output [31:0] vga_fengzhi; 20 output [ 7:0] ad_to_vga_data; //VGA 中显示的频率值 //VGA 中显示的峰峰值 //VGA 中显示的波形数据 21 22 //--------------------------------------------------------------------------- 23 //-- 内部端口声明 24 //--------------------------------------------------------------------------- 25 reg 26 wire 27 reg [ 1:0] [ 1:0] detect_edge; detect_edge_n; posedge_reg; //记录 AD_CS 的开始脉冲,即第一个上降沿 //detect_edge 的下一个状态 //上升沿标志 28 wire posedge_reg_n; //posedge_reg 的下一个状态 29 reg [15:0] ad_to_vga_addr; //读取 AD 到 VGA 的地址 30 reg [15:0] ad_to_vga_addr_n; //ad_to_vga_addr 的下一个状态 31 reg 32 reg 33 reg 34 reg [15:0] [15:0] [26:0] [26:0] ad_to_fft_addr; ad_to_fft_addr_n; time_cnt; time_cnt_n; //读取 AD 到 FFT 的地址 //ad_to_fft_addr 的下一个状态 //定时计数器 //time_cnt 的下一个状态 35 36 reg fft_rst_flag; //FFT 模块复位标志位 37 reg 38 wire 39 wire 40 wire [ 9:0] [ 9:0] [ 9:0] fft_rst_flag_n; fft_bit_cnt; fft_real_out_int; fft_imag_out_int; //fft_rst_flag 标志位 //FFT 位计数器 //FFT 实数的输出 //FFT 虚数的输出 41 wire [ 9:0] ad_to_fft_data; //FFT 中用到的 AD 数据 42 wire [31:0] vga_freq; //VGA 中显示的频率值 43 wire [31:0] vga_fengzhi; //VGA 中显示的峰峰值 44 45 //设置定时器的时间为 1s,计算方法为 (1*10^6)ns / (1/50)ns 50MHz 为开发板晶振 46 parameter SET_TIME_1S = 27'd50_000_000; 47 48 //--------------------------------------------------------------------------- 49 //-- 逻辑功能实现 50 //--------------------------------------------------------------------------- 51 //时序电路,用来给 detect_edge 寄存器赋值 52 always @ (posedge CLK_50M or negedge RST_N) 53 begin 54 if(!RST_N) //判断复位 Zircon Opto-Electronic Technology CO.,Ltd. §8 数字示波器设计实战 55 detect_edge <= 2'b11; //初始化 detect_edge 值 56 else 57 detect_edge <= detect_edge_n; //用来给 detect_edge 赋值 58 end 59 60 //组合电路,检测上升沿 61 assign detect_edge_n = {detect_edge[0] , AD_CS}; //接收 AD_CS 的时钟信号 62 63 //时序电路,用来给 posedge_reg 寄存器赋值 64 always @ (posedge CLK_50M or negedge RST_N) 65 begin 66 if(!RST_N) //判断复位 67 posedge_reg <= 1'b0; //初始化 posedge_reg 值 68 else 69 posedge_reg <= posedge_reg_n; //用来给 posedge_reg 赋值 70 end 71 72 //组合电路,判断上升沿,如果 detect_edge 等于 01,posedge_reg_n 就置 1 73 assign posedge_reg_n = (detect_edge == 2'b01) ? 1'b1 : 1'b0; 74 75 //时序电路,用来给 ad_to_vga_addr 寄存器赋值 76 always @ (posedge posedge_reg or negedge RST_N) 77 begin 78 if(!RST_N) //判断复位 79 ad_to_vga_addr <= 1'b0; //初始化 ad_to_vga_addr 80 else 81 ad_to_vga_addr <= ad_to_vga_addr_n;//给 ad_to_vga_addr 赋值 82 end 83 84 //组合电路,用于生成 RAM_AD_TO_VGA 的地址 85 always @ (*) 86 begin 87 if(ad_to_vga_addr < 16'd800) //判断地址 88 ad_to_vga_addr_n = ad_to_vga_addr + 1'b1;//地址累加 89 else 90 ad_to_vga_addr_n <= 0; //地址清零 91 end 92 93 //例化双口 RAM_AD_TO_VGA 模块 94 RAM_AD_TO_VGA AD_TO_VGA_Init 95 ( 96 .wrclock (CLK_50M 97 .wraddress (ad_to_vga_addr ), //写时钟 ), //写地址 98 .wren (posedge_reg ), //写使能 http://www.fpga.gs/ 179 180 项目实战篇 §8 99 .data (10'd255 - in_ad_data ), //写数据 100 101 .rdclock (CLK_40M 102 .rdaddress (vga_x - 16'd100 ), //读时钟 ), //读地址 103 .q (ad_to_vga_data ) //读数据 104 ); 105 106 //时序电路,用来给 ad_to_vga_addr 寄存器赋值 107 always @ (posedge posedge_reg or negedge RST_N) 108 begin 109 if(!RST_N) //判断复位 110 ad_to_fft_addr <= 1'b0; //初始化 ad_to_fft_addr 111 else 112 ad_to_fft_addr <= ad_to_fft_addr_n;//给 ad_to_fft_addr 赋值 113 end 114 115 //组合电路,用于生成 RAM_AD_TO_FFT 的地址 116 always @ (*) 117 begin 118 if(ad_to_fft_addr < 16'd256) //判断地址 119 ad_to_fft_addr_n = ad_to_fft_addr + 1'b1;//地址累加 120 else 121 ad_to_fft_addr_n <= 0; //地址清零 122 end 123 //例化双口 RAM_AD_TO_FFT 模块 124 RAM_AD_TO_FFT AD_TO_FFT_Init 125 ( 126 .wrclock (CLK_50M ), 127 .wraddress (ad_to_fft_addr ), 128 .wren (posedge_reg ), //写时钟 //写地址 //写使能 129 .data (in_ad_data ), //写数据 130 131 .rdclock (CLK_50M ), //读时钟 132 .rdaddress (fft_bit_cnt ), 133 .q (ad_to_fft_data ) //读地址 //读数据 134 ); 135 136 //时序电路,用来给 time_cnt 寄存器赋值 137 always @ (posedge CLK_50M or negedge RST_N) 138 begin 139 if(!RST_N) 140 time_cnt <= 27'h0; //判断复位 //初始化 time_cnt 值 141 else 142 time_cnt <= time_cnt_n; //用来给 time_cnt 赋值 Zircon Opto-Electronic Technology CO.,Ltd. §8 数字示波器设计实战 181 143 end 144 145 //组合电路,实现 1s 的定时计数器 146 always @ (*) 147 begin 148 if(time_cnt == SET_TIME_1S) //判断 1s 时间 149 time_cnt_n = 27'h0; //如果到达 1s,定时计数器将会被清零 150 else 151 time_cnt_n = time_cnt + 27'h1; //如果未到 1s,定时计数器将会继续累加 152 end 153 154 //时序电路,用来给 fft_rst_flag 寄存器赋值 155 always @ (posedge CLK_50M or negedge RST_N) 156 begin 157 if(!RST_N) 158 fft_rst_flag <= 1'b0; //判断复位 //初始化 fft_rst_flag 值 159 else 160 fft_rst_flag <= fft_rst_flag_n; //用来给 fft_rst_flag 赋值 161 end 162 163 //组合电路,用来生成 FFT 模块复位标志位 164 always @ (*) 165 begin 166 if(time_cnt == SET_TIME_1S) //判断时间 167 fft_rst_flag_n = 1'b0; //FFT 模块复位标志位置 0 168 else 169 fft_rst_flag_n = 1'b1; //FFT 模块复位标志位置 1 170 end 171 172 //例化 FFT 控制模块 173 FFT_Control_Module FFT_Control_Init 174 ( 175 .CLK_50M (CLK_50M ), //时钟端口,开发板用的 50M 晶振 176 .RST_N (fft_rst_flag ), //FFT 模块复位标志位 177 .data_real_in_int (ad_to_fft_data ), //FFT 中用到的 AD 数据 178 .fft_real_out_int (fft_real_out_int ), //FFT 实数的输出 179 .fft_imag_out_int (fft_imag_out_int ), //FFT 虚数的输出 180 .fft_bit_cnt (fft_bit_cnt ), //FFT 位计数器 181 .vga_freq (vga_freq ), //VGA 中显示的频率值 182 .vga_fengzhi (vga_fengzhi ) //VGA 中显示的峰峰值 183 ); 184 185 endmodule 从该代码中我们可以看出,该代码的核心部分主要是两个双口 RAM 模块和一个 FFT 控制 http://www.fpga.gs/ 182 项目实战篇 §8 模块。第一个双口 RAM 模块主要实现了 AD 模块与 VGA 模块的数据传输,首先,我们使用 AD 外设将模拟转换成数字写入到双口 RAM 中,然后我们再通过读取双口 RAM 将数据读出,并传 输给 VGA 模块进行显示。看到这里,也许有的朋友会有这么一个疑问,为什么不直接将数据传 输到 VGA 数据线上?这样岂不是更省事?这主要是因为,我们的 AD 模块是工作在 50MHz 的 时钟下,而我们的 VGA 模块是工作在 40MHz 的时钟下,由于它们的时钟不同步,所以我们不 能直接进行数据的传输,我们只有解决了这一跨时钟域问题,我们才能完成不同时钟频率下的数 据传输。我们这里采用了双口 RAM 来解决这一问题,由于我们的双口 RAM 有独立的写时钟和 独立的读时钟,所以这一特性便能够很好的解决 2 个不同的时钟下进行数据的读写操作,当然, FIFO 也是可以的。介绍完了第一个双口 RAM,接下来我们再来介绍第二个双口 RAM 模块,第 二个双口 RAM 模块主要实现了 AD 模块与 FFT 控制模块的数据传输,首先,我们使用 AD 外 设将模拟转换成数字写入到双口 RAM 中,然后我们再通过读取双口 RAM 将数据读出,并传输 给 FFT 控制模块进行处理。介绍完了第二个双口 RAM,最后我们再来看下 FFT 控制模块,如 代码 8.3 所示。 代码 8.3 FFT_Control_Module.v 代码 1 module FFT_Control_Module 2 ( 3 //输入端口 4 CLK_50M,RST_N,data_real_in_int, 5 //输出端口 6 fft_real_out_int,fft_imag_out_int,fft_bit_cnt, vga_freq,vga_fengzhi,vga_pingjunzhi 7 ); 8 9 //--------------------------------------------------------------------------10 //-- 外部端口声明 11 //--------------------------------------------------------------------------- 12 input CLK_50M; 13 input RST_N; 14 input [ 9:0] data_real_in_int; 15 output [31:0] vga_freq; 16 output [31:0] vga_fengzhi; 17 output [ 9:0] vga_pingjunzhi; 18 output [ 9:0] fft_bit_cnt; 19 output [ 9:0] fft_real_out_int; 20 output [ 9:0] fft_imag_out_int; 21 22 //--------------------------------------------------------------------------23 //-- 内部端口声明 24 //--------------------------------------------------------------------------- 25 wire sink_sop; 26 wire sink_eop; 27 wire inverse; Zircon Opto-Electronic Technology CO.,Ltd. §8 数字示波器设计实战 28 wire 29 wire 30 wire 31 wire 32 wire 33 wire 34 wire 35 wire 36 wire 37 wire 38 reg 39 reg 40 wire 41 wire 42 reg 43 reg 44 reg 45 reg 46 reg 47 reg 48 reg 49 reg 50 reg 51 reg 52 reg 53 reg 54 reg 55 reg 56 reg 57 reg 58 wire 59 reg 60 reg 61 reg 62 reg 63 reg 64 reg 65 reg 66 reg 67 reg 68 reg 69 reg 70 reg 71 reg [ 1:0] [ 1:0] [ 5:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 5:0] [ 5:0] [31:0] [31:0] [15:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [ 9:0] [15:0] [15:0] [15:0] [15:0] [ 9:0] sink_error; source_ready; sink_ready; source_error; source_sop; source_eop; source_valid; source_exp; source_real; source_imag; fft_start; fft_start_n; end_input; end_output; fft_bit_cnt; fft_bit_cnt_n; sink_real; sink_real_n; sink_imag; sink_imag_n; sink_valid; sink_valid_n; fft_real_out_int; fft_real_out_int_n; fft_imag_out_int; fft_imag_out_int_n; exponent_out_int; exponent_out_int_n; fft_real_image_data; fft_real_image_data_n; sqrt_data_out; data_max; data_max_n; data_max2; data_max2_n; max_cnt; max_cnt_n; max_cnt2; max_cnt2_n; fengzhi_max; fengzhi_max_n; fengzhi_min; fengzhi_min_n; data_imag_in_int = 10'b0; http://www.fpga.gs/ 183 184 项目实战篇 §8 72 reg [ 9:0] fftpts_array = 255; 73 74 //--------------------------------------------------------------------------- 75 //-- 逻辑功能实现 76 //--------------------------------------------------------------------------- 77 // Set FFT Direction '0' => FFT '1' => IFFT 78 assign inverse = 1'b0; 79 80 //no input error 81 assign sink_error = 2'b0; 82 83 // for example purposes, the ready signal is always asserted. 84 assign source_ready = 1'b1; 85 86 // sink_valid == 1'b1 && sink_ready == 1'b1 87 assign fft_ready_valid = (sink_valid && sink_ready); 88 89 // start valid for first cycle to indicate that the file reading should start. 90 always @ (posedge CLK_50M or negedge RST_N) 91 begin 92 if(!RST_N) 93 fft_start <= 1'b1; 94 else 95 fft_start <= fft_start_n; 96 end 97 98 always @ (*) 99 begin 100 if(fft_ready_valid) 101 fft_start_n = 1'b0; 102 else 103 fft_start_n = 1'b1; 104 end 105 106 //sop and eop asserted in first and last sample of data 107 always @ (posedge CLK_50M or negedge RST_N) 108 begin 109 if(!RST_N) 110 fft_bit_cnt <= 10'b0; 111 else 112 fft_bit_cnt <= fft_bit_cnt_n; 113 end 114 115 always @ (*) Zircon Opto-Electronic Technology CO.,Ltd. §8 数字示波器设计实战 185 116 begin 117 if(fft_ready_valid && (fft_bit_cnt == fftpts_array)) 118 fft_bit_cnt_n = 1'b0; 119 else if(fft_ready_valid) 120 fft_bit_cnt_n = fft_bit_cnt + 1'b1; 121 else 122 fft_bit_cnt_n = fft_bit_cnt; 123 end 124 125 // signal when all of the output data has be received from the DUT 126 assign end_output = (source_eop && source_valid && source_ready) ? 1'b1 : 1'b0; 127 128 // signal when all of the input data has been sent to the DUT 129 assign end_input = (sink_eop && sink_valid && sink_ready) ? 1'b1 : 1'b0; 130 131 // generate start and end of packet signals 132 assign sink_sop = (fft_bit_cnt == 1'b0) ? 1'b1 : 1'b0 ; 133 assign sink_eop = (fft_bit_cnt == fftpts_array) ? 1'b1 : 1'b0; 134 135 reg end_test; 136 reg end_test_n; 137 138 // halt the input when done 139 always @ (posedge CLK_50M or negedge RST_N) 140 begin 141 if(!RST_N) 142 end_test <= 1'b0; 143 else 144 end_test <= end_test_n; 145 end 146 147 always @ (*) 148 begin 149 if(end_input) 150 end_test_n = 1'b1; 151 else 152 end_test_n = end_test; 153 end 154 155 // Read input data from files. Data is generated on the negative edge of the clock, CLK_50M, in the 156 // testbench and registered by the core on the positive edge of the clock 157 always @ (posedge CLK_50M or negedge RST_N) 158 begin http://www.fpga.gs/ 186 项目实战篇 §8 159 if(!RST_N) 160 sink_real <= 10'b0; 161 else 162 sink_real <= sink_real_n; 163 end 164 165 always @ (*) 166 begin 167 if(end_test || end_input) 168 sink_real_n = 10'b0; 169 else if (fft_ready_valid || (fft_start & !(sink_valid && sink_ready == 1'b0))) 170 sink_real_n = data_real_in_int; 171 else 172 sink_real_n = sink_real; 173 end 174 175 always @ (posedge CLK_50M or negedge RST_N) 176 begin 177 if(!RST_N) 178 sink_imag <= 10'b0; 179 else 180 sink_imag <= sink_imag_n; 181 end 182 183 always @ (*) 184 begin 185 if(end_test || end_input) 186 sink_imag_n = 10'b0; 187 else if (fft_ready_valid || (fft_start & !(sink_valid && sink_ready == 1'b0))) 188 sink_imag_n = data_imag_in_int; 189 else 190 sink_imag_n = sink_imag; 191 end 192 193 194 always @ (posedge CLK_50M or negedge RST_N) 195 begin 196 if(!RST_N) 197 sink_valid <= 1'b0; 198 else 199 sink_valid <= sink_valid_n; 200 end Zircon Opto-Electronic Technology CO.,Ltd. §8 数字示波器设计实战 187 201 202 always @ (*) 203 begin 204 if(end_test || end_input) 205 sink_valid_n = 1'b0; 206 else if (fft_ready_valid || (fft_start & !(sink_valid && sink_ready == 1'b0))) 207 sink_valid_n = 1'b1; 208 else 209 sink_valid_n = 1'b1; 210 end 211 212 always @ (posedge CLK_50M or negedge RST_N) 213 begin 214 if(!RST_N) 215 fft_real_out_int <= 10'b0; 216 else 217 fft_real_out_int <= fft_real_out_int_n; 218 end 219 220 always @ (*) 221 begin 222 if(source_valid && source_ready) 223 fft_real_out_int_n = source_real[9] ? (~source_real[9:0]+1) : source_real; 224 else 225 fft_real_out_int_n = fft_real_out_int; 226 end 227 228 always @ (posedge CLK_50M or negedge RST_N) 229 begin 230 if(!RST_N) 231 fft_imag_out_int <= 10'b0; 232 else 233 fft_imag_out_int <= fft_imag_out_int_n; 234 end 235 236 always @ (*) 237 begin 238 if(source_valid && source_ready) 239 fft_imag_out_int_n = source_imag[9] ? (~source_imag[9:0]+1) : source_imag; 240 else 241 fft_imag_out_int_n = fft_imag_out_int; 242 end 243 http://www.fpga.gs/ 188 项目实战篇 §8 244 always @ (posedge CLK_50M or negedge RST_N) 245 begin 246 if(!RST_N) 247 exponent_out_int <= 10'b0; 248 else 249 exponent_out_int <= exponent_out_int_n; 250 end 251 252 always @ (*) 253 begin 254 if(source_valid && source_ready) 255 exponent_out_int_n = source_exp; 256 else 257 exponent_out_int_n = exponent_out_int; 258 end 259 260 261 always @ (posedge CLK_50M or negedge RST_N) 262 begin 263 if(!RST_N) 264 fft_real_image_data <= 32'b0; 265 else 266 fft_real_image_data <= fft_real_image_data_n; 267 end 268 269 always @ (*) 270 begin 271 if(source_valid && source_ready) 272 fft_real_image_data_n = fft_real_out_int*fft_real_out_int + fft_imag_out_int*fft_imag_out_int; 273 else 274 fft_real_image_data_n = fft_real_image_data; 275 end 276 277 SQRT_Module SQRT_Init 278 ( 279 .radical (fft_real_image_data ), 280 .q (sqrt_data_out ) 281 ); 282 283 always @ (posedge CLK_50M or negedge RST_N) 284 begin 285 if(!RST_N) 286 data_max <= 10'b0; Zircon Opto-Electronic Technology CO.,Ltd. §8 数字示波器设计实战 287 else 288 data_max <= data_max_n; 289 end 290 291 always @ (*) 292 begin 293 if(sqrt_data_out > data_max) 294 data_max_n = sqrt_data_out; 295 else 296 data_max_n = data_max; 297 end 298 299 always @ (posedge CLK_50M or negedge RST_N) 300 begin 301 if(!RST_N) 302 data_max2 <= 10'b0; 303 else 304 data_max2 <= data_max2_n; 305 end 306 307 always @ (*) 308 begin 309 if(sqrt_data_out > data_max) 310 data_max2_n = data_max2; 311 else if((sqrt_data_out > data_max2) && (sqrt_data_out != data_max)) 312 data_max2_n = sqrt_data_out; 313 else 314 data_max2_n = data_max2; 315 end 316 317 always @ (posedge CLK_50M or negedge RST_N) 318 begin 319 if(!RST_N) 320 max_cnt <= 10'b0; 321 else 322 max_cnt <= max_cnt_n; 323 end 324 325 always @ (*) 326 begin 327 if(sqrt_data_out > data_max) 328 max_cnt_n = max_cnt; 329 else if((sqrt_data_out > data_max2) && (sqrt_data_out != data_max)) 330 max_cnt_n = max_cnt2 - 10'd23; http://www.fpga.gs/ 189 190 项目实战篇 §8 331 else 332 max_cnt_n = max_cnt; 333 end 334 335 always @ (posedge CLK_50M or negedge RST_N) 336 begin 337 if(!RST_N) 338 max_cnt2 <= 10'b0; 339 else 340 max_cnt2 <= max_cnt2_n; 341 end 342 343 always @ (*) 344 begin 345 if(sqrt_data_out > data_max) 346 max_cnt2_n = max_cnt2 + 1'b1; 347 else if((sqrt_data_out > data_max2) && (sqrt_data_out != data_max)) 348 max_cnt2_n = max_cnt2 + 1'b1; 349 else if(max_cnt2 >= 10'd256) 350 max_cnt2_n = 10'b0; 351 else 352 max_cnt2_n = max_cnt2 + 1'b1;; 353 end 354 355 always @ (posedge CLK_50M or negedge RST_N) 356 begin 357 if(!RST_N) 358 fengzhi_max <= 16'b0; 359 else 360 fengzhi_max <= fengzhi_max_n; 361 end 362 363 always @ (*) 364 begin 365 if(data_real_in_int > fengzhi_max) 366 fengzhi_max_n = data_real_in_int; 367 else 368 fengzhi_max_n = fengzhi_max; 369 end 370 371 always @ (posedge CLK_50M or negedge RST_N) 372 begin 373 if(!RST_N) 374 fengzhi_min <= 16'd255; Zircon Opto-Electronic Technology CO.,Ltd. §8 数字示波器设计实战 191 375 else 376 fengzhi_min <= fengzhi_min_n; 377 end 378 379 always @ (*) 380 begin 381 if(fengzhi_min > data_real_in_int) 382 fengzhi_min_n = data_real_in_int; 383 else 384 fengzhi_min_n = fengzhi_min; 385 end 386 387 assign vga_freq = (max_cnt) * 32'd42667 >> 8'd8; 388 assign vga_fengzhi = ((fengzhi_max - fengzhi_min) * 10'd50) >> 8'd8; //((data_max3 * 10'd100) >> 8'd8) + 10'd2; 389 390 fft fft_init 391 ( 392 .clk (CLK_50M ), 393 .reset_n (RST_N ), 394 .inverse (inverse ), 395 .sink_valid (sink_valid ), 396 .sink_sop (sink_sop ), 397 .sink_eop (sink_eop ), 398 .sink_real (sink_real ), 399 .sink_imag (sink_imag ), 400 .sink_error (sink_error ), 401 .source_ready (source_ready ), 402 .sink_ready (sink_ready ), 403 .source_error (source_error ), 404 .source_sop (source_sop ), 405 .source_eop (source_eop ), 406 .source_valid (source_valid ), 407 .source_exp (source_exp ), 408 .source_real (source_real ), 409 .source_imag (source_imag ) 410 ); 411 412 endmodule 下面我们就来简单的介绍一下该代码,代码第 1 至 72 行是端口声明和参数定义,第 74 至 258 行是对 FFT IP 核模块进行控制操作,我们将第二个双口 RAM 中的数据读取并传输给 FFT IP 核处理。这部分代码我们主要是参考的 fft_tb.v 文件,这个文件是创建 FFT IP 核自动生成的, 是用来仿真 FFT IP 核模块的 TestBench 激励文件。第 261 至 281 行是将 FFT IP 核处理好的实 http://www.fpga.gs/ 192 项目实战篇 §8 部和虚部数据进行计算,求出该复数的模(复数的实部与虚部的平方和的正平方根)。第 283 至 353 行是利用复数的模计算出波形的频率值。第 355 至 385 行是利用波形的数据计算出波形的 峰峰值。第 390 至 410 行是 FFT IP 核模块,FFT IP 核的配置如图 8.3 所示。 图 8.3 FFT IP 核的配置图 这里需要我们注意的是,如果你对 FFT 快速傅氏变换不太理解,那么你需要恶补一下 FFT 方面的知识。说完了第二个重点,下面我们再来看最后一个重点,就是我们的 Vga 显示模块, 由于我们在前面的学习中已经讲解了如何控制 VGA 时序进行显示,所以我们就不再讲解了,我 们这里主要讲解的是如何使用 VGA 来显示字符、数字和汉字。首先我们需要使用 PCtoLCD2002 软件取字模,我们将 PCtoLCD2002 设置,如图 8.4 所示。 图 8.4 PCtoLCD2002 软件设置 PCtoLCD2002 软件设置好了以后,我们就可以取字模了,比如我们这里想要显示 5V,我 们只需要将 5V 输入到该软件中,该软件会自动帮我们生成 5V 的字模,如图 8.5 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §8 数字示波器设计实战 193 图 8.5 5V 字模生成图 我们将生成的 5V 字模输入到 mif 文件中就可以了,当我们制作好了字库,也就是我们的 mif 文件以后,我们就可以通过读取 ROM IP 核来使用这些字库了,如代码 8.4 所示。 代码 8.4 Vga_Module.v 代码 1 //例化 ROM 字库模块 2 ROM_Font_Module ROM_Font_Init 3( 4 .address 5 .clock 6 .q (rom_font_addr (CLK_40M (rom_font_data ), //字库的地址位 ), //字库的时钟 ) //字库的数据位 7 ); 这里需要我们注意的是,我们的字库中不仅仅只有 5V,它包含了我们屏幕上显示的所有字 符、数字和汉字。知道了字库的制作,下面我们就来讲解如何将制作的字库显示到 VGA 屏幕上, 这里我们就以 5V 为例,我们给出显示 5V 的代码,如代码 8.5 所示。 代码 8.5 Vga_Module.v 代码 1 assign rom_5v_en = (vga_y >= 10'd90) && (vga_y <= 10'd106) && (vga_x >= 10'd98) && (vga_x <= 10'd116); 2 3 if(rom_5v_en) 4 begin 5 if(vga_x == 10'd98) 6 rom_font_addr_n = 8'h28; 7 else if(vga_x == 10'd106) 8 rom_font_addr_n = 8'h58; 9 else 10 rom_font_addr_n = rom_font_addr + 1'b1; http://www.fpga.gs/ 194 项目实战篇 §8 11 end 12 13 if(rom_5v_en) //在屏幕上显示 5V 14 begin 15 if(rom_font_data[10'd106 - vga_y]) 16 VGA_DATA_N = 10'd253; 17 else 18 VGA_DATA_N = 8'h03; 19 end 从代码中我们可以看出,第 1 行是用来指定 VGA 屏幕显示位置,当屏幕显示到指定的位置 以后,我们的 rom_5v_en 就会为 1。当 rom_5v_en 为 1 以后,我们的第 3 至 11 行和 13 至 19 行代码就会生效开始执行。第 3 至 11 行代码是用来生成要读取 ROM 数据的地址,第 13 至 19 行是将 ROM 中读出的数据显示到 VGA 屏幕上,最终显示效果,如图 8.6 所示。 图 8.6 5V 在 VGA 上的显示效果图 至此,该项目中所有的重点我们就讲解完了,由于其他的字符、数字和汉字的显示同理,所 以我们就不再进一步讲解了。 §8.4 板级调试 最后,我们将 Quartus 编译生成的 A4_Oscilloscope_Top.sof 文件下载到开发板中,接着 我们使用 VGA 数据线将显示器和 A4 开发板进行连接。这里我们需要注意的是,我们还需要使 用跳线帽将 AD 的输入管脚和 DA 的输出管脚短接。这时我们就可以在 VGA 显示器中看到数字 示波器的界面了,如图 8.7 所示。 Zircon Opto-Electronic Technology CO.,Ltd. §8 数字示波器设计实战 195 5V 4.375V 3.75V 3.125V 2.5V 1.875V 1.25V 0.625V 0V 频率:500Hz 峰峰值:2.5V 图 8.7 数字示波器项目的板级调试图 我们这里的波形它是实时显示的,我们拔掉跳线帽可以看到波形立刻就没有了,我们再插上, 波形立刻就有了。我们还可以使用它测试电压,我们将 DA 输入端拔掉接到 3.3V 上,我们可以 看到数字示波器界面中显示的位置大约在 3.3V 左右。 http://www.fpga.gs/ 版权声明 (1) 南京锆石光电科技有限公司对其发行的或与合作公司共同发行的包括但不限于产品或 服务的全部内容拥有版权等知识产权,受法律保护。 (2) 所有产品及资料内容仅供用户学习使用。 (3) 未经本公司书面许可,任何单位及个人不得以任何方式或理由对上述产品、服务、信息、 材料的任何部分进行复制、修改、抄录或与其它产品捆绑使用、销售。 (4) 凡侵犯本公司版权等知识产权的,本公司必依法追究其法律责任。 声明单位:南京锆石光电科技有限公司

Top_arrow
回到顶部
EEWORLD下载中心所有资源均来自网友分享,如有侵权,请发送举报邮件到客服邮箱bbs_service@eeworld.com.cn 或通过站内短信息或QQ:273568022联系管理员 高进,我们会尽快处理。