[笔记].如何使用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.1 例化PIO核

[笔记].如何使用Nios II的中断:PIO中断与定时器中断_第1张图片

图1.2 Basic Setting

[笔记].如何使用Nios II的中断:PIO中断与定时器中断_第2张图片

图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所指的选项,其他选项缺省设置即可。

[笔记].如何使用Nios II的中断:PIO中断与定时器中断_第3张图片

图1.4 Capture功用

[笔记].如何使用Nios II的中断:PIO中断与定时器中断_第4张图片

图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

[笔记].如何使用Nios II的中断:PIO中断与定时器中断_第5张图片

图2.1 例化Interval Timer核

[笔记].如何使用Nios II的中断:PIO中断与定时器中断_第6张图片

图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.3 Counter Size功用

[笔记].如何使用Nios II的中断:PIO中断与定时器中断_第7张图片

图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

  // 用户中断代码
}

[笔记].如何使用Nios II的中断:PIO中断与定时器中断_第8张图片

图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]

 

参考

Altera.Embedded Peripherals IP User Guide

你可能感兴趣的:([笔记].如何使用Nios II的中断:PIO中断与定时器中断)