I.MX6U 裸机开发13.中断、中断向量表、中断函数

  • 一、STM32 的中断介绍
  • 1. 中断概念
  • 2. 中断向量表
  • 3. 中断向量偏移
  • 4. NVIC 嵌套向量中断控制器
  • 二、Cortext-A7中断系统
  • 1. Cortext-A7的中断向量表
  • 2. 中断向量偏移
  • 3. GIC (Generic Interrupt Controller) 中断控制器
  • (1)GIC V2整体框图
  • (2)GIC 组件
  • (3)GIC 版本
  • (4)GIC 中断源分类
  • (5)中断ID
  • (6) 中断服务函数
  • 三、中断向量表编写
  • 1. 原理图
  • 2. 创建项目
  • 3. 启动文件start.S 添加 中断向量表
  • 4. 实现中断服务函数
  • 5. 关闭I Cache , D Cache , MMU
  • (1)关闭I Cache, D Cache, MMU的作用
  • (2)CP15 协处理器
  • Control Register(c1)
  • Translation Table Register(c2)
  • Domain Access Control Register(c3)
  • Fault Status Registers(c5)
  • Fault Address Register(c6)
  • Cache Operation Registers(c7)
  • TLB Operations Registers (c8)
  • 操作CP15,使用下面指令:
  • MCR指令
  • MRC
  • (3)代码实现
  • (4)设置中断向量偏移
  • 6. 设置处理器工作模式下的SP指针
  • 7. 清除BSS段
  • 8. 开关中断


I.MX6U 裸机开发13.中断、中断向量表、中断函数_嵌入式硬件

一、STM32 的中断介绍

本文先对比在STM32的中断处理机制。

1. 中断概念

中断是指在程序执行过程中,当某个事件发生时,CPU 暂停当前的程序执行,转而去执行与该事件相关的中断服务程序(ISR)。中断可以分为外部中断和内部中断。外部中断通常由外部设备(如按键、传感器)触发,而内部中断则由内部事件(如定时器溢出、ADC 转换完成)触发。

2. 中断向量表

中断向量表是一个存储在特定内存地址处的表格,其中包含了每个中断源对应的中断服务程序的入口地址。在 STM32 微控制器中,中断向量表通常位于内存的起始地址(0x00000000)。每个中断源在中断向量表中都有一个固定的位置,CPU 根据中断源的编号从中断向量表中读取对应的中断服务程序的地址,并跳转到该地址执行中断服务程序。

3. 中断向量偏移

中断向量偏移是指中断向量表在内存中的起始地址的偏移量。在某些情况下,可能需要将中断向量表移动到内存的其他位置,这时就需要设置中断向量偏移。STM32 微控制器通过 SCB->VTOR 寄存器来设置中断向量表的基地址,从而实现中断向量偏移。

4. NVIC 嵌套向量中断控制器

NVIC 在 ARM Cortex-M 中负责管理中断和异常。它通过提供嵌套中断、优先级级别和向量表重定位等功能,提高了中断处理的效率和灵活性。
NVIC 主要功能如下:

  1. 嵌套中断:允许高优先级中断抢占低优先级中断,从而实现更快速和高效的中断处理。
  2. 优先级级别:支持多个中断优先级级别,允许对中断处理进行精细控制。
  3. 向量表重定位:允许将中断向量表重定位到不同的内存位置,这对于引导加载程序和动态中断管理非常有用。
  4. 中断屏蔽:提供启用或禁用特定中断的机制,允许选择性地处理中断。

二、Cortext-A7中断系统

1. Cortext-A7的中断向量表

Cortex-A 处理器的中断向量表通常包含以下几种中断和异常向量:

向量地址

向量名称

描述

0x00000000

Reset Vector

复位向量

0x00000004

Undefined Instruction Vector

未定义指令异常向量

0x00000008

Software Interrupt Vector

软件中断向量

0x0000000C

Prefetch Abort Vector

预取指令中止向量

0x00000010

Data Abort Vector

数据中止向量

0x00000014

Reserved

保留

0x00000018

IRQ Vector

外部中断请求向量

0x0000001C

FIQ Vector

快速中断请求向量

