FPGA 之 SOPC 系列(五)Nios II 软件使用与程序开发 I

2020-12-30 10:50:11 浏览数 (1)

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。

今天给大侠带来今天带来FPGA 之 SOPC 系列第五篇,Nios II软件使用与程序开发 I,希望对各位大侠的学习有参考价值,话不多说,上货。

本篇首先简单介绍Nios II IDE开发环境的使用;然后重点介绍了硬件抽象层(HAL)系统库,包括HAL下的基本应用程序开发和实操训练,包括了字符型外设的应用和中断机制的实现。

以下为本篇的目录简介:

5.1 Nios II IDE简介

5.2 设置工程系统库属性和编译选项

5.3 调试/运行程序

5.4 下载程序到Flash

5.5 使用HAL开发应用程序

5.6 UART-JTAG开发和实验

5.7 LCD开发和实验

5.8 SYSTEM ID实验

5.9 中断机制和软件调试

5.1 Nios II IDE简介

Nios II IDE为软件开发提供4个主要功能:

一、工程管理

二、编辑器和编译器

三、调试器

四、闪存编程器

统一开发平台,用于所有Nios II处理器系统。

?

一、工程管理器

1、新工程向导

[File]→[New]→[C/C Application]

PIO内核结构框图

2、软件工程模板

PIO内核结构框图

3、软件组件

  • 硬件抽象层系统库(HAL)
  • 轻量级IP TCP/IP库
  • uC/OS-II实时操作系统(RTOS)
  • Altera压缩文件系统

?

二、编辑器和编译器

1、文本编辑器

成熟的全功能源文件编辑器,包括:语法高亮显示C/C 程序代码、全面的搜索工具、文件管理、在线帮助和教程、快速定位及自动纠错、内置调试等功能。

2、C/C 编译器

Nios II IDE使用GCC编译器,并为其提供了一个图形化用户界面。图形化用户界面为GCC编译器提供了一个易用的按钮式流程,同时允许开发人员手工设置高级编译选项,使得操作更简单方便。

?

三、调试器

软件调试器(GDB):是强大的、在GNU调试器基础之上的软件调试器。该调试器提供许多基本调试功能以及一些在低成本处理器开发套件中不会经常用到的高级调试功能。

基本调试功能包括:运行控制、调用堆栈查看、软件断点、反汇编代码查看、调试信息查看、指令集仿真器。

高级调试功能包括:硬件断点调试ROM或闪存中的代码、数据触发、指令跟踪。

?

四、闪存编程器

多数使用Nios II处理器的设计都需要采用闪存(Flash)来存储FPGA配置数据和/或应用程序。Nios II IDE提供了一个方便的闪存编程方法。任何连接到FPGA的兼容通用闪存接口(CFI)的闪存器件以及主动串行配置器件EPCS都可以通过Nios II IDE闪存编程器来烧写。

5.2 设置工程系统库属性和编译选项

C/C Build 设置:

右击C/C 工程文件夹→[System Library Properties]

C/C Indexer设置:

右击C/C 工程文件夹→[System Library Properties]

System Library 设置:

右击C/C 工程文件夹→[System Library Properties]

5.3 调试/运行程序

选择Debug的目标器件:

  • Nios II硬件
  • Nios II指令仿真器
  • Nios II多处理器集

对话框操作:[RUN]→[Debug] /[Debug As]

调试器目标连接设置--(Target Connection):

对话框操作:[RUN]→[Debug] →[Nios II Hardware] →Target Connection

JTAG cable:进行JTAG下载电缆的选择。

JTAG device:进行连接在JTAG下载电缆上带JTAG接口器件的选择。

Nios II Terminal…:进行Nios II系统中断通信工具选择。

调试器设置--(Debugger):

对话框操作:[RUN]→[Debug] →[Nios II Hardware] →Debugger

调试器设置--(Debugger 视窗模式):

对话框操作:Nios II IDE 切换到Debug视窗模式

运行程序--(Debugger 视窗模式)

对话框操作:[RUN]→[RUN] /[RUN As]

5.4 下载程序到Flash

当调试工作完成并确保程序无错后,就可以把程序下载到Flash中了。

5.5 使用HAL开发应用程序

Nios II IDE 工程结构:

Nios II IDE工程结构

一个Nios II IDE工程

与目标系统相关的system.h系统描述文件:

System.h文件构成过程

程序清单5.1 system.h描述的UART设备:

数据宽度及HAL类型定义:

表5.1 HAL数据类型定义

表5.2 Altera提供的GNU编译器下的ANSI C数据类型宽度

5.6 UART-JTAG开发和实验

