目录
2.创建BSP工程
2.1 创建BSP工程
2.2 BSP Editor
2.3 创建C代码文件
3.Nios Ⅱ实例
3.1 Hello NIOS Ⅱ
3.2 System ID与Timestamp
3.3 蜂鸣器定时鸣叫
3.4 拨码开关输入GIO控制
4.FPGA器件的代码固化
4.1 嵌入式软件HEX文件生成
4.2 程序存储器初始化文件加载
4.3 JIC文件生成和烧录配置
进行Nios Ⅱ嵌入式软件开发
点击Tool->Nios Ⅱ Software Buid Tools for Eclipse,创建工作空间
点击File->New->Nios Ⅱ Application and BSP from Template新建项目
点击Target hardware information中的[...] 按钮选择Quartus工程下的.sopcinfo文件,Nios II SBT for Eclipse软件会自动识别Qsys系统中CPU的名称
然后给创建的 Nios Ⅱ工程命名(名称需要以英文字母开头,可以包含字母、数字和下划线,不能有中文及特殊字符)
在取消勾选Use default location后可以修改然后将工程存放的位置
在Project template一栏中列出了一些工程模板,也可以选择创建空白工程 (Bland Project)
点击下方Next系统会自动创建一个BSP(Board Support Package,板级支持包)工程,提供了访问底层硬件(Qsys 系统)的函数库
.sopcinfo文件是Qsys系统生成时一同产生,包含了所有Qsys系统的硬件信息,将它导入到BSP工程使得BSP获得全部硬件信息
在Project Explorer下出现了nios2bsp_bsp工程,可以看到这个文件夹下包含了各种和当前Qsys系统相关的板级动源文件和头文件,供应用软件调用。
头文件system.h将Qsys系统中的 Nios Ⅱ处理器和所有外设的名称、基地址、中断有无以及优先级号码等相关硬件信息进行了定义。
其中nios2bsp是C/C++应用工程,nios2bsp_bsp是函数库
在BSPEditor中可以对板级驱动层进行一些定制化的配置,比如代码裁剪、标准输入/输出外设和定时器外设的设置等。
在Project Explorer下工程名右键点击Nios Ⅱ->BSP Editor
在左侧一栏选中Common,然后在右侧勾选两个选项:
enable_reduced_device_drivers: BSP为处理器的外设提供了两个版本的驱动库:一种是执行速度快但代码量比较大的版本:另一种是封装小的版本。默认使用的是代码量大的版本,这里通过[enable reduced device drivers]选项来选择封装小的版本,从而减少代码量。
enalbe_small_c_library: 完整的ANSIC标准库通常不适用于嵌入式系统,BSP提供了一系列经过裁剪的ANSIC标准库,占用资源比较少,通过[enalbe small c lbrary]选项来选择精简的ANSIC标准库。
在左侧一栏选中Settings,将右侧两个选项(默认勾选) 取消勾选:
enable_c_plus_plus: 使用 C 语言来编写软件程序,因此不需要使能 C+。
enable clean exit: 当选中该选项时,系统库在主函数main()返回时会调用exit()。调用 exit0时,首先会清理I/O的缓冲区,然后再调用exit()。当不选中该选项时,系统库会只调用exit(),这样将会节省程序空间。对于嵌入式系统程序来说,一般都不会从 main()返回,所以可以不勾选该选项。
设置完成后先点击Generate然后再点击Exit
在Project Explorer下工程名右键点击New->Source File新建C代码源文件
在Project Explorer下工程名右键Build Project (CTRL+B)进行软件工程编译
通过JTAG UART在Nios Console中每隔3s打印一串“Hello NIOS II”的字符串
#include "system.h" //定义Qsys中各个外设的地址、中断优先级等基本硬件信息
#include //定义标准输入、输出函数
#include //包含了延时函数usleep()函数的生命
///
//功能:每隔3s通过JTAG UART打印一条字符串“Hello NIOS Ⅱ”
///
int main(void)
{
while(1)
{
printf("Hello NIOS II!\n");
usleep(3000000);
}
return 0;
}
printf对应的设备在BSP Editor中设定
下载验证
先把.sof文件下载到开发板中,下载完成后开发板上没有任何实验现象
再下载.elf 文件(注意一定要先下载 sof 文件,再下载 elf 文件),Nios Ⅱ SBT for Eclipse中工具栏Run->Run AS->Nios II Hardware,将编译生成可执行文件.elf下载到硬件系统中。
如果在程序下载的过程中弹出了“Run Configurations”窗口,提示找不到与Nios II硬件系统的连接
那么在这个窗口中点击Target Connection标签,点击Refresh Connections按钮,软件使会自动识别开发板上的 Qsys 系统,并显示 Qsys 系统的相关信息,接着点击[Apply] [Run],软件会把.elf 文件下载至开发板中
下载结束后,程序自动开始运行,在软件下方的“Nios II Console”中会打印“Hello,Nios Ⅱ!”信息
读取 System ID外设的两个寄存器值,一个是id 值,另一个是Testamp值
地址偏移 | 寄存器名称 | 读/写 | 功能描述 |
0 | id | 读 | 基于Qsys系统定义的唯一的32位数值,该id值类似于校验和;不同组件、不同选项配置的Qsys系统产生不同的id值 |
1 | timestamp | 读 | 基于系统生成时间的唯一32位值,该值等效于从1970年1月1日以来所经过的总秒数 |
#include "alt_types.h" //对altera定义的数据类型进行宏定义声明,alt_u32表示32位的无符号整型
#include "system.h"
#include
#include "altera_avalon_sysid_qsys_regs.h" //定义了System ID硬件寄存器访问的接口函数
///
//功能:读取System ID的ID值核timestamp值,通过JTAG UART打印
///
int main(void)
{
//读取%sy_id值
alt_u32 hardware_id = IORD_ALTERA_AVALON_SYSID_QSYS_ID(SYSID_BASE); //读取定义的System ID外设的id值,SYSID_BASE是定义的System ID外设的基址
//读取%sy_id的timestap值
alt_u32 hardware_timestamp = IORD_ALTERA_AVALON_SYSID_QSYS_TIMESTAMP(SYSID_BASE); //读取定义的System ID外设的Timestamp值
printf("System ID is 0x%8x\n",hardware_id);
printf("System timestamp is 0x%8x\n",hardware_timestamp);
while(1);
return 0;
}
使用Timer定时器产生秒中断信号,驱动蜂鸣器发出响声
Timer 定时器组件内部有可编程的32位定时计数器,通过编程访问该组件的控制和状态寄存器实现定时中断功能。
Timer定时器外设寄存器定义
地址偏移 | 寄存器名称 | 读/写 |
功能描述 |
0 | status | 读写 | bit 15~2:保留 bit 1:运行指示位。当计数寄存器运行时,该位置高。 bit 0:定时结束指示位。当计数回零时,该位置高。一旦定时结束事件发生,该位置高,直到对该寄存器读写后该位清零 |
1 | control | 读写 | bit 15~4:保留 bit 3:停止位。该位写1将停止当前定时计数功能。 bit 2:启动位。该位写1将启动定时计数功能。 bit 1:运行指示位。该位置1后,计数器清零后将继续计数,即执行连续计数;该位清0后,计数器清零后将停止计数,即只执行一次计数。 bit 0:中断表示位。该位提高后,一旦status寄存器的bit 0拉高,即产生IRQ中断。 |
2 | periodl | 读写 | 定时计数周期值-1(高16位) |
3 | periodh | 读写 | 定时计数周期值-1(低16位) |
4 | snapl | 读写 | 当前计数值(高16位) |
5 | snaph | 读写 | 当前计数值(低16位) |
#include "alt_types.h"
#include "altera_avalon_pio_regs.h" //声明PIO外设函数,如IOWR_ALTERA_AVALON_PIO_DATA()函数
#include "altera_avalon_timer_regs.h" //声明Timer定时器外设函数,如IOWR_ALTERA_AVALON_TIMER_STATUS()/CONTROL()
#include "sys/alt_irq.h" //声明中断相关的函数,如all_irq_register
#include "system.h"
int flag,second;
///
//功能:Timer定时器初始化函数
///
void init_timer(void)
{
//注册定时器中断函数
//TIMER_IRQ是system.h中定义的Timer定时器组件的中断号,TIMER_BASE是Timer定时器组件的基址,handle_time_interrupts为中断函数
alt_irq_register(TIMER_IRQ,TIMER_BASE,handle_time_interrupts);
//对Timer定时器组件的control寄存器写数据7,即启动timer定时计数功能,持续循环计数产生IRQ中断中断
IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER_BASE,7);
//清除标志位
flag = 0;
//初始化函数没有对periodl和periodh寄存器进行设置,使用默认的计数值,即在Qsys中定义的1s计数周期
}
///
//功能:秒定时中断处理函数
///
static void handle_time_interrupts(void)
{
//函数写Timer定时器组件的status寄存器,即清TO标志
IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER_BASE,0);
flag = 1;
second++;
}
///
//功能:秒定时中断处理函数
///
int main(void)
{
IOWR_ALTERA_AVALON_PIO_DATA(PIO_BEEP_BASE,0); //拨码开关OFF
init_timer(); //Timer定时器初始化函数
while(1)
{
if(flag)
{
flag = 0;
//该函数对基址为PIO_BEEP_BASE的PIO外设写数据1/0
if(second & 0x01) IOWR_ALTERA_AVALON_PIO_DATA(PIO_BEEP_BASE,1); //拨码开关ON
else IOWR_ALTERA_AVALON_PIO_DATA(PIO_BEEP_BASE,0); //拨码开关OFF
}
}
return 0;
}
使用4个拨码开关产生中断,对不同拨码开关值进行判断,相应驱动蜂鸣器发出1~4此响声
PIO组件寄存器定义
地址偏移 | 寄存器名称 | 读/写 |
功能描述 |
0 | data | 读写 | 作为输入PIO时,读控制获取当前输入PIO的电平值; 作为输出PIO时,写控制将数据输出到PIO上 |
1 | direction | 读写 | 每个PIO引脚单独的方向控制。电平0设置PIO为输入;电平1设置PIO为输出 |
2 | interruptmask | 读写 | 每个PIO引脚对应的IRQ中断使能。电平1设置IRQ中断使能 |
3 | edgecapture | 读写 | 每个PIO引脚的边沿变化状态捕获 |
#include "alt_types.h"
#include "altera_avalon_pio_regs.h"
#include "sys/alt_irq.h"
#include "system.h"
#include
#include
#include
/
//函数申明
void init_switch_pio(void); //switch GIO初始化函数
void beep_didi(alt_u8 time); //蜂鸣器发出“滴滴”响声函数
/
//宏定义
/
//变量申明
alt_u8 edge_capture_value; //当前拨码开关值
/
//功 能: 拨码开关中断服务函数
/
static void handle_switch_interrupts(void)
{
//捕获当前PIO值
edge_capture_value = IORD_ALTERA_AVALON_PIO_DATA(PIO_SWITCH_BASE);
//清除边沿中断标志位
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(PIO_SWITCH_BASE,0x00);
}
/
//功 能: 主函数,拨码开关从off到on拨动时,蜂鸣器发出几声清脆的“滴”响声
/
int main(void)
{
IOWR_ALTERA_AVALON_PIO_DATA(PIO_BEEP_BASE,0); //beep off
init_switch_pio(); //switch GIO初始化函数
while(1)
{
if(~edge_capture_value & 0x01) //蜂鸣器发出1声“滴”
{
beep_didi(1);
}
else if(~edge_capture_value & 0x02) //蜂鸣器发出2声“滴”
{
beep_didi(2);
}
else if(~edge_capture_value & 0x04) //蜂鸣器发出3声“滴”
{
beep_didi(3);
}
else if(~edge_capture_value & 0x08) //蜂鸣器发出4声“滴”
{
beep_didi(4);
}
}
return 0;
}
/
//功 能: switch GIO初始化函数
/
void init_switch_pio(void)
{
//初始化拨码开关状态值
edge_capture_value = 0xff;
//使能3个拨码开关中断
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(PIO_SWITCH_BASE, 0xff);
//复位拨码开关边沿状态
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(PIO_SWITCH_BASE, 0x00);
//拨码开关输入GIO中断复位申明
alt_irq_register(PIO_SWITCH_IRQ,PIO_SWITCH_BASE,handle_switch_interrupts);
}
/
//功 能: 蜂鸣器发出“滴滴”响声函数
//参 数: alt_u8 time 发出响声的次数
/
void beep_didi(alt_u8 time)
{
alt_u8 i;
for(i=0;i
其他实例可参照参考书籍进行
基于Nios Ⅱ处理器的FPGA器件代码固化和一般只有 FPGA 逻辑的代码固化不同
除了正常的FPGA逻辑部分代码需要固化外,还有Nios Ⅱ处理器的程序代码也需要固化。
使用FPGA片内存储器作为Nios Ⅱ处理器的代码存储器,在生成逻辑代码部分的烧录文件时,将 Nios Ⅱ处理器的代码集成在一起,就可以使用一个烧录文件完成 FPGA 器件的固化
打开工程的BSP Editor页面,选择Setting->Advanced->hal->linker,勾选allow_code_at_reset选项,然后重新编译C/C++软件工程文件和函数库文件
设置该选项的目的是在确保FPGA使用了片内存储器作Nios Ⅱ处器代码存储器时,在FPGA逻辑运行起来以后,Nios Ⅱ处理器复位后直接可以从片内存储器开始运行代码
然后右键软件工程文件->Make Targets->Build,选择men_init-generate,点击Build
然后就可以在软件工程文件夹下发现.hex文件,为软件工程代码对应的十六进制HEX文件,即Nios Ⅱ处理器的软件代码
进入Qsys界面,双击onchip_men组件进入配置界面的Memery initialization
勾选Initialize memory content和Enable non-default initialization file,并在User created initialization file中添加刚才生成的.hex文件
重新生成Qsys工程并且重新编译整个Quartus工程并产生新的.sof 文件
此时.sof 文件通过JTAG在线烧录到FPGA器件中则会直接运行软件程序
但.sof 文件只能在线烧录,因此需要将.sof文件转换为jic文件,实现SPI Flash的固化
在Quartus中选择File->Convert Programming Files
在Programming file type中选择JTAG Indirect Configuration File(.jic)
Configuration device选择配置设备,File name输入转换后的文件名(output_files 文件夹下)
在Input files to convert中
单击Flash Loader所在的行,然后单击右侧Add Device按钮在Select Devices 窗口中选择设备
单击SOF Data所在行,然后单击右侧的Add File按钮,在弹出的窗口中选择output files文件夹下的 .sof文件
完成设置后,单击 Generate 生成 *.jic,弹出如图所示的提示信息,表示成功生成jic文件
打开 Quartus的 Programmer 页面,单击Add File按钮加载的jic 文件(File下若有其他文件,请删除),并且确保勾选 Program/Configure 所在列
单击 Start 按钮执行下载操作,完成下载后,开发板默认处于不工作状态,需要重启开发板,重启后就能看到最新下载的代码已经固化到 SPI Flash 中并且掉电后重启仍然可以运行。