0%

一、实验设计目标

在完成了实验3.1的基础上,使用ADDA集成板上的THS5651A与THS1030联动,完成DAC波形发生和ADC采集实验。

二、实验设计思路

2.1 DAC电路计算

对于ADDA开发板上的THS5651A电路来说,通过R27和内部VREF设定的DAC输出差分电流是IOUTA = 0mA-5mA,IOUTB = 5mA-0mA(这里IOUTA和IOUTB是差分对),具体的输出电流值由输入DAC的码值DA_Data[9:0](也即是下文中的Code)确定。IOUTA和IOUTB通过外部运放电路转化为单端电压。计算过程如下:

VOUTA = IOUTA * RLOAD = Code/1024 * 5mA * 200Ω = 1V * Code/1024;

VOUTB = IOUTB * RLOAD = (1024-Code)/1024 * 5mA * 200Ω = 1V * (1-Code/1024);

DAC_OUT = VOUTA - VOUTB = (2*Code/1024 - 1) * 1V * 2 (运放提供两倍放大)

img

2.1 实验过程

img

图3.2.1 实验整体框图

在此实验中,50MHz输入时钟通过PLL生成了ADC时钟和DAC时钟。ADC的采样时钟为40MHz(注意THS1030数据手册中额定的最高采样时钟为30MHz,如果波形出现毛刺,请降低采样频率试试),DAC的刷新时钟也为40MHz,在ADC和DAC的数据之间使用了一个FIFO来缓冲,避免跨时钟产生的数据毛刺。DAC的数据可以选择来自ADC采样的数值,也可以来自DDS IP核的输出。

三、功能模块图与输入输出引脚说明

该工程包含顶层模块ADDA与相位累加器模块、IP核PLL,FIFO,ROM等。图3.2.2是顶层原理图,整个工程的模块功能图参考图3.2.1即可。下面介绍一下顶层模块各主要引脚的功能:

img

3.2.2 ADDA顶层文件原理图

(1)CLK:50MHz的基准时钟信号输入。

(2)RSTn:系统复位输入信号,低电平有效,硬件。

(3)ADC_DATA[9:0]:ADC的输出数据

(4)DAC_DATA[9:0]:输出给DAC的数据

(5)ADC_CLK:CLK经过PLL模块后产生的ADC驱动时钟,40MHz;

(6)DAC_CLK:CLK经过PLL模块后产生的DAC驱动时钟,40MHz;

(7)SW1:选择DAC的数据源于ADC数据(SW1为高)或DDS模块(SW1为低);

四、程序设计

(1)图3.2.3是截取自顶层模块ADDA的代码,理解了此段程序则可以相应理解该模块的所有程序:

img

11 - 17:CLK经过PLL锁相环产生40MHz的ADC时钟和40MHz的DAC时钟;

21 - 31:数据缓冲FIFO,写时钟是ADC时钟,读时钟是DAC时钟,ADC的数据在ADC时钟的驱动下进入FIFO,ADC2DAC的数据在DAC时钟的驱动下输出FIFO;

34 - 48:DDS核,用于产生一个数字正弦信号,输出频率由KW确定;计算公式为:fout = Fs * KW/2^28;这里Fs即PLL输出的DAC_CLK,40MHz;

51:通过SW1选择输出给DAC的数据来源;

五、FPGA管脚配置