其中我们重点关注的是: IRQ。 Cortext-A的中断向量表需要开发者自己定义。

2. 中断向量偏移

对于Cortext-A处理器,中断向量表通常位于内存的起始地址(0x00000000)。
程序的入口地址,通常是复位向量所在的地址,在Cortext-A中,复位向量是中断向量表的第一个条目,即位于 0x00000000,处理器复位时,会从该地址读取程序入口地址。

本系列文章裸机历程,程序入口地址是 0x87800000,不是0x00000000,因此需要设置中断向量偏移。

3. GIC (Generic Interrupt Controller) 中断控制器

GIC 类似于NVIC。
GIC 提供了灵活的中断管理机制,包括中断优先级、中断分发和中断处理等功能。

(1)GIC V2整体框图

I.MX6U 裸机开发13.中断、中断向量表、中断函数_寄存器_02

(2)GIC 组件

  • Distributor:负责中断的分发和管理,包括中断的使能、优先级设置和目标 CPU 核心的选择。
  • CPU Interface:每个 CPU 核心都有一个 CPU 接口,用于接收和处理中断请求。

(3)GIC 版本

GIC有多个版本,主要区别如下:

  • GICv1:基本中断管理功能。
  • GICv2:增强的中断管理功能和多核支持。
  • GICv3:扩展的中断管理功能和系统寄存器接口。
  • GICv4:增加了对虚拟化环境的支持。

IMX6ULL 使用GICV2,主要特性:

  • 增强功能:在 GICv1 的基础上增加了更多的中断管理功能。
  • 中断数目:支持最多 1020 个中断。
  • 中断优先级:支持 256 级中断优先级。
  • 多核支持:支持多核处理器的中断分发和管理。

(4)GIC 中断源分类

GIC 中的中断源可以分为以下几类:

中断类型

描述

SGI (Software Generated Interrupts)

软件生成的中断,通常用于进程间通信。

PPI (Private Peripheral Interrupts)

私有外设中断,每个 CPU 核心都有独立的 PPI。

SPI (Shared Peripheral Interrupts)

共享外设中断,所有 CPU 核心共享的中断源。

LPI (Locality-specific Peripheral Interrupts)

特定于 GICv3 和 GICv4,用于支持更大规模的中断,IMX6ULL不支持。

(5)中断ID

中断ID(Interrupt ID)是用于标识特定中断源的唯一标识符。每个中断源在中断控制器中都有一个唯一的中断ID,用于区分不同的中断源。

  • ID0~ID15:给SGI
  • ID16~ID31:给PPI
  • ID32~ID1019:给SPI

对于SPI,由芯片厂商定义 。 对于IMX6ULL,在参考手册P183页有详细描述。

I.MX6U 裸机开发13.中断、中断向量表、中断函数_单片机_03


根据文档描述, IMX6ULL 支持128个外部中断。前32个给CPU使用。

(6) 中断服务函数

在 i.MX6ULL 处理器中,中断服务函数(ISR, Interrupt Service Routine)用于处理中断事件。以下是如何定义和使用中断服务函数的步骤:

  1. 定义中断服务函数:编写中断处理代码。
  2. 注册中断服务函数:将中断服务函数注册到特定的中断源。
  3. 使能中断:配置中断控制器以使能特定中断。

三、中断向量表编写

本文要实现GPIO捕获按键中断。

1. 原理图

I.MX6U 裸机开发13.中断、中断向量表、中断函数_寄存器_04


I.MX6U 裸机开发13.中断、中断向量表、中断函数_中断向量表_05


key0 使用 UART1_CTS IO。

2. 创建项目

从上章节复制文件夹。
修改项目名称为 gpio_irq。

3. 启动文件start.S 添加 中断向量表

I.MX6U 裸机开发13.中断、中断向量表、中断函数_中断处理_06


首先是复位中断,写代码:

_start:
    ldr pc, =Reset_Handler

解释 :

  • ldr:加载指令,用于将一个值加载到寄存器中。
  • pc:程序计数器寄存器,指向当前执行的指令地址。
  • =Reset_Handler:表示 Reset_Handler 函数的地址。

这条指令的效果是将 Reset_Handler 函数的地址加载到 pc 寄存器中,从而实现跳转到 Reset_Handler 函数执行。

