熟悉NiosII PIO设备的访问方法。学习边沿触发中断。
硬件:PC机,EP1C3或EP1C12核心板,MyEpx3实验版。
软件:Quartus II 7.2;MagaCore 7.2;Nios II IDE 7.2。
用一个按键控制一个LED灯显示。
熟悉PIO外设的访问和控制和PIO的中断控制机制。
(1) 创建一个Quartus II项目PIOEINT。
(2) 如下图,定制一个Nios II CPU,命名为myniosii。
其中:
l onchip_mem为8K。启动向量和异常向量配置如下:
l KEY_PIO为1位输入,如下图设置为下降沿触发中断。
l LED_PIO为1位输出。
(3) 在Quartus II中创建一个原理图,放置一个上面定制好的Nios II核myniosii,并如图设置引脚。编译、下载硬件配置到FPGA。
(4) 启动Nios II IDE,新建一个空白Nios II C/C++ Application项目,并命名为pio_eint。
(5) 在pio_eint项目中添加一个C文件pio_eint.c。
(6) 设置pio_eint项目Properties属性,对该项目的代码编译进行优化:
C/C++ Build > Tool Settings > General > Optimize size (-Os)
(7) 设置pio_eint_syslib项目Properties属性,对该项目的代码编译进行优化:
a) C/C++ Build > Tool Settings > General > Optimize size (-Os)
b) 在System library属性页面,清除Support C++和Clean exit (flush buffers);选上Program、LightWeight device driver API、Reduced device drivers和Small C library。
(8) 编译、调试、运行。
从上面的编译、链接信息看到,优化后,pio_eint.elf文件代码加指令总共只有1728字节,这样实验就完全可以在低成本的EP1C3系列芯片上进行。
PIO外部中断实验程序
/*************************************************************
* 文件名:pio_eint.c
* 功 能:用按键以中断方式控制LED。每当有一次按键中断时,取反LED一次
* 说 明:按下KEY1观察LED1的状态
************************************************************/
#include <stdio.h>
#include "system.h"
#include "altera_avalon_pio_regs.h"
#include "alt_types.h"
#include "sys/alt_irq.h"
#include "priv/alt_busy_sleep.h"
#define LEDCON 0x01
#define KEYCON 0x01
volatile alt_u32 done = 0; //信号量:通知中断事件发生
/*************************************************************
* 名 称:KeyDown_interrupts()
* 功 能:键按下事件中断服务子程序,当键按下时,通过down标志告知外界
* 入口参数:context,一般用于传递中断状态寄存器的值,这里未使用
* id,中断号,这里未使用
* 出口参数:无
************************************************************/
static void KeyDown_interrupts(void* context, alt_u32 id)
{
/* 清中断捕获寄存器 */
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(PIO_KEY_BASE, ~KEYCON);
/* 通知外部有中断事件发生 */
done++;
}
/*************************************************************
* 名 称:InitPIO()
* 功 能:初始化PIO_KEY为输入,PIO_LED为输出,开中断,清边沿捕获寄存器
* 入口参数:无
* 出口参数:无
************************************************************/
void InitPIO(void)
{
/* 初始化PIO_KEY为输入,PIO_LED为输出 */
IOWR_ALTERA_AVALON_PIO_DIRECTION(PIO_KEY_BASE, ~KEYCON); //0为输入
IOWR_ALTERA_AVALON_PIO_DIRECTION(PIO_LED_BASE, LEDCON); //1为输出
/* 开PIO_KEY中断 */
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(PIO_KEY_BASE, KEYCON);
/* 清边沿捕获寄存器 */
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(PIO_KEY_BASE, ~KEYCON);
/* 注册中断服务子程序 */
alt_irq_register(PIO_KEY_IRQ, NULL, KeyDown_interrupts);
}
/*************************************************************
* 名 称:main()
* 功 能:等待按键中断,并输出控制LED
************************************************************/
int main(void)
{
volatile alt_u32 key_state, old_state, new_state;
old_state = KEYCON;
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LED_BASE, old_state); //初始化LED熄灭
InitPIO();
while(1)
{
if(0 != done)
{
done--; //中断事件数量减1
alt_busy_sleep(5000); //延时5ms
key_state = IORD_ALTERA_AVALON_PIO_DATA(PIO_KEY_BASE)&KEYCON;
if(key_state == 0xFF) //如果是由短暂脉冲引起的中断,则忽略
continue; //消除键盘抖动
new_state = ~(old_state^key_state); //按键按下时LED取反。
old_state = new_state; //保存LED的状态
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LED_BASE, new_state);
}
}
return(0);
}
注意:不要在中断服务程序中进行等待或者其他阻塞性操作。
l 在中断服务程序内设置断点。当中断发生后,处理器会在断点处停下,用户可以单步调试中断服务程序。
l 使用spintf()函数把关键数据写到内存中,然后触发外部分析程序。在中断服务程序中,不可调用printf()函数,因为它可能引起阻塞且运行时间无法预知,但可以调用sprintf()。
由于按键的按下与抬起都会有10~20ms的抖动毛刺存在,如下图所示。因此,为了获取稳定的按键信息,须要避开这段抖动期。
去抖动的方法有很多种,如使用R-S触发器的硬件方法、运用不同算法的各种软件方法等。硬件方法会增加成本和体积,对于按键较多的矩阵式键盘,一般会使用硬件方法;一般情况下软件方法用的比较普遍,但加固定延时的去抖动法效率最低,它以无谓地耗费机时来实现去抖动。本例采用短延时加异或算法消除键盘抖动。
system.h文件在路径\PIOEINT\software\pio_eint_syslib\Debug\system_description下。注意:system.h要编译以后才有。system.h头文件是根据SOPC Builder生成的myniosii.ptf文件产生的系统硬件信息的宏定义文件,清单如下:
/* system.h
*
* Machine generated for a CPU named "cpu" as defined in:
* d:\lecture\embed\FPGA\SOPCExample1C12\PIO\PIOEINT\software\pio_eint_syslib\..\..\myniosii.ptf
*
* Generated: 2009-03-31 07:55:41.873
*
*/
#ifndef __SYSTEM_H_
#define __SYSTEM_H_
/*
* system configuration
*
*/
#define ALT_SYSTEM_NAME "myniosii"
#define ALT_CPU_NAME "cpu"
#define ALT_CPU_ARCHITECTURE "altera_nios2"
#define ALT_DEVICE_FAMILY "CYCLONE"
#define ALT_STDIN "/dev/null"
#define ALT_STDIN_TYPE ""
#define ALT_STDIN_BASE UNDEFINED VARIABLE %BASE_ADDRESS%
#define ALT_STDIN_DEV null
#define ALT_STDOUT "/dev/null"
#define ALT_STDOUT_TYPE ""
#define ALT_STDOUT_BASE UNDEFINED VARIABLE %BASE_ADDRESS%
#define ALT_STDOUT_DEV null
#define ALT_STDERR "/dev/null"
#define ALT_STDERR_TYPE ""
#define ALT_STDERR_BASE UNDEFINED VARIABLE %BASE_ADDRESS%
#define ALT_STDERR_DEV null
#define ALT_CPU_FREQ 50000000
#define ALT_IRQ_BASE NULL
/*
* processor configuration
*
*/
#define NIOS2_CPU_IMPLEMENTATION "tiny"
#define NIOS2_BIG_ENDIAN 0
#define NIOS2_ICACHE_SIZE 0
#define NIOS2_DCACHE_SIZE 0
#define NIOS2_ICACHE_LINE_SIZE 0
#define NIOS2_ICACHE_LINE_SIZE_LOG2 0
#define NIOS2_DCACHE_LINE_SIZE 0
#define NIOS2_DCACHE_LINE_SIZE_LOG2 0
#define NIOS2_FLUSHDA_SUPPORTED
#define NIOS2_EXCEPTION_ADDR 0x00002020
#define NIOS2_RESET_ADDR 0x00002000
#define NIOS2_BREAK_ADDR 0x00004820
#define NIOS2_HAS_DEBUG_STUB
#define NIOS2_CPU_ID_SIZE 1
#define NIOS2_CPU_ID_VALUE 0
/*
* A define for each class of peripheral
*
*/
#define __ALTERA_AVALON_ONCHIP_MEMORY2
#define __ALTERA_AVALON_PIO
/*
* onchip_mem configuration
*
*/
#define ONCHIP_MEM_NAME "/dev/onchip_mem"
#define ONCHIP_MEM_TYPE "altera_avalon_onchip_memory2"
#define ONCHIP_MEM_BASE 0x00002000
#define ONCHIP_MEM_SPAN 8192
#define ONCHIP_MEM_ALLOW_MRAM_SIM_CONTENTS_ONLY_FILE 0
#define ONCHIP_MEM_RAM_BLOCK_TYPE "M4K"
#define ONCHIP_MEM_INIT_CONTENTS_FILE "onchip_mem"
#define ONCHIP_MEM_NON_DEFAULT_INIT_FILE_ENABLED 0
#define ONCHIP_MEM_GUI_RAM_BLOCK_TYPE "Automatic"
#define ONCHIP_MEM_WRITEABLE 1
#define ONCHIP_MEM_DUAL_PORT 0
#define ONCHIP_MEM_SIZE_VALUE 8192
#define ONCHIP_MEM_SIZE_MULTIPLE 1
#define ONCHIP_MEM_USE_SHALLOW_MEM_BLOCKS 0
#define ONCHIP_MEM_INIT_MEM_CONTENT 1
#define ONCHIP_MEM_ALLOW_IN_SYSTEM_MEMORY_CONTENT_EDITOR 0
#define ONCHIP_MEM_INSTANCE_ID "NONE"
#define ONCHIP_MEM_IGNORE_AUTO_BLOCK_TYPE_ASSIGNMENT 1
#define ONCHIP_MEM_CONTENTS_INFO "QUARTUS_PROJECT_DIR/onchip_mem.hex 1238111903"
#define ALT_MODULE_CLASS_onchip_mem altera_avalon_onchip_memory2
/*
* pio_key configuration
*
*/
#define PIO_KEY_NAME "/dev/pio_key"
#define PIO_KEY_TYPE "altera_avalon_pio"
#define PIO_KEY_BASE 0x00005000
#define PIO_KEY_SPAN 16
#define PIO_KEY_IRQ 0
#define PIO_KEY_DO_TEST_BENCH_WIRING 0
#define PIO_KEY_DRIVEN_SIM_VALUE 0
#define PIO_KEY_HAS_TRI 0
#define PIO_KEY_HAS_OUT 0
#define PIO_KEY_HAS_IN 1
#define PIO_KEY_CAPTURE 1
#define PIO_KEY_DATA_WIDTH 1
#define PIO_KEY_EDGE_TYPE "RISING"
#define PIO_KEY_IRQ_TYPE "EDGE"
#define PIO_KEY_BIT_CLEARING_EDGE_REGISTER 0
#define PIO_KEY_FREQ 50000000
#define ALT_MODULE_CLASS_pio_key altera_avalon_pio
/*
* pio_led configuration
*
*/
#define PIO_LED_NAME "/dev/pio_led"
#define PIO_LED_TYPE "altera_avalon_pio"
#define PIO_LED_BASE 0x00005010
#define PIO_LED_SPAN 16
#define PIO_LED_DO_TEST_BENCH_WIRING 0
#define PIO_LED_DRIVEN_SIM_VALUE 0
#define PIO_LED_HAS_TRI 0
#define PIO_LED_HAS_OUT 1
#define PIO_LED_HAS_IN 0
#define PIO_LED_CAPTURE 0
#define PIO_LED_DATA_WIDTH 1
#define PIO_LED_EDGE_TYPE "NONE"
#define PIO_LED_IRQ_TYPE "NONE"
#define PIO_LED_BIT_CLEARING_EDGE_REGISTER 0
#define PIO_LED_FREQ 50000000
#define ALT_MODULE_CLASS_pio_led altera_avalon_pio
/*
* system library configuration
*
*/
#define ALT_MAX_FD 32
#define ALT_SYS_CLK none
#define ALT_TIMESTAMP_CLK none
/*
* Devices associated with code sections.
*
*/
#define ALT_TEXT_DEVICE ONCHIP_MEM
#define ALT_RODATA_DEVICE ONCHIP_MEM
#define ALT_RWDATA_DEVICE ONCHIP_MEM
#define ALT_EXCEPTIONS_DEVICE ONCHIP_MEM
#define ALT_RESET_DEVICE ONCHIP_MEM
/*
* The text section is initialised so no bootloader will be required.
* Set a variable to tell crt0.S to provide code at the reset address and
* to initialise rwdata if appropriate.
*/
#define ALT_NO_BOOTLOADER
#endif /* __SYSTEM_H_ */
altera_avalon_pio_regs.h位于路径C:\altera\72\ip\sopc_builder_ip\altera_avalon_pio\inc下。该头文件提供PIO内核寄存器访问宏定义,程序中对I/O端口操作的宏定义都在该文件中。其清单如下:
#ifndef __ALTERA_AVALON_PIO_REGS_H__
#define __ALTERA_AVALON_PIO_REGS_H__
#include <io.h>
#define IOADDR_ALTERA_AVALON_PIO_DATA(base) __IO_CALC_ADDRESS_NATIVE(base, 0)
#define IORD_ALTERA_AVALON_PIO_DATA(base) IORD(base, 0)
#define IOWR_ALTERA_AVALON_PIO_DATA(base, data) IOWR(base, 0, data)
#define IOADDR_ALTERA_AVALON_PIO_DIRECTION(base) __IO_CALC_ADDRESS_NATIVE(base, 1)
#define IORD_ALTERA_AVALON_PIO_DIRECTION(base) IORD(base, 1)
#define IOWR_ALTERA_AVALON_PIO_DIRECTION(base, data) IOWR(base, 1, data)
#define IOADDR_ALTERA_AVALON_PIO_IRQ_MASK(base) __IO_CALC_ADDRESS_NATIVE(base, 2)
#define IORD_ALTERA_AVALON_PIO_IRQ_MASK(base) IORD(base, 2)
#define IOWR_ALTERA_AVALON_PIO_IRQ_MASK(base, data) IOWR(base, 2, data)
#define IOADDR_ALTERA_AVALON_PIO_EDGE_CAP(base) __IO_CALC_ADDRESS_NATIVE(base, 3)
#define IORD_ALTERA_AVALON_PIO_EDGE_CAP(base) IORD(base, 3)
#define IOWR_ALTERA_AVALON_PIO_EDGE_CAP(base, data) IOWR(base, 3, data)
/* Defintions for direction-register operation with bi-directional PIOs */
#define ALTERA_AVALON_PIO_DIRECTION_INPUT 0
#define ALTERA_AVALON_PIO_DIRECTION_OUTPUT 1
#endif /* __ALTERA_AVALON_PIO_REGS_H__ */
下面是与中断屏蔽寄存器相关的宏定义说明。
中断屏蔽寄存器访问宏定义
宏定义 | 意义 |
IOADDR_ALTERA_AVALON_PIO_IRQ_MASK(base) | 计算中断屏蔽寄存器的物理地址。参数base为PIO内核的基地址 |
IORD_ALTERA_AVALON_PIO_IRQ_MASK(base) | 读取中断屏蔽寄存器的内容 |
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(base, data) | 写中断屏蔽寄存器。参数data为向中断屏蔽寄存器写入的数据 |
alt_types.h位于路径C:\altera\72\nios2eds\components\altera_nios2\HAL\inc下。它定义与altera Nios II自己的与编译器无关的数据类型。其清单如下:
#ifndef __ALT_TYPES_H__
#define __ALT_TYPES_H__
/*
* Don't declare these typedefs if this file is included by assembly source.
*/
#ifndef ALT_ASM_SRC
typedef signed char alt_8;
typedef unsigned char alt_u8;
typedef signed short alt_16;
typedef unsigned short alt_u16;
typedef signed long alt_32;
typedef unsigned long alt_u32;
typedef long long alt_64;
typedef unsigned long long alt_u64;
#endif
#define ALT_INLINE __inline__
#define ALT_ALWAYS_INLINE __attribute__ ((always_inline))
#define ALT_WEAK __attribute__((weak))
#endif /* __ALT_TYPES_H__ */
sys/alt_irq.h位于路径C:\altera\72\nios2eds\components\altera_nios2\HAL\inc\sys下。它定义了与中断相关的函数(参见定时器实验),如alt_irq_register()。其清单如下:
#endif /* __ALT_TYPES_H__ */
#ifndef __ALT_IRQ_H__
#define __ALT_IRQ_H__
/*
* alt_irq.h is the nios2 specific implementation of the interrupt controller
* interface.
*/
#include <errno.h>
#include "nios2.h"
#include "alt_types.h"
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
/*
* Macros used by alt_irq_enabled
*/
#define ALT_IRQ_ENABLED 1
#define ALT_IRQ_DISABLED 0
/*
* number of available interrupts
*/
#define ALT_NIRQ NIOS2_NIRQ
/*
* Used by alt_irq_disable_all() and alt_irq_enable_all().
*/
typedef int alt_irq_context;
/*
* alt_irq_enabled can be called to determine if interrupts are enabled. The
* return value is zero if interrupts are disabled, and non-zero otherwise.
*/
static ALT_INLINE int ALT_ALWAYS_INLINE alt_irq_enabled (void)
{
int status;
NIOS2_READ_STATUS (status);
return status & NIOS2_STATUS_PIE_MSK;
}
/*
* alt_irq_init() is the device initialisation function. This is called at
* config time, before any other driver is initialised.
*/
static ALT_INLINE void ALT_ALWAYS_INLINE
alt_irq_init (const void* base)
{
NIOS2_WRITE_IENABLE (0);
NIOS2_WRITE_STATUS (NIOS2_STATUS_PIE_MSK);
}
/*
* alt_irq_register() can be used to register an interrupt handler. If the
* function is succesful, then the requested interrupt will be enabled upon
* return.
*/
extern int alt_irq_register (alt_u32 id,
void* context,
void (*irq_handler)(void*, alt_u32));
/*
* alt_irq_disable_all() inhibits all interrupts.
*/
static ALT_INLINE alt_irq_context ALT_ALWAYS_INLINE
alt_irq_disable_all (void)
{
alt_irq_context context;
NIOS2_READ_STATUS (context);
NIOS2_WRITE_STATUS (0);
return context;
}
/*
* alt_irq_enable_all() re-enable all interrupts that currently have registered
* interrupt handlers (and which have not been masked by a call to
* alt_irq_disable()).
*/
static ALT_INLINE void ALT_ALWAYS_INLINE
alt_irq_enable_all (alt_irq_context context)
{
NIOS2_WRITE_STATUS (context);
}
/*
* alt_irq_disable() disables the individual interrupt indicated by "id".
*/
static ALT_INLINE int ALT_ALWAYS_INLINE alt_irq_disable (alt_u32 id)
{
alt_irq_context status;
extern volatile alt_u32 alt_irq_active;
status = alt_irq_disable_all ();
alt_irq_active &= ~(1 << id);
NIOS2_WRITE_IENABLE (alt_irq_active);
alt_irq_enable_all(status);
return 0;
}
/*
* alt_irq_enable() enables the individual interrupt indicated by "id".
*
*/
static ALT_INLINE int ALT_ALWAYS_INLINE alt_irq_enable (alt_u32 id)
{
alt_irq_context status;
extern volatile alt_u32 alt_irq_active;
status = alt_irq_disable_all ();
alt_irq_active |= (1 << id);
NIOS2_WRITE_IENABLE (alt_irq_active);
alt_irq_enable_all(status);
return 0;
}
#ifndef ALT_EXCEPTION_STACK
/*
* alt_irq_initerruptable() should only be called from within an ISR. It is used
* to allow higer priority interrupts to interrupt the current ISR. The input
* argument, "priority", is the priority, i.e. interrupt number of the current
* interrupt.
*
* If this function is called, then the ISR is required to make a call to
* alt_irq_non_interruptible() before returning. The input argument to
* alt_irq_non_interruptible() is the return value from alt_irq_interruptible().
*
* Care should be taken when using this pair of functions, since they increasing
* the system overhead associated with interrupt handling.
*
* If you are using an exception stack then nested interrupts won't work, so
* these functions are not available in that case.
*/
static ALT_INLINE alt_u32 ALT_ALWAYS_INLINE alt_irq_interruptible (alt_u32 priority)
{
extern volatile alt_u32 alt_priority_mask;
extern volatile alt_u32 alt_irq_active;
alt_u32 old_priority;
old_priority = alt_priority_mask;
alt_priority_mask = (1 << priority) - 1;
NIOS2_WRITE_IENABLE (alt_irq_active & alt_priority_mask);
NIOS2_WRITE_STATUS (1);
return old_priority;
}
/*
* See Comments above for alt_irq_interruptible() for an explanation of the use of this
* function.
*/
static ALT_INLINE void ALT_ALWAYS_INLINE alt_irq_non_interruptible (alt_u32 mask)
{
extern volatile alt_u32 alt_priority_mask;
extern volatile alt_u32 alt_irq_active;
NIOS2_WRITE_STATUS (0);
alt_priority_mask = mask;
NIOS2_WRITE_IENABLE (mask & alt_irq_active);
}
#endif
/*
* alt_irq_pending() returns a bit list of the current pending interrupts.
* This is used by alt_irq_handler() to determine which registered interrupt
* handlers should be called.
*/
static ALT_INLINE alt_u32 ALT_ALWAYS_INLINE alt_irq_pending (void)
{
alt_u32 active;
NIOS2_READ_IPENDING (active);
return active;
}
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __ALT_IRQ_H__ */
priv/alt_busy_sleep.h位于路径C:\altera\72\nios2eds\components\altera_nios2\HAL\inc\priv下。它定义了延时函数alt_busy_sleep。其清单如下:
#ifndef __ALT_BUSY_SLEEP_H
#define __ALT_BUSY_SLEEP_H
/*
* The function alt_busy_sleep provides a busy loop implementation of usleep.
* This is used to provide usleep for the standalone HAL, or when the timer is
* unavailable in uC/OS-II.
*/
extern unsigned int alt_busy_sleep (unsigned int us);
#endif /* __ALT_BUSY_SLEEP_H */
[1] 周立功,等. SOPC嵌入式系统实验教程(一)[M]. 北京:北京航空航天大学出版社,2006.
[2] 李兰英,等. Nios II 嵌入式软核 SOPC设计原理及应用[M]. 北京:北京航空航天大学出版社,2006.
[3] http://www.altera.com.cn/.
[4] http://www.icembed.com/info-16530.htm 一种软件去除键抖动的方法.
文章评论(0条评论)
登录后参与讨论