小梅哥

  • 2020-02-29
  • 发表了主题帖: 【工程源码】FPGA免费视频课程观看地址和介绍

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。    1、《FPGA设计思想与验证方法视频教程》 该视频属于小梅哥的经典之作,共20集,按照循序渐进的方式,依次讲解FPGA开发中的各种常见设计和调试方法,并涵盖了非常多的应用知识点,整个课程全部现场讲解原理,现场写代码调代码,非常适合0基础的用户学习。以下为课程在线观看链接。 https://www.bilibili.com/video/av73705557   本链接仅用作预览用,如需观看全集高清,可以进入B站查看原视频。

  • 发表了主题帖: 【工程源码】数字信号处理学习——混频器

    混频 混频是指将信号从一个频率变换到另外一个频率的过程 ,其实质是频谱线性搬移的过程。简单的说,就是两个信号相乘。相乘的结果就得到两种频率,和频、差频。可以用积化和差公式观察和频、差频信号的产生。   在模拟电路中经常见到的就是把接收机接收到的高频信号,经过混频变成中频信号。上次课设用三极管搭了一个混频器,频率太高,搞到头大!最后还是用了乘法器芯片。当然这次设计的是数字混频。 数字混频在通信的调制、解调、数字上变频、数字下变频等系统中应用广泛。通常把其中一个信号称为本振信号,另一个信号称为混频器的输入信号。 程序设计 程序中我将本振信号设为1MHz,输入信号设为625kHz,采样频率就定为时钟的50MHz。   其中本振信号和输入信号都用DDS来模拟产生(直接用梅哥的DDS历程,这里就不往出贴了)。要注意的是在FPGA设计中,IP核几乎都是采用二进制补码带符号数,也有很多的ADC、DAC芯片的数据接口也采用的是二进制补码。因此,在设计中,ROM中的正弦数据我都改成了带符号数。混频处理的数据也是带符号数二进制补码,则在整个混频程序设计中都要保持这个数值表示方法,否则就会出错。   module Mixer(       rst_n,       clk,   //  din,       dout   );          input       rst_n;       //复位信号,高电平有效       input       clk;         //数据采样时钟/FPGA系统时钟,频率为50MHz   //  input      [11:0] din;  //输入的625KHz单频信号       output  [23:0] dout; //输出混频滤波后的的1.25Hz单频信号              wire [11:0] din;       wire [11:0] DDS_sin;          DDS DDS(           .clk(clk),           .rst_n(rst_n),           .en_DDS(1'b1),           .Fword(32'd85899345),           .Pword(12'h0),           .q(DDS_sin)       );              DDS DDS_din(           .clk(clk),           .rst_n(rst_n),           .en_DDS(1'b1),           .Fword(32'd53687090),           .Pword(12'd0),           .q(din)       );                //乘法运算实现混频输出       reg signed [23:0] mult;       wire signed [11:0] s_din;       wire signed [11:0] s_DDS_sin;       assign s_din = din;       //将乘数转换成有符号数运算       assign s_DDS_sin = DDS_sin; //将乘数转换成有符号数运算       always @(posedge clk or negedge rst_n)           if (!rst_n)               mult <= 24'd0;           else               mult <= s_din * s_DDS_sin;              assign dout = mult;              wire [11:0]dout2;       assign dout2 = mult[23:12];          endmodule    仿真

  • 发表了主题帖: 【工程源码】ubuntu18.04使用静态ip

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。     ubuntu 18.04已经正式发布。后面会有更多小伙伴会迁移到这个系统。所以将这个设置ip的帖子更新了。      在ubuntu18.04中,使用 NetPlan 进行网络管理。而且16.04之前使用的/etc/network/interfaces也已经不再使用。现在必须使用/etc/netplan/*.yam来配置。              Netplan官网如下:https://netplan.io/       Netplan框图如下: 1   netplan会根据*yaml文件生成backend config文件,config文件供后端使用。目前netplan支持NetworkManager或systemd-networkd作为后端。NetworkManager更多用于桌面环境,networkd常用于服务器。       默认情况下,有一个如下配置文件 2    这个文件表示由NetworkManager管理所有设备,让所有网络设备默认使用DHCP方式分配IP。              使用静态ip分配,修改*.yaml文件内容如下: 3            执行sudo netplan apply。这时再用ip addr show查看系统ip,会发现ip已经修改。        enjoy linux!    如下内容是之前的帖子,删减了下,可以供大家了解下ip命令。     直接使用ip命令。不要再使用ifconfig命令啦。这个命令18.04已经默认移除了。ifconfig是net-tools中已被废弃使用的一个命令,许多年前就已经没有维护了。 ip命令操作步骤如下: 1 - 使用ip addr show查看当前ip 4   2 - 删除这个ip sudo ip addr del 192.168.6.5/24 dev ens33   3 - 添加一个期望的ip sudo ip addr add 192.168.6.160/24 dev ens33   4 - 查看一下当前的情况 5

  • 发表了主题帖: 【工程源码】ARM汇编指令 连载二

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。    ARM微处理器的指令集是加载/存储型的,即指令集中大部分指令仅能处理寄存器中的数据,而且处理结构都要放回寄存器。而对系统存储器的访问则需要通过专门的加载/存储指令来完成。ARM微处理器的指令集可以分为数据处理指令、数据加载指令与存储指令,分支指令、程序状态寄存器(PSR)处理指令、协处理器指令和异常产生指令6大类。        先从ARM数据处理指令开始吧。这之前需要先了解下寻址方式,这个微机原理里面应该都会将,就不再重复啦。      补充:       ARM微处理器有一个显著的特征是,在操作数进入ALU之前,对操作数进行预处理。如指定位数的左移动或右移动,这种功能明显增强了数据处理操作的灵活性。移位操作主要包括以下5种类型:       LSL 逻辑左移。       LSR 逻辑右移。      ASR 算术右移。      ROR 循环右移。      RRX 带扩展的循环右移。

  • 发表了主题帖: 【工程源码】ARM汇编指令 连载一

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。    虽然现在很少有程序使用汇编进行编写,但是了解一下还是很有必要的。了解这个有些bug找起来会快很多。我现在看一些执行起来很奇怪的代码,就直接看看对应的汇编部分,很多C语言中不易发现的问题,汇编一目了然。        相信了解过arm汇编的都听说过ARM指令集,Thumb指令集。现在很多处理器还有Thumb-2指令集(如STM32)。我不打算在开头去讲各种指令集有什么区别,一起看看各种指令集下的指令,一切就都清楚了。       ARM指令在机器中的表示格式是用32位的二进制数表示。计算机根据二进制代码去完成所需的操作。       ARM指令代码一般分为5个域;      [31:28]是4位的条件码域(cond),4位条件码共有16种组合;      [27:20]是指令代码域 (opcode)。      [19:16]是地址基址(Rn),为R0~R15共16个寄存器编码。      [15:12]是目标或源寄存器Rd,为R0~R15共16个寄存器编码。      [11:0]是地址偏移或操作寄存器、操作数区域 Op2。      汇编指令的表示格式      <opcode>{<cond>}{S}<Rd>,<Rn>{,<OP2>}      <>中的内容必不可少,{}的内容可省略。      <opcode>表示操作码,如ADD表示算术加法。      {<cond>}表示指令的条件域,如EQ\NE等。      {S}决定指令的执行结果是否影响CPSR的值,使用该指令后缀则指令执行的结果影响CPSR的值,否则不影响。      <Rd>表示目的寄存器      <Rn>表示第一个操作数,为寄存器      <op2>表示第二个操作数,可以是立即数,寄存器或者寄存器移位操作数。      例子:ADDEQS R0,R1,#8。其中操作码为ADD,条件为EQ,S表示执行结果影响CPSR寄存器。目的寄存器Rd为R0,第一个操作数Rn为R1,第二个操作为OP2为立即数#8。          小知识:        程序的执行指令都保存在存储器中。当计算机需要执行一条指令时,首先产生这条指令的地址,并根据地址去打开相应的存储单元,取出指令代码,CPU根据指令代码执行相应操作。        当处理器工作在ARM状态时,几乎所有的指令都根据CPSR中条件码的状态和指令的条件域有条件的执行。当执行条件满足时,指令被执行,否则,指令被忽略。根据上面所说,每条指令的条件码为[31:28]这4bit,共16种,每种条件码可用两个字母表示,这两个字符可以添加在指令助记符的后面和指令同时使用。在16中条件码中,有15种可以使用,第16种(1111)被系统保留。指令条件码如下表:      

  • 2020-02-28
  • 发表了主题帖: 【工程源码】Quartus时序约束教程

    http://home.eeworld.com.cn/forum.php?mod=attachment&aid=NDYxNDMxfDljNTZjYWQzMjMwOTM1YTlkZWZmNDQ1MWNlZmMwMTNifDE1ODYyMjA1MDg%3D&request=yes&_f=.rar

  • 发表了主题帖: 【工程源码】Verilog语言例程《王金明:《Verilog HDL 程序设计教程》》

    http://home.eeworld.com.cn/forum.php?mod=attachment&aid=NDYxNDI5fGIzZWI1MWM0OTM0YWM2YWI4YTVkZDFiYTY5Zjc2MWE1fDE1ODYyMjA1MDg%3D&request=yes&_f=.rar

  • 发表了主题帖: 【工程源码】BMP2mif文件软件,方便使用ROM存储图像数据并显示

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。    BMP2mif文件软件,方便使用ROM存储图像数据并显示用。     http://home.eeworld.com.cn/forum.php?mod=attachment&aid=NDYxNDI4fDJmNTVkZGU0OTI4YWU5NzRlOGUxZTZkYTgwNTJkNTUyfDE1ODYyMjA1MDg%3D&request=yes&_f=.rar  

  • 发表了主题帖: 【工程源码】已经安装器件库却报未安装——解决方案

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。    在软件安装过程中,明明安装了器件库。打开 Quartus II 却显示没有安装的情况:     我们点击YES,指向device所在的目录,安装器件即可。     (点击否,重新安装,发现器件已经变成了不可选状态,无法解决问题。)

  • 发表了主题帖: 【工程源码】无法下载FPGA程序,无法start,找不到下载器

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。   很多新手在刚开始接触FPGA学习的时候,都不会下载程序,经常出现无法下载程序,或者说下载窗口找不到下载器,或者Start按钮是灰色的情况,如下图1所示:   这种情况下,会显示NoHardware,可以点击左上角的Hardware Setup按钮进入下载器设置界面,进入界面之后又分成两种情况 1、能找到下载器,出现如下图所示的USB Blaster信息,这个时候双击该USB Blaster文字或者在上方的下拉列表中选择USB Blaster即可。然后Close,回到下载界面就可以下载了。   2、在设置界面找不到USB Blaster,如下图所示。   此时应打开设备管理器,查看在通用串行总线控制器一栏下面是否有Altera USB Blaster设备,如下图所示,且没有黄色感叹号和问好,如果设备正常,按照以下顺序操作: a、关闭Programmer窗口 b、从电脑上拔掉下载器的USB数据线 c、等待3秒后重新插上USB 数据线 d、打开Quartus的Programmer窗口,看看是否有下载器了,如果没有,按照前面讲的第一种方法的操作查看并选择下载器 第三种情况     如果设备管理器中没有Altera USB Blaster设备,或者显示驱动没装好,请先安装驱动。 第四 软件没有自动的添加sof文件到下载窗口中,如下所示,遇到这种情况,点击Add File按钮手动添加文件然后勾选Program/Config就可以了.

  • 2020-02-27
  • 发表了主题帖: 【工程源码】NIOS II 自定义IP核的静态地址对齐和动态地址对齐

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。   如果使用静态地址对齐(每个寄存器在Avalon总线上占4个字节的地址) 设置IP使用静态地址对齐的方式为,在hw.tcl脚本里加上一局话:set_interface_property as addressAlignment {NATIVE} 在软件编程时 可以使用IOWR(基地址,寄存器编号(n),数据)对自定义IP的第n个寄存器进行写入操作 可以使用IORD(基地址,寄存器编号(n)) 对自定义IP的第n个寄存器进行读出操作 如果使用动态地址对齐,(每个寄存器在Avalon总线上占 数据位宽/8个字节的地址)(默认) 在软件编程时,使用IOWR_32DIRECT(数据位宽为32位)、IOWR_16DIRECT(数据位宽为16位)、IOWR_8DIRECT(数据位宽为8位)进行写操作 在软件编程时,使用IORD_32DIRECT(数据位宽为32位)、IORD_16DIRECT(数据位宽为16位)、IOWR_RDIRECT(数据位宽为8位)进行写操作 IOWR_8DIRECT(基地址、地址偏移量、数据),地址偏移量 = 寄存器编号*1 IOWR_16DIRECT(基地址、地址偏移量、数据),地址偏移量 = 寄存器编号*2 IOWR_32DIRECT(基地址、地址偏移量、数据),地址偏移量 = 寄存器编号*4 IORD_8DIRECT(基地址、地址偏移量),地址偏移量 = 寄存器编号*1 IORD_16DIRECT(基地址、地址偏移量),地址偏移量 = 寄存器编号*2 IORD_32DIRECT(基地址、地址偏移量),地址偏移量 = 寄存器编号*4

  • 发表了主题帖: 【工程源码】NIOS II 软件程序固化的相关知识

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。   片上RAM和ROM的SOPC系统 1、生成hex文件 2、将hex文件添加到quartus工程中(添加qip文件) 3、对工程进行全编译 4、下载sof就可以看到程序运行 5、将sof转换为jic文件,烧写到EPCS中,就能掉电保存 使用片外RAM作为CPU内存时候的程序固化方案 1、在Qsys中添加EPCS控制器 2、slave连接到CPU的指令总线和数据总线 3、将引脚信号导出(export) 4、连接中断 5、设置系统基地址 6、设置CPU的复位向量为EPCS 7、在Quartus工程中添加引脚并分配IO 8、在设置中将用到的四个脚设置为Regular IO 9、在NIOS II EDS中的工程BSP中linker Script中,执行Retore Defaults 10、run时,需要在bsp editor中,advanced中,allow code at reser和enable alt load取消勾选。 使用 Flash Programer进行sof和elf文件的合并以及烧写到EPCS存储器中

  • 发表了主题帖: 【工程源码】使用华邦的SPI FLASH作为EPCS时固化NIOS II软件报错及解决方案

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。   Altera器件有EPCS系列配置器件,其实,这些配置器件就是我们平时通用的SPIFlash,据AlteraFAE描述:“EPCS器件也是选用某家公司的SPIFlash,只是中间经过Altera公司的严格测试,所以稳定性及耐用性都超过通用的SPIFlash”。就本人看来,半导体的稳定性问题绝大部分都是由本身设计缺陷造成的,而成熟的制造工艺不会造成产品的不稳定;并且,现在Altera的器件在读入配置数据发生错误时,可以重新读取SPIFlash里面的数据,所以在工艺的稳定性以及设计的可靠性双重保证下,通过选用通用的SPIFlash来减少产品的成本压力。 假设我们正在使用一个普通SPIFlash,打开nios II command shell窗口,使用nios2-flash-programmer命令下载***.flash文件时,会发生如下错误: No EPCS layout data --- looking for section [EPCS-1C2017] 不同公司的SPIFlash有不同的ID,并且不同大小的Flash的Sector大小及个数都不一样,所以需要新建一个文档去说明这些数据: 1、首先在<nios2_install>/bin文件夹下面新建nios2-flash-override.txt文件; 注意:是NIOS2EDS的安装目录,不是Quartus的安装目录,很多人不注意,还是找的Quartus的安装目录,正确的位置,例如笔者的PC上应该是:D:\altera\13.0\nios2eds\bin 2、输入下述代码,下面描述的器件都是Altera的EPCS器件,sector_size表示sector大小,sector_count表示sector个数; [EPCS-202011] # EPCS1N (lead-free) sector_size = 32768 sector_count = 4 [EPCS-202013] # EPCS4N (lead-free) sector_size = 65536 sector_count = 8 [EPCS-202015] # EPCS16N (lead-free) sector_size = 65536 sector_count = 32 [EPCS-202017] # EPCS64N (lead-free) sector_size = 65536 sector_count = 128   3、在上述代码中添加自己选择的通用SPIFlash,例如: [EPCS-EF4015] # EPCS16N (lead-free) sector_size = 65536 sector_count = 32   4、然后再使用nios2-flash-programmer命令下载***.flash文件,就可以对SPIFlash进行下载了。  

  • 发表了主题帖: 【工程源码】如何在设备驱动冲突导致蓝屏的情况下强制删除该设备驱动

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。 插上设备,在设备管理器中选择要卸载的设备驱动,卸载其驱动,大家都会,但是万一设备一插上电脑就蓝屏,连设备管理器打开的机会都不给你咋办呢?这里找到了解决办法。该办法还可以帮忙清理一大堆无用的驱动哦。 这两天,玩儿了下CrazyBingo的那个USB Camera。他那个Camera是用USB2.0芯片CY7C68013做的,该芯片默认驱动不支持win764位。需要关闭windows强制签名。我就关了这个驱动签名,关闭之后成功的安装了驱动并捕获到了图像。然而,当我再将我自家的USB3.0开发板查到电脑上时,则开始蓝屏,关闭驱动签名也蓝屏,不管咋样,一插上就直接蓝屏。猜想应该是两者驱动冲突了。 考虑先卸载驱动。怎么卸载呢?网上说的都是找到设备,再点击设备卸载。然而我现在设备一插上马上就蓝屏了,不给自己卸载的机会,咋办呢?能不能在不接设备的情况下就找到该设备对应的驱动病卸载呢?答案是肯定的。 先在开始  > cmd, 然后在打开的命令提示符中输入 setdevmgr_show_nonpresent_devices=1 之後再输入:devmgmt.msc  然后在打开的设备管理器中,查看选项中勾选上"显示隐藏的设备"    灰色的都是之前装了驱动,现在设备没有连接的。然后,找到你想要删除的设备驱动,慢慢删吧,我刚刚一口气删了一大堆。删完之后再插上USB3.0的开发板,也不蓝屏了。问题解决。

  • 发表了主题帖: 【工程源码】CYUSB3014芯片使用EEPROM无法下载固件说明

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。 当使用128KB的EEPROM存储CYUSB3014芯片的固件时,需要注意,不同厂家的EEPROM存储器,其A0、A1、A2功能不一样,在设计时电路也不一样。Microchip对应的128KB的EEPROM存储器芯片型号为24LC1025。Atmel的128KB的EEPROM存储器型号为24C1024。例如, Microchip EEPROM 将引脚 A1 和 A0 用于芯片选择, 并不使用引脚 A2。但 Atmel EEPROM 会将引脚 A2 和 A1 用于芯片选择, 不使用引脚 A0。因此针对不同的厂家的器件,EEPROM存储器的A0、A1、A2三个引脚设置也应该不同具体如下所示:

  • 2020-02-26
  • 发表了主题帖: 【工程源码】Altera FPGA 开启引脚片上上拉电阻功能

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。   Cyclone IV E FPGA的通用输入输出管脚都支持内部弱上拉电阻,但是时钟输入脚不支持。所以,当需要上拉电阻的信号(如本例中的矩阵键盘Row信号和IIC协议中的SDA、SCL信号)连接到了FPGA的通用输入输出管脚上,在一些要求不高的场合,就可以使用片上上拉电阻来为这些信号设置上拉了。 分配引脚并设置row上拉电阻详细方式 1. 如下图所示,在菜单 Assignments 中选择 Pin Planner,也可以直接点击面板上引脚分配的图标;   2.进入引脚分配的界面之后,按照上面给出的矩阵键盘与AC620板卡的连接关系以及引脚分配情况,完成引脚分配工作,Starter板卡用户请按照Starter板引脚分配关系分配,并将Key_Value的4位信号分别连接到4位LED上,以方便通过LED的亮灭值确定按键的值。   3.在弹出的Pin Planner界面的All Pins区域里任意位置点击鼠标右键,找到 Customize Columns并点击进入,如下图所示;   4. 在弹出的Customize Columns对话框的左列表框选择Weak Pull-Up Resistor,如图下图所示,再点击和大于号(>)一样的图标,这样把Weak Pull-Up Resistor添加到右列表框,最后点击OK。   5. 经过步骤4后,在引脚分配界面就会多出一个Weak Pull-Up Resistor列,如下图所示:   6.再把需要上拉的Row0~Row3对应WeakPull-Up Resistor的位置双击鼠标左键,就会弹出一个Off/On的选项,选上On就可以了。

  • 发表了主题帖: 【工程源码】SOPC开发流程之NIOS II 处理器运行 UC/OS II

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。 第一步:建立 Quartus II 工程 建立 Quartus II 工程时需要注意以下几点 1、 器件选择为 EP4CE10F17C8N; 2、 工程路径中不得出现非法字符(空格和中文字符); 3、 开发工具选择 Quartus II 11.0及以上,这里我选择的版本为 Quartus II 15.1。 4、 由于我们最终的软件工程也是建立在 Quartus II 工程目录下的,而软件工程有一个最让人崩溃的地方就是,软件工程在建立时,很多包含文件是以据对路径存在的,如果你更改了工程路径,那么当你再次编译工程时,就会报错。所以这里我在建立工程时,直接在 E 盘根目录下建立了一个 mysystem 的文件夹,并将工程建立在此文件夹中。这样可以避免路径太深,以后切换电脑时,只需要将工程直接拷贝到新的电脑的 E 盘下即可。只要 QII 软件安装位置相同,再次编译软件工程就不会报错。 第二步:打开 Qsys 工具 在 Quartus II 软件中,依次点击 tools/Qsys,以打开 Qsys 工具。 第三步:在 Qsys 系统中添加 NIOS II 处理器 在左侧的 IP Catalog 的搜索框中,输入 nios,在搜索结果中,选择并添加 NIOS II(Classic) Processor 到右侧的 System Contents 中来。添加时,选择 NIOS II/f 型 CPU。添加完成后,在 System Contents 中修改其名字为 mycpu。 1   添加完成后,会报如下错误 2   这个是因为目前只添加了CPU,还没有添加对应的存储器,因此会报错。这里不用理会,在后面的步骤中,只要按照操作来,这些错误最好都会消失的。后续操作中如果遇上其他错误也不要惊慌,最后只要系统搭建完成,错误都是会消失的。 第四步:在 Qsys 中添加 UCOS 系统所需定时器 在左侧的 IP Catalog 的搜索框中,输入 timer,在搜索结果中,选择并添加 Interval timer 到右侧的 System Contents 中来。添加时,将 timeout period 设定为 10ms。添加完成后,修改其名字为 ucosii_timer。 3   第五步:在 Qsys 中添加 SDRAM 在左侧的 IP Catalog 的搜索框中,输入 sdram,在搜索结果中,选中并双击 SDRAM Control,在打开的参数配置选项卡中,按照如下图所示的配置进行设置: 4     添加完成后,修改其名字为 sdram。 第六步:在 Qsys 系统中添加 JTAG_UART 在左侧的 IP Catalog 的搜索框中,输入 uart,在搜索结果中,选择并添加 JTAG UART 到右侧的 System Contents 中来。添加时,所有参数默认即可。添加完成后,修改其名字为 JTAG_ UART_0。 第七步:在 Qsys 系统中添加 sysid 在左侧的 IP Catalog的搜索框中,输入 sys,在搜索结果中,选择并添加 System ID Peripheral 到右侧的 System Contents 中来。添加时,设定 32bit system id 为自定义值,我这里设定为0x00000002。添加完成后,修改其名字为 sysid0。 第八步:连线和导出端口 自此,所有的外设添加就已经完成了,接下来,需要将每个模块的端口连接到对应的总线上,这里不一一介绍,详情请参看下图: 6   其中,clk_100M 模块的 clk_in 和 clk_reset,以及 sdram 模块的 wire 需要 export。 将 ucosii_timer 和 jtag_uart_0 的 irq 连接到 mycpu 的 d_irq 上,在右侧的 IRQ 一栏中, ucosii_timer 的中断号设置为 0,jtag_uart_0 的中断号设置为 1。 注意,在Quartus II13.0及以前的版本中,所有部件第二列name栏中并不会有d_irq或irq这样一个信号,这个无需在意,只是没显示而已。只需要直接点击右侧的IRQ一栏中的空心圈并输入序号即可完成中断的分配,如下图所示: 7   第九步:设定 sdram 基地址 在 Base 一栏中,将 sdram 模块的起始地址设置为 0,然后点击地址前面很小的那个锁的符号,使其变为锁定状态。 第十步:自动分配所有模块地址 在 Qsys 系统中,依次点击菜单栏中的 sysytem/Assign Base Address,系统即可自动为我们分配各个模块的基地址。由于 sdram 的基地址本身处于锁定状态,所以在此过程中,可能其他模块的基地址会因为自动修改而发生变化,但是 sdram 的基地址将保持不变。 第十一步:设定 CPU 的复位向量地址和异常向量地址 双击 mycpu 模块,在打开的参数设置中,将 Reset Vector 和 Exception Vector 都选择为 sdram.s1。 以上,我们就完成了完整 Qsys 系统的搭建,接下来,我们就可以生成 Qsys 系统的 RTL 代码了。 第十二步:保存设计 点击 file/save,将我们的系统保存,我这里命名为“cpu”。 第十三步:生成 Qsys 系统的 HDL 代码 在 Qsys 系统中,依次点击 Generate/Generate HDL,在弹出的窗口中,点击 Generate 来完成 HDL 代码的生成。此过程根据电脑配置的不同,大约需要 3 到 5 分钟。 第十四步:在 Quartus II 系统中添加并例化 QSYS 系统 在 Qsys 系统中,依次点击 Generate/HDL Example,在打开的界面中,将 HDL Example 中的代码选中并复制,如下图中红线圈起来的内容: 8   回到 Quartus II 工程中,新建一个 Verilog HDL 文件,将刚刚复制的内容粘贴进去,将文件保存,命名为 mysystem。 第十五步:添加锁相环 在 Quartus II 软件中,依次点击 tools/IP Catalog,在右侧弹出的窗口中,在搜索栏输入 pll,然后双击打开搜索结果中的 Altera PLL,在打开的窗口中,选择 Verilog 格式,并保存名为 sys_pll。然后点击 OK。等待大于 10 秒钟左右,会弹出 PLL 的配置窗口,在配置窗口中,按下图所示的内容进行配置: 9   第十六步:添加 Qsys 系统到 Quartus II 工程中 第十四步中,只是完成了 Qsys 系统的例化,实际并没有将 Qsys 系统添加到 Quartus II 工程中来。这里,我们双击 Quartus II 软件中 Project Navigator 窗口中 Files 选项卡中的 Files 10   在弹出的窗口中,找到 E:\mysystem\cpu\synthesis 路径下的 cpu.qip 文件,加入到工程中。 第十七步:完善 mysystem.v 模块 将 mysystem.v 中的内容完善如下: module mysystem(     refclk,     rst_n,     sdram_addr,     sdram_ba,     sdram_cas_n,     sdram_cke,     sdram_cs_n,     sdram_dq,     sdram_dqm,     sdram_ras_n,     sdram_we_n,     sdram_clk );      input refclk;     input rst_n;      output [12:0]sdram_addr;     output [1:0]sdram_ba;     output sdram_cas_n;     output sdram_cke;     output sdram_cs_n;     inout [15:0]sdram_dq;     output [1:0]sdram_dqm;     output sdram_ras_n;     output sdram_we_n;     output sdram_clk;           wire cpu_clk;     wire sdram_clk;     wire cpu_reset_n;     wire pll_locked;          assign cpu_reset_n = pll_locked;           cpu u0 (          .clk_clk       (cpu_clk),       //   clk.clk          .reset_reset_n (cpu_reset_n), // reset.reset_n          .sdram_addr    (sdram_addr),    // sdram.addr          .sdram_ba      (sdram_ba),      //      .ba          .sdram_cas_n   (sdram_cas_n),   //      .cas_n          .sdram_cke     (sdram_cke),     //      .cke          .sdram_cs_n    (sdram_cs_n),    //      .cs_n          .sdram_dq      (sdram_dq),      //      .dq          .sdram_dqm     (sdram_dqm),     //      .dqm          .sdram_ras_n   (sdram_ras_n),   //      .ras_n          .sdram_we_n    (sdram_we_n)     //      .we_n      ;          sys_pll_0002 sys_pll_inst (          .refclk   (refclk),   //  refclk.clk          .rst      (~rst_n),      //   reset.reset          .outclk_0 (cpu_clk), // outclk0.clk          .outclk_1 (sdram_clk), // outclk1.clk          .locked   (pll_locked)    //  locked.export      );      endmodule 第十八步:分析和综合; 第十九步:分配引脚 引脚分配如下表所示: 11 12 13   第二十步:全编译 此过程大约需要 6 到 10 分钟左右; 第二十一步:配置固件到FPGA芯片中 将生成的 SOF 文件配置到芯航线开发板中。 第二十二步:打开 NIOS II EDS 软件 在 Quartus II 软件中,依次点击 tools/NIOS II software Buil tools for Eclipse,在打开的 Workspace Launcher 中,设定 Workspace 为 E:\mysystem,然后点击 OK。 第二十三步:建立 UC/OS II 模版工程 在打开的开发环境中,点击 File/New/Nios II Application and BSP from Template。 14   在打开的窗口中,按下图所示的内容进行选择: 15   然后系统便能为我们自动生成完整的系统,生成后的界面如下所示: 16   第二十四步:编译软件工程 此时,我们按下键盘上的组合键“ctrl + B”,就能够对软件工程全编译。 第二十五步:下载并运行 编译完成后,点击 Run/Run Configurations,在打开的窗口中,  17   双击 Nios II Hardware,在弹出的界面中,选择 Project Name 为 myucosii(注:图中我的工程名为 myucos,只是工程名字不同而已,无其他影响)。   18   选择完成后,切换到 Target_Connection 选项卡中,如下图: 19   若 1 处和 2 处无内容,则点击 3 处的 Refresh Connections,直到 1 和 2 中出现我们的 Cable 和 Device 为止。选中 4 处的两个选项。然后点击 5 处的 apply,最后点击 6 处的 Run,则系统将启动软件烧写过程,并在烧写完成后启动 FPGA 中的 Nios II 处理器。 此时,我们在Eclipse 的 Nios II Console 窗口中便能看到 Nios II 处理器通过串口发送到 PC 上的内容,如下图所示: 20

  • 发表了主题帖: 【工程源码】给NIOS II CPU增加看门狗定时器并使用

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。 配置看门狗定时器: 1. 设置计时溢出时间为1秒 2. 计数器位宽为32位 3. 勾选No Start/Stop control bits 4. 勾选Fixed period 5. 不勾选Readable snapshot 6. 勾选System reset on timeout.(Watchdog) 7. 不勾选Timeout Pulse (1 clock wide)  1   这部分配置可以参见“Embedded Peripheral IP User Guide.pdf”中Interval Timer Core章下的Configuration节下的Configuring the Timer as a Watchdog Timer小节相关内容。  2     添加完成后,将核名字改为watchdog(当然也可以自己定义),然后将clk信号连接到系统clk上,reset与所有reset网络信号相连(不包括clk核的reset_in),将s1连接到nios cpu的data_master上,点击连接irq,将resetrequest连接到所有核的reset输入上。 4   然后生成QSYS系统。 创建Quartus II工程并编写顶层文件。如果是在上一节实验的基础上增加的看门狗核,则不需要对Quartus II工程做任何更改,直接全编译即可。否则请按照我们提供的示例工程创建Quartus II工程并编写顶层文件。 打开NIOS II 开发软件,切换工作空间到当前工程目录,如果整个工程是直接复制上一个实验的工程得到的,则工作空间中还会有上一个实验的软件工程,这里需要首先删除工作空间中已经有的工程,注意,不要删除工程源文件,因为这时候工程源文件还是指向了上一个工程的源文件所在位置,直接删除源文件会破坏上一个实验的工程。然后在software目录下手动删除原有的工程文件。 接着点击【File】->【New】->【Nios II Application and BSP from Template】打开创建新工程和板级支持包向导。   5   然后选择sopcinfo文件为本工程目录下的sopcinfo文件(注意,软件总是记住上一次选择sopcinfo文件时的路径,因此这里特别容易出错,我们需要手动重新定位目录,软件自动打开的那个目录下的sopcinfo文件往往都是上一个实验的,不能使用)创建hello world模版工程,命名工程为watchdog_test。然后点击finish即可创建新工程。   6   复制上一个工程中软件工程中的【hardware】文件夹到本工程下的【watchdog_test】文件夹下,最好直接在电脑的文件管理器中进行(对Eclipse使用非常熟练的,请尽情使用拖拽添加的方式)。复制完成后,选中【watchdog_test】工程,单击右键,选择【Refresh】,就可以在工程中看到【hardware】文件夹了。 7   接着我们添加【hardware】下的【inc】路径到工程头文件包含路径中来。我们选中【watchdog_test】工程,按下键盘组合键“ATRL + Enter”键打开【Properties】设置界面,选择Nios II Application Properties下的Nios II Application Paths,在右侧的Application include directories下,点击Add按钮,添加hardware/inc到包含路径中。然后在弹出的对话框中点击Yes,即可将此路径添加为我们的头文件包含路径。如果用户之后自己有其他的头文件路径需要添加,也是按照这种方法进行。 8   接着我们查看下Project References中是否勾选了watchdog_test_bsp工程,如果没有勾选的话,当工程关闭了重新打开时,工程有可能会报各种无法理解的错误(当一个workspace中有多个应用工程时)。这里我们需要确认这个选项被勾选上了。 9   然后我们再在C/C++ General下的Indexer下,勾选按照下图中所示进行勾选。通过这样勾选后,一般工程不管怎么折腾,都不会出现xxxx 'xxxx' could not be resolved的报错了。 10   设置完成后,我们点击Apply,然后点击OK退出。 然后我们选中【watchdog_test_bsp】工程,单击右键选择【Nios II】->【BSP Editor】,设置sys_clk_timer为none,(软件会默认搜索工程中存在的定时器外设并添加为sys_clk_timer),因此我们这里可能看到打开时默认将watchdog设置为了sys_clk_timer,所以我们要手工更改为none,如果不更改,系统将不能正常运行。Timestame_timer设置为none。Stdin、stdout、stderr都设置为uart_0。(默认为jtag_uart,但是jtag_uart容易导致Eclipse崩溃,因此我一般不用)。设置完毕后点击generate,然后点击exit退出。 接着我们在hardware/src目录下创建一个“watchdog.c”文件,编写内容如下: #include "watchdog.h" /******************************************  * 驱动使用的看门狗定时器基地址,独立定义,然后初  * 始化时由用户输入具体基地址名称,避免不同用户对看  * 门狗定时器不同的命名带来的程序移植性问题。 ******************************************/ alt_u32 WTD_BASE; /****************************************************************** * 名    称:WatchDog_Init() * 功    能:初始化WatchDog,即开启看门狗定时器; *         需要注意的是,看门狗定时器一旦开启,无法关闭。 * 入口参数:WATCHDOG_BASE :看门狗定时器基地址 * 出口参数:无 ******************************************************************/ void  WatchDog_Init(void *MyWatchDog_BASE) {     //将系统的watchdog基地址传递给驱动使用的基地址     WTD_BASE = MyWatchDog_BASE;      /* 启动 WATCHDOG */    IOWR_ALTERA_AVALON_TIMER_CONTROL(WTD_BASE,             ALTERA_AVALON_TIMER_CONTROL_START_MSK); } /****************************************************************** * 名    称:WatchDog_Feed() * 功    能:看门狗喂狗操作,向看门狗寄存器写入任意值即可完成看门狗复位 * 入口参数:无 * 出口参数:无 ******************************************************************/ void  WatchDog_Feed(void) {     IOWR_ALTERA_AVALON_TIMER_PERIODL(WTD_BASE, 0x1234); } 接着我们在hardware/inc目录下创建一个“watchdog.h”文件,编写内容如下: #include "system.h" #include "altera_avalon_timer_regs.h" #include "alt_types.h" //Altera定义的数据类型 #ifndef WATCHDOG_H_ #define WATCHDOG_H_ void  WatchDog_Init(void *MyWatchDog_BASE); void  WatchDog_Feed(void); #endif /* WATCHDOG_H_ */ 最后我们编写main文件函数如下所示: /****************************************************************** * 文 件 名:main.c * 功    能:运行WDT,并控制LED0--LED3显示输出。程序开始先对LED0--LED3进行闪烁控制 *           和喂狗处理;然后只点亮LED1,并进入死循环,等待WDT复位。 * 说    明: ******************************************************************/ #include <stdio.h> #include "key.h" #include "led.h" #include "uart.h" #include "string.h" #include "stdlib.h" #include "priv/alt_busy_sleep.h" #include "watchdog.h" //#define USE_WTD 1 //该宏定义决定系统中的看门狗是否被使用 /****************************************************************** * 名    称:DelayNS() * 功    能:长软件延时,具有喂狗功能。 * 入口参数:dly    延时参数,值越大,延时越久 * 出口参数:无 ******************************************************************/ void  DelayNS(alt_u32  dly) {     alt_u32  i;     for(; dly>0; dly--)     {         for(i=0; i<1000; i++)         {             #ifdef USE_WTD                 WatchDog_Feed();    //喂狗             #endif         }     } } /****************************************************************** * 名    称:main() * 功    能:初始I/O口及WDT,然后开始先对LED0--LED3闪烁控制,并进行喂狗处理; *           然后只点亮LED1,并进入死循环,等待WDT复位。 * 说    明:如果main函数中InitWDT()被使用,则系统会自动复位,现象就是led永远不停的闪烁 *            如果main函数中InitWDT()被屏蔽,则系统无法自动复位,现象就是led闪烁8次后 *            停止下来,led0保持点亮 ******************************************************************/ int main(void) {     alt_u8  i;     LED_HANDLE hLED;     hLED = LED_Init(PIO_LED_BASE);     if (!hLED) {         printf("Failed to init LED\n");     } #ifdef USE_WTD     //屏蔽此句可以禁止使用看门狗,用来测试不使用看门狗时的系统运行情况     WatchDog_Init(WATCHDOG_BASE);              // 初始化看门狗     WatchDog_Feed();              // 进行喂狗操作 #endif     for(i=0; i<8; i++)     {         //点亮LED0和LED2,熄灭LED1和LED3         LED_WriteData(hLED, LED0 | LED2);         DelayNS(300);         //点亮LED1和LED3,熄灭LED0和LED2         LED_WriteData(hLED, LED1 | LED3);         DelayNS(300);     }     //关闭LED0~3     LED_Off(hLED, LED0 | LED1 | LED2 | LED3);     LED_On(hLED, LED1); //打开LED1     while(1);     return(0); } 这样我们的软件就编写完成了,接着我们点击键盘的组合键“CTRL + B”(或者依次点击【Project】【Build All】)来编译整个工程。 接着我们在Quartus II中打开配置下载窗口将sof文件下载到FPGA中,然后在Eclipse中点击【Run】->【Run Configuration】,在弹出的界面中,双击左侧的【Nios II Hardware】新建一个下载设置,将name更改为Watchdog_test,与工程名保持一致。右侧Project Name选择Watchdog_test,如下图所示: 11   然后切换到Target Connection选项卡,查看如果Connections下能找到USB Blaster就行,如果不能找到,就点击Refresh Connections,以让软件找到连接。在System ID Checks下勾选忽略System ID和timestamp,如下图所示: 12   然后点击Apply,再点击Run即可开始下载。下载完成后,我们看到开发板上的4个LED灯闪烁8次后停止下来,这是因为没有使用watchdog的缘故,程序正常运行到while(1)处进入死循环。 接着,我们将“//#define USE_WTD 1”这一行前面的“//”注释标识符取消,然后重新编译工程,并运行下载。这里我们可以看到,开发板上的4个LED灯将持续不停的闪烁。这是因为当程序运行到while(1)死循环中后,不再有喂狗操作,因此看门够计数溢出,然后复位系统,系统重新开始运行,则LED重新开始闪烁,如此持续不断的进行。  

  • 发表了主题帖: 【工程源码】Altera SOPC FrameBuffer系统设计教程

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。 在嵌入式系统中,LCD屏作为最友好的人机交互方式,被大量的应用到了各个系统中。在基于ARM处理器的系统中,应用更是非常广泛。FPGA作为广义嵌入式系统的一员,自然也有很多时候需要来驱动显示屏显示一些内容,例如经常有需求要用FPGA来做液晶测试架,做显示器驱动测试卡。很多学习了FPGA的朋友都知道,FPGA驱动VGA显示器是比较轻松的,几乎每个板卡商提供的资料中都提供了有诸如显示彩条,显示图案,甚至显示简单的文字。但是,当希望显示更加复杂的内容的时候,往往就很难办到了。因为FPGA设计的是电路,是硬件,特点是头脑简单,四肢发达。而显示复杂的内容,恰恰需要一个头脑发达的控制器来执行这项任务。 在FPGA这些年的发展中,先后经历了简单逻辑扩展、复杂逻辑设计、SOPC系统和SOC系统,逻辑设计部分就不多说了,对于SOPC系统设计,Xilinx家有MicroBlaze软核、Altera家有大名鼎鼎的NIOS II软核处理器。而到了SOC时代,Xilinx家和Altera(现已被因特尔收购,成为Intel的可编程事业部)都推出了内嵌双核ARM Cortex-A9的SOC芯片,Xilinx家著名的zynq7000系列,以及Altera花了大力气在高校推广的Cyclone V SOC。 虽然随着技术的发展以及时间的推移,SOC的应用会越来越广泛,但是作为我们一般学习和使用来说,SOPC技术也还是有其使用的价值的,毕竟内嵌硬核的SOC芯片,动辄几百块,而使用软核的方案,只要40元不到的BOM成本就能完成一个系统设计,优势还是很明显的。本例,我们就带领大家使用Altera Cyclone IV系列最低端的FPGA芯片EP4CE6/EP4CE10设计的能够驱动640*480分辨率显示屏动态显示复杂内容的系统。例如显示文字,显示图片 在之前我们讲解过RGB显示屏的驱动,RGB显示屏驱动时序和VGA驱动时序几乎完全一样,只是不同的分辨率之间,其时序参数不同。说到这个驱动480*272分辨率显示屏动态显示复杂内容,相信很多朋友并不陌生,是的,在我们之前的SOPC公开课时候,就已经给大家讲解过实现这种系统的一种方式,即使用了一片独立的SRAM来作为显示屏的显存,显示驱动模块直接读取SRAM中的数据并实时刷新显示,而NIOS II CPU则只在需要刷新显示内容的时候,才执行对SRAM的写入操作。此种方式,需要一片独立的SRAM作为显存,以及一片SDRAM作为NIOS II的运行内存。除了SRAM的使用会增加系统的BOM成本外,SRAM也会占用掉FPGA芯片较多的管脚,导致必须得使用256脚BGA方案的芯片才能够实现。因此,此种方案仅适合在对成本不太敏感高的场合使用。本节要介绍的方案,是对该系统改进优化得到的。 本节所描述的系统,仅需用到以下硬件资源 1. EP4CE6/EP4CE10型FPGA芯片一片(30元) 2. 16Mbit 或以上SDRAM芯片一片(3元,如华邦w9816g6) 3. 50M有源晶振一片(3元) 4. 4Mbit SPI FLASH芯片(1.5元,如W25Q80) 5. 3.3V、2.5V、1.2V LDO稳压电源(AMS117系列,合计不到1元) 本节希望达到的目的: 在显示屏上显示一帧图像,并在指定位置显示字符内容 1   上述框架是我们经过分析,要实现本系统方案所需的最小硬件系统,本节我们将基于芯航线的AC620学习套件来完成FPGA的系统搭建和NIOS II软件设计。芯航线的AC620 FPGA开发板整体硬件配置高于上述分析,有助于我们进行原型验证。 FPGA系统搭建 本系统主要在Qsys系统中完成,包含了以下IP核: clk_0:输入时钟管理单元,系统默认添加 altpll_0:PLL锁相环,对输入时钟进行分频和倍频,以得到两路频率为100MHz,相位相差180度的时钟信号,分别提供给系统中逻辑电路工作(包含NIOS II软核处理器)和SDRAM芯片使用。以及1路24M的时钟信号,供640*480分辨率的VGA驱动电路使用。 NIOS II CPU:实现系统的控制以及显示内容的处理。 SDRAM:NIOS II CPU运行内存和TFT显示图像帧缓存。 onchip_memory:片上存储器,指定SGDMA要执行的数据传输,主要用作SGDMA的描述符存储器。 lcd_sgdma:SGDMA IP,主要实现大量数据的高效搬运,支持流模式,效率比Avalon MM接口的DMA核高。 timing_adapter:时序匹配IP核,主要用于流数据在两个不同模块间进行传递时,对部分信号加上一定延迟,使得数据和标志信号能够完全同步(说的简单点儿,就是用寄存器打拍的方式对一些信号进行延迟,使数据和控制信号对齐)。 fifo:双时钟fifo,主要完成数据流的传输速度转换。SGDMA输出的数据流,是按照和存储器相同的时钟速度(本例中为100M)进行的,而在数据使用端,即VGA显示部分,是按照9M的时钟取用数据的,因此使用双时钟fifo,解决跨时钟域的问题。 VGA_SINK:该IP为第三方开源IP,由友晶科技提供,主要完成Avalon ST流数据转换成VGA行场时序并驱动VGA屏进行显示。 EPCS:EPCS FLASH存储器控制IP,使用该IP,使得EPCS芯片能够存储FPGA固件和NIOS II的程序。 jtag_uart0:调试串口,主要用来打印一些调试信息,在设计的前期调试中很有用 2   接下来,将详细讲解本系统的搭建过程。 1、 altpll_0 为了保证整个系统能够具有较高的性能,因此让其运行在100MHz,所以需要使用PLL对外部晶振产生的50MHz输入的时钟信号进行倍频,得到两路频率为100M,相位相差90度的时钟信号。另外,还需要提供一路9MHz时钟信号供480*272分辨率的TFT屏驱动使用。关于PLL的详细添加和参数修改方式这里不再多说,仅提供设置结果: inclk0:50M 不需要areset信号和locked信号 c0:9MHz c1:100MHz,相位为0 c2:100MHz,相位为-180度 2、 nios ii cpu 所有设置使用默认值,等后续添加完sdram和EPCS和,将复位向量(Reset Vector)设置为epcs,将异常向量(Exception Vector)设置为sdram。 3、 sdram 数据位宽(Data Width)为16bit。 结构(Architecture)为一个片选,4个bank。 地址宽度(Address Width)Row地址为12、Column地址为9。 其他默认即可。 3   4、 onchip_memory 片上缓存onchip_memory作为SGDMA的描述符存储器,存储SGDMA的数据传输描述内容。 类型为RAM; 数据位宽选择32位; 存储器总量选择16384Bytes(用户可以根据自己芯片的RAM容量适当增加或减小该值); 4   5、 lcd_sgdma 选择传输模式为存储器到数据流模式(Memory To Streram); 数据宽度(Data width)为16bit,该位宽与存储器(SDRAM)的Avalon MM总线宽度一致。 其他按照默认即可,如下图所示。 5   6、 timing_adapter 时序匹配IP核,主要用于流数据在两个不同模块间进行传递时,对部分信号加上一定延迟,使得数据和标志信号能够完全同步(说的简单点儿,就是用寄存器打拍的方式对一些信号进行延迟,使数据和控制信号对齐)。 如下图所示设置,所有勾选项都勾选上,输入Ready Latency设置为0,输出Ready Latency设置为1,每个数据总共有两个symbols,每个symbol含8位数据。(根据RGB数据流的实际意义,这里应该是每个数据有3个symbols,但是本设计使用的是16位色RGB565模式,即RGB总共才16位,所以在搭建系统的时候,假设只有2个symbols,只是在最终数据输出到VGA的时候,将16bit数据拆分成RGB565,分别送给VGA的R、G、B色通道) 6   以上各个模块,都是工作在100MHz的时钟频率下。主要完成将需要显示的数据从SDRAM中读取出来。而读取出来的数据最终是需要送到TFT显示屏上去显示的,TFT显示部分,对于本系统,数据传输速率为9M,因此就需要实现数据从100M时钟域到9M时钟域的跨时钟域传输。而实现数据流跨时钟域传输,较好的方式是使用双时钟fifo。因此,接下来就需要添加一个双时钟fifo,先将100M时钟域的数据缓存起来,然后再等待9M时钟域的读取逻辑来读取。 7、 fifo 设置fifo的深度为512字节,每个数据含2个symbols,每个symbols由8位数据组成,即整个数据宽度为16位。时钟设置为双时钟模式(Dual Clock Mode),输入和输出都使用Avalon ST接口,使能数据包(Enable Packet Data)。这里顺带说一下,Cyclone IV E器件中的嵌入式块RAM为M9K存储器,每个器件里都有若干个M9K存储器。所谓M9K存储器,就是一个块存储器中有9Kb个存储位,每个M9K存储器可以被配置为以下模式: 8192 × 1 4096 × 2 2048 × 4 1024 × 8 1024 × 9 512 × 16 512 × 18 256 × 32 256 × 36 由于本设计中,数据是16位的,因此,如果要想仅使用一个M9K存储器就实现本Fifo,存储深度可在1到512之间任选。假如我们选择存储深度为128,那么就只占用1/4个M9K的存储容量,但是,剩下的3/4的存储容量也依旧无法单独使用,在该设计中会永远的被浪费掉,因此,不如将存储深度直接设置为512,还能保证缓存足够大,避免意外丢失数据。 7   8、 VGA_SINK VGA_SINK模块是从友晶提供的设计中修改得到的,该IP原本仅支持24位色模式,而本系统,由于SDRAM为16位宽度,如果强行使用24位色模式,则必须得从SDRAM中读取两个数据才能拼接成一个像素,导致SDRAM的负载过大,从而使得系统带宽遇到瓶颈,无法正常工作,因此对该IP进行了修改,即降低为16bit的RGB565模式。 (根据RGB数据流的实际意义,这里应该是每个数据有3个symbols,但是本设计使用的是16位色RGB565模式,即RGB总共才16位,所以在搭建系统的时候,假设只有2个symbols,只是在VGA_SINK模块内部,在最终数据输出到VGA的时候,进行了修改,将16bit数据拆分成RGB565,分别送给VGA的R、G、B色通道)。这里参数分别如下设置: H_DISP(行显示有效像素):640 H_FPORCH:16时钟周期 H_SYNC:96时钟周期 H_BPROCH:48时钟周期 V_DISP(行显示有效像素):480 V_FPORCH:1时钟周期 V_SYNC:2时钟周期 V_BPROCH:33时钟周期 8   这些参数都是从VGA标准中查到的。 9、EPCS EPCS的添加没有什么需要特别注意的,一切按照默认设置即可。 至此,我们已经完成了整个显示系统的组件添加工作,接下来就该进行组件间的总线连接了。 在总线连接之前,必须要先给大家讲解一下本系统的数据流向,因为本系统和我们之前在SOPC公开课时候讲过的系统架构还不太一样,本系统主要引入了Avalon ST总线。在之前公开课时候讲过的各种例子,都是基于Avalon MM总线的,所有外设IP都通过Avalon MM总线连接到NIOS II CPU上,因此总线连接是非常简单的。但是Avalon MM总线的数据搬运能力是比较弱的,无法支持高速大量的数据传输。而在基于Avalon ST总线的数据流中,NIOS II CPU实质相当于被旁路了,只起到了状态管理的作用,所有的数据流转都是模块间通过Avalon ST总线进行交互的,不需要NIOS II去执行搬运工作,因此数据的搬运效率大大提高。 本节暂不对Avalon ST总线进行过多的讲解,总之,我们可以把Avalon ST总线想象成一个水管,水从水管的一头可以很容易且快速的流到另一头,不需要第三方用水瓢一瓢一瓢的去舀过去。 Framebuffer系统数据流 在整个FrameBuffer系统中,SGDMA模块是最核心的部分,他实现了对SDRAM存储器的直接读取操作,并将读到的数据最终以数据流的形式输出。SGDMA的使用相当的广泛,不仅是在本系统中,在Altera很多官方设计中也都使用了SGDMA IP核,比如PCIE 示例、千兆以太网示例、VIP示例等,关于SGDMA的详细介绍,可以参看SGDMA的用户手册,我们也希望能尽快推出SGDMA的专题讲解。这里仅对SGDMA的端口和功能进行下简述。 本系统中SGDMA被配置为存储映射模式到数据流模式,即数据的源头采用地址映射的方式组织,而数据的接收方则使用数据流的方式进行接收。在此种模式下,SGDMA总共有5个端口,分别为Avalon MM Slave接口的控制总线,Avalon MM Master接口源数据读取总线、Avalon MM Master接口的描述符写入总线和Avalon MM Master接口的描述符读取总线,以及最终的Avalon ST Source数据输出总线。 9   csr:Avalon MM Slave接口的控制总线,与NIOS II CPU进行连接,NIOS II CPU通过该接口读写SGDMA内部的控制和状态寄存器,以实现DMA的传输控制。这也是整个数据流传输唯一需要NIOS II参与的地方。NIOS II实际就扮演了个老板的角色,安排SGDMA去做什么,SGDMA接到任务后就下去干搬运的苦力活儿了,而NIOS II这个老板,则可以坐在办公室喝喝咖啡,看看报纸,等着SGDMA把苦力干完后回来报告(中断)或者隔一段时间打个电话问问工作状态和进度(查询)。 m_read:Avalon MM Master接口的源数据读取总线,该接口实现对Avalon MM总线上挂载的存储需要传输的数据源存储器的读取,例如SDRAM、DDR2 SDRAM。从这里,大家也可以看到,在Qsys搭建的系统中,不是只有NIOS II能够去读取SDRAM、DDR2等存储器,实际上,只要是Avalon MM Master接口的IP,都可以去读取这些存储器,我们甚至可以自己编写一个Avalon MM Master接口的逻辑,来取代NIOS II CPU完成各种IP核的读写操作。 descriptor_write:描述符写端口,SGDMA实现数据传输需要有一个描述符,SGDMA的所有传输都是受描述符控制的,通过描述符存储SGDMA的实时传输状态。这里专门使用一个片上存储器来存储SGDMA的描述符,能够节省FPGA资源。否则,如果使用FPGA资源来实现描述符,将会带来非常大的资源消耗。 descriptor_read:描述符读端口,SGDMA使用此端口读取描述符,以获得传输信息。 out:数据流输出端口,从SDRAM或DDR2中读取到的数据会经由此端口流出,以提供给数据的使用端使用。 对于我们分析数据流来说,可以暂时忽略csr、descriptor_write和descriptor_read端口,因为这些端口并不处于数据通路上,真正处于数据通路上的,是m_read(数据流入)和out(数据流出)端口。下图为本系统的数据流图。  10   NIOS II CPU使用SDRAM作为运行存储器,存储程序和数据,同时,SGDMA从SDRAM中实时读取需要显示到VGA屏上的数据。NIOS II 和 SGDMA之间对SDRAM的使用仲裁由Avalon MM总线的仲裁机制自动处理。 通过以上介绍,了解了系统的数据流向,就可以首先完成数据流总线的连接了。每个st 流接口的模块都有2个流端口,一个是流输入端口(in: Avalon Streaming Sink),另一个是流输出端口(out: Avalon Streaming Source)。连接时,只需将上一级模块的流out连接到下一级模块的流in端口,即可实现数据流的自动衔接。在本例中每个模块端口连接如下表所示:  11   整体连接完成后,结果如下图所示: 12   当然,连接完成总线后,不要忘了连接复位网络、导出需要引出到系统顶层的信号以及中断网络。 修改NIOS II的复位向量为EPCS、异常向量为SDRAM。 自动分配基地址。 到HDL Eaxmple栏复制例化模版。 保存并开始generate。 至此,整个系统就搭建完成了,下一步就是将系统加入到Quartus II工程中。首先是将mysystem.qsys文件添加到工程,然后完成工程顶层文件的编写。这些最基本的操作,这里就不在赘述,以下为工程顶层代码: module Framebuffer_VGA(         input  wire        clk_50m,                   input  wire        reset_n,                   output wire        vga_clk,                   output wire        vga_de,                    output wire [7:0]  vga_r,                     output wire [7:0]  vga_g,                     output wire [7:0]  vga_b,                     output wire        vga_hs,                    output wire        vga_vs,                    output wire        vga_bl,                    output wire        sdram_clk,                 output wire [11:0] sdram_addr,                output wire [1:0]  sdram_ba,                  output wire        sdram_cas_n,               output wire        sdram_cke,                 output wire        sdram_cs_n,                inout  wire [15:0] sdram_dq,                  output wire [1:0]  sdram_dqm,                 output wire        sdram_ras_n,               output wire        sdram_we_n,                output wire        epcs_dclk,                 output wire        epcs_sce,                  output wire        epcs_sdo,                  input  wire        epcs_data0         );        assign vga_bl = 1;          wire vga_clk_r;          assign vga_clk = ~vga_clk_r;    //对VGA时钟反相,以保证数据中心对齐          mysystem u0 (         .clk_50m_clk                       (clk_50m),                       .reset_50m_reset_n                 (reset_n),                       .vga_clk                           (vga_clk_r),                     .vga_de                            (vga_de),                        .vga_r                             (vga_r),                         .vga_g                             (vga_g),                         .vga_b                             (vga_b),                         .vga_hs                            (vga_hs),                        .vga_vs                            (vga_vs),                        .altpll_0_phasedone_conduit_export (),         .altpll_0_locked_conduit_export    (),             .altpll_0_areset_conduit_export    (),             .epcs_dclk                         (epcs_dclk),                     .epcs_sce                          (epcs_sce),                      .epcs_sdo                          (epcs_sdo),                      .epcs_data0                        (epcs_data0),                    .sdram_clk_clk                     (sdram_clk),                     .sdram_addr                        (sdram_addr),                    .sdram_ba                          (sdram_ba),                      .sdram_cas_n                       (sdram_cas_n),                   .sdram_cke                         (sdram_cke),                     .sdram_cs_n                        (sdram_cs_n),                    .sdram_dq                          (sdram_dq),                      .sdram_dqm                         (sdram_dqm),                     .sdram_ras_n                       (sdram_ras_n),                   .sdram_we_n                        (sdram_we_n)      ); endmodule 然后分配引脚,本系统在AC620开发板上的引脚分配请参照AC620开发板引脚分配表。  分配引脚后,记得在quartus ii软件中设置所有双功能引脚为用户模式。 13     编译工程得到sof编程文件。 至此,整个FrameBuffer系统硬件设计就完成了,下一步,我们就可以编写相应的驱动程序和应用程序,在目标板上运行了。 创建Eclipse工程 打开NIOS II EDS软件,导入我们提供的VGA和VGA_bsp两个工程。修改setting.bsp文件中第7行和第9行两个位置为自己电脑上对应的位置。关闭文件,然后build工程。如果编译失败,提示无权限,请先关闭所有文件,然后选择整个文件夹右键获取管理员权限。 14   以下为main函数内容   #include "stdio.h" #include "stdlib.h" #include "io.h" #include "sys/alt_alarm.h" #include "altera_avalon_sgdma.h" #include "altera_avalon_sgdma_descriptor.h" #include "altera_avalon_sgdma_regs.h" #include "alt_types.h" #include "alt_video_display.h" #include "unistd.h" #include "pic1.h" #include "pic2.h" #include  "system.h"     #define  WIDTH 640 #define  HEIGHT 480 #define  NUM_FRAME 1     int main() {     unsigned int d = 0;       ////Initial LCD Display     alt_video_display* display_global; //  printf("Initializing LCD display controller\n  ");     display_global = alt_video_display_init(LCD_SGDMA_NAME, // Name of video controller             WIDTH, // Width of display             HEIGHT, // Height of display             16, // Color depth (32 or 16)             SDRAM_BASE + SDRAM_SPAN / 2, // Where we want our frame buffers             ONCHIP_MEMORY_BASE, // Where we want our descriptors             NUM_FRAME); //  if (display_global) //      printf(" - LCD Initialization OK\n"); //  else //      printf(" - LCD FAILED\n");     alt_video_display_clear_screen(display_global, 0xff);     show_pic(display_global, pic1);     usleep(1000000);     while(1){     } }   编译完成后下载sof,并在nios ii eds中执行run。连接VGA显示器,就能在屏幕上显示一副美女图,如下图所示:

  • 发表了主题帖: 【工程源码】基于FPGA的XPT2046触摸控制器设计

    本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。 XPT2046是一款设计用于移动电话、个人数字助理、便携式一起、付款中断设备、触摸屏显示器等设备的4线制电阻触摸屏控制器。该芯片实质为一个多通道ADC+电压输出芯片,通过在不同时刻对电阻触摸屏的两组不同电极上分别施加电压,然后测量另一组电极上的电压值,从而获取触摸点的X或Y位置坐标,进而提供给处理器进行处理。 电阻触摸屏简介 四线电阻式触摸屏,主要由两层镀有ITO镀层的薄膜组成。其中一层在屏幕的左右边缘各有一条垂直总线,另一层在屏幕的底部和顶部各有一条水平总线,如果在一层薄膜的两条总线上施加电压,在ITO镀层上就会形成均匀电场。当使用者触击触摸屏时,触击点处两层薄膜就会接触,在另一层薄膜上就可以测量到接触点的电压值。 1 为了在X轴方向进行测量,将左侧总线偏置为0V,右侧总线偏置为VCC。将顶部或底部总线连接到ADC,当顶层和底层相接触时即可作一次测量。 为了在Y轴方向进行测量,将顶部总线偏置为VCC,底部总线偏置为0V。将ADC输入端接左侧总线或右侧总线,当顶层与底层相接触时即可对电压进行测量。 如下图,测量出来的电压值与接触点的位置线性相关,即可以由VPX和VPY分别计算出接触点P的X和Y坐标。 在实际测量中,控制电路会交替在X和Y电极组上施加VCC电压,进行电压测量和计算接触点的坐标。举例说明测量流程: 第一步,在X+上施加VCC,X-上施加0V电压,测量Y+(或Y-)电极上的电压值VPX,计算出接触点P的X坐标; 第二步,在Y+上施加VCC,Y-上施加0V电压,测量X+(或X-)电极上的电压值VPY,计算出接触点P的Y坐标; 以上两步组成一个测量周期,可以得到一组(X,Y)坐标。 2   图2.1:触摸屏工作原理示意图 电阻触摸屏控制器XPT2046 通过以上介绍,可知要实现对某个触摸点的坐标测量,需要对电阻触摸屏模组的两层导电薄膜分时施加电压,在对其中一个导电薄膜的电极施加电压时,使用ADC去测量另一层导电薄膜的电极上的电压。由此可知,触摸控制器必须能够支持两个功能: 触摸控制器能够对连接的电极施加电压 触摸控制器能够测量电极上的电压(ADC) 即触摸控制器不仅仅是简单的ADC,因为其还要能够给电极提供电压,所以我们无法使用通用的ADC来完成4线电阻触摸屏的控制。为了实现对电阻触摸屏的控制,以TI为代表的众多厂商推出了专用的触摸控制器,如TI的TSC2046、ADS7843,两者功能相同,封装兼容,可以直接替换。同时,国内也有厂商推出了能够完全兼容的器件,最典型的如深圳矽普特公司推出的XPT2046,该芯片可完全兼容TI的TSC2046器件。本教程主要以该芯片为依据进行讲解。 XPT2046特性: 工作电压范围为 2.2V~5.25V 支持 1.5V~5.25V 的数字 I/O 口 内建 2.5V 参考电压源 电源电压测量( 0V~6) 内建结温测量功能 触摸压力测量 采用 SPI 3 线控制通信接口 具有自动 power-down 功能 封装: QFN-16、 TSSOP-16 和 VFBGA-48 与 TSC2046、 AK4182A 完全兼容 XPT2046 在 125KHz 转换速率和 2.7V 电压下的功耗仅为750 µW。 XPT2046 以其低功耗和高速率等特性,被广泛应用在采用电池供电的小型手持设备上,比如 PDA、手机等。 下图为XPT2046的功能框图,可见XPT2046内部包含了一个多路选择器,能够测量电池电压、AUX电压、芯片温度。一个12位的ADC用于对选择的模拟输入通道进行模数转换,得到数字量,然后送入控制逻辑电路,供主控CPU进行读取,同时,具体选择哪个通道进行转换,也是由主控CPU发送命令给控制逻辑来设置的。 3   XPT支持笔触中断,即当触摸屏检测到触摸被按下时,可以立即产生笔触中断,通知主控制器可以控制开始转换并读取数据。在转换过程中,通过busy信号指示当前忙状态,以避免主控发出新的命令中断之前的命令。 XPT2046引脚 XPT2046通过SPI接口与主控制器进行通信,其与主控制器的接口包括以下信号: PENIRQ_N:笔触中断信号,当设置了笔触中断信号有效时,每当触摸屏被按下,该引脚被拉为低电平。当主控检测到该信号后,可以通过发控制信号来禁止笔触中断,从而避免在转换过程中误触发控制器中断。该引脚内部连接了一个50K的上拉电阻。 CS_N:芯片选中信号,当CS_N被拉低时,用来控制转换时序并使能串行输入/输出寄存器以移出或移入数据。当该引脚为高电平时,芯片(ADC)进入掉电模式。 DCLK:外部时钟输入,该时钟用来驱动SAR ADC的转换进程并驱动数字IO上的串行数据传输。 DIN:芯片的数据串行输入脚,当CS为低电平时,数据在串行时钟DCLK的上升沿被锁存到片上的寄存器。 DOUT:串行数据输出,在串行时钟DCLK的下降沿数据从此引脚上移出,当CS_N引脚为高电平时,该引脚为高阻态。 BUSY:忙输出信号,当芯片接收完命令并开始转换时,该引脚产生一个DCLK周期的高电平。当该引脚由高点平变为低电平的时刻,转换结果的最高位数据呈现在DOUT引脚上,主控可以读取DOUT的值。当CS_N引脚为高电平时,BUSY引脚为高阻态。 XPT2046工作原理 XPT2046 是一种典型的逐次逼近型模数转换器( SAR ADC),包含了采样/保持、模数转换、串口数据输出等功能。同时芯片集成有一个 2.5V的内部参考电压源、温度检测电路,工作时使用外部时钟。 XPT2046 可以单电源供电,电源电压范围为 2.7V~5.5V。参考电压值直接决定ADC的输入范围,参考电压可以使用内部参考电压,也可以从外部直接输入1V~VCC范围内的参考电压(要求外部参考电压源输出阻抗低)。 X、 Y、 Z、 VBAT、 Temp和AUX模拟信号经过片内的控制寄存器选择后进入ADC, ADC可以配置为单端或差分模式。选择VBAT、 Temp和AUX时可以配置为单端模式;作为触摸屏应用时,可以配置为差分模式,这可有效消除由于驱动开关的寄生电阻及外部的干扰带来的测量误差,提高转换准确度。 下图为XPT2046的典型工作电路: 4   XPT2046有四个引脚,用于连接到四线制电阻屏的FPC上,分别为XP、XN、YP、YN,连接到对应的四线制电阻屏的X电极的正端、负端和Y电极的正端、负端。此四个引脚每个都能工作于两种状态,分别为电源/GND输出、ADC输入。例如设置ADC工作在差分模式,当测量X方向的坐标时,XP输出VCC、XN连接到GND,此时,YP和YN作为ADC的差分输入脚连接到ADC上,通过测量YP和YN之间的电压差来得到当前触摸点的X位置。同理,当测量Y方向的坐标时,YP输出VCC、YN连接到GND、此时,XN和XP作为ADC的差分输入脚连接到ADC上,通过测量YP和YN之间的电压差来得到当前触摸点的Y位置。 XPT2046控制驱动方案 了解了XPT2046的接口电路,接下来我们就可以通过主控MCU或FPGA来控制该芯片实现坐标的读取了。要想正确的读到X、Y坐标,需要按照芯片规定的控制协议进行数据的读写。XPT2046实现一次X、Y坐标的读取需要完成两次转换,单一一次转换只能得到单一X或Y的坐标,因此,我们必须通过两次控制才能到到结果。至于每一次转换的对象为X或Y坐标,由控制器发出的控制字决定。ADC在转换时能够被配置为单端或差分模式,具体的控制字在每次传输开始的时候,由主控MCU驱动DIN信号传输。下图为XPT2046典型的24时钟周期转换控制时序: 5   XPT2046 数据接口是串行接口,其典型工作时序如上图所示,图中展示的信号来自带有基本串行接口的单片机或数据信号处理器。处理器和转换器之间的的通信需要 8 个时钟周期,可采用 SPI、 SSI 和 Microwire 等同步串行接口。一次完整的转换需要 24 个串行同步时钟( DCLK)来完成。 前 8 个时钟用来通过DIN引脚输入控制字节。当转换器获取有关下一次转换的足够信息后,接着根据获得的信息设置输入多路选择器和参考源输入,并进入采样模式,如果需要,将启动触摸面板驱动器。 3 个多时钟周期后,控制字节设置完成,转换器进入转换状态。这时,输入采样-保持器进入保持状态,触摸面板驱动器停止工作(单端工作模式)。接着的12 个时钟周期将完成真正的模数转换。如果是度量比率转换方式( SER/DFR ——=0),驱动器在转换过程中将一直工作,第13 个时钟将输出转换结果的最后一位。剩下的 3 个多时钟周期将用来完成被转换器忽略的最后字节( DOUT置低) 控制字节由 DIN 输入的控制字如下表所示,它用来启动转换,寻址,设置 ADC 分辨率,配置和对 XPT2046 进行掉电控制。 起始位:第一位,即 S 位。控制字的首位必须是 1,即 S=1。在 XPT2046 的 DIN 引脚检测到起始位前,所有的输入将被忽略。 地址:接下来的 3 位( A2、 A1 和 A0)选择多路选择器的现行通道(见表 1、表 2),触摸屏驱动和参考源输入。 MODE:模式选择位,用于设置 ADC 的分辨率。 MODE=0,下一次的转换将是 12 位模式; MODE=1,下一次的转换将是 8 位模式。 SER/DFR:SER/ DFR位控制参考源模式,选择单端模式( SER/DFR=1),或者差分模式( SER/DFR=0)。在X坐标、 Y坐标和触摸压力测量中,为达到最佳性能,首选差分工作模式。参考电压来自开关驱动器的电压。在单端模式下,转换器的参考电压固定为VREF相对于GND引脚的电压(更详细的说明,见表 1 和表 2)。 PD0 和 PD1:表 5 展示了掉电和内部参考电压配置的关系。 ADC 的内部参考电压可以单独关闭或者打开,但是,在转换前,需要额外的时间让内部参考电压稳定到最终稳定值;如果内部参考源处于掉电状态,还要确保有足够的唤醒时间。 ADC 要求是即时使用,无唤醒时间的。另外还得注意,当 BUSY 是高电平的时候,内部参考源禁止进入掉电模式。XPT2046 的通道改变后,如果要关闭参考源,则要重新对 XPT2046 写入命令。 表1 单端模式下的地址与通道对应关系 6   表2 差分模式下的地址与通道对应关系 7   表3 控制字段的每一位功能 8   表4 控制字段每一位功能的具体说明 9   表5 PD位功能说明 10   上述通过24时钟周期的转换时序讲解了单次转换的时序,在实际应用中,为了提高转换效率,XTP2046提供了16时钟转换模式和15时钟转换模式。 16 时钟周期转换  第 n+1 次转换的控制位可以与第 n 次转换部分重叠,所以可以用 16 个时钟周期完成一次转换,如图 16 所示。图 16 也说明了处理器和转换器之间的串行通信是可以双向独立进行的。此时,每次转换必须在开始后(接收到 start)的 1.6mS 内完成,否则输入采样保持电路取样的信号会逐渐被放电衰减,影响转换结果。另外,在转换过程中另一串行通信的存在会使 XPT2046 工作于全功耗状态下。 11   8 位总线接口,无 DCLK 时钟延迟 16 时钟周期转换时序 该模式下,DCLK的时钟高电平和低电平均要求最小值为200ns,即DCLK的时钟周期为2.5MHz。 15 时钟周期转换  下图给出了 XPT2046 的最快时序。这种方法不支持大部分的微控制器和数字信号处理器的串行接口,因为它们一般都不提供 15 周期的串行传输方式。但是,这种方法适用于 FPGA 和 ASIC。需要注意的是,这样有效地提高了转换器的最大转换速率。  12   最快转换速率, 15 时钟周期转换 在不影响输出精度的前提下提高数据吞吐量, XPT2046 可以采用 8 位的转换模式。切换到 8 位转换模式,完成提前 4 个时钟完成一次转换。不仅每次转换缩短了都 4 位(数据吞吐量提高了 25%),而且由于精度的降低,可以工作在更快的转换速率下,时钟速度可以提高 50%,时钟速度的提高和转换周期的减少,共同可以使转换速率提高 2 倍。 XPT2046驱动设计 通过上述介绍,我们了解了电阻触摸屏的工作原理以及常用的触摸控制器XPT2046的特性以及时序接口,接下来,我们将针对XPT2046的接口特性以及FPGA的工作特点,使用Verilog设计一个能够控制XPT2046完成坐标转换并最终得到触摸坐标点的逻辑驱动电路。 设计的控制器希望能够具有以下特性: 1. 12位转换精度 2. 高转换效率 3. 使用笔触中断 4. 使用差分模式测量坐标 另外,根据工程实践应用经验,触摸屏的按压过程中会存在抖动,因此,我们需要对转换结果进行抖动滤波处理,滤波最简单的算法就是多次采用,去掉最大值和最小值后计算平均值。即我们需要连续多次采样,然后对采样的结果进行处理后,再作为最终的XY坐标,送给下一级使用。 上述需求主要与控制字段有关。 条件1,要得到12位采样精度,根据表4,控制字段的第3位MODE位应该为0。 条件2,要实现高转换效率,由于我们使用FPGA进行控制,因此可以使用专用的15时钟周期转换时序。 条件3,使用笔触模式,则在转换过程中设置PD始终为00。 条件4,设置为差分模式,则设置SER/DFR位为0。 当实际进行转换时,根据表2,测量X坐标时,设置A2—A0为101。测量Y坐标时,设置A2—A0为001。 为了实现多次测量求均值,可以选择每检测到一次笔触中断,分别进行18次X坐标和Y坐标转换,然后分别找出其中的最大值和最小值并去除,再将余下的16次结果除以16(右移4位),即可获得当前位置的坐标滤波后的值。 13   根据XPT2046提供的时序接口可知,要实现该控制接口,最方便的方式就是使用线性序列机。因为在每一个DCLK的上升沿或者下降沿需要发出或者读取什么数据是完全已知的,符合线性序列机的设计特点。 设计实现:本实例代码中均做了详细注释,本例中不再逐行讲解,请大家在学习时候,将看不明白的地方指出来,方便我们针对性进行补充讲解。 XPT2046驱动代码 module xpt2046(     Clk50m,     Rst_n,     EN,     X_Value,     Y_Value,     Get_Flag,          PenIrq_n,     DCLK,     DIN,     DOUT,     CS_N,     BUSY );     input Clk50m;     input Rst_n;     input EN;     output reg [11:0]X_Value;     output reg [11:0]Y_Value;          output reg Get_Flag;          input PenIrq_n;     input BUSY;     output reg DCLK;     output reg DIN;     output reg CS_N;     input  DOUT;          wire pen_flag;     wire pen_state;          reg [4:0]DIV_CNT;//得到DCLK时钟两倍的采样时钟以产生DCLK     reg [5:0]CLK_GEN_CNT;//产生DCLK时钟计数器     reg [5:0]CONV_CNT;//记录完成了多少次转换          reg [19:0]PEN_CNT;          reg DCLK2X;     reg CONV_DONE;     reg [11:0]Dtmp;     reg EN_CONV;          reg [16:0]tmp_X_Value,tmp_Y_Value;     reg [11:0]X_MAX,X_MIN,Y_MAX,Y_MIN;     reg r_Get_Flag;          localparam S = 1'b1;    //起始位     localparam MODE = 1'b0; //采样精度     localparam SER_DFR = 1'b0; //单端/差分采样模式     localparam PD = 2'b00;  //功耗控制     parameter CONV_TIMES = 36;  //每多少次转换计算一次均值     parameter FILTER_PARAM = 4; //除以16 == 右移4位          parameter CNT_TOP = 20'd499999; //对PEN引脚信号滤波延时          wire [2:0]ADDR; //采样通道控制          assign ADDR = (CONV_CNT[0])?3'b101:3'b001;//CONV_CNT值为偶数,选择测量X通道          wire cnt_full;//PEN引脚信号滤波计数器计数满标志          //PEN引脚延时滤波计数器     always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         PEN_CNT <= 20'd0;     else if(!PenIrq_n)begin //笔触为低电平         if(cnt_full)    //计满归零             PEN_CNT <= 20'd0;         else    //未计满累加             PEN_CNT <= PEN_CNT + 1'b1;     end else    //笔触为高电平,禁止计数         PEN_CNT <= 20'd0;              assign cnt_full = (PEN_CNT == CNT_TOP);          assign pen_state = cnt_full;//在PenIrq_n引脚为低电平的时候,每计数满产生一次pen_state信号,触发一36次采样     //2倍DCLK采样时钟分频计数器        always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         DIV_CNT <= 5'd0;     else if(EN_CONV)begin         if(DIV_CNT == 5'd24)             DIV_CNT <= 5'd0;         else              DIV_CNT <= DIV_CNT + 1'b1;     end     else         DIV_CNT <= 5'd0;          //产生2倍DCLK使能时钟     always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         DCLK2X <= 1'b0;     else if(DIV_CNT == 5'd24)         DCLK2X <= 1'b1;     else         DCLK2X <= 1'b0;     //对2倍DCLK采样时钟进行技术,以产生序列机基本序列     always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         CLK_GEN_CNT <= 6'b0;     else if(EN_CONV)begin         if(DCLK2X)begin             if(CLK_GEN_CNT == 6'd45)//计数到46以后,回到16开始重新计数                 CLK_GEN_CNT <= 6'd16;             else                 CLK_GEN_CNT <= CLK_GEN_CNT + 1'b1;         end     end     else         CLK_GEN_CNT <= 6'b0;     //根据CLK_GEN_CNT值控制序列,发送控制字并读取采样结果     always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)begin         DIN <= 1'b1;         Dtmp <= 12'd0;         DCLK <= 1'd0;         CONV_CNT <= 6'd0;     end          else if(EN_CONV)begin         if(DCLK2X)begin             case(CLK_GEN_CNT)                 0:begin DIN <= S; DCLK <= 1'b0; end //发送首次转换起始位                 1:begin DCLK <= 1'b1; end                                  2:begin DIN <= ADDR[2]; DCLK <= 1'b0; end   //发送A2                 3:begin DCLK <= 1'b1; end                                  4:begin DIN <= ADDR[1]; DCLK <= 1'b0; end//发送A1                 5:begin DCLK <= 1'b1; end                                  6:begin DIN <= ADDR[0]; DCLK <= 1'b0; end//发送A0                 7:begin DCLK <= 1'b1; end                                  8:begin DIN <= MODE; DCLK <= 1'b0; end//发送采样精度设置位                 9:begin DCLK <= 1'b1; end                                  10:begin DIN <= SER_DFR; DCLK <= 1'b0; end//发送ADC输入模式位                 11:begin DCLK <= 1'b1;end                                  12:begin DIN <= PD[1]; DCLK <= 1'b0; end//发送功耗控制位PD1                 13:begin DCLK <= 1'b1; end                                  14:begin DIN <= PD[0]; DCLK <= 1'b0; end//发送功耗控制位PD0                 15:begin DCLK <= 1'b1; end                                  16:begin DIN <= 0; DCLK <= 1'b0; end//等待采样保持电路工作                 17:begin DCLK <= 1'b1; end                                  18:begin DIN <= 0; DCLK <= 1'b0; end                 19:begin Dtmp[11] <= DOUT; DCLK <= 1'b1; end//读取第11位转换结果                                  20:begin DIN <= 0; DCLK <= 1'b0; end                 21:begin Dtmp[10] <= DOUT; DCLK <= 1'b1; end//读取第10位转换结果                                  22:begin DIN <= 0; DCLK <= 1'b0; end                 23:begin Dtmp[9] <= DOUT; DCLK <= 1'b1; end//读取第9位转换结果                                  24:begin DIN <= 0; DCLK <= 1'b0; end                 25:begin Dtmp[8] <= DOUT;DCLK <= 1'b1; end//读取第8位转换结果                                  26:begin DIN <= 0; DCLK <= 1'b0; end                 27:begin Dtmp[7] <= DOUT; DCLK <= 1'b1; end//读取第7位转换结果                                  28:begin DIN <= 0; DCLK <= 1'b0; end                 29:begin Dtmp[6] <= DOUT; DCLK <= 1'b1; end//读取第6位转换结果                                  30:begin DIN <= S; DCLK <= 1'b0; end    //发送下次转换的控制字起始位                 31:begin Dtmp[5] <= DOUT; DCLK <= 1'b1; end//读取第5位转换结果                                  32:begin DIN <= ADDR[2]; DCLK <= 1'b0; end//发送下次转换的A2                 33:begin Dtmp[4] <= DOUT; DCLK <= 1'b1; end                                  34:begin DIN <= ADDR[1]; DCLK <= 1'b0; end//发送下次转换的A1                 35:begin Dtmp[3] <= DOUT; DCLK <= 1'b1; end                                  36:begin DIN <= ADDR[0]; DCLK <= 1'b0; end//发送下次转换的A0                 37:begin Dtmp[2] <= DOUT; DCLK <= 1'b1; end                                  38:begin DIN <= MODE; DCLK <= 1'b0; end//发送下次转换的采样精度设置位                 39:begin Dtmp[1] <= DOUT; DCLK <= 1'b1; end                                  40:begin DIN <= SER_DFR; DCLK <= 1'b0; end//发送下次采样ADC输入模式位                 41:begin Dtmp[0] <= DOUT; DCLK <= 1'b1; CONV_CNT <= CONV_CNT + 1'b1; end                          42:begin DIN <= PD[1]; DCLK <= 1'b0; end//发送功耗控制位PD1                 43:begin DCLK <= 1'b1; end                                  44:begin DIN <= PD[0]; DCLK <= 1'b0; end//发送功耗控制位PD0                 45:begin DCLK <= 1'b1; CONV_DONE <= 1'b1; end                endcase         end else             CONV_DONE <= 1'b0;     end else if(!EN_CONV)begin         CONV_CNT <= 0;         CONV_DONE <= 1'b0;     end          //将36次采样中18次的X通道的采样结果累加     always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         tmp_X_Value <= 17'd0;     else if(EN_CONV == 1'b0)         tmp_X_Value <= 17'd0;     else if(CONV_DONE && CONV_CNT[0])//转换完成,转换计数为奇数,将转换结果累加到X临时寄存器         tmp_X_Value <= tmp_X_Value + Dtmp;     //记录18次X通道采样的最大值           always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         X_MAX <= 12'd0;     else if(EN_CONV == 1'b0)         X_MAX <= 12'd0;     else if(CONV_DONE && CONV_CNT[0])begin//转换完成,转换计数为奇数,判断当前值是否大于已存最大值         if(Dtmp > X_MAX)             X_MAX <= Dtmp;         else             X_MAX <= X_MAX;     end          //记录18次X通道采样的最小值           always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         X_MIN <= 12'd0;     else if(EN_CONV == 1'b0)         X_MIN <= 12'd4095;     else if(CONV_DONE && CONV_CNT[0])begin//转换完成,转换计数为奇数,判断当前值是否小于已存最小值         if(Dtmp < X_MIN)             X_MIN <= Dtmp;         else             X_MIN <= X_MIN;     end          //将36次采样中18次的Y通道的采样结果累加     always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         tmp_Y_Value <= 17'd0;     else if(EN_CONV == 1'b0)         tmp_Y_Value <= 17'd0;     else if(CONV_DONE && (!CONV_CNT[0]))//转换完成,转换计数为偶数,将转换结果累加到Y临时寄存器         tmp_Y_Value <= tmp_Y_Value + Dtmp;          //记录18次Y通道采样的最大值       always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         Y_MAX <= 12'd0;     else if(EN_CONV == 1'b0)         Y_MAX <= 12'd0;     else if(CONV_DONE && (~CONV_CNT[0]))begin//转换完成,转换计数为奇数,判断当前值是否大于已存最大值         if(Dtmp > Y_MAX)             Y_MAX <= Dtmp;         else             Y_MAX <= Y_MAX;     end          //记录18次Y通道采样的最小值     always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         Y_MIN <= 12'd0;     else if(EN_CONV == 1'b0)         Y_MIN <= 12'd4095;     else if(CONV_DONE && (~CONV_CNT[0]))begin//转换完成,转换计数为奇数,判断当前值是否小于已存最小值         if(Dtmp < Y_MIN)             Y_MIN <= Dtmp;         else             Y_MIN <= Y_MIN;     end          //使能一个36次转换     always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         EN_CONV <= 1'b0;     else if(EN)begin         if(pen_state)             EN_CONV <= 1'b1;         else if((CONV_CNT == CONV_TIMES) && CLK_GEN_CNT == 29)//转换完成,对齐15周期时序             EN_CONV <= 1'b0;         else             EN_CONV <= EN_CONV;     end     else         EN_CONV <= 1'b0;     //     always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         r_Get_Flag <= 1'b0;     else if((CONV_CNT == CONV_TIMES) && CONV_DONE)             r_Get_Flag <= 1'b1;     else         r_Get_Flag <= 1'b0;              always@(posedge Clk50m)         Get_Flag <= r_Get_Flag;          always@(posedge Clk50m)         CS_N <= ~EN_CONV;              reg [11:0]r_X_Value,r_Y_Value;          //计算当前X均值,X均值 = (18次累加值 - 最大值 - 最小值)/ 16     always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         r_X_Value <= 12'd0;     else if(r_Get_Flag)         r_X_Value <= (tmp_X_Value - X_MAX - X_MIN) >> FILTER_PARAM;     else         r_X_Value <= r_X_Value;          //计算当前Y均值,Y均值 = (18次累加值 - 最大值 - 最小值)/ 16       always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         r_Y_Value <= 12'd0;     else if(r_Get_Flag)         r_Y_Value <= (tmp_Y_Value - Y_MAX - Y_MIN) >> FILTER_PARAM;     else         r_Y_Value <= r_Y_Value;     //存储上一次X结果作为输出,为了滤除最后一次转换结果,因为最后一次转换结果存在按压释放时刻,结果不太稳定     always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         X_Value <= 12'd0;     else if(r_Get_Flag)         X_Value <= r_X_Value;     //存储上一次Y结果作为输出,为了滤除最后一次转换结果,因为最后一次转换结果存在按压释放时刻,结果不太稳定            always@(posedge Clk50m or negedge Rst_n)     if(!Rst_n)         Y_Value <= 12'd0;     else if(r_Get_Flag)         Y_Value <= r_Y_Value; endmodule   XPT2046设计验证 `timescale 1ns/1ns module xpt2046_tb;     reg Clk50m;     reg Rst_n;     reg EN;     wire [11:0]X_Value;     wire [11:0]Y_Value;          wire Get_Flag;          reg PenIrq_n;     reg BUSY;     wire DCLK;     wire DIN;     wire CS_N;     reg  DOUT;     initial Clk50m = 1;     always #10 Clk50m = ~Clk50m;          initial begin         Rst_n = 0;         PenIrq_n = 1;         EN = 0;         #201;         Rst_n = 1;         EN = 1;         #300;         PenIrq_n = 0;         #5000000;         PenIrq_n = 1;         #5000000;         $stop;              end      //  initial begin //      DOUT = 1'b0; //      forever begin //          DOUT = ~DOUT; //          #30154; //      end //  end     initial DOUT = 1;          xpt2046 xpt2046(         .Clk50m(Clk50m),         .Rst_n(Rst_n),         .EN(EN),         .X_Value(X_Value),         .Y_Value(Y_Value),         .Get_Flag(Get_Flag),                  .PenIrq_n(PenIrq_n),         .DCLK(DCLK),         .DIN(DIN),         .DOUT(DOUT),         .CS_N(CS_N),         .BUSY(BUSY)     ); endmodule 以下为简单的XPT2046测试脚本,该测试脚本没有产生复杂的激励,仅仅产生了时钟和笔触中断,用以观察对应的控制信号在整个转换过程中是否正常工作。   XPT2046在开发板上验证  

统计信息

已有299人来访过

  • 芯币:1171
  • 好友:5
  • 主题:137
  • 回复:93
  • 课时:--
  • 资源:2

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言