1.02k likes | 1.59k Views
第 7 章 Nios II 常用外设编程. 本章介绍了 Nios II 处理器常用外围设备 (Peripherals) 内核的特点、配置以及软件编程,供大家在使用这些外设定制 Nios II 系统时查阅。这些外设都是以 IP 核的形式提供给用户的 , 用户可以根据实际需要把这些 IP 核集成到 Nios II 系统中去。 主要介绍: 硬件结构; 内核的特性核接口; SOPC Builder 中各内核的配置选项; 软件编程。. 主要内容. 7.1 并行输入 / 输出内核.
E N D
本章介绍了Nios II处理器常用外围设备(Peripherals)内核的特点、配置以及软件编程,供大家在使用这些外设定制Nios II系统时查阅。这些外设都是以IP核的形式提供给用户的,用户可以根据实际需要把这些IP核集成到Nios II系统中去。 主要介绍: 硬件结构; 内核的特性核接口; SOPC Builder中各内核的配置选项; 软件编程。 主要内容
7.1 并行输入/输出内核 并行输入/输出内核(PIO内核①)提供Avalon从控制器端口和通用I/O口②间的存储器映射接口。PIO内核提供简单的I/O访问用户逻辑或外部设备,例如: 控制LED 读取开关量 控制显示设备 配置并且与片外设备通信 说明: • SOPC Builder中提供了PIO内核,可以很容易将PIO内核集成到SOPC Builder生成的系统中。 • 通用I/O端口既连接到片内逻辑又连接到外部设备的FPGA I/O管脚。
7.1 并行输入/输出内核 PIO内核简介 PIO 内核 PIO 内核 PIO 内核 每个Avalon接口的PIO内核可提供32个I/O端口且端口数可设置,用户可以添加一个或多个PIO内核。CPU通过I/O寄存器控制I/O端口的行为。I/O口可以配置为输入、输出和三态,还可以用来检测电平事件和边沿事件。 Nios II 系统 CPU通过寄存器控制I/O端口行为 寄存器 CPU 内核 端口数可设置 Pio[7] Pio[6] Pio[5] Pio[4] Pio[3] Pio[2] Pio[1] Pio[0] Pio[31] Pio[30] Pio[29] Pio[3] Pio[2] Pio[1] Pio[0] 最多32个I/O端口 PIO内核结构框图
7.1 并行输入/输出内核 PIO内核寄存器描述 CPU读/写PIO接口的映射寄存器控制PIO的各个端口 四个寄存器: data、direction、interruptmask和edgecapture “①该寄存器是否存在取决于硬件的配置。如果该寄存器不存在,那么读寄存器将返回未定义的值,写寄存器无效。” 注: ① 该寄存器是否存在取决于硬件的配置。如果该寄存器不存在,那么读寄存器将返回未定义的值,写寄存器无效。 ② 写任意值到边沿捕获寄存器将清除所有位为0。
7.1 并行输入/输出内核 - PIO 内 核 配 置 选 项 双击
7.1 并行输入/输出内核 - PIO 内 核 配 置 选 项 I/O口宽度:可设置为1~32的任何整数值。 Basic Settings 选项卡
7.1 并行输入/输出内核 - PIO 内 核 配 置 选 项 Basic Settings 选项卡
7.1 并行输入/输出内核 - PIO 内 核 配 置 选 项 Rising Edge:上升沿 Falling Edge:下降沿 Either Edge: 上升或下降沿 边沿捕获寄存器 说明:当指定类型的边沿在输入端口出现时,边沿捕获寄存器对应位置1。 Level:输入为高电平且中断使能, 则PIO内核产生一个IRQ。 Edge:边沿捕获寄存器相应位为1且中断使能,则PIO内核产生一个IRQ。 中断寄存器 说明:中断只有高电平中断,如果希望低电平时中断,则需在该I/O输入引脚前加一个“非”门。 Input Options 选项卡
7.1 并行输入/输出内核 - PIO 内 核 配 置 选 项 当需要对外进行仿真时,要设置simulation选项卡。 Simulation 选项卡
7.1 并行输入/输出内核 软件编程 Altera_avalon_pio_regs.h PIO内核提供了对硬件进行寄存器级访问的文件。 该文件定义了内核的寄存器映射并提供硬件设备访问宏定义。可通过阅读上述文件以熟悉PIO设备的软件访问方法,但不应该修改文件。
3. 软件编程 • PIO内核不是HAL支持的通用设备模型,不能通过HAL API或ANSI C 标准库来访问 • Altera提供了PIO的寄存器头文件altera_avalon_pio_regs.h。 文件定义其寄存器映射,提供了对底层硬件的符号化访问方法。 • PIO 内核的设备信息在system.h 中
实验3 实验目的:熟悉NiosII PIO设备的访问方法
#include "system.h" #include "altera_avalon_pio_regs.h" #include "alt_types.h“ int main (void) __attribute__ ((weak, alias ("alt_main"))); int alt_main (void) { alt_u8 led = 0x2; alt_u8 dir = 0; volatile int i; while (1) { if (led & 0x81) {//led为边缘情况1或8的时候,改方向 dir = (dir ^ 0x1);} if (dir) { led = led >> 1;} else {led = led << 1;} IOWR_ALTERA_AVALON_PIO_DATA(LED_PIO_BASE, led); //led值写入PIO i = 0; while (i<200000) i++; //延时} return 0;} 此处:定义main的别名为alt_main
#include "system.h“ #define LED_PIO_NAME "/dev/led_pio" #define LED_PIO_BASE 0x06424110 ...... #include "altera_avalon_pio_regs.h“ #define IORD_ALTERA_AVALON_PIO_DATA(base) IORD(base, 0) #define IOWR_ALTERA_AVALON_PIO_DATA(base, data) IOWR(base, 0, data) #include "alt_types.h“ typedef signed char alt_8;......
7.1 并行输入/输出内核 例: 红色发放二极管LEDR 在LEDR上输出显示data数据的C/C++语句格式为: IOWR _ALTERA_AVALON_PIO_DATA(LED_RED_BASE, data) 或者: IOWR_ALTERA_AVALON_PIO_DATA(0x00B81090,data) IOWR (LED_RED_BASE, 0, data) IOWR(0x00B81090,0,data)
例: 编写 C/C++程序,让DE2开发板上的18只红色发光二极管LEDR17~LEDR0依次向左移位发光。 #include “count_binary.h” int alt_main(void) { int i,data; data=0x01; for(i=0;i<18;i++) { IOWR(LEDR_BASE,0,data); data<<=1; usleep(100000); } }
2)电平开关SW 有18只电平开关SW17~SW0,在硬件设备命名为SWITCH_PIO,电平开关的定义在system.h头文件中。 电平开关的编程 用变量key读取电平开关SW上的数据的C/C++语句格式为: key=IORD_ALTERA_AVALON_PIO_DATA(SWITCH_PIO_BASE) 或: key=IORD(SWITCH_PIO_BASE,0);
例: 编写 C/C++程序,用变量key读取DE2开发板上的18只电平开关上的数据,并用七段数码管显示读出的数据。 #include “count_binary.h” int alt_main(void) { int key; key=IORD(SWITCH_PIO_BASE,0); IOWR(SEG7_DISPLAY_BASE,0,key); }
3) 按钮BUTTON 有4只按钮KEY3~KEY0,在硬件设备命名为BUTTON_PIO,按钮BUTTON的定义在system.h头文件中。 按钮BUTTON的编程 用变量key读取按钮BUTTON上的数据的C/C++语句格式为:key=IORD_ALTERA_AVALON_PIO_DATA(BUTTON_PIO_BASE) 或: key=IORD(BUTTON_PIO_BASE,0);
异常处理 • 异常是指程序离开正常流程的一种控制上的切换。 • 异常可能由需要立即响应的处理器内部或者外部设备的某一事件引起。 • 异常处理指响应异常所进行的处理,并且返回到异常产生之前程序执行的状态。 异常不是bug!!!
Nios II处理器异常分类如下: • 硬件中断 • 软件陷阱 • 未实现的指令 • 其他异常 • 所有的异常(硬件和软件)由位于异常地址(exception address) 的异常程序来处理。 • 异常地址在系统创建时指定,软件编程人员不用直接访问异常处 理地址,编写程序也不需要知道该地址。 • 学习重点:处理硬件中断请求 使用HAL注册用户定义的中断服务程序(Interrupt Service Routine——ISR)来处理硬件中断请求。
1. 硬件中断 • NIOS处理器支持32个外部中断源,中断号越小,优先级越高 • 处理硬件中断的流程 来自外设的请求硬件中断的信号(interrupt request) 保存Nios II处理器寄存器(context switch) 禁止硬件中断 将控制权交到Nios II处理器异常地址(exception handler) 跳转到异常处理地址后执行异常处理程序,处理器开始执行一段HAL系统代码,判断中断源和中断优先级,然后再转跳到相应的用户的中断服务程序(ISR)中。即发生异常后,异常处理除ISR外的所有工作都由HAL系统库代码自动完成。 【context】:语境
使用HAL开发应用程序 • HAL与异常处理 异常处理过程和中断服务程序
(1)中断服务程序的编写和注册方法 HAL的ISR API • Nios II IDE创建系统库工程时,包含了所有需要的ISR。 用户不必去写HAL ISR,除非用户要和定制的外设通信。 • HAL API定义了很多函数管理硬件中断,如: alt_irq_register() alt_irq_disable() alt_irq_enable()…… • 用HAL API编写用户的ISR,步骤: 1. 编写特定设备中断的ISR。 2. 调用alt_irq_register()函数来注册ISR。
编写用户的ISR • 用户编写的ISR必须符合alt_irq_register()函数的原型 void isr_name (void *context, alt_u32 id) 第一个参数:中断上下文指针(向ISR传递语境信息的指针) 第二个参数:中断号, 在system.h中声明,例如:PIO_IRQ • 注意:编写的ISR中不能使用ANSI C标准库中I/O有关操作, 不要调用printf(),可能使系统死锁……
注册用户的ISR,向HAL层传递中断服务程序信息 注册用户的ISR,向HAL层传递中断服务程序信息 alt_irq_register(alt_u32 id,void *context, void(*isr)(void *, alt_u32)) 第一个参数为中断号; (alt_u32在alt_types.h定义为unsigned long) 第二个参数为中断上下文指针; 第三个参数是中断服务函数的指针。(可直接用函数名字) • 中断发生时,系统回调用户注册的中断服务程序,传入的第一个参数即为用户调用alt_irq_register时传入的中断上下文指针参数;传入的第二个参数为本中断的中断号。 • 注意:使用该函数需包含”sys/alt_irq.h”头文件
(2)举例—中断编程 (实验5 PIO外部中断实验) • Button PIO以中断的形式与应用程序 通信,设备的初始化程序比较特殊,也是整个软件设计的难点 编写用户的ISR static void handle_button_interrupts(void* context, alt_u32 id) { /* 清中断捕获寄存器 */ IOWR_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_PIO_BASE, 0); /* 通知外部有中断事件发生 */ done++; }
注册用户的ISR( 初始化) 采用中断方式时还需要对按钮进行开放中断、复位边沿捕获寄存器和登记中断源的初始化处理。按钮初始化过程语句如下: static void init_button_pio() { void* edge_capture_ptr=(void*)&edge_capture; /*开放全部4个按钮的中断*/ IOWR_ALTERA_AVALON_PIO_IRQ_MASK(BUTTON_PIO_BASE,0xf); /*复位边沿捕获寄存器*/ IOWR_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_PIO_BASE,0x0); /*登记中断源*/ alt_irq_register(BUTTON_PIO_IRQ,edge_capture_ptr,handle_button_interrupts);
提高处理性能的措施 (1)从软件上改善处理性能 • 把无关紧要的以及影响中断执行性能的事情放在中断服务程序之外处理,尽量使中断服务程序精简; • 把传送大量数据之类的事情交给DMA来完成; • 如果使用DMA传输大量数据,可以加大缓冲区以减少中断次数,从而提高中断性能。 • DMA传输大量数据应用中,采用双缓冲区; • 在允许的情况下,一直保持中断使能。 • 使用快速的存储空间来存储关键代码; • 使用中断嵌套 • 将应用程序工程和系统库工程都设置为高的编译优化等级。
提高处理性能的措施 (1)从硬件上改善处理性能 • 添加或加大快速存储器来存储关键代码或作为数据缓存。 • 添加DMA控制器,这要与软件改善中的第3.4步同时执行。 • 使用性能更好的Nios II处理器,如Nios II/f。 • 根据系统实际的中断优先级来合理分配SOPC Builder系统中的中断号,注意IRQ0为最高优先级。
设计关键技术补充——volatile • 程序中采用volatile修饰变量,这是操作系统内核编程经常用到的一个修饰关键字。 • 如果将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。 • 一般说来,volatile用在如下的几个地方: • 中断服务程序中修改的供其它程序检测的变量需要加volatile ; • 多任务环境下各任务间共享的标志应该加volatile; • 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义。
7.2 定时器内核 定时器内核综述 定时器是挂载在Avalon总线上的32位定时器,特性如下: 两种计数模式:单次减1和连续减1计数模式 定时器到达0时产生中断请求(IRQ); 可选择设定为看门狗定时器,计算到达0时复位系统; 可选择输出周期性脉冲,在定时器计算到达0时输出脉冲; 可由软件启动、停止或复位定时器; 可由软件使能或屏蔽定时器中断。
7.2 定时器内核 定时器内核综述 寄存器文件 Status 数据总线 Control Periodh Periodl 地址总线 计数器 Avanlon Snaph 总线从机 Snapl 接口到内 核逻辑 IRQ Timeoutpulse 控制 Reset 逻辑 (看门狗) 定时器结构框图
7.2 定时器内核 定时器内核综述 定时器可进行的基本操作如下所述: Avalon主控制器通过对控制寄存器执行不同的写操作来控制: 启动和停止定时器 使能/禁能IRQ 指定单次减1计数或连续减1计数模式 处理器读状态寄存器获取当前定时器的运行信息。 处理器可通过写数据到periodl和periodh寄存器来设定定时器周期。
7.2 定时器内核 定时器可进行的基本操作如下所述: 内部计数器计数减到0,立即从周期寄存器开始重新装载。 处理器可以通过写snapl或snaph获取计数器的当前值。 当计数器计数到达0时: 如果IRQ被使能,则产生一个IRQ (可选的)脉冲发生器输出有效持续一个时钟周期 (可选的)看门狗输出复位系统
7.2 定时器内核 定时器寄存器描述 RUN TO STOP START CONT ITO periodl Periodh snapl snaph 定时器寄存器 注:*表示该位保留,读取值未定义。
7.2 定时器内核 状态寄存器描述 定时器寄存器
7.2 定时器内核 控制寄存器描述 定时器寄存器
7.2 定时器内核 • - 定 时 器 内 核 配 置 选 项 Initial perod: 用于预设硬件生成后的定时器周期,即perodl和periodh寄存器的值。 CFI控制器框图
7.2 定时器内核 • - 定 时 器 内 核 配 置 选 项 Preset Configurations: 可选择的预定义的硬件配置。 CFI控制器框图
7.2 定时器内核 • - 定 时 器 内 核 配 置 选 项 Writeable perod: • 使能:主控制器可通过写period而改变向下计数周期。 • 禁能:向下计数周期由Timeout Period确定,且period寄存器不在硬件中存在。 Readable snapshot: • 使能:主控制器可读当前向下计数器的值。 • 禁能:计数器的状态仅通过状态寄存器或IRQ信号来检测。Snap寄存器不在硬件中存在。 Start/Stop control bits: • 使能:主控制可通过写START和STOP位来启动和停止定时器。 • 禁能:定时器连续运行。 CFI控制器框图
7.2 定时器内核 • - 定 时 器 内 核 配 置 选 项 Timeout pulse: • 使能:定时器到0时,timeout_pulse输出一个时钟周期的高电平。 • 禁能:timeout_out信号不存在。 System reset on timeout: • 使能: 定时器到0时, resetrequest信号输出一个时钟周期的高电平使系统复位。 • 禁能: resetrequest信号不存在。 CFI控制器框图
7.2 定时器内核 软件编程 Altera为NiosII处理器用户提供硬件抽象层(HAL)系统库驱动程序,允许用户使用HAL应用程序接口(API)函数来访问定时器内核。 HAL系统库支持 系统时钟驱动程序 时间标记驱动程序 软件文件
Altera_avalon_timer_regs.h 7.2 定时器内核 timer内核提供了对硬件进行寄存器级访问的文件。 该文件定义了内核的寄存器映射并提供硬件设备访问宏定义。可通过阅读上述文件以熟悉timer设备的软件访问方法,但不应该修改文件。
#ifndef __ALTERA_AVALON_TIMER_REGS_H__ • #define __ALTERA_AVALON_TIMER_REGS_H__ • #include <io.h> • /* STATUS register */ • #define ALTERA_AVALON_TIMER_STATUS_REG 0 • #define IOADDR_ALTERA_AVALON_TIMER_STATUS(base) \ • __IO_CALC_ADDRESS_NATIVE(base, ALTERA_AVALON_TIMER_STATUS_REG) • #define IORD_ALTERA_AVALON_TIMER_STATUS(base) \ • IORD(base, ALTERA_AVALON_TIMER_STATUS_REG) • #define IOWR_ALTERA_AVALON_TIMER_STATUS(base, data) \ • IOWR(base, ALTERA_AVALON_TIMER_STATUS_REG, data)
/* CONTROL register */ #define ALTERA_AVALON_TIMER_CONTROL_REG 1 #define IOADDR_ALTERA_AVALON_TIMER_CONTROL(base) \ __IO_CALC_ADDRESS_NATIVE(base, ALTERA_AVALON_TIMER_CONTROL_REG) #define IORD_ALTERA_AVALON_TIMER_CONTROL(base) \ IORD(base, ALTERA_AVALON_TIMER_CONTROL_REG) #define IOWR_ALTERA_AVALON_TIMER_CONTROL(base, data) \ IOWR(base, ALTERA_AVALON_TIMER_CONTROL_REG, data)
HAL API • 定时器设备是可以计数时钟的硬件设备,产生周期性的中断 请求。用户使用定时器设备提供一些和时间相关的功能,如 HAL系统时钟、报警器、时间测量 使用定时器设备,Nios II处理器系统在硬件上必须包含一 个定时器外设。 • HAL API提供两种定时器设备的驱动程序: 系统时钟驱动——支持报警(alarms),用户在调度程序使用。 时间标记驱动程序——支持高精度的时间测量。 注意:1.一个单独的定时器外设可以作为一个系统时钟或是时间 标记,但是不能兼顾。 2. HAL特定的访问定时器设备的API函数在sys/alt_alarm.h 和sys/alt_timestamp.h中定义。