类似的,添加其它中断向量:

_start:
    ldr pc, =Reset_Handler      /* 设置复位中断处理函数*/
    ldr pc, =Undefine_Handler   /* 设置未定义指令中断处理函数*/
    ldr pc, =SVC_Handler        /* 设置SVC中断处理函数*/
    ldr pc, =PreAbort_Handler   /* 设置预取中断处理函数*/
    ldr pc, =DataAbort_Handler  /* 设置数据中断处理函数*/
    ldr pc, =IRQ_Handler        /* 设置IRQ中断处理函数*/
    ldr pc, =FIQ_Handler        /* 设置FIQ中断处理函数*/

4. 实现中断服务函数

先写上空函数:

_start:
    ldr pc, =Reset_Handler      /* 设置复位中断处理函数*/
    ldr pc, =Undefine_Handler   /* 设置未定义指令中断处理函数*/
    ldr pc, =SVC_Handler        /* 设置SVC中断处理函数*/
    ldr pc, =PreAbort_Handler   /* 设置预取中断处理函数*/
    ldr pc, =DataAbort_Handler  /* 设置数据中断处理函数*/
    ldr pc, =IRQ_Handler        /* 设置IRQ中断处理函数*/
    ldr pc, =FIQ_Handler        /* 设置FIQ中断处理函数*/

Reset_Handler:
    b Reset_Handler

Undefine_Handler:
    b Undefine_Handler

SVC_Handler:
    b SVC_Handler

PreAbort_Handler:
    b PreAbort_Handler

DataAbort_Handler:
    b DataAbort_Handler

IRQ_Handler:
    b IRQ_Handler

FIQ_Handler:
    b FIQ_Handler

5. 关闭I Cache , D Cache , MMU

(1)关闭I Cache, D Cache, MMU的作用

在启动代码中关闭I Cache(指令缓存)、D Cache(数据缓存)和MMU(内存管理单元),可以确保系统在初始化阶段的稳定性和可预测性。以下是具体原因:

  1. 确保内存一致性:在系统启动时,内存可能尚未完全初始化。如果缓存和MMU开启,可能会导致内存访问不一致的问题。关闭缓存和MMU可以确保所有内存访问都是直接的、可预测的。
  2. 简化初始化过程:在初始化阶段,关闭缓存和MMU可以简化内存管理和地址映射的复杂性。这样可以避免在初始化过程中处理缓存一致性和地址转换的问题。
  3. 避免缓存污染:在系统启动时,缓存中的数据可能是无效的或过时的。关闭缓存可以避免在初始化过程中使用这些无效数据。
  4. 确保正确的外设初始化:在初始化外设时,通常需要直接访问物理地址。关闭MMU可以确保所有地址访问都是物理地址,避免地址转换带来的问题。

(2)CP15 协处理器

在ARM架构中,CP15(协处理器15)是一个系统控制协处理器,用于管理系统控制寄存器,包括缓存和MMU的控制。通过访问CP15的寄存器,可以启用或禁用I Cache、D Cache和MMU。

CP15主要的寄存器有:

Control Register(c1)

控制MMU、缓存、对齐检查、分支预测等功能,常用位有:

  • 0:MMU使能
  • 2:D Cache 使能
  • 12: I Cache 使能
Translation Table Register(c2)

存储一级页表的基地址,用于地址转换。

Domain Access Control Register(c3)

控制域访问权限 , 用于内存保护。

Fault Status Registers(c5)

存储最近一次发生的故障状态信息。

Fault Address Register(c6)

存储最近一次发生故障的地址。

Cache Operation Registers(c7)

控制缓存操作,如无效化、清除等。

TLB Operations Registers (c8)

控制 TLB(Translation Lookaside Buffer)操作,如无效化等。

操作CP15,使用下面指令:
  • MRC:读CP15寄存器
  • MCR:写CP15寄存器
MCR指令

语法 : MCR p15, <op1>, <Rt>, <CRn>, <CRm>, <op2>

  • p15:协处理器编号,CP15是系统控制协处理器。
  • :操作码1。
  • :源寄存器,数据将从这个寄存器写入协处理器。
  • :主操作寄存器。
  • :次操作寄存器。
  • :操作码2。

