在上一篇关于中断机制的介绍之后,这一章来进行实战演练,下面会用例子来演示共享外设中断是怎样使用的。
例子:MIO按键中断
在ZYNQ内部GPIO由78个引脚的MIO和288个信号的扩展多路复用I/O接口(EMIO)组成,这些信号分为96个来自可编程逻辑(PL)的输入和192个输出到PL。GPIO被组织成六组寄存器,这些寄存器对相关接口信号进行分组,每个GPIO通道独立且动态编程为输入、输出或者中断检测。如图所示:
一组GPIO引脚的基本功能如图所示
对图中左边的寄存器组做一个简单介绍:
DATA_RO:该寄存器使软件能够观察设备引脚上的值。如果将GPIO信号配置为输出,则这通常用来反映输出值,忽略对此寄存器的写入。注意:如果软件未将此管脚配置为GPIO管脚,则DATA_RO不可预测,因为软件无法通过GPIO寄存器观察非GPIO管脚上的值。
DATA:当GPIO信号配置为输出时,该寄存器控制要输出的值,该寄存器的所有32位同时写入。从该寄存器读取时,返回先前写入DATA或MASK_DATA_{LSW,MSW}的值,它不会返回设备引脚上的当前值。
MASK_DATA_LSW:该寄存器允许对所需输出值进行选择性更改。最多可写入16位的任意组合。未写入的位保持不变,并保留其先前的值。从该寄存器读取时,返回先前写入DATA或MASK_DATA_{LSW,MSW}的值;它不会返回设备引脚上的当前值。该寄存器避免了对不变位进行读-修改-写序列的需要。
MASK_DATA_MSW:该寄存器与MASK_DATA_LSW相同,不同之处在于它控制着BANK的高16位。
DIRM:方向模式。控制着I/O引脚是作为输入还是输出,由于输入逻辑始终处于启用状态,因此可以有效启用/禁用输出驱动程序,当DIRM[x]==0时,输出驱动程序被禁用。
OEN:输出启用。当I/O配置为输出时,这将控制是否启用输出,当输出被禁用时,引脚为三态(高电平、低电平、高阻态)。当OEN[x]0时,输出驱动程序被禁用。注意:如果MIO TRI_ENABLE设置为1,则会启用三态并禁用驱动程序,则OEN被忽略,输出为3态。
INT_MASK:该寄存器为只读,显示当前屏蔽的位与未屏蔽/启用的位。
INT_EN:将1写入该寄存器的任何位,可以启用/取消屏蔽中断信号,从该寄存器读取会返回不可预知的值。
INT_DIS:将1写入该寄存器的任何位,屏蔽中断信号,从该寄存器读取会返回不可预知的值。
INIT_STAT:该寄存器显示是否发生了中断事件,将1写入该寄存器中的某个位,可清除该位的中断状态。忽略将0写入此寄存器的位。
INT_TYPE:该寄存器控制中断是边缘敏感还是电平敏感。
INT_POLARITY:该寄存器控制中断是上升沿敏感还是下降沿敏感。
== INT_ON_ANY:如果INT_TYPE设置为边缘敏感,则该寄存器在上升沿和下降沿启用中断事件。如果INT_TYPE设置为电平敏感,则忽略此寄存器。下面是触发模式与寄存器配置的关系:
经过以上了解,现在来实现通过按下按键来点亮LED,再次按下时,熄灭LED,我这边通过原理图可以看出按键接在了MIO26,按键按下为低电平,不按则为高电平。而LED接在了MIO40,高电平点亮。
主程序设计流程如下:
GPIO初始化–>设置按键和LED方向–>设置产生中断方式–>设置中断–>打开中断控制器–>打开中断异常–>打开GPIO中断–>判断KEY_FLAG值,是1,写入LED
中断处理流程:
查询中断状态寄存器–>判断状态–>清除中断–>设置KEY_FLAG值
#include "xparameters.h"
#include "xscugic.h"
#include "xgpiops.h"
#include "xil_printf.h"
#include "xil_exception.h"
/* GPIO paramter */
#define MIO_ID XPAR_XGPIOPS_0_DEVICE_ID //外设ID号
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID //GIC编号
#define KEY_INTR_ID XPAR_XGPIOPS_0_INTR //中断ID号,48恰好对应了GPIO在IRQ中的编号
#define PS_KEY_MIO 26
#define PS_LED_MIO 40
#define GPIO_INPUT 0
#define GPIO_OUTPUT 1
int key_flag ;
XGpioPs GPIO_PTR ;
XScuGic INTCInst;
int IntrInitFuntion(XScuGic *InstancePtr, u16 DeviceId, XGpioPs *GpioInstancePtr); //异常处理程序
void GpioHandler(void *CallbackRef); //中断服务函数
int main()
{
XGpioPs_Config *GpioConfig ;
int Status ;
int key_val = 0 ;
key_flag = 0 ;
/*
* Initialize the gpio.
*/
GpioConfig = XGpioPs_LookupConfig(MIO_ID) ;
Status = XGpioPs_CfgInitialize(&GPIO_PTR, GpioConfig, GpioConfig->BaseAddr) ;
if (Status != XST_SUCCESS)
{
return XST_FAILURE ;
}
/*
* Set the direction for the pin to be output and
* Enable the Output enable for the LED Pin.
*/
XGpioPs_SetDirectionPin(&GPIO_PTR, PS_LED_MIO, GPIO_OUTPUT) ;
XGpioPs_SetOutputEnablePin(&GPIO_PTR, PS_LED_MIO, GPIO_OUTPUT) ;
/*
* Set the direction for the pin to be input.
* Set interrupt type as rising edge and enable gpio interrupt
*/
XGpioPs_SetDirectionPin(&GPIO_PTR, PS_KEY_MIO, GPIO_INPUT) ;
XGpioPs_SetIntrTypePin(&GPIO_PTR, PS_KEY_MIO, XGPIOPS_IRQ_TYPE_EDGE_RISING) ; //对INT_TYPE、INT_POLARITY、INT_ON_ANY寄存器操作,设置中断触发模式
XGpioPs_IntrEnablePin(&GPIO_PTR, PS_KEY_MIO) ; //使能中断
/*
* sets up the interrupt system
*/
Status = IntrInitFuntion(&INTCInst, MIO_ID, &GPIO_PTR) ;
if (Status != XST_SUCCESS)
return XST_FAILURE ;
while(1)
{
if (key_flag)
{
XGpioPs_WritePin(&GPIO_PTR, PS_LED_MIO, key_val) ;
key_val = ~key_val ;
key_flag = 0 ;
}
}
return 0 ;
}
/*
*1、初始化中断控制器;2、设置中断优先级和触发类型
*3、关联中断ID和中断服务函数;4、打开中断控制器
*/
int IntrInitFuntion(XScuGic *InstancePtr, u16 DeviceId, XGpioPs *GpioInstancePtr)
{
XScuGic_Config *IntcConfig;
int Status ;
/*
* Initialize the interrupt controller driver so that it is ready to
* use.
*/
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
Status = XScuGic_CfgInitialize(InstancePtr, IntcConfig, IntcConfig->CpuBaseAddress) ;
if (Status != XST_SUCCESS)
return XST_FAILURE ;
/*
* set priority and trigger type
*/
XScuGic_SetPriorityTriggerType(InstancePtr, KEY_INTR_ID,
0xA0, 0x3);
/*
* Connect the device driver handler that will be called when an
* interrupt for the device occurs, the handler defined above performs
* the specific interrupt processing for the device.
*/
Status = XScuGic_Connect(InstancePtr, KEY_INTR_ID,
(Xil_ExceptionHandler)GpioHandler,
(void *)GpioInstancePtr) ;
if (Status != XST_SUCCESS)
return XST_FAILURE ;
/*
* Enable the interrupt for the device.
*/
XScuGic_Enable(InstancePtr, KEY_INTR_ID) ;
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
InstancePtr);
Xil_ExceptionEnable();
return XST_SUCCESS ;
}
void GpioHandler(void *CallbackRef)
{
XGpioPs *GpioInstancePtr = (XGpioPs *)CallbackRef ;
int Int_val ;
Int_val = XGpioPs_IntrGetStatusPin(GpioInstancePtr, PS_KEY_MIO) ;
/*
* Clear interrupt.
*/
XGpioPs_IntrClearPin(GpioInstancePtr, PS_KEY_MIO) ;
if (Int_val)
key_flag = 1 ;
}
我一直没看懂为什么代码里要使能两次中断,我以为是相同操作,其实不然,在我阅读linux书籍ARM64架构后才知道,道理很简单,首先使能外设的中断,因为它中断产生后并不是直接把请求发送给处理器,而是发送给中断控制器,这里的使能决定了中断控制器会不会把该外设发送的中断转发给处理器,而后面使能中断控制器则决定了处理器会不会响应中断请求。
若我解释有误,欢迎指出问题所在,一起讨论!
参考书籍:黑金教程
塞琳思官网文档PG1085
Linux内核深度解析