本帖最后由 SuperX-man 于 2012-7-13 13:21 编辑
本实验完成了使用Microblaze-Custom-IP实现INTEL 8255A芯片的端口A模式2的功能。
输入功能:没有数据输入时LD0亮,输入数据按右下方BTNU、LD0灭,船形开关SW0-3数据读入T8255IP读寄存器。随后T8255IP向CPU发出中断请求,CPU响应中断并在3秒后读取读寄存器并解除中断,此时LD0重新点亮。
输出功能:有数据输出时,数据先到输出寄存器,此时数据时LD1亮,按右下方BTNL,寄存器数据输出LED4-7,此时LD1灭、并向CPU发出中断请求,CPU响应中断后清空中断请求(本实验数据在输入数据请求按下后即将数据输出,且仅输入输入4bit数据)。
这里首先介绍一下Microblaze的中断系统及软件操作,Microblaze内核支持外部中断响应,但仅支持一个外部中断,其中断入口地址为0x10。PC指针保存在R14寄存器内,中断控制位为MSR的IEbit(详细介绍请看附件中PDF手册)。
单中断操作比较简单,仅需用void myISR( ) __attribute__ ((interrupt_handler));
函数声明即可,然后添加用户代码即可
未使用函数声明的编译代码
void myISR( void );
void myISR( void )
{
}
Compiles to:
myISR:
.frame r1,0,r15 # vars= 0, regs= 0, args= 0
.mask 0x00000000
$LM2:
.stabn 68,0,55,$LM2-myISR
rtsd r15,8
nop # Unfilled delay slot
.end myISR
使用函数声明编译后的代码void myISR( void ) __attribute__ ((interrupt_handler));
void myISR( void )
{
}
myISR:
_interrupt_handler:
.frame r1,20,r15 # vars= 0, regs= 3, args= 0
.mask 0x00060800
addik r1,r1,-20
swi r15,r1,0
swi r11,r1,8
swi r17,r1,12
mfs r11,rmsr #mfs
swi r18,r1,16
swi r11,r1,4
$LM2:
.stabn 68,0,55,$LM2-myISR
lwi r15,r1,0
lwi r11,r1,4
mts rmsr,r11 #mts
lwi r11,r1,8
lwi r17,r1,12
lwi r18,r1,16
rtid r14,0
addik r1,r1,20
.end _interrupt_handler
多中断则需要用到int_ctrl控制器,入下图所示,这个控制器管理所有外部中断,将多个外部中断合为一个,中断响应后通过判断此控制器内部寄存器以判断是哪个中断,下面是没有用到库函数时是例程
// ISR Code****************************************************************
#define TIMER_INT XPAR_TIMER_INTERRUPT_MASK
#define BTN_INT XPAR_BUTTONS_IP2INTC_IRPT_MASK
#define SPI_INT XPAR_SPI_IP2INTC_IRPT_MASK
#define INTC_IPR (*((volatile unsigned long *)(XPAR_INT_CTRL_BASEADDR + 0x04)))
#define INTC_IER (*((volatile unsigned long *)(XPAR_INT_CTRL_BASEADDR + 0x08)))
#define INTC_IAR (*((volatile unsigned long *)(XPAR_INT_CTRL_BASEADDR + 0x0C)))
#define INTC_MER (*((volatile unsigned long *)(XPAR_INT_CTRL_BASEADDR + 0x1C)))
void myISR( void ) __attribute__ ((interrupt_handler));
void myISR( void )
{
if( INTC_IPR & TIMER_INT ) // Timer Interrupt Is Pending
timer_ISR();
if( INTC_IPR & BTN_INT ) // Button interrupt is pending
button_ISR();
if( INTC_IPR & SPI_INT ) // SPI interrupt is pending
spi_ISR();
INTC_IAR = INTC_IPR; // Acknowledge Interrupts
}
// *************************************************************************
// Timer Specific Code *****************************************************
#define TCSR0 (*((volatile unsigned long *)(XPAR_TIMER_BASEADDR + 0x00)))
#define TLR0 (*((volatile unsigned long *)(XPAR_TIMER_BASEADDR + 0x04)))
void timer_ISR( void )
{
// Do Stuff Here
TCSR0 = TCSR0; // Acknogledge Interrupt In Timer (Clear pending bit)
}
// *************************************************************************
// GPIO (Connected to Buttons) Specific Code *******************************
#define BTNS (*((volatile unsigned long *)(XPAR_BUTTONS_BASEADDR)))
#define BTN_OE (*((volatile unsigned long *)(XPAR_BUTTONS_BASEADDR + 0x04)))
#define BTN_GIE (*((volatile unsigned long *)(XPAR_BUTTONS_BASEADDR + 0x11C)))
#define BTN_IER (*((volatile unsigned long *)(XPAR_BUTTONS_BASEADDR + 0x128)))
#define BTN_ISR (*((volatile unsigned long *)(XPAR_BUTTONS_BASEADDR + 0x120)))
void button_ISR( void )
{
// Do Stuff Here
BTN_ISR = BTN_ISR; // Clear any pending button interrupts
}
// *************************************************************************
// SPI Specific Code *******************************************************
#define SPI_GIE (*((volatile unsigned long *)(XPAR_SPI_BASEADDR+0x1C)))
#define SPI_ISR (*((volatile unsigned long *)(XPAR_SPI_BASEADDR+0x20)))
#define SPI_IER (*((volatile unsigned long *)(XPAR_SPI_BASEADDR+0x28)))
void spi_ISR( void )
{
// Do Stuff Here
SPI_ISR = SPI_ISR; // Clear pending interrupts
}
// *************************************************************************
void main( void )
{
// ...
TCSR0 = 0x000007F6; // Timer Load and Clear any Pending Ints
TCSR0 = 0x000007D6; // Timer Clear Load Bit
BTN_OE = ~0x00; // Buttons are inputs
BTN_IER = BTN_CHNL1; // Enable Interrupts for all 3 buttons;
BTN_GIE = ~0x00; // Enable Interrupts for Button GPIO
SPI_IER = SPI_TxEMPTY; // Enable Interrupt for empty
SPI_GIE = ~0x00000000; // Global SPI Interrupt Enable
// Enable Timer and Button Interrupt in IntC
INTC_IER = TIMER_INT | BTN_INT | SPI_INT;
INTC_MER = 0x03; // Int Controller Master Enable
microblaze_enable_interrupts();
//...
}
下面是使用库函数后的代码,要简洁的多,,此例程硬件有多个中断,使用int_ctrl控制器,但程序中仅声明一个中断
T8255_EnableInterrupt(XPAR_T8255_0_BASEADDR);
// Enable MicroBlaze Interrupts
microblaze_enable_interrupts();
/* Register the t8255 interrupt handler in the vector table */
XIntc_RegisterHandler(XPAR_XPS_INTC_0_BASEADDR,
XPAR_XPS_INTC_0_T8255_0_IP2INTC_IRPT_INTR,
(XInterruptHandler) t8255_int_handler,
(void *)XPAR_T8255_0_BASEADDR);
/* Start the interrupt controller */
XIntc_MasterEnable(XPAR_XPS_INTC_0_BASEADDR);
XIntc_EnableIntr(XPAR_XPS_INTC_0_BASEADDR, 0x1);
说完软件,来看看硬件中断是怎么连接及生成的。在生成用户IP时,勾选下面图片选项,第一个为使用中断,第二个为中断优先级编码控制,暂时用不上这么高级的玩意。中断数量为2。里面还有一个中断触发条件选项,有上升沿、下降沿、高低电平等。这里选上升沿触发。
生成PLB总线IP代码后,需要修改三个文件t8255_v2_1_0.mpd、user_logic.v、t8255.vhd,分别在目录\lab_8255\atlys\pcores\t8255_v1_00_a\data、\lab_8255\atlys\pcores\t8255_v1_00_a\hdl下,由于本版比较熟悉Verilog,所以在生成用户代码时勾选了生成.V选项。
中断线连接很简单,在user_logic.v文件里,已经例化了中断线,数量为刚才选着的2,将这个信号和用户想要使用的触发中断信号连接起来即可。这里将他和外部按键项链,按键按下触发中断。代码如下output [0 : C_NUM_INTR-1] IP2Bus_IntrEvent;
assign IP2Bus_IntrEvent[1] = ~wt_cnt;
assign IP2Bus_IntrEvent[0] = rd_cnt;
读写寄存器代码如下,PLB总线读写信号高电平有效,用户需在PLB总线读写信号到来时,将数据输出或输入到总线上即可。代码如下
// --USER logic implementation added here
/*** read data logic ***/
reg rd_cnt;
reg [3:0] rd_reg1;
always @(posedge Bus2IP_Clk or posedge Bus2IP_Reset) begin
if(Bus2IP_Reset) begin
rd_cnt <= 1'd0;
rd_reg1 <= 4'd0;
end else begin
if(key1[0] == 1'd1) begin
rd_cnt <= 1'd1;
rd_reg1 <= switch1;
end
if(Bus2IP_RdCE[0]) rd_cnt <= 1'd0;
end
end
assign IP2Bus_IntrEvent[0] = rd_cnt;
assign IP2Bus_Data = {28'd0,rd_reg1};
//assign led1[7] = ~rd_cnt;
/*** wite data logic ***/
reg wt_cnt;
reg [3:0] wt_reg1;
reg [3:0] led_reg;
always @(posedge Bus2IP_Clk or posedge Bus2IP_Reset) begin
if(Bus2IP_Reset) begin
wt_cnt <= 1'd0;
wt_reg1 <= 4'd0;
end else begin
if(Bus2IP_WrCE[0]) begin
wt_cnt <= 1'd1;
wt_reg1 <= Bus2IP_Data[0:3];
end
if(key1[1] == 1'd1)begin
wt_cnt <= 1'd0;
led_reg <= wt_reg1;
end
end
end
assign IP2Bus_IntrEvent[1] = ~wt_cnt;
assign led1 = {~rd_cnt,wt_cnt,2'd0,led_reg};
t8255_v2_1_0.mpd文件里更改需要添加进的端口,代码如下
## Ports
PORT key1 = "",DIR = I,VEC = [0:1]
PORT switch1 = "",DIR = I,VEC = [0:3]
PORT led1 = "",DIR = O,VEC = [0:7]
t8255.vhd同样,这里就不贴出来了。修改完成后,双击左边T8255IP添加进MICROBLAZE内核,并将其接入mb_plb总线,连接网表,连接t8255中断至microblaze—interrupt,下拉key1,switch1,和led1的选项,选择make external将IP端口引出,引出网标如下。分配t8255地址,至此整个工程结束图片如下:
附件lab5为基于Spartan3e的EDK13.2工程,为多中断系统使用中断控制器例子。
Atyls_success为基于Atyls的EDK13.2工程,为单中断系统不使用中断控制器例子。
Lab_source为程序原文件。
|
|