[笔记].如何使用Nios II的中断:PIO中断与定时器中断
引子
定时器中断,我以前在艾米电子论坛发帖讨论过;PIO中断我在博客里也讨论过,最近发现以前的总结有一点小错误。于是结合我最近玩触摸屏的一点点心得,写篇博文。
软硬件环境
硬件:艾米电子EP2C8核心板+2.4’ TFT套件
软件:Altera Quartus II 10.0 + Nios II 10.0 Software Build Tools for Eclipse
内容
1 PIO中断
此处以ADS的nIRQ引脚为例。
1.1 在SOPC Builder中例化PIO
图1.1 例化PIO核
图1.2 Basic Setting
图1.3 Input Option
在ADS7843中,当用触摸笔触摸到TFT时,nIRQ引脚会拉低,因此我们可以检测nIRQ引脚的边沿,当为下降沿的时候,产生中断。查看手册Embedded Peripherals IP User Guide中的PIO一节,阅读相关片段。根据图1.4和1.5的描述,对nIRQ的PIO的输入选项的设置如图1.3所示。只需配置图1.2和图1.3所指的选项,其他选项缺省设置即可。
图1.4 Capture功用
图1.5 IRQ Generation功用
1.2 PIO中断的C代码
#include "system.h" // 系统 #include "altera_avalon_pio_regs.h" // PIO,ads_nIRQ #include "sys/alt_irq.h" // 中断 // unsigned int nirq_isr_context; // 定义全局变量以储存isr_context指针 void nIRQ_Initial(void); void nIRQ_ISR(void* isr_context); // int main(void) { nIRQ_Initial(); // 初始化PIO中断 while(1) { } } // nIRQ中断初始化 void nIRQ_Initial(void) { // 改写timer_isr_context指针以匹配alt_irq_register()函数原型 void* isr_context_ptr = (void*) &nirq_isr_context; IOWR_ALTERA_AVALON_PIO_IRQ_MASK(ADS_NIRQ_BASE, 1); // 使能中断 IOWR_ALTERA_AVALON_PIO_EDGE_CAP(ADS_NIRQ_BASE, 1); // 清中断边沿捕获寄存器 // 注册ISR alt_ic_isr_register( ADS_NIRQ_IRQ_INTERRUPT_CONTROLLER_ID, // 中断控制器标号,从system.h复制 ADS_NIRQ_IRQ, // 硬件中断号,从system.h复制 nIRQ_ISR, // 中断服务子函数 isr_context_ptr, // 指向与设备驱动实例相关的数据结构体 0x0); // flags,保留未用 } // 中断服务子函数 void nIRQ_ISR(void* nirq_isr_context) { IOWR_ALTERA_AVALON_PIO_EDGE_CAP(ADS_NIRQ_BASE, 1); // 清中断边沿捕获寄存器 // 用户中断代码 }
以第21行为例,使能中断和清清中断边沿捕获寄存器都是按位操作的。此处nIRQ引脚为1位,因此所写的值为0或1。
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(ADS_NIRQ_BASE, 1); // 使能中断
倘若是总线,比方说4位,那么使能的话,就应该如下操作。对其他寄存器的操作也是类似的。
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(BUS_4_BASE, 0xF); // 使能中断
同时需要注意若是在SOPC Builder中选择了enable bit-clearing for edge capture register的话,那么对于edge capture就应该是写1清中断;若是没有选择enable bit-clearing for edge capture register,则是写任意数清中断。(由韩彬总结)
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(ADS_NIRQ_BASE, 1); // 清中断边沿捕获寄存器
若是总线,便与上面类似。
在清中断这一点上,我们可以总结一下:arm和mips类的nios是写1清中断;而51单片机是写0清中断。
注意:
1 中断服务代码区不要使用printf(),否则会严重阻塞中断。
2 如果需要中断服务子函数传参,那么参数必须转为空类型。且所传参数为16位的全局变量。
3 9.1版本的以后的中断注册写法有两种,此处示范为增强版的中断注册写法。
2 定时器中断
此处以high_res_timer为例。
2.1 在SOPC Builder中例化Timer
图2.1 例化Interval Timer核
图2.2 配置Timer counter size和Hardware option
查看手册Embedded Peripherals IP User Guide中的Interval Timer核一节,阅读相关片段。参考图2.3和2.4的描述,配置为32位的全功能定时器(当然也可以配置为64位定时器,但是后面的软件需要稍微修改)。只需配置图2.2,其他选项缺省设置即可。
图2.3 Counter Size功用
图2.4 Hardware Options功用
2.2 定时器中断的C代码
#include "system.h" // 系统 #include "altera_avalon_timer_regs.h" // 定时器 #include "sys/alt_irq.h" // 中断 // unsigned int timer_isr_context; // 定义全局变量以储存isr_context指针 void Timer_Initial(void); void Timer_ISR(void* isr_context); // int main(void) { Timer_Initial(); // 初始化定时器中断 while(1) { } } // 定时器中断初始化 void Timer_Initial(void) { // 改写timer_isr_context指针以匹配alt_irq_register()函数原型 void* isr_context_ptr = (void*) &timer_isr_context; // 设置PERIOD寄存器 // PERIODH << 16 | PERIODL = 计数器周期因子 * 系统时钟频率因子 - 1 // PERIODH << 16 | PERIODL = 5m*100M - 1 = 499999 = 0x7A11F IOWR_ALTERA_AVALON_TIMER_PERIODH(HIGH_RES_TIMER_BASE, 0x0007); IOWR_ALTERA_AVALON_TIMER_PERIODL(HIGH_RES_TIMER_BASE, 0xA11F); // 设置CONTROL寄存器 // 位数 | 3 | 2 | 1 | 0 | // CONTROL | STOP | START| CONT | ITO | // ITO 1,产生IRO; 0,不产生IRQ // CONT 1,计数器连续运行直到STOP被置一; 0,计数到0停止 // START 1,计数器开始运行; 0,无影响 // STOP 1,计数器停止运行; 0,无影响 IOWR_ALTERA_AVALON_TIMER_CONTROL(HIGH_RES_TIMER_BASE, ALTERA_AVALON_TIMER_CONTROL_START_MSK | // START = 1 ALTERA_AVALON_TIMER_CONTROL_CONT_MSK | // CONT = 1 ALTERA_AVALON_TIMER_CONTROL_ITO_MSK); // ITO = 1 // 注册定时器中断 alt_ic_isr_register( HIGH_RES_TIMER_IRQ_INTERRUPT_CONTROLLER_ID, // 中断控制器标号,从system.h复制 HIGH_RES_TIMER_IRQ, // 硬件中断号,从system.h复制 Timer_ISR, // 中断服务子函数 isr_context_ptr, // 指向与设备驱动实例相关的数据结构体 0x0); // flags,保留未用 } // 定时器中断服务子函数 void Timer_ISR(void* timer_isr_context) { // 应答中断,将STATUS寄存器清零 IOWR_ALTERA_AVALON_TIMER_STATUS(HIGH_RES_TIMER_BASE, ~ ALTERA_AVALON_TIMER_STATUS_TO_MSK); // TO = 0 // 用户中断代码 }
图2.3 32位的Interval Timer核的寄存器结构。
查看手册Embedded Peripherals IP User Guide中的Interval Timer核一节,阅读相关片段。参考图2.3,设置寄存器。此处以5ms为例,系统时钟为100Mhz。之所以还要减去1,是因为从0开始计数的。关于其他寄存器的功用,请参阅手册的相关片段。
// 设置PERIOD寄存器 // PERIODH << 16 | PERIODL = 计数器周期因子 * 系统时钟频率因子 - 1 // PERIODH << 16 | PERIODL = 5m*100M - 1 = 499999 = 0x7A11F IOWR_ALTERA_AVALON_TIMER_PERIODH(HIGH_RES_TIMER_BASE, 0x0007); IOWR_ALTERA_AVALON_TIMER_PERIODL(HIGH_RES_TIMER_BASE, 0xA11F);
与PIO注意事项类似。
注意:
1 中断服务代码区不要使用printf(),否则会严重阻塞中断。
2 如果需要中断服务子函数传参,那么参数必须转为空类型。且所传参数为16位的全局变量。
3 9.1版本的以后的中断注册写法有两种,此处示范为增强版的中断注册写法。
3 一点心得
Nios II的中断操作基本类似。今后我会写点其他IP和自定义IP里面的中断如何使用。
Embedded Peripherals IP User Guide提供了很多IP核的功能和用法说明,是学习Nios II的红宝书。关于Nios II软件编程的更多细节,可以参阅Nios II Software Developer's Handbook;关于Nios II软核的更多细节,可以参阅Nios II Processor Reference Handbook。如果你有一些解决不了的问题,可以去alterafoum.com检索相关关键字,很多时候是可以找到前人的经验和答案的。
另见
[笔记].Nios II 9.1的sys/alt_irq.h与之前版本的区别.[Nios II]