示例:

MCR p15, 0, r0, c1, c0, 0   /* 将r0中的数据写入CP15的c1寄存器 */
MRC

语法 : MRC p15, <op1>, <Rt>, <CRn>, <CRm>, <op2>

  • p15:协处理器编号,CP15是系统控制协处理器。
  • :操作码1。
  • :目标寄存器,数据将被读取到这个寄存器。
  • :主操作寄存器。
  • :次操作寄存器。
  • :操作码2。
    示例:
MRC p15, 0, r0, c1, c0, 0   /* 从CP15的c1寄存器读取数据到r0 */

(3)代码实现

MRC P15, 0, R0, C1, C0, 0   /* 从CP15的c1寄存器读取当前值到r0 */
    BIC R0, R0, #0x0001         /* 清除r0的第0位,关闭MMU */
    BIC R0, R0, #(1<<1)         /* 清除r0的第1位,关闭对齐检查 */
    BIC R0, R0, #(1<<2)         /* 清除r0的第2位,关闭D Cache */
    BIC R0, R0, #(1<<12)        /* 清除r0的第12位,关闭I Cache */
    BIC R0, R0, #(1<<11)        /* 清除r0的第11位,关闭高速缓存(分支预测) */
    MCR P15, 0, R0, C1, C0, 0   /* 将修改后的值写回CP15的c1寄存器 */

(4)设置中断向量偏移

/** (设置中断向量偏移)跳转到用户程序入口点 */
    LDR R0, =0x87800000
    DSB
    ISB
    MCR P15, 0, R0, C12, C0, 0
    DSB
    ISB

6. 设置处理器工作模式下的SP指针

在使用中断时,需要为IRQ模式设置堆栈指针(SP),以确保在处理中断时有一个有效的堆栈空间。
最好为所有处理器模式(如FIQ、SVC、Abort、Undefined、System、User)设置堆栈指针,以确保在这些模式下执行代码时有足够的堆栈空间。
下面是设置代码:

/** 设置处理器进入IRQ模式 */
    MRS R0, CPSR        /* 读取 CPSR 到R0*/
    BIC R0, R0, #0x1f   /* 清除 CPSR 的bit4-0*/
    ORR R0, R0, #0x12   /* 使用 IRQ 模式*/
    MSR CPSR, R0        /* 将 R0 写入到 CPSR*/
    LDR PC, =0x80600000  /*  设置 IRQ模式的PC,即用户程序入口点 */

    /* 设置处理器进入SYS模式 */
    MRS R0, CPSR        /* 读取 CPSR 到R0*/
    BIC R0, R0, #0x1f   /* 清除 CPSR 的bit4-0*/
    ORR R0, R0, #0x1f   /* 使用 SYS 模式*/
    MSR CPSR, R0        /* 将 R0 写入到 CPSR*/
    LDR PC, =0x80400000  /*  设置 SYS模式的PC,即用户程序入口点 */


    /* 设置处理器进入SVC模式 */
    MRS R0, CPSR        /* 读取 CPSR 到R0*/
    BIC R0, R0, #0x1f   /* 清除 CPSR 的bit4-0*/
    ORR R0, R0, #0x13   /* 使用 SVC 模式*/
    MSR CPSR, R0        /* 将 R0 写入到 CPSR*/
    LDR PC, =0x80200000  /*  设置 SVC模式的PC,即用户程序入口点 */

7. 清除BSS段

/*清除BSS段*/
    LDR R0, _bss_start
    LDR R1, _bss_end
    MOV R2, #0
bss_loop:
    STMIA R0!, {R2}
    CMP R0, R1      /* 比较R0和R1里面的值 */
    BLE bss_loop    /*如果R0地址小于等于R1,继续清除bss段*/

8. 开关中断

在操作 i Cache , d Cache,MMU、清BSS段时,先关闭总中断,使用指令:

cpsid i

打开中断,使用:

cpsie i

最后完成的start.S

.global _start
.global _bss_start
_bss_start:
    .word __bss_start

.global _bss_end
_bss_end:
    .word __bss_end

_start:
    LDR PC, =Reset_Handler      /* 设置复位中断处理函数*/
    LDR PC, =Undefined_Handler   /* 设置未定义指令中断处理函数*/
    LDR PC, =SVC_Handler        /* 设置SVC中断处理函数*/
    LDR PC, =PreAbort_Handler   /* 设置预取中断处理函数*/
    LDR PC, =DataAbort_Handler  /* 设置数据中断处理函数*/
	LDR	PC, =NotUsed_Handler	/* 未使用中断					*/
    LDR PC, =IRQ_Handler        /* 设置IRQ中断处理函数*/
    LDR PC, =FIQ_Handler        /* 设置FIQ中断处理函数*/

Reset_Handler:
	cpsid i						/* 关闭全局中断 */
    MRC P15, 0, R0, C1, C0, 0   /* 从CP15的c1寄存器读取当前值到R0 */
    BIC R0, R0, #0x0001         /* 清除R0的第0位,关闭MMU */
    BIC R0, R0, #(1<<1)         /* 清除R0的第1位,关闭对齐检查 */
    BIC R0, R0, #(1<<2)         /* 清除R0的第2位,关闭D Cache */
    BIC R0, R0, #(1<<12)        /* 清除R0的第12位,关闭I Cache */
    BIC R0, R0, #(1<<11)        /* 清除R0的第11位,关闭高速缓存(分支预测) */
    MCR P15, 0, R0, C1, C0, 0   /* 将修改后的值写回CP15的c1寄存器 */

    /** (设置中断向量偏移)跳转到用户程序入口点 */
    LDR R0, =0x87800000
    DSB
    ISB
    MCR P15, 0, R0, C12, C0, 0
    DSB
    ISB

    /*清除BSS段*/
    LDR R0, _bss_start
    LDR R1, _bss_end
    MOV R2, #0
bss_loop:
    STMIA R0!, {R2}
    CMP R0, R1      /* 比较R0和R1里面的值 */
    BLE bss_loop    /*如果R0地址小于等于R1,继续清除bss段*/

    /** 设置处理器进入IRQ模式 */
    MRS R0, CPSR        /* 读取 CPSR 到R0*/
    BIC R0, R0, #0x1f   /* 清除 CPSR 的bit4-0*/
    ORR R0, R0, #0x12   /* 使用 IRQ 模式*/
    MSR CPSR, R0        /* 将 R0 写入到 CPSR*/
    LDR SP, =0x80600000  /*  设置 IRQ模式的PC,即用户程序入口点 */

    /* 设置处理器进入SYS模式 */
    MRS R0, CPSR        /* 读取 CPSR 到R0*/
    BIC R0, R0, #0x1f   /* 清除 CPSR 的bit4-0*/
    ORR R0, R0, #0x1f   /* 使用 SYS 模式*/
    MSR CPSR, R0        /* 将 R0 写入到 CPSR*/
    LDR SP, =0x80400000  /*  设置 SYS模式的PC,即用户程序入口点 */


    /* 设置处理器进入SVC模式 */
    MRS R0, CPSR        /* 读取 CPSR 到R0*/
    BIC R0, R0, #0x1f   /* 清除 CPSR 的bit4-0*/
    ORR R0, R0, #0x13   /* 使用 SVC 模式*/
    MSR CPSR, R0        /* 将 R0 写入到 CPSR*/
    LDR SP, =0x80200000  /*  设置 SVC模式的PC,即用户程序入口点 */

	cpsie i				/* 打开全局中断 */

    B main              /* 跳转到C语言main函数*/

Undefined_Handler:
	LDR R0, =Undefined_Handler
	BX R0

SVC_Handler:
	LDR R0, =SVC_Handler
	BX R0

PreAbort_Handler:
	LDR R0, =PreAbort_Handler
	BX R0

DataAbort_Handler:
	LDR R0, =DataAbort_Handler
	BX R0

/* 未使用的中断 */
NotUsed_Handler:
	LDR R0, =NotUsed_Handler
	BX R0

IRQ_Handler:
	LDR R0, =IRQ_Handler
	BX R0

FIQ_Handler:
	LDR R0, =FIQ_Handler
	BX R0