set_pin_assignment { ADC_CLK } { LOCATION = R5; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { ADC_DATA[0] } { LOCATION = N5; IOSTANDARD = LVCMOS33; }

set_pin_assignment { ADC_DATA[1] } { LOCATION = T12; IOSTANDARD = LVCMOS33; }

set_pin_assignment { ADC_DATA[2] } { LOCATION = R12; IOSTANDARD = LVCMOS33; }

set_pin_assignment { ADC_DATA[3] } { LOCATION = T9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { ADC_DATA[4] } { LOCATION = R9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { ADC_DATA[5] } { LOCATION = T7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { ADC_DATA[6] } { LOCATION = T8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { ADC_DATA[7] } { LOCATION = T5; IOSTANDARD = LVCMOS33; }

set_pin_assignment { ADC_DATA[8] } { LOCATION = T6; IOSTANDARD = LVCMOS33; }

set_pin_assignment { ADC_DATA[9] } { LOCATION = T4; IOSTANDARD = LVCMOS33; }

set_pin_assignment { CLK } { LOCATION = R7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_CLK } { LOCATION = T15; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { DAC_DATA[0] } { LOCATION = T13; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { DAC_DATA[1] } { LOCATION = M6; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { DAC_DATA[2] } { LOCATION = P5; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { DAC_DATA[3] } { LOCATION = P6; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { DAC_DATA[4] } { LOCATION = N6; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { DAC_DATA[5] } { LOCATION = M7; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { DAC_DATA[6] } { LOCATION = L7; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { DAC_DATA[7] } { LOCATION = R14; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { DAC_DATA[8] } { LOCATION = T14; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { DAC_DATA[9] } { LOCATION = R15; IOSTANDARD = LVCMOS33; SLEWRATE = FAST; }

set_pin_assignment { RSTn } { LOCATION = A9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW1 } { LOCATION = A10; IOSTANDARD = LVCMOS33; }

实验结果

硬件连接图:

img

6.1 AD输入接口袋仪器的信号源输出,DA输出接口袋仪器的示波器输入,DA数据来源选择来自ADC的输出;可以看到输入10K,1Vpp,输出也是10K,1Vpp.

img

6.2 DA输出接AD输入同时送给口袋仪器的示波器观察,DA数据来源选择来自DDS;

img

七、思考

\1. 修改PLL模块,产生不同的ADC和DAC驱动时钟,观察在不同DAC时钟频率下DDS模块输出频率的改变;

\2. 修改KW值,产生不同频率的正弦信号。

一、实验设计目标

(1)在FPGA中实现串口协议,通过Anlogic_FPGA开发板上的“UART2USB”口向计算机发送数据。

二、实验设计思路

在学习了实验4.1中UART串口接收实验的基础上,理解UART串口发送实验就较为简单了。本实验设计使用FPGA向计算机重复发送六个数据“0A、0B、0C、0D、0E、0F”,每隔0.5秒发送一个,在计算机上使用“串口助手”接收数据并验证是否正确。

图4.10为发送一帧数据时的简单示意图。可以看出,串口的发送时序和接收时序是一样的,而串口接收和发送的不同点就在于,接收是把数据从时序中提取出来,发送是把数据添加到时序中去。

img

4.10 串口发送时序示意图

​ 同串口接收一样,进行串口发送实验首先需要产生一个BPS_CLK信号,然后根据BPS_CLK实现TX的时序即可。以下为串口发送的简单步骤:

(1)由TX产生一个下降沿变化,作为一帧数据的开始,“开始位”时,TX信号置为0。

(2)把要发送的数据按照先低位后高位的顺序一位一位送给TX。

(3)“校验位”和“停止位”无操作,TX信号置为1。

(4)一帧数据发送完毕,等到下一次发送开始时,回到步骤(1)。

图4.11是本实验设计的串口发送系统的示意图,包含数据控制模块U1、波特率控制模块U2和发送控制模块U3。

img

4.11 串口发送系统示意图

数据控制模块内有一个计数器Count,控制系统每隔0.5秒发送一次数据。TX_Data为要发送的8位并行数据;TX_En_Sig为发送使能标志位,它的上升沿将驱动模块U2开始产生BPS_CLK信号,同时驱动U3模块开始发送数据;TX_Done_Sig为发送完成标志位,当一帧数据发送完毕后,TX_Done_Sig信号被置

为1,这将驱动TX_En_Sig信号变为0 直到开始发送下一组数据。

波特率控制模块与实验4.1节串口接收实验中模块功能相同,U2模块的输入信号Count_Sig即为U1模块的输出信号TX_En_Sig。

发送控制模块将TX_Data的值一位一位(LSB先导)的送给TX_Out信号,当一帧数据发送完毕时,TX_Done_Sig被置为1。

三、功能模块图与输入输出引脚说明

在FPGA中实现此系统,顶层模块为tx_top,底层模块data_control_module、tx_bps_module、tx_control_module从左至右依次对应于图4.11中的U1~U3。下面介绍一下顶层模块输入输出引脚的功能:

(1)CLK:50MHz的时钟信号输入。

(2)RSTn:复位输入信号,低电平有效。当RSTn信号为0时,TX_Out恒为1。

(3)TX_In:发送输出信号。连接到串口的TX线。

四、程序设计

​ 串口发送程序中的大部分代码功能都已在实验4.1节串口接收实验中使用过,容易理解,此处仅简单说明。图4.12是截取自底层模块data_control_module的部分代码:

img

4.12 data_control_module核心代码

​ 28-32:isTX是一个寄存器型信号,最终将赋给TX_En_Sig。当一帧数据发送完毕后,将isTX信号置为0,禁止系统发送数据。

33-35:分频数T05S的值为25000000,当距离上一次发送数据过去0.5秒后,将isTX信号置为1,开始新一轮数据的发送。

37-40:i的取值范围是0~5,用于从六组数据中选择一组发送。

五、FPGA管脚配置

以下是使用Anlogic的IO Constraint,CLK信号与开发板上的50MHz的晶振时钟相连;RSTn信号与SW0相连;TX_Pin_Out信号与R6管脚相连。

set_pin_assignment { CLK } { LOCATION = R7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { RSTn } { LOCATION = A9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { TX_Pin_Out } { LOCATION = D12; IOSTANDARD = LVCMOS33; }

六、实验结果

类似于串口接收实验,直接使用2根USB数据线分别将开发板上的JTAG接口和UART2USB接口与计算机相连,在计算机上安装一个“串口调试助手”,即可进行实验。

图4.13是使用“串口调试助手”接收数据“0A、0B、0C、0D、0E、0F”的结果。波特率选择9600,选择8位数据位,1位校验位和1位停止位。实验结果与程序设计相符。

img

4.1****3 串口发送实验结果

同样的,下面我们使用ChipWatcher来观察时序关系,加深对程序的理解:

img

4.1****4 串口发送实验ChipWatcher设置

设置ChipWatcher,采样时钟为500KHz,触发条件为TX的下降沿到来且i==4’b0000,也就是我们要看发8’h0A的时序,如果要观察8’h0C的发送时序,需要设置触发条件为TX的下降沿到来且i==4’b0010;

设置完成后点击Single Trigger我们可以看到发送“0A”的完整时序:

img

4.1****5 发送0A的完整时序

下面我们按照发送流程依次解读程序:

\1. (21-43)每0.5秒拉高一次isTX(也即TX_EN_Sig),发送完毕后再拉低TX_EN_Sig;(33-52)每发送一次,i自加1,i在0-5里循环,从而在0A - 0F里切换发送数据。

img

4.1****6 产生TX_EN_Sig和数据循环发送控制

\2. 当TX_EN_Sig为高时,Count_BPS计数,当Count_BPS=26时,BPS_CLK置1;注意跟UART收数不同的地方在于,收数时Count_BPS是在RX_EN_Sig为高且起始条件到来时(neg_sig==1)才开始计数。

img

4.1****7 产生BPS_CLK

\3. 当BPS_CLK为高时,State自加1,同时拉低TX,发出起始位:

img

4.1****8 产生TX开始标志(TX由高到低)

第一步到第三步的时序解释如下:

img

4.1****9 使能发送数据并发送起始位的时序

\4. State=1到8,从低位到高位发送TX_Data的数据:

img

图**4.**20 由低位到高位发送TX_Data

img

图**4.**21 由低位到高位发送TX_Data

\5. State=9到10,发送校验位和停止位

img

\6. State=11,发出TX_Done_Sig标志,State=12,拉低TX_Done_Sig,State归零

img

img

七、思考与拓展

(1)仔细阅读tx_control_module模块的程序,并参考图4.6绘出本实验中发送过程的简单示意图。

(2)目前是发送完一组数据等待计时到达0.5秒再发送下一组数据,更改程序,实现发送完一组数据后立刻发送下一组数据的功能,并使用ChipWatcher验证从0A发到0F的完整过程。

(3)建立一个工程,将接收到计算机发来的数据立即返还给计算机。

八、实验小结

实验14.1与实验14.2节分别描述了串口接收实验与串口发送实验,但仍存在部分限制。例如,当计算机使用UART连续向FPGA写入100个数据时,缺少一个存储区域把这100个数据有效存储起来。

一、实验设计目标

(1)在FPGA中实现串口协议,通过Anlogic_FPGA开发板上的“UART2USB”口接收从计算机发来的数据。

二、实验设计思路

UART串口是一种类似于USB、VGA的接口,有固定的引脚和通信协议。使用FPGA实现串口通信,可分为“计算机发送数据给FPGA”和“FPGA发送数据给计算机”两部分。本节为串口接收实验,使用FPGA接收从计算机发来的数据。

进行串口接收实验首先需要了解串口的接收时序,图4.1为接收一帧数据时的简单示意图。串口接收只使用一条数据线RX,特点如下:

img

4.1 串口接收时序示意图

(1)当RX产生了一个下降沿后才开始一帧数据的接收,接收到的第一位为开始位。

(2)一帧周期内有11个时钟,第一个时钟是开始位,2~9个时钟为数据位,并且数据是从低位到高位的接收的。

(3)串口的接收时钟频率取决于它的波特率,常用的波特率是有几个固定值的。本实验的波特率选用9600bps,表明1秒内传送9600位数据,则一位数据的周期为:

img (4.1)

可以看出,进行串口接收实验的关键在于准确读取RX上的数据,需做到以下两点:①判断什么时候RX开始接收一帧数据,即判断开始位;②判断什么时候读取D0~D7上的数据,即判断数据位。

为实现第①点,借鉴实验一PWM实验中的“消抖程序”的思想检测RX线上的信号,在RX信号产生了一个下降沿后才启动一帧数据的接收;并且,为保证每一次检测到的都是开始位,必须确定在一次接收中经过了11个时钟后才开始下一帧数据的接收,这可以通过编程实现。

对于第②点,为保证数据的准确,应在数据位的正中间读取数据。图4.2为读取数据的简单示意图,当BPS_CLK信号为高时,读取RX线上的数据,BPS_CLK信号的高电平出现的频率应为9600Hz。本实验首先将50MHz时钟通过计数器分频到500KHz,然后设计一个计数器Count_BPS,将500KHz的系统基准时钟进行分频,分频数为:

500000/9600 = 52 (4.2)

因此,当Count_BPS计数到26时,令BPS_CLK信号为高电平,其他计数值置低,这样使用500KHz时钟在BPS_CLK的高电平时读取数据,就可以在每一位数据的正中间读取数据,确保数据已完整建立。

img

4.2 数据读取示意图

三、功能模块图与输入输出引脚说明

图4.3是本实验设计的串口接收系统的示意图,包含开始位检测模块U1、波特率控制模块U2、接收控制模块U3和LED显示模块U4。

img

4.3 串口接收系统示意图

开始位检测模块用于检测一帧数据的开始位,功能如前所述。RX_In与串口的RX线相连,在没有数据传输时,RX线上为高电平;neg_sig为“下降沿标志位”,当检测到RX_In的下降沿时,neg_sig信号被置为1,其他时刻为0。

波特率控制模块内包含一个计数器Count_BPS,功能如前文所述。BPS_CLK为使能时钟信号,控制何时读取数据。Count_Sig为计数标志位,当一帧数据开始时,Count_Sig被置为1,此时计数器Count_BPS才能开始计数;当一帧数据接收完毕后,Count_Sig被置为0,计数器Count_BPS停止工作。

接收控制模块从RX_En_Sig信号上的数据帧中读取出数据位,将串行数据转换为并行8位数据并存储于RX_Data中。当一帧数据接收完毕时,“接收完成标志位”RX_Done_Sig被置为1。

LED显示模块将接收到的八位数据输送到LED灯上。

在FPGA中实现此系统,工程包含顶层模块DA与底层模块detect_module、rx_bps_module、rx_control_module、LED_display_module,底层模块从左至右依次对应于图4.3中的U1~U4。下面介绍一下顶层模块输入输出引脚的功能:

(1)CLK:50MHz的时钟信号输入。

(2)RSTn:复位输入信号,低电平有效。当RSTn信号为0时,LED_Out全为0。

(3)RX_In:接收输入信号。连接到串口的RX线。

(4)LED_Out:输送到LED灯,共有八位。LED_Out[7:0]存储了一帧数据的MSB至LSB位。

四、程序设计

​ 串口接收程序中的大部分代码功能已在前面的实验中使用过,容易理解,此处仅简单说明。

(1)图4.4是截取自底层模块rx_control_module的部分代码:

img

4.4 rx_control_module核心代码

​ 49-54:数据位存储。计算机发送数据时,是先发(最低有效位)LSB的,53行代码依次将RX_In信号的值存储于rData[0]~rData[7]中,rData的值最终将赋给RX_Data。

​ (2)图4.6是根据rx_control_module模块中的代码并结合图4.2画出的过程示意图,State信号内寄存当前状态值,最后的“12→13→0”的状态改变是在500KHz时钟上升沿立刻发生的,与前面不同,请结合程序理解。时序设计满足串口通信协议。

img

4.6 接收过程示意图

五、FPGA管脚配置

以下是Anlogic FPGA的IO Constraint,CLK信号与开发板上的50MHz的晶振时钟相连;RSTn信号与SWO相连;LED_Out[7:0]分别与LED7~LED0相连;RX_En_Sig信号与SW1相连;RX_In信号与T5管脚相连。

set_pin_assignment { CLK } { LOCATION = R7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[0] } { LOCATION = B14; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[1] } { LOCATION = B15; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[2] } { LOCATION = B16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[3] } { LOCATION = C15; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[4] } { LOCATION = C16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[5] } { LOCATION = E13; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[6] } { LOCATION = E16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[7] } { LOCATION = F16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { RSTn } { LOCATION = A9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { RX_En_Sig } { LOCATION = A10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { RX_Pin_In } { LOCATION = F12; IOSTANDARD = LVCMOS33; }

串口接收实验IO Constraint

六、实验结果

串口通信实验需要通过Anlogic_FPGA开发板上的USB转串口接口完成,因此,直接使用2根USB数据线分别将开发板上的JTAG接口和UART2USB接口与计算机相连,并在电脑上使用串口调试助手,按照以下参数配置:

img img

4.8 硬件连接和“串口助手”参数设置

图4.8是使用“串口调试助手”发送数据“AA”的参数界面,实验前需下载串口的驱动程序并安装,插拔UART2USB口的线缆,观察在串口号内是否出现和消失COMx,选择COMx,配置波特率为9600,数据位8位,停止位1位,无校验位和流控制。点击开启串口,选择HEX发送,键入“AA”,(设置ChipWatcher的捕获触发条件为RX引脚的下降沿,并点击)然后点击发送按钮。

为了更好的理解程序,我们使用ChipWatcher来捕获关键信号,加以诠释,设置ChipWatcher时,关键是设置RX的下降沿为捕获的触发条件,点击Single Trigger,软件会等待RX的下降沿到来才会抓取完整的数据并显示出来:

img

图**4.**9 Chipwatcher设置

img

图**4.**10 Chipwatcher进入等待触发条件状态

img

图**4.**11 这时在串口助手上点击发送,Chipwatcher捕获到完整的一帧RX时序

我们可以看到关键信号的产生和相互关系:

\1. 当RX的下降沿到来时,利用neg1和neg2产生确定起始条件的neg_sig,neg_sig的产生方法在detect_module.v中描述;

img

\2. 在State=0时,在500K时钟的上升沿采到neg_sig为1就将isCount即Count_Sig赋值为1,同时将State值自加1,进入起始位状态,这在rx_control_module.v里描述;

img

\3. 当Count_Sig为1时,Count_BPS开始计数,仅当Count_BPS =26时,BPS_CLK被赋值为1,时序如下图:

img

这在rx_bps_module.v里描述:

img

\4. 随后就是关键的状态转换,当Count_Sig为1时,Count_BPS以500K时钟频率在0-52循环计数,每到26,BPS_CLK就为高,此时State信号自加1,当State=2,3,4,5,6,7,8,9时,把RX_Pin_In的值移位存到RX_DATA里(可以看到起始状态下RX_DATA里存的是上一次的数据8’h55,随着State的递增,RX_Pin的值依次赋值给对应的位,最终输出这一次的数据。注意是先发低位,后发高位,最终RX_DATA=8‘b10101010),对应ChipWatcher的图更容易理解如下程序:

img

img

\5. State = 10和11,是校验位和停止位状态;

\6. State=12,isDone信号置1,Count_Sig归零;State=13,State和isDone信号归零;注意State = 12和13没有判断条件,只要500KHz上升沿到来就跳转,因此对UART连续接收的时序没有影响。

img

七、思考与拓展

(1)图4.6中,State的值在发生“12→13→0”的变化时,是随着基准时钟CLK的上升沿而变化的,也就是说,停止位的持续时间只有半个周期,但依然得到

了正确的实验结果,请结合程序从“实际有用数据”的角度解释其原因。

(1)在使用“串口调试助手”时,若选择“无校验位”,实验现象是怎样的?请结合程序及“一帧数据”的格式解释其原因。

一、实验设计目标

编写程序,使用FPGA调用存有正弦波表的ROM驱动R-2R DAC:

\1. 使用计数器生成地址线,产生固定频率的正弦波;

\2. 使用DDS方式产生地址信号驱动ROM,从而输出频率可调的正弦波。

二、实验设计思路

本实验中,第一步FPGA以500KHz时钟频率驱动一个计数器,产生0-1023的循环计数,并将其作为地址信号从ROM中顺序读取1024个点送给R-2R DAC,当ROM中存储一个完整的正弦波时,R-2R DAC输出的就是一个频率为500KHz/1024 = 488.28125Hz的正弦波。那么如何改变这个正弦波的频率呢?这就要使用DDS(直接数字合成)技术。

DDS信号源是能直接合成所需波形的一种频率合成器件,由频率合成器(相位累加器、波形查询表)和数模转换器构成。波形合成的基本思想与实验2.1中锯齿波的产生原理相同,只要控制送入数模转换器的数字值以一定规律周期性变化,便能得到所需的波形。

图2.7是DDS器件的核心部分流程图,包含相位累加器和波形查询表。CLK为工作脉冲,KW代表频率控制字。相位累加器内有一个N位寄存器,每来一个时钟脉冲CLK,相位累加器以步长KW递增,将相位累加器的高n位用作ROM的地址输入,即可完成从地址到波形幅值的映射工作。波形查询表ROM中存储了一个完整周期的正弦波,包含了2n个采样点的数据。查询表把送入的地址信息addr映射成所需波形的数字幅度信号,即输出Wave_Data。

img

2.7 核心部分流程图

DDS合成公式为:

img (2.3)

可以看出,当CLK的频率和N的值不变时,只要改变输入的频率控制字KW,即可改变输出波形的频率。

三、功能模块图与输入输出引脚说明

该工程包含顶层模块DAC_DDS,底层模块Phase_Acc和Sine_ROM。下面介绍一下顶层模块各引脚的功能:

(1)CLK:50MHz的时钟信号输入。

(2)SW_In:拨动开关输入,其中SW_In[0]是复位输入信号,低有效;SW_In[1]是ROM地址选择信号,为高时是计数器作为ROM地址,为低时是DDS的相位累加器输出的高10位作为ROM地址。

(3)DAC_Data:输送到R-2R电路的数字值,共有八位总线。

四、程序设计

DAC的驱动和计算公式在上一个实验中已经描述,这里主要描述下面两个关键点:

4.1 调用ROM IP,存储正弦波表

在上一个实验中,我们是以计数器的输出作为DAC的数据提供给R-2R DAC电路,而在这一个实验里,我们将在FPGA里存一张表,表里是一个完整的正弦波数字信号,接下来用计数器生成正弦波表的地址,就可以将正弦波表里的幅度数值输出给R-2R DAC,这样DAC的输出就是一个正弦信号。

首先我们需要生成一张正弦波表,可以用matlab,excel产生,也可以使用Mif_Maker2010来生成一个1024点8位数据宽度的正弦波表,程序如下:

1、如图2.15所示,打开Mif_Maker2010,点击设定波形里的全局参数。

img

图2.15 打开Mif_Maker2010,选择全局参数

设定全局参数如下,点击确定

img

设定波形中选择正弦波。

img

点击保存。

img img

图2.16 保存正弦波表为sine_1024.mif

将产生的sine_1024.mif文件复制到工程文件夹里面,下面我们调用TD的IP核来建立一个 ROM 查找表,步骤如下:

5、打开TD中的IP Generator管理器。如图2.17所示。

6、选择“Create a new IP core”,点击OK,将IP命名为sin_rom并存在dds程序文件夹下。如图2.18所示。

img

图2.17 选择IP Generator

img img

图2.18 创建IP名

7、在弹出来的IP核管理器中选择BRM下面的ROM。如图2.19所示。

img

图2.19 选择建立ROM IP

8、双击ROM后,进入ROM的设定页面。如图2.20所示设定ROM参数。

img

图2.20 配置ROM参数并将其初始化为sine_1024.mif的表

这样便生成了正弦ROM表的源文件。 在Top中可以直接调用这个ROM。

img

ROM的数据输出送到R-2R DAC的数据端口,ROM的地址ROM_ADDR由SW_In[1]选择是由计数器提供的0-1023循环,还是DDS的相位累加器模块产生的。两种地址的产生程序如下:

img

15 -28:分频器从50MHz时钟输入分频得到500KHz的时钟。

31-40: 500KHz时钟循环计数从0-1023,这样得到的正弦信号频率为500KHz/1024 = 488.2815Hz;

42-51:调用相位累加器模块产生地址,步进量KW可以手动修改,计算方法为KW=f*2^28/500000,这里2^28是指相位累加器的长度为28位,500000是我们的采样率500KHz。因此我们知道了期望频率后,就可以反推KW的值,例如本例中我们期望产生100Hz的正弦信号,通过上面公式计算得到KW=53,687.0912,我们这里取整得KW=53687,因此我们产生的100Hz实际上是99.99983Hz,并不是完美的100Hz,因此如果需要更精确的频率步进,可以使用更长的累加器位数,设计中上常用40位甚至更长。下面给出相位累加器的代码。

4.2 相位累加器模块

img

11-21: 设定一个28位的相位累加寄存器,然后在每个时钟(本例中是500KHz)的上升沿将其与频率控制字KW累加,一旦超过2^28,就折返回起始位置。

23:将相位累加寄存器的高10位取出来送给ROM的地址线,实现地址与幅度之间的转换。

五、FPGA管脚配置

set_pin_assignment { CLK } { LOCATION = R7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { div_clk } { LOCATION = T4; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[0] } { LOCATION = B14; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[1] } { LOCATION = B15; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[2] } { LOCATION = B16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[3] } { LOCATION = C15; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[4] } { LOCATION = C16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[5] } { LOCATION = E13; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[6] } { LOCATION = E16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[7]} { LOCATION = F16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW_In[0] } { LOCATION = A9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW_In[1] } { LOCATION = A10; IOSTANDARD = LVCMOS33; }

DA****C_DDS输出实验IO Constraint

六、实验结果

SW0拨至“UP”,SW1也拨到“UP”,DAC输出口上产生488Hz的正弦波,连接到口袋仪器的AIN1观察信号。如下图所示。

img

图2.6 ROM表使用计数器查表时输出的正弦波

img

图2.7 ROM表使用DDS查表法时输出的正弦波

大家可以自行计算其他的正弦信号频率计算出KW值进行验证。当输出频率变高时,可能需要在DAC输出后面加入模拟低通滤波器来平滑波形。

img

图2.7 DDS查表法输出10KHz时的正弦波(KW=5368709)

img

图2.7 DDS查表法输出50KHz的正弦波(KW=26843545)

一、实验设计目标

(1)编写程序,使用Anlogic 自带的ADC进行四通道数据轮询采集,同时介绍TD软件IP核的用法。

二、实验设计思路

本实验设计使用FPGA自带的12位串行AD芯片工作,将直流模拟信号转化为数字信号。

ADC分辨率为12bit,8个通道,采样率最大为1MSPS,如果4个通道工作,每通道最大采样率250KSPS。ADC 需要3.3V 模拟工作电压和模拟地以及一个独立的VREF 电压输入。8个模拟通道输入和用户IO 复用,用户实际可用通道数随芯片封装而异,当用户不需要ADC 时可以用作普通用户IO。采样时序如图3.1所示,每16个ADC时钟周期完成一次采样,输出一个EOC信号,ADC的驱动时钟最高16MHz,最高采样率1MSPS,EOC的时钟频率也即是最高1MSPS。

img

3.1 Eagle ADC 采样控制时序

图3.2表示了Anlogic内部AD具体的性能指标。

img

img

3.2 ADC内外部端口

ADC内外部端口如图3.3所示。其中内部端口为IP模块的端口。

img

3.3 ADC内外部端口

三、ADC 和PLL IP配置

实验采用了TD软件自带的IP核,可以方便的进行ADC的配置。详细的说明可以参考TD User Guide。打开使用手册方法入图3.4所示。

img

3.4 TD User Guide打开方法

IP核的调用参考前面的介绍,本节介绍了AD核的具体实例化。

\1. 打开IP Generator里的AD模块,如图3.5所示。

img

3.5选择内部AD采集的IP

\2. 勾选需要进行采样的AD通道,然后在左上角输入模块的名称。EG4系列一共有8个通道,本次实验只采集通道CH1,2,3,4的数据,如图3.6所示。

img

3.6 AD IP核参数配置

\3. 最后实例化这个IP。

img

3.7 AD IP的例化

程序的解析后面我们会加以说明。

根据数据手册AD的最大输入时钟为16MHz,而实验板的时钟为50MHz,因此我们调用PLL模块,来产生16MHz的ADC时钟。EAGLE系列 FPGA最多内嵌有4个多功能锁相环(PLL0~PLL3),可实现高性能时钟管理功能。每个PLL都能实现时钟分频 /倍频、输入和反馈时钟对准、多相位时钟输出功能。

img

图3.8 EAGLE PLL 特性表

创建IP核PLL模块:

\1. 打开IP Generator里的PLL模块,如图3.9所示。

img

图3.9 创建PLL模块

\2. 在General设置界面,根据实验需求,选择输入时钟为50MHz,其他的设置保持默认。也可以根据自己的需要,选择反馈设置和动态设置。详细使用方法,可以参考TD_User_Guide文档。

img

图3.10 PLL模块时钟输入配置

\3. 点击右下角的Next,进行时钟输出配置。本实验中我们使用C0产生200M时钟用于Chip Watcher采样时钟,使用C1产生16MHz的ADC时钟,其余设置保持默认。

img

图3.11 PLL模块时钟输出配置

\4. 点击 Next, 选择输出时钟的带宽,本实验也选择默认Medium设置。如果用户需要更快的时钟锁定,需要把BandWidth选择为High,如果用户需要更小的时钟抖动,需要把Bandwidth设为Low。

img

图3.12 PLL模块时钟输出带宽配置

\5. 点击Next,检查配置信息,完成PLL模块功能。

img

图3.12 PLL模块时钟输出带宽配置

\6. 最后实例化这个IP。Standby和reset功能暂时不启用,设置为1’b0。注意给CW_CLK加上 //synthesis keep;以免被编译器优化。

img

图3.12 实例化PLL模块

四、程序设计

img

24-34,使用EOC信号产生CH1-CH4的地址信号,3’b001 - 3’b100间循环。

37-38,定义ADC的输出数据,注意我们整个程序里没有使用ADC_Data的低四位,这会导致编译的时候低四位被优化。我们这里使用//synthesis keep;语句来保证编译后该信号能够整体保留,以便我们在ChipWatcher里观察。

41-50,调用ADC的例化。

53,将ADC通道1采样后的数据的高八位放到LED上观察。

五、FPGA管脚配置

下面是Anlogic FPGA的IO Constraint,CLK是50MHz时钟输入,EOC信号我们把它送到插座上观察,LED0-LED7用来观察ADC CH1的直流采样结果(取高8位);RSTn、start输入信号分别与开发板上的SW0、SW1相连。ADC输入通道不需要在IO Constraint里配置,我们需要对照原理图查找插座上的输入位置。

set_pin_assignment { CLK } { LOCATION = R7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { RSTn } { LOCATION = A9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { start } { LOCATION = A10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { EOC } { LOCATION = T4; IOSTANDARD = LVCMOS33; }

img img

ADC 输入端口CH1-M10,CH2-L10,CH3-P11, CH4 - M12

六、实验结果

使用口袋仪器的“信号源”向ADC的CH1和CH4提供直流1V,向CH2和CH3提供直流2V,ADC的EOC信号接示波器通道1,并注意口袋仪器和开发板需共地。

img

3.2****2 硬件连接示意图

(1)图3.23是使用口袋仪器EPI-m204产生的直流信号及参数说明。

img

3.2****3 AD转换前的直流信号

(2)图3.24是EOC信号,可以看到周期是1uS,频率1MHz

img

图3.24 实测EOC信号

(3)图3.24是使用200MHz观察时钟的Chip Watcher得到的ADC_CLK (16MHz),EOC (1MHz),ADC_Data和Channel的时序图,可以看到ADC_Data比Channel信号滞后一个周期(3’b001对应的是CH4数据,3’b010对应的CH1,3’b011对应的CH2,3’b100对应的CH3),CH4和CH1采到的值分别为1227和1242,CH2和CH3采到的值分别为2458和2464。

img

3.24 Chip Watcher****工作图

(4)通过 Vin = Vref * Code/4096 = 3.3V * Code/4096可以换算得到实际采到的电压值。

一、实验设计目标

编写程序,使用FPGA驱动R-2R电路,输出正弦波。

二、实验设计思路

任意复杂的数字电路都可以使用FPGA实现,但当关联上模拟信号时,就需要AD、DA芯片作为转换器件。AD是把模拟信号转变成数字信号,再送给FPGA做数字算法处理;DA则是将FPGA处理过后的数字信号转变成模拟信号。

本实验由FPGA的8个管脚输出的二进制数值,经过R-2R电路转换后输出相应的模拟值,改变输入的数字值便可得到幅度变化的模拟信号输出。R-2R DAC的实物位置和电路原理图如图2.1所示。

img

img

2.1 R-2R DAC****电路

输出电压与DAC_D0-DAC_D7的码值关系为:DAC_VOUT = 3.3V * 码值/256

本实验使用FPGA驱动R-2R电路,由按键KEY0控制R-2R DAC的输入码值是来自拨动开关SW0-7,还是来自计数器产生的循环递增信号。

三、功能模块图与输入输出引脚说明

下面介绍一下顶层模块各引脚的功能:

(1)CLK:50MHz的时钟信号输入。

(2)DAC_Data:输送到R-2R电路的数字值,共有八位,引脚与LED复用。

(3)row:矩阵按键的行信号;这里固定输出4’b0111;

(4)col:矩阵按键的列信号;配合行线输出的4’b0111来判断KEY0和KEY1是否按下。其中col[0]/KEY0用来选通DAC输入数据来源,col[1]/KEY1用来实现低电平有效的全局复位。

(5)SW_In:拨动开关输入;

四、程序设计

img

21-31:从50MHz分频产生100KHz的时钟

35-43:使用100KHz时钟产生0-255的循环计数

46:使用按键KEY0选择送给DAC的码值是来自拨动开关还是计数器输出

五、FPGA管脚配置

set_pin_assignment { CLK } { LOCATION = R7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { div_clk } { LOCATION = T4; IOSTANDARD = LVCMOS33; }

set_pin_assignment { col[0] } { LOCATION = E11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { col[1] } { LOCATION = D11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[0] } { LOCATION = B14; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[1] } { LOCATION = B15; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[2] } { LOCATION = B16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[3] } { LOCATION = C15; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[4] } { LOCATION = C16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[5] } { LOCATION = E13; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[6] } { LOCATION = E16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DAC_Data[7]} { LOCATION = F16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[0] } { LOCATION = D9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[1] } { LOCATION = F9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[2] } { LOCATION = C10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[3] } { LOCATION = E10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW_In[0] } { LOCATION = A9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW_In[1] } { LOCATION = A10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW_In[2] } { LOCATION = B10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW_In[3] } { LOCATION = A11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW_In[4] } { LOCATION = A12; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW_In[5] } { LOCATION = B12; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW_In[6] } { LOCATION = A13; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW_In[7] } { LOCATION = A14; IOSTANDARD = LVCMOS33; }

六、实验结果

当KEY0未按下时,DAC输出为SW_In的数字码值转换为的模拟电压。例如当SW_In=8’b00111111时,示波器采到DAC输出为815mV,与计算得到的3.3V*63/256 = 812mV是吻合的。其他码值与电压的关系可以自行验证。

img

当KEY0按下时,DAC输出dac_cnt的计数值,该计数值从0-255循环,输出的模拟波形如下图所采集,锯齿波的频率为100KHz/256 = 390Hz,峰峰值3.3V。

img

2.6 R-2R DAC输出的锯齿波

一、实验设计目标

本文实现一个二进制转十进制的电路,8位拨码开关(SW7-SW0)作为一组8位二进制输入信号,程序将8位二进制输入转为3位十进制表示,经由数码管显示输出。

二、实验设计思路

在数字逻辑设计课程中,我们已经学过了BCD码的相关知识,它用4位二进制数来表示1位十进制数中的09,是二进制编码的十进制代码,常见的BCD码有8421BCD码,2421BCD码,5421BCD码,余3码以及格雷码等等。在这篇文章中,我们所采用的BCD码为8421BCD码,8421BCD码各位的权值为8、4、2、1,用00001001表示十进制数,其它几种BCD码编码方式在这里不过多赘述。

实现这样的一个效果,可以先大致的将电路分为二进制转化和数码管显示两个部分,框架如图10.1所示,本实验主要讲述二进制转化部分。

img

图10.1 二进制转BCD码动态显示框架

二进制转BCD码看起来最简单的方法是用除法运算来获得百位、十位、个位上的值,但实际上这种方式取模会占用过多资源,底层电路设计十分复杂,这告知我们选择合适的算法对于系统是有很大帮助的,因此通常使用加3移位法实现二进制转BCD码。

在这里简单介绍一下加3移位法的原理。我们知道,四位二进制大于15才进位,而8421BCD码是大于9就进位,若四位二进制大于9时进位,这样得到的就是15的BCD码,因此将大于9的四位二进制数加6就能得到其BCD码。对于大于四位的二进制数,通过左移,逢9加6进位,即可转换任意位的二进制数,比如说,对于5位二进制数,由高4位二进制数左移一位得到,那么将前4位得到的BCD码也左移一位,并重新判断低四位是否大于9,若大于9,则加6进位,即可得到5位二进制数对应的BCD码。

加3移位法相比与加6移位法在算法上的结果是等效的,但占用的资源更小,相比于加6移位法先移位,再判断低4位是否大于9,加3移位法是先判断低四位是否大于4,再进行移位。值得一提的是,加3移位法对于最后的低4位而言无需判断低4位是否大于4,因为已经没有移位操作了。

参考网上的一个将八位二进制数255,将其转为8421BCD码的例子给大家加以说明。

0 1111 1111; //原数

1 0000 0001; //左移一次

2 0000 0011; //左移二次

3 0000 0111; //左移三次,检查低四位>4?

3.1. 0000 1010; //大于4,加3进行调整

4 0001 0101; //左移四次,检查低四位>4?

4.1. 0001 1000; //大于4,加3进行调整

5 0011 0001; //左移五次

6 0110 0011; //左移六次,检查高四位>4?

6.1. 1001 0011; //大于4,加3进行调整

7 1 0010 0111; //左移七次,检查低四位>4?

7.1. 1 0010 1010; //大于4,加3进行调整

8 10 0101 0101; //左移八次(得到BCD码255)

理解了算法,接下来就是如何实现算法。实现同样功能有不同的算法,同样的,实现算法也有各式各样的电路。如何让实现的电路简单有效,就是FPGA工程师的价值所在。直白的看来,实现加3移位法,需要通过寄存器进行若干次的移位,并判断检查是否需要进位,这样“简单粗暴”实现的电路,完成一组数据的转化需要经过多个时钟周期,电路实际上也较为复杂。在这里,针对8位二进制数,我们实现的加3移位法电路结构如图10.2所示。

img

图10.2 加3移位法电路

在这个电路中,Adder模块实现输入大于4就加3进位的功能,再通过人为的“移位”操作,就实现了加3移位法。具体实现过程可以理解为,首先,0和高三位(a7、a6、a5)的数据构成左移三次后的低四位二进制数据,经过Adder1模块后,得到调整后的数据;调整后数据的低三位和a4构成左移四次后的低四位数据,这四位数据作为Adder2模块的输入,就实现了“移位”,再通过Adder2进行调整数据即可;Adder3部分与Adder2部分一致,实现了左移五次的数据修正;这时,经过Adder1、Adder2和Adder3处理后的数据与a2构成了左移六次的数据,这七位有效数据的高三位可能会大于4,因而高四位和低四位都需要进行数据的调整,也就是分别通过Adder4和Adder5来修正;Adder6和Adder7与Adder4和Adder5一致,实现左移七位后的数据调整;最后,在高位填0并与最低位的a0构成最终左移八次的十二位数据。

Adder模块的功能为当输入大于4时,加3调整,当输入小于等于4时,保持原值,其电路结构如下,通过比较器,多路选择器和加法器实现功能。图10.3是Adder模块的功能框图。

img

图10.3 Adder模块功能图

数码管动态显示模块和开关的使用方法可以参考之前的实验。数码管动态显示框图如图10.4所示。

img

图10.4 数码管动态显示框图

三、功能模块图与输入输出引脚说明

该工程包含顶层模块binary2bcdlight与底层模块binary2bcd、adder3、和Digitron_NumDisplay_module。整个工程的模块功能图参考图10.4即可。下面介绍一下顶层模块各主要引脚的功能:

(1)clk:50MHz的基准时钟信号输入。

(2)SW:开关输入,8个数码管的状态值最终显示在数码管上。

(3)Digitron_Out:七段数码管的显示输出,共有八位总线。

(4)DigitronCS_Out:数码管的片选信号,共有四位总线。

四、程序设计

下面给出了二进制转BCD码模块的参考代码。通过七个adder3模块以及人为的移位实现转换。

img

10.5 二进制转BCD码模块

下面是adder3模块的代码,通过case语句实现功能。

img

10.6 加三方法模块

接下来是动态显示的各部分代码,与之前实验相同不赘述。

五、FPGA管脚配置

下面是Anlogic FPGA的IO Constraint图,CLK时钟输入信号、数码管片选信号DigitronCS_Out和数码管输出显示信号Digitron_Out的配置方式与之前相同, SW0-SW7控制数码管显示的数据。相应引脚连接信息已经在第一篇的1.3节中做过详细介绍,这里就不再赘述。

set_pin_assignment { DigitronCS_Out[0] } { LOCATION = C9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[1] } { LOCATION = B6; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[2] } { LOCATION = A5; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[3] } { LOCATION = A3; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[0] } { LOCATION = A4; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[1] } { LOCATION = A6; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[2] } { LOCATION = B8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[3] } { LOCATION = E8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[4] } { LOCATION = A7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[5] } { LOCATION = B5; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[6] } { LOCATION = A8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[7] } { LOCATION = C8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { clk } { LOCATION = R7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW[0] } { LOCATION = A9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW[1] } { LOCATION = A10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW[2] } { LOCATION = B10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW[3] } { LOCATION = A11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW[4] } { LOCATION = A12; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW[5] } { LOCATION = B12; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW[6] } { LOCATION = A13; IOSTANDARD = LVCMOS33; }

set_pin_assignment { SW[7] } { LOCATION = A14; IOSTANDARD = LVCMOS33; }

六、实验结果

当把开关拨到不同的状态,数码管会显示不同的二进制转成的数据。

一、实验设计目标

(1)编写程序,使用FPGA产生脉冲调制(PWM)信号,且信号的周期和占空比可通过按键调节。

(2)进一步巩固之前学习到的矩阵键盘、按键消抖的功能。

二、实验设计思路

PWM信号可用于控制步进电机的工作,图1.1是一个PWM信号的示意图。这个脉冲的周期为Period,宽度为1的那段时间称为脉冲宽度,占空比定义为高电平信号占整个脉冲周期的百分比,即:

img (1.1)

img

1.1 一个PWM信号

要产生这样的PWM信号,基本思想就是使用一个计数器,当计数值Cnt1小于脉冲宽度时,让PWM信号为1;当Cnt1大于等于脉冲宽度时,让PWM信号为0;当Cnt1的值等于Period-1时,计数器复位,Cnt1变为0。循环往复便产生了一个连续的PWM信号。

该PWM信号发生器系统由占空比、周期调整模块,PWM信号产生模块和数码管显示模块组成,图1.2是该系统的示意图。

img

1.2 PWM信号发生器示意图

占空比、周期调整模块用于调整PWM信号的占空比Duty和周期Period,设置四个按键开关,分别控制占空比增加、占空比减少、周期增加和周期减少。输出信号Duty用于控制PWM波的占空比,取值范围为0~100;Count_P内存储了一个数值,用于表征周期Period。需要注意的是,当按下一次按键开关时,FPGA可能识别到多次操作或未识别到操作,所以,U1模块内增加“按键消抖模块”用于准确识别按键操作。

PWM信号产生模块用于产生PWM信号。Count_D内存储了一个数值,用于表征脉冲宽度,计算公式为

img (1.2)

U2模块内有一个计数器Cnt1,按照前文提到的基本思想,比较Cnt1和Count_P、Count_D的数值,即可产生所需的PWM波。

​ 数码管显示模块用于显示当前PWM信号的Duty、Count_P和Count_D,便于观察验证实验结果。因数码管个数限制,增加开关信号Count_D_Display与Count_P_Display用于切换数码管界面,这与第二篇的实验八中的思想相同。

三、功能模块图与输入输出引脚说明

该工程较包含顶层模块pwm与底层模块Duty_Period_Adjust_module、PWM_Generate_module、Digitron_NumDisplay_module,整个工程的模块功能图参考图1.2即可。下面介绍一下顶层模块各主要引脚的功能:

(1)CLK:50MHz的时钟信号输入。

(2)RSTn:复位输入信号。当RSTn为低电平时,PWM信号的周期、占空比分别置为5ms和50%。

(3)col,row:键盘输入信号。因为只用了KEY0和KEY1两个按键,因此我们将row设置为4’b0111;KEY0对应col[0],控制PWM的周期,每按一下,相应参数变化一次。KEY[1]对应col[1],控制PWM占空比,每按一下,相应参数变化一次。

(4)Count_D_Display、Count_P_Display:拨动开关输入信号,高电平有效,分别控制数码管显示Count_D和Count_P。

(5)Digitron_Out、DigitronCS_Out:分别为七段数码管的显示输出信号和扫描驱动信号。

(6)PWM_LED_Out:U2模块产生的PWM信号,输出到LED灯,共有一位总线。因为PWM信号的平均直流值和占空比是成比例的,可以通过LED灯的亮度变化来定性观察PWM信号的占空比的变化情况。

(7)PWM_EPI_Out:与(6)相同,也是所产生的PWM信号,输送到硬木课堂口袋实验平台中,定量观察PWM信号。

四、程序设计

​ (1)图1.3是截取自底层模块PWM_Generate_module的部分代码:

img

1.3 PWM_Generate_module核心代码

15:计算Count_D,见公式(1.2)。

​ 29-32:使用一个简易的计数器Cnt1,通过比较Cnt1与Count_D的值,决定信号PWM_Out电平的高或低。

五、FPGA管脚配置

以下是Anlogic FPGA的IO Constraint,CLK、DigitronCS_Out、Digitron_Out、col和row信号已经多次用到,不再赘述;复位输入信号RSTn与开发板上的SWO相连;Count_D_Display、Count_P_Display 输入信号分别与开发板上的SW1、SW2相连;输出信号PWM_LED_Out送到LED7;PWM_EPI_Out送到开发板上侧的I/O通道“T4”中,通过“T4”口送往硬木口袋实验平台。

set_pin_assignment { Count_D_Display } { LOCATION = A10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { RSTn } { LOCATION = A9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Count_P_Display } { LOCATION = B10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { CLK } { LOCATION = R7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[0] } { LOCATION = C9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[1] } { LOCATION = B6; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[2] } { LOCATION = A5; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[3] } { LOCATION = A3; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[0] } { LOCATION = A4; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[1] } { LOCATION = A6; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[2] } { LOCATION = B8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[3] } { LOCATION = E8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[4] } { LOCATION = A7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[5] } { LOCATION = B5; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[6] } { LOCATION = A8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[7] } { LOCATION = C8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { col[0] } { LOCATION = E11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { col[1] } { LOCATION = D11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { col[2] } { LOCATION = C11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { col[3] } { LOCATION = F10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[0] } { LOCATION = D9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[1] } { LOCATION = F9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[2] } { LOCATION = C10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[3] } { LOCATION = E10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { PWM_LED_Out } { LOCATION = F16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { PWM_EPI_Out } { LOCATION = T4; IOSTANDARD = LVCMOS33; }

六、实验结果

本实验使用硬木课堂口袋实验平台观察PWM波形,使用LED7定性观察PWM波的占空比变化情况,使用数码管显示Duty、Count_D和Count_P的值。表1.1和表1.2分别是在调节PWM波时信号Duty和信号Count_P可能出现的数值,十六进制数值即数码管上所显示的数值,表1.2还列出了相应的PWM波的频率。

1.1 Duty信号数值

十进制数值 00 10 20 30 40 50 60 70 80 90 100
十六进制数值 00 0A 14 1E 28 32 3C 46 50 5A 64

1.2 Count_P信号数值

十进制 (img 50 100 150 200 250 300 350 400 450 500
十六进制 C350 186A0 249F0 30D40 3D090 493E0 55730 61A80 6DDD0 7A120
PWM的 频率(Hz) 1000 500 333 250 200 167 143 125 111 100

图1.6是使用EPI观察到的初始状态时的PWM波形。当按下按键KEY0和KEY1后,波形、LED0的亮度、数码管上的数值都会有一定变化,请结合程序及表1.1、表1.2自行验证。

img

七、思考与拓展

(1)请仔细阅读代码,计算PWM信号的周期的可调范围,并与实际结果比对。

(2)请参照表1.1,列出当PWM信号的频率固定为100Hz时,Count_D可能出现的数值。

一、实验设计目标

在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式。在矩阵键盘中,行线和列线不直接连通,而是通过一个按键进行连接。本实验实现的功能非常简单,为矩阵键盘的十六个按键编号为0~15,按下哪一个按键,数码管就显示该按键对应编号的十六进制数。

二、实验设计思路

如下是本文所使用的FPGA开发板上板载矩阵键盘的原理图。

img

9.1 矩阵键盘原理图

从图9.2中可以看到,键盘的列线已经都设置了上拉电阻,在没有按下按键时,键盘列线的为高电平。若给键盘的某一行输入低电平,并按下该行的一个按键,那么该按键连接的列线的输出就会被拉低。因此,使用FPGA驱动矩阵键盘时,一般通过输送高/低电平给矩阵键盘的行信号,并读取键盘列信号来判断是否有按键按下。举个例子,按照上图中,Key_Row[0]Key_Row[3]为矩阵键盘的行信号,Key_Col[0]Key_Col[3]为矩阵键盘的列信号,若给Key_Row[0]输送低电平,读取键盘列信号Key_Col[3:0]的结果为1011,则说明KEY14被按下了。

img

9.2 基于FPGA的矩阵键盘驱动

对于列线已设置上拉的矩阵键盘,应该通过对所用的行线输送低电平信号,读取列信号的值,如果某一列的信号低电平,则说名该行列交叉处的按键被按下。由于本例中将使用所有的按键,如果对所有的行线都输送低电平信号,即使读到了某一列的电平信号为低也无法判定是哪一行的按键,这时就需要采用行扫描法。其原理也非常简单,即按照合适的扫描频率依次给各行输送低电平信号,当前行列电平信号均为低的按键就是被按下的按键。

img

9.3 顶层电路框图

根据实例二的需求进行功能划分,一共有四个功能模块,分别是按键扫描模块、按键消抖模块、独热码转BCD码模块和数码管段码译码模块。其中,按键扫描模块负责监测矩阵键盘的列信号并对矩阵键盘进行逐行扫描,并将判定后的按键信号输出;按键消抖模块负责接收按键扫描模块得到的按键信号并进行消抖处理;独热码转二进制数模块则用于将消抖后的按键信号转换为二进制数;最后由数码管段码译码模块进行译码并驱动数码管的显示。

三、功能模块图与输入输出引脚说明

该工程包含顶层模块keyboard_instance与底层模块keyboard_scan、key_filter、onehot2binary和Digitron_NumDisplay_module。整个工程的模块功能图参考图9.3即可。下面介绍一下顶层模块各主要引脚的功能:

(1)clk_50M:50MHz的基准时钟信号输入。

(2)RSTn:系统复位输入信号。低电平有效,控制是否开始扫描矩阵键盘。

(3)col:矩阵键盘的列信号。

(4)row:矩阵键盘的行信号,通过顺序对4个信号赋值0,获取按键信息。

(5)Digitron_Out:七段数码管的显示输出,共有八位总线。

(6)DigitronCS_Out:数码管的片选信号,共有四位总线。

四、程序设计

下面给出了矩阵扫描模块的参考代码,通过移位操作周期性地为矩阵键盘的行信号输送1110、1101、1011、0111的电平信号,实现行扫描。可以注意到,在时序设计上,行信号的移位操作是在扫描时钟的上升沿进行的,而列信号的采集是在扫描时钟的下降沿进行的。分析代码不难得出,如果有按键按下(这里仅考虑一次只按下一个按键),该模块的按键信号输出key会是一个16bit的独热码。

img

9.4 矩阵键盘扫描方式

下面给出了独热码转二进制模块,实现将消抖后的按键信号转化为可供现成的数码管段码译码模块进行显示译码的编码。由于位数较少,直接使用查表的方法实现。另外,该模块引入了时序电路,还含有实例一中那个带使能端的寄存器类似的功能,即松开按键后,该模块的输入为16bit的全零信号,此时按照如下代码的描述将不会更新编码输出,而是保持上次按下按键时相同的输出,故不需要一直按住按键来保持数码管的显示。

img

9.5 独热码转二进制模块

五、FPGA管脚配置

以下是Anlogic FPGA的IO Constraint,CLK时钟输入信号、数码管片选信号DigitronCS_Out和数码管输出显示信号Digitron_Out的配置方式与实验六相同,DIG1~DIG2显示按键信息,复位信号RSTn与SW0相连,col和row与矩阵键盘8个引脚相连。

set_pin_assignment { DigitronCS_Out[0] } { LOCATION = C9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[1] } { LOCATION = B6; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[2] } { LOCATION = A5; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[3] } { LOCATION = A3; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[0] } { LOCATION = A4; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[1] } { LOCATION = A6; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[2] } { LOCATION = B8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[3] } { LOCATION = E8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[4] } { LOCATION = A7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[5] } { LOCATION = B5; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[6] } { LOCATION = A8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[7] } { LOCATION = C8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { RSTn } { LOCATION = A9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { clk_50M } { LOCATION = R7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { col[0] } { LOCATION = E11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { col[1] } { LOCATION = D11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { col[2] } { LOCATION = C11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { col[3] } { LOCATION = F10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[0] } { LOCATION = D9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[1] } { LOCATION = F9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[2] } { LOCATION = C10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { row[3] } { LOCATION = E10; IOSTANDARD = LVCMOS33; }

六、实验结果

当按下矩阵键盘时候,数码管最低位会以16进制显示编号。

一、实验设计目标

在FPGA中设计实现一个多功能数字钟,具备以下功能:

(1)准确计时。能显示时、分、秒,小时的计时为24进制,分和秒的计时为60进制。

(2)校时功能。时、分可调。

(3)准点报时。当“时-分-秒”为“XX-59-50、XX-59-52、XX-59-54、XX-59-56、XX-59-58”时,蜂鸣器发“嘀”;当“时-分-秒”为“XX-00-00”时,扬声器发“嗒”。

二、实验设计思路

本节实验是在综合了前面几节实验知识点的基础上开展的,容易理解和掌握,该数字钟系统由计时、校时模块,数码管显示模块和整点报时模块组成,图8.1是该系统的示意图。

img

8.1 数字钟系统示意图

计时、校时模块能工作于“计时”和“校时”两个状态。本模块内含一个分频电路用于产生标准秒脉冲。设置3个开关用于调整时间,包括小时调整开关AdjtHour和分钟调整开关AdjtMin,当其有效时,模块停止计时,工作于“校时”状态。输出信号包括秒个位SecL、秒十位SecH、分个位MinL、分十位MinH、时个位HourL、时十位HourH。

数码管显示模块用于显示“时、分、秒”。Anlogic_FPGA开发板上只有4个数码管,但要显示包括“时、分、秒”在内的6个时间信号,因此,我们增加一个“小时显示输入(DispHour)”信号,用于查看当前小时,高电平有效。当要调整小时,也需要使用数码管显示小时界面, AdjtHour信号便是用于告知这一点的。

整点报时模块的关键在于判断出当前时刻。使用if语句判断是否满足报时条件,通过改变输送到蜂鸣器上的导通信号的频率来改变蜂鸣器发声的频率,这在实验七中已经用到过,工作原理相同。

三、功能模块图与输入输出引脚说明

img

如上图,该工程包含顶层模块clock与底层模块TimeKeeper_module、Digitron_TimeDisplay_module和Buzzer_module。下面介绍一下顶层模块各主要引脚的功能:

(1)CLK:50MHz的基准时钟信号输入。将其50_000_000分频得到1HZ的数字钟工作频率CLK_1HZ。将其100_000分频得到500HZ的蜂鸣器报时频率“Di”,将其50_000分频得到1KHZ的蜂鸣器报时频率“Da”。将其200分频得到七段数码管的扫描频率。

(2)RSTn:系统复位输入信号,连接KEY0,低电平有效,我们将Key_Row置位4’b0111,当KEY0按下时,Key_Col[0]就输出低电平。复位后“小时-分钟-秒”置为“00-00-00”。

(3)DispHour:小时显示输入信号,低电平有效,我们将Key_Row置位4’b0111,当KEY1按下时,Key_Col[1]就输出低电平,经过按键消抖模块后,消抖模块的输出由低变高表示按键按下。当DispHour或AdjtHour信号有效时,数码管DIG3-DIG2显示当前小时,DIG1-DIG0显示当前分钟;当DispHour和AdjtHour信号均无效时,数码管DIG3-DIG0显示当前“分、秒”。

(4)AdjtHour 、AdjtMin:小时、分钟调节输入信号,高电平有效。当AdjtHour=1时,每来一个标准秒脉冲CLK_1HZ的上升沿,小时增加一。调整分钟开关相似。AdjtHour 、AdjtMin信号的优先级从左至右依次降低。

(5)Buzzer_Out:输出到蜂鸣器,通过改变输送到蜂鸣器的导通信号的频率,来改变蜂鸣器发声的频率,控制蜂鸣器产生“嘀、嗒”的报时声。

(6)Digitron_Out:七段数码管的段码显示输出,共有八位。

(7)DigitronCS_Out:数码管的片选信号,共有四位。扫描频率为250KHz。

四、程序设计

(1)图8.2是截取自底层模块TimeKeeper_module的部分代码,理解了此段程序则可以相应理解该模块的所有程序:

img

8.2 TimeKeeper_module代码

45-62:当AdjtHour为1时,系统进入“校时”状态,开始调整“小时”。若时个位为9,则时个位清零并向时十位进位;若时十位、时个位为“23”,则时十位、时个位清零;若以上均不满足,则时个位加1,时十位不变。

五、FPGA管脚配置

以下是Anlogic FPGA的IO Constraint,CLK时钟输入信号、数码管片选信号DigitronCS_Out和数码管输出显示信号Digitron_Out的配置方式与实验六相同,DIG3DIG0依次显示“分、秒”,DispHour按下时DIG3DIG0依次显示“小时、分”;复位信号RSTn和小时显示信号DispHour分别与KEY0和KEY1相连;调整开关AdjtHour 、AdjtMin依次与SW0、SW1相连;Buzzer_Out信号输出到蜂鸣器引脚。

set_pin_assignment { AdjtHour } { LOCATION = A10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { AdjtMin } { LOCATION = A9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Buzzer_Out } { LOCATION = H11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { CLK } { LOCATION = R7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[0] } { LOCATION = C9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[1] } { LOCATION = B6; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[2] } { LOCATION = A5; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DigitronCS_Out[3] } { LOCATION = A3; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[0] } { LOCATION = A4; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[1] } { LOCATION = A6; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[2] } { LOCATION = B8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[3] } { LOCATION = E8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[4] } { LOCATION = A7; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[5] } { LOCATION = B5; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[6] } { LOCATION = A8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Digitron_Out[7] } { LOCATION = C8; IOSTANDARD = LVCMOS33; }

set_pin_assignment { DispHour } { LOCATION = D11; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Key_Row[0] } { LOCATION = D9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Key_Row[1] } { LOCATION = F9; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Key_Row[2] } { LOCATION = C10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { Key_Row[3] } { LOCATION = E10; IOSTANDARD = LVCMOS33; }

set_pin_assignment { RSTn } { LOCATION = E11; IOSTANDARD = LVCMOS33; }

六、实验结果

当SW1、SW0拨至“UP”时,分别调整“时、分”,且优先级从高到

低排列为SW0、SW1;KEY1按下时DIG3,DIG2显示“小时”,DIG1,DIG0显示分钟;KEY0按下时时钟复位,“时、分、秒”置为“00、00、00”;另有整点报时功能。

七、思考与拓展

(1)自行设计程序或查阅相关资料,实现以下功能:①显示“年-月”;②“定时闹钟”功能。