一直占着博客推荐位,却没有拿出什么真正有技术含量的东西,实在遗憾,所以决定将近期在学习与关注的网络连载学习文章转到这里来,avic是一位热心而且乐于共享的一位网友,令人非常震撼的是在开放学习的姿态下,NIOSII教程里面的每个步骤都描述的非常清楚,图片详尽,如此的精神实在令人佩服,能将他的文章转到我的博客也是我的幸事。
目前已经有12文章,也请关注avic的博客动态:
http://www.cnblogs.com/kingst/ NIOS II开发的整个流程---硬件开发(一)前言
从今天开始,NIOS的学习征途正式拉开了。对于NIOS的学习爱好者,我相信这是一个福音,我将毫无保留的将我对NIOS的研究成果分享给大家。我之所
以采用博客这种方式,就是想跟大家充分的交流,大家可以给我留言,也可以在Ourdev中提出问题,我将尽我的全力为大家解决问题。由于本人水平有限,如
果有我解决不了的问题,还请高手们多多帮忙,我相信能为大家解决问题是一件很快乐的事情,你不会错过的。
废话少说,我们马上进入正题。今天是第一节,我首先说一下学习NIOS都需要哪些前提条件。听到
这,初学者可以会有些害怕了,难道学习NIOS还要条件?是的,需要条件,不过这些条件并不是很高,只要大家努力,这些条件都不是问题。
- 具有一定
的单片机基础; - 具有一定
的C语言编程能力; - 了解
Quartus II的开发流程; - 一块开发
板;
就这么多,大家觉得难么?首先说说第一条,具有一定的单片机基础,这个条件是要有的。单片机的基础在NIOS
II学习中体现在它的寄存器操作方式上,这种操作方式是通用的,不管是ARM,DSP,还是51都是一样的,你只要有一种单片机的实践经验就没问题了。再
说第二条,这一条没什么可争议的,NIOS的开发完全是用C语言的,如果你没有C语言的基础,我建议你还是先学习一下C语言再考虑学习NIOS吧。第三条
呢,有最好,如果没有的话也可以,我在以后的文章中都会涉及到,大家跟着学就可以了。第四条也不是必须的,不过学习NIOS不像学习
Verilog/VHDL,通过仿真看看也行,NIOS的学习跟单片机很相似,最好是亲手操作硬件,这样对你的学习有更好的效率和效果。在这里推荐一下我
的FPGA黑金开发板,不仅仅是广告哦,因为我以后的讲解都是以我的黑金板为基础的,大家学习起来也很方便的。
简介
NIOS是一个用户可配置的通用32位RISC嵌入式处理器,它是SOPC(System On a Programmable
Chip,片上可编程系统)的核心。处理器以软核形式实现,具有高度的灵活性和可配置性。NIOS的开发包括硬件开发和软件开发两部分。硬件开发是在
Quartus II中实现的,而软件开发部分是在NIOS IDE软件中实现的。我们首先来介绍NIOS的硬件开发。所谓硬件开发就是用Quartus
II 和 SOPC builder来建立自己需要的软核。
软件版本:本教程基于Quartus II 9.0,NIOS IDE 9.0编写
建立工程
首先,打开Quartus II 9.0软件
接下来,建
立一个工程File->New Project Wizard
第一行是工
程的路径,你选择你放置的路径即可。
第二,第三
行都是是工程名,写好以后如下
点击
Next,
这个不用
动,接着点击Next
在这
里,Family里选择Cyclone II,在
Available devices中选择EP2C5Q208C8(具
体内容根据你的芯片所定)。如上图所示。
接着点击
Next,不用动,点击Finish,显示如下图所示。
到此为止,
工程已经建立完成。接下来,需要建立一个Block
Diagram/Schematic File,点击File->New,如下图所示
点击OK,
完成建立,工程中出现了一个Block1.bdf文件
建立NIOS II软核
接下来,我们进入了软核建立环节,这一步很重要,是建立软核的核心步骤。点击Tools->SOPC Builder或者下图红圈标示的图标
点击以
后,SOPC Builder运行,界面如下图示
System
Name中输入软核的名字:我将其命名为KERNEL。点击OK后,如下图所示
按图中标注
的,clk_0为时钟名称,50.0为时钟值(单位为MHz),我们可以对他们进行修改。用鼠标点击50.0,将其改为100.0。这时候,我们的软核时
钟就为100.0MHz了。这是软核建立的第一步,接下来,我们要建立Nios II Processor。
建立CPU
用鼠标点击左侧边框的Nios II Processor,如下图红框所示
点击后,将
出现下图
这一步我们
来选择软核的类型。这里给我们提供了三种类型,Nios II/e占用资源最少600-800LEs,功能也最简单,速度最慢。Nios
II/s占资源比前者多一些,功能也多了,速度也快一些Nios
II/f占资源最多,功能也最多,速度就快。选择的时候要根据你的需求和你的芯片资源来决定。在这里,我选择Nios
II/s,功能和速度都可以得到满足。下面的Reset
Vector是复位后启动时的Memory类型和偏移量, Exception
Vector是异常情况时的Memory类型和偏移量。现在还不能配置,需要SDRAM和FLASH设置好以后才能修改这里,这两个地方很重要。接下来连续点击Next,一直到下图为止
这里设置
JTAG Debug Module,即JTAG调试时所用到的功能模块。功能越多,需要的资源越多,这里,我们选择Level
1即可,不需要过多其他的功能。点击Finish,结束Nios II
Processor的建立后,如下图所示
建立SDRAM模块
接下来,我们要建立SDRAM控制器,点击下图红框所示的地方
点击后,如
下图所示
在
Presets中选择第一项Custom,在
Bits中选择16,其他项不动,点击Next,
点击Finish,完成SDRAM控制器的设置。在这里之所以选择16,是因为我们用的SDRAM(HY57V641620)是16位的。
建立Avalon三态桥
接下来,我们要建立一个Avalon三态桥,在NIOS系统中,要实现与FPGA片外存储器通信,就必须在Avalon总线和连接外部存储器的总线之间添
加一个桥,这个桥就是Avalon三态桥。点击下图所示红圈处
点击后,如
下图所示
点击
Finish后,完成三态桥的建立。
建立CFI模块
接下来,我们就建立Flash Memory Interface(CFI)模块,点击下图所示红圈处
点击后,如
下图所示
其中
Presets,我们选择Custom,Address Width(bits)是地址线宽度,我们选择21,Data
Width(bits)数据线宽度,我们选择8位。这些的选择都是根据芯片和电路设置决定的,我们的FLASH(AM29LV1602)可以选择为8位模
式和16位模式,我在设计电路的时候将其配置为8位模式,所以在此数据线宽度选择为6位,地址线宽度选择为21位。设置好以后,点击Next,出现下图所示
这里,我们需要设置三个量,Setup,Wait,Hold,我们分别将其设置为40,160,40如上图所示。这些量都是根据FLASH的芯片决定的,
各个芯片都不一样,在此不具体讲了。点击Finish后,完成Flash Memory Interface(CFI)的建立。到此为止,我们已经建立了CPU,SDRAM,FLASH模块,接下来,我们还要添加一些必要的模
块,System ID,JTAG UART。
建立SYSTEM ID
System
ID就是一种标示符,类似校验和的这么个东西,在你下载程序之前或者重启之后,都会对它进行检验,以防止错误发生,大家知道这个东西就可以了,没太大的用
处,但需要有的。点击下图所示红圈处
点击后下图
所示
点击
Finish,完成System ID的建立。
建立JTAG UART
JTAG UART是实现PC和Nios II系统间的串行通信接口,它用于字符的输入输出,在Nios
II的开发调试过程中扮演了重要的角色,接下来我们开始建立它的模块。点
击下图所示红圈处,
点击后,如
下图所示
什么都不用
修改,直接点击Next->Finish完成 JTAG UART模块的建立。到此为止,最基本的NIOS系统模块就建立完成了,如下图所示
配置及编译NIOS II
我建议大家将他们的名字都改一下,将_0都去掉,看着别扭,鼠标右键点击相应名字,Rename就可以修改了。我们离成功已经很近了,再进行几步处理以后,我们就可以编译了 ,大家加油吧。大家看下图红圈处
cfi_flash
还没有跟tristate_master连接,当我们把鼠标移到红圈处位置时,就出现了下图所示的情况
这时候,我
们用鼠标点击红圈处的空心圆,这时候奇迹出现了,呵呵,如下图所示
空心圆变成
了实心圆,这就完成了cfi_flash与三态桥的连接,大家亲手试试吧。接
下来我们需要对cpu进行设置一下,双击cpu,Reset Vector处的Memeory选择cfi_flash,Exceptioni
Vector选择sdram,其他不变,如下图所示出现下图所示
点击
Finish,完成cpu设置。
接下来,我们需要对FLASH地址进行锁定,保证FLASH的起始地址为0x00000000,因为FLASH是系统重启后的起始位置,这样做的好处就是
有利于我们操作,系统重启后从0x00000000开始也是我们的思维习惯。点击系统所示的红圈处
点击后,奇
迹又出现了,看看下图吧
开着的锁合
上了,ALTERA还真逗啊。
最后一步设置就是对地址自动分配,这样做是为了不浪费空间,如果有特殊要求的,也可以手动设置,点击相应的地址,就可以手动修改了,大家自己试试吧。地址自动分配操作如下,点击下图所示红圈处,SYSTEM->Auto-assign
Basic Addresses
点击后,大
家可以发现,各个模块的地址都有相应的变化,但CFI_FLASH基地址还是0x00000000,锁定的还算成功啊,呵呵!接下来是中断的自动分配,和地址自动分配一样,SYSTEM-> Auto-assign
IRQs,如果有什么特殊需要,可以手动更改中断的优先级。之所以要自动分配一下,是因为这个软件还不够智能,当模块建好以后,有出现重复中断号的现象,
编译的过程就会出现问题,自动分配了以后就不从上自下按顺序排列了。你还会发现,图片上出现的红叉也都消失了。展现我们的劳动成果最后的样子,红叉没有了吧,这时候我们就可以编译了。
点击
Next,出现下图,如果需要仿真的,点击红圈处,将其选中,我一般不进行仿真,此处就不选了。
点击
Generate,我们就开始编译了。想休息的赶紧休息一下,想去WC也快去吧,时间还充裕,呵呵……经过漫长的等待以后,我们的程序终于编译好,请看下
图红圈处,看到successful了么?只要看到他就没问题了,软核已经好编译好了,点击Exit,回到了Quartus界面。
分配管脚
回到Quartus界面以后,在Block1.bdf界面里在空白处双击左键,会出现下图,看多了下图红圈处的地方了么,这个就是我们做好的NIOS
II软核了,用鼠标双击它(或者选中以后点击OK)后,将其放到Block.bdf的空白处。
放好以后,
如下图所示
在NIOS
软核KERNEL上点击右键后点击Generate Pins for symbol
ports,这一步作用就是生成管脚,通过命名以后分配真是的引脚。
点击后,如
下图所示,
建立锁相环PLL模块
接下来的工作我们还需要建立一个锁相环,对时钟进行倍频,我们板子上是20MHz的有源晶振,我们要将其倍频到100MHz满足我们之前设定的NOIS软
核的时钟,还需要为SDRAM提供100MHz的时钟。下面我们就开始锁相环PLL模块。在Block.bdf的空白处双击鼠标,点击下图所示的红圈处
出现下图后
点击Next
出现下图所
示,用鼠标选中红圈1所在的位置ALTPLL,在红圈2所在的位置处,加上PLL,目的是将我们所要建立的锁相环命名为PLL。
一切弄好以
后,点击Next,如下图所示,将红圈处设置为20,即我们输入的晶振为20MHz
设置好以
后,点击Next,将下图中选中的地方去掉后,
连续点击
Next,等出现下图以后,将红圈处设置为5,这是c0输出的频率就设置为了100MHz,可以从Actual settings处看出来。
设置好以后点击Next,出现下图,将红圈1处选中,红圈2处修改为5,红圈3处修改为-75。这部分是给SDRAM提供时钟,我们利用锁相环PLL的
c1来提供,时钟频率设置为100MHz,偏移量为-75deg(这个地方影响到SDRAM是否正常运行,我们将在以后具体讲述他的设置,在这里不详述
了)。
设置好以后,连续点击Next,中间没有需要修改的,最后点击Finish,完成PLL的建立,然后点击OK,回到Quartus界面,将我们建好的
PLL放到空白处。接下来的工作就是将PLL连接到NIOS软核上。如下
图所示(连线方式很简单,大家自己研究一下,我就不具体说了)
接下来我们要给SDRAM的时钟分配引脚,这个就由c1完成了。首先是加一个output引脚,方法很简单,在空白处双击,出现下图,在Name处输入
output,一个回车,OK了。假如你要加入input,即输入引脚,你就在Name处输入input,还有双向引脚的是bidir,常用的就这几个,
大家记住了就可以了,很简单。
将其放到下
图红框所示位置,连好线就OK了
调整FLASH引脚
接下来,我们要对Flash连接做一下处理,因为是8位模式的解法,所以FLASH的21位地址线的最低位要跟F_ALSB相连,如下图所示红圈处,(下
图为FLASH的原理图,8位模式的时候,BYTE#引脚下拉,16位模式的时候则需要上拉,请大家注意)
那么,我们
就来修改一下,修改结果如下图所示
这里,需要注意红圈处的改动,将软核输出命名为F_A_T[20..0],F_A[19..0]连接的是F_A_T[20..1],F_ALSB连接的是
F_A_T[0],不知大家是否能看的懂。这个地方命名方式是这样的,点击想命名的线,当被选中后,直接输入你想输入的网络标号即可,大家自己试试吧,很
简单。大家要注意一下,网络标号的颜色跟线的颜色是一样的,而文字的颜色是绿色的,大家要区分开,如下图所示
TCL脚本文件
接下来就要开始分配引脚了,分配引脚有两种方式,在这里我只讲一种,也是我觉得比较好的一种,就是通过TCL脚本文件,这个文件有固定的写法,只要根据你
的引脚情况修改一下就可以了,如下图示所示
我们要命的
名字就是后面那部分,PIN_*是FPGA硬件上的引脚,S_DB就是对应的名字。下面我们举例说明一下吧我现在修改时钟管脚,如下图所示,将其命名为CLOCK,咱们再来看对应的TCL脚本文件
我们可以看
到,CLOCK是对应PIN_23,我们再来看原理图
我们可以看
到,下面的原理图中20MHz晶振X1接的就是FPGA的23脚
这回大家知
道是怎么回事了吧,我将会提供给大家写好的TCL文件,到时候大家根据我给大家的TCL文件,将相应的管脚命名即可。下面我就将其他的管脚重命名,这是个
繁琐的工作,很无趣。修改好以后,我们来看如何执行TCL脚本文件,按下
图所示操作
点击以后,
出现下图,我们选择第一个(前提是你将kingst.tcl放到工程文件下)
选中后点击
Run,如下所示,又看见Successfully了吧,这说明我们脚本文件运行成功了。
配置工程
先保存一下吧,保存好以后,接下来,我们要对工程配置一下了,在左侧边框栏右键点击SOPC_T,如下图所示
点击
setting后,如下图所示,点击红圈处Device and Pin Options
点击后,如
下图所示
点击后如下
图所示,点击红圈处Configuration
点击后,如
下图所示,将红圈处改为EPCS1,然后点击Dual-Purpose Pins
点击后,如
下图所示,将红圈处改为Use as regular I/O
都修改好以
后,点击确定,点击OK。接下来就开始了又一个漫长的编译过程了,大家又可以休息一会了。点击下图所示红圈处的按钮,开始编译。
经过了漫长
的编译过程,如果没有问题,编译成功后将出现下面的对话框
点击确定,编译过程全部结束。我们可以通过编译报告来看看我们用了多少资源,如下图所示,红圈处可以看出,我们这个系统,用了66%的TLE。
最后,我们需要检查一下,是否每个引脚都已经分配好了,以免有个别没有分配的,影响后面的实验。回到SOPC_T.bdf,看一看是不是每个引脚都有个小
“尾巴”,如下图所示
有的,说明
这个引脚已经分配好了,仔细检查好每一个引脚,如果没有问题,就可以把程序下载到硬件里面了。
下载程序
编译好以后的程序会生成两种格式的文件*.sof和*.pof,*.sof文件是给通过JTAG模式下载到FPGA内部的,掉电丢失。*.pof文件是通
过AS模式下载到EPCS1中的,掉电不会丢失。说到这,简单介绍一下AS模式和JTAG模式。Cyclone系列的FPGA使用SRAM单元来存储配置数据。SRAM是易失性的,每次上电之前,
配置数据必须重新下载到FPGA中。Cyclone FPGA的配置方式包括:主动配置模式(AS),被动配置模式(PS)以及JTAG配置模式。我的黑金板上配置了AS和JTAG两种模式,我们通过AS口可以将程序下载到EPCS1中,如下图
操作方式,点击红圈处的图标
点击后,进入下图所示界面,红圈1是模式选择,红圈2是下载器的选择,红圈3加入相应的文件。如果你通过JTAG口下载程序,就要将下载线接到JTAG
口,选择JTAG模式,将*.sof文件加入到这里面。如果你通过AS口下载程序,你就要将下载线接到AS口,选择AS模式,将*.pof文件加入到这
里,大家自己试试吧。最后点击Start,开始下载。现在市面上的下载线以USB-BLASTER为主,价格各有不同,贵的有200多的,便宜的50多块
钱,我觉得可以用就行,贵的未必好,自己的看法啊,大家自行选择吧。
到这里,我们有关硬件开发的部分就高一段落了,如果对以上内容有问题,请大家留言,我将尽快给大家解决。
下一节,我将给你讲解NIOS II软件开发部分内容,敬请关注……
NIOS II开发的整个流程---软件开发(二)
这一节,我将给大家讲解NIOS
II软件开发部分,这一部分是以第一节硬件开发部分为基础的,如果大家是从这一节开始看的,那我们就先回顾一下上一节我们所讲的内容。
回顾
上一节,我们详细讲解了NIOS
II硬件开发部分的全过程,其中包括了工程的建立,NIOS II软核的构建,以及锁相环PLL倍频和如何下载等等。其中NIOS
II软核的构建是重点内容,我们在软核中构建了CPU,SDRAM,AVALON三态桥,FLASH,SYSTEM ID,JTAG UART
6个模块,时钟我们设计为100MHz,FLASH为8位模式。这一节我们就在其基础上详细讲解软件开发的全过程。
摘要
首先,我先给大家简单介绍一下这一节的重要内容,这一节将详细讲述NIOS II的软件开发的整个流程,并简单介绍NIOS II 9.0
IDE的一些简单的使用方法,会让那些从来没有用过NIOS II IDE软件的人可以很轻松的上手使用。
NIOS II IDE简介
NIOS II IDE是一个基于Eclipse
IDE构架的集成开发环境,它包括了很多的功能,学过JAVA的人对其应该非常的熟悉,它的功能是非常之强大,下面我们就简单介绍一下NIOS II
IDE的功能和特点:
- GNU开发工具。熟悉Linux操作系统的人对它一定不陌生,它是一种开源
的编译环境,包括标准的GCC编译器,连接器,汇编器以及makefile工具等。说到这大家可能头都大了,这些都是啥东西啊,如果你是一个Linux的
忠实粉丝,这些你必须非常的清楚,但在NIOS
II开发过程中你只要知道这些就OK了,没必要刨根问底的弄清楚它们,不过我在这里强烈推荐大家学习学习linux方面的东西,你一旦接触上了它,你就会
发现它的魅力是太大了,里面的好东西让你一辈子都受益,呵呵,话说多了,我们继续我们的NIOS II。 - 基于GDB的调试工具,包括仿真和硬件调试。这个东西也是在Linux平台
下流行的调试工具,所以说Linux很强大吧。 集成了一个硬件抽象层HAL(Hardware Abstraction Layer); - 支持MicroChip/OS II 和 LwTCP/IP协议栈。
- 支持Flash下载(Flash Programmer 和
Quartus II Programmer)。
理论的就说这么多了,下面我们来点实际的。
建立软件工程
首先,将NIOS II 9.0 IDE软件打开,打开后NIOS
II
IDE的界面赫然显现在我们面前,界面很简单,跟其他的IDE没什么太大的区别,我们需要做的就是首先建立一个软件工程,操作方式如下图所
示,File->New->Project
点击后,会出现工程向导界面,如下图所示,选中红圈处的内容,Nios II
C/C++ Application,
点击Next,会出现下图所示内容,红圈1处是工程名,我将其修改为
hello_world,红圈2处是目标硬件文件,点击Browse,找到我们上一节生成的NIOS软核的位置,这个文件是以.ptf为后缀的,如果大家
跟我的地址一样的话,地址应该是在E:\nios\KERNEL.ptf。在红圈3处选中Hello
World,这个地方是工程模版。再说说红圈4,这个地方是改变工程所放位置的,如果不修改,软件工程的位置就在Quartus工程目录下的
software下面。
点击Next,这里不用修改,点击Finish,完成工程向导。
完成了上面的工程向导后,我们正式进入NIOS II
IDE的界面了,如下图所示,我主要介绍三个部分,其他部分用处不大。我按功能将这三部分命名为代码区,工程目录区,和观察区(这几个名字很山寨吧,哥追
求的就是通俗易懂,呵呵)
不用我多说,代码区就是显示代码的,工程目录区呢,显示所有与工程有关系的文
件,跟我们有关系有.c和.h文件,还有一个非常非常之重要的system.h文件。观察区中有两个栏我们是会经常用到的,一个是Console,一个是
Problems。第一个是编译信息显示区,一个是错误警告显示区。有了JTAG
UART后,第一个Console(控制台)栏多了一个用途,就是作为标准输出(stdout)的终端,这里不多了,一会我们就会用到。
我们接下来的工作就是需要对工程配置一下,大家跟我来吧。在工程目录区中的hello_world项单击鼠标右键后,点击红圈处的位置system
library Properties
点击后,可看到下图所示界面,
按顺序来,红圈1处是标准输入(stdin)、标准输出(stdout)、标
准错误(stderr)的设置区,我们在软核中构建了JTAG UART,在此出现效果了吧,如果我们没有构建JTAG
UART,那么,这个地方就不会出现jtag_uart选项了。在所红圈2处,这个地方也不需要修改,不过有一个地方需要注意,就是Support
C++,这个库相对Small C library要大,如果大家手中的板子没有FLASH,SDRAM这样大容量存储设备的话,选择Small C
library,用FPGA内部的SRAM,也可以跑些小程序。再说红圈3处,这个是一些有关内存的选项,我们构建了SDRAM模块,这个地方也用到了,
默认就可以,不用修改。该说红圈4了,点击红圈4后,出现下面界面,这个是对编译器就行配置的界面,大家可以自己观察一下,大部分都不需要修改,我们来看
一下比较重要的地方,点击红圈处。
点击后,我们看看是什么样子,这里有两个关键点,一个是红圈1处,这个地方时
配置编译器的优化级别,红圈2的地方是调试级别。编译器的优化级别会让你的生成的代码更小,当要求也很高,你的代码如果不严谨,有可能优化以后不好用了,
大家要注意。调试级别是你在编译过程中显示编译内容多少,级别越高显示内容的越多,建议将调试级别调到最高。
将上面设置好以后,点击Apply,然后点击OK,回到主页面。
接下来我们就要开始编译了,第一次的编译时间比较长,因为编译过程中会生成一个我们之前所说的一个非常非常重要的文件system.h,这个文件是根据我
们构建的NIOS
II软核产生的,也就是说,system.h的内容与软核的模块一一对应。一旦软核发生变化,就需要重新编译,重新产生system.h文件。现在就开始
编译,如下图所示,在工程目录栏中单击右键后,点击红圈处Build Project,或者直接按快捷键Ctrl + b。
开始编译后,会出现下面的界面,进度条走的很慢吧,如果不想看它,点击Run in
Background,编译就在后台进行了。在编译构成中,大家可以观察观察栏中Console栏的内容,其中出现的内容就是编译器正在编译的东西,大家
不妨好好看看编译过程都编译和生成了哪些东西。
编译好以后,大家可以看到下面界面,红圈处说明了,编译完成。
大家可以对比一下,编译前和编译后工程目录栏有哪些变化。
看来编译的成果还是很显著的,其他的我们没必要知道,关键的一个大家要知道,
就是我们反复提到的system.h文件。下面我们就把它揪出来,它隐藏的还是很深的。
看到了吧,它在hello_world_syslib/Debug
/system_description/system.h,现在我们就看看里面有什么东西这么重要。
双击以后,在代码区大家就可以看到了,都是一些宏定义,我们找一个典型的作为
例子,我给大家讲解一下,看下面的截图
在这些信息中,对我们有用的是JTAG_UART_BASE,还有JTAG_UART_IRQ,JTAG_UART_BASE是JTAG_UART的基地
址,JTAG_UART_IRQ是中断号,其他的是一些配置信息,我们先不关注。同理,SDRAM、FLASH等都有相应的基地址,我们以后就要用到这些
地址对NIOS软核进行寄存器操作,达到我们要实现的跟单片机一样的寄存器操作方式,在此我们就不详细讲述了,后面我们会单独讲解这一节。看了这个例子以
后,大家在看看其他的,都大同小异,没有中断的就不会出现*_IRQ这一项,不信大家自己看。NIOS强大之处就在于此,根据大家的需求进行对软核的构
建,然后产生相应的寄存器,整个构建都由设计者来掌控,缺什么建什么,不行的话还可以自己写底层的模块,你说强不强大。
system.h文件我们以后还会提到,暂时先讲到这,接下来我们要看看我们
编译好的程序是不是跟我们想想的一样。对于NIOS
IDE提供了几种方法来验证,一种是直接硬件在线仿真,一种是软件仿真。我们先说第一种硬件在线仿真,很显然这种方式需要硬件配合,一块开发板,一个仿真
器(仿真器就是大家用的USB-BLASTER或者BYTE-BLASTER)。将仿真器与开发板的JTAG口相连(假设你的仿真器驱动已经装好了,如果
有不知道怎么装仿真器驱动的请跟我联系)。安装好以后,我们进行下面的操作,点击红圈处Nios II Hardware。
点击后,可以看观察栏的控制台(Console),如果一切正常,我们将看到
下面的结果出现。
看到了么?如果没看到,再好好检查一下,你的操作是否跟我说的一样,如果自己
无法解决,请联系我,我将手把手帮你解决。
说完第一种硬件在线仿真以后,我们再说说软件仿真。软件仿真不需要硬件,电脑
单独运行即可,按下图所示操作,点击红圈处,Nios II Insruction Set Simulator。
点击后,还是看观察栏的控制台(Console),结果一样吧。我不建议大家
用软件仿真,因为软件仿真在不涉及到硬件的情况下还好,如果有相关硬件操作了,效果就没有了。
到此为止,我们的NIOS
II软件开发部分就结束了,如果想熟练掌握NIOS II
IDE,还要大家自己亲手去试试,光靠我的讲解是不行的,大家没事的时候可以研究一下它的每一个选项,都有什么功能,这样才能加深你对它的熟悉程度,更好
的去掌握它,用好它。在此再多说一句,我不建议大家经常更新软件,对于Quartus和NIOS软件的升级,无非就是解决一些BUG,多支持一些器件,编
译的速度快了一点。在你还没有遇到非常严重的BUG之前,最好不要去更新它。更新以后带来的后果就是需要重新熟悉它的特性。NIOS II IDE
9.1和9.0之间变化就很大,使用起来就不是那么顺手。建议大家不要轻易去更改。
好了,这一节的内容就到此结束吧,下一节我将给大家讲解“如何让NIOS II的开发像单片机一样简单,看透NIOS
II系统的寄存器操作方式”。这一节内容是我教程中的核心部分,希望大家耐心等待,拜拜了!
编程风格(三)一、 规范参照标准
良好的代码风格及编程规范,是书写优良代码的基础,也是工程师必备的技能。本规范遵循C语言的创始人B.W.Kernighan和
D.M.Ritchit (简称 K & R)所著的《The C Programming Language》一书的示例,并参照 Linux
内核代码风格。
本规范适用于有一定c语言基础的读者,对于需要入门的,建议熟读几遍《The C Programming
Language》。另外本规范部分内容仅适用于基于单片机、arm等嵌入式处理器的固件开发。
二、 格式
1. 缩进
函数体、if、for、while、switch case、do
while等都需要使用缩进。不管你用任何编辑器或者是集成开发环境,缩进均是基于“Tab”键的,而不是基于“空格”键。一般来说,我采用 8
个字符的缩进长度。例如:
2. 空格及空行
空格和空行的出现,是以增强程序的可读性为目的。但是不要插入过多(两个以上)的空格和空行。函数开始局部变量声明后需加一个空行,函数内逻辑相对
独立的部分,需加一个空行。文件结尾需加一个空行。
代码中加入空格是以程序逻辑清晰为目的。c关键字后需加空格,例如:
3. 大括号
函数的大括号位于函数体的第二行与末行,if、while、switch、do的大括号位于关键字所在行的行尾和逻辑末行,末行的大括号与关键字上
下对齐。例如:
三、 元素及命名规则
1. 文件
C语言源文件主要包括 .c文件和 .h 文件。文件命名要以体现其意义的名词为主,例如芯片 max525的驱动程序,我们可以命名为
max525.c;实现fat32协议的驱动,我们可以命名为 fat32.c;忌出现
a.c、newfile.c、my.c、wang.c等无意义文件名。
文件名用小写字母、下划线、数字的组合命名,不可出现空格等其他字符,更不允许出现汉字、日语、俄语等非 ascii码的字符。
每个 .c文件都要对应有一个 .h 文件来配合其对外资源声明。 .h 文件内可包含宏定义、类型定义、对外资源(全局变量、全局函数)声明。
.c 文件可以包含变量声明、函数原型、函数体。为了防止重复调用,.h文件的逻辑开头需要加入开关控制,例如:
2. 宏、枚举体
宏、枚举体均需用大写字母、数字及下划线的组合,宏与常量之间用“tab”隔离,同一类含义的宏定义在一起,并放于相关的头文件中。宏定义以能表达
清楚含义为标准,除专业术语外,推荐用完整单词表示宏含义。不同含义的宏定义需用空行分割,部分需加注释。例如:
3. 自定义类型
我们可以用c关键字 typedef 进行自定义c语言中的数据类型。类型定义一般包括结构体、联合体类型定义及函数类型定义。ANSI
C包含的数据类型(如unsigned char、unsigned short int、double 等),不建议重定义。
结构体、联合体类型定义推荐大写字母加 _T 的形式出现,例如:
4. 函数声明及实体
函数命名采用谓宾结构,中间用下划线隔开,函数必须使用小写、数字及下划线的组合。不管函数原型声明还是函数体,必须包含完整的函数类型及参数类型
(包括 void型亦不能省略)。文件的内部函数(不需要外部调用),需要在函数类型前加static 关键字。
函数原型声明时,需用注释的方式,添加函数调用参数的意义,例如:
大部分函数需返回函数执行状态,定义 0 为正常执行, -1为一般错误,-1 ~
-999为自定义错误。自定义错误可以通过宏或者变量实现。例如:
从逻辑功能划分的角度来讲,函数需要简、短、精,函数实现的逻辑内容要跟函数名要一一对应,不要超过函数名表达的范围,也不要只实现函数名所表达的
部分功能。一般情况,尽量调用系统库来实现功能而不是自己去实现。
5. 变量及初始化
变量一律用小写字母、数字及下划线实现,全局变量要体现变量的意义,需用单词的全写;
由于局部变量作用的范围,一般都在视野范围你,所以可以用简写及单个字母,如 i、a等。
全局变量尽量越少越好,并且需根据属性划分,以结构体形式体现为主。整个工程的全局变量需在头文件中用 extern
关键字对外声明,隶属于文件的全局变量(不属于整个工程的全局变量),需加 static 关键字修饰。
变量使用前必须初始化,初始化可以采用静态初始化和函数执行时初始化。结构体的初始化建议采用 c99 规范里的指定初始化。例如:
数组维数最好用宏定义,数组用时必须初始化(赋值或者清零),对数组维数判断时,需用sizeof 关键字,切忌直接用数字。
使用指针时,切忌指针越界及野指针的出现;指针也是个变量,用它之前也必须初始化。对CPU外设进行直接映射或者是中断内变量使用时,变量前需加
volatile 以防系统优化。
6. 注释
注释不宜过少,但也不宜过多。以表达清楚程序员的意图为最终目的,注释尽量不要用中文。函数体内注释,推荐采用 “//” 的注释方式。
每个文件头,均需加一个说明性的注释。例如:
每个函数体的开始,均需加一个说明性的注释。例如:
四、 项目管理
1. 项目文件夹
每个C工程中,可以以功能为依据对源文件进行文件夹分类。文件夹不可以出现空格、除英文字母、数字、下划线外的字符;更不允许出现汉字、俄语等非
ASCII码字符。例如某个工程可以划分为如下文件夹:
2. 功能划分
功能划分应以逻辑清晰、层次关系明显为目的。一旦划分好后,不可越级调用系统资源(例如只有driver内文件内直接操作硬件资源,其他文件夹代码
均不可调用最底层硬件资源)。也不要互相调用而使系统资源很快耗尽。
3. 文件管理
文件一旦建立,就需在文件头说明文件目的、版权及历史记录,每次修改后必须记录。工程完工或者是间歇性搁置时,需要对所有工程文件夹、文件进行只读
属性设置及当前状态记录(进行到什么状态、存在什么bug等)。对源代码最好做到每天一备份,以防意外篡改及丢失,以备恢复之用。
五、 一些建议
1. 代码编辑器
有很多优秀的代码编辑器可供大家选择,例如
Vim、Emacs、Souce-Insight、Edit-Plus等。每个人可以根据自己的喜好,选择一种编辑器,切忌滥用。
2. PC 端编译器及集成开发环境
GCC (GNU Compiler Collection)
是一个非常优秀的编译器套装。他几乎在所有的操作系统下均有移植,并且有很多CPU的交叉编译器可供我们使用,例如
SDCC、arm-elf-gcc等。MS-Windows下推荐使用 Mingw32移植版,相应的集成开发环境推荐 Dev-cpp 和
Code::block。
3. 参考资源及网站
《The C Programming Language》 c语言圣经;
《Advanced Programming in the UNIX Environment》 UNIX C 程序员圣经;
http://www.gnu.org GNU Operating
System
http://www.sf.net Source Forge
http://www.kernel.net The Linux
Kernel Archives
六、 示例代码
1. c文件
2. 头文件
LED 实验(四)这一节,我将给大家讲解第一个与硬件有关的程序,虽然内容简单,却极具代表性。我将采用一种寄存器的操作方式,让大家感受到开发NIOS跟单片机一
样的简单,看透NIOS II开发的本质,尽量避免使用NIOS II
IDE提供的API,这样做有很多好处。首先,有单片机开发经验的人很熟悉这种操作方式,其次,可以了解到NIOS的本质,真正的去开发它,而不是仅仅用
它的API来写一些应用程序。
做过单片机试验的人一定对LED实验记忆犹新,因为它是硬件试验部分的第一课,通过这个简单的实验,可以让你对单片机的操作有一个感官上的了解,可以说意
义不同寻常。这一节,我也通过LED实验来带大家进入NIOS II的开发世界,感受NIOS的魅力所在,下面我们开始吧。
构建PIO模块
第一步,我们要在软核中加入PIO模块。打开我们第一节建的Quartus工程,然后双击KERNEL,如下图红圈所示
点击后进入了SOPC BUILDER界面,如下图所示
点击下图所示红圈处PIO(Parallel I/O)
点击后,如下图所示,红圈1处是你需要的PIO口的宽度,即你需要几个IO口,这里面我设置为4,即我要控制4个LED,红圈2是选择输出方式,我
选择为输出(Output)。
接下来,点击Finish,完成PIO模块的构建,然后将其改名为PIO_LED,如下图所示
接下来,需要自动分配一下地址,第一节我们已经讲过,如下图所示
接下来,我们就要开始编译了,点击Generate,需要保存一下,点击save,开始编译。
经过一段耐心的等待,编译成功,如下图所示
完成了上面的工作,点击Exit,会出现下面的界面,询问是否需要对KERNEL进行更新,点击“是(Y)”。
然后,会出现下面界面,点击OK
点击后,会出现下面情况,连线错位,这就需要我们手工来整理一下,将相应的管脚相连接。
整理还以后,如下图所示,可以看出,还有PIO_LED没有管脚相连,我们来建立一个输出管脚,并重命名
最后的样子大家可以看到,如下图所示,我们将将其命名为LED[3..0],这是quartus中总线的命名方式,大家要注意。
一切准备好了,我们需要给他进行引脚分配,按下图所示
点击Tcl scripts,如下图所示,选中kingst.tcl,点击Run,完成管脚分配。
下面,我们开始编译了,又是一次漫长的等待后,看到了下图红圈所示,说明编译成功。
我们再来看看我们占用了多少资源,如下图所示,还是66%,说明一个PIO模块占用资源很少的。
到此为止,我们在软核中添加PIO模块已经完成,接下来,我们进行的就是在NIOS II IDE中开发软件部分。
软件编程
首先打开NIOS II 9.0
IDE,打开后,第一件事就是重新编译整个工程,按快捷键Ctrl+b。又一次漫长的等待之后,我们打开system.h文件,大家能看到有什么变化么,
可能有人发现了,system.h文件对比之前的多了下面内容,这部分内容就是我们在软核中新构建的PIO_LED模块,我们将要用到的是
PIO_LED_BASE,它是PIO_LED所有寄存器的首地址。
下面,我们就来运用这个首地址来进行编程,完成对4个LED的控制。
第一步,我需要建立一个放置头文件的文件夹,我将其命名为inc,方法如下图所示,鼠标在hello_world上点击右键,然后点击New中的
Source Folder,这样就建立了一个文件夹,
然后会出现下图,在红圈处输入inc,点击Finish,完成文件夹建立。
大家可以看出,在左边工程目录栏中多出了一个inc文件夹,如下图所示
接下来,我要在inc中建立一个头文件,方法如下图所示,在inc上点击右键,然后点击New中的Header File,红圈处所示
点击以后,出现下图,在红圈处输入sopc.h,点击Finish,完成头文件的构建。
完成上面的工作以后,代码栏处,可以看到下图,现在我们就对其进行编辑
修改后的代码如下图所示,
大家可能看到上面的代码不明白什么意思,这很正常,第一次接触NIOS的人不是很了解这个东西,我现在就给大家详细的讲解一下。
首先我们看红圈2处的结构体,在这里,我们定义了一个结构体,将其命名为PIO_STR,这个结构体的结构是有说法的,它是根据芯片手册来写的。我
现在给大家截个图,这个图来自《n2cpu_Embedded Peripherals.pdf》,版本是下图所示
其中9-5页和9-6也中有这么一个表格,如下图所示
这个表格就是PIO
Core寄存器映射,我的结构体就是根据它来写的,名字不重要,重要的是它的顺序,也就是偏移量(offset)。第一项是数据data,第二项是IO口
方向,第三项是中断控制位,第四项是边沿控制位。他们每一项都有n位,这个n就是我们在软核构建中的宽度(width),如下图所示的红框1,我们之前设
置为4,那么n等于4。
接下来,我们来讲红框3中的代码,这部分代码中你一定记得PIO_LED_BASE,对,这就是我之前在system.h中特意强调
的,PIO_LED的基地址。那我红圈3中的代码意图就很明显了,定义了一个宏,命名为LED,它是指向PIO_LED_BASE的结构体指针,这个结构
体就是PIO_STR(如果大家对结构体指针不理解,那你就得回去看C语言书了,我在这就不具体说了)。红圈1的代码也很简单,是为了控制红圈3的定义
的,这样做是为了增强代码的严谨性和可控制性。当你没有定义PIO_LED_BASE时,你就可以将红圈1中的宏定义#define
_LED去掉,不多说了,再多说就挨砖块了,呵呵。
大家理解了sopc.h代码以后,我们就可以进行C代码编程了。忘了说了,在NIOS II
IDE中一定要记得修改以后保存,它很幼稚的,编译前不会提醒你去保存的,如果你忘记了保存,相当于你没有修改。好了,下面我们进行C代码编程了。
为了规范化程序,我需要对程序做一些调整,以便以后的讲解。首先建立两个文件夹,分别命名为driver,main。建好以后,如下图所示,
然后将hello_world.c名字改为main.c,并将它放到main文件夹中。改好以后,如下图所示
接下来,我们来修改main.c中的程序,我先介绍一下我这段代码的目的,这段代码就是控制我的黑金开发板的4个LED,让他们按顺序闪烁,就是大
家习惯说的流水灯。下图就是我们要控制的4个LED,当FPGA将相应的管脚置1时,LED就会亮,当置0,LED就不会亮了。
修改好的程序如下图所示,首先说红框1,有些人可能没用过这种方式,这是一种相对路径的方式调用头文件,好处就是可移植性好,不管工程放到其他什么
地方,这个头文件和c文件的相对位置都不会变,也就不需要对这个地方进行修改。再说红框2处的代码,关键是LED->DATA =
1<<i;LED大家应该记得,LED是我们在sopc.h中定义的宏,是结构体指针,那么LED->DATA是什么意思大家就应该很
清楚了,是它的结构体中DATA的内容(不是地址)。LED->DATA =
1<<i;就是对LED->DATA的四个位循环置1。所以,4个LED就会按顺序闪烁了。usleep是微妙级延时,我们在这里每次
延时0.5ms。
讲到这里,第一个程序就讲完了,下面需要做的就是编译,下载程序到FPGA,验证是否正确,我就不详细说了。
接下来,我们总结一下这节内容。我们来对比一下寄存器操作方式与API之间有什么联系和不同,上面的程序,如果用NIOS II
IDE提供的API来写,那么如下图所示
IOWR_ALTERA_AVALON_PIO是一个宏,在altera_avalon_pio_regs.h中,其定义如下
(大家可以按住ctrl键后,用鼠标点击进入定义所在的位置),大家可以看到,它是一个IOWR的宏,而IOWR的具体写法我就在此不详细说了(大
家感兴趣的可以去NIOS的源码),反正就是对硬件地址的控制。我的做法就是绕过这个大圈子,直接去控制它的寄存器。
大家可能可能有点纳闷,我们的机构体中定义了四个变量,但只用了DATA一个,在这说明一下原因,首先是DIRECTION。这个是IO的方向,就是说是
输入还是输出,或者是双向的。因为在我们构建PIO模块的构成中有了一个选项,如下图所示,红圈2,我们选择了输出(Output ports
only),也就是我们在底层就已经固定了它的方向,所以在软件中就不需要在定义了。还有两个变量是涉及到中断时才会用到,所以在这个程序中也没有用到。
这一节是以后内容的基础,希望大家好好的理解,尤其是sopc.h中定义的那个结构体,大家一定要理解清楚。以后,还会涉及到UART,SPI等,都需要
建立结构体,这个都是触类旁通,举一反三的东西,弄清楚一个,其他的都好理解了。
我要提醒大家一句,我虽然提倡大家用操作寄存器方式编程,但并不希望所有的程序都按这种方式来写,比如说对flash的操作,我们就可以使用API来写,
因为flash的操作相对复杂,而利用API可以很简单的几个语句就能完成,没必要自己来写。
好了,今天我们就讲到这里,下一节我们将讲一下NIOS如何在线调试,以及调试中的一些小技巧,希望大家耐心等待……
外部中断实验(五)
简介
这一节,我们通过来讲解一下NIOS II的硬件中断的内容,同时借助这节内容我们也要介绍NIOS II
IDE在线调试的方法和技巧。首先来点理论知识,介绍一下与硬件中断相关的内容,让大家对NIOS II 的硬件中断有一个概括性的了解。
ISR(Interrupt Service Routine)中断服务函数是为硬件中断服务的子程序。NIOS
II处理器支持32个硬件中断,每一个使能了的硬件中断都应该有一个ISR与之对应。中断发生时,硬件中断处理器会根据检测到的有效中断级别,调用相应的
ISR为其进行中断服务。
要完成硬件中断工作,我们需要做两件事:
第一, 注册中断函数ISR,它的函数原型如下所示:
int alt_irq_register(alt_u32 id, void* context, void(*handler)
(void*,alt_u32));
id:中断优先级,即所注册的ISR是为哪个中断优先级的中断服务的。中断优先级在SOPC
Builder中分配的,在第一节中我们提起过,不知道大家是否记得,我们来回忆一下,如下图所示,通过这一步我们来完成中断的自动分配
分配好的IRQ如下图所示,当然,IRQ可根据自己的特殊要求进行手工修改,只要不重复就没问题。
而在NIOS II
IDE软件中体现在system.h文件中,之前我们也提到过,如下图所示,看到了吧,JTAG_UART_IRQ为0,与上图的中断号正好相同,这可不
是巧合。我已经说过了,system.h文件是根据软核来生成的,所以JTAG_UART_IRQ等于0也是可想而知的了。
接下来我们继续说剩下两个参数,
context,为所注册的ISR传递参数,可以是NULL;
handler,中断服务函数ISR的指针。
再来说返回值,返回值是0时,表示中断注册成功;返回为负数,表明中断注册失败。
这里面有一个需要注册的地方,如果handler不是NULL,则该优先级中断在注册成功后将自动使能,也即是说,只要我们在handler处有相应的
ISR,我们就不需要再进行使能处理了。说完第一,我们来说说第二。
第二,
编写ISR函数,这个函数有我们自己来写,而不是HAL系统提供的。它跟一般的函数定义没什么区别,只是对ISR的函数原型有特定的要求:
void ISR_handler(void* context, alt_u32 id);
context: 传给ISR的形参,可以是NULL;
id: 中断优先级。
OK,只要这两步我们就可以完成中断函数的处理了。废话少说,我们来点实际的吧,跟我来。
硬件开发
下面,我们就利用一个外部按键来验证一下中断函数的处理过程。
首先,我们要构造一个给外部按键用的PIO模块。这部分内容之前已经详细的讲过了,在此简述略过吧。
打开Quartus II软件,然后双击KERNEL,进入SOPC
BUILDER。进入后,我们建立一个PIO模块,在建立过程中有一个地方有所不同,我们来看一下,如下图所示,红圈1处,我们输入1,因为我们只需要一
个按键(我们的黑金开发板中一共有5个按键);红圈2处选择Input ports only,也就是作为输入。完成后,点击Next,
点击后,如下图所示,我们将红圈2(General
IRQ)处选中,其他不变。在这多说两句,这里的中断分为两种,一种是电平(Level)中断,也就是高电平/低电平中断,还有一种就是沿中断,包括上升
沿、下降沿。做过单片机的人应该都很熟悉,如果你想要实现沿中断,需要把红圈1(Synchronously
capture)处选中,下面包括3种方式,大家可以根据自己的要求选择。完成上面工作以后,点击Finish,完成PIO构建。
下面需要对名字需要进行修改,我将其命名为KEY,如下图红圈1处所示。大家再看一下红圈2处,大家可以发现中断号有两个0,一个是jtag_uart
的,一个是我们新建立的KEY的,这回大家明白了,为什么我们需要自动分配中断号了吧,出现了相同的中断优先级了。
接下来,我们就对中断自动分配一下。如下图所示,大家可以看到红圈2处发生变化了吧,KEY的中断级别变化了,变成了1。自动中断分配是自上由下按
顺序分配的。
完成上面的构建以后开始编译了,又需要漫长的等待了……(其实也不是很长,也就一两分钟吧,根据电脑配置而定)
编译好了以后,回到Quartus界面,需要进行整理,添加KEY的输入管脚,我这里就简要略过了。看一下整好要以后的样子,如下图所示,我我将其命名为
KEY[4],这个对应的是我的黑金开发板的中间的那个按键。下图中有一个地方需要注意,由于电平中断时,NIOS只对高电平敏感,所以如果想实现低电平
敏感需要加一个非门,
非门的加入方式跟加入input管脚是一样的,双击空白处出现下图,然后在红圈处输入not,点击OK即可。
然后,分配引脚,编译,又是等待……
编译好以后,咱们再来看看用了多少资源,如下图所示,还是66%,
咱们进行对以一下,在上一节我们占用资源如下图所示
对比以后发现,PIO模块占用资源是相当的少。
编译好以后,大家可以选择是通过AS模式或JTAG模式下载都可以,不过记得JTAG模式掉电会丢失,记得上电以后重新烧写。
硬件部分就讲完了,下面我们来讲软件编程部分的内容。
软件开发
打开NIOS II 9.0
IDE软件,打开后,先编译一次,快捷键为CTRL+b(本人比较喜欢快捷键,方便快捷么,呵呵)。编译好以后,我们来看看system.h有什么变化。
大家可以看到,system.h中多出来了下面内容,这部分内容就是有关KEY的,其中红圈1处是基地址,红圈2处室中断号,这些都是我们在下面要用到
的,大家先有个印象。
接下来,我们就要开始写程序了,首先需要在sopc.h中添加内容,如下图所示,跟上一节的LED是一样的,在此就不做解释了。
修改好以后,我们对main.c函数进行更改,整体程序如下图所示,这个程序没有对按键进行防抖处理,只是为了展示外部中断处理的操作过程,如想作
为项目中应用必须加入按键防抖处理,在此不具体说明。这个函数通过外部按键来产生中断,因为我们设置的是低电平产生中断,所以当按键按下时,就会产生了一
个低电平,这时就会进入中断函数。在中断函数中,我们对key_flag进行取反。而在主函数中,我们不断地进行查询,当key_flag为1
时,LED->DATA置1,也就是让外部发光二极管亮;当key_flag为0时,LED->DATA置0,这时,发光二极管不亮。
接下来我们具体讲解一下每一个函数。
首先编译一个初始化程序,如下图所示,一共有两条语句,我们先说红圈1处的语句,这个语句的目的是,使能中断位,上一节我已经讲过了,PIO模块对应的结
构体中的INTERRUPT_MASK是中断控制寄存器的内存映射,当该位置1时,允许中断,否则,禁止中断。再说红圈2处的语句,我们前面已经见过这个
语句了,用它来完成中断的注册,KEY_IRQ来自system.h,ISR_key是ISR函数。用return返回为了在主函数中判断注册是否成功,
如果成功返回0,非0表示注册失败。
下面来看看ISR_key函数,如下图所示,只有一条语句,其中,key_flag是一个全局变量,这条语句的意思,没进一次中断,就将key_flag
取反。
下面是主函数,红圈1处部分是判断初始化是否成功,如果init_key()函数返回值是0,说明注册成功,打印register
successfully!\n,可以再观察栏中看到它,否则打印Error: register
failure!\n。红圈2处是判断标志位key_flag,如果它为1,则对LED->DATA
置1,也就是让让对应的LED亮,否则LED不亮。
这个程序都很简单,有利于我们来了解中断的处理过程。
现在借着讲中断的机会,给大家介绍一下NIOS II
IDE中如何进行在线调试(DEBUG)。如下图示,在左边框的hello_world点击右键,选择Debug As->Nios II
Hardware,或者快捷键F11(前提:已将仿真器与JTAG口相连,并且电源通电),我们就进入了DEBUG界面
我们来简单介绍一下DEBUG界面下的几个功能栏,代码区不用说了,显示我们的源代码,观察区包括主要用到两个,一个是控制台console,另一个是
Memory。Memory在正常模式下是不能使用的,也就是说只有在Debug模式下,我们才能通过Memory来观察内存中数值的改变。变量区主要是
用来观察变量值,它的存在有助于我们判断数值的变化是否与我们预想的一样。还有一部分使我们在调试过程中经常用的功能键。包括开始,停止,还有就是三种模
式的单步调试功能键。其他区域也有一定的功能,大家可以自己研究一下,对大家调试程序有一定的益处。
简单介绍了一些功能以后,我们来调试一下我们的中断程序。对于调试中断程序,如何判断中断是否进入,我们有一个小的技巧,简单而有效。
首先,我们在中断函数处设定一个断点,设置断点的方法是在你需要设置断点的地方,即红圈处,双击右键即可。
记下来,让程序全速跑起来,按红圈处的按钮。程序跑起来以后,按开发板上的的外部按键(中间的那个按键),如果中断没有问题,程序就会停在我们设置的中断
处,这说明,中断进入成功。这个方法即使用有简单,对调试中断这类程序很有好处。
对于如何运用单步调试之类的功能,只要玩过单片机的,都应该会的,我就不多说了。
总结
最后,再简单总结一下中断需要注意的地方,大家知道中断程序主要处理一些实时性比较强的操作,因此不能在中断程序中进行等待或者阻塞性的操作。所以,尽量
保持中断服务程序精简,不要在中断程序中处理复杂的处理,比如浮点型操作,printf函数等。
这一节就到此为止,由于匆忙,内容可能会出现有问题的地方,希望大家指出,我将进行修改。
串口实验(六)
简介
这一节,我们来说说RS232,俗称串口。大家对这东西应该很了解,没什么可说的。相对前面我们讲的内容,这一节比较复杂,我会尽力把它讲清楚。在
这一节中,我不仅要给大家讲解如何去实现RS232功能,更重要的是要提出一种编程思想,如何让程序编写的更严谨,更专业,更有利于以后的维护和移植。
硬件开发
首先,我们要在NIOS II 软核中构建RS232模块。打开Quartus软件,双击进入SOPC BUILDER,然后点击下图所示红圈处,
点击后,如下图所示,红圈1处为波特率,我们设置为115200;红圈2处是是否允许通过软件改变波特率,我们选中,便是允许,这样我们就可以通过
软件来随时更改波特率,如果软件不设置,默认值就是上面设置的115200;红框3中是设置一些与串口有关的参数,校验方式,数据位,停止位,后面那个基
本不用,大家根据实际情况来修改。设置好以后,点击Next,Finish,完成构建。
构建好以后,将其更名为RS232,然后进行自动分配地址,自动分配中断号。一切就绪,点击General,进行编译。
编译好以后,退出,进入Quartus界面,给其分配引脚,如下图所示
然后运行TCL脚本,编译,等待……
编译好以后,大家可以选择自己的方式将程序下载到FPGA中,AS或JTAG都可以。
软件开发
打开NIOS II 9.0 IDE后,按快捷键Ctrl+b编译程序,等待编译……
编译好以后,我们再来看system.h文件。可以看到rs232部分的代码了,如下表所示,红圈处就是我们要用到的部分,大家已经熟悉了,一个是
基地址,一个是中断号
09 | #define RS232_NAME "/dev/RS232" |
11 | #define RS232_TYPE "altera_avalon_uart" |
13 | #define RS232_BASE 0x00201000 |
19 | #define RS232_BAUD 115200 |
21 | #define RS232_DATA_BITS 8 |
23 | #define RS232_FIXED_BAUD 0 |
25 | #define RS232_PARITY 'N' |
27 | #define RS232_STOP_BITS 1 |
29 | #define RS232_SYNC_REG_DEPTH 2 |
31 | #define RS232_USE_CTS_RTS 0 |
33 | #define RS232_USE_EOP_REGISTER 0 |
35 | #define RS232_SIM_TRUE_BAUD 0 |
37 | #define RS232_SIM_CHAR_STREAM "" |
39 | #define RS232_FREQ 100000000 |
41 | #define ALT_MODULE_CLASS_RS232 altera_avalon_uart |
下面,我们开始编写软件程序,首先是修改sopc.h。如下表格所示
06 | volatile unsigned
long int RECEIVE_DATA :8; |
07 | volatile unsigned
long int NC :24; |
09 | volatile unsigned long int
WORD ; |
14 | volatile unsigned
long int TRANSMIT_DATA :8; |
15 | volatile unsigned
long int NC :24; |
17 | volatile unsigned long int
WORD ; |
22 | volatile unsigned
long int PE :1; |
23 | volatile unsigned
long int FE :1; |
24 | volatile unsigned
long int BRK :1; |
25 | volatile unsigned
long int ROE :1; |
26 | volatile unsigned
long int TOE :1; |
27 | volatile unsigned
long int TMT :1; |
28 | volatile unsigned
long int TRDY :1; |
29 | volatile unsigned
long int RRDY :1; |
30 | volatile unsigned
long int E :1; |
31 | volatile unsigned
long int NC :1; |
32 | volatile unsigned
long int DCTS :1; |
33 | volatile unsigned
long int CTS :1; |
34 | volatile unsigned
long int EOP :1; |
35 | volatile unsigned
long int NC1 :19; |
37 | volatile unsigned long int
WORD ; |
42 | volatile unsigned
long int IPE :1; |
43 | volatile unsigned
long int IFE :1; |
44 | volatile unsigned
long int IBRK :1; |
45 | volatile unsigned
long int IROE :1; |
46 | volatile unsigned
long int ITOE :1; |
47 | volatile unsigned
long int ITMT :1; |
48 | volatile unsigned
long int ITRDY :1; |
49 | volatile unsigned
long int IRRDY :1; |
50 | volatile unsigned
long int IE :1; |
51 | volatile unsigned
long int TRBK :1; |
52 | volatile unsigned
long int IDCTS :1; |
53 | volatile unsigned
long int RTS :1; |
54 | volatile unsigned
long int IEOP :1; |
55 | volatile unsigned
long int NC :19; |
57 | volatile unsigned long int
WORD ; |
62 | volatile unsigned
long int BAUD_RATE_DIVISOR :16; |
63 | volatile
unsigned long
int NC :16; |
65 | volatile unsigned int WORD ; |
这个结构体中包括5个共用体,这5个共用体对应RS232的5个寄存器,我们来看看这5个寄存器,下图所示,这个图来自
《n2cpu_Embedded Peripherals.pdf》的第6-11页
这个图中的(1)有一个说明,就是说第7,8位根据设置的数据位有所改变,我们设置数据位8位,所以7,8位与前6为性质相同。
与之前讲的PIO的结构体类似,这个结构体的内容是按上图的寄存器顺序来定义的,(因为endofpacket没用到,所以在结构中没有定义)这样
在操作过程中就可以实现相应的偏移量(offset)。
在这个结构体中,我们嵌套了5个共有体,在共用体中,我们又使用了结构体和位域。头一次看的一定很头晕。其实,我们这样做的目的就是想对寄存器的每
一位进行单独的控制,同时也可以实现这个寄存器的整体控制。具体应用,我们在下面的程序中会应用到。
有了上面来的结构体以后,我们需要定义一个宏,跟PIO的类似。
6 | #define UART ((UART_STR *) RS232_BASE) |
不用解释了吧,在PIO部分已经解释过了,应该没什么问题了吧。
接下来,我们要在inc下建立uart.h文件,如下图所示
建好以后,对uart.h进行编写,如下表所示
31 | #include "../inc/sopc.h" |
33 | #define BUFFER_SIZE 200 |
43 | unsigned char mode_flag; |
45 | unsigned int receive_flag; |
47 | unsigned int receive_count; |
49 | unsigned char receive_buffer[BUFFER_SIZE]; |
51 | int (* send_byte)(unsigned char data); |
53 | void (* send_string)(unsigned int len, unsigned char
*str); |
57 | unsigned int (* baudrate)(unsigned int baudrate); |
在上面的代码中,结构体UART_T很重要,它是模拟面向对象的一种编程思想,也是我之前说的一种很重要的编程方式。我们将与UART有关系的所有
函数、变量都打包在一起,对其他函数来说,它们只能看到uart这个结构体,而里面的单独部分都是不可见的。希望大家可以好好体会其中的思想,对大家的编
程一定会有很大的好处。
下面,我们要开始写RS232的驱动了,首先我们要在driver下面建立一个.c文件,命名为uart.c,如下图所示
建好以后,我们来编写uart.c文件,如下表所示
020 | #include "sys/alt_irq.h" |
021 | #include "../inc/sopc.h" |
024 | #include "../inc/uart.h" |
029 | static int uart_send_byte(unsigned
char data); |
030 | static void uart_send_string(unsigned
int len, unsigned char *str); |
031 | static int uart_init( void ); |
032 | static void uart_ISR( void ); |
033 | static int set_baudrate(unsigned
int baudrate); |
040 | .send_byte=uart_send_byte, |
041 | .send_string=uart_send_string, |
043 | .baudrate=set_baudrate |
052 | static int uart_send_byte(unsigned
char data) |
055 | UART->TXDATA.BITS.TRANSMIT_DATA = data; |
056 | while (!UART->STATUS.BITS.TRDY); |
066 | static void uart_send_string(unsigned
int len, unsigned char *str) |
070 | uart_send_byte(*str++); |
079 | static int uart_init( void ) |
082 | set_baudrate(115200); |
085 | UART->CONTROL.BITS.IRRDY=1; |
091 | alt_irq_register(RS232_IRQ, NULL, uart_ISR); |
102 | static void uart_ISR( void ) |
105 | while (!(UART->STATUS.BITS.RRDY)); |
108 | uart.receive_buffer[uart.receive_count++] = UART->RXDATA.BITS.RECEIVE_DATA; |
111 | if (uart.receive_buffer[uart.receive_count-1]== '\n' ){ |
112 | uart.receive_buffer[uart.receive_count]= '\0' ; |
113 | uart_send_string(uart.receive_count,uart.receive_buffer); |
114 | uart.receive_count=0; |
124 | static int set_baudrate(unsigned
int baudrate) |
127 | UART->DIVISOR. WORD =(unsigned int )(ALT_CPU_FREQ/baudrate+0.5); |
编写好上面的函数以后,我们要修改main.c,如下表所示
01 | #include "../inc/sopc.h" |
03 | #include "sys/alt_irq.h" |
06 | #include "../inc/uart.h" |
10 | unsigned
char buffer[50]= "Hello FPGA!\n" ; |
2 | uart.send_string( sizeof (buffer),buffer); |
今天就讲到这,上面的讲解方式不知道大家觉得是否合适,如果有什么问题,请给我留言。
实时时钟 (七)
简介
这一节,我将给大家讲解实时时钟部分的内容,我在黑金板上用的实时时钟芯片是DS1302,这块芯片很常见,性价比也很高。我们主要来讲如何在NIOS中
实现其功能,所以DS1302功能介绍我简单概括一下,有问题的百度一下就都知道了。
DS1302是DALLAS公司推出的涓流充电实时时钟芯片,内含一个实时时钟/日历和31字节静态RAM,仅需要三根线:RES(复位),I/O(数据
线),SCLK(串行时钟)。时钟/RAM 的读/写数据以一个字节或多达 31 个字节的字符组方式通信 DS1302
工作时功耗很低,保持数据和时钟信息时功率小于 1mW。下面看一下电路图吧,下图所示,很简单,三根线就可以搞定了。
硬件开发
首先,我们需要在软核中构建三个PIO模块,方法跟以前讲的一样。需要注意的是RTC_DATA这个PIO,在构建的过程中,我们将其选择为双向的IO
口,因为它是数据线,既要输入也需要输出,如下图所示,红圈处就是我们需要注意的地方,其他两个IO口设置为仅输出。
看看构建好以后的样子吧,如下图是所示
接下来就是自动分配地址,中断,然后开始编译,等待……
回到Quartus后,分配引脚,还是需要注意数据线,也是双向的,分配引脚的时候,要构建双向引脚(bidir),如下图所示。
都设置好以后,我们运行TCL脚本文件,然后开始编译,又是等待……
软件开发
编译好后,我们打开NIOS II
IDE,首先,还是需要编译一下,CTRL+b,编译之后,我们看看system.h有什么变化。观察后可以看出,里面对了,RTC部分的代码,如下表所
示,
01 | #define RTC_DATA_NAME "/dev/RTC_DATA" |
02 | #define RTC_DATA_TYPE "altera_avalon_pio" |
03 | #define RTC_DATA_BASE 0x00201030 |
11 | #define RTC_SCLK_NAME "/dev/RTC_SCLK" |
12 | #define RTC_SCLK_TYPE "altera_avalon_pio" |
13 | #define RTC_SCLK_BASE 0x00201040 |
20 | #define RTC_NRST_NAME "/dev/RTC_nRST" |
21 | #define RTC_NRST_TYPE "altera_avalon_pio" |
22 | #define RTC_NRST_BASE 0x00201050 |
在这些代码中,我们需要用到的是以下部分
1 | #define RTC_DATA_BASE 0x00201030 |
2 | #define RTC_SCLK_BASE 0x00201040 |
3 | #define RTC_NRST_BASE 0x00201050 |
好的,接下来,我们就开始写程序吧
第一步,修改sopc.h文件,加入以下代码到sopc.h中
4 | #define RTC_SCLK ((PIO_STR *) RTC_SCLK_BASE) |
5 | #define RTC_DATA ((PIO_STR *) RTC_DATA_BASE) |
6 | #define RTC_RST ((PIO_STR *) RTC_NRST_BASE) |
没什么可说的,接下来我们在inc文件夹下建立ds1302.h,在其中加入以下内容,跟串口程序一样,里面也有个结构体,用这种方式整合所有的函
数和变量。
17 | #include "../inc/sopc.h" |
20 | #define RTC_DATA_OUT RTC_DATA->DIRECTION = 1 |
21 | #define RTC_DATA_IN RTC_DATA->DIRECTION = 0 |
24 | void (* set_time)(unsigned char *ti); |
25 | void (* get_time)( char * ti); |
准备工作都做好以后,接下来我们要做的就是写ds1302的驱动了,根据DS1302的时序图来进行编写,首先我来给看看时序图吧,如下图所示,这
个是读数据的时序图,
这个是写数据时序图
还有一个有关寄存器的表格,大家也要注意看一下,如下所示,前面两列是读和写的地址,每次操作时,都先写地址,再传数据。
现在,我们就根据时序图来编写ds1302的驱动,在driver文件夹下建ds1302.c文件,然后添加以下内容,
015 | #include "../inc/ds1302.h" |
017 | static void delay(unsigned
int dly); |
018 | static void write_1byte_to_ds1302(unsigned
char da); |
019 | static unsigned char read_1byte_from_ds1302( void ); |
020 | static void write_data_to_ds1302(unsigned
char addr, unsigned char da); |
021 | static unsigned char
read_data_from_ds1302(unsigned char addr); |
022 | void set_time(unsigned char
*ti); |
023 | void get_time( char
*ti); |
027 | .set_time = set_time, |
037 | void delay(unsigned int
dly) |
048 | void write_1byte_to_ds1302(unsigned char da) |
077 | unsigned char read_1byte_from_ds1302( void ) |
080 | unsigned char da = 0; |
088 | if (RTC_DATA->DATA !=0 ) |
108 | void write_data_to_ds1302(unsigned char addr, unsigned char da) |
117 | write_1byte_to_ds1302(addr); |
118 | write_1byte_to_ds1302(da);
|
132 | unsigned char read_data_from_ds1302(unsigned
char addr) |
143 | write_1byte_to_ds1302(addr); |
144 | da = read_1byte_from_ds1302(); |
161 | void set_time(unsigned char
*ti) |
164 | unsigned char addr = 0x80; |
166 | write_data_to_ds1302(0x8e,0x00); |
170 | write_data_to_ds1302(addr,*ti);
|
176 | write_data_to_ds1302(0x8e,0x80); |
184 | void get_time( char
*ti) |
187 | unsigned char addr = 0x81; |
191 | time =read_data_from_ds1302(addr); |
192 | ti = time /16*10+ time %16; |
OK,我们的驱动写好了,现在我们来写一个main函数来验证一下我们的驱动是否好用吧。
02 | #include "../inc/uart.h" |
03 | #include "../inc/ds1302.h" |
07 | unsigned char time [7] = {0x00,0x19,0x14,0x17,0x03,0x17,0x10}; |
11 | unsigned
char buffer[50]= "\0" ; |
13 | ds1302.set_time( time ); |
17 | ds1302.get_time( time ); |
20 | sprintf (buffer, "20%d-%d-%d %d:%d:%d\n" , |
21 | time [6], time [4], time [3], time [2], time [1], time [0]); |
24 | uart.send_string( sizeof (buffer),buffer); |
在上面的程序中,我们获取时间后通过串口发送到上位机,这样也复习了我们上一节讲的串口程序。当然,大家也可以直接通过printf()打印出来。
在操作ds1302的时候有一点需要注意,ds1302的输入和输出都是8421BCD码进行的,所以我们需要对其进行转换。不过,输入的时候我是
直接输入16进制,比如,我们设置分钟为10的话,我直接输入十六进制的0x10,这样就不需要转化了。而在输出的时候是必须要转化的,大家在写程序的时
候注意这一点。
好了,我们来看看我们的劳动果实,看看串口传出的数据吧。
这一节就讲到这吧,如果有问题请给我留言,或者加入我们的NIOS技术群:107122106,让我们共同讨论解决,谢谢大家!
SPI 实验 (八)
简介
这一节,我们来讲讲NIOS II中的SPI总线的用法。首先,我们来简单介绍一下SPI总线吧,SPI是英文Serial
Peripheral
Interface的缩写,中文意思是串行外围设备接口,是Motorola公司推出的一种同步串行通讯方式,是一种四线同步总线,因其硬件功能很强,与
SPI有关的软件就相当简单,使CPU有更多的时间处理其他事务。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(用于单向传输时,也就是半
双工方式)。也是所有基于SPI的设备共有的,它们是MISO(主入从出),MOSI(主出从入),SCK(时钟),CS(片选)。
(1)MISO – 主设备数据输出,从设备数据输入
(2)MOSI – 主设备数据输入,从设备数据输出
(3)SCK – 时钟信号,由主设备产生
(4)CS – 从设备使能信号,由主设备控制
其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效。这就允许在同一总线上连接多个
SPI设备成为可能。
SPI总线的理论知识就介绍这么多,想要看具体点的去网上百度一下吧。下面我们就开始SPI总线的开发旅程吧。
硬件开发
在我们开发板中网口部分是用SPI总线实现的,网络芯片是MICROCHIP公司的ENC28J60,我们先看一下这部分的电路,如下图所示,其中与
SPI总线相关的有,LAN_MISO,LAN_MOSI,LAN_SCK这三个根线,其余的都是通过PIO模块实现的。而且有些线还用不到,比如
LAN_nWOL。
我们这一节主要是教大家如何来实现SPI总线的功能,对于ENC28J60的原理相对复杂,在这里我就不详细讲解了,大家有兴趣的可以自己研究一
下。
下 面我们就来构建SPI模块,进入SOPC BUILDER后,我们如下图所示,点击红圈处(SPI)
点击后,如下图所示,在这里面,我们有5个地方需要注意,
红圈1处是主从模式选择,我们选择主模式(Master);
红圈2处是从设备的个数,我们选择1;
红圈3处是SPI时钟速率,我们选择10M,这个地方需要注意一下,我们设置的频率与实际的频率有时候是不一致的(下面显示的是实际频率),例如,
我们输入50MHz,实际的频率只有25MHz。
红圈4处是数据的位数,我们选择8;
红圈5处是移位的方向,就是说串行数据过来时,是最高位先来还是最低位先来,我们选择MSB first。
处理好这些以后,点击Finish,完成构建。
接下来,我们还要构建两个PIO模块,一个用作CS信号控制,一个用作中断信号。之所以没有用SPI总线本身的CS,是由程序处理本身决定的。中断信号的
PIO模块,构建过程需要注意一下内容,首先作为中断信号,是输入信号,所以在选择过程中,如下图所示,红圈1处选择为1,红圈2处选择Input
ports only,仅作为输入端口,点击Next,进行下一步
点击后,进入下一步,如下图所示,外部中断要求电平触发,所以按红圈处选择方式。
然后,我们点击Finish,完成构建。
完成上述内容以后,我们需要对模块进行改名,如下图所示,
一切就绪,别忘了自动地址分配和中断分配。哦了,我们开始编译吧,等待……
编译好以后,我们回到Quartus界面,根据TCL脚本文件进行管脚分配,如下图所示
接下来我们运行脚本文件,进行编译,又一次漫长的等待……
编译成功以后,我们开始进行软件部分的开发
软件开发
打开NIOS II 9.0
IDE,然后进行编译,快捷键Ctrl+b,等待编译成功后,我们来看看system.h中多了些什么,如下表所示,
05 | #define LAN_NAME "/dev/LAN" |
06 | #define LAN_TYPE "altera_avalon_spi" |
07 | #define LAN_BASE 0x00201020 |
13 | #define LAN_CS_NAME "/dev/LAN_CS" |
14 | #define LAN_CS_TYPE "altera_avalon_pio" |
15 | #define LAN_CS_BASE 0x00201060 |
21 | #define LAN_NINT_NAME "/dev/LAN_nINT" |
22 | #define LAN_NINT_TYPE "altera_avalon_pio" |
23 | #define LAN_NINT_BASE 0x00201070 |
我们需要以下内容
1 | #define LAN_BASE 0x00201020 |
2 | #define LAN_CS_BASE 0x00201060 |
3 | #define LAN_NINT_BASE 0x00201070 |
接下来,我们需要对sopc.h进行修改,在其中加入以下代码
02 | volatile unsigned long int
RXDATA; |
03 | volatile unsigned long int
TXDATA; |
06 | volatile unsigned
long int NC :3; |
07 | volatile unsigned
long int ROE :1; |
08 | volatile unsigned
long int TOE :1; |
09 | volatile unsigned
long int TMT :1; |
10 | volatile unsigned
long int TRDY :1; |
11 | volatile unsigned
long int RRDY :1; |
12 | volatile unsigned
long int E :1; |
13 | volatile unsigned
long int NC1 :23;
|
15 | volatile unsigned long int
WORD ; |
20 | volatile unsigned
long int NC :3; |
21 | volatile unsigned
long int IROE :1; |
22 | volatile unsigned
long int ITOE :1; |
23 | volatile unsigned
long int NC1 :1; |
24 | volatile unsigned
long int ITRDY :1; |
25 | volatile unsigned
long int IRRDY :1; |
26 | volatile unsigned
long int IE :1; |
27 | volatile unsigned
long int NC2 :1; |
28 | volatile unsigned
long int SSO :21; |
30 | volatile unsigned long int
CONTROL; |
33 | unsigned long int
RESERVED0; |
34 | unsigned
long int SLAVE_SELECT; |
这部分代码是根据《n2cpu_Embedded
Peripherals.pdf》的第7-10页,如下表所示,结构体的顺序是根据下表的排列顺序进行设计的,与串口中结构体的道理相同。
除了上述结构体以外,我们还要在sopc.h中加入以下代码
2 | #define LAN ((SPI_STR *) LAN_BASE) |
3 | #define LAN_CS ((PIO_STR *) LAN_CS_BASE) |
修改好sopc.h以后,我们需要在inc文件夹下建立一个enc28j60.h,在其中加入以下内容,(这只是enc28j60.h文件中的一部
分,还有很大一部分宏定义没有写出)
05 | unsigned
char (* read_control_register)(unsigned char address); |
06 | void (* initialize)( void ); |
07 | void (* packet_send)(unsigned short len,unsigned char
* packet); |
08 | unsigned
int (* packet_receive)(unsigned short
maxlen,unsigned char * packet); |
14 | extern ENC28J60 enc28j60; |
大家可以看出,在我们的程序中,这样的结构体随处可见,在之前的串口程序,还是这个SPI程序,我们都在用。它的好处就在于,可以将零散的函数和变
量整合在一起,通过结构体的形式来处理,大大提高了程序的可读性,也增强了程序的可维护性和可移植性。
处理好上述内容后,我们开始编写enc28j60的驱动程序,内容很多,我们截取其中一部分有关SPI的内容来进行讲解
018 | #include "../inc/enc28j60.h" |
019 | #include "../inc/sopc.h" |
025 | static unsigned char
enc28j60_read_control_register( |
026 | unsigned char address); |
027 | static void enc28j60_initialize( void ); |
028 | static void enc28j60_packet_send(unsigned
short len, |
029 | unsigned char
* packet); |
030 | static unsigned int
enc28j60_packet_receive( |
031 | unsigned short maxlen,unsigned
char * packet); |
038 | .read_control_register = enc28j60_read_control_register, |
039 | .initialize = enc28j60_initialize, |
040 | .packet_send = enc28j60_packet_send, |
041 | .packet_receive = enc28j60_packet_receive |
044 | static unsigned char enc28j60_bank = 1; |
045 | static unsigned short next_packet_pointer; |
052 | static void set_cs(unsigned
char level) |
068 | static void enc28j60_write_operation(unsigned
char op, |
069 | unsigned char address, unsigned char data) |
075 | LAN->TXDATA = (op | (address & 0x1F)); |
076 | while (!(LAN->STATUS.BITS.TMT)); |
081 | while (!(LAN->STATUS.BITS.TMT)); |
092 | static unsigned char
enc28j60_read_operation(unsigned char op, |
093 | unsigned char address) |
101 | LAN->TXDATA = op|(address&0x1f); |
102 | while (!(LAN->STATUS.BITS.TMT)); |
106 | while (!(LAN->STATUS.BITS.TMT)); |
111 | while (!(LAN->STATUS.BITS.TMT));
|
对于网口这部分程序,需要结合TCP/IP协议才能进行通信,所以,这部分主函数就暂时不写了,等讲到TCP/IP协议那部分的时候,我们再进行讲
解。
我们这一节主要是讲解SPI总线的使用方法,对于数据的收发都有所涉及,希望大家能够充分的理解其中的编程方法。有关ENC28J60的完整驱动,我将以
附件形式提供给大家,这一节到此结束了,如果大家对这部分内容有疑问,可以加入我们的NIOS技术群,或者通过邮件形式与我沟通,谢谢大家。
程序下载(九)
简介
这一节,我们来讲解一下如何将编译好的程序下载到开发板中。
在开发NIOS过程中,需要下载两次程序。第一次是在quartus软件中,将我们的逻辑和软核生成的配置文件通过AS模式下载到EPCS*(*为
1,4,8…)中,或者通过JTAG模式下载到FPGA内部的SRAM中。第二次是在NIOS软件中,将程序下载到FLASH中。下面,我们就一个一个的
给大家讲解。
下载配置文件
首先,需要将usb
blaster与开发板连接,我们先将其与开发板的AS模式接口相连。然后我们打开quartus软件(假设我们已经将工程编译好了),打开后,点击下图
所示红圈处
打开后,我们可以看到下图
红圈1处是选择下载模式,我们选择Active Serial Programming。如果你发现红圈2处旁边写着No
Hardware,你点击红圈2处。
点击后,如下图所示,双击红圈1处,发现红圈2处为USB-Blaster[USB-0]即可,点击close。
点击后,我们回到下图界面,我们点击红圈3处(Add File…)将要下载的程序加入进来。
点击后,如下图所示,我们双击红圈处后,点击打开
点击后,我们回到下图,我们还需要配置一下,将红圈1处的两个选项选中(Program和Verity),
然后点击红圈2处的Start,点击后开始下载,红圈3处可以看到下载进度。
下载好以后,我们可以看到,红圈3处变成了下图样子,进度变为100%,说明下载成功。
在AS模式下,下载成功后,需要把USB
Blaster与开发板断开,重新启动开发板,下载的程序才能跑起来,这一点大家需要注意。用AS模式下载的程序是下载到EPCS*的,掉电以后可以保
存,数据不会丢失。而用JTAG下载的程序时下载到FPGA内部的SRAM中的,掉电后数据会丢失。下面我们来看看如何实现JTAG口的程序下载。
方法跟AS模式相似,只是在模式选择的时候有所不同。如下图所示,红圈处选择JTAG,然后点击Add File,
点击后,如下图所示,选中红圈处的led.sof
点击打开回到下图界面,点击红圈1出的Start,点击后,红圈2处会看到进度,完成后进度显示100%。
OK,在quartus下的程序下载就讲完了,下面我们来讲一讲在NIOS下如何进行程序下载到FLASH中。
烧写程序到FLASH
对于NIOS下程序下载,我们只需要JTAG口就可以了,我们首先将USB-Blaster与开发板的JTAG口连接,然后,打开NIOS II IDE
打开后,点击下图红圈处
点击后,如下图所示,双击红圈处
双击后,如下图所示,红圈1处选中,就为将NIOS程序烧到Flash中,红圈2为工程名,红圈3为烧到flash的文件名,以.elf后缀结束。
红圈4处是将配置文件烧到Flash中。红圈5处是将文件烧到Flash中,红圈6处在下载前检测SYSTEM
ID,这些根据大家的需求来修改。我们要将程序烧到flash中,默认就可以了,不需要改变。点击Program Flash,开始烧写。
烧写过程中,NIOS II IDE的观察栏可以看到下载信息,下载好以后,如下图所示
烧写好以后,我们重新启动就可以了。
其实,在NIOS II IDE下也可以进入Quartus IIProgrammer下载配置信息,如下图所示
好了,这一节我们就讲到这,有问题的可以给我留言,我的qq:984597569,或者加入我们的NIOS技术高级群:107122106。
IIC 总线实验(十)
简介
这一节,我们来讲一讲有关IIC总线的实验,在硬件中,我们实用了24LC04,一个512字节的EEPROM。在NIOS
II中,没有集成IIC接口,为了实现这一功能,我们有两种途径,一种就是自己写IP核或者移植别人的IP核,另一种方法就是通过IO口模拟IIC总线协
议。我们这一节采用的方法是后者,通过IO口模拟IIC总线协议,以达到对24LC04控制读写的目的。
首先,我简单介绍一下IIC总线的原理,大家稍微了解一下。IIC(Inter-Integrated
Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线SDA和时钟SCL构成的串行总线,
可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率100kbps。它在传送数据过程中共有三种类型信号,
它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待
受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。下图就为IIC总线的时序图。
简单介绍之后,我们就要开始实践一下了,开始吧
硬件开发
首先,需要在软核中添加两个IO模块,并将其命名为SCL和SDA,其中,SCL为output建ports
only(仅输出),SDA为Bidirection (tristate) port(双向),建好以后,如下图所示
接下来,我们自动分配一下地址,编译。
完成后,我们回到Quartus界面。然后我们来分配引脚,如下图所示
分配好管脚以后,我们运行TCL脚本文件,开始编译(Ctrl+L)……
编译完成后,我们的硬件部分就结束了,接下来,就是我们的软件开发部分了。
软件开发
首先,我们打开NIOS II 9.0 IDE,然后进行编译(Ctrl+B)。
编译好以后,我们看一下system.h文件,看是否多出了SCL和SDA部分代码。跟我们预期的一样,system.h文件中出现了SCL和SDA部分
代码,如下表所示
05 | #define SCL_NAME "/dev/SCL" |
06 | #define SCL_TYPE "altera_avalon_pio" |
07 | #define SCL_BASE 0x00201060 |
14 | #define SDA_NAME "/dev/SDA" |
15 | #define SDA_TYPE "altera_avalon_pio" |
16 | #define SDA_BASE 0x00201070 |
在跟大家讨论过程中,我了解到很多人都想知道有关NIOS
II自带的API的用法,所以,今天我就用这种方式来实现我们的程序。不过我还是推荐大家用我之前的方式编写程序,道理我已经说过了, 在此不再重复。
下面我们在inc目录下建立一个iic.h文件,如下表所示
08 | void (* write_byte)(unsigned short addr, unsigned char
dat); |
09 | unsigned
char (* read_byte)(unsigned short
addr); |
接下来,我们需要在driver下建立iic.c文件,如下表所示
002 | #include <sys/unistd.h> |
006 | #include "altera_avalon_pio_regs.h" |
007 | #include "alt_types.h" |
008 | #include "../inc/iic.h" |
010 | static alt_u8 read_byte(alt_u16 addr); |
011 | static void write_byte(alt_u16 addr, alt_u8 dat); |
014 | .write_byte = write_byte, |
015 | .read_byte = read_byte |
025 | static void start( void ) |
027 | IOWR_ALTERA_AVALON_PIO_DIRECTION(SDA_BASE, OUT); |
028 | IOWR_ALTERA_AVALON_PIO_DATA(SDA_BASE, 1); |
029 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 1); |
031 | IOWR_ALTERA_AVALON_PIO_DATA(SDA_BASE, 0); |
040 | static void stop( void ) |
042 | IOWR_ALTERA_AVALON_PIO_DIRECTION(SDA_BASE, OUT); |
043 | IOWR_ALTERA_AVALON_PIO_DATA(SDA_BASE, 0); |
044 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 0); |
046 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 1); |
048 | IOWR_ALTERA_AVALON_PIO_DATA(SDA_BASE, 1); |
061 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 0); |
062 | IOWR_ALTERA_AVALON_PIO_DIRECTION(SDA_BASE, IN); |
064 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 1); |
066 | tmp = IORD_ALTERA_AVALON_PIO_DATA(SDA_BASE); |
068 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 0); |
079 | void iic_write(alt_u8 dat) |
083 | IOWR_ALTERA_AVALON_PIO_DIRECTION(SDA_BASE, OUT); |
086 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 0); |
088 | tmp = (dat & 0x80) ? 1 : 0; |
090 | IOWR_ALTERA_AVALON_PIO_DATA(SDA_BASE, tmp); |
092 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 1); |
102 | static alt_u8 iic_read( void ) |
106 | IOWR_ALTERA_AVALON_PIO_DIRECTION(SDA_BASE, IN); |
109 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 0); |
111 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 1); |
114 | dat |= IORD_ALTERA_AVALON_PIO_DATA(SDA_BASE); |
119 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 0); |
121 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 1); |
123 | IOWR_ALTERA_AVALON_PIO_DATA(SCL_BASE, 0); |
134 | static void write_byte(alt_u16 addr, alt_u8 dat) |
137 | cmd = (0xa0 | (addr >> 7)) & 0xfe; |
154 | static alt_u8 read_byte(alt_u16 addr) |
157 | cmd = (0xa0 | (addr >> 7)) & 0xfe; |
最后,我们来建立main.c函数
02 | #include "../inc/iic.h" |
06 | alt_u8 write_buffer[512], read_buffer[512]; |
13 | printf ( "\nWriting data to EEPROM!\n" ); |
22 | iic.write_byte(i, dat); |
24 | printf ( "0x%02x " , dat); |
28 | printf ( "\nReading data from EEPROM!\n" ); |
32 | read_buffer = iic.read_byte(i); |
33 | printf ( "0x%02x " , read_buffer); |
38 | printf ( "\nVerifing data!\n" ); |
42 | if (read_buffer != write_buffer) |
47 | printf ( "\nData write and read successfully!\n" ); |
49 | printf ( "\nData write and read failed!--%d errors\n" , err); |
程序很简单,大家只要对IIC总线有一定的了解就会明白的。
好的,这节的内容就讲到这,谢谢大家对我的支持。如果有问题,可以给我留言或者直接加入我们的NIOS技术高级群:107122106,也可以加我的
qq:984597569
定时器实验(十一)
简介
这一节,我们来讲讲有关定时器的内容。定时器,顾名思义,与时间有关系的一个器件,是用于对时钟周期进行计数并产生周期性中断信号的硬件外围设备。
用过单片机的人对定时器一定很熟悉,它主要用来处理周期性的事件,比如设置AD的采样频率,通过定时器产生周期性的定时器中等等。我发现,在很多资料中,
都是介绍如何实现系统时钟,Timestamp,或者是看门狗的功能,却没有真正的介绍如何真正的去使用定时器本身具备的功能,这样很容易误导大家。有的
人可能在学习这部分内容的时候遇到这样的问题,一个软核只能定义一个系统时钟,那如果我们想用到两个定时器怎么办呢?没办法,这种方式行不通,这就是系统
时钟方式的弊端。为了解决这个问题,我们就要撇开系统时钟,真正的去了解NIOS本身定时器所具备的功能,它是可以像单片机的定时器一样来操作的。下面我
们开始吧。
硬件开发
首先我们需要在NIOS软核中构建timer模块,如下图所示
点击进入后,如下图所示,红圈1处是用于预设硬
件生成后的定时器周期,
也就是说这是个初始值,我们可以通过软件来改变定时器的周期。红圈2处是定时器计数器的大小,它分为32位和64位两种,需要根据你的定时器的周期来选
择,我们在这里选择32位。红圈3处是定时器的几种预设方式,是为了实现不同的功能,我们在这里选择Full-featured,就是全功能。选择了这个
选项,我们就可以修改周期,可以控制停止开始位。选择好以后,点击Finish完成设置。
我们在这里建立两个定时器,另一个方法相同。建
立好以后,如下图所示
我们这个定时器试验需要4个LED来配合,所以还
需要建立一个PIO模块,这里不具体讲了,前面已经讲过了。接下来,自动配置地址,自动配置中断,跟以前的都一样,下来就是编译,等待,编译,等待…
硬件部分就OK了,接下来我们开始软件开发了。
软件开发
打开NIOS 9.0 IDE,首先编译一下,快捷键Ctrl+b。编译完以后,我们可以去看看system.h文件,应该出现了下面内容
01 | #define TIMER1_NAME "/dev/timer1" |
03 | #define TIMER1_TYPE "altera_avalon_timer" |
05 | #define TIMER1_BASE 0x00201000 |
09 | #define TIMER2_NAME "/dev/timer2" |
11 | #define TIMER2_TYPE "altera_avalon_timer" |
13 | #define TIMER2_BASE 0x00201000 |
大家在开发NIOS软件的时候,要注意一定要先看systetm.h文件,首先确定硬件已经正确加载,而且名字跟你设定的是一样的,避免在这个地方出现问
题。
下面,我们来看看定时器的寄存器,这面的表格来自《n2cpu_Embedded Peripherals.pdf》的第24-6页。
通过这个表格,我们可以看到,定时器有状态寄存器,控制寄存器,定时器周期的高16位,低16位,还有snap的高16位,低16位,我们用到的是
前3个寄存器,snap寄存器还没有弄到。
我们下面的实验是通过定时器1来控制4个LED的闪烁,而定时器2是用来改变定时器1的定时器周期,这样,我们就可以看到,4个LED的闪烁频率在不断的
变化。
下面我们来看看源代码
20 | #include <sys/unistd.h> |
25 | #include "altera_avalon_pio_regs.h" |
26 | #include "altera_avalon_timer_regs.h" |
28 | #include "sys/alt_irq.h" |
29 | #include "../inc/sopc.h" |
34 | static void timer_init( void ); |
37 | alt_u32 timer_prd[4] = {5000000, 10000000, 50000000, 100000000}; |
23 | static void ISR_timer1( void *context, alt_u32 id) |
34 | IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER1_BASE, 0x00); |
43 | static void ISR_timer2( void *context, alt_u32 id) |
46 | IOWR_ALTERA_AVALON_TIMER_PERIODL(TIMER1_BASE, timer_prd[j]); |
47 | IOWR_ALTERA_AVALON_TIMER_PERIODH(TIMER1_BASE, timer_prd[j] >> 16); |
50 | IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER1_BASE, 0x07); |
66 | IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER2_BASE, 0); |
78 | IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER1_BASE, 0x00); |
80 | IOWR_ALTERA_AVALON_TIMER_PERIODL(TIMER1_BASE,100000000); |
81 | IOWR_ALTERA_AVALON_TIMER_PERIODH(TIMER1_BASE, 100000000 >> 16); |
83 | IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER1_BASE, 0x07); |
85 | alt_irq_register(TIMER1_IRQ, ( void *)TIMER1_BASE, ISR_timer1); |
88 | IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER2_BASE, 0x00); |
90 | IOWR_ALTERA_AVALON_TIMER_PERIODL(TIMER2_BASE,500000000); |
91 | IOWR_ALTERA_AVALON_TIMER_PERIODH(TIMER2_BASE, 500000000 >> 16); |
93 | IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER2_BASE, 0x07); |
95 | alt_irq_register(TIMER2_IRQ, ( void *)TIMER2_BASE, ISR_timer2); |
上面的方式是通过HAL提供的API实现的,当然我们也可以通过我以前提供的方法实现,建立一个结构体,直接对寄存器进行赋值,这样的方法我更喜
欢,清晰而且结构完全自己控制。下面提供给大家一个这个结构体,实现方法大家自己实现吧,很简单,一句一句替换就可以了。建立结构体的内容根据下面的表格
决定。
04 | volatile unsigned
long int TO :1; |
05 | volatile unsigned
long int RUN :1; |
06 | volatile unsigned
long int NC :30; |
08 | volatile unsigned long int
WORD ; |
13 | volatile unsigned
long int ITO :1; |
14 | volatile unsigned
long int CONT :1; |
15 | volatile unsigned
long int START:1; |
16 | volatile unsigned
long int STOP :1; |
17 | volatile unsigned
long int NC :28; |
19 | volatile unsigned long int
WORD ; |
22 | volatile unsigned long int
PERIODL; |
23 | volatile unsigned long int
PERIODH; |
24 | volatile unsigned long int
SNAPL; |
25 | volatile unsigned long int
SNAPH; |
有了这个结构体就可以按照我之前给大家讲的方法实现定时器功能了。到此,我说的定时器的方法就讲完了,同时用两个定时器的问题也能够得以解决了。
下面,我给大家一个有关定时器系统时钟的例程作为参考,但我不建议大家使用,原因我已经说过了。这个函数实现的是每隔一秒点亮一个LED,一共4个
LED。首先需要软件设置一下,如下图所示,进入系统库属性,在System clock
timer选项框中选择你要的定时器。选择好以后,需要重新编译才行。
20 | #include "altera_avalon_pio_regs.h" |
22 | #include "sys/alt_irq.h" |
23 | #include "../inc/sopc.h" |
24 | #include "sys/alt_alarm.h" |
29 | alt_u32 my_alarm_callback( void *context); |
35 | unsigned int alarm_flag; |
54 | if (alt_alarm_start(&alarm,INTEVAL_TICK,my_alarm_callback,NULL) < 0){ |
56 | printf ( "Error: No system clock available\n" ); |
62 | printf ( "Success: System clock available\n" ); |
87 | alt_u32 my_alarm_callback( void *context) |
定时器的内容就讲完了,由于时间匆忙,可能其中有错误的地方,大家看到了给我留言,我确定后将加以修改,谢谢大家!
SDRAM 实验(十二)
一、简介
这一节,我们来聊聊SDRAM吧。作为NIOS系统中最重要的一个外部器件,它担任着重要的角色,大家对它也应该很熟悉。每次上电的时候,FPGA都会把
FLASH中的程序送到SDRAM中运行,之所以这样来做就是因为它的速度很快,但它掉电是要丢失数据的,所以要把数据存到FLASH中。
有关SDRAM的理论知识我在这里不说了,不知道的百度google一下都可以。其实在NIOS
II开发过程中,就算你对SDRAM的理论知识不了解,也不耽误你对它的使用。SOPC
builder都已经完美的将它驱动起来,我们只要知道怎么使用它就可以了。下面,我们就来讲讲他的使用方法,其实真的很简单。
在我们讲第一节的时候,我们就已经讲了如何构建SDRAM的控制器了,我在这里不再重复了,假设你已经构建好了,我主要讲一下有关软件的部分。
二、软件开发
首先打开NIOS II 9.0
IDE软件,打开后,我们来看看system.h文件,确定一下SDRAM控制器模块是否已经加入进来。如果加入,有下面的内容出现
1 | #define SDRAM_NAME "/dev/sdram" |
3 | #define SDRAM_TYPE "altera_avalon_new_sdram_controller" |
5 | #define SDRAM_BASE 0x01000000 |
接下来,我们开始编写有关SDRAM的软件代码,代码很简单,如下所示
18 | #include "../inc/sopc.h" |
25 | unsigned short * ram = (unsigned short *)(SDRAM_BASE+0x10000); |
45 | printf ( "%d\n" ,*(--ram)); |
程序很简单,就是向指定的SDRAM中赋值。在这个程序里面有几个地方需要说明一下。首先,我在程序前面定义了一个unsigned
short类型的指针变量ram,并将其指向SDRAM+0x10000这个位置。之所以设置为unsigned
short数据类型,是因为我们用的SDRAM是16位数据总线的。而将其指向SDRAM+0x10000是因为在NIOS
II运行时会用到SDRAM的部分空间,我们必须避开这部分空间,以免运行错误。0x10000这个值不是固定的,只要避开SDRAM的那部分空间就可以
了。除此之外还有一个地方需要注意,就是当我们对sdram赋值以后,指针就会向后移动,指向下一个地址空间,每加一次,地址都会向后面移动16位。假如
我们现在是在SDRAM+0X10000这个位置,当指针向后移动一次以后,地址就变为了SDRAM+0X10010,再加一次就变为了
SDRAM+0X10020,以此类推。这些都是内部自动处理的,不需要我们来参与,我们只要知道就可以了。
其实我讲这部分内容是想告诉大家,SDRAM控制器一旦构建好以后,我们对SDRAM的处理就像对内部地址一样,我们可以随意的进行赋值和读取。对于开发
板上的64Mbit的SDRAM其实有很少一部分用给NIOS系统,其余部分都在空闲,大家是不是觉得很浪费呢。其实在有些情况下,我们就可以利用起这部
分资源,比如在某个系统中,我们需要将外设接收到的数据缓存一下,我们就可以用这部分空闲的SDRAM空间来处理。
在C语言中,如果我们要接收比较大的数据,还有另一种处理方法,那就是借助堆(heap)。可能有些人对堆和栈还分不清楚,我在这简单解释一下。栈
(stack) 由系统自动分配。 例如,声明在函数中一个局部变量 int
b,系统自动在栈中为b开辟空间。而堆(heap)需要程序员自己申请,并指明大小。有人用这样一个比喻来解释堆和栈的区别,非常形象贴切:使用栈就象我
们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自
由度小。使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大,这个人真是太有才了。下面我来写一个堆的代码,这部分代
码是节选的,并不完全,功能是通过xmodem协议接收数据,并将其内容放到堆里面。如下所示
09 | char *ram = ( char *) malloc ( sizeof ( char )* 500000 ); |
12 | fprintf ( stderr, "\ndynamic memory allocation failed\n" ); |
23 | printf ( "ram_cnt:%d\n" ,ram_cnt); |
24 | parse_srecord_buf(ram,ram_cnt); |
25 | printf ( "successful!" ); |
看了上面的代码大家应该了解了堆的用法了吧,它是通过malloc向系统中申请空间的。申请成功以后,就会返回申请地址的首地址,失败则返回NULL。到
底申请在什么地方,我们可以通过打印指针来得知,但并没这个必要,因为这些都是系统来处理的,我们得到了首地址然后用就可以了。需要注意一点,我们申请完
的地址用完以后需要释放,用free来释放就可以了。如果不释放,可能会出现内存泄露问题,到时候麻烦就大了,具体大到什么程度我也不知道,呵呵。
可能有人会问,为什么不用栈来处理这个问题呢?这就跟编译有关系了,在编译过程中,系统会将栈需要的空间加到代码中,也就是说如果你在代码中用栈来处理大
的数据,那么你编译以后的代码会非常大,你下载到flash以后,加载到sdram中的时间也会非常之长。而堆这不会,系统对堆的处理方式是何时用合适分
配,并不占代码空间。
说到这,有关SDRAM部分的内容讲完了。总结一下,使用SDRAM有两种方法,第一种是直接对SDRAM地址处理;第二种方法就是利用堆来处理。好了,
这部分内容就讲到这吧,如果大家对此有疑问,或者发现我讲的内容有问题可以直接跟我联系,邮箱:avic633@gmail.com;qq:984597569。
如何将程序下载到EPCSX中(十三)
简介
这一节,应网友的要求,我们来讲解如何将FPGA配置文件和NIOS的程序下载到EPCSx(x为1,4,16…)里面。首先说几句,之所以我们要将程序
下载到EPCSX中,而不下载到并行FLASH中,是因为我们可以将并行的FLASH去掉,这样就可以节省32根引脚,在布PCB的时候也可以节省空间,
何乐而不为,我的改进的核心板就是这样做的。有人会问,并行的速度要快很多啊,其实我们存储在FLASH的代码,每次上电或复位,仅仅需要读取一次而已,
在速度上没有太大的影响。除非你有特殊要求,如在程序中需要频繁的操作FLASH,这样才需要并行的满足速度的要求。好了,下面我们就来进行操作。
硬件设置
首先在软核中添加EPCS Serial Flash controller,如下图所示红圈处
双击红圈处后,如下图所示
没什么需要修改的,直接点击Finish完成添加。
然后修改名字,完成后,如下图所示,其实修不修改都可以,只是为了看起来简单而已
修改好以后,我们还有一步需要处理,双击cpu,点击后,如下图所示红圈处,我们要memory选择为epcs,也就是说,上电后,读取epcs中
的数据。大家记得我在讲并行FLASH的时候讲的,这个地方应该选择CFI_FLASH吧,也就是说,上电或复位以后,你需要NIOS读取哪里的数据,就
选择哪一个。因为我们要实现从EPCS中读取数据,所以我们这里选择epcs。
上面都完成了以后,接下来,就是自动配置地址,中断,编译,等待,编译,等待,硬件部分的工作就结束了。
软件设置
下面我们来看看在NIOS 9.0
IDE中都需要进行哪些配置。在这里,我们假设我们需要下载的程序都已经编译成功。那么我们就开始下载程序到EPCSx中,点击下图所示红圈处,
点击后,如下图所示,其中需要将红圈1、2、4选中,红圈3不用选,选中红圈1是将NIOS软件程序写到FLASH中,而到底是写到哪里,是由前面
我们讲过的CPU中Reset
Vector的memory决定的。也就是说,我们之前选择了epcs,那么我们就是将代码下载到EPCSX中了。选中红圈2是将FPGA的配置文件下载
到FLASH中,而这里到底下载到哪是由红圈5处决定的,我们在这里还是选择epcs,就是就将配置文件下载到EPCSX中。其实EPCSX实质就是一种
串行的FLASH。再说说红圈3,红圈3是将文件下载到flash中,比如说字库文件啊,波形文件啊等等,将这些文件直接存储到FLASH中,只需要读取
就可以了。不过这个选项跟我们下载配置文件和NIOS程序时没有关系的。所以这里不选择它。红圈4的作用我以前已经说过了,在这里不重复了。
上面选择好以后,点击Apply,然后点击Program Flash,就可以开始烧写FLASH了。
至此,如何将FPGA配置文件和NIOS的程序下载到EPCSx(x为1,4,16…)里面的过程就讲完了。很简单吧,大家只要稍加注意就可以了。
好了,这一节的内容就讲完了,有问题的可以给我直接留言,或者加入我的qq:984597569,或者加入最新的
群:109711029(107122106已满),当然也可以发邮件给我:avic633@gmail.com,
谢谢大家支持。
用户333178 2011-6-14 10:00