实验目的:

  • 学习使用UART-JTAG通信功能
  • 掌握NIOSII软件开发流程
  • 熟识NIOSII开发环境使用
  • 学会利用UART-JTAG进行软件调试

使用UART-JTAG可以方便的通过下载线建立主机与SOPC的联系,这种串口方式把复杂的硬件驱动隐藏,从代码的角度来看我们可以很方便操作。

在ANSI C库的支持下,用户既可以把JTAG UART设备当作标准I/O设备使用,也可以将其当作文件操作。其实质是通过ANSI C库函数调用JTAG UART设备驱动函数访问硬件设备。

通过上面的设置,将器件JTAG_UART的输入输出映射到STDOUTSTDERRSTDIN等,那么我们就可以利用下面这些函数,操作JTAG_UART。

  • 函数原型:int fopen (char * file_name, way_use);
  • 输入参数:file_name文件名,way_use使用文件方式,比如r,w分别对应着读写
  • 函数说明:打开文件,对其进行某种文件操作
  • 返回值:打不开则出错,返回一个空指针NULL
  • 函数原型:int fclose (fp)
  • 输入参数:fp的定义为:FILE *fp函数说明:关闭文件fp返回值:成功返回0,反之为-1(EOF)
  • 函数原型:int fread(void *ptr, int size, int count, FILE * fp);
  • 输入参数:buffer为指针;是读入数据的存放地址;size读字节数;count读字节数的数目;fp文件型指针
  • 函数说明:从一个流中读取数据
  • 返回值:成功返回值为count
  • 函数原型:int fwrite(void *ptr, int size, int count, FILE *fp)
  • 输入参数:buffer为指针;是读入数据的存放地址;size读字节数;count读字节数的数目;fp文件型指针,
  • 函数说明:写内容到流中返回值:成功返回值为count
  • 函数原型:int fprintf(FILE *fp, char *format[, argument,...]);
  • 输入参数:fp文件型指针;format格式字符串;[, argument,...]输出列表,如:
  • fprintf(fp,“%d,%f”,i,t)
  • 函数说明:传送格式化输出到一个流中
  • 返回值:-
  • 函数原型:int fscanf(FILE * fp, char *format[,argument...])
  • 输入参数:fp文件型指针;format格式字符串;[, argument,...]输入列表,如:
  • fscanf(fp,“%d,%f”,i,t)
  • 函数说明:从一个流中执行格式化输入
  • 返回值:-
  • 函数原型:int fputc(int ch, FILE *fp)
  • 输入参数:ch字符;fp:文件型指针
  • 函数说明:送一个字符到一个流中
  • 返回值:成功返回字符,反之返回-1(EOF)
  • 函数原型:int fgetc(FILE *fp);
  • 输入参数:fp:文件型指针
  • 函数说明:从流中读取字符
  • 返回值:遇到文件结束返回-1(EOF)
  • 函数原型:int putw(int w, FILE *fp)
  • 输入参数:w: 字符或字; fp:文件型指针
  • 函数说明:把一字符或字送到流中
  • 返回值:-
  • 函数原型:int getw(FILE *fp)
  • 输入参数:fp:文件型指针
  • 函数说明:从流中取一整数
  • 返回值:-
  • 函数原型:int rewind(FILE *fp)
  • 输入参数:fp:文件型指针
  • 函数说明:将文件指针重新指向一个流的开头
  • 返回值:-
  • 函数原型:int fseek(FILE *fp, long offset, int fromwhere);
  • 输入参数:fp:文件型指针;offset:long型偏移量;fromwhere:起始点
  • 起始点为0,1,2分别代表文件开始,当前位置,文件末尾
  • 函数说明:重定位流上的文件指针
  • 返回值:-
  • 函数原型:int ferror(FILE *fp)
  • 输入参数:fp:文件型指针
  • 函数说明:检测流上的错误
  • 返回值:未出错返回值为0,反之为非0
  • 函数原型:long ftell(FILE *fp)
  • 输入参数:fp:文件型指针
  • 函数说明:返回当前文件指针,得到当前位置
  • 返回值:返回值为-1表示出错,反之为非0
  • 函数原型:void clearerr(FILE *fp)
  • 输入参数:fp:文件型指针
  • 函数说明:复位错误标志
  • 返回值:出错为非0,反之为0
  • 函数原型:char *fgets(char *string, int n, FILE *fp)
  • 输入参数:string:字符串指针;fp:文件型指针
  • 函数说明:从流中读取一字符串,但只从文件输入n-1个字符,后一个为‘’结束标志位
  • 返回值:-
  • 函数原型:nt fputs(char *string, FILE *fp)
  • 输入参数:string:字符串指针;fp:文件型指针
  • 函数说明:送一个字符串到一个流中
  • 返回值:-
  • 函数原型:int feof(FILE *fp)
  • 输入参数:fp:文件型指针
  • 函数说明:检测流上的文件结束符
  • 返回值:-

在上面红色部分设置好后,在CONSOLE窗口可以显示PC方的内容。

例程一:

代码语言:javascript复制
#include <stdio.h>//字符操作库
 
int main()
{
    printf("hahahaha");
    return 0;
}

扩展,利用函数:fopen、getc、fwrite、fprintf、fclose

完成一个识别键盘字符V和T的程序,要求识别到T后,利用fwrite打印出关于识别T的信息,识别到V后关闭JTAG,打印出V的信息,程序停止。

5.7 LCD开发和实验

The LCD controller core consists of two user-visible components:

  • Eleven signals that connect to pins on the Optrex 16207 LCD panel—These signals

are defined in the Optrex 16207 data sheet.

  • E—Enable (output)
  • RS—Register Select (output)
  • R/W—Read or Write (output)
  • DB0 through DB7—Data Bus (bidirectional)
  • An Avalon Memory-Mapped (Avalon-MM) slave interface that provides access to 4 registers

同样的LCD显示也是用写文件的方式(LCD只有输出)。如下例子:

代码语言:javascript复制
#include <stdio.h>
#include <string.h>
#include "system.h"
#define ESC 27
#define ESC_TOP_LEFT "[1;0H" 
#define ESC_BOTTOM_LEFT "[2;0H" 
int main( void )
{
  FILE *lcd;
  lcd = fopen("/dev/lcd", "w");
  fprintf(lcd, "%c%shello red logicnLCDDisplaying.",ESC,ESC_BOTTOM_LEFT); 
  printf("If you can see messages scrolling on the LCD Display, then it is functional!n");
  usleep(2000000);
  putchar('a');
  fclose( lcd );
}

扩展:将实验一中,通过键盘向JTAG-UART输入的字符 V T输出到LCD中。

5.8 SYSTEM ID实验

实验目的:

通过实验了解SYSTEM ID的意义和作用。

实验说明:

SOPC BUILDER生成该系统的时候,将为每一个NIOS系统生成一个标志符。该标志符会被填入SYSTEM ID寄存器中,供编译器和用户辨别所运行的程序是否与目标系统匹配。当程序运行在与之不匹配的系统上会出现错误。

使用系统ID有两种基本方法:

  • 其一,下载新的程序到硬件上时,检查硬件中的系统ID与软件是否匹配,若不匹配则终止下载。
  • 第二复位检查系统ID

实现方法:

访问SYSTEMID的HAL函数为ALT_AVALON_SYSID_TEST(),该函数返回一个值来指示软件期望的系统ID是否匹配。使用头文件是<altera_avalon_sysid.h>

相匹配返回0,硬件时间标记大于软件标记返回1,软件时间标记大于硬件时间标记返回-1。

利用JTAG-UART功能将 SYSTEM ID功能加入实验中,要求程序运行首先检查ID,根据不同情况打印不同信息。

5.9 中断机制和软件调试

中断定义:

在特定的事件(中断源,也称中断请求信号)触发下引起CPU暂停正在运行的程序(主程序),转而先去处理一段为特定事件而编写的处理程序(中断处理程序),等中断处理程序处理完成后,再回到主程序被打断的地方继续运行。

NIOS Ⅱ 中断类型:

  • NIosⅡ处理器提供以下中断类型:硬件中断异常和软件异常。软件异常又分为未定义指令、软件脚指令和其他异常。
  • 硬件中断是由外部中断源通过处理器的32个中断输入请求硬件中断。当且仅当满足以下条件,就会产生中断:

(1)status寄存器(ctl0)的PIE位为l;

(2)有一个中断请求输入irq产生;

(3) ienable寄存器(ctl3)的响应位n为l。

  • 软件tap:通过执行一条TRAP指令,软件可直接请求将控制传送到中断处理程序。
  • 未定义指令:当处理器执行一条在硬件中没定义的有效指令时,即可生成一个未定义的指令中断。

中断控制机制:

Nios II 的中断处理方式带有典型的RISC处理器的特征,所有的中断处理都从同一入口进入,然后由软件加以分配。负责分配工作的软件叫系统ISR,它是由开发系统提供的,自动的连接到可执行程序上。系统ISR维护着一个中断向量表,表中的每一项代表着一个专项处理程序的入口。所有的专项处理程序都是由用户定义然后注册到中断向量表中的,叫做用户ISR。系统ISR的入口地址是在SOPC_Builder中定义的,叫Exception Address。和中断有关的CPU寄存器有:ctl0、ctl1、ctl3、ctl4。Ctl0 是程序状态字,它的bit0位是全局中断允许位,1代表允许,0代表禁止。Ctl1是程序状字的堆栈,当发生中断时,由它保留一个程序状态字的备份。Ctl3是中断允许寄存器,其中每一位控制着一个中断源,1代表允许,0代表禁止,共计32位。Ctl4是中断申请寄存器,每一位对应着一个中断源的中断请求,1代表有中断,0代表没有……计32位。

NiosII的中断处理过程:

  • 拷贝一份程序状态字到ctl1;
  • 清除全局中断允许位PIE,禁止中断;
  • 将下一条将执行的指令的地址存入R29,以便中断返回之用;
  • 跳转到中断入口地址,进入系统ISR;
  • 系统ISR保护现场;
  • 系统ISR检测ctl1的PIE位,如为0则进入软中断处理程序从11继续,否则由7继续;
  • 系统ISR检测Ctl4,如果有中断申请,则转到硬中断处理和序,否则进入软中断处理程序;
  • 硬中断处理程序将检测中断申请号,并检索中断向量表,跳转到用户中断处理程序;
  • 用户中断处理程序做出具体的处理,最后返回系统ISR;
  • 系统ISR恢复现场,并返回;
  • 软中断处理程序进行陷井指令、模拟指令判断,并做相应处理,然后返回系统ISR;
  • 系统ISR恢复现场并返回

软中断处理程序是用来处理由软件发起的中断事件的,包括调试指令引起的中断及未定义指令引起的中断。目前未定义指令的处理主要为乘、除法运算指令的处理,不支持用自定义的操作码,除用户自己修改系统程序。如果软中断处理程序遇到了一个不识别的操作码,将返回一个不确定的结果。

完成中断的步骤:

1、注册中断函数ISR,它的函数原型如下:

Int alt_irq_register(alt_u32 id, void* context, void(*handler) (void*,alt_u32));

id:中断优先级,即所注册的ISR是为哪个中断优先级的中断服务的

Context,为所注册的ISR传递参数,可以是NULL;

Handler,中断服务函数ISR的指针。

返回值是0时,表示中断注册成功;返回为负数,表明中断注册失败。这里面有一个需要注册的地方,如果handler不是NULL,则该优先级中断在注册成功后将自动使能,也即是说,只要我们在handler处有相应的ISR,我们就不需要再进行使能处理了。

2、编写ISR函数,这个函数由我们自己来写,而不是HAL系统提供的。它跟一般的函数定义没什么区别,只是对ISR的函数原型有特定的要求: void ISR_handler( void* context, alt_u32 id );

context: 传给ISR的形参,可以是NULL;

id: 中断优先级

举例说明:

代码语言:javascript复制
void    ISR_key(void *context,unsigned long id)
{
        IOWR_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_BASE,0x0);
        key_flag  ;
            IOWR_ALTERA_AVALON_PIO_IRQ_MASK(BUTTON_BASE,0x0);
}


int init_key(void)
{
    IOWR_ALTERA_AVALON_PIO_IRQ_MASK(BUTTON_BASE,0xf);
    IOWR_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_BASE,0x0);
    return alt_irq_register(BUTTON_IRQ,NULL,ISR_key);
}

大体上的思路是,先判断初始化是否成功,如果init_key()函数返回值是0函数返回值是0,说明注册成功,打印register。

1、alt_irq_register()是向系统ISR注册用户ISR的API函数。其原形为:int alt_irq_register( alt_u32 id,void *context,void (* isr)(void *, alt_u32)) id 代表被服务的中断向量号; context 是运行参数指针,将来作为第一个参数传给用户ISR; Isr 是一个函数指针,指向用户ISR入口;如果注册成功,函数返回0,并允许全局中断及被服务中断;不成功返回非0值。

2、alt_irq_disable()用来禁止某个中断服务。

原形为:Int alt_irq_disable(alt_u32 id)

Id 为对应的中断号;

返回值为0;

3、alt_irq_enable()与alt_irq_disable()对应,用来开启某个中断服务。

原形为:int alt_irq_enable(alt_u32 id)

4、alt_irq_disable_all()用于关闭全局中断,

原形为:alt_irq_context alt_irq_disable_all(void)

返回值为中断控制寄存器的值。

5、alt_irq_enable_all()用于开启全局中断,

原形为:void alt_irq_enable_all( alt_irq_context context)

context 代表中断控制寄存器的值

FPGA 之 SOPC 系列第五篇就到这里结束,下一篇将带来第六篇,Nios II 程序开发II 等相关内容。各位大侠,明天见!

END

后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。

大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!

0 人点赞