文章目录
0 前言
本文参考博主仲夏夜之梦~的博文【裸机开发】认识中断向量表和博主Maverick的博文 ZYNQ中断跳转过程,仅作为个人学习记录。
1 中断类型
在ZYNQ中,中断大致可以分为七种类型,不同的中断类型对应着一种CPU工作模式。当中断产生时,CPU会先切换到对应的工作模式,然后再处理中断。
- Reset:CPU复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,如初始化DDR
- Undefined Instruction:如果指令不能识别的话就会产生此中断
- SWI:Linux的系统调用会用SWI指令来引起软中断,通过软中断来陷入到内核空间
- Prefetch Abort:预取指令的出错的时候会产生此中断
- Data Abort:访问数据出错的时候会产生此中断
- IRQ:外设中断都会引起此中断的发生
- FIQ:快速中断,如果需要快速处理中断的话就可以使用此中断
偏移地址 | 中断类型 | 中断模式 |
---|---|---|
0x00 | 复位中断(Reset) | SVC |
0x04 | 未定义指令中断(Undefined Instruction) | Undef |
0x08 | 软件中断(Software Interrupt) | SVC |
0x0C | 指令预取中止中断(Prefetch Abort) | Abort |
0x10 | 数据中止中断(Data Abort) | Abort |
0x14 | 保留(Reserved) | - |
0x18 | IRQ中断(IRQ Interrupt) | IRQ |
0x1C | FIQ中断(FIQ Interrupt) | FIQ |
2 中断执行流程
2.1 简单流程
当某个中断发生时,中断执行的简单流程如下图所示:
- 先判断属于哪一类
- 去中断向量表找对应类的中断服务函数
- 执行对应的中断服务函数
- 返回程序继续执行
2.2 详细流程
当某个中断发生时,中断执行的详细流程如下图所示:
- 中断向量表跳转:根据中断类型跳转到对应的中断服务函数
- 执行中断服务函数:该函数的功能是保护现场、中断处理、恢复现场,中断处理过程首先需要判断中断源,因此需要调用中断解析函数
- 执行中断解析函数:中断解析函数的作用是通过读取中断寄存器的值,获取中断ID,并调用相应中断ID的中断处理函数
- 执行中断处理函数:完成清除中断标志位、实现具体中断功能、重新开启中断等操作
注意 中断服务函数和中断处理函数的区别,个人理解:
- 中断服务函数对应处理器的中断向量表
- 中断处理函数对应具体的中断源
3 IRQ中断系统
ZYNQ IRQ中断系统由三部分组成,即中断源、中断控制器和处理器核心,如下图所示。
3.1 IRQ中断源
中断源有三种:
- SGI
- Software Generated Interrupts,即软件生成中断
- 由软件写入通用中断控制器(GIC)的寄存器而触发
- 在多核处理器系统中用于实现各种通信和同步机制,如任务调度、锁机制、屏障同步等
- 数量16个
- PPI
- Private Perpheral Interrupts,即私有外设中断
- 是CPU的私有外设中断,每个CPU都有一组独立的PPI中断,用于处理来自特定外设的中断请求
- 包括全局计时器、专用看门狗计时器、专用计时器以及从PL端输入的FIQ/IRQ等中断源
- 数量每个CPU核心各5个
- SPI
- Shared Peripheral Interrupts,即共享外设中断
- 是由PS和PL端的共享外设产生的中断
- 用于处理来自多个外设的中断请求,如DMA、UART、Timer、IIC、SDIO等,确保这些外设能够及时向CPU报告状态变化或请求服务
- 数量60个(PS端44个+PL端16个)
3.2 IRQ中断号
每个中断源在中断控制器GIC中都被分配了一个唯一的ID号,即中断ID。这个ID号用于在中断处理过程中唯一地标识和区分不同的中断源。
3.2.1 软件生成中断SGI的ID
3.2.2 私有外设中断PPI的ID
3.2.3 共享外设中断的ID
3.3 IRQ中断控制器
中断控制器GIC(Generic Interrupt Controller)位于中断源和处理器核心之间,是用于管理中断的核心组件。中断控制器GIC通过集中管理中断请求,确保CPU能够高效地处理来自各个外设和内部组件的中断信号,其功能主要包括:
- 中断接收
- GIC接收来自PS(Processing System)和PL(Programmable Logic)端的多个外设和内部组件的中断请求
- 中断请求包括软件生成的中断(SGI)、CPU私有外设中断(PPI)和共享外设中断(SPI)
- 中断仲裁
- 当多个中断请求同时发生时,GIC会根据中断的优先级进行仲裁,确保高优先级的中断能够优先得到处理
- 优先级可以通过软件编程进行配置
- 中断分发
- 仲裁完成后,GIC会将中断请求分发到指定的CPU进行处理
- 对于SPI中断,GIC可以将其分发到一个或两个CPU中的任何一个
- 对于SGI和PPI中断,它们通常被分发到特定的CPU进行处理
- 中断管理
- GIC提供了丰富的中断管理功能,包括中断的使能、禁用、屏蔽、优先级配置等
- 功能允许开发者根据系统的实际需求对中断进行灵活的配置和管理
中断控制器GIC结构如下图所示,ICC(Interrupt Controller CPU)连接SGI和PPI,ICD(Interrupt Controller CPU))连接SPI。
4 在SDK中使用IRQ中断
4.1 IRQ中断处理流程
IRQ中断处理流程如下图所示:
- 外设产生IRQ中断并提交给GIC
- GIC接收IRQ中断并转发给处理器
- 处理器接收IRQ中断后调用IRQ中断服务函数
- IRQ中断服务函数调用外设中断处理函数
在ZYNQ SDK中处理IRQ中断的流程如下图所示:
在ZYNQ SDK中处理IRQ中断的代码如下所示:
//
int Status;
XScuGic_Config *GicConfigPtr; /* Instance of the interrupt controller */
/*
* step1. 初始化中断控制器GIC
*/
GicConfigPtr= XScuGic_LookupConfig(GIC_DEVICE_ID);
if (NULL == GicConfigPtr) {
return XST_FAILURE;
}
Status = XScuGic_CfgInitialize(GicInstPtr, GicConfigPtr, GicConfigPtr->CpuBaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
* step2. 在处理器中初始化异常处理功能
*/
Xil_ExceptionInit();
/*
* step3. 在处理器中为IRQ注册中断服务函数
*/
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, GicInstPtr);
/*
* step4. 在处理器中使能IRQ中断
*/
Xil_ExceptionEnable();
/*
* step5. 在GIC中为外设注册中断处理函数
*/
XScuGic_Connect();
/*
* step6. 在GIC中设置中断优先级和触发类型
*/
XScuGic_SetPriorityTriggerType();
/*
* step7. 在GIC中使能外设中断
*/
XScuGic_Enable();
总结
IRQ中断分三级:
- 外设产生中断
- 设置外设中断触发方式(外设如何检测外部输入信号)
- 使能外设中断(外设能够产生中断)
- 外设检测到外部中断信号后,产生一个新的中断信号提交给GIC
- GIC接收中断
- 注册外设中断处理函数
- 设置外设中断触发方式(GIC如何检测外设提交的中断)
- 使能外设中断(GIC能够接收外设产生的中断)
- GIC接收外设提交的中断信号,经过中断识别、优先级仲裁等处理后,转发或分发给处理器
- 处理器处理中断
- 注册IRQ中断服务函数(注意,处理器能够处理的中断也叫异常,本文为了统一概念,统称为中断,IRQ中断只是其中之一)
- 使能IRQ中断(处理器能够接收GIC提交的IRQ中断)
- 处理器接收GIC提交的IRQ中断后,暂停当前正在执行的程序,调用IRQ中断服务函数和外设中断处理函数来处理中断
4.2 MIO中断实例
PS侧按键触发中断控制PS侧LED灯亮和灭,实例代码如下所示:
/*****************************************************************************/
#include "stdio.h"
#include "xparameters.h"
#include "xgpiops.h"
#include "xscugic.h"
#include "xil_exception.h"
#include "sleep.h"
/*****************************************************************************/
#define GIC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define PS_GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
// GPIO的中断号 52
#define PS_GPIO_INTR_ID XPAR_XGPIOPS_0_INTR
// PS端LED
#define PS_MIO_LED 0
#define PS_MIO_KEY 50
/*****************************************************************************/
XGpioPs_Config *PsGpioConfigPtr;
XScuGic_Config *GicConfigPtr;
XGpioPs PsGpioInst;
XScuGic GicInst;
void SetupInterruptSystem(XScuGic *GicInstPtr, XGpioPs *PsGpioInstPtr, u16 PsGpioIntrId);
void PsGpioIntrHandler(void *CallBackRef);
int KeyPress = 0;
int LedValue = 0;
/*****************************************************************************/
int main()
{
//
printf("GPIO Interrupt Test.\n\r");
// 初始化GPIO外设驱动实例
PsGpioConfigPtr = XGpioPs_LookupConfig(PS_GPIO_DEVICE_ID);
XGpioPs_CfgInitialize(&PsGpioInst, PsGpioConfigPtr, PsGpioConfigPtr->BaseAddr);
// 设置GPIO外设MIO引脚为输出(0=输入,1=输出)
XGpioPs_SetDirectionPin(&PsGpioInst, PS_MIO_LED, 1);
// 设置GPIO外设MIO引脚为输入(0=输入,1=输出)
XGpioPs_SetDirectionPin(&PsGpioInst, PS_MIO_KEY, 0);
// 设置GPIO外设MIO引脚输出使能(0=关闭,1=打开)
XGpioPs_SetOutputEnablePin(&PsGpioInst, PS_MIO_LED, 1);
// 设置GPIO外设中断系统
SetupInterruptSystem(&GicInst, &PsGpioInst, PS_GPIO_INTR_ID);
//
while(1)
{
if(KeyPress == 1)
{
//
KeyPress = 0;
LedValue = ~ LedValue;
// 设置GPIO外设MIO引脚输出值
XGpioPs_WritePin(&PsGpioInst, PS_MIO_LED, LedValue);
// 延时消抖(有效果,但无法完全消除)
usleep(300000);
// 使能GPIO外设MIO引脚中断
XGpioPs_IntrEnablePin(&PsGpioInst, PS_MIO_KEY);
}
}
//
return 0;
}
void SetupInterruptSystem(XScuGic *GicInstPtr, XGpioPs *PsGpioInstPtr, u16 PsGpioIntrId)
{
// 初始化中断控制器GIC
GicConfigPtr = XScuGic_LookupConfig(GIC_DEVICE_ID);
XScuGic_CfgInitialize(GicInstPtr, GicConfigPtr, GicConfigPtr->CpuBaseAddress);
// 在处理器中初始化异常处理功能
Xil_ExceptionInit();
// 在处理器中为IRQ异常注册中断服务函数
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, GicInstPtr);
// 在处理器中使能IRQ中断
Xil_ExceptionEnable(); // <=> Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ)
// 在GIC中为GPIO外设注册中断处理函数
XScuGic_Connect(GicInstPtr, PsGpioIntrId, (Xil_ExceptionHandler)PsGpioIntrHandler, (void *)PsGpioInstPtr);
// 在GIC中设置GPIO外设中断优先级(0xA0)和触发类型(高电平触发)
XScuGic_SetPriorityTriggerType(GicInstPtr, PsGpioIntrId, 0xA0, 0x1);
// 在GIC中使能GPIO外设中断
XScuGic_Enable(GicInstPtr, PsGpioIntrId);
// 设置GPIO外设MIO引脚中断触发类型(下降沿触发)
XGpioPs_SetIntrTypePin(PsGpioInstPtr, PS_MIO_KEY, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
// 使能GPIO外设MIO引脚中断
XGpioPs_IntrEnablePin(PsGpioInstPtr, PS_MIO_KEY);
}
void PsGpioIntrHandler(void *CallBackRef)
{
//
XGpioPs *PsGpioInstPtr = (XGpioPs *)CallBackRef;
// 禁用GPIO外设MIO引脚中断
XGpioPs_IntrDisablePin(PsGpioInstPtr, PS_MIO_KEY);
// 清除GPIO外设MIO引脚中断状态
XGpioPs_IntrClearPin(PsGpioInstPtr, PS_MIO_KEY);
//
KeyPress = 1;
//
printf("Interrupt Detected.\r\n");
}