V1.0
北京智能芯片科技有限公司
FII-PR040实验指导手册
版权声明:
本手册版权归属北京智能芯片科技有限公司所有, 并保留一切权力。非经本公司(书面形式)同意,任何单位及个人不得擅自摘录或修改本手册部分或全部内容,违者我们将追究其法律责任。
感谢您购买智能芯片科技开发板,在使用产品之前,请仔细地阅读该手册并且确保知道如何正确使用该产品,不合理的操作可能会损坏开发板,使用过程中随时参考该手册以确保正确使用。此手册不断更新中,建议您使用时下载最新版本。
北京智能芯片科技官网:
http://www.icintelligent.com
https://shop296232497.taobao.com
文档版本控制
文档版本 | 日期 | 修改内容记录 |
V1.0 | 2019/7/19 | 创建文档 |
目录
第二部分 FII-PRA040 主要硬件资源的使用与FPGA开发实验 5
2.4 SignalTap Logic Analyzer的使用及实验验证 23
工程文件附录
实验一:LED_shifting
实验二:SW_LED
实验三:BCD_counter
实验四:block_counter
实验五:block_debouncing
实验六:mult_sim
实验七:HEX_BCD,HEX_BCD_mult
实验八:memory_rom
实验九:dual_port_ram
实验十:UART_FRAME
实验十一:eeprom_test
实验十二:adda_test
实验十三:hdmi
实验十四:Ethernet
实验十五:sram
实验十六:audio_test
实验十七: 5640_camera_test
实验十八: high_speed_ad_040
实验十九: dac_9767_test
实验二十: LCD专题实验
1、系统的设计目标
本系统设计的主要目的是配合Intel Quartus完成FPGA 学习、开发与实验。主器件采用Inte Cyclone10 10CL040YF484C8G,目前是Intel最新一代 FPGA器件。可完成主要学习和开发项目,如下
- 基础的FPGA设计训练;
- SOPC(NIOSII)系统的构建及训练;
- IC的设计与验证,本系统提供了RISC-V CPU的硬件设计,仿真与验证;
- 基于RISC-V的开发与应用;
- 系统专门为RISC-V系统的应用进行了硬件设计优化。
2、系统资源
- 扩展内存:采用两片Super Sram(IS61WV25616,256K x 16bit)并联成32位的数据接口,最大访问空间可达2M bytes
- 串行flash:Spi接口serial flash(16M bytes)
- 串行EEPROM
- 千兆以太网:100/1000 Mbps
- USB转串行接口:USB-UART bridge
3、人机交互开关
- 8个拨动开关
- 共8个按键,其中7个按键定义为(MENU,UP,RETUN,LEFT,OK,RIGHT,DOWN),一个按键定义为FPGA硬件复位RESET
- 8个LED灯
- 6个7段位数码管
- I2C总线接口
- UART外部接口
- 两个JTAG 编程接口:一个为FPGA下载调试接口,一个为RISC-V CPU 的JTAG调试接口
- 内置RISC-V CPU软件调试器,无需外接RISC-V JTAG仿真器
- 4个12脚GPIO连接口,符合PMOD接口标准
4、软件开发环境
- Quartus 18.0 及后续版本,用于开发FPGA,Nios-II SOPC
- FreedomStudio-Win_x86_64 用于 RISC-V CPU的软件开发
5、配套资源
RISC-V JTAG 调试器
Intel Altera JTAG 下载调试器
FII-PRA040 040硬件参考指南
FII-PRA040 实验指导书
6、实物展示
- FII-PRA040系统框图如图一所示:
图一 PRA040系统框图
- FII-PRA040实物展示如图所示
31
30
29
28
27
26
25
24
23
22
21
20
18
19
17
16
15
14
13
12
11
10
9
8
7
6
5
4
1
3
3
2
图二 PRA040实物图正面
33
32
图三 PRA040实物图背面
- 对应实物图,开发板主要器件具体如下:
1、10CL040YE484C8G芯片
2、外部12V电源接口
3、GPIO接口
4、热敏电阻(NTC-MF52)
5、光敏电阻
6、可调电阻
7、音频输出口(绿色)和音频输入口(红色)
8、PCIE接口
9、TFTCLD接口
10、音频芯片(WM8978)
11、7个按键
12、50M系统时钟
14、视频芯片(ADV7511)
15、外部JTAG下载接口
16、HDMI接口
17、USB供电与下载接口
18、FPGA和RISC_V JTAG下载芯片(FT2232)
19、USB_UART接口
20、串口芯片(CP2102)
21、六位共阳数码管
22、以太网接口
23、以太网PHY芯片(RTL8211E-VB)
24、4个USB接口
25、USB鼠标键盘控制芯片
26、8位LED灯
27、8位拨动开关
28、复位按键(RESET)
29、电源开关
30、Flash(N25Q128A,128M bit/16M bytes)
31、EEPROM(AT24C02N)
32、两片SRAM
33、AD/DA转换芯片(PCF8591)
第二部分 FII-PRA040 主要硬件资源的使用与FPGA开发实验
本部分主要通过FPGA的开发实例逐渐引导用户学会FPGA程序的开发以及板载硬件的使用,同时结合实例由浅入深的介绍开发系统软件Quartus使用。本部分所涉及的开发练习内容如下。
实验一:LED流水灯设计实验
实验二:SignalTap实验
实验三:数码管显示实验
实验四:Block/SCH实验
实验五:按键消抖实验
实验六:乘法器的使用与modelsim仿真
实验七:十六进制数到BCD码转换及应用
实验八:ROM的使用
实验九:利用双端口RAM实现帧数据的读写实验
实验十:异步串行口设计与实验
实验十一:IIC传输实验
实验十二:AD、DA实验
实验十三:HDMI实验
实验十四:以太网实验
按照实验设计顺序进行学习练习,顺利完成这些基本实验,我们将可以达到初级FPGA工程师的水平与能力。
实验一 LED流水灯设计
1.1 实验目的
- 练习开发软件Quartus的使用,新项目的建立,以及系统资源IP Core的使用;
- 熟练Verilog HDL 程序的编写,养成良好的代码书写风格;
- 掌握分频的设计,实现流水灯设计;
- 结合硬件资源进行FPGA的管脚分配,并实现实际下板检验;
- 观察实验现象,总结实验结果。
1.2 实验要求
- 运用全部LED灯,复位时全部点亮;
- 结束复位,LED灯由低到高(由右向左)依次点亮;
- 每个LED灯亮1秒钟时间;
- 最后一个(最高位)灯点亮后,下一次回到第一个(最低位)灯,实现循环;
1.3 实验内容
1.3.1 LED灯简介
LED灯,全称Light-Emitting Diode Light,别名发光二极管。
它的特点是工作电流小,可靠性高,寿命长。发展至今,LED灯种类很多,如图1-1所示,FII-PRA040采用的是图中红圈内的LED灯。
1.3.2 硬件设计
板载8位LED灯实物图如图1-2所示,LED灯原理图如图1-3所示。可以看到本次实验板的LED灯模块采用的是8个共阳极LED灯,通过180 R电阻与Vcc 3.3V相连,阴极由FPGA直接连接控制。当FPGA输出低电平0时,LED灯导通,此时LED上有电流通过,LED灯被点亮。
图1-2 8位LED灯实物图
图1-3 原理图LED灯部分
1.3.3 程序设计
1.3.3.1 创建工程
编写程序之前,首先简单介绍一下我们使用的开发环境,如何创建一个工程。以Quartus 18.1版本综合器为例,具体工程建立步骤如图1-4至1-9所示。
- 如图1-4,打开Quartus后,可以直接点击屏幕中间的New Project Wizard新建工程,也可以在工具栏里点击File后新建工程,或者快捷键Ctrl+N后按提示新建工程。
图1-4 启动后的Quartus II 18.1 界面
- 如图1-5所示,选择正确的工程路径,工程命名为LED_shifting,建议路径简单易查找,方便以后的查看与调用。
图1-5 工程文件的路径选择及命名
- 如图1-6所示,可直接添加一些提前写好的文件,由于是新建工程,因此直接点击Next执行下一步操作。
图1-6 添加文件
- 如图1-7所示,选择正确的FPGA芯片型号,板载芯片型号为10CL040YE484C8G,在Family处选择Cyclone 10 LP,package处选择FBGA,Pin count处选择484,Core speed grage处选择8,可帮我们缩小选择范围,快速找到目标型号。
图1-7 器件选择
- 如图1-8所示,进行EDA工具的选择,在这里暂时不做任何操作,直接使用Quartus自带的EDA工具即可
图1-8 EDA工具选择
- 点击Next进入下一个界面后选择Finish完成项目工程的建立。
- 点击File→New或使用快捷键Ctrl+N,弹出如图1-9所示对话框,建立一个程序文件(注意:Verilog HDL File),进行代码的书写。应该注意程序名和工程名的统一性,并保存在正确的路径(文件夹)下。
图1-9 建立程序文件(LED_shifting.v)
准备工作就绪后,开始编写程序。
1.3.3.2 程序介绍
第一步:主程序框架的建立(接口设计)
module Led_shifting(
input clk, input rst_n, output reg [7:0] led ); endmodule |
本次实验的输入信号有50MHz的系统时钟clk,复位信号rst_n,以及输出信号8位led灯,采用 led [7:0] 的总线形式定义。
第二步:IP Core的调用,PLL模块的建立与使用
- 如图1-10所示,在主界面右边IP catalog 选项栏中,找到ALTPLL,
- 如图1-11所示,双击ALTPLL,在弹出的对话框中输入PLL模块的名称。这里给出名称为PLL1。注意选择类型为Verilog语言类型。
- 如图1-12所示,完成上一步操作后,进入详细的设置界面。Inclk0为PLL的输入时钟,由开发板提供,应与系统时钟保持一致,设为50MHz;PLL反馈路径设置为正常模式(In normal mode)这里涉及到的高级特性,请阅读帮助文件;PLL补偿的输出时钟为C0;设置完成后点击Next进行下一步设置。
图1-10 IP目录
图1-11 PLL的命名与保存
图1-12 PLL设置1 (输入时钟设置)
- 如图1-13所示,是PLL异步复位(areset)控制和捕获锁定(LOCKED)状态的设置,本次实验按照图中默认方式选择即可。
图1-13 PLL设置2
- 接下来的三个设置页面里的内容均按默认设置执行即可。
- 如图1-14所示,是对PLL输出时钟Output Clocks的设置,一共能够输出5个不同的时钟clk c0~clk c4,本次实验只用到一个,对clk c0进行设置,其它默认不适用即可。设置输出频率为100Mhz, 相位偏移(clock phase shift)为0,占空比(clock duty cycle)为50%,
图1-14 PLL设置3(输出时钟设置)
- EDA设置默认即可。
- 如图1-15所示,输出文件类型设置选择.bsf(后续采用图形符号设计时会用到)文件和.v文件,其他按默认值设置,点击Finish完成设置。
图1-15 PLL设置4 输出文件类型设置
- 如图1-16所示,在项目界面的Project Navigator 的类型框中选择file(默认为项目层次Hierarchy)
图1-16 PLL1.v文件位置
- 如图1-17所示,点击PLL1.v,主窗口会显示出PLL的内容,找到模块名和端口列表,复制到顶层,进行实例化。
图1-17 PLL1.v文件内容
系统上电,pll_locked信号在PLL锁定(稳定工作)前的值为0,PLL锁定后pll_locked拉高,正常输出时钟信号sys_clk。锁相环例化如下:
wire sys_clk;
wire pll_locked; PLL1 PLL1_inst ( .areset (1’b0), .inclk0 (clk), .c0 (sys_clk), .locked (pll_locked) ) |
- sys_rst作为分频部分的复位信号,ext_rst作为流水灯部分的复位信号,在时钟sys_clk的上升沿驱动下,经过一级寄存器同步复位。
reg sys_rst;
reg ext_rst; always @ (posedge sys_clk) begin sys_rst <= !pll_locked; ext_rst <= rst_n; end |
第三步:分频器的设计
设计采用经PLL输出的100MHz时钟作为系统时钟,实验要求流水灯的移动速度为1秒钟,采用分频设计,首先经过微秒分频得到1us,然后进过毫秒分频得到1ms,最后经过秒分频得到1s钟。
- 微秒分频
reg [6:0] us_cnt;
reg us_f; always @ (posedge sys_clk) begin if (sys_rst) begin us_cnt <= 0; us_f <= 1’b0; end else begin us_f <= 1’b0; if (us_cnt == 99) begin us_cnt <= 0; us_f <= 1’b1; end else us_cnt <= us_cnt + 1’b1; end end |
100MHz时钟一个周期是10ns,1us需要100个时钟周期,即100个10ns,因此定义一个微秒计数器us_cnt [6:0],一个微秒脉冲信号us_f。复位时计数器清零,在每一个时钟上升沿,计数器加一,当计数器等于99时,经过了1us时间,此时将微秒脉冲信号us_f拉高。这样,每经过1us,这个模块就会产生一个脉冲信号。
- 毫秒分频
同理1ms等于1000个1us,因此定义一个毫秒计数器ms_cnt [9:0],一个微秒脉冲信号ms_f。
reg [9:0] ms_cnt;
reg ms_f; always @ (posedge sys_clk) begin if (sys_rst) begin ms_cnt <= 0; ms_f <= 1’b0; end else begin ms_f <= 1’b0; if (us_f) begin if (ms_cnt == 999) begin ms_cnt <= 0; ms_f <= 1’b1; end else ms_cnt <= ms_cnt + 1’b1; end end end |
- 秒分频
同理1s等于1000个1ms,因此定义一个秒计数器s_cnt [ 9:0],一个秒脉冲信号s_f。当三个计数器同时计满,此时时间进过1s钟,发出秒脉冲信号。
reg [9:0] s_cnt;
reg s_f; always @ (posedge sys_clk) begin if (sys_rst) begin s_cnt <= 0; s_f <= 1’b0; end else begin s_f <= 1’b0; if (ms_f) begin if (s_cnt == 999) begin s_cnt <=0; s_f <= 1’b1; end else s_cnt <= s_cnt + 1’b1; end end end |
第四步:流水灯设计
复位时,8个LED灯全亮,所以输出led的值为8’hff,接下来LED灯要流动起来,所以首先点亮最低位LED灯,此时led的值为8’b0000_0001,当秒脉冲信号到来,点亮下一个LED灯,此时led的值为8’b0000_0010,由此可见,只要将“1”高电平循环向左移即可,采用位拼接的形式实现,即led <= {led[6:0], led[7]}。
always @ (posedge sys_clk)
begin if (ext_rst) led <= 8’hff; else begin if (led == 8’hff) led <= 8’b0000_0001; else if (s_f) led <= {led[6:0], led[7]}; end end |
1.4 实验验证
1.4.1 下板验证前的准备工作
图1-18 部分功能介绍
如图1-18所示,程序编写完成之后需要进行分析综合,检查对错。点击分析综合图标即可完成,或者用快捷键Ctrl+K即可,管脚分配是是将各个信号与FPGA管脚相绑定,全编译是将整个工程生成下板时使用的编程文件,并再次检查错误,点击下板图标,按提示将工程下板。点击分析综合图标,完成后Quartus 会自动生成一个报告,如图1-19所示,报告具体内容这里不予详细介绍。
图1-19 编译报告
检查修改至没有错误,即可开始下板验证,在此之前,需要进行管脚分配,管脚具体分配如图表1-1所示。
表1-1 LED灯实验管脚映射表
点击管脚分配图标,打开管脚分配窗口,如图1-20所示。双击每个管脚对应的location栏,直接输入对应管脚编号回车确定即可,也可以点击下拉键找到对应的管脚,但相对较慢。需要注意的是,图1-21中I/O standard栏,这里显示的内容是每个I/O口的电压标准,通过原理图中的BANK电压以及设计要求进行确定,本次实验中的I/O电压应该选择为3.3V,双击I/O standard栏,点击下拉键,如图1-22所示,选择我们需要的电压标准即可。也可以在选择芯片型号的时候提前设置好默认电压标准,点击图1-7中Device and Pin Options→Voltage→Default I/O standard进行设置。
管脚分配完成,如图1-22所示。然后点击全编译,通过后,即可进行下板验证。
图1-20 管脚分配窗口
图1-21 I/O电压选择
图1-22 完成管脚分配
1.4.2 下板验证方法及步骤
下板验证之前,需要对Quartus进行一些设置,具体请参考附录文件夹中的“JTAG下载器使用说明-ALTERA”,按照说明设置按成后,点击下载键打开下载窗口,如图1-23所示。
图1-23 下载窗口
将开发板与主机连接后,点击Hardware Setup,选择我们的开发板,如图1-24所示。
图1-24 硬件选择
点击Start开始下载,如图1-25所示,Progress显示100%(Successful),即下载完成。
图1-25 下载成功
如图1-26所示,LED灯从低到高循环点亮,间隔一秒钟。
图1-26 流水灯实验现象
实验二 SignalTap实验
2.1 实验目的
- 继续练习开发板硬件的使用;
- 练习Quartus 中 SignalTap Logic Analyzer的使用;
- 学会对捕捉到的信号进行分析。
2.2 实验要求
- 用开关控制LED灯的亮灭
- 通过SignalTap的使用,对开发板上的开关信号进行捕捉并分析。
2.3 实验内容
2.3.1 开关简介和SignalTap简介
- 开关简介
板载开关为8个拨动开关,如图2-1所示,拨动开关是通过拨动开关柄使电路接通或断开,从而达到切换电路的目的的。
图2-1 拨动开关实物图
- SignalTap简介
SignalTap,即逻辑分析仪,使用嵌入式逻辑分析器将信号数据送往SignalTap中,当系统正常工作时,对内部节点信号和I/O引脚进行实时分析
2.3.2 硬件设计
拨动开关部分原理图如图2-2所示,8个开关的2号端口与VCC相连,3号端口与FPGA 相连,因此当开关拨动到3号端口时,开关导通,向FPGA输入一个高电平信号。
图2-2 原理图拨动开关部分
2.3.3 程序设计
第一步:主程序框架的建立(接口设计)
module SW_LED(
input clk, input [7:0] sw, output reg [7:0] led ); endmodule |
实验输入信号有系统时钟clk,频率50Mhz,8位开关SW,高有效,以及输出8位LED灯。
第二步 实现开关控制LED灯
wire sys_rst;
always @ (posedge inclk) begin if (sys_rst) led <= 8’hff; // 全部熄灭 else led <= ~sw; //由开关的状态决定 end |
当复位信号有效时,8个LED灯全部熄灭。复位结束后,LED灯的亮灭由开关控制,开关选通时LED灯点亮。
2.4 SignalTap Logic Analyzer的使用及实验验证
第一步:分配管脚
管脚分配如表2-1所示。
表2-1 开关试验管脚映射表
程序信号名 | 网络标号 | FPGA管脚 | 端口说明 |
clk | C10_50M | G21 | 输入时钟 |
SW[7] | PB7 | W6 | 拨码开关第7位 |
SW[6] | PB6 | Y8 | 拨码开关第6位 |
SW[5] | PB5 | W8 | 拨码开关第5位 |
SW[4] | PB4 | V9 | 拨码开关第4位 |
SW[3] | PB3 | V10 | 拨码开关第3位 |
SW[2] | PB2 | U10 | 拨码开关第2位 |
SW[1] | PB1 | V11 | 拨码开关第1位 |
SW[0] | PB0 | U11 | 拨码开关第0位 |
led[7] | LED7 | F2 | Led灯第7位 |
led[6] | LED6 | F1 | Led灯第6位 |
led[5] | LED5 | G5 | Led灯第5位 |
led[4] | LED4 | H7 | Led灯第4位 |
led[3] | LED3 | H6 | Led灯第3位 |
led[2] | LED2 | H5 | Led灯第2位 |
led[1] | LED1 | J6 | Led灯第1位 |
led[0] | LED0 | J5 | Led灯第0位 |
第二步: SignalTap II启动及基本设置
菜单Tools→SignalTap II logic Analyzer,
- 在图2-3中选择Setup(Data,Setup)进入设置界面
- 设置JTAG调试器,点击Setup设成与下载器一样的类型
- 设置器件类型(Scan Chain)
- 设置SOF Manager:如图设置成刚编译生成的*.SOF
- 时钟和储存深度设置如图2-4所示
点击图2-4所示位置添加时钟,如图2-5,在时钟设置对话框中:Filter 选择 SignalTap : pre-synthesis → List,选择需要的时钟信号,在PLL1: PLL1_INST 中选中 c0,移到右边框中。
其它设置按照图2-2所列设置即可。(SignalTap II 的高级使用,用户可以阅读帮助文件)
双击此空白处添加观察信号
图2-3 SignalTap设置界面
点击此处
添加时钟
点击此处
设置储存深度
图2-4 时钟信号及存储深度设置
图2-5 时钟信号选择对话框
第三步:添加观察信号
图2-6 观察信号的添加界面
如图2-3所示,双击空白处添加观察信号,添加界面如图2-6所示,在左侧选中想要观察的信号,添加至右侧,点击Insert即可,添加完成后Save 保存并重新编译。
第四步:观察信号设置
对于待观察的信号,我们还需要对其进行一些设置,是上升沿触发,还是下降沿触发,还是不关心等情况,需要手动调整,方法如图2-6所示。
图2-6 Trigger信号设置
第五步:观察结果
如图2-7所示,点击Run Analysis,观察SignalTap的输出结果。
图2-7 测试结果
经过分析,将开关SW[4]接通后,其信号为高电平,对应的LED灯将点亮。修改Trigger 条件,测试不同Trigger条件下的输出结果,分析并总结。
实验现象如图2-8所示,当选通开关SW5与SW1时,对应LED5与led1被点亮,其他LED灯保持熄灭状态。
实验三 数码管显示
3.1 实验目的
- 复习实验一,熟练掌握PLL的配置,分频器的设计,以及工程的验证;
- 学习BCD码计数器;
- 数码管的译码显示设计;
- 学会将工程烧写到开发板的串行FLASH中;
3.2 实验要求
- 数码管低两位显示秒,中间两位显示分钟,最高两位显示小时
- 小数点保持熄灭状态,暂不予考虑
3.3 实验内容
3.3.1 数码管简介
数码管的一种是半导体发光器件,数码管可分为七段数码管和八段数码管,区别在于八段数码管比七段数码管多一个用于显示小数点的发光二极管单元DP(decimal point),其基本单元是发光二极管。板载数码管为六位一体的八段数码管如图3-1所示,其结构如图3-2所示。
图3-1 板载数码管实物图
图3-2 数码管的段结构
我们使用的是共阳极数码管,共阳极顾名思义,就是将发光二极管的阳极连接在一起,如图3-3所示。因此需要FPGA控制发光二极管阴极为低电平,点亮二极管,并显示对应的信息。六位共阳极八段数码管,把控制哪一位点亮的信号称之为位选信号,每个数码管显示的内容,称之为段选信号,对应真值表如表3-1所示。
图3-3 共阳极数码管原理图
表3-1 共阳极八段数码管段选信号真值表
段选信号 | DP | G | F | E | D | C | B | A |
· | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 |
2 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
3 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
4 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 |
5 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
6 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
7 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
8 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
9 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
A | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
B | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
C | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
D | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
E | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
F | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 |
数码管的显示有两种方式,静态显示和动态显示。
静态显示:每个数码管的段选都要连接一个8位数据线,控制并保持显示的字形,直到下一个段选信号到来。优点是驱动简单,缺点是占用I/O资源过多。
动态显示:将所有数码管的段选线并联在一起,由位选线控制哪一位数码有效并点亮。通过发光二极管的余晖效应和人眼的视觉暂留效应,在一定频率下,数码管看起来是持续点亮的。优点是节省I/O资源,缺点是驱动比较复杂,亮度没有静态显示高。
本次实验,采用动态扫描的方式驱动数码管。
3.3.2 硬件设计
数码管原理图如图3-4所示,阳极通过P沟道型场相应管与VCC相连,因此当位选信号SEG_3V3_D[0:5]为低电平0时,场效应管导通,数码管阳极为高电平;阴极(段选信号)SEG_PA,SEG_PB,SEG_PC,SEG_PD,SEG_PE,SEG_PF,SEG_PG,SEG_DPZ直接与FPGA相连,由FPGA直接控制,因此,当位选信号为0,段选信号也为0 时,数码管被点亮。
图3-4 原理图数码管部分
3.3.3 程序设计
3.3.3.1 程序介绍
第一步:主程序框架的建立(接口设计)
module BCD_counter (
input clk, input rst_n, output [7:0] seven_seg, output [5:0] scan ); endmodule |
输入信号有时钟和复位信号,输出信号为段选信号seven_seg和位选新信号scan。
第二步:系统控制模块
//例化锁相环
PLL PLL_inst ( .areset (1’b0), .inclk0 (clk), .c0 (sys_clk), .locked (locked) ); //复位信号 always @ (posedge sys_clk) begin sys_rst <= !locked; ext_rst <= rst_n; end |
在第一个子模块(系统控制模块)中,输入时钟为系统50MHz时钟,经过锁相环出输一个100MHz,作为其他子模块的工作时钟,同时将锁相环锁定信号取反作为系统复位信号,将按键复位寄存一拍作为外部硬件复位信号。
第三步:分频模块
参考实验一,输出一个毫秒脉冲信号和一个秒脉冲信号,作为数码管驱动模块的输入信号。
第四步:数码管驱动模块
- 计数部分
计数部分与分频模块类似,通过秒脉冲信号分别计时60秒,60分钟,24小时,当时间到23小时59分59秒,计数器全部清零,相当于一天时间。
- 数码管动态扫描部分
reg [3:0] count_sel;
reg [2:0] scan_state; always @ (posedge clk) begin if (rst) begin scan <= 6’b111_111; count_sel <= 4’d0; scan_state <= 0; end else case (scan_state) 0 : begin scan <= 6’b111_110; count_sel <= counta; if (ms_f) scan_state <= 1; end 1 : begin scan <= 6’b111_101; count_sel <= countb; if (ms_f) scan_state <= 2; end 2 : begin scan <= 6’b111_011; count_sel <= countc; if (ms_f) scan_state <= 3; end 3 : begin scan <= 6’b110_111; count_sel <= countd; if (ms_f) scan_state <= 4; end 4 : begin scan <= 6’b101_111; count_sel <= counte; if (ms_f) scan_state <= 5; end 5 : begin scan <= 6’b011_111; count_sel <= countf; if (ms_f) scan_state <= 0; end default : scan_state <= 0; endcase end |
数码管的动态扫描通过状态机来实现,一共六位数码管,需要六个状态即可,定义状态机scan_state[2:0],在不同状态下显示对应的内容count_sel。复位时,六个数码管全部熄灭,跳转到0状态。在毫秒脉冲的驱动下,以1毫秒时间动态扫描数码管:
0状态下点亮第0位数码管,并显示秒的个位;
1状态下点亮第1位数码管,并显示秒的十位;
2状态下点亮第2位数码管,并显示分钟的个位;
3状态下点亮第3位数码管,并显示分钟的十位;
4状态下点亮第4位数码管,并显示小时的个位;
5状态下点亮第5位数码管,并显示小时的十位。
第五部分:段码显示部分
always @ (*)
begin case (count_sel) 0 : seven_seg_r <= 7’b100_0000; 1 : seven_seg_r <= 7’b111_1001; 2 : seven_seg_r <= 7’b010_0100; 3 : seven_seg_r <= 7’b011_0000; 4 : seven_seg_r <= 7’b001_1001; 5 : seven_seg_r <= 7’b001_0010; 6 : seven_seg_r <= 7’b000_0010; 7 : seven_seg_r <= 7’b111_1000; 8 : seven_seg_r <= 7’b000_0000; 9 : seven_seg_r <= 7’b001_0000; default : seven_seg_r <= 7’b100_0000; endcase end |
对照表3-1,将需要显示的字符与段码相对应起来,小数点置高熄灭,然后以拼接的形式组成最终的输出段选信号。
3.4 Flash_memory的运用及实验验证
第一步:管脚分配
管脚分配如表3-1所示
表3-1 数码管显示实验管脚映射表
程序信号名 | 网络标号 | FPGA管脚 | 端口说明 |
clk | CLK_50M | G21 | 输入时钟 |
rst_n | PB3 | Y6 | 复位按键 |
scan[0] | SEG_3V3_D0 | F14 | 位选信号第0位 |
scan[1] | SEG_3V3_D1 | D19 | 位选信号第1位 |
scan[2] | SEG_3V3_D2 | E15 | 位选信号第2位 |
scan[2] | SEG_3V3_D2 | E13 | 位选信号第3位 |
scan[4] | SEG_3V3_D4 | F11 | 位选信号第4位 |
scan[5] | SEG_3V3_D5 | E12 | 位选信号第5位 |
seven_seg[0] | SEG_PA | B15 | 数码管段选a |
seven_seg[1] | SEG_PB | E14 | 数码管段选b |
seven_seg[2] | SEG_PC | D15 | 数码管段选c |
seven_seg[3] | SEG_PD | C15 | 数码管段选d |
seven_seg[4] | SEG_PE | F13 | 数码管段选e |
seven_seg[5] | SEG_PF | E11 | 数码管段选f |
seven_seg[6] | SEG_PG | B16 | 数码管段选g |
seven_seg[7] | SEG_DP | A16 | 数码管段选h |
第二步:全编译
第三步:将程序固化到Flash上
板载Flash_memory(N25Q128A)是一个串行Flash芯片,可存储128Mbit的内容,对于我们学习过程中的工程来说,绰绰有余。Flash原理图如图3-7所示。
图3-7 原理图Flash部分
Flash_memroy的作用就是将程序烧录保存在开发板上,断电之后程序不消失,开发板下次通电之后可直接使用,实际学习生活过程中更加实用。在SPI_CLK时钟的驱动下,FPGA通过SPI_ASDO线将程序烧录到Flash中保存起来,重新上电后,FPGA通过SPI_XDATA将程序重新读取到FPGA中进行试验。
Flash具体配置过程如下:
- 菜单 File→Convert programming files,如图3-8;
- 选项设置
- 选择JTAG Indirect configuration File(.Jic)
- Configuration Device:EPCQ 128A(兼容开发板N25Q128A)
- Mode:Active serial
图3-8 .jic文件设置
- 点击Advanced按钮,按图3-9进行设置
图3-9 Advanced选项设置
- 添加转换文件,如图3-10所示
图3-10 添加转换文件
- 添加设备,如图3-11所示
图3-11 添加设备
- 点击Generate,生成BCD_counter.jic文件
- 与之前的下板验证操作一致,选择正确的文件(.jic)下板
第四步:上电验证
如图3-12所示,重新上电后,FPGA自动将Flash中的程序读入FPGA中并运行。
图3-12 数码管实验现象
实验四 Block/SCH实验
4.1 实验目的
- 复习Quartus中新建FPGA项目,器件选择,PLL创建,PLL频率设定,Verilog的树形层次结构的设计,SignalTapII的使用
- 掌握图形法从上到下的设计方法
- 结合BCD_counter项目实现数字钟的显示
- 观察实验结果
4.2 实验要求
运用图形设计工程。
4.3 实验内容
本次实验主要是掌握另一种设计方法,其他设计内容与实验三基本一致,不再详细介绍,下面介绍模块化设计方法。
- 新建项目 :File→New Project Wizard…
项目名:block_counter
选择器件(10cl010YE144c8G)
- 新建文件:File→New, 选择Blcok Diagram/Schematic File,如图4-1所示。
图4-1 新建文件
- 新建如图4-2所示,中间部分为图形设计区,可以进行Block/SCH设计
- 存储文件名为block_counter.bdf
- 双击图形设计区的空白处,添加符号
图4-2 图形设计界面
- 图形编辑
双击图形设计区,在libraries选择合适的库和器件
选中,修改
管脚名称
图4-3 输入图形符号
- 添加输入、输出,并修改输入、输出名称
- 添加自定义的符号
- 新建一个block/sch文件,存储为PLL_sys.bdf
- 添加添加PLL IP ,参照实验一
- 选择生成的文件中包含PLL1.bsf文件
- 在PLL_sys.bdf 文件中的空白区双击,选择刚生成的PLL1 符号,添加到该文件中,如图4-4所示
图4-4 在图形编辑界面中调用IP catalog 中生成的符号
- 继续添加其它符号,input,output,dff,GND等并连线,如图4-5。
图4-5 器件连线
- 将新建的文件重新创建图形编辑的符号,以便后续设计使用
- File→Create/Update→Create Symbol file for Current File,如图4-6所示
- 生成PLL_sys.bsf
图4-6 为当前文件创建符号文件(symbol file *.bsf)
- 创建分频模块
- 新建一个分频器的verilog文件div_us(代码参考附录工程文件)
- 将PLL输出时钟作为自身输入时钟,将100Mhz的时钟分频成1Mhz的时钟
- 重复(7),创建div_us.bsf
- 新建1000分频的verilog文件:div_1000f.v
- 创建div_1000f.bsf符号
- 创建输出脉冲us,ms,second模块,如图4-7所示,具体实现参考代码以及实验一、三的分频设计
- 新建block/sch文件block_div,在block_div.bdf中添加已设计好的图形符号文件
- 重复(7)创建block_div.bsf符号
图4-7 block/sch设计的us, ms, second脉冲
- 新建verilog文件bcd_counter.v,设计时、分计数器,并创建bsf符号,参考实验三,将其中一部分分频用(9)中block_div实现,
- 组合各个*.bsf, 完成数字钟的设计(block_counter.bdf),如图4-8所示
图4-8 BDF设计的数字钟
4.4 实验验证
管脚分配,编译,下载验证与实验三一致,可参考实验三,此处省略。
实验五 按键消抖实验
5.1 实验目的
- 复习流水灯设计过程
- 学习按键去抖原理,以及自适应程序设计
- 学习Fii-PRA040按键硬件电路的连接及使用
- 综合应用按键去抖与其它符合程序设计
5.2 实验要求
- 通过按键控制流水灯的移动
- 每按一次按键,流水灯移动一位
- 当按下左移按键时,流水灯向左移动,按下右移按键,流水灯向右移动
5.3 实验内容
5.3.1 按键和消抖原理简介
- 按键简介
板载按键为常见的轻触按键,按下有效,松开后自动弹起。共8个,分别为PB1(MENU)PB2(UP),PB3(RETURN),PB4(LETF),PB5(OK),PB6(RIGHT),PB7(DOWN)和一个硬件复位按键RESET。如图5-1所示。
图5-1 按键实物图
- 消抖原理简介
只要用到机械按键,我们就必须考虑它的不稳定性。通常按键所用的都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如图 5-1 所示。
按键稳定闭合时间长短是由操作人员决定的,通常都会在 100ms 以上,刻意快速按的话能达到 40-50ms 左右,很难再低了。抖动时间是由按键的机械特性决定的,一般都会在几毫秒到几十毫秒之间,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。当检测到按键状态变化时,不是立即去响应动作,而是先等待闭合或断开稳定后再进行处理。按键消抖可分为硬件消抖和软件消抖。
图5-2 按键抖动原理
在绝大多数情况下,我们是用软件即程序来实现消抖的。最简单的消抖原理,就是当检测到按键状态变化后,先等待一个 10ms 左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。这种检测在传统的软件设计中使用比较广泛。但是随着按键使用次数增加,或不同质量的按键其反应也会不同。如果延时过短抖动不能滤除干净,延时过长又影响按键的灵敏度。
5.3.2 硬件设计
按键原理图如图5-3所示,可以看到按键一侧(P1,P2)与GND相连,另一侧(P3,P4)与FPGA相连,同时通过一个10K电阻接VCC。正常状态下,按键悬空,此时按键P3处电位为1,因此按键向FPGA输入值为1;当按键按下时,按键两侧导通,此时按键P3处电位为0,因此按键向FPGA输入值为0。所以板载开关为低有效。
图5-3 原理图按键部分
5.3.3 程序设计
5.3.3.1 顶层设计
顶层设计如图5-6所示。
图5-6 顶层设计
5.3.3.2 程序介绍
分频模块和LED显示模块参考之前的实验,对新增部分按键消抖模块进行介绍。
本次实验介绍的是一种自适应的按键去抖方法:在探测到按键状态变化时开始计时,在10ms内如果状态发生变化说明抖动存在,返回初始状态,清除延时计数器,重新探测按键状态,直到延时计数器计满到10ms为止。按键的按下与释放采用相同的去抖方法。流程图如图5-7所示。其中状态0、1对按键按下状态去抖;2、3对按键释放去抖。在完成整个去抖后,该程序输出一个同步时钟脉冲。
module pb_ve (
input sys_clk, input sys_rst, input ms_f, input keyin, output keyout ); reg keyin_r; reg keyout_r; reg [1:0] ve_key_st; reg [3:0] ve_key_count; always @ (posedge sys_clk) begin keyin_r <= keyin; end always @ (posedge sys_clk) begin if (sys_rst) begin keyout_r <= 1’b0; ve_key_count <= 0; ve_key_st <= 0; end else case (ve_key_st) 0 : begin keyout_r <= 1’b0; ve_key_count <= 0; if (!keyin_r) ve_key_st <= 1; end 1 : begin if (keyin_r) ve_key_st <= 0; else begin if (ve_key_count == 10) ve_key_st <= 2; else if (ms_f) ve_key_count <= ve_key_count + 1’b1; end end 2 : begin ve_key_count <= 0; if (keyin_r) ve_key_st <= 3; end 3 : begin if (!keyin_r) ve_key_st <= 2; else begin if (ve_key_count == 10) begin ve_key_st <= 0; keyout_r <= 1’b1; end else if (ms_f) ve_key_count <= ve_key_count + 1’b1; end end default : ; endcase end assign keyout = keyout_r; endmodule |
图5-7 按键去抖流程图
5.4 实验验证
第一步:分配管脚
管脚分配如表5-1所示
表5-1 按键消抖实验管脚映射表
程序信号名 | 网络标号 | FPGA管脚 | 端口说明 |
left | PB44 | AB4 | 左移信号 |
right | PB6 | AA4 | 右移信号 |
clk | CLK_50M | G21 | 输入时钟 |
rst_n | PB3 | Y6 | 复位按键 |
led[7] | LED7 | F2 | Led灯第7位 |
led[6] | LED6 | F1 | Led灯第6位 |
led[5] | LED5 | G5 | Led灯第5位 |
led[4] | LED4 | H7 | Led灯第4位 |
led[3] | LED3 | H6 | Led灯第3位 |
led[2] | LED2 | H5 | Led灯第2位 |
led[1] | LED1 | J6 | Led灯第1位 |
led[0] | LED0 | J5 | Led灯第0位 |
第二步:下板验证
管脚分配完成后进行全编译,通过后进行下板验证。实验现象如图7-8至5-10所示。
程序下板后 LED灯全亮,如图7-8所示。
图7-8 按键消抖实验现象(复位状态)
按下right右移按键后,最高位LED灯点亮,如图7-9所示。
图7-9 按键消抖实验现象(按一次右移按键)
再次按下右移按键,LED灯向右移动一位。如图7-10所示。
图7-10 按键消抖实验现象(再按一次下右移按键)
实验六 乘法器的使用与modelsim 仿真
6.1 实验目的
- 学会乘法器的使用
- 使用modelsim仿真设计输出
6.2 实验要求
- 8×8 的乘法器,第一个输入数值为8位开关,第二个输入值为8位计数器的输出
- 在modelsim中观察输出结果
- 用四位数码管观察计算结果
6.3 实验内容
由于学习使用对仿真工具和新IP核的,因此无简介和硬件设计部分。
6.3.1 程序介绍
Modelsim是一款HDL语言仿真软件。可以将我们的程序进行模拟和仿真,达到检验和纠错的功能。使用Modelsim仿真时,与之前的实验有所不同,在建立工程的时候需要用将使用的仿真工具在EDA工具选择窗口进行添加,如图6-1所示。
图6-1 EDA工具设置
程序中只用到了一个计数器,一个PLL和一个乘法器,这里只对乘法器进行介绍。
第一步:主程序框架的建立
module mult_sim (
input inclk, input rst, input [7:0] sw, output [15:0] mult_res, output reg [7:0] count ); |
将开关的值作为乘法器第一个输入,将计数器的值第二个输入,并将计算结果输出。
第二步:乘法器IP核设置步骤如下:
- 添加LPM_MULT IP(IP Catalog→Library→Basic Functions→Arithmatic→LPM_MULT)保存好路径后,弹出乘法器的设置窗口,如图6-2所示,按要求将两个输入数据的尾款设为八位。
图6-2 mult设置1+
- 选择输入数据为unsigned,如图6-3所示
图6-3 mult设置2
- 选择带pipeline(流水线),用以加快运行速度,如图6-4所示
- 其他默认设置
图6-4 mult设置3
8×8乘法器例化如下
reg sys_clk;
mult_8x8 mult_8x8_inst ( .clock (sys_clk), .dataa (sw), .datab (count), .result (mult_res) ); |
6.4 Modelsim的使用及实验验证
本次实验我们使用Modelsim进行模拟仿真来验证试验。
方法一:基于波形输入的仿真
- 点击菜单栏Tools→Option,如图6-5 指定路径,点击OK
图6-5 指定Modelsim-Altera的路径
- 点击菜单栏Tools→Run Simulation Tool→RTL Simulation 启动modelsim,如图6-6
图6-6 modelsim窗口
- 设置modelsim
- 菜单栏Simulate→Start Simulation
- 添加libraries,如图6-7
图6-7 添加仿真库
- 在Design 标签选择仿真项目 mult_sim,点击OK,如图6-8
图6-8 选择要仿真的项目
- 选择objects窗口中的信号拖到wave 窗口,如图6-9
图6-9 添加观察信号
- 设置wave窗口中的输入信号,右键弹出窗口如图6-10
图6-10 信号设置
- 逻辑信号选择force,时钟信号选择 clock
- rst 信号设置,如图6-11
图6-11 rst 信号设置
- Inclk信号设置,如图6-12
图6-12 Inclk信号设置
- sw信号设置,如图6-13
图6-13 sw信号设置
- 运行仿真,工具栏如图6-14,设置仿真时间100ns(蓝色圈),点击红色圈内图标运行仿真
图6-14 仿真时间设置
- 观察结果,如图6-15所示,
图6-15 仿真结果
- 结果分析
- 数器并没有按预定的方式计数,而是保持了未知XXXX
- sys_rst没有复位信号出现,从开始X到0
- 添加pll_locked信号到波形图中,重新仿真
- 从图6-16中可以看出,由于在PLL锁定之前sys_clk出现了上升沿,此时PLL_locked信号恰好由低到高转换,因此没有形成可靠的复位
图6-16 再次仿真结果
- 解决方案
- 定义sys_rst时设为1’b1;
- 利用外部rst提供复
这里采用方案a,修改代码如下:
module pll_sys_rst(
input inclk, output sys_clk, output reg sys_rst = 1’b1 ); |
- 重新编译仿真
图6-17 重新编译仿真
由于波形编辑效率较低,因此介绍另一种比较常用的仿真方法:编写仿真激励文件进行仿真。
方法二:编写仿真激励文件进行仿真
- 新建仿真激励文件,文件名为tb_mult.v
- 具体程序如下:
`timescale 1ns/1ps
module tb_mult; reg rst; reg clk; reg [7:0] sw; wire [7:0] count; wire [15:0] mult_res; //将需要仿真的工程例化进激励文件 mult_sim S1( .rst (rst), .inclk (clk), .sw (sw), .count (count), .mult_res (mult_res) ); //定义仿真所需要的时钟,将结果以文本形式显示出来 always begin #10 clk = ~clk; $monitor (“%d * %d = %d”, count, sw, mult_res); end //设置仿真条件 initial begin rst = 0; clk = 1;
#10 sw = 20; #10 sw = 50; #10 sw = 100; #10 sw = 101; #10 sw = 102; #10 sw = 103; #10 sw = 104; #50 sw = 105; //停止信号 #1000 $stop; end endmodule |
编写激励文件时,首先要在开头标注仿真的时间单位,本次实验为1ns,然后将需要仿真的工程例化到激励文件中,定义好时钟周期后以及仿真条件,并在一定时间后停止仿真,本次仿真经过1000个时钟周期后停止。
编写完成,进行编译,编译通过后,将激励文件添加到Modelsim中进行仿真,具体步骤如下:
- 点击菜单栏Assignments→settings如图6-19所示
图6-19 仿真设置1
- 选择 Compile test bench,点击Test Benches添加tb仿真文件,如图6-20所示
图6-20 仿真设置2
- 点击New,如图6-21所示,输入名称(与tb文件保持一致)
图6-21 仿真设置3
- 点击图6-21红色圈内…添加仿真文件,找到之前写好的tb_mult.v文件
- 打开,点击Add添加,如图6-22所示,然后点击OK(三次),完成设置。
图6-22 仿真设置4
- 点击菜单Tools→Run Simulation Tool→RTL Simulation,打开modelsim。
运行结果如图6-23所示。
图6-23 波形结果
达到一定延时后在Trascript 出口显示运行结果的文本信息,如图6-24。
因为运算结果会比输入晚一个时钟周期,所以文本信息中,乘数和结果会错一行,看起来不匹配,但不影响对实验结果的分析。
图6-24 文本显示运行结果信息
实验总结与思考
尝试用开关作为乘法器的输入,高四位一个数,第四位一个数,两数相乘,将结果输出。
实验七 十六进制数到BCD码转换及应用
7.1 实验目的
- 学会二进制数转BCD码(bin_to_bcd)
- 学会十六进制数转BCD码(hex_to_bcd)
7.2 实验要求
结合实验六,将运算结果显示到数码管上。
7.3 实验内容
7.2.1 十六进制数转BCD码原理简介
由于16进制数显示不直观,因此在实际开发中往往采用10进制显示输出。
人眼识别数字速度较慢,因此16进制到10进制的显示也不需要太快,一般采用两种方法进行性
- 减计数方法:
在同步时钟的控制下将16进制数做循环减1,直到减为0。同时设计合适的BCD码 10进制计数器,当16进制的数减为0时,BCD计数器恰好得到与16进制数相等的数值进行显示。
- 快速运算方法:移位加3运算得到,实现方法如下,
- 设定表达的10进制最大值,假定要将16位2进制数值(4位16进制)转换为10进制,数值最大可以表达65535。先定义5个四位2进制数单元ten_thousands, thousands, hundreds, tens, ones来容纳计算结果
- 将16进制数逐次左移1位,并将移出的部分放入定义好的变量中,依次判断ten_thousands, thousands, hundreds, tens, ones的单元中是否大于等于5,如果大于5,则将对应的位加3,直到16位移位完毕,则得到相应的结果。
注意:在移到最后一位时不做加3处理,直接得到运算结果
- 十六进制数到BCD码转换的原理
假设ABCD为4位2进制数(可能为个位,10位或100位等),将其调整为BCD码。由于整个计算都是在逐次移位中实现的,移动一位后会得到ABCDE(E是从低位移过来的,其数值为0或1)。此时应判断该值是否大于等于10,如果大于等于10,则该值加6将其调整到10以内,并将进位位移到高4位BCD码上。这里采用移动前调整,先判断ABCD是否大于等于5(10的一半),如果大于5就做加3(6的一半),然后进行移位。
例如 ABCD=0110(十进制6)
- 移位后为1100(12),大于1001(十进制9)
- 加0110(十进制6),ABCD=0010,进位位为1,结果表达为十进制12
- 采用移位前处理,ABCD=0110(6),大于等于5,加3
- ABCD=1001(9),左移一位
- ABCD=0010,移出的一位作为高四位BCD的最低位
- 由于移出的位为1,ABCD=0010(2),结果也位10进制的12
- 可以看出运算结果一致
- 需要注意(1)先判断,再加3,最后移位(2)如果有多个BCD码同时存在,则多个BCD码都要先判断是否需要加2调整再移位。
- 第一种方法比较简单,使用者可以自行练习,下面主要以第二种方法为主介绍。
例1:二进制转BCD,过程如图7-1所示
图7-1 例1,bin_to_bcd
例2:十六进制转BCD,过程如图7-2所示
图7-2 hex_to_bcd
7.2.2 程序介绍
第一步:主程序框架的建立
module HEX_BCD (
input [15:0] hex, output reg [3:0] ones, output reg [3:0] tens, output reg [3:0] hundreds, output reg [3:0] thousands, output reg [3:0] ten_thousands ); |
输入一个16位二进制数hex,最大可以表示十进制65535,因此输出个位ones,十位tens,百位hundreds,千位thousands,以及万位ten_thousands。
第二步:快速算法的实现
reg [15:0] hex_reg;
integer i; always @ (*) begin hex_reg = hex; ones = 0; tens = 0; hundreds = 0; thousands = 0; ten_thousands = 0;
for (i = 15; i >= 0; i = i-1) begin if(ten_thousands >= 5) ten_thousands = ten_thousands + 3; if(thousands >= 5) thousands = thousands + 3; if(hundreds >= 5) hundreds = hundreds + 3; if(tens >= 5) tens = tens + 3; if(ones >= 5) ones = ones + 3; ten_thousands = ten_thousands << 1; ten_thousands[0] = thousands[3]; thousands = thousands << 1; thousands[0] = hundreds[3]; hundreds = hundreds << 1; hundreds[0] = tens[3]; tens = tens << 1; tens[0]= ones[3]; ones = ones << 1; ones[0] = hex_reg[15]; hex_reg = {hex_reg[14:0], 1’b0}; end end |
参照图7-2,程序前一部分为判断计算部分,大于等于5则加3,后一部分为移位部分。
第三步:验证
参考实验六,采用Modelsim进行仿真,仿真条件设置如下:
initial begin
hex = 0 ; repeat (20) begin #10; hex = {$random}%20000; #10; end end |
开始时16位二进制数等于0,然后延时10ns,16位二进制数取小于20000的随机数,延时10ns以后结束,整个过程重复20遍。
对modelsim设置完成,添加完激励文件后,进行仿真,结果如图7-3所示
图7-3 二进制转BCD仿真结果
注意与思考:
- 上例中的赋值符号全部使用“=”而不是“<=”符号,为什么?
- 由于整个程序采用组合逻辑设计,因此在调用该程序时应注意和调用者中其它模块的时序同步。
7.4 十六进制数到BCD码转换的应用
- 继续完成实验六的乘法器,并将结果以10进制的方式用数码管显示,每经过1S钟,数码管上的计算结果改变一次,实验需要用到分频,数码管显示,乘法器以及十六进制数转BCD码。
- 编译,打开Compilation Report 观察timing Analysis,
- Fmax Summary 83.71Mhz,如图7-4
图7-4 Fmax Summary
- Setup Memory
图7-5 Setup time summary
- Timing Closure Recommendation 如图7-6
图7-6 Timing Analysis
- 从上面三项指标,可以看出上面的程序设计并不满足时序要求。同时也可以看到,最大的延时路径是乘法器的输出到HEX_BCD的延时。
有3种解决办法:
- 降低时钟频率
- 提高HEX_BCD的时序,增加流水线
- 在外围插入流水线隔离(可以减小一部分延迟)
增加流水线的方式,后续实验中介绍,由于HEX_BCD的功能主要用来做人机界面显示,对速度要求较低,这里采用降频的方式进行。
- 修改PLL增加一个20M频率输出(BCD_clk)。
- 重新编译,观察时序结果
- 管脚锁定,编译,下载到FII-PRA006/010开发板上,进行测试
7.5 实验验证
第一步:分配管脚
管脚分配表如表7-1所示。
表7-1 十六进制数转BCD实验管脚映射表
程序信号名 | 网络标号 | FPGA管脚 | 端口说明 |
clk | CLK_50M | G21 | 输入时钟 |
rst_n | PB3 | Y6 | 复位按键 |
scan[0] | SEG_3V3_D5 | F14 | 位选信号第0位 |
scan[1] | SEG_3V3_D4 | D19 | 位选信号第1位 |
scan[2] | SEG_3V3_D3 | E15 | 位选信号第2位 |
scan[3] | SEG_3V3_D2 | E13 | 位选信号第3位 |
scan[4] | SEG_3V3_D1 | F11 | 位选信号第4位 |
scan[5] | SEG_3V3_D0 | E12 | 位选信号第5位 |
seven_seg[0] | SEG_PA | B15 | 数码管段选a |
seven_seg[1] | SEG_PB | E14 | 数码管段选b |
seven_seg[2] | SEG_PC | D15 | 数码管段选c |
seven_seg[3] | SEG_PD | C15 | 数码管段选d |
seven_seg[4] | SEG_PE | F13 | 数码管段选e |
seven_seg[5] | SEG_PF | E11 | 数码管段选f |
seven_seg[6] | SEG_PG | B16 | 数码管段选g |
seven_seg[7] | SEG_DP | A16 | 数码管段选h |
SW[7] | PB7 | W6 | 拨码开关第7位 |
SW[6] | PB6 | Y8 | 拨码开关第6位 |
SW[5] | PB5 | W8 | 拨码开关第5位 |
SW[4] | PB4 | V9 | 拨码开关第4位 |
SW[3] | PB3 | V10 | 拨码开关第3位 |
SW[2] | PB2 | U10 | 拨码开关第2位 |
SW[1] | PB1 | V11 | 拨码开关第1位 |
SW[0] | PB0 | U11 | 拨码开关第0位 |
第二步:下板验证
管脚分配完成后进行全编译,通过后进行下板验证。实验现象如图7-7所示。拨码开关输入的值为00001010,十进制为10,计数器不断在累加,因此显示结果也一直以10累加变化。
图7-7 BCD码转换实验现象
实验总结与思考
- 超过16位的2进制转BCD码如何实现;
- 什么是同步时钟,如何处理异步时钟;
- 学习如何根据实际需求,设计满足时序要求的电路。
实验八 ROM的使用
8.1 实验目的
- 学习FPGA 内部memory block的
- 学习*.mif的格式,以及学会如何编辑mif文件来配置ROM的内容
- 学会RAM的使用,以及RAM的读写
8.2 实验要求
- 设计16数据输出的ROM,地址范围0-255
- 接口位8位开关输入,作为ROM的寻址
- 码管显示ROM的内容,要求将16进制数转换为BCD输出
8.3 实验内容
8.3.1 程序介绍
本次实验是在实验7的基础上进行,引用实验7的内容,因此只介绍IP核ROM部分。
- 在IP Catalog 选择ROM: 1-PORT, IP variation file type 选择verilog, 如图8-1所示
图8-1 ram IP核调用
- 按缺省设置进行,在如图8-2所示的设置界面中红色椭圆圈内,需要添加一个ROM的初始化文件.mif文件,(图片中已经添加完毕)
- 创建顶层文件rom.mif
- 生成rom.mif 文件, file→new→Memory Initialization File,如图8-3所示
- 如图8-4所示,在红色圈内修改存储深度(Number of words)和字节宽度(Word size),
- 如图8-5所示红色椭圆线标示的区域,在行列的地址区,右键可以选择输入数据和地址显示的格式,如16进制,8进制,2进制,有符号数,无符号数等
图8-2 ROM设置
图8-3 新建.mif文件
图8-4 .mif文件设置1
图8-5 .mif文件设置2
- 完成对rom IP和的设置后,对rom.mif进行数据的填写,为了方便验证,从低字节到高字节以递增的形式存储和地址一样的数据,如图8-6所示,起始地址0,终止地址255(之前设置的地址深度为256),初始值为0,步进为1。
图8-6 rom.mif进行数据填写
- 设置完成之后,系统将自动填写数据,如图8-7所示。
图8-7 自动填写后的部分数据
- 引用实验七中十六进制数转BCD码的实验,将ROM中的数据rom_q经过一级寄存器同步后,显示在数码管上
ROM例化如下:
reg [15:0] rom_q_r;
always @ (posedge BCD_clk) rom_q_r<=rom_q; HEX_BCD HEX_BCD_inst( .hex (rom_q_r), .ones (ones), .tens (tens), .hundreds (hundreds), .thousands (thousands), .ten_thousands (ten_thousands) ); onep_rom onep_rom_dut( .address (sw), .clock (sys_clk), .q (rom_q) ); |
8.4 实验验证
管脚分配与实验7一致。编译完成后下板验证。如图8-8所示。当拨码开关为10010100时,十进制为148,即表示我们将读出rom中第148字节内的内容,数码管显示为148,与我们存入的数据一致。
图8-8 rom实验现象
实验总结与思考
- 如何利用ROM 的初始化文件实现译码,如6位7段数码管的译码及扫描的译码
- 编写mif文件形成正弦波,余弦波形等函数发生器
- 综合应用,结合ROM和PWM的特点形成SPWM调制波形
实验九 利用双端口RAM实现帧数据的读写
9.1 实验目的
- 学会双端口RAM的配置及使用
- 学会使用同步时钟控制帧结构的同步
- 学会使用异步时钟控制帧结构的同步
9.2 实验要求
- 使用signaltap II 观察同步时钟帧的同步结构
- 双口RAM的扩展使用
- 设计三段状态机的使用
- 设计一个16位数据帧
- 数据由8位计数器生成:Data={~counta,counta}
- 由开关输入数据帧的ID(7位最大表达128个不同的数据帧)
- 16位checksum提供数据校验
- 16位checksum累加,丢弃进位位
- checksum取补后,追加到帧数据之后
- 由parameter 提供可配置的数据长度data_len
- 封包:当数据和checksum包写到双口RAM后,最后将userID, 帧长度和valid标志写到双口RAM的特定位置,memory的结构如表9-1所示
表9-1 memory结构
Wr_addr | 数据/标志 | Rd_addr |
8’hff | {valid,ID,data_len} | 8’hff |
… | N/A | … |
8’hnn+2 | N/A | 8’hnn+2 |
8’hnn+1 | ~checksum+1 | 8’hnn+1 |
8’hnn | datann | 8’hnn |
… | …. | … |
8’h01 | Data1 | 8’h01 |
8’h00 | Data0 | 8’h00 |
- 读、写按约定的顺序进行
握手信号为valid,这个标志为读写同步提供了可能,因此在程序设计中一定要处理好该信号的准确性。
9.3 实验内容
9.3.1 程序介绍
第一步:主程序框架的建立
module frame_ram
#(parameter data_len=250) ( input inclk, input rst, input [6:0] sw, output reg [6:0] oID, output reg rd_done, output reg rd_err ); |
第二步:状态机的定义
parameter [2:0] mema_idle=0,
mema_init=1, mema_pipe0=2, mema_read0=3, mema_read1=4, mema_wr_data=5, mema_wr_chsum=6, mema_wr_done=7; parameter [2:0] memb_idle=0, memb_init=1, memb_pipe0=2, memb_read0=3, memb_read1=4, memb_rd_data=5, memb_rd_chsum=6, memb_rd_done=7; |
第三步:其他定义:
时钟变量定义
wire sys_clk; wire BCD_clk; wire sys_rst; reg ext_clk; 双口RAM接口定义 reg [7:0] addr_a; reg [15:0] data_a; reg wren_a; wire [15:0] q_a; reg [7:0] addr_b; reg wren_b; wire [15:0] q_b; 写状态机部分变量定义 reg [6:0] user_id; reg [7:0] wr_len; reg [15:0] wr_chsum; wire wr_done; reg [7:0] counta; wire [7:0] countb; assign countb=~counta; reg [15:0] rd_chsum; reg [7:0] rd_len; reg [15:0] rd_data; reg ext_rst; reg [2:0] sta; reg [2:0] sta_nxt; 读状态机部分变量定义 reg [15:0] rd_chsum; reg [7:0] rd_len; reg [15:0] rd_data; reg [2:0] stb; reg [2:0] stb_nxt; |
第四步:生成双口RAM,PLL
dp_ram dp_ram_inst
( .address_a (addr_a), .address_b (addr_b), .clock (sys_clk), .data_a (data_a), .data_b (16’b0), .wren_a (wren_a), .wren_b (wren_b), .q_a (q_a), .q_b (q_b) ); pll_sys_rst pll_sys_rst_inst ( .inclk (inclk), .sys_clk (sys_clk), .BCD_clk (BCD_clk), .sys_rst (sys_rst) ); |
RAM宽度16位,深度为256。PLL输入50MHz时钟,输出100M作为其他模块工作时钟,20M用来驱动数码管显示。
第五步:数据生成计数器
always @ (posedge sys_clk)
if(sys_rst) begin counta <= 0; user_id <= 0; end else begin counta <=counta + 1; user_id <= sw; end |
第六步:写状态机
assign wr_done = (wr_len == (data_len – 1’b1));
//思考为什么采用wr_len==data_len-1,而不是wr_len==data_len //第一段 always @ (posedge sys_clk) begin if (sys_rst) begin sta = mema_idle; end else sta = sta_nxt; end //第二段 always @ (*) begin case (sta) mema_idle : sta_nxt = mema_init; mema_init : sta_nxt = mema_pipe0; mema_pipe0 : sta_nxt = mema_read0; mema_read0 : begin if (!q_a[15]) sta_nxt = mema_read1; else sta_nxt = sta; end mema_read1 : begin if (!q_a[15]) sta_nxt = mema_wr_data; else sta_nxt = sta; end mema_wr_data : begin if (wr_done) sta_nxt = mema_wr_chsum; else sta_nxt = sta; end mema_wr_chsum : sta_nxt = mema_wr_done; mema_wr_done : sta_nxt = mema_init; default : sta_nxt = mema_idle; endcase end |
//第三段
always @ (posedge sys_clk) begin case (sta) mema_idle : begin addr_a <= 8’hff; wren_a <= 1’b0; data_a <= 16’b0; wr_len <= 8’b0; wr_chsum <= 0; end mema_init, mema_pipe0, mema_read0, mema_read1 : begin addr_a <= 8’hff; wren_a <= 1’b0; data_a <= 16’b0; wr_len <= 8’b0; wr_chsum <= 0; end mema_wr_data : begin addr_a <= addr_a + 1’b1; wren_a <= 1’b1; data_a <= {countb, counta}; wr_len <= wr_len + 1’b1; wr_chsum <= wr_chsum + {countb, counta}; end mema_wr_chsum : begin addr_a <= addr_a + 1’b1; wr_len <= wr_len + 1’b1; wren_a <= 1’b1; data_a <= (~wr_chsum) + 1’b1; end mema_wr_done : begin addr_a <= 8’hff; wren_a <= 1’b1; data_a <= {1’b1, user_id, wr_len}; end default : ; endcase end |
写顺序:
- 读取8’hff地址的标志(控制字),如果valid=1’b0,程序进入下一步,否则等待
- 地址加1,8’hff+1恰好为零,从0地址开始写数据并计算checksum
- 判断是否达到预定的数据长度,达到转下一步,否则继续写数据并计算checksum
- checksum取补,写入存储器中
- 在地址8’hff中写入控制字,封包
第七步:读状态机
//第一段
always @ (posedge sys_clk) begin if (ext_rst) begin stb = memb_idle; end else stb = stb_nxt; end //第二段 always @ (*) begin case (stb) memb_idle : stb_nxt = memb_init; memb_init : stb_nxt = memb_pipe0; memb_pipe0 : stb_nxt = memb_read0; memb_read0 : begin if (q_b[15]) stb_nxt = memb_read1; else stb_nxt = memb_init; end memb_read1 : begin if (q_b[15]) stb_nxt = memb_rd_data; else stb_nxt = memb_init; end memb_rd_data : begin if(rd_done) stb_nxt = memb_rd_chsum; else stb_nxt = stb; end memb_rd_chsum : stb_nxt = memb_rd_done;
memb_rd_done : stb_nxt = memb_init; default : stb_nxt = memb_idle; endcase end |
//第三段,实际操作需在时钟的边沿驱动下进行
always @ (posedge sys_clk) begin case (stb) memb_idle : begin addr_b <= 8’hff; rd_data <= 0; rd_chsum <= 0; wren_b <= 1’b0; rd_len <= 8’b0; oID <= 7’b0; rd_err <= 1’b0; end memb_init : begin addr_b <= 8’hff; rd_data <= 0; rd_chsum <= 0; wren_b <= 1’b0; rd_len <= 8’b0; oID <= 7’b0; rd_err <= 1’b0; end memb_pipe0 : begin addr_b <= 8’b0; end memb_read0 : begin if (q_b[15]) addr_b <= addr_b + 1’b1; else addr_b <= 8’hff; rd_data <= 0; rd_chsum <= 0; wren_b <= 1’b0; rd_len <= 8’b0; oID <= 7’b0; end memb_read1 : begin if(q_b[15]) addr_b <= addr_b + 1’b1; else addr_b <= 8’hff; rd_data <= 0; rd_chsum <= 0; wren_b <= 1’b0; rd_len <= q_b[7:0]; oID <= q_b[14:8]; end memb_rd_data : begin addr_b <= addr_b + 1’b1; rd_data <= q_b; rd_chsum <= rd_chsum + rd_data; wren_b <= 1’b0; rd_len <= rd_len – 1’b1; end memb_rd_chsum : begin addr_b <= 8’hff; wren_b <= 1’b0; if (|rd_chsum) rd_err <= 1’b1; end memb_rd_done : begin addr_b <= 8’hff; wren_b <= 1’b1; end default : ; endcase end |
读顺序
- Idle 为复位后的状态
- Init: 初始化,置地址为8’hff
- Rd_pipe0:加一个latency(由于读地址和数据都经过锁存)。地址+1,形成读数据流水结构
- Read0:置地址为8’hff, 读控制字并判断valid位是否有效
- 如果valid=1’b1,地址+1,跳到下一步
- 如果valid=1’b0,表示数据包还没有准备就绪,地址设置成8’hff,返回到init状态
- Read1: 再次读控制字
- 如果valid=1’b1,地址+1,ID和数据长度赋给相应变量并跳到下一步
- 如果valid=1’b0,表示数据包还没有准备就绪,地址设置成8’hff, 返回到init状态
- Rd_data:
- 读数据付给数据变量
- 计算checksum,data_len-1
- 判读data_len是否为0
- 0:已读完所有数据,跳下一步
- 非0,继续本状态的操作
- rd_chsum:读checksum的值,并计算最后一步checksum。判读数据的正确性,设置rd_err的标志
- rd_done: 最后一步清除存储器中的valid标志,为下个数据包开放写使能
9.4 实验验证
第一步:分配管脚
管脚分配如表9-2所示。
表9-2 帧数据读写实验管脚映射表
程序信号名 | 网络标号 | FPGA管脚 | 端口说明 |
Inclk | CLK_50M | G21 | 输入时钟 |
rst | PB3 | Y6 | 复位信号 |
SW[7] | SW7 | W6 | 开关输入7 |
SW[6] | SW6 | Y8 | 开关输入6 |
SW[5] | SW5 | W8 | 开关输入5 |
SW[4] | SW4 | V9 | 开关输入4 |
SW[3] | SW3 | V10 | 开关输入3 |
SW[2] | SW2 | U10 | 开关输入2 |
SW[1] | SW1 | V11 | 开关输入1 |
SW[0] | SW0 | U11 | 开关输入0 |
第二步:用signaltap观察双口RAM的读写结果
- 为了便于观察读写状态机协同结果,这里将数据长度改为4,重新编译下载。长数据的测试,用户可以自己进行测试。
module frame_ram
#(parameter data_len=4) ( input inclk, input rst, input [6:0] sw, output reg [6:0] oID, output reg rd_done, output reg rd_err ); |
- 观察测试结果
- 观察通过双端口RAM的握手机制
- 是否实现写数据包封包后,读取才开始进行
- 在读取整个数据包完成前,写数据包是否阻塞
- 观察外部接口信号和状态
- rd_done, rd_err
设置rd_err=1,或上升沿为触发信号,观察是否捕捉到错误信号
- 观察wren_a,wren_b信号与状态机跳转是否严格配合,达到设计要求
- Signaltap的实测结果,如图9-1
实验总结与思考
- 回顾设计要求,如何将一个实际需求经过分析,逐渐建立数字控制与状态机的模型并进行设计
- 在状态机的第三段修改成if…else模型并实现
- 注重思考如果读、写时钟不同,变成异步机制,如何控制握手
- 根据上面例子的理解,思考双口RAM如何在数据采集,异步通信,嵌入式CPU接口,DSP芯片接口方面的应用
- 如何利用双口RAM构建ITCM,DTCM为将来设计CPU做准备
图9-1 signaltab II 的信号
实验十 异步串行口设计与实验
10.1 实验目的
由于异步串行口在工业控制,通信以及软件调试中使用非常普遍,因此在FPGA开发中也是具有非常重要的地位
- 学习异步串行口通信的基本原理,握手机制,数据贞的结构
- 掌握异步采样技巧
- 复习数据包的贞结构
- 学习FIFO的使用
- 与PC的常用调试软件联合调试(SSCOM,teraterm等)
10.2 实验要求
- 设计收发全双工异步通信接口Tx,Rx
- 波特率11520bps,8bit数据,1个起始位,1个或2个停止位
- 接收缓冲(Rx FIFO),发送缓冲(Tx FIFO)
- 形成数据包
- 数据包解析
10.3 实验内容
10.3.1 UART接口简介
板载一个USB-B接口和一片CP2102芯片用以实现串行数据通信。
CP2102具有集成度高的特点,可内置USB2.0全速功能控制器、USB收发器、晶体振荡器、EEPROM及异步串行数据总线(UART),支持调制解调器全功能信号,无需任何外部的USB器件。实物图如图10-1所示。
图10-1 USB-B接口与CP2102芯片实物图
10.3.2 硬件设计
图10-2 原理图串口部分
USB串口转换的原理如图10-2所示。CP2102的TTL_TX和TTL_RX与FPGA相连,用以发送和接收数据,经过芯片内部处理之后,通过D_R_P和D_R_N经过一个保护芯片后与USB接口相连,与PC端进行数据传输,实现串口通讯。
10.3.3 程序介绍
第一步:主程序架构建立
module uart_top
( input inclk, input rst, input [1:0] baud_sel, input tx_wren, input tx_ctrl, input [7:0] tx_data, input tx_done, output txbuf_rdy,
input rx_rden, output [7:0] rx_byte, output rx_byte_rdy, output sys_rst, output sys_clk, input rx_in, output tx_out ); |
这里有许多的握手信号,带tx前缀的为发送部分信号,带rx前缀的为接收部分信号。
第二步:新建波特率生成器文件
- 输入时钟7.3728MHz(115200的64倍)。实际为7.377049MHz,这是由于PLL的系数是整数分频造成的,不过误差不大,在异步通信中可以通过停止位调节过来。如图10-3所示。
精细的解决方案
- 采用两级PLL实现,得到更精细的频率
- 停止位设为2位,可以有效消除误差
本次实验暂不做处理,默认输入频率为7.3728MHz
图10-3 PLL设置
- 支持输出波特率115200,57600,38400,19200四种
- 默认的波特率为115200
- 波特率设计源文件
//发送波特率,时钟分频选择
wire [8:0] frq_div_tx; assign frq_div_tx = (baud_sel == 2’b00) ? 9’d63: (baud_sel == 2’b01) ? 9’d127: (baud_sel == 2’b10) ? 9’d255:9’d511; reg [8:0] count_tx=9’d0; always @ (posedge inclk) if (rst) begin count_tx <= 9’d0; baud_tx <= 1’b0; end else begin if (count_tx == frq_div_tx) begin count_tx <= 9’d0; baud_tx <= 1’b1; end else begin count_tx <= count_tx + 1’b1; baud_tx <= 1’b0; end end |
设置了四个不同的档位对波特率进行选择,对应第二步(1)。接收部分波特率与发送部分一致类似。
第三步:设计发送缓冲区文件 tx_buf
- 8位FIFO,深度256,读写时钟分离,写满标志,读空标志
- 接口与握手
- rst复位信号
- wr_clk 写时钟
- tx_clk 发送时钟
- 8位写数据tx_data
- wr_en 写使能
- ctrl 写入的内容是数据还是控制字
- rdy 缓冲区ready,可以接受下一个数据贞
发送缓冲区例化文件
tx_buf
#(.TX_BIT_LEN(8),.STOP_BIT(2)) tx_buf_inst ( .sys_rst (sys_rst), .uart_rst (uart_rst), .wr_clk (sys_clk), .tx_clk (uart_clk), .tx_baud (tx_baud), .tx_wren (tx_wren), .tx_ctrl (tx_ctrl), .tx_datain (tx_data), .tx_done (tx_done), .txbuf_rdy (txbuf_rdy), .tx_out (tx_out) ); |
- 串行发送、接口与握手文件设计
- 接口设计
- tx_rdy,发送空闲,可以接受新的8bit数据
- tx_en,发送数据使能,传递给发送模块8bit数据使能信号
- tx_data, 待发送的8bit数据
- tx_clk ,发送时钟
- tx_baud, 发送波特率
- 例化
tx_transmit
#(.DATA_LEN(TX_BIT_LEN), .STOP_BIT(STOP_BIT) ) tx_transmit_inst ( .tx_rst (uart_rst), .tx_clk (tx_clk), .tx_baud (tx_baud), .tx_en (tx_en), .tx_data (tx_data), .tx_rdy (trans_rdy), .tx_out (tx_out) ); |
- 编写激励文件对发送部分进行仿真。(tb_uart)
- 发送部分modelsim 仿真波形,如图10-4所示。
图10-4 串口发送moselsim仿真波形
- 扩展设计(扩展内容只留给用户思考与练习,这里不在累述)
- 设计发送器支持5,6,7,8bit的PHY
- 支持奇偶校验
- 以上各个步骤的设置涉及到FIFO,PLL等,参看uart_top的项目文件
第四步:UART 接受文件的设计
- rx_phy.v的设计
- 设计策略和步骤
- 采用8倍采样:因此rx_baud与tx_baud是不同的,这里采样rx_band=8*tx_band
- 采用判断的方式实现接收数据的判决
在采样值计数完成后判断数据计数器是否大于4
- 接收数据的步骤:
- 同步:是指如何从接收的0101…找到起始位,sync_dtc
- 接收起始位(start)
- 循环接收8bit数据
- 接收停止位(判断是一个停止位,还是两个停止位)
- 判断停止位是否正确
- 正确,跳转B
- 错误,跳转A,重新同步
- 不判断,直接跳转B,本设计采用不判断的方案
- rx_buf的设计
- 设计策略与步骤
- 添加256深度,8位的fifo
- 读写时钟分离
- 异步清零(内部同步)
- 数据先于出现rdreq出现在读端口
- 步骤:
- 初始化:fifo,rx_phy
- 等待:FIFO 满信号(wrfull)为0
- 写:由rx_phy的:rx_phy_byte,rx_phy_rdy触发
- 写结束
- 返回 b. 继续等待
- rx_buf.v源程序(参考工程文件)
- 接收仿真激励
内容和步骤
- tx,rx回环测试(assign rx_in=tx_out)
- 继续使用TX部分的激励文件
- 编写rx的激励部分
- Modelsim仿真,如图10-5所示
- 思考与扩展
- 修改程序完成5、6、7、8bit的设计
- 完成接收端rx_phy的start,stop 出错时的重新同步的设计
- 完成rx_buf的收据贞的分析与打包
- 改判多采样为数据180°对齐的采样方式进行设计,比较FPGA资源,时序以及数据恢复效果
图10-5 rx_phy波形图
10.4 实验验证
- 硬件接口,FII-PRA040开发板已经集成USB到串口的转换
UART芯片
CP2102
RXD
USB
CON8
TXD
- 编写硬件测试文件
- 测试方案,开发板CON8连接主机USB接口
- 使用测试软件如teraterm,SSCOM3等,也可以自己编写一个串口通讯程序(C#,C++,JAVA,Python…)
- PC 按照一定格式发送数据
- 测试端采用计数器的方式生成一定格式的数据
- 编写测试程序hw_tb_uart,并将uart_top例化进去。
- 将此程序设为顶层,例化之前的程序,即可下板验证。
- 分配管脚:
锁定管如表10-1所示
表10-1 串行口实验管脚映射表
程序信号名 | 网络标号 | FPGA管脚 | 端口说明 |
Inclk | CLK_50M | G21 | 输入时钟 |
rst | KEY2 | Y6 | 复位信号 |
rx_in | TTL_RX | E16 | 串口输入数据 |
tx_out | TTL_TX | F15 | 串口发送数据 |
- 实验现象
- 观察PC收到的数据,如图10-6所示。
- 用signaltapII观察FPGA收到的数据
图10-6 发送数据在上位机上显示实验现象
- 接收部分请同学们自己动手练习
实验十一 IIC传输实验
11.1 实验目的
- 学习异IIC总线的基本原理,掌握IIC通信协议
- 掌握读写EEPROM的方法
- 使用逻辑分析仪联合调试
11.2 实验要求
- 正确通过FPGA往EEPROM中任意一个地址(本例程写入8’h03地址的寄存器)写入一个数字(本例程通过(SW7~SW0)改变写入的8bit数据值)。写入之后,并且成功读出该写入数据。读出数据直接显示在数码管上。
- 下载程序进入FPGA按LEFT键,执行数据写入eeprom操作,按RIGHT键读出刚才写入数据。
- 通过读取数码管上显示的值判断读取的值正确与否。数码管显示值与写入值一致,则正确,否则不正确。
- 用Signaltab II分析内部数据的正确性,与数码管的显示情况对比验证。
11.3 实验内容
11.3.1 EEPROM简介与IIC协议简介
- EEPROM简介
EEPROM(Electrically Erasable Programmable read only memory)是指带电可擦可编程只读寄存器。是一中掉电后数据不丢失的存储芯片。
在实验板上有一个IIC接口的的EEPROM芯片24LC02,容量大小为256bytes。由于其掉电后数据不丢失的特性,用户可以用来存储一些硬件设置数据或者用户信息。
- IIC的整体时序协议如下
- 总线空闲状态:SDA,SCL均为高电平
- IIC协议开始:SCL保持高电平,SDA则由高电平跳转为低电平,产生一个起始信号
- IIC读写数据阶段:包括数据的串行输入输出和数据接收方发出的应答型号
- IIC的传输结束位:SCL为高电平,SDA由低电平跳转为高电平,产生一个结束标志位。具体的时序图如下图11-1所示
- SDA在SCL为高时必须保持不变,仅SCL为低时改变
图11-1 IIC协议时序图
11.3.2 硬件介绍
每个IIC器件都有一个器件地址,有的器件地址在出厂时,就由厂家固定好了(具体数据可以找厂家的数据手册),有的确定了高几位,低位可以由使用者自行依据使用需求配置。开发板所采用的芯片EEPROM芯片24LC02的高四位地址已由元器件厂商固定为1010,低三位在开发板链接如下图,所以器件地址为1010000。IIC器件原理图(eeprom)如图11-2所示。Eeprom通过I2C_SCL时钟线与I2C_SDA数据线两根线与FPGA实现数据的读写。
图11-2 IIC器件原理图eeprom部分
11.3.3 程序介绍
本次实验共有两个主要模块,I2C的读写模块与LED的显示模块;这里主要介绍前者。
第一步:主程序框架的建立
module iic_com(
input clk, input rst_n, input [7:0] data, input sw1,sw2, inout scl, inout sda, output reg iic_done, output [7:0] dis_data ); |
输入的8位数据data是需要写入Eeprom中的数据,有8位拨码开关提供。
第二步:创建时钟I2C_CLK
reg [2:0] cnt;
reg [8:0] cnt_delay; reg scl_r; reg scl_link ; always @ (posedge clk or negedge rst_n) begin if (!rst_n) cnt_delay <= 9’d0; else if (cnt_delay == 9’d499) cnt_delay <= 9’d0; else cnt_delay <= cnt_delay + 1’b1; end always @ (posedge clk or negedge rst_n) begin if (!rst_n) cnt <= 3’d5; else begin case (cnt_delay) 9’d124: cnt <= 3’d1; //cnt=1:scl 9’d249: cnt <= 3’d2; //cnt=2:scl 9’d374: cnt <= 3’d3; //cnt=3:scl 9’d499: cnt <= 3’d0; //cnt=0:scl default: cnt<=3’d5; endcase end end `define SCL_POS (cnt==3’d0) //cnt=0:scl `define SCL_HIG (cnt==3’d1) //cnt=1:scl `define SCL_NEG (cnt==3’d2) //cnt=2:scl `define SCL_LOW (cnt==3’d3) //cnt=3:scl always @ (posedge clk or negedge rst_n) begin if (!rst_n) scl_r <= 1’b0; else if (cnt == 3’d0) scl_r <= 1’b1; else if (cnt == 3’d2) scl_r <= 1’b0; end assign scl = scl_link ? scl_r: 1’bz ; |
首先,用系统50MHZ时钟分频得到一个周期为10us的100KHz时钟,作为I2C协议的传输时钟,然后通过计数器,定义了时钟的上升沿,高状态,下降沿和低状态,为后续数据的读写以及I2C协议的开始于结束做准备。最后一句话的意思是定义一个数据有效信号,只有当信号为高,即数据有效的时候,I2C时钟再回有效,否则为高阻态。这也是按照I2C传输协议设置的。
第三步:I2C传输的具体实现
`define DEVICE_READ 8’b1010_0001
`define DEVICE_WRITE 8’b1010_0000 `define BYTE_ADDR 8’b0000_0011 parameter IDLE = 4’d0; parameter START1 = 4’d1; parameter ADD1 = 4’d2; parameter ACK1 = 4’d3; parameter ADD2 = 4’d4; parameter ACK2 = 4’d5; parameter START2 = 4’d6; parameter ADD3 = 4’d7; parameter ACK3 = 4’d8; parameter DATA = 4’d9; parameter ACK4 = 4’d10; parameter STOP1 = 4’d11; parameter STOP2 = 4’d12; reg [7:0] db_r; reg [7:0] read_data; reg [3:0] cstate; reg sda_r; reg sda_link; reg [3:0] num; always @ (posedge clk or negedge rst_n) begin if (!rst_n) begin cstate <= IDLE; sda_r <= 1’b1; scl_link <= 1’b1; sda_link <= 1’b1; num <= 4’d0; read_data <= 8’b0000_0000; cnt_5ms <= 20’h00000; iic_done <= 1’b0; end else case (cstate) IDLE : begin sda_link <= 1’b1; scl_link <= 1’b1; iic_done <= 1’b0 ; if (sw1_r || sw2_r) begin db_r <= `DEVICE_WRITE; cstate <= START1; end else cstate <= IDLE; end START1 : begin if (`SCL_HIG) begin sda_link <= 1’b1; sda_r <= 1’b0; num <= 4’d0; cstate <= ADD1; end else cstate <= START1; end ADD1 : begin if (`SCL_LOW) begin if (num == 4’d8) begin num <= 4’d0; sda_r <= 1’b1; sda_link <= 1’b0; cstate <= ACK1; end else begin cstate <= ADD1; num <= num + 1’b1; case (num) 4’d0 : sda_r <= db_r[7]; 4’d1 : sda_r <= db_r[6]; 4’d2 : sda_r <= db_r[5]; 4’d3 : sda_r <= db_r[4]; 4’d4 : sda_r <= db_r[3]; 4’d5 : sda_r <= db_r[2]; 4’d6 : sda_r <= db_r[1]; 4’d7 : sda_r <= db_r[0]; default : ; endcase end end else cstate <= ADD1; end ACK1 : begin if (`SCL_NEG) begin cstate <= ADD2; db_r <= `BYTE_ADDR; end else cstate <= ACK1; end ADD2 : begin if (`SCL_LOW) begin if (num == 4’d8) begin num <= 4’d0; sda_r <= 1’b1; sda_link <= 1’b0; cstate <= ACK2; end else begin sda_link <= 1’b1; num <= num+1’b1; case (num) 4’d0 : sda_r <= db_r[7]; 4’d1 : sda_r <= db_r[6]; 4’d2 : sda_r <= db_r[5]; 4’d3 : sda_r <= db_r[4]; 4’d4 : sda_r <= db_r[3]; 4’d5 : sda_r <= db_r[2]; 4’d6 : sda_r <= db_r[1]; 4’d7 : sda_r <= db_r[0]; default : ; endcase cstate <= ADD2; end end else cstate <= ADD2; end ACK2 : begin if (`SCL_NEG) begin if (sw1_r) begin cstate <= DATA; db_r <= data_tep; end else if (sw2_r) begin db_r <= `DEVICE_READ; cstate <= START2; end end else cstate <= ACK2; end START2 : begin if (`SCL_LOW) begin sda_link <= 1’b1; sda_r <= 1’b1; cstate <= START2; end else if (`SCL_HIG) begin sda_r <= 1’b0; cstate <= ADD3; end else cstate <= START2; end ADD3 : begin if (`SCL_LOW) begin if (num == 4’d8) begin num <= 4’d0; sda_r <= 1’b1; sda_link <= 1’b0; cstate <= ACK3; end else begin num <= num + 1’b1; case (num) 4’d0 : sda_r <= db_r[7]; 4’d1 : sda_r <= db_r[6]; 4’d2 : sda_r <= db_r[5]; 4’d3 : sda_r <= db_r[4]; 4’d4 : sda_r <= db_r[3]; 4’d5 : sda_r <= db_r[2]; 4’d6 : sda_r <= db_r[1]; 4’d7 : sda_r <= db_r[0]; default: ; endcase end end else cstate <= ADD3; end ACK3 : begin if (`SCL_NEG) begin cstate <= DATA; sda_link <= 1’b0; end else cstate <= ACK3; end DATA : begin if (sw2_r) begin if (num <= 4’d7) begin cstate <= DATA; if(`SCL_HIG) begin num <= num + 1’b1; case (num) 4’d0 : read_data[7] <= sda; 4’d1 : read_data[6] <= sda; 4’d2 : read_data[5] <= sda; 4’d3 : read_data[4] <= sda; 4’d4 : read_data[3] <= sda; 4’d5 : read_data[2] <= sda; 4’d6 : read_data[1] <= sda; 4’d7 : read_data[0] <= sda; default: ; endcase end end else if((`SCL_LOW) && (num == 4’d8)) begin num <= 4’d0; cstate <= ACK4; end else cstate <= DATA; end else if (sw1_r) begin sda_link <= 1’b1; if (num <= 4’d7) begin cstate <= DATA; if (`SCL_LOW) begin sda_link <= 1’b1; num <= num + 1’b1; case (num) 4’d0 : sda_r <= db_r[7]; 4’d1 : sda_r <= db_r[6]; 4’d2 : sda_r <= db_r[5]; 4’d3 : sda_r <= db_r[4]; 4’d4 : sda_r <= db_r[3]; 4’d5 : sda_r <= db_r[2]; 4’d6 : sda_r <= db_r[1]; 4’d7 : sda_r <= db_r[0]; default: ; endcase end end else if ((`SCL_LOW) && (num==4’d8)) begin num <= 4’d0; sda_r <= 1’b1; sda_link <= 1’b0; cstate <= ACK4; end else cstate <= DATA; end end ACK4 : begin if (`SCL_NEG) cstate <= STOP1; else cstate <= ACK4; end STOP1 : begin if (`SCL_LOW) begin sda_link <= 1’b1; sda_r <= 1’b0; cstate <= STOP1; end else if (`SCL_HIG) begin sda_r <= 1’b1; cstate <= STOP2; end else cstate <= STOP1; end STOP2 : begin if (`SCL_NEG) begin sda_link <= 1’b0; scl_link <= 1’b0; end else if (cnt_5ms==20’h3fffc) begin cnt_5ms <= 20’h00000; iic_done <= 1; cstate <= IDLE; end else begin cstate <= STOP2; cnt_5ms <= cnt_5ms + 1’b1; end end default: cstate <= IDLE; endcase end |
整个过程采用状态机实现。复位时为空闲状态IDLE,同时数据线sda_r拉高,时钟和数据都有效,即scl_link,sda_link为高;计数器num清零,读出数据read_data为0,5ms延时计数器清零,I2C传输结束信号iic_done为低无效。
- 空闲状态IDLE:当接收到读使能或者写使能信号sw1_r || sw2_r,将写控制字赋值给中间变量db_r <= `DEVICE_WRITE,并跳转到起始状态 START1;
- 起始状态START1:在时钟信号为高的状态下将数据线拉低,产生I2C传输的起始信号,并跳转到器件地址状态ADD1;
- 器件地址状态ADD1:将写控制字(器件地址加一位0)按MSB(高位优先)传输完成后,将sda_link拉低释放数据总线为高阻态,并跳转到第一次应答状态ACK1,等待从机(EEPROM)的应答信号
- 第一次应答状态ACK1:如果数据线被拉低则证明从机正常接收数据,否则数据没有写进到Eeprom中去,接下来是重新写还是停止由实验者决定,这里没有暂时不做任何判断及处理,直接跳转至写寄存器地址状态ADD2,并将数据写入的地址BYTE_ADDR赋值给中间变量(本次实验将数据写入第三个寄存器中,即BYTE_ADDR = 0000_0011)
- 寄存器地址状态ADD2:与(3)相同,将寄存器地址传输给从机并跳转到第二次应答状态ACK2
- 第二次应答状态ACK2:此时急需要判断,如果是写状态sw1,则跳转至数据传输状态DATA,同时将写入的数据赋值给中间变量,如果是读状态sw2,则跳转至第二次开始状态START2,并将读控制字赋值给中间变量
- 第二次开始状态START2:与(2)相同产生一个起始信号,并跳转到读寄存器地址状态ADD3
- 读寄存器地址状态ADD3:将需要读出数据的寄存器地址传输完毕后跳转至第三次应答状态ACK3S
- 第三次应答状态ACK3:直接跳转到数据传输状态DATA,在读状态下,需要读出的数据会紧跟着寄存器地址直接被读出来
- 数据传输状态DATA:这里需要判断,如果是读状态,则直接将数据输出,如果是写状态则将需要写入的数据传输给数据线SDA,两种状态都需要跳转至第四次应答状态ACK4
- 第四次应答状态ACK4:直接跳转至停止传输转态STOP1
- 停止传输转态STOP1:在时钟线为高的情况下拉高数据线,产生一个停止信号,并跳转到传输完成状态STOP2
- 传输完成状态STOP2:将时钟线和数据线全部释放,并经过5ms延时后重新回到空闲状态IDLE等待下一次传输指令的到来。这是因为Eeprom规定连续两次读写操作之间的间隔不得小于5ms。
11.4 实验验证
第一步:分配管脚
管脚分配如表11-1所示
表11-1 IIC协议传输实验管脚映射表
程序信号名 | 网络标号 | FPGA管脚 | 端口说明 |
clk | CLK_50M | G21 | 系统时钟50M |
rst_n | PB3 | Y6 | 复位信号 |
sm_db[0] | SEG_PA | B15 | 数码管段选a |
sm_db [1] | SEG_PB | E14 | 数码管段选b |
sm_db [2] | SEG_PC | D15 | 数码管段选c |
sm_db [3] | SEG_PD | C15 | 数码管段选d |
sm_db [4] | SEG_PE | F13 | 数码管段选e |
sm_db [5] | SEG_PF | E11 | 数码管段选f |
sm_db [6] | SEG_PG | B16 | 数码管段选g |
sm_db [7] | SEG_DP | A16 | 数码管段选h |
sm_cs1_n | SEG_3V3_D1 | D19 | 数码管段选1 |
sm_cs2_n | SEG_3V3_D0 | F14 | 数码管段选0 |
data[0] | SW0 | U11 | 拨码开关输入 |
data[1] | SW1 | V11 | 拨码开关输入 |
data[2] | SW2 | U10 | 拨码开关输入 |
data[3] | SW3 | V10 | 拨码开关输入 |
data[4] | SW4 | V9 | 拨码开关输入 |
data[5] | SW5 | W8 | 拨码开关输入 |
data[6] | SW6 | Y8 | 拨码开关输入 |
data[7] | SW7 | W6 | 拨码开关输入 |
sw1 | PB4 | AB4 | 写入eeprom按键 |
sw2 | PB6 | AA4 | 读出eeprom按键 |
scl | I2C_SCL | D13 | eeprom随路时钟 |
sda | I2C_SDA | C13 | eeprom数据线 |
第二步:下板验证
管脚分配完成后进行全编译,通过后进行下板验证。
程序下板后,按LEFT键将SW7~SW0所代表的8bit值,写入eeprom.再按RIGHT键读取刚才写入位置的的值。观察实验板上数码管显示的值与写入eeprom地址8’h03寄存器的值(SW7~SW0)的一致性(本次实验写入8’h34)。读出来的值显示在数码管上。实验现象如图11-3所示。
图11-3 EEPROM实验现象
实验十二 AD、DA实验
12.1 实验目的
由于在现实世界中,所有的自然存在的信号都是模拟信号,而在实际工程中读取并且处理的都为数字信号。所以存在一个自然信号和工程信号相互转化的过程(数模转化:DAC,,模数转化:ADC)。本实验的目的如下:
- 学习AD转化的相关理论
- 复习上一个实验学习到的IIC协议知识,对实验板上的PCF8591写入数据
- 从PCF8591中读取AD采集的值,并且将取到的值转化为实际值,用数码管显示来
12.2 实验要求
- 利用该芯片的ADC口进行模数转换,正确配置芯片,采集实验板上三个可变电阻(电位器,光敏电阻,热敏电阻)变化,并将采集的电压值经过数码管显示出来
- 下板验证,与电阻特性进行对比,验证结果的正确性
12.3 实验内容
12.3.1 AD转换芯片(PCF8591)简介
PCF8591是一个单片集成、单独供电、低功耗、8-bit CMOS数据获取器件。PCF8591具有4个模拟输入、1个模拟输出和1个串行I²C总线接口。PCF8591的3个地址引脚A0, A1和A2可用于硬件地址编程,在PCF8591器件上输入输出的地址、控制和数据信号都是通过双线双向I2C总线以串行的方式进行传输。有关IIC总线协议的内容请参考上实验十一。发送完器件地址信息和读写控制字后,就发送控制字信息。
图12-1 PCF8591控制字示意图
具体的控制字信息如图12-1所示。D1-D0用于四个通道设置,D2自动增益选择,1有效,D5-D4决定模拟量输入选择,D6为模拟输出使能,D7,D3为预留项,为0。发送到PCF8951的第二个字节将被存储到控制寄存器中,用于控制器件功能。控制寄存器的高半字节用于允许模拟输出,将和模拟输入编程为单端或差分输入。低半字节选择一个由高半字节定义的模拟输入通道。如果自动增量标志置1,每次A/D转换后通道号将自动增加。
本次实验利用拨码开关(SW1,SW0)选择输入通道作为AD采集输入通道,具体通道信息如表12-1所示。把控制信息配置为8’h40,即为模拟输出,并默认为00通道,即默认情况下显示的是光敏电阻电压值。
表12-1 通道信息对照表
SW1,SW0 | 通道选择 | 采集对象 |
00 | 0 | 光敏电阻电压值 |
01 | 1 | 热敏电阻电压值 |
10 | 2 | 可调变阻器电压值 |
12.3.2 硬件设计
图12-2 原理图AD/DA转换部
用PCF8591进行AD/DA转换的原理图部分如图12-2所示。I2C总线经过两个上拉电阻,在不工作的时候拉高。A0,A1,A2接地,因此器件地址为7’b1010000,模拟输入通道AIN0接光敏电阻,AIN1接热敏电阻,AIN3接电位器。当选择好通道后,FPGA将通过数据总线ADDA_I2C_SLC把PCF8591中的值读到FPGA中进行运算处理。
12.3.3 程序介绍
本次实验同样是用I2C总线控制PCF8951芯片,因此程序与实验十一基本一致。本次只介绍不同的地方。
reg [7:0] read_data_temp [15:0];
reg [11:0] dis_data_temp; always @ (posedge clk) dis_data_temp <= read_data_temp[0] + read_data_temp[1] + read_data_temp[2] + read_data_temp[3] + read_data_temp[4] + read_data_temp[5] + read_data_temp[6] + read_data_temp[7] + read_data_temp[8] + read_data_temp[9] + read_data_temp[10] + read_data_temp[11] + read_data_temp[12] + read_data_temp[13] + read_data_temp[14] + read_data_temp[15]; always @ (posedge clk ) dis_data <= dis_data_temp >> 4; integer i; always @ (posedge clk or negedge rst_n) begin if (!rst_n) begin for (i=0;i<16;i=i+1) read_data_temp[i] <= 8’h00; end else if (iic_done) begin for (i=0;i<15;i=i+1) read_data_temp[i+1] <= read_data_temp[i]; read_data_temp[0] <= read_data ; end else begin for (i=0;i<16;i=i+1) read_data_temp[i] <= read_data_temp[i]; end end |
整个这一部分的作用,就是芯片在不断采集电阻两端的电压值的时候,由于一系列不稳定因素,会导致电压值的不稳定,因此输出的值将会出现较大误差,因此我们每次采集16组数据,然后求其平均值,将其结果输出作为此时电阻两端的电压值。再由电压值的变化,就可以判断阻值的变化规律。如光敏电阻,光强越大电压值越小,阻值越小,符合光敏电阻特性;热敏电阻温度越高电压值越小,阻值越小,符合光敏电阻特性;电位器顺时针旋转,电压增大,阻值增大,逆时针旋转电压减小,阻值减小。
AD芯片最总输出的是一个8位的数字量,但实质上他还不是我们所需要的电压值。他是将量程的电压值量化成256份(8位二进制数可表示256个数),因此在数码管显示的时候,我们需要作进一步的计算和转换工作。
parameter V_REF = 12’d3300;
reg [19:0] num_t; reg [19:0] num1; wire [3:0] data0; wire [3:0] data1; wire [3:0] data2; wire [3:0] data3; wire [3:0] data4; wire [3:0] data5; assign data5 = num1 / 17’d100000; assign data4 = num1 / 14’d10000 % 4’d10; assign data3 = num1 / 10’d1000 % 4’d10 ; assign data2 = num1 / 7’d100 % 4’d10 ; assign data1 = num1 / 4’d10 % 4’d10 ; assign data0 = num1 % 4’d10;
always @ (posedge clk) num_t <= V_REF * dis_data; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin num1 <= 20’d0; end else num1 <= num_t >> 4’d8; end |
我们的VCC为3.3V,因此电阻电压最大3.3V,所以将8位数据dis_data乘3300扩大1000倍赋值给numt,方便显示和观察,numt再缩小256倍(左移8位)赋值给num1与PCF8951量化的256份相对应,此时的num1就是此时电阻两端电压值的1000倍。我们将他的每一位显示到数码管上,由高位到低位(data5到data0),在千位时(data3)加上小数点,则此时数码管山显示的值就是此时电阻两端的电压值,并精确到小数点后3位。
12.4 实验验证
第一步:分配管脚
管脚分配如表12-2所示。
表12-2 AD转换实验管脚映射表
程序信号名 | 网络标号 | FPGA管脚 | 端口说明 |
clk | CLK_50M | G21 | 系统时钟50M |
rst_n | PB3 | Y6 | 复位信号 |
sm_db[0] | SEG_PA | B15 | 数码管段选a |
sm_db [1] | SEG_PB | E14 | 数码管段选b |
sm_db [2] | SEG_PC | D15 | 数码管段选c |
sm_db [3] | SEG_PD | C15 | 数码管段选d |
sm_db [4] | SEG_PE | F13 | 数码管段选e |
sm_db [5] | SEG_PF | E11 | 数码管段选f |
sm_db [6] | SEG_PG | B16 | 数码管段选g |
sm_db [7] | SEG_DP | A16 | 数码管段选h |
sel[0] | SEG_3V3_D0 | F14 | 位选信号第0位 |
sel[1] | SEG_3V3_D1 | D19 | 位选信号第1位 |
sel[2] | SEG_3V3_D2 | E15 | 位选信号第2位 |
sel[3] | SEG_3V3_D3 | E13 | 位选信号第3位 |
sel[4] | SEG_3V3_D4 | F11 | 位选信号第4位 |
sel[5] | SEG_3V3_D5 | R12 | 位选信号第5位 |
data[0] | SW0 | U11 | 拨码开关输入 |
data[1] | SW1 | V11 | 拨码开关输入 |
data[2] | SW2 | U10 | 拨码开关输入 |
data[3] | SW3 | V10 | 拨码开关输入 |
data[4] | SW4 | V9 | 拨码开关输入 |
data[5] | SW5 | W8 | 拨码开关输入 |
data[6] | SW6 | Y8 | 拨码开关输入 |
data[7] | SW7 | W6 | 拨码开关输入 |
scl | ADDA_I2C_SCL | C20 | PCF8591时钟线 |
sda | ADDA_I2C_SDA | D20 | PCF8591数据线 |
第二步:下板验证
管脚分配完成后进行全编译,通过后进行下板验证。
当默认状态下,即通道选择为00时,数码管显示当前环境亮度状态下,光敏电阻两端的电压值为2.010V,如图12-3所示。
图12-3 光敏电阻实验现象
当通道选择为01时,数码管显示当前环境温度下,热敏电阻两端的电压值为2.926V,如图12-4所示。
图12-4 热敏电阻实验现象
当通道选择为10时,数码管显示当前阻值状态下,电位器两端的电压为1.456V,如图12-5所示。
图12-5 电位器实验现象
实验十三 HDMI实验
13.1 实验目的
- 复习IIC协议知识
- 复习EEPROM的读写
- 学习HDMI理论知识
13.2 实验要求
通过HDMI接口,在屏幕上显示不同的图像内容。
13.3 实验内容
13.3.1 HDMI接口及ADV7511芯片简介
图像显示处理一直是FPGA研究的重点内容。现阶段图像显示方式也在不断发展。图像显示接口也逐渐由旧的VGA接口向新的DVI或者HDMI接口的方式过渡。HDMI是高清多媒体接口(High Definition Multimedia Interface)的简称。是一种数字化视频/音频接口技术,适合影像传输的专用型数字化接口,可同时传送音频和影像信号。
板载ADV7511是一款按照VESA标准将FPGA数字信号转换为HDMI信号的芯片。具体内容可以查看相关芯片手册。其中ADV7511_Programming_Guide和ADV7511_Hardware_Users_Guide是最重要的,可以通过查看该文档,来对ADV7511的寄存器进行配置。
ADV7511寄存器配置说明:ADV7511的总线输入D0-D3,D12-D15,D24-D27是没有输入,每位数据是8位的模式。直接设置0x15 [3:0]) 0x0数据,0x16的[3:2]数据对其模式也不需要设置了。把0X16的[5:4]设置11,为8位数据,其他的位数的值都是默认的。0x17[1]是指图像的长和宽的比率,可以设置为0或者1,在实际的液晶屏幕也不会根据该数据变化,而是根据液晶自己的设置,自动拉伸满屏模式。0x18[7]是开启色彩范围拉伸的方式,我们做的设计是RGB直接映射成RGB,所以直接禁用就可以。0X18[6:5]这个时候也无效使用了。在0XAF[1]是设定HDMI或者DVI模式,HDMI比DVI最直接的一点是HDMI可以发送数字音频数据和加密数据内容。这次我们是不需要,直接设置成DVI模式,值就是0。对于0XAF[7],设定为0,关闭HDMI加密功能。由于GCCD ,深度色彩加密数据我们不适用的,所以关闭掉GC选项。0xAF[7]设置为0,关闭GC CD的数据。当然这个时候也不需要对0x4c的寄存器设定了。其他的声音数据,因为设置为DVI输出方式,所以不用关心设定。 写完这些寄存器以后,图像可以显示出来的
13.3.2 硬件设计
板载HDMI模块由一个HDMI接口和ADV7511芯片组成,实物图如图13-1所示。原理图如图13-2所示。
图13-1 HDMI接口与ADV7511芯片实物图
图13-2 原理图HDMI部分
我们通过IIC总线对ADV7511芯片进行设置,将需要显示的图片信息通过HDMI_D0到HDMI_D23送到芯片内,以及控制信号HDMI_HSYNC和HDMI_VSYNC和时钟信号HDMI_CLK,经过芯片内部处理后通过HDMI接口传到显示器端。
13.3.3 程序介绍
对ADV7511芯片的配置部分,采用IIC协议进行,参考实验十一和实验十二。现在对数据处理部分进行简单介绍。
module hdmi_test (
input rst_n, input clk_in, input key1, output rgb_hs, output rgb_vs, output [7:0] rgb_r, output [7:0] rgb_g, output [7:0] rgb_b, output rgb_clk, inout scl, inout sda, output en ); |
FPGA通过IIC总线(时钟线scl,数据线sda)对ADV7511芯片进行配置,配置完成后,需要对输出的图像信息进行确定。以1080P(1920*1080)图像格式为例,会输出数据信号rgb_r(红色分量),rgb_g(绿色分量),rgb_b(蓝色分量),行同步信号rgb_hs,场同步信号rgb_vs以及一个时钟rgb_clk。每一个像素点由三种颜色分量组合形成,每一行1920个像素点按一定顺序(从左往右)依次填入颜色信息,完成一行后进行下一行,按一定顺序(从上到下)完成1080行,这样一帧图像信息就填写完成。通过这种行列扫描的方式确定每一帧图像信息,然后传送到ADV7511进行处理。行列扫描的时序图如图13-3,13-4所示。
图13-3 行同步时序
图13-4 场同步时序
第二步:1080p 图像时序产生程序数据定义
水平行扫描参数的设定1920*1080 60Hz 时钟位130M
parameter LinePeriod = 2000; //行周期 parameter H_SyncPulse = 12; //行同步脉冲(Sync a) parameter H_BackPorch = 40; //显示后沿(Back porch b) parameter H_ActivePix = 1920; //显示时序段(Display interval c) parameter H_FrontPorch= 28; //显示前沿(Front porch d) parameter Hde_start = 52; parameter Hde_end = 1972; 垂直列扫描参数的设定1920*1080 60Hz parameter FramePeriod = 1105; //列周期数 parameter V_SyncPulse = 4; //列同步脉冲(Sync o) parameter V_BackPorch = 18; //显示后沿(Back porch p) parameter V_ActivePix = 1080; //显示时序段(Display interval q) parameter V_FrontPorch= 3; //显示前沿(Front porch r) parameter Vde_start = 22; parameter Vde_end = 1102;
reg [12:0] x_cnt; reg [10:0] y_cnt; reg [23:0] grid_data_1; reg [23:0] grid_data_2; reg [23:0] bar_data; reg [3:0] rgb_dis_mode; reg [7:0 ] rgb_r_reg; reg [7:0] rgb_g_reg; reg [7:0] rgb_b_reg; reg hsync_r; reg vsync_r; reg hsync_de; reg vsync_de; reg [15:0] key1_counter; //按键
wire locked; reg rst; wire [12:0] bar_interval;
assign bar_interval = H_ActivePix[15: 3]; //彩条宽度 |
第三部:生成显示内容
always @ (posedge rgb_clk)
begin if (rst) hsync_r <= 1’b1; else if (x_cnt == LinePeriod) hsync_r <= 1’b0; else if (x_cnt == H_SyncPulse) hsync_r <= 1’b1; if (rst) hsync_de <= 1’b0; else if (x_cnt == Hde_start) hsync_de <= 1’b1; else if (x_cnt == Hde_end) hsync_de <= 1’b0; end always @ (posedge rgb_clk) begin if (rst) y_cnt <= 1’b1; else if (x_cnt == LinePeriod) begin if (y_cnt == FramePeriod) y_cnt <= 1’b1; else y_cnt <= y_cnt + 1’b1; end end always @ (posedge rgb_clk) begin if (rst) vsync_r <= 1’b1; else if ((y_cnt == FramePeriod) &(x_cnt == LinePeriod)) vsync_r <= 1’b0; else if ((y_cnt == V_SyncPulse) &(x_cnt == LinePeriod)) vsync_r <= 1’b1; if (rst) vsync_de <= 1’b0; else if ((y_cnt == Vde_start) & (x_cnt == LinePeriod)) vsync_de <= 1’b1; else if ((y_cnt == Vde_end) & (x_cnt == LinePeriod)) vsync_de <= 1’b0; end assign en = hsync_de & vsync_de; always @(posedge rgb_clk) begin if ((x_cnt[4]==1’b1) ^ (y_cnt[4]==1’b1)) grid_data_1 <= 24’h000000; else grid_data_1<= 24’hffffff; if ((x_cnt[6] == 1’b1) ^ (y_cnt[6] == 1’b1)) grid_data_2 <=24’h000000; else grid_data_2 <=24’hffffff; end always @ (posedge rgb _clk) begin if (x_cnt==Hde_start) bar_data <= 24’hff0000; //红色彩条 else if (x_cnt == Hde_start + bar_interval) bar_data <= 24’h00ff00; //绿色彩条 else if (x_cnt == Hde_start + bar_interval*2) bar_data <= 24’h0000ff; //蓝色彩条 else if (x_cnt == Hde_start + bar_interval*3) bar_data <= 24’hff00ff; //紫色彩条 else if (x_cnt == Hde_start + bar_interval*4) bar_data <= 24’hffff00; //黄色彩条 else if (x_cnt == Hde_start + bar_interval*5) bar_data <= 24’h00ffff; //青色彩条 else if (x_cnt == Hde_start + bar_interval*6) bar_data <= 24’hffffff; //白色彩条 else if (x_cnt == Hde_start + bar_interval*7) bar_data <= 24’hff8000; //橙色彩条 else if (x_cnt == Hde_start + bar_interval*8) bar_data <= 24’h000000; //其余黑色 end always @ (posedge rgb_clk) begin if (rst) begin rgb_r_reg <= 0; rgb_g_reg <= 0; rgb_b_reg <= 0; end else case (rgb_dis_mode) 4’b0000 : //图像显示全黑 begin rgb_r_reg <= 0; rgb_g_reg <= 0; rgb_b_reg <= 0; end 4’b0001 : //图像显示全白 begin rgb_r_reg <= 8’hff; rgb_g_reg <= 8’hff; rgb_b_reg <= 8’hff; end 4’b0010 : //图像显示全红 begin rgb_r_reg <= 8’hff; rgb_g_reg <= 0; rgb_b_reg <= 0; end 4’b0011 : //图像显示全绿 begin rgb_r_reg <= 0; rgb_g_reg <= 8’hff; rgb_b_reg <= 0; end 4’b0100 : //图像显示全蓝 begin rgb_r_reg <= 0; rgb_g_reg <= 0; rgb_b_reg <= 8’hff; end 4’b0101 : //图像显示方格1 begin rgb_r_reg <= grid_data_1[23:16]; rgb_g_reg <= grid_data_1[15:8]; rgb_b_reg <= grid_data_1[7:0]; end 4’b0110 : //图像显示方格2 begin rgb_r_reg <= grid_data_2[23:16]; rgb_g_reg <= grid_data_2[15:8]; rgb_b_reg <= grid_data_2[7:0]; end 4’b0111 : //图像显示水平渐变色 begin rgb_r_reg <= x_cnt[10:3]; rgb_g_reg <= x_cnt[10:3]; rgb_b_reg <= x_cnt[10:3]; end 4’b1000 : //图像显示垂直渐变色 begin rgb_r_reg <= y_cnt[10:3]; rgb_g_reg <= y_cnt[10:3]; rgb_b_reg <= y_cnt[10:3]; end 4’b1001 : //图像显示红水平渐变色 begin rgb_r_reg <= x_cnt[10:3]; rgb_g_reg <= 0; rgb_b_reg <= 0; end 4’b1010 : //图像显示绿水平渐变色 begin rgb_r_reg <= 0; rgb_g_reg <= x_cnt[10:3]; rgb_b_reg <= 0; end 4’b1011 : //图像显示蓝水平渐变色 begin rgb_r_reg <= 0; rgb_g_reg <= 0; rgb_b_reg <= x_cnt[10:3]; end 4’b1100 : //图像显示彩条 begin rgb_r_reg <= bar_data[23:16]; rgb_g_reg <= bar_data[15:8]; rgb_b_reg <= bar_data[7:0]; end default : //图像显示全白 begin rgb_r_reg <= 8’hff; rgb_g_reg <= 8’hff; rgb_b_reg <= 8’hff; end endcase end assign rgb_hs = hsync_r; assign rgb_vs = vsync_r; assign rgb_r = (hsync_de & vsync_de) ? rgb_r_reg : 8’h00; assign rgb_g = (hsync_de & vsync_de) ? rgb_g_reg : 8’b00; assign rgb_b = (hsync_de & vsync_de) ? rgb_b_reg : 8’h00;
always @(posedge rgb_clk) begin if (key1 == 1’b1) key1_counter <= 0; else if ((key1 == 1’b0) & (key1_counter <= 16’d130000)) key1_counter <= key1_counter + 1’b1; if (key1_counter == 16’h129999) begin if(rgb_dis_mode == 4’b1100) rgb_dis_mode <= 4’b0000; else rgb_dis_mode <= rgb_dis_mode + 1’b1; end end |
当按下按键,将输入一个key1信号,此时屏幕显示的内容将根据rgb_dis_mode的改变发生变化,显示对应图片内容。
13.4 实验验证
第一步:分配管脚
管脚分配如表13-1所示。
表13-1 HDMI实验管脚映射表
程序信号名 | 网络标号 | FPGA管脚 | 端口说明 |
clk | CLK_50M | G21 | 系统时钟50M |
rst_n | PB3 | Y6 | 复位信号 |
en | HDMI_R_DE | A8 | 使能信号 |
scl | I2C_SCL | D13 | i2c时钟线 |
sda | I2C_SDA | C13 | i2c数据线 |
key1 | PB2 | V5 | 切换显示内容 |
rgb_clk | HDMI_R_CLK | E5 | HDMI图像时钟 |
rgb_hs | HDMI_R_HS | B9 | 行同步信号 |
rgb_vs | HDMI_R_VS | A9 | 场同步信号 |
rgb_b[0] | HDMI_R_D0 | A7 | 图像蓝色分量 |
rgb_b[1] | HDMI_R_D1 | B8 | |
rgb_b[2] | HDMI_R_D2 | E9 | |
rgb_b[3] | HDMI_R_D3 | B7 | |
rgb_b[4] | HDMI_R_D4 | C8 | |
rgb_b[5] | HDMI_R_D5 | C6 | |
rgb_b[6] | HDMI_R_D6 | F8 | |
rgb_b[7] | HDMI_R_D7 | B6 | |
rgb_g[0] | HDMI_R_D8 | A5 | 图像绿色分量 |
rgb_g[1] | HDMI_R_D9 | C7 | |
rgb_g[2] | HDMI_R_D10 | D7 | |
rgb_g[3] | HDMI_R_D11 | B5 | |
rgb_g[4] | HDMI_R_D12 | C6 | |
rgb_g[5] | HDMI_R_D13 | A4 | |
rgb_g[6] | HDMI_R_D14 | D6 | |
rgb_g[7] | HDMI_R_D15 | B4 | |
rgb_r[0] | HDMI_R_D16 | E7 | 图像红色分量 |
rgb_r[1] | HDMI_R_D17 | A3 | |
rgb_r[2] | HDMI_R_D18 | C4 | |
rgb_r[3] | HDMI_R_D19 | B3 | |
rgb_r[4] | HDMI_R_D20 | C3 | |
rgb_r[5] | HDMI_R_D21 | F7 | |
rgb_r[6] | HDMI_R_D22 | F9 | |
rgb_r[7] | HDMI_R_D23 | G7 |
第二步:下板验证
管脚分配完成后进行全编译,通过后进行下板验证。
按下切换按键,显示内容相应改变,实验现象如图所示(仅列举出几个)。
图13-5 HDMI实验现象1(全白)
图13-6 HDMI实验现象2(方格)
图13-7 HDMI实验现象3(彩条)
实验十四 以太网实验
14.1 实验目的
- 了解什么是以太网,以及其工作原理
- 熟悉不同接口类型(MII,GMII,RGMII)之间的关系以及优缺点(我们的开发板采用RGMII)
- 结合开发板,完成数据的传输与接收,并验证
14.2 实验要求
- 进行回环测试,检查硬件是否正常工作
- 进行数据的就收验证
- 进行数据的发送验证
14.3 实验内容
14.3.1 实验原理简介
以太网是一种基带局域网技术,以太网通信是一种使用同轴电缆作为网络媒体,采用载波多路访问和冲突检测机制的通信方式,数据传输速率达到1Gbit/s,可满足非持续性网络数据传输的需要。以太网接口作为一种互联型接口,当下应用非常的广泛。千兆以太网MII接口类型很多,常用GMII和RGMII。
MII接口一共有16根线,如图14-1所示。
图14-1 MII接口
RXD(Receive Data)[3:0]:数据接收信号,共4根信号线;
TX_ER(Transmit Error):发送数据错误提示信号,同步于TX_CLK,高电平有效,表示TX_ER有效期内传输的数据无效。对于10Mbps速率下,TX_ER不起作用;
RX_ER(Receive Error): 接收数据错误提示信号,同步于RX_CLK,高电平有效,表示RX_ER有效期内传输的数据无效。对于10Mbps速率下,RX_ER不起作用;
TX_EN(Transmit Enable): 发送使能信号,只有在TX_EN有效期内传的数据才有效;
RX_DV(Reveive Data Valid): 接收数据有效信号,作用类型于发送通道的TX_EN;
TX_CLK:发送参考时钟,100Mbps速率下,时钟频率为25MHz,10Mbps速率下,时钟频率为2.5MHz,注意,TX_CLK时钟的方向是从PHY侧指向MAC侧的,因此此时钟是由PHY提供的;
RX_CLK:接收数据参考时钟,100Mbps速率下,时钟频率为25MHz,10Mbps速率下,时钟频率为2.5MHz。RX_CLK也是由PHY侧提供的;
CRS:Carrier Sense,载波侦测信号,不需要同步于参考时钟,只要有数据传输,CRS就有效,另外,CRS只有PHY在半双工模式下有效;
COL:Collision Detectd,冲突检测信号,不需要同步于参考时钟,只有PHY在半双工模式下有效。
GMII接口如图14-2所示。
图14-2 GMII接口
与MII接口相比,GMII的数据宽度由4位变为8位,GMII接口中的控制信号如TX_ER、TX_EN、RX_ER、RX_DV、CRS和COL的作用同MII接口中的一样,发送参考时钟GTX_CLK和接收参考时钟RX_CLK的频率均为125MHz(1000Mbps/8=125MHz)。
在这里有一点需要特别说明下,那就是发送参考时钟GTX_CLK,它和MII接口中的TX_CLK是不同的,MII接口中的TX_CLK是由PHY芯片提供给MAC芯片的,而GMII接口中的GTX_CLK是由MAC芯片提供给PHY芯片的。两者方向不一样。
在实际应用中,绝大多数GMII接口都是兼容MII接口的,所以,一般的GMII接口都有两个发送参考时钟:TX_CLK和GTX_CLK(两者的方向是不一样的,前面已经说过了),在用作MII模式时,使用TX_CLK和8根数据线中的4根。
RGMII接口如图14-3所示。
图14-3 RGMII接口
RGMII即Reduced GMII,是GMII的简化版本,将接口信号线数量从24根减少到14根(COL/CRS端口状态指示信号,这里没有画出),时钟频率仍旧为125MHz,TX/RX数据宽度从8为变为4位,为了保持1000Mbps的传输速率不变,RGMII接口在时钟的上升沿和下降沿都采样数据。在参考时钟的上升沿发送GMII接口中的TXD[3:0]/RXD[3:0],在参考时钟的下降沿发送GMII接口中的TXD[7:4]/RXD[7:4]。RGMI同时也兼容100Mbps和10Mbps两种速率,此时参考时钟速率分别为25MHz和2.5MHz。
TX_EN信号线上传送TX_EN和TX_ER两种信息,在TX_CLK的上升沿发送TX_EN,下降沿发送TX_ER;同样的,RX_DV信号线上也传送RX_DV和RX_ER两种信息,在RX_CLK的上升沿发送RX_DV,下降沿发送RX_ER。
14.3.2 硬件设计
图14-4 原理图RTL8211E-VB部分
实验板上采用RTL8211E-VB芯片组成千兆以太网模块,原理图如图14-4所示。PHY芯片通过接收和发送两组信号与FPGA相连接。接收组信号前缀为RG0_RX,发送组信号前缀为RG0TX,分别有控制信号CTL,时钟信号CK和四个年数据信号3-0组成。RG0_LED0和RG0_LED1分别连接网口黄色信号灯和绿色信号灯。同时,FPGA可以通过时钟线NPHY_MDC和数据线NPHY_MDIO对PHY芯片进行配置。
14.3.3 程序介绍
- 回环测试设计(test1)
第一步:程序介绍
回环测试很简单,只需要将输入的数据直接输出即可。
module test1 (
input rst, input rxc, input rxdv, input [3:0] rxd, output txc, output txen, output [3:0] txd, );
assign txd = rxd; assign txen = rxdv; assign txc = rxc; endmodule |
(注意:本次实验中每个程序都含有一个smi_ctrl模块,在文件夹config中,是对PHY芯片的设置模块,以便于解决有些电脑不能正常连接网口的问题,暂时不做详细解释)
第二步:分配管脚
管脚分配如表14-1所示。
表14-1 以太网实验管脚映射表
程序信号名 | 网络标号 | FPGA管脚 | 端口说明 |
rxc | RGMII_RXCK | B12 | 输入数据时钟 |
rxdv | RGMII_RXCTL | A13 | 输入数据控制信号 |
rxd[3] | RGMII_RX3 | A15 | 输入数据第3位 |
rxd[2] | RGMII_RX2 | B14 | 输入数据第2位 |
rxd[1] | RGMII_RX1 | A14 | 输入数据第1位 |
rxd[0] | RGMII_RX0 | B13 | 输入数据第0位 |
txc | RGMII_TXCK | B20 | 输出数据时钟 |
txen | RGMII_TXCTL | A19 | 输出数据控制信号 |
txd[3] | RGMII_TX3 | B18 | 输出数据第3位 |
txd[2] | RGMII_TX2 | A18 | 输出数据第2位 |
txd[1] | RGMII_TX1 | B17 | 输出数据第1位 |
txd[0] | RGMII_TX0 | A17 | 输出数据第0位 |
e_mdc | NPHY_MDC | C17 | 配置时钟 |
e_mdio | NPHY_MDIO | B19 | 配置数据 |
验证之前(默认自己的PC网卡是千兆网卡,否则请自行更换),需要首先确认自己的PC端MAC地址,如果不知道,就在DOS命令窗口,用ipconfig -all命令查看一下,以本机为例,如图14-5所示。
图14-5 PC端MAC地址信息
为方便后续实验,我们需要给PC端一个固定的IP地址,以本次实验为例,配置为192.168.0.3(可以修改,但必须保证与实验的发送模块目标IP地址一致),如图14-6所示:
图14-6 配置PC端IP地址
由于本次试验没有ARP协议内容(绑定开发板IP地址和MAC地址),因此需要通过DOS命令窗口人为绑定,本次试验将IP设为192.168.0.2,MAC地址设为00-0A-35-01-FE-C0,(可以喜好自行更换)如图14-7,方法如下:(注意:以管理员身份运行DOS命令窗口)
运行命令:ARP -s 192.168.0.2 00-0A-35-01-FE-C0
查看绑定结果:arp -a
图14-7 地址绑定方法1
如果运行ARP命令时出现了失败,可以换一种方法绑定,如图14-8:
- 输入netsh i i show in 命令查看本地连接的编号,如本次使用的电脑的“5”
- 输入netsh -c “i i” add neighbors 5(编号) “192.168.0.2” “00-0A-35-01-FE-C0”
- 输入 arp -a查看绑定结果
图14-8 地址绑定方法2
准备就绪后,我们同样用DOS命令窗口进行连通检测,如图14-9所示。Ping 是Windows系列自带的一个可执行命令。利用它可以检查网络是否能够连通,用好它可以很好地帮助我们分析判定网络故障。应用格式:Ping IP地址。(除本机意外的任意地址)
图14-9 发送数据
打开SignalTap,发送完命令后,如图14-10所示,我们可以看到数据正常,硬件完好。
图14-10 SignalTap II数据捕捉
- 特殊IP核配置(test2)
由于是RGMII接口,数据为双边沿4bit数据,因此在FPGA内部进行数据处理时,需要转成8bit数据,我们调用IP core(ddio_in)来实现,同时内部数据处理之后,再经由IP core(ddio_out)将8bit数据转成双滚边沿4bit数据传出,需要注意的是,考虑到使能信号与数据信号同步的问题,因此将使能信号一同输入ddio进行转换,具体设置如图如图14-11,14-12所示。
图14-11 ddio_in设置
图14-12 ddio_out设置
考虑到PHY芯片提供的时钟的驱动能力比较差,所以经过锁相环处理,与以往不同的是,输入时钟rxc选择同源输入,如图14-13所示,输出C0时钟ddio_clk作为两个ddio IP核的驱动时钟,如图14-14所示,输出 C1时钟txc作为数据的发送时钟(需要注意的是由于硬件电路原因以及时序原因,这里需要将txc做90°的相位差)如图14-15所示。
图14-13 PLL输入时钟设置
图14-14 PLL输出时钟(c0)设置
图14-15 PLL输出时钟(c1)设置
将三个IP核实例化到之前的回环测试中,进行数据传输正确性测试,(需要注意的是有序时序原因,需要将ddio_out输入数据相颠倒,具体参考工程文件(test2))。这次我们采用一个网络调试助手小程序作为辅助测试工具。下板,验证。
如图14-16所示,在设置好正确的地址和数据类型后,我们通过上位机发送检测信息(love you!)。通过Wireshark进行数据包的抓取,如图14-17所示,可以看到,数据正确的传输回PC端。
图4-16 上位机发送测试数据
图14-17 PC端数据正确接收
- 完整以太网数据传输设计
完整的以太网数据传输,需要有数据的接收部分,以及数据的发送部分,为方便实验,我们将PC端发送的数据首先存到ram里,再经由发送端读取后发送给PC端,中间的一系列数据解包和打包参考工程文件“ethernet”。下面对每一个模块进行简单的介绍。
- 数据接收模块(ip_receive)
这个模块所要解决的问题就是对数据帧进行检测识别,将有效的数据帧进行解包,并将真正的数据存入ram中。
always @ (posedge clk) begin
if (clr) begin rx_state <= idle; data_receive <= 1’b0; end else case (rx_state)
idle : begin valid_ip_P <= 1’b0; byte_counter <= 3’d0; data_counter <= 10’d0; mydata <= 32’d0; state_counter <= 5’d0; data_o_valid <= 1’b0; ram_wr_addr <= 0; if (e_rxdv == 1’b1) begin if (datain[7:0] == 8’h55) begin //接收到第一个55// rx_state <= six_55; mydata <= {mydata[23:0], datain[7:0]}; end else rx_state <= idle; end end
six_55 : begin //接收6个0x55// if ((datain[7:0] == 8’h55) && (e_rxdv == 1’b1)) begin if (state_counter == 5) begin state_counter <= 0; rx_state <= spd_d5; end else state_counter <= state_counter + 1’b1; end else rx_state <= idle; end
spd_d5 : begin //接收1个0xd5// if ((datain[7:0] == 8’hd5) && (e_rxdv == 1’b1)) rx_state <= rx_mac; else rx_state <= idle; end
rx_mac : begin //接收目标mac address和源mac address if (e_rxdv == 1’b1) begin if (state_counter < 5’d11) begin mymac <= {mymac[87:0], datain}; state_counter <= state_counter + 1’b1; end else begin board_mac <= mymac[87:40]; pc_mac <= {mymac[39:0], datain}; state_counter <= 5’d0; if((mymac[87:72] == 16’h000a) && (mymac[71:56] == 16’h3501) && (mymac[55:40] == 16’hfec0)) //判断目标MAC Address是否为本FPGA rx_state <= rx_IP_Protocol; else rx_state <= idle; end end else rx_state <= idle; end
rx_IP_Protocol : begin //接收2个字节的IP TYPE if (e_rxdv == 1’b1) begin if (state_counter < 5’d1) begin myIP_Prtcl <= {myIP_Prtcl[7:0], datain[7:0]}; state_counter <= state_counter+1’b1; end else begin IP_Prtcl <= {myIP_Prtcl[7:0],datain[7:0]}; valid_ip_P <= 1’b1; state_counter <= 5’d0; rx_state <= rx_IP_layer; end end else rx_state <= idle; end
rx_IP_layer : begin //接收20字节的udp虚拟包头,ip address valid_ip_P <= 1’b0; if (e_rxdv == 1’b1) begin if (state_counter < 5’d19) begin myIP_layer <= {myIP_layer[151:0], datain[7:0]}; state_counter <= state_counter + 1’b1; end else begin IP_layer <= {myIP_layer[151:0], datain[7:0]}; state_counter <= 5’d0; rx_state <= rx_UDP_layer; end end else rx_state <= idle; end
rx_UDP_layer : begin //接受8字节UDP的端口号及UDP数据包长 rx_total_length <= IP_layer[143:128]; pc_IP <= IP_layer[63:32]; board_IP <= IP_layer[31:0]; if (e_rxdv == 1’b1) begin if (state_counter < 5’d7) begin myUDP_layer <= {myUDP_layer[55:0], datain[7:0]}; state_counter <= state_counter + 1’b1; end else begin UDP_layer <= {myUDP_layer[55:0], datain[7:0]}; rx_data_length <= myUDP_layer[23:8]; //UDP数据包的长度 state_counter <= 5’d0; rx_state <= rx_data; end end else rx_state <= idle; end
rx_data : begin //接收UDP的数据 if (e_rxdv == 1’b1) begin if (data_counter == rx_data_length-9) begin //存最后的数据 data_counter <= 0; rx_state <= rx_finish; ram_wr_addr <= ram_wr_addr + 1’b1; data_o_valid <= 1’b1; //写RAM if (byte_counter == 3’d3) begin data_o <= {mydata[23:0], datain[7:0]}; byte_counter <= 0; end else if (byte_counter==3’d2) begin data_o <= {mydata[15:0], datain[7:0],8’h00}; //不满32bit,补0 byte_counter <= 0; end else if (byte_counter==3’d1) begin data_o <= {mydata[7:0], datain[7:0], 16’h0000}; //不满32bit,补0 byte_counter <= 0; end else if (byte_counter==3’d0) begin data_o <= {datain[7:0], 24’h000000}; //不满32bit,补0 byte_counter <= 0; end end else begin data_counter <= data_counter + 1’b1; if (byte_counter < 3’d3) begin mydata <= {mydata[23:0], datain[7:0]}; byte_counter <= byte_counter + 1’b1; data_o_valid <= 1’b0; end else begin data_o <= {mydata[23:0], datain[7:0]}; byte_counter <= 3’d0; data_o_valid <= 1’b1; //接受4bytes数据,写ram请求 ram_wr_addr <= ram_wr_addr+1’b1; end end end else rx_state <= idle; end
rx_finish : begin data_o_valid <= 1’b0; //added for receive test// data_receive <= 1’b1; rx_state <= idle; end default : rx_state <= idle; endcase end |
接收模块,就是将接收到的数据进行一步步解析。
idle状态:如果接收到55,则跳转到six_55状态。
six_55状态:如果继续收到连续六个55,则跳转到spd_d5状态,否则返回idle状态。
spd_d5状态:如果接继续接收到d5,则证明收到了完整的数据包前导码 55_55_55_55_55_55_55_d5,并跳转到rx_mac转态,否则返回idle转态。
rx_mac状态:这一部分是对目标MAC地址和源MAC地址的判断,如果匹配则跳转到rx_IP_Protocol状态,否则返回idle状态重新发送。
rx_IP_Protocol状态:判断数据包的类型和长度,跳转到rx_IP_layer状态。
rx_IP_layer状态:接收20字节的udp虚拟包头以及IP地址,跳转到rx_UDP_layer状态
rx_UDP_layer状态:接收8字节UDP的端口号及UDP数据包长,跳转到rx_data状态
rx_data状态:接收UDP的数据,跳转到rx_finish状态,
rx_finish状态:一包数据接收完成,跳转到idle状态等待下一包数据的到来。
- 数据发送模块(ip_send)
这个模块的主要内容是将ram中的数据读出,并以正确的数据包协议类型(UDP)将数据打包并发送出去。在发送之前,还要进行数据的校验,用CRC校验。
initial begin
tx_state <= idle; //定义IP 包头 preamble[0] <= 8’h55; //7个前导码55,一个帧开始符d5 preamble[1] <= 8’h55; preamble[2] <= 8’h55; preamble[3] <= 8’h55; preamble[4] <= 8’h55; preamble[5] <= 8’h55; preamble[6] <= 8’h55; preamble[7] <= 8’hD5;
mac_addr[0] <= 8’hB4; //目的MAC地址 ff-ff-ff-ff-ff-ff, 全ff为广播包 mac_addr[1] <= 8’h2E; //目的MAC地址 B4-2E-99-20-C4-61, 为本次实验使用的PC端地址,调试阶段根据自己PC端的值改变内容 mac_addr[2] <= 8’h99; mac_addr[3] <= 8’h20; mac_addr[4] <= 8’hC4; mac_addr[5] <= 8’h61;
mac_addr[6] <= 8’h00; //源MAC地址 00-0A-35-01-FE-C0 mac_addr[7] <= 8’h0A; //根据自己的需求修改为指定的值 mac_addr[8] <= 8’h35; mac_addr[9] <= 8’h01; mac_addr[10]<= 8’hFE; mac_addr[11]<= 8’hC0;
mac_addr[12]<= 8’h08; //0800: IP包类型 mac_addr[13]<= 8’h00;
i<=0; end |
这一部分定义了数据包的前导码,PC端MAC地址,开发板MAC地址以及IP包类型,需要注意的是在实际实验中,PC端MAC地址需要根据自己的电脑进行修改,PC端MAC地址需要根据自己绑定的地址进行修改,但前提是都不洗保证一致性,否则后续实验会接收不到数据。
always @ (posedge clk) begin
case (tx_state) idle : begin e_txen <= 1’b0; crcen <= 1’b0; crcre <= 1; j <= 0; dataout <= 0; ram_rd_addr <= 1; tx_data_counter <= 0; if (time_counter == 32’h04000000) begin //等待延迟, 每隔一段时间发送一个数据包 tx_state <= start; time_counter <= 0; end else time_counter <= time_counter + 1’b1; end
start : begin //IP header ip_header[0] <= {16’h4500, tx_total_length}; //版本号:4; 包头长度:20;IP包总长 ip_header[1][31:16] <= ip_header[1][31:16]+1’b1; //包序列号 ip_header[1][15:0] <= 16’h4000; //Fragment offset ip_header[2] <= 32’h80110000; //mema[2][15:0] 协议:17(UDP) ip_header[3] <= 32’hc0a80002; //192.168.0.2源地址 ip_header[4] <= 32’hc0a80003; //192.168.0.3目的地址广播地址 ip_header[5] <= 32’h1f901f90; //2个字节的源端口号和2个字节的目的端口号 ip_header[6] <= {tx_data_length, 16’h0000}; //2个字节的数据长度和2个字节的校验和(无) tx_state <= make; end
make : begin //生成包头的校验和 if (i == 0) begin check_buffer <= ip_header[0][15:0] + ip_header[0][31:16] + ip_header[1][15:0] + ip_header[1][31:16] + ip_header[2][15:0] + ip_header[2][31:16] + ip_header[3][15:0] + ip_header[3][31:16] + ip_header[4][15:0] + ip_header[4][31:16]; i <= i + 1’b1; end else if(i == 1) begin check_buffer[15:0] <= check_buffer[31:16] + check_buffer[15:0]; i <= i+1’b1; end else begin ip_header[2][15:0] <= ~check_buffer[15:0]; //header checksum i <= 0; tx_state <= send55; end end
send55 : begin //发送8个IP前导码:7个55, 1个d5 e_txen <= 1’b1; //GMII数据发送有效 crcre <= 1’b1; //reset crc if(i == 7) begin dataout[7:0] <= preamble[i][7:0]; i <= 0; tx_state <= sendmac; end else begin dataout[7:0] <= preamble[i][7:0]; i <= i + 1’b1; end end
sendmac : begin //发送目标MAC address和源MAC address和IP包类型 crcen <= 1’b1; //crc校验使能,crc32数据校验从目标MAC开始 crcre <= 1’b0; if (i == 13) begin dataout[7:0] <= mac_addr[i][7:0]; i <= 0; tx_state <= sendheader; end else begin dataout[7:0] <= mac_addr[i][7:0]; i <= i + 1’b1; end end
sendheader : begin //发送7个32bit的IP 包头 datain_reg <= datain; //准备需要发送的数据 if(j == 6) begin if(i == 0) begin dataout[7:0] <= ip_header[j][31:24]; i <= i + 1’b1; end else if(i == 1) begin dataout[7:0] <= ip_header[j][23:16]; i <= i + 1’b1; end else if(i == 2) begin dataout[7:0] <= ip_header[j][15:8]; i <= i + 1’b1; end else if(i == 3) begin dataout[7:0] <= ip_header[j][7:0]; i <= 0; j <= 0; tx_state <= senddata; end end else begin if(i == 0) begin dataout[7:0] <= ip_header[j][31:24]; i <= i + 1’b1; end else if(i == 1) begin dataout[7:0] <= ip_header[j][23:16]; i <= i + 1’b1; end else if(i == 2) begin dataout[7:0] <= ip_header[j][15:8]; i <= i + 1’b1; end else if(i == 3) begin dataout[7:0] <= ip_header[j][7:0]; i <= 0; j <= j + 1’b1; end end end
senddata : begin //发送UDP数据包 if(tx_data_counter == tx_data_length – 9) begin //发送最后的数据 tx_state <= sendcrc; if (i == 0) begin dataout[7:0] <= datain_reg[31:24]; i <= 0; end else if (i == 1) begin dataout[7:0] <= datain_reg[23:16]; i <= 0; end else if (i == 2) begin dataout[7:0] <= datain_reg[15:8]; i <= 0; end else if (i == 3) begin dataout[7:0] <= datain_reg[7:0]; datain_reg <= datain; //准备数据 i <= 0; end end else begin //发送其它的数据包 tx_data_counter <= tx_data_counter+1’b1; if (i == 0) begin dataout[7:0] <= datain_reg[31:24]; i <= i + 1’b1; ram_rd_addr <= ram_rd_addr + 1’b1; //RAM地址加1, 提前让RAM输出数据 end else if (i == 1) begin dataout[7:0] <= datain_reg[23:16]; i <= i + 1’b1; end else if (i == 2) begin dataout[7:0] <= datain_reg[15:8]; i <= i + 1’b1; end else if (i == 3) begin dataout[7:0] <= datain_reg[7:0]; datain_reg <= datain; //准备数据 i <= 0; end end end
sendcrc : begin //发送32位的crc校验 crcen <= 1’b0; if (i == 0) begin dataout[7:0] <= {~crc[24], ~crc[25], ~crc[26], ~crc[27], ~crc[28], ~crc[29], ~crc[30], ~crc[31]}; i <= i + 1’b1; end else begin if (i == 1) begin dataout[7:0] <= {~crc[16], ~crc[17], ~crc[18], ~crc[19], ~crc[20], ~crc[21], ~crc[22], ~crc[23]}; i <= i + 1’b1; end else if (i == 2) begin dataout[7:0] <= {~crc[8], ~crc[9], ~crc[10], ~crc[11], ~crc[12], ~crc[13], ~crc[14], ~crc[15]}; i <= i + 1’b1; end else if (i == 3) begin dataout[7:0] <= {~crc[0], ~crc[1], ~crc[2], ~crc[3], ~crc[4], ~crc[5], ~crc[6], ~crc[7]}; i <= 0; tx_state <= idle; end end end
default : tx_state <= idle; endcase end |
idle状态:等待延时,每隔一段时间发送一个数据包,跳转到start状态。
start状态:发送数据包头,跳转到make状态。
make状态:生成包头的校验和,跳转到send55状态。
send55状态:发送8个前导码,跳转到sendmac状态。
sendmac状态:发送目标MAC地址,源MAC地址和IP包类,跳转到sendheader状态。
sendheader状态:发送7个32bit的IP 包头,跳转到senddata状态。
senddata状态:发送UDP数据包,跳转到sendcrc状态。
sendcrc状态:发送32位的crc校验,返回idle状态。
这样,整个一包数据发送结束,返回idle状态等待下一包数据的发送。
- CRC校验模块(crc)
一个IP数据包的CRC32校验是在目标MAC Address开始计算的,一直计算到一个包的最后一个数据为止。以太网的CRC32的verilog的算法和多项式可以在以下网站中直接生成:http://www.easics.com/webtools/crctool
- UDP数据测试模块(UDP)
这个模块只需要将前三个子模块实例化到一起即可,注意每个连接的正确性。
- 顶层模块设置(ethernet)
将PLL,ddio_in,ddio_out,ram以及UDP模块实例化到顶层,并在ram中提前存入特定的信息(Welcome To ZGZNXP World!),在没有数据输入的时候,FPGA一直发送此信息,有数据输入后,发送接收到的数据。具体实现参考工程文件。
14.4 实验验证
管脚分配测试程序test1一致。
下板验证之前,需要注意的是必须将自己的PC端IP地址和开发板MAC地址确定好,并对应起来,否则将收不到数据。
将编译好的工程下载到开发板中,可以看到,如图14-18所示,FPGA一直在向PC端发送信息。在Wireshark也可以看到整个发送的数据包,如图14-19所示。
图14-18 发送特定信息
图14-19 特定信息数据包
当PC 端向FPGA发送数据时,如图14-20所示,可以看到在一整个数据包到达FPGA,并且紧接着FPGA将接收到的数据重新打包后发送给PC端。如图14-21所示,同时网络助手也同时准确接收到发送数据信息,如图14-22所示。同样的通过SignalTap我们可以看到接受到的数据的写入过程,如图14-23所示。
图14-20 PC发送数据包
图14-21 FPGA将接收到的数据重新打包发送给PC端
图14-22 FPGA发送给PC端接受到的信息
图14-23 FPGA结束数据并存入ram过程
需要注意的是, 以太网EthernetII规定,以太网帧数据域部分最小为46字节,也就是以太网帧最小是6+6+2+46+4=64。除去4个字节的FCS,因此,抓包时就是60字节。当数据字段的长度小于46字节时,MAC子层就会在数据字段的后面填充以满足数据帧长不小于64字节。在用UDP局域网通信时,经常发生“Hello World”来进行测试,但是“Hello World”并不满足最小有效数据(64-46)的要求,为什么小于18个字节,对方仍然可用收到呢?因为在链路层的MAC子层中会进行数据补齐,不足18个字节的用0补齐。但当服务器在公网,客户端在内网,发生小于18个字节的数据,就会出现接收端收不到数据的情况。因此如果出现接收不到的情况,将发送的信息增加到18个字节以上即可。
实验十五 SRAM读写实验
15.1 实验目的
- 掌握SRAM的读写
- 复习分频,按键消抖,进制转换实验内容
15.2 实验要求
- 通过按键控制对SRAM的读写功能
- 将写入到SRAM的数据再读出来,并显示在数码管上
- 在读数据的过程中,要求每次读操作有一定时间间隔
15.3 实验内容
15.3.1 SRAM简介
SRAM(Static Random-Access Memory)静态随机存取存储器,是随机存取存储的一种。所谓静态就是指只要保持通电状态,SRAM中的数据就会一直保持不变,但仍保留ram的特点,掉电后数据将丢失。
板载两片SRAM(IS61WV25616BLL),每片SRAM都有256*16个字的存储空间,每个字16bit,最高读写速度可达到100M。实物图如图15-1所示。
图15-1 SRAM实物图
15.3.2 硬件设计
如图15-2所示,一组控制信号(低有效)片选信号CE,读控制信号OE,写使控制号WE,以及两个字节控制信号UB和LB,通过CE_N_SRAM,OE_N_SRAM,WE_N_SRAM,UB_N_SRAM,LB_N_SRAM与FPGA相连,由FPGA 控制读写状态。将地址通过地址线A[17:0]送到SRAM中,在写状态下,将需要写的数据通过数据线D[15:0]送到SRAM中就可以写入到对应地址的寄存器中;在读状态下,对应地址寄存器中的数据可直接由数据线读到FPGA中。
图15-2 原理图SRAM部分
15.3.3 程序介绍
本次实验会用到分频,按键消抖,进制转换和数码管显示模块,参考之前的实验,治理主要介绍SRAM读写模块。
第一步:主程序框架的建立
module sram (
input IN_CLK_50M, //板载系统时钟 input [7:1] PB, //板载按键
output sram0_cs_n, //第一片sram控制信号组 output sram0_we_n, output sram0_oe_n, output sram0_ub_n, output sram0_lb_n,
output sram1_cs_n, //第二篇sram控制信号组 output sram1_we_n, output sram1_oe_n, output sram1_ub_n, output sram1_lb_n,
output [17:0] sram_addr, //sram地址信号 inout [31:0] sram_data, //sram数据信号
output [5:0] tube_sel, //数码管信号控制组 output [7:0] tube_seg ); endmodule |
输入有50M系统时钟IN_CLK_50M,按键模块PB[7:1],用PB[3](RETURN)作为外部硬件复位,PB[2](UP)作为写控制,PB[7](DOWN)作为读控制;输出有两组控制信号分别控制两片sram,具体为片选信号sram_cs_n,写控制信号sram_we_n,读控制信号sram_oe_n,以及字节控制信号sram_ub_n和sram_lb_n;地址总线sram_daddr[17:0],数据总线sram_data[31:0],以及数码管位选信号tube_sel[5:0]和段选信号tube_seg[7:0]。
第二步:sram读写模块
本次实验我们将两片sram同时使用,扩展为32bit位宽的数据存储器。
reg [31:0] wr_data;
reg wr_en; reg [3:0] state; reg [7:1] PB_flag; reg wr_done; reg rd_done; reg s_flag; assign sram_data = wr_en ? wr_data : 32’hz; always @ (posedge clk) begin if (!rst_n) begin wr_done <= 1’b0; rd_done <= 1’b0; rd_data <= 32’d0; wr_data <= 32’d0;
sram0_cs_n <= 1’b1; sram0_we_n <= 1’b1; sram0_oe_n <= 1’b1; sram0_ub_n <= 1’b1; sram0_lb_n <= 1’b1;
sram1_cs_n <= 1’b1; sram1_we_n <= 1’b1; sram1_oe_n <= 1’b1; sram1_ub_n <= 1’b1; sram1_lb_n <= 1’b1; sram_addr <= 18’d0;
wr_en <= 1’b0; state <= 4’d0; end else case(state) 0 : begin wr_done <= 1’b0; rd_done <= 1’b0;
sram_addr <= 18’d511; wr_data <= 32’d123456;
if (PB_flag[2]) begin wr_en <= 1’b1; state <= 4’d1;
sram0_cs_n <= 1’b0; sram0_we_n <= 1’b0; sram0_oe_n <= 1’b1; sram0_ub_n <= 1’b0; sram0_lb_n <= 1’b0;
sram1_cs_n <= 1’b0; sram1_we_n <= 1’b0; sram1_oe_n <= 1’b1; sram1_ub_n <= 1’b0; sram1_lb_n <= 1’b0; end
else if (PB_flag[7]) begin wr_en <= 1’b0; state <= 4’d2;
sram0_cs_n <= 1’b0; sram0_we_n <= 1’b1; sram0_oe_n <= 1’b0; sram0_ub_n <= 1’b0; sram0_lb_n <= 1’b0;
sram1_cs_n <= 1’b0; sram1_we_n <= 1’b1; sram1_oe_n <= 1’b0; sram1_ub_n <= 1’b0; sram1_lb_n <= 1’b0; end
else state <= 4’d0; end
1 : begin if (sram_addr == 18’d0) begin state <= 4’d4; wr_done <= 1’b1; wr_en <= 1’b0; end else begin state <= 4’d1; sram_addr <= sram_addr – 1’b1; wr_data <= wr_data – 1’b1; end end
2 : begin if (sram_addr == 18’d0) state <= 4’d4; else state <= 4’d2; if (s_flag) begin sram_addr <= sram_addr – 1’b1; rd_data <= sram_data; rd_done <= 1’b1; end else rd_done <= 1’b0; end
4 : begin sram0_cs_n <= 1’b1; sram0_we_n <= 1’b1; sram0_oe_n <= 1’b1; sram0_ub_n <= 1’b1; sram0_lb_n <= 1’b1;
sram1_cs_n <= 1’b1; sram1_we_n <= 1’b1; sram1_oe_n <= 1’b1; sram1_ub_n <= 1’b1; sram1_lb_n <= 1’b1;
wr_done <= 1’b0; rd_done <= 1’b0;
state <= 0; end
default : state <= 0; endcase end |
在写状态下,将写使能wr_en拉高,此时sram_data为需要写入的数据wr_data,在其他情况下写使能拉低,在读状态下数据直接由sram_data读到FPGA中。
复位时,sram控制信号全部拉高,sram处于未选通状态,然后跳转到0状态,通过状态机完成数据的读写。
0状态:给定一个初始地址511,一个初始数据123456,当写使能信号PB_flag[2]有效时,片选信号拉低选中sram,写控制信号拉低准备进行写操作,读控制信号保持拉高状态,字节控制信号同时拉低,表示高、低两个字节数据同时进行写操作,并跳转到1状态。当读使能信号PB_flag[7]有效时,与写使能相反,将写控制信号保持拉高状态,读控制信号拉低准备进行读操作,并跳转到2状态。
1状态:从初始地址511开始,写入初始数据123456,每个时钟周期地址和数据同时减一,进行512个数据连续写操作,当寄存器地址位0时,结束写操作并跳转到4状态。
2状态:从初始地址511开始,地址在秒脉冲s_flag的控制下每1S减一,进行512个数据的连续读操作,当数据全部读取完毕,地址为0时跳转到4状态。
4状态:控制信号全部拉高,释放对SRAM的控制,并跳转到0状态,等待下一次操作。
15.4 实验验证
第一步:分配管脚
管脚分配入表15-1所示。
表15-1 SRAM读写实验管教映射表
第二步:下板验证
管脚分配完成后进行全编译,通过后进行下板验证
程序下板后,数码管会全部点亮,但因为没有读取出任何数据,所以数码管会显示全0,如图15-3所示。按下PB[2](UP)按键,将数据写入SRAM中,再按下PB[7](DOWN)按键将写入的数据读出来,此时数码管上显示123456,并每隔一秒钟减一,如图15-4所示。由此可以验证,我们成功的将规定数据写入到SRAM中并正确的读出。
图15-3 SRAN读写实验现象1
图15-3 SRAN读写实验现象2
实验十六 8978音频回环实验
16.1 实验目的
- 了解I2S(Inter-IC Sound)总线的相关知识,以及其工作原理
- 熟悉WM8978的工作模式.并且通过配置接口模式相关寄存器选择结合开发板,完成数据的传输与接收,并验证
16.2 实验要求
- 通过配置板载音频芯片WM8978进行音频回环测试,检查硬件是否正常工作
- 通过按键调节音量输出大小.
16.3 实验内容
16.3.1 WM8978简介
WM8978是由欧胜(Wolfson)推出的一个低功耗、高质量的立体声多媒体数字信号编译码器。它主要应用于便携式应用,比如数码照相机、可携式数码摄像机。该芯片具有高级的数字信号处理功能: 包含一个5路均衡功能,一个用于ADC和麦克风或者线路输入之间的混合信号的电平自动控制功能,一个纯粹的录音或者重放的数字限幅功能。另外在ADC的线路上提供了一个数字滤波的功能,可以更好的应用滤波,比如“减少风噪声”。
图16-1 WM8978内部结构总体框图
实验板原理图如下图所示:
图16-2 开发板Audio部分原理图
16.3.2 WM8978控制接口时序
WM8978控制接口有两线方式和三线方式.具体选择哪种方式由电路板WM8978的MODE引脚连接方式选择.mode引脚接低电平时为两线模式,接高电平时为3线模式.040开发板mode引脚接地.当控制接口为两线模式时,时序图如下16-3.时序图与IIC时序相同.WM8978的器件地址固定为7’b0011010 .此芯片寄存器只支持写入,不支持读取.
图16-3 两线模式接口时序图
16.3.3 I2S 音频总线协议
I2S(Inter-IC Sound Bus)仅仅是PCM的一个分支,接口定义都是一样的, I2S的采样频率一般为44.1KHZ和48KHZ做,PCM采样频率一般为8K,16K。有四组信号: 位时钟信号,同步信号,数据输入,数据输出.
I2S是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准。在飞利浦公司的I2S标准中,既规定了硬件接口规范,也规定了数字音频数据的格式。I2S有3个主要信号:串行时钟 SCLK,也叫做位时钟BCLK,即对应数字音频的每一位数据,SCLK的频率=2×采样频率×采样位数 。帧时钟LRCK,用于切换左右声道的数据,LRCK为“0”表示正在传输的是左声道的数据,为“1”表示正在传输的是右声道的数据。LRCLK == FS,就是采样频率
串行数据SDATA,就是用二进制补码表示的音频数据,有时为了使系统间能够更好的同步,还需要另外传输一个信号MCLK,称为主时钟,也叫系统时钟(System Clock),是采样频率的256或384倍
I2S协议时序如下图16-4所示。 I2S格式的信号无论有多少位数据,,数据的最高位总是出现在LRCK变化(也就是一帧开始)后的第2个BCLK脉冲处。这就使得接收端与发送端的有效位数可以不同。如果接收端能处理的有效位数少于发送端,可以放弃数据帧中多余的低位数据;如果接收端能处理的有效位数多于发送端,可以自行补足剩余的位。这种同步机制使得数字音频设备的互连更加方便,而且不会造成数据错。
图16-4 I2S协议时序
16.3.4 主要程序设计
- WM8978寄存器配置程序
此处只给出寄存器配置主程序的程序,完整程序请参考工程文件
module wm8978_config
( input clk_50m, output reg cfg_done=0, input rst_n, input rxd, output txd, input key1, input key2, output i2c_sclk, inout i2c_sdat ); wire tr_end; reg [4:0] i; //************************************ reg [23:0] i2c_data_r=0; wire [7:0] data_read ; reg [7:0] read_req ; reg uart_rd =0; reg uart_wr =0;
reg [7:0] txd_i2c_data; reg txd_start = 0; wire txd_busy; wire [7:0] rxd_i2c_data; wire rxd_ready; wire rxd_eop; wire test_pin; uart_transceiver uart_transceiver_inst ( .sys_clk (clk_50m), // 50m .uart_rx (rxd), .uart_tx (txd), .divisor (55), // 115200 * 8 .rx_data (rxd_i2c_data), .rx_done (rxd_ready), .rx_eop (rxd_eop), .tx_data (txd_i2c_data), .tx_wr (txd_start), .tx_done (), .tx_busy (txd_busy), .test_pin (), .sys_rst () ); reg rx_end_ack = 0; reg rx_end = 0; always @ (posedge clk_50m) if(rxd_eop) rx_end <= 1; else if(rx_end_ack) rx_end <= 0; reg [7:0] cmd_dir = 0; reg [3:0] uart_st = 0; always @ (posedge clk_50m) if(cfg_done == 0) begin rx_end_ack <= 0; uart_wr <= 0; uart_rd <= 0; uart_st <= 0; end else case(uart_st) 0: begin rx_end_ack <= 0; uart_wr <= 0; uart_rd <= 0; if(rxd_ready) begin cmd_dir <= rxd_i2c_data; uart_st <= 1; end end 1: begin if(rxd_ready) begin i2c_data_r[23:16] <= rxd_i2c_data; uart_st <= 2; end end 2: begin if(rxd_ready) begin i2c_data_r[15:08] <= rxd_i2c_data; if(cmd_dir[0]) uart_st <= 5; else uart_st <= 3; end end 3: // write begin if(rxd_ready) begin i2c_data_r[07:00] <= rxd_i2c_data; uart_wr <= 1; uart_st <= 4; end end 4: begin if(tr_end) begin uart_wr <= 0; uart_st <= 7; end end 5: //read begin uart_rd <= 1; uart_st <= 6; end 6: begin uart_rd <= 0; if(tr_end) begin txd_i2c_data <= data_read; txd_start <= 1; uart_st <= 7; end end 7: begin txd_start <= 0; if(rx_end) begin rx_end_ack <= 1; uart_st <= 0; end else rx_end_ack <= 0; end endcase //*************************************** reg start;
//parameter define reg [5:0] PHONE_VOLUME = 6’d32; reg [5:0] SPEAK_VOLUME = 6’d32; reg [31:0] i2c_data=0 ;
reg [7:0] start_init_cnt; reg [4:0] init_reg_cnt ; reg [25:0] on_counter; reg [25:0] off_counter; reg key_up, key_down; always @(posedge clk_50m , negedge cfg_done) if (!cfg_done) begin on_counter<=0; off_counter<=0; key_up<=1’b0; key_down<=1’b0; end else begin if (key1==1’b1) on_counter<=0; else if ((key1==1’b0)& (on_counter<=500000)) on_counter<=on_counter+1’b1;
if (on_counter==49950) key_up<=1’b1; else key_up<=1’b0;
if (key2==1’b1) off_counter<=0; else if ((key2==1’b0)& (off_counter<=500000)) off_counter<=off_counter+1’b1;
if (off_counter==49950) key_down<=1’b1; else key_down<=1’b0;
end always @(posedge clk_50m , negedge cfg_done) if (!cfg_done) begin PHONE_VOLUME <=6’d32 ; end else begin if (( 2<=PHONE_VOLUME )&( PHONE_VOLUME <=56)&(on_counter==49948)) PHONE_VOLUME <=PHONE_VOLUME+6 ; else if (( 8<=SPEAK_VOLUME )&( SPEAK_VOLUME <=62)& (off_counter==49948)) PHONE_VOLUME <=PHONE_VOLUME-6 ; else PHONE_VOLUME <=PHONE_VOLUME ; end always @ ( posedge clk_50m ) if( rst_n==1’b0 ) begin i <= 5’d0; read_req<=0 ; i2c_data <= 32’h000000; start <= 1’b0; cfg_done <=0; end else begin case( i ) 0: begin if( tr_end ) begin start <= 1’b00; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d0 ,9’b1}; end end
1: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <={7’h1a,1’b0, 8’h00,7’d1 ,9’b1_0010_1111}; end end
2: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d2 ,9’b1_1011_0011}; end end
3: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d3 ,9’b0_0110_1111}; end end
4: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d4 ,{2’d0,2’b11,5’b10000}}; end end
5: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <={7’h1a,1’b0,8’h00,7’d6 ,9’b0_0000_0001}; end end
6: begin if( tr_end ) begin start <= 2’b00; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data<= {7’h1a,1’b0,8’h00,7’d7 ,9’b0_0000_0001}; end end
7: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d10,9’b0_0000_1000}; end end
8: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d14,9’b1_0000_1000}; end end
9: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d43,9’b0_0001_0000}; end end
10: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <={7’h1a,1’b0,8’h00,7’d47,9’b0_0111_0000}; end end
11: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <={7’h1a,1’b0,8’h00,7’d48,9’b0_0111_0000}; end end
12: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <={7’h1a,1’b0,8’h00,7’d49,9’b0_0000_0110}; end end
13: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d50,9’b1 };end end
14: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d51,9’b1 };end end
15: begin if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end else begin start <= 1’b1; i2c_data <={7’h1a,1’b0,8’h00,7’d52,{3’b010,PHONE_VOLUME}};end end
16: begin if( tr_end ) begin start <= 1’b0; i <= i + 1; end else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d53,{3’b110,PHONE_VOLUME}};end end
17: begin cfg_done<=1 ; if (uart_wr) begin start <= 1’b1; i2c_data <={cmd_dir,i2c_data_r} ; i<=i+1 ; end if (uart_rd) begin read_req <= 1’b1; i2c_data <={cmd_dir,i2c_data_r} ; i<=i+2 ; end if (key_up|key_down) i<= 15;
end 18: begin if( tr_end ) begin start <= 1’b0; i <=19; end else i<=20; end
19: begin if( tr_end ) begin read_req <= 1’b0; i <= 19; end else begin i<=21;end end default:i<=1 ; endcase
end i2c_control i2c_control_inst ( .Clk (clk_50m), .Rst_n(rst_n), .wrreg_req(start) , .rdreg_req (read_req), .addr({i2c_data[15:8],i2c_data[23:16]}) , //16bit .addr_mode(0), .wrdata (i2c_data[7:0]), // .rddata (data_read), //8bit .device_id (i2c_data[31:24]), //8bit .RW_Done(tr_end), .ack (),
.i2c_sclk(i2c_sclk), .i2c_sdat(i2c_sdat) );
endmodule |
- 音频信号采集程序
module audio_receive (
//system clock 50MHz input rst_n , //wm8978 interface input aud_bclk , input aud_lrc , input aud_adcdat, //user interface output reg rx_done , output reg [31:0] adc_data ); parameter WL = 6’d32 ; reg aud_lrc_d0; reg [ 5:0] rx_cnt; reg [31:0] adc_data_t; wire lrc_edge ; assign lrc_edge = aud_lrc ^ aud_lrc_d0; always @(posedge aud_bclk or negedge rst_n) begin if(!rst_n) aud_lrc_d0 <= 1’b0; else aud_lrc_d0 <= aud_lrc; end always @(posedge aud_bclk or negedge rst_n) begin if(!rst_n) begin rx_cnt <= 6’d0; end else if(lrc_edge == 1’b1) rx_cnt <= 6’d0; else if(rx_cnt < 6’d35) rx_cnt <= rx_cnt + 1’b1; end always @(posedge aud_bclk or negedge rst_n) begin if(!rst_n) begin adc_data_t <= 32’b0; end else if(rx_cnt < WL) adc_data_t[WL – 1’d1 – rx_cnt] <= aud_adcdat; always @(posedge aud_bclk or negedge rst_n) begin if(!rst_n) begin rx_done <= 1’b0; adc_data <= 32’b0; end else if(rx_cnt == 6’d32) begin rx_done <= 1’b1; adc_data<= adc_data_t; end else rx_done <= 1’b0; end endmodule |
- 音频发送模块
module audio_send (
input rst_n ,
input aud_bclk , input aud_lrc , output reg aud_dacdat,
input [31:0] dac_data , output reg tx_done ); parameter WL = 6’d32 ; reg aud_lrc_d0; reg [ 5:0] tx_cnt; reg [31:0] dac_data_t; wire lrc_edge; assign lrc_edge = aud_lrc ^ aud_lrc_d0; always @(posedge aud_bclk or negedge rst_n) begin if(!rst_n) aud_lrc_d0 <= 1’b0; else aud_lrc_d0 <= aud_lrc; end always @(posedge aud_bclk or negedge rst_n) begin if(!rst_n) begin tx_cnt <= 6’d0; dac_data_t <= 32’d0; end else if(lrc_edge == 1’b1) begin tx_cnt <= 6’d0; dac_data_t <= dac_data; end else if(tx_cnt < 6’d35) tx_cnt <= tx_cnt + 1’b1; end always @(posedge aud_bclk or negedge rst_n) begin if(!rst_n) begin tx_done <= 1’b0; end else if(tx_cnt == 6’d32) tx_done <= 1’b1; else tx_done <= 1’b0; end always @(negedge aud_bclk or negedge rst_n) begin if(!rst_n) begin aud_dacdat <= 1’b0; end else if(tx_cnt < WL) aud_dacdat <= dac_data_t[WL – 1’d1 – tx_cnt]; else aud_dacdat <= 1’b0; end endmodule |
- 工程主程序
module audio_test(
input wire sys_clk_50, input wire rst_n, input rxd, output txd, output [7:0] led , input key1, input key2, inout wm_sdin, output wm_sclk, input wire wm_lrc, input wire wm_bclk, input wire adcdat, output wire dacdat, output wire mclk ); wire cfg_done ; assign led ={7’h7f,~cfg_done} ;
pll_50_12 pll_50_12_inst ( // Clock out ports .c0(clk_out_12), // output clk_out_12 // Status and control signals .areset(~rst_n), // input reset .locked(locked), // output locked // Clock in ports .inclk0(sys_clk_50)); // input sys_clk_50 wire clk_out_12 ; assign mclk = clk_out_12 ; wm8978_config wm8978_config_inst ( .key1 (key1), .key2 (key2), .clk_50m (sys_clk_50 ) , .rst_n (rst_n) , .cfg_done (cfg_done) , .i2c_sclk (wm_sclk) , .rxd (rxd), .txd (txd), .i2c_sdat (wm_sdin) ); wire [31:0] adc_data ; audio_receive audio_receive_inst( .rst_n (rst_n), .aud_bclk (wm_bclk), .aud_lrc (wm_lrc), .aud_adcdat (adcdat), .adc_data (adc_data), .rx_done (rx_done) ); audio_send audio_send_inst( .rst_n (rst_n), .aud_bclk (wm_bclk), .aud_lrc (wm_lrc), .aud_dacdat (dacdat),
.dac_data (adc_data), .tx_done (tx_done) ); endmodule |
16.4 实验验证
- 管脚分配
表16.1 管脚分配
程序信号名 | 端口信号说明 | 网络标号 | FPGA管脚 |
Sys_clk_50 | 系统50M时钟 | C10_50MCLK | G21 |
Reset_n | 系统复位信号 | KEY1 | Y4 |
Wm_sdin | 8978寄存器配置数据线 | I2C_SDA | C13 |
Wm_sclk | 8978寄存器配置时钟 | I2C_SCL | D13 |
Wm_lrc | 8978对齐时钟 | WM_LRCK | AB19 |
Wm_bclk | 8978位时钟 | WM_BCLK | AA19 |
adcdat | 8978的adc输入 | WM_MISO | AA18 |
Dacdat | 8978的dac输入 | WM_MOSI | Y17 |
Mack | PLL提供给8978
工作主时钟 |
WM_MCLK | W17 |
Key1 | 音量加按键 | Key2 | V5 |
Key2 | 音量减按键 | Key7 | AB3 |
txd | 串口发送 | TTL_RX | E16 |
rxd | 串口接收 | TTL_TX | F15 |
- 程序下板验证
如下图16-5 ,程序烧录入FPGA开发板后,用双公头音频线,一端插入红色音频接收端,另一端插入音乐播放器。耳机插入绿色音频播放口。即可听到播放机播放音乐。音量分为5个挡位,按UP键可以将音量调大,按down键可以将音量调低。
图16-5 wm8978音频下板验证图
实验十七 OV5640摄像头的照相显示实验
17.1 实验目的
- 了解OV5640摄像头的上电时序,以及输出不同分辨率的图像时,相对应寄存器配置过程
- 复习前边所学IIC总线的相关知识
- 复习前边所学HDMI相关知识
17.2 实验要求
- 阅读OV5640 datasheet的上电时序,依据OV5640外设模块原理图,正确编写5640的上电控制程序。
- 依据SCCB接口时序要求正确编写OV5640摄像头在分辨率在640X480下的配置程序
- 依据上个实验知识。编写程序将5640采集的图象数据真确存在开发板SRAM中。
- 编写程序将存储在SRAM中的图像通过HDMI显示到显示器。
- 图像的刷新通过按键控制,每按一次屏幕显示图像更新一次,类似于照相机。
17.3 实验内容
下面给出部分主要程序,完整程序请参考对应工程
- 0v5640上电初始化程序设计依据5640在内接DVDD时的上电时序图17-1
图17-1 5640上电时序图
上电程序如下:
module power_on_delay(clk_50M,reset_n_r,camera_pwup,initial_en,cam_resetb);
input clk_50M; input reset_n_r; output camera_pwup; output initial_en; (*mark_debug=”true”*)output reg cam_resetb =0; (*mark_debug=”true”*)reg [31:0]cnt1=0; reg initial_en=0; reg camera_pwup_reg=0; reg reset_n =0; assign camera_pwup=camera_pwup_reg; always @ (posedge clk_50M) reset_n<=reset_n_r ; //5ms, delay from sensor power up stable to Pwdn pull down always@(posedge clk_50M) begin if(reset_n==1’b0) cnt1<=0; else begin if (cnt1<50000000) cnt1<=cnt1+1 ; else cnt1<=cnt1 ; end end always@(posedge clk_50M) begin if(reset_n==1’b0) begin camera_pwup_reg<=0;
end else begin if (cnt1==15000000) camera_pwup_reg<=1;
else camera_pwup_reg<=camera_pwup_reg; end end always@(posedge clk_50M) begin if(reset_n==1’b0) begin cam_resetb <=0; end else begin if (cnt1==35000000) cam_resetb <=1; else cam_resetb <=cam_resetb ; end end always@(posedge clk_50M) begin if(reset_n==1’b0) begin initial_en<=0; end else begin if (cnt1==48000000) initial_en<=1; else initial_en<=initial_en; end
end endmodule |
- 5640芯片配置程序
开发板正确上电后,会对OV5640相关寄存器进行配置。OV5640配置芯片内部寄存器的配置是通过SCCB(串行摄像机控制协议总线)协议进行。该协议相当于一个简单的I2C总线。SCCB总线时序如下图17-2。配置时,直接使用前面的I2C部分程序即可。
图17-2 SCCB总线写寄存器时序图
5640摄像功能完成需要配置的寄存器如下:
always@(reg_index)
begin case(reg_index) 0:reg_data<=24’h310311; 1:reg_data<=24’h300882; 2:reg_data<=24’h300842; 3:reg_data<=24’h310303; 4:reg_data<=24’h3017ff; 5:reg_data<=24’h3018ff; 6:reg_data<=24’h30341A; 7:reg_data<=24’h303713; 8:reg_data<=24’h310801; 9:reg_data<=24’h363036; 10:reg_data<=24’h36310e; 11:reg_data<=24’h3632e2; 12:reg_data<=24’h363312; 13:reg_data<=24’h3621e0; 14:reg_data<=24’h3704a0; 15:reg_data<=24’h37035a; 16:reg_data<=24’h371578; 17:reg_data<=24’h371701; 18:reg_data<=24’h370b60; 19:reg_data<=24’h37051a; 20:reg_data<=24’h390502; 21:reg_data<=24’h390610; 22:reg_data<=24’h39010a; 23:reg_data<=24’h373112; 24:reg_data<=24’h360008; 25:reg_data<=24’h360133; 26:reg_data<=24’h302d60; 27:reg_data<=24’h362052; 28:reg_data<=24’h371b20; 29:reg_data<=24’h471c50; 30:reg_data<=24’h3a1343; 31:reg_data<=24’h3a1800; 32:reg_data<=24’h3a19f8; 33:reg_data<=24’h363513; 34:reg_data<=24’h363603; 35:reg_data<=24’h363440; 36:reg_data<=24’h362201; 37:reg_data<=24’h3c0134; 38:reg_data<=24’h3c0428; 39:reg_data<=24’h3c0598; 40:reg_data<=24’h3c0600; 41:reg_data<=24’h3c0708; 42:reg_data<=24’h3c0800; 43:reg_data<=24’h3c091c; 44:reg_data<=24’h3c0a9c; 45:reg_data<=24’h3c0b40; 46:reg_data<=24’h381000; 47:reg_data<=24’h381110; 48:reg_data<=24’h381200; 49:reg_data<=24’h370864; 50:reg_data<=24’h400102; 51:reg_data<=24’h40051a; 52:reg_data<=24’h300000; 53:reg_data<=24’h3004ff; 54:reg_data<=24’h300e58; 55:reg_data<=24’h302e00; 56:reg_data<=24’h430061; 57:reg_data<=24’h501f01; 58:reg_data<=24’h440e00; 59:reg_data<=24’h5000a7; 60:reg_data<=24’h3a0f30; 61:reg_data<=24’h3a1028; 62:reg_data<=24’h3a1b30; 63:reg_data<=24’h3a1e26; 64:reg_data<=24’h3a1160; 65:reg_data<=24’h3a1f14; 66:reg_data<=24’h580023; 67:reg_data<=24’h580114; 68:reg_data<=24’h58020f; 69:reg_data<=24’h58030f; 70:reg_data<=24’h580412; 71:reg_data<=24’h580526; 72:reg_data<=24’h58060c; 73:reg_data<=24’h580708; 74:reg_data<=24’h580805; 75:reg_data<=24’h580905; 76:reg_data<=24’h580a08; 77:reg_data<=24’h580b0d; 78:reg_data<=24’h580c08; 79:reg_data<=24’h580d03; 80:reg_data<=24’h580e00; 81:reg_data<=24’h580f00; 82:reg_data<=24’h581003; 83:reg_data<=24’h581109; 84:reg_data<=24’h581207; 85:reg_data<=24’h581303; 86:reg_data<=24’h581400; 87:reg_data<=24’h581501; 88:reg_data<=24’h581603; 89:reg_data<=24’h581708; 90:reg_data<=24’h58180d; 91:reg_data<=24’h581908; 92:reg_data<=24’h581a05; 93:reg_data<=24’h581b06; 94:reg_data<=24’h581c08; 95:reg_data<=24’h581d0e; 96:reg_data<=24’h581e29; 97:reg_data<=24’h581f17; 98:reg_data<=24’h582011; 99:reg_data<=24’h582111; 100:reg_data<=24’h582215; 101:reg_data<=24’h582328; 102:reg_data<=24’h582446; 103:reg_data<=24’h582526; 104:reg_data<=24’h582608; 105:reg_data<=24’h582726; 106:reg_data<=24’h582864; 107:reg_data<=24’h582926; 108:reg_data<=24’h582a24; 109:reg_data<=24’h582b22; 110:reg_data<=24’h582c24; 111:reg_data<=24’h582d24; 112:reg_data<=24’h582e06; 113:reg_data<=24’h582f22; 114:reg_data<=24’h583040; 115:reg_data<=24’h583142; 116:reg_data<=24’h583224; 117:reg_data<=24’h583326; 118:reg_data<=24’h583424; 119:reg_data<=24’h583522; 120:reg_data<=24’h583622; 121:reg_data<=24’h583726; 122:reg_data<=24’h583844; 123:reg_data<=24’h583924; 124:reg_data<=24’h583a26; 125:reg_data<=24’h583b28; 126:reg_data<=24’h583c42; 127:reg_data<=24’h583dce; 128:reg_data<=24’h5180ff; 129:reg_data<=24’h5181f2; 130:reg_data<=24’h518200; 131:reg_data<=24’h518314; 132:reg_data<=24’h518425; 133:reg_data<=24’h518524; 134:reg_data<=24’h518609; 135:reg_data<=24’h518709; 136:reg_data<=24’h518809; 137:reg_data<=24’h518975; 138:reg_data<=24’h518a54; 139:reg_data<=24’h518be0; 140:reg_data<=24’h518cb2; 141:reg_data<=24’h518d42; 142:reg_data<=24’h518e3d; 143:reg_data<=24’h518f56; 144:reg_data<=24’h519046; 145:reg_data<=24’h5191f8; 146:reg_data<=24’h519204; 147:reg_data<=24’h519370; 148:reg_data<=24’h5194f0; 149:reg_data<=24’h5195f0; 150:reg_data<=24’h519603; 151:reg_data<=24’h519701; 152:reg_data<=24’h519804; 153:reg_data<=24’h519912; 154:reg_data<=24’h519a04; 155:reg_data<=24’h519b00; 156:reg_data<=24’h519c06; 157:reg_data<=24’h519d82; 158:reg_data<=24’h519e38; 159:reg_data<=24’h548001; 160:reg_data<=24’h548108; 161:reg_data<=24’h548214; 162:reg_data<=24’h548328; 163:reg_data<=24’h548451; 164:reg_data<=24’h548565; 165:reg_data<=24’h548671; 166:reg_data<=24’h54877d; 167:reg_data<=24’h548887; 168:reg_data<=24’h548991; 169:reg_data<=24’h548a9a; 170:reg_data<=24’h548baa; 171:reg_data<=24’h548cb8; 172:reg_data<=24’h548dcd; 173:reg_data<=24’h548edd; 174:reg_data<=24’h548fea; 175:reg_data<=24’h54901d; 176:reg_data<=24’h53811e; 177:reg_data<=24’h53825b; 178:reg_data<=24’h538308; 179:reg_data<=24’h53840a; 180:reg_data<=24’h53857e; 181:reg_data<=24’h538688; 182:reg_data<=24’h53877c; 183:reg_data<=24’h53886c; 184:reg_data<=24’h538910; 185:reg_data<=24’h538a01; 186:reg_data<=24’h538b98; 187:reg_data<=24’h558006; 188:reg_data<=24’h558340; 189:reg_data<=24’h558410; 190:reg_data<=24’h558910; 191:reg_data<=24’h558a00; 192:reg_data<=24’h558bf8; 193:reg_data<=24’h501d40; 194:reg_data<=24’h530008; 195:reg_data<=24’h530130; 196:reg_data<=24’h530210; 197:reg_data<=24’h530300; 198:reg_data<=24’h530408; 199:reg_data<=24’h530530; 200:reg_data<=24’h530608; 201:reg_data<=24’h530716; 202:reg_data<=24’h530908; 203:reg_data<=24’h530a30; 204:reg_data<=24’h530b04; 205:reg_data<=24’h530c06; 206:reg_data<=24’h502500; 207:reg_data<=24’h300802; //680×480 30帧/秒, night mode 5fps, input clock =24Mhz, PCLK =56Mhz 208:reg_data<=24’h303511; 209:reg_data<=24’h303646; 210:reg_data<=24’h3c0708; 211:reg_data<=24’h382047; 212:reg_data<=24’h382101; 213:reg_data<=24’h381431; 214:reg_data<=24’h381531; 215:reg_data<=24’h380000; 216:reg_data<=24’h380100; 217:reg_data<=24’h380200; 218:reg_data<=24’h380304; 219:reg_data<=24’h38040a; 220:reg_data<=24’h38053f; 221:reg_data<=24’h380607; 222:reg_data<=24’h38079b; 223:reg_data<=24’h380802; 224:reg_data<=24’h380980; 225:reg_data<=24’h380a01; 226:reg_data<=24’h380be0; 227:reg_data<=24’h380c07; 228:reg_data<=24’h380d68; 229:reg_data<=24’h380e03; 230:reg_data<=24’h380fd8; 231:reg_data<=24’h381306; 232:reg_data<=24’h361800; 233:reg_data<=24’h361229; 234:reg_data<=24’h370952; 235:reg_data<=24’h370c03; 236:reg_data<=24’h3a0217; 237:reg_data<=24’h3a0310; 238:reg_data<=24’h3a1417; 239:reg_data<=24’h3a1510; 240:reg_data<=24’h400402; 241:reg_data<=24’h30021c; 242:reg_data<=24’h3006c3; 243:reg_data<=24’h471303; 244:reg_data<=24’h440704; 245:reg_data<=24’h460b35; 246:reg_data<=24’h460c22; 247:reg_data<=24’h483722; 248:reg_data<=24’h382402; 249:reg_data<=24’h500183; 250:reg_data<=24’h350300; 251:reg_data<=24’h301602; 252:reg_data<=24’h3b070a; 253:reg_data<=24’h3b0083 ; 254:reg_data<=24’h3b0000 ; default:reg_data<=24’h000000; endcase end |
- led灯控制和拍照功能的控制代码如下:
always @(posedge clk_50 , negedge initial_en)
if (!initial_en) begin on_counter<=0; off_counter<=0; key_on<=1’b0; key_off<=1’b0; end else begin if (key1==1’b1) on_counter<=0; else if ((key1==1’b0)& (on_counter<=500000)) on_counter<=on_counter+1’b1;
if (on_counter==500000) key_on<=1’b1; else key_on<=1’b0; if (key1==1’b0) off_counter<=0; else if ((key1==1’b1)& (off_counter<=500000)) off_counter<=off_counter+1’b1; if (off_counter==500000) key_off<=1’b1; else key_off<=1’b0; end reg [1:0] st_Strobe =0 ; always @(posedge clk_50 , negedge initial_en) if (!initial_en) begin sign_Strobe <= 2’b00 ; st_Strobe <= 2’b00 ; end else begin case ( st_Strobe ) 0: st_Strobe <= 2’b01 ; 1:begin if (key_on) begin if (sign_Strobe == 2’b00) st_Strobe <= 2’b10 ; else if (sign_Strobe == 2’b11) st_Strobe <= 2’b11 ; end else begin st_Strobe <= st_Strobe ; sign_Strobe <=sign_Strobe ; end end 2: begin sign_Strobe <=sign_Strobe+1 ; if (sign_Strobe == 2’b10) st_Strobe <= 2’b01 ; end 3: begin sign_Strobe <=sign_Strobe-1 ; if (sign_Strobe == 2’b01) st_Strobe <= 2’b01 ; end endcase end |
- 拍照功能实现部分关键代码:
module pic(
input wire key3 , output reg hdmi_valid , output reg [15:0] fifo_hdmi_dout , input wire hdmi_rd_en , input wire hdmi_end , input wire hdmi_req , output wire [10:0] hdmi_fifo_rd_data_count , output sram1_cs_n , output sram1_we_n , output sram1_oe_n , output sram1_ub_n , output sram1_lb_n , output sram0_cs_n , output sram0_we_n , output sram0_oe_n , output sram0_ub_n , output sram0_lb_n , output [17:0] sram_addr , inout [31:0] sram_data , input wire clk_50m , input wire rst_n_50m , input wire hdmi_reg_done , input wire reg_conf_done , input wire pic_clk , input wire vga_clk , input wire camera_href , input wire camera_vsync, input wire [7:0] camera_data ,
output led );
reg camera_on = 0 ; reg lock_r = 0 ; reg wr_en = 0 ; reg [7:0] din = 0 ; reg rec_sign = 0 ; reg sign_we = 0 ; reg write_ack = 0 ; reg [1:0] camera_vsync_rr =2’b00;
reg [11:0]camera_h_count; reg [10:0]camera_v_count; assign led=!{camera_h_count,camera_v_count} ; always @ (posedge pic_clk) camera_vsync_rr <= {camera_vsync_rr[0],camera_vsync };
always @ (posedge pic_clk) if (hdmi_reg_done&(camera_vsync_rr==2’b10)&rst_n_50m&rec_sign) sign_we <=1 ; else if (camera_vsync_rr==2’b01) sign_we <=0 ; else sign_we <=sign_we ; always @(posedge pic_clk) begin if (!reg_conf_done) camera_h_count<=1; else if((camera_href==1’b1) & (camera_vsync==1’b0)) camera_h_count<=camera_h_count+1’b1; else camera_h_count<=1; end always @(posedge pic_clk) begin if (!reg_conf_done) camera_v_count<=0; else if (camera_vsync==1’b0) begin if(camera_h_count==1280) camera_v_count<=camera_v_count+1’b1; else camera_v_count<=camera_v_count; end else camera_v_count<=0 ;
end always @ (posedge pic_clk) if (reg_conf_done==0) begin wr_en <=0 ; din <=0 ; end else begin
if(camera_href&sign_we)
begin wr_en <=1 ; din <=camera_data ; end
else begin wr_en <=0 ; din <=0 ; end end
wire valid ; wire [31: 0] dout ; reg rd_en =0 ; wire [9 : 0] rd_data_count ; wire [11 : 0] wr_data_count ; fifo_8_to_32 fifo_8_to_32_inst ( .data ( din ), .rdclk ( clk_50m ), .rdreq ( rd_en ), .wrclk ( pic_clk ), .wrreq ( wr_en ), .q ( dout ), .rdempty ( ), .rdusedw ( rd_data_count ), .wrfull ( ), .wrusedw ( wr_data_count ) ); reg [31:0] din_sram_fifo = 32’d0 ; reg sram_fifo_wen = 0 ; wire[15:0] fifo_hdmi_dout_r ; wire hdmi_valid_r ; wire [9:0] hdmi_fifo_wr_data_count ; wire full ; wire empty ; always @ (posedge vga_clk) begin fifo_hdmi_dout <= fifo_hdmi_dout_r ; hdmi_valid <= hdmi_rd_en ; // hdmi_valid <= hdmi_valid_r ; end
fifo_32_to_16 fifo_32_to_16_inst ( .data ( din_sram_fifo ), .rdclk ( vga_clk ), .rdreq ( hdmi_rd_en ), .wrclk ( clk_50m), .wrreq ( sram_fifo_wen ), .q ( fifo_hdmi_dout_r ), .rdempty ( ), .rdusedw ( ), .wrfull ( ), .wrusedw ( ) ); reg [25:0] on_counter =26’d0 ;
always @(posedge clk_50m , negedge reg_conf_done) if (!reg_conf_done) begin on_counter<=0; camera_on<=1’b0; end else begin if (key3==1’b1) on_counter<=0; else if ((key3==1’b0)& (on_counter<=500000)) on_counter<=on_counter+1’b1;
if (on_counter==500000) camera_on<=1’b1; else if (write_ack ) camera_on<=1’b0; else camera_on<=camera_on; end wire [31:0] data_rd ; wire data_valid ; reg [4:0] sram_wr_st = 0 ; reg w_cnt = 0 ; reg [17:0] sram_addr_wr_rd = 18’d0 ; reg [31:0] data_we = 32’d0 ; reg [31:0] rd_data = 32’d0 ; reg wr_en_req = 0 ; reg rd_en_req = 0 ; (* keep *) reg [8:0] hdmi_req_cnt = 0 ; always @ (posedge clk_50m , negedge reg_conf_done ,negedge rst_n_50m ) begin
if ( ~rst_n_50m|~reg_conf_done)
begin rec_sign <= 0 ; sram_addr_wr_rd <= 18’d0 ; sram_wr_st <= 0 ;
data_we <= 32’d0 ; rd_data <= 32’d0 ; wr_en_req <= 0 ; rd_en_req <= 0 ; hdmi_req_cnt <= 0 ;
end
else begin case ( sram_wr_st) 0 : begin rec_sign <= 0 ; sram_addr_wr_rd <= 18’d0 ; data_we <= 32’d0 ; rd_data <= 32’d0 ; wr_en_req <= 0 ; rd_en_req <= 0 ; sram_wr_st <= 1 ;
write_ack <= 0 ; end 1 : begin if (camera_on) begin sram_wr_st <= 2 ; rec_sign <= 1 ; write_ack <= 1 ;
end else sram_wr_st <= 7 ;
end 2 : begin write_ack <= 0 ; if (sign_we)
begin data_we <= 0 ; sram_addr_wr_rd <= 0 ; wr_en_req <= 0 ; rec_sign <= 0 ; sram_wr_st <= 3 ;
end else begin rec_sign <= 1 ; sram_wr_st <= 2 ;
end
end
3 : begin if ( sign_we ) begin if ( rd_data_count ) begin rd_en <= 1 ; sram_wr_st <= 4 ; end else begin rd_en <= 0 ; sram_wr_st <= 3 ; end end else begin rd_en <= 0 ; sram_wr_st <= 0 ; end
end
4 :begin rd_en <= 0 ; data_we <= {dout[7:0] ,dout[15:8],dout[23:16],dout[31:24]}; sram_addr_wr_rd <= sram_addr_wr_rd ; wr_en_req <= rd_en ; sram_wr_st <= 5 ; end
5: begin data_we <= dout ; sram_addr_wr_rd <= sram_addr_wr_rd ; wr_en_req <= rd_en; sram_wr_st <= 6 ; end
6 : begin
data_we <= dout ; sram_addr_wr_rd <= sram_addr_wr_rd +1 ; wr_en_req <= rd_en; sram_wr_st <= 3 ;
end 7: begin din_sram_fifo <= {data_rd[15:0],data_rd[31:16] }; sram_fifo_wen <= data_valid ; if (hdmi_end) sram_wr_st <= 0 ; else begin if (hdmi_req) begin rd_en_req <=1 ; sram_addr_wr_rd<=sram_addr_wr_rd; sram_wr_st <= 8 ; hdmi_req_cnt <= 0 ; end else begin hdmi_req_cnt <= 0 ; sram_wr_st <= 7 ; end end end
8: begin sram_addr_wr_rd<=sram_addr_wr_rd+1; sram_wr_st <= 9 ; end
9 :begin sram_addr_wr_rd <= sram_addr_wr_rd + 1 ; sram_wr_st <= 10 ; hdmi_req_cnt <= hdmi_req_cnt+1 ;
end
10 : begin hdmi_req_cnt <= hdmi_req_cnt+1 ; din_sram_fifo <= {data_rd[15:0],data_rd[31:16] } ; sram_fifo_wen <= data_valid ; sram_addr_wr_rd<= sram_addr_wr_rd+1 ; if (hdmi_req_cnt==318) begin rd_en_req <= 0 ; sram_wr_st <= 7 ; end end
default : sram_wr_st <= 0 ; endcase end
end sram_ctr sram_ctr_inst0 (
.clk_50 (clk_50m ) , .rst_n (rst_n_50m ) , .wr_en (wr_en_req) , .rd_en (rd_en_req) , .sram_addr_wr_rd (sram_addr_wr_rd) , .data_we (data_we ) , .data_rd (data_rd ) , .data_valid (data_valid ) , .sram_addr (sram_addr) , .sram_data (sram_data) ,
.sram1_ce (sram1_cs_n) , .sram1_oe (sram1_oe_n) , .sram1_we (sram1_we_n) , .sram1_lb (sram1_lb_n) , .sram1_ub (sram1_ub_n) ,
.sram0_ce (sram0_cs_n) , .sram0_oe (sram0_oe_n) , .sram0_we (sram0_we_n) , .sram0_lb (sram0_lb_n) , .sram0_ub (sram0_ub_n) ) ; endmodule |
(4) HDMI部分请参考前面HDMI相关章节内容
17.4 实验下板验证
- 程序管脚分配表
程序信号名 | 端口信号说明 | 网络标号 | FPGA管脚 |
Clk_50m | 系统50M时钟 | C10_50MCLK | G21 |
Reset_n | 系统复位信号 | KEY1 | Y4 |
Clk_24 | PLL共给5640时钟 | IO30 | Y13 |
Camera_data[0] | 5640图像数据总线 | IO31 | AA13 |
Camera_data[1] | 5640图像数据总线 | IO27 | V12 |
Camera_data[2] | 5640图像数据总线 | IO3 | AA15 |
Camera_data[3] | 5640图像数据总线 | IO2 | V16 |
Camera_data[4] | 5640图像数据总线 | IO7 | U16 |
Camera_data[5] | 5640图像数据总线 | IO1 | R16 |
Camera_data[6] | 5640图像数据总线 | IO5 | U17 |
Camera_data[7] | 5640图像数据总线 | IO4 | AB20 |
Camera_pclk | 5640图像随路时钟 | IO1 | T16 |
Camera_href | 5640输入行信号 | IO28 | R14 |
Camera_vsync | 5640输入场信号 | IO24 | AA14 |
Camera_pwup | 5640上电控制引脚 | IO0 | AA20 |
Key1 | 拍照按键 | KEY3 | L8 |
Key2 | led灯 | KEY6 | P1 |
vga_hs | 行同步信号 | HDMI_HSYNC | C24 |
vga_vs | 场同步信号 | HDMI_VSYNC | A25 |
en | 数据有效 | HDMI_DE | A24 |
vga_clk | 显示时钟 | HDMI_CLK | B19 |
key1 | 显示效果切换按钮 | KEY2 | L4 |
scl | adv7511配置时钟 | I2C_SCL | R20 |
sda | adv7511配置数据线 | I2C_SDA | R21 |
HDMI_D[23] | 红色输出 | HDMI_D23 | G7 |
HDMI_D[22] | 红色输出 | HDMI_D22 | F9 |
HDMI_D[21] | 红色输出 | HDMI_D21 | F7 |
HDMI_D[20] | 红色输出 | HDMI_D20 | C3 |
HDMI_D[19] | 红色输出 | HDMI_D19 | B3 |
HDMI_D[18] | 红色输出 | HDMI_D18 | C4 |
HDMI_D[17] | 红色输出 | HDMI_D17 | A3 |
HDMI_D[16] | 红色输出 | HDMI_D16 | E7 |
HDMI_D[15] | 绿色输出 | HDMI_D15 | B4 |
HDMI_D[14] | 绿色输出 | HDMI_D14 | D6 |
HDMI_D[13] | 绿色输出 | HDMI_D13 | A4 |
HDMI_D[12] | 绿色输出 | HDMI_D12 | C6 |
HDMI_D[11] | 绿色输出 | HDMI_D11 | B5 |
HDMI_D[10] | 绿色输出 | HDMI_D10 | D7 |
HDMI_D[9] | 绿色输出 | HDMI_D9 | C7 |
HDMI_D[8] | 绿色输出 | HDMI_D8 | A5 |
HDMI_D[7] | 蓝色输出 | HDMI_D7 | B6 |
HDMI_D[6] | 蓝色输出 | HDMI_D6 | F8 |
HDMI_D[5] | 蓝色输出 | HDMI_D5 | A6 |
HDMI_D[4] | 蓝色输出 | HDMI_D4 | C8 |
HDMI_D[3] | 蓝色输出 | HDMI_D3 | B7 |
HDMI_D[2] | 蓝色输出 | HDMI_D2 | E9 |
HDMI_D[1] | 蓝色输出 | HDMI_D1 | B8 |
HDMI_D [0] | 蓝色输出 | HDMI_D0 | A7 |
Uart_rx | 串口接收 | TTL_TX | L17 |
Uart_tx | 串口发送 | TTL_RX | L18 |
Sram_data[0] | Sram数据总线 | D0 | F22 |
Sram_data[1] | Sram数据总线 | D1 | E21 |
Sram_data[2] | Sram数据总线 | D2 | D21 |
Sram_data[3] | Sram数据总线 | D3 | E22 |
Sram_data[4] | Sram数据总线 | D4 | D22 |
Sram_data[5] | Sram数据总线 | D5 | C21 |
Sram_data[6] | Sram数据总线 | D6 | B21 |
Sram_data[7] | Sram数据总线 | D7 | C22 |
Sram_data[8] | Sram数据总线 | D8 | M16 |
Sram_data[9] | Sram数据总线 | D9 | K19 |
Sram_data[10] | Sram数据总线 | D10 | M20 |
Sram_data[11] | Sram数据总线 | D11 | M19 |
Sram_data[12] | Sram数据总线 | D12 | L22 |
Sram_data[13] | Sram数据总线 | D13 | L21 |
Sram_data[14] | Sram数据总线 | D14 | J22 |
Sram_data[15] | Sram数据总线 | D15 | J18 |
Sram_data[16] | Sram数据总线 | D16 | M21 |
Sram_data[17] | Sram数据总线 | D17 | K18 |
Sram_data[18] | Sram数据总线 | D18 | N21 |
Sram_data[19] | Sram数据总线 | D19 | M22 |
Sram_data[20] | Sram数据总线 | D20 | P22 |
Sram_data[21] | Sram数据总线 | D21 | P20 |
Sram_data[22] | Sram数据总线 | D22 | R20 |
Sram_data[23] | Sram数据总线 | D23 | P21 |
Sram_data[24] | Sram数据总线 | D24 | W19 |
Sram_data[25] | Sram数据总线 | D25 | W20 |
Sram_data[26] | Sram数据总线 | D26 | R17 |
Sram_data[27] | Sram数据总线 | D27 | T17 |
Sram_data[28] | Sram数据总线 | D28 | U19 |
Sram_data[29] | Sram数据总线 | D29 | AA21 |
Sram_data[30] | Sram数据总线 | D30 | AA22 |
Sram_data[31] | Sram数据总线 | D31 | R18 |
Sram_addr[0] | Sram地址总线 | A0 | J21 |
Sram_addr[1] | Sram地址总线 | A 1 | H22 |
Sram_addr[2] | Sram地址总线 | A 2 | H19 |
Sram_addr[3] | Sram地址总线 | A 3 | G18 |
Sram_addr[4] | Sram地址总线 | A 4 | H17 |
Sram_addr[5] | Sram地址总线 | A 5 | H21 |
Sram_addr[6] | Sram地址总线 | A 6 | H20 |
Sram_addr[7] | Sram地址总线 | A 7 | F19 |
Sram_addr[8] | Sram地址总线 | A 8 | H18 |
Sram_addr[9] | Sram地址总线 | A 9 | F20 |
Sram_addr[10] | Sram地址总线 | A 10 | W21 |
Sram_addr[11] | Sram地址总线 | A 11 | W22 |
Sram_addr[12] | Sram地址总线 | A 12 | V21 |
Sram_addr[13] | Sram地址总线 | A 13 | U20 |
Sram_addr[14] | Sram地址总线 | A 14 | V22 |
Sram_addr[15] | Sram地址总线 | A 15 | R21 |
Sram_addr[16] | Sram地址总线 | A 16 | U21 |
Sram_addr[17] | Sram地址总线 | A 17 | R22 |
Sram_addr[18] | Sram地址总线 | A 18(此管脚无效) | U22 |
Sram0_cs_n | 第0片sram使能 | CE_N_SRAM0 | F21 |
Sram0_we_n | 第0片sram写使能 | OE_N_SRAM0 | B22 |
Sram0_oe_n | 第0片sram读使能 | WE_N_SRAM0 | F17 |
Sram0_ub_n | 第0片sram高字节使能 | UE_N_SRAM0 | K22 |
Sram0_lb_n | 第0片sram低字节使能 | LE_N_SRAM0 | K21 |
Sram0_cs_n | 第1片sram使能 | CE_N_SRAM1 | N22 |
Sram0_we_n | 第1片sram写使能 | OE_N_SRAM1 | R19 |
Sram0_oe_n | 第1片sram读使能 | WE_N_SRAM1 | Y21 |
Sram0_ub_n | 第1片sram高字节使能 | UE_N_SRAM1 | Y22 |
Sram0_lb_n | 第1片sram低字节使能 | LE_N_SRAM1 | T18 |
Led0 | OV5640寄存器配置指示灯 | LED0 | J5 |
Led1 | ADV7511寄存器配置指示灯 | LED1 | J6 |
i2c_sclk | 5640配置时钟 | IO26 | AB13 |
i2c_sdat | 5640配置数据线 | IO29 | AB14 |
- 程序下板验证:
程序下板后,led0 和led1点亮说明OV5640和ADV7511配置完成。
触按按键RIGHT具有打开关闭led补光灯的功能。
按一次按键RETURN,则摄像头会拍一张照片,并在HDMI接口的显示器上显示出来。实际上板测试结果如下图17-3。
图17-3 5640 显示器显示拍摄图片
实验十八 高速ADC9226采集实验
18.1 实验目的
- 学习并行ADC采集器的相关知识,掌握ADC9226的使用。
18.2 实验要求
1. 将ADC9226模块正面向上插入FPGA开发板紧挨红绿色音频模块的GPIO2,GPIO1口。编写程序分别使用该模块测试
18.3 实验内容
18.3.1 ADC9226模块简介
ADC9226模块采用ADI公司的AD9226线片设计。此芯片是一款款单芯片、12 位、65 MSPS 模数转换器(ADC),采用单电源供电,内置一个片内高性能采样保持放大器和基准电压源。它采用多级差分流水线架构,数据速率达65 MSPS,在整个工作温度范围内保证无失码。
ADC9226时序图如下图18-1
图18-1 ADC9226时序图
由此时序图,可知,并不需要对AD9226芯片进行配置,只需提供合适的CLOCK,芯片就可以进行数据采集。
18.3.2 程序设计
- AD采集子模块
从上面时序图18-1中可以看出,AD9226的高位为bit0,低位为bit[11],所以程序中需要把数据位序颠倒一下。
module ad_9226(
input ad_clk, input [11:0] ad1_in, output reg [11:0] ad_ch );
always @(posedge ad_clk) begin ad_ch[11] <= ad1_in[0] ; ad_ch[10] <= ad1_in[1] ; ad_ch[9 ] <= ad1_in[2] ; ad_ch[8 ] <= ad1_in[3] ; ad_ch[7 ] <= ad1_in[4] ; ad_ch[6 ] <= ad1_in[5] ; ad_ch[5 ] <= ad1_in[6] ; ad_ch[4 ] <= ad1_in[7] ; ad_ch[3 ] <= ad1_in[8] ; ad_ch[2 ] <= ad1_in[9] ; ad_ch[1 ] <= ad1_in[10] ; ad_ch[0 ] <= ad1_in[11] ; end endmodule |
- 数据转换程序
AD9226模块设计采用内部基准源,VREF是基准源的输出端口,可供1V和2V两种基准电压。通过SENCE可以经行选择。当SENCE接地时,提供2V基准源,当与VREF连接时,提供1V基准电源。该模块采用2V基准电源。VINA输入范围为1.0~3.0V。
Ad9226的22(12)引脚具有采集数据选择功能,AD9226的输入输出数据格式有两种,具体格式可以查看9226datasheet文档。本次实验的AD9226模块22(12)引脚连接高电平,所以采用Binary Output Mode 模式。数据转换程序中的bcd转码子程序在实验8中已经介绍过,此处不再赘述。
module volt_cal(
input wire ad_clk, input wire [11:0] ad_ch1, output [19:0] ch1_dec, output reg ch1_sig ); reg [31:0] ch1_data_reg; reg [11:0] ch1_reg; reg [31:0] ch1_vol; always @(posedge ad_clk) begin if(ad_ch1[11]==1’b1) begin ch1_reg<={1’b0,ad_ch1[10:0]}; ch1_sig <= 0; end else begin ch1_reg<={12’h800-ad_ch1[10:0]}; ch1_sig<=1; end end always @(posedge ad_clk) begin ch1_data_reg<=ch1_reg * 2000; ch1_vol<=ch1_data_reg >>11; end bcd bcd1_ist( .hex (ch1_vol[15:0]), .dec (ch1_dec), .clk (ad_clk) ); endmodule |
- 9226 模块AD采集量程选择
AD采集模块衰减量程分为挡位。按一次开发板板上的UP键量程切换一次。
表 19.1 挡位切换指示表
挡位对照表(输入电压百分比) | 对应指示灯 |
4% | led0点亮 |
8% | led0,led1点亮 |
20% | led0,led1, led2点亮 |
40% | led0,led1, led2, led2点亮 |
module range (
input wire clk , input wire rst_n , input wire key , output wire [3:0] led , output wire [1:0] scope );
wire flag_switch ; key_process key_process_inst( .clk (clk) , .rst_n (rst_n) , .key_switch (key) , .flag_switch (flag_switch) ); reg [1:0] scope_st =00 ; reg [3:0] led_temp =4’he ; always @ (posedge clk ,negedge rst_n) begin if (~rst_n) begin scope_st <= 0 ; led_temp <= 4’he ; end else begin case (scope_st) 0: begin led_temp <= 4’he ; if (flag_switch ) scope_st <= 1 ; end 1: begin led_temp <= 4’hc ; if (flag_switch ) scope_st <= 2 ; end 2: begin led_temp <= 4’h8 ; if (flag_switch ) scope_st <= 3 ; end 3: begin led_temp <= 4’h0 ; if (flag_switch ) scope_st <= 0 ; end endcase end end assign led = led_temp ; assign scope = scope_st ; endmodule |
- 主程序设计
主程序下面分三个子程序,分别位AD_9226采集模块, 数据转换计算模块volt_cal,电压值数码管显示模块。数码管部分在前边实验已经介绍过了,此处不再介绍。
module high_speed_ad_test(
input wire sys_clk, input wire otr , input wire key_switch , input wire sys_rst_n , input wire [11:0] ad1_in, output wire ad1_clk, output wire [5:0] sel, output wire [3:0] led , output wire cain_a, output wire cain_b, output wire [7:0] sm_db ); assign ad1_clk = sys_clk ; assign sm_db={point1, ~sm_db_r }; wire [19:0] ch1_dec; wire [11:0] ad_ch1 ; wire ch1_sig ; wire point1 ; wire [6:0] sm_db_r ; ad_9226 u1 ( .ad_clk (sys_clk), .ad1_in (ad1_in ), .ad_ch (ad_ch1 )
); volt_cal u2( .ad_clk (sys_clk), .ad_ch1 (ad_ch1), .ch1_dec (ch1_dec), .ch1_sig (ch1_sig) ); led_seg7 u3( .clk (sys_clk), .rst_n (sys_rst_n ), .otr (otr) , .ch1_sig (ch1_sig ), .ch1_dec (ch1_dec), .sel (sel), .point1 (point1), .sm_db (sm_db_r) ); range u4( .clk (sys_clk), .rst_n (sys_rst_n ), .key (key_switch) , .led (led), .scope ({cain_b,cain_a}) ); endmodule |
18.4 实验验证
- 管脚分配
程序信号名 | 端口信号说明 | 网络标号 | FPGA管脚 |
sys_clk | 系统时钟 | C10_50MCLK | G21 |
sys_rst_n | 系统复位 | KEY1 | Y4 |
lg_en | ADG612输入开关 | IO25 | AB20 |
hg_en | ADG612输入开关 | IO24 | AA20 |
ad1_clk | ad采集时钟 | IO28 | R16 |
otr | 输入电压超量程标志 | IO1 | AA13 |
sm_db[0] | 数码管段选 | SEG_PA | B15 |
sm_db[1] | 数码管段选 | SEG_PB | E14 |
sm_db[2] | 数码管段选 | SEG_PC | D15 |
sm_db[3] | 数码管段选 | SEG_PD | C15 |
sm_db[4] | 数码管段选 | SEG_PE | F13 |
sm_db[5] | 数码管段选 | SEG_PF | E11 |
sm_db[6] | 数码管段选 | SEG_PG | B16 |
sm_db[7] | 数码管段选 | SEG_DP | A16 |
sel[0] | 数码管位选 | SEG_3V3_D0 | E12 |
sel[1] | 数码管位选 | SEG_3V3_D1 | F11 |
sel[2] | 数码管位选 | SEG_3V3_D2 | E13 |
sel[3] | 数码管位选 | SEG_3V3_D3 | E15 |
sel[4] | 数码管位选 | SEG_3V3_D4 | D19 |
sel[5] | 数码管位选 | SEG_3V3_D5 | F14 |
ad1_in[0] | AD9226采集数据总线 | IO0 | V12 |
ad1_in[1] | AD9226采集数据总线 | IO5 | Y13 |
ad1_in[2] | AD9226采集数据总线 | IO4 | AB13 |
ad1_in[3] | AD9226采集数据总线 | IO3 | AB14 |
ad1_in[4] | AD9226采集数据总线 | IO6 | W13 |
ad1_in[5] | AD9226采集数据总线 | IO2 | R14 |
ad1_in[6] | AD9226采集数据总线 | IO7 | AA14 |
ad1_in[7] | AD9226采集数据总线 | IO29 | U16 |
ad1_in[8] | AD9226采集数据总线 | IO30 | AA15 |
ad1_in[9] | AD9226采集数据总线 | IO31 | T16 |
ad1_in[10] | AD9226采集数据总线 | IO27 | V16 |
ad1_in[11] | AD9226采集数据总线 | IO26 | U17 |
led0 | 输入信号衰减指示灯 | LED0 | J5 |
led1 | 输入信号衰减指示灯 | LED1 | J6 |
led2 | 输入信号衰减指示灯 | LED2 | H5 |
led3 | 输入信号衰减指示灯 | LED3 | H6 |
key_switch | 衰减切换开关 | PB2 | V5 |
- 下板验证
用本实验开发板外插DA9667模块生成1M正弦波作为信号源,用AD9226模块连接到FIA040开发板的GPIO1和GPIO2上,用逻辑分析仪抓取信号如下图18-2,可以看到如下波形。数码管从左到右第一个数码管中间段选点亮表示输入测量电压超过AD9226测量范围(VINA-VINB的绝对值小于等于参考电压,本模块参考电压为2V)。第2个数码管表示输入电压(VINA-VINB)的正负。后四位为输入电压值。输入信号值是一个缓变信号时,则数码管可以显示信号电压幅值。
图18-2 逻辑分析仪抓取AD9226实测信号波形图
实验十九 DAC9767 DDS信号源实验
19.1 实验目的
- 学习了解DDS(直接数字式频率合成器(Direct Digital Synthesizer))相关理论知识。
- 阅读AD9767 datasheet,使用AD9767设计一个可以生成正弦波,方波,三角波,锯齿波的信号源。
19.2 实验要求
- 学习DDS理论相关知识。
- 在理解DDS原理的基础上,结合该理论知识,利用AD9767模块和开发板搭建一个波形,幅值,频率均可调节的信号源。(此处对波形,幅值,频率的调节不做具体要求,只要通过按键可以调节变换即可)。
19.3 实验内容
19.3.1 DDS相关理论知识
DDS技术是根据奈奎斯特取样定律,从连续信号的相位出发,将正弦信号取样,编码,量化,形成一个正弦函数表,存在ROM中,合成时,通过改变相位累加器的频率字来改变相位增量,也就是我们所称的步长。相位增量的不同导致一个周期内取样点的不同,在时钟频率即采样频率不变的情况下,通过相位的改变来改变频率。原理示意图如下图20-1。
图19-1 DDS结构原理图
19.3.2 AD9767配置简介
AD9767模块使用ADI公司AD9767型DAC芯片,该芯片为14位,125Msps转换速率的高性能DAC器件.支持IQ输出模式,可以用在通信领域。
AD9767的接口时序要求。如下图19-2 所示,随路时钟的上升沿到来时,数据必须保持稳定ts的时间,时钟上升沿到来之后,数据必须保持th的稳定时间来才能被正确
图19-2 9767接口时序图
19.3.3 波形存储区文件配置
波形存储区文件为dds_4096x10b_wave_init.coe文件。具体制作过程请参考实验九rom的使用中.coe文件制作。将含有波形信息的文件存于rom中,工程文件烧录FPGA后,FPGA直接从rom读取波形信息,送给AD9767接口,则在AD9767模块上输出对应波形。波形存储如下图19-3 。
图19-3波形文件存储图
19.3.4 程序设计
1.主程序包括,波形选择,mode模式选择,频率调节,幅度调节功能。其中,具体代码如下:
module dac_9767_test(
input wire sys_clk_50m, input wire rst_n, (*mark_debug=”true”*) output mode, output wire dac_clk, output wire led , input wire mode_adjust, input wire a_adjust, input wire f_adjust, input wire wave_adjust, output reg [13:0] data_out ); wire [9:0] douta ; wire clk_50m ; BUFG BUFG_inst ( .O(clk_50m), .I(sys_clk_50m) ); wire locked ; wire clk_100m ; pll_50_100 pll_50_100_inst ( .clk_out1(clk_100m), .clk_out2(dac_clk), //nclk_100m .reset(0), .locked(locked), .clk_in1(clk_50m)); reg rst_n_g = 0 ;
always @ (posedge clk_100m) rst_n_g<=locked &rst_n ;
wire A_ctrl ; wire F_ctrl ; wire wave_switch ; wire mode_ctrl ; reg [1:0] base_addr =0 ; reg [3:0] base_A =0 ; reg [1:0] a_st =0 ; always @ (posedge clk_100m , negedge rst_n_g ) begin if (~rst_n_g) base_addr <=0; else if (wave_switch) base_addr <= base_addr +1 ; else base_addr <= base_addr ; end
always @ (posedge clk_100m , negedge rst_n_g ) begin if (~rst_n_g) begin base_A <=1; a_st <=0; end else begin case (a_st) 0: begin base_A <=8; a_st <=1; end 1: begin if (A_ctrl) begin a_st<=2 ; base_A <=11; end else begin a_st<=1 ; base_A <=8; end end
2: begin if (A_ctrl) begin a_st<=3; base_A <=15; end else begin a_st<=2; base_A <=11; end end 3: begin if (A_ctrl) begin a_st<= 1; base_A <=8; end else begin a_st<=3 ; base_A <=15; end end default :begin base_A <=1; a_st <=0; end endcase end end
always @ (posedge clk_100m , negedge rst_n_g ) begin if (~rst_n_g) data_out <=0 ; else data_out<= douta * base_A ; end reg [9:0] addr_r =0; reg [9:0] addr_temp_F =1 ; reg [3:0] f_st =0 ; always @ (posedge clk_100m , negedge rst_n_g ) begin if (~rst_n_g) begin addr_temp_F <= 0; f_st <=0 ; end else begin case (f_st) 0 : begin addr_temp_F <= 0 ; f_st <= 1 ; end 1 : begin addr_temp_F <= 1 ; if (F_ctrl) f_st <= 2 ; end 2 : begin addr_temp_F <= 2 ; if (F_ctrl) f_st <= 3 ; end
3 : begin addr_temp_F <= 3 ; if (F_ctrl) f_st <= 4 ; end 4 : begin addr_temp_F <= 4 ; if (F_ctrl) f_st <= 5 ; end 5 : begin addr_temp_F <= 5 ; if (F_ctrl) f_st <= 6 ; end 6 : begin addr_temp_F <= 6 ; if (F_ctrl) f_st <= 7 ; end 7 : begin addr_temp_F <= 8 ; if (F_ctrl) f_st <= 1 ;
end default : f_st <= 1 ; endcase end
end
always @ (posedge clk_100m , negedge rst_n_g ) begin if (~rst_n_g) addr_r <=0; else addr_r <=addr_r+1+addr_temp_F ; end (*mark_debug=”true”*)reg [11:0] addra=0 ; always @ (posedge clk_100m , negedge rst_n_g ) begin if (~rst_n_g) addra <=0 ; else addra<={base_addr, addr_r }; end reg mode_r=0; always @ (posedge clk_100m , negedge rst_n_g ) begin if (~rst_n_g) mode_r <=0 ; else if (mode_ctrl) mode_r <=~mode_r; else mode_r <= mode_r ; end assign mode=mode_r ; assign led= ~mode_r ; key_process ( .clk (clk_100m ) , .rst_n (rst_n_g ) , .key_switch (wave_adjust) , .key_adjust (a_adjust ) , .key_add (f_adjust ) , .key_sub (mode_adjust) , .flag_switch (wave_switch) , .flag_adjust (A_ctrl ) , .flag_add (F_ctrl ) , .flag_sub (mode_ctrl ) ); rom_dds_4096_10 rom_dds_4096_10_inst ( .clka(clk_100m), // input wire clka .addra(addra), // input wire [11 : 0] addra .douta(douta) // output wire [9 : 0] douta ); endmodule |
19.4 实验验证
1. 管脚分配
程序信号名 | 端口信号说明 | 网络标号 | FPGA管脚 |
sys_clk_50m | 系统时钟 | C10_50MCLK | G21 |
mode | 9767模式控制 | IO24 | AA14 |
wave_adjust | 波形选择 | key2 | V5 |
a_adjust | 幅值选择 | key3 | Y6 |
f_adjust | 频率选择 | key4 | AB4 |
mode_adjust | 模式选择 | key6 | AA4 |
led | 模式指示灯 | LED0 | J5 |
dac_clk | 9767驱动时钟 | IO28 | W13 |
rst_n | 系统复位 | key1 | Y4 |
data_out[0] | AD9767数据总线 | IO1 | U16 |
data_out[1] | AD9767数据总线 | IO0 | AA15 |
data_out[2] | AD9767数据总线 | IO5 | T16 |
data_out[3] | AD9767数据总线 | IO4 | V16 |
data_out[4] | AD9767数据总线 | IO3 | U17 |
data_out[5] | AD9767数据总线 | IO6 | R16 |
data_out[6] | AD9767数据总线 | IO2 | AB20 |
data_Out[7] | AD9767数据总线 | IO7 | AA20 |
data_out[8] | AD9767数据总线 | IO29 | AA13 |
data_out[9] | AD9767数据总线 | IO30 | Y12 |
data_out[10] | AD9767数据总线 | IO31 | Y13 |
data_out[11] | AD9767数据总线 | IO27 | AB13 |
data_out[12] | AD9767数据总线 | IO26 | AB14 |
data_out[13] | AD9767数据总线 | IO25 | R14 |
2. 下板验证
工程文件下载到FPGA开发板后,按right键(模式),当模式指示灯led0点亮。
即可依据UP键(波形选择),RETURN键(幅值选择),LEFT键(频率选择)来选择自己需要的波形。(本此实验只为介绍DDS理论知识,并验证其正确性。所以工程波形只设定了四种,分别为正弦波,方波,三角波,锯齿波。频率和幅值也是随机设定了几组。)下图19-4为示波器测量9767模块输出的四种波形图。
图19-4a 正弦波 图19-4b 方波
图19-4c 三角波 图19-4d 锯齿波
实验二十 LCD彩屏实验
内容请见附件LCD彩屏专题章节