中断
stm32中断
ARM芯片从0x00000000开始运行, 最开始的时候设置有中断向量表, 用于主要用来记录中断对应的中断函数
代码最开始的地方存放堆栈栈顶指针, 之后存放各种中断函数
中断向量偏移
ARM从0x00000000开始的但是stm32是从0x80000000地址开始的, 如果代码一定要从别的位置开始, 就需要告诉内核, 设置中断向量偏移,
设置内核SCB的VTOR寄存器为新的中断向量表起始地址就可以了
NVIC终端管理
使能关闭终端, 设置优先级
中断服务函数的编写
Cortex-A7中断系统
中断向量表
只有八个, 位于地址的最开始部分
- 复位: 上电以后第一个, 初始化工作
- 未定义指令: 指令不能识别
- 软中断: SWI指令中断, LInux使用进入内核, SVC特权模式
- 指令预取中止中断: 预取指令出错
- 数据访问中止中断: 数据访问出错
- 未使用:
- IRQ中断: 外部中断, 所有的外设中断
- FIQ中断: 需要快速处理的终端
我们外面使用的是IRQ
没有已经写好的中断向量表, 用户自己定义
所有的裸机中断偏移都是在0x87800000开始的, 在main函数最开始的地址设置
GIC中断控制器
和NVIC一样, 用来控制中断, 有四个版本, V1已经被放弃了, V2是给ARMv7-A架构使用的, Cortex-A7, Cortex-A9等, V3,V4是给ARMv8-A/R使用的, 64位芯片
GIC-V2最多支持八个核
接收终端之后发送给内核, 但是内核只提供了四个信号,
- VFIQ: 虚拟快速
- VIRQ: 虚拟快速
- FIQ: 快速中断
- IRQ: 外部快速
cpsid i
: 禁止IRQ
cpsie i
: 使能IRQ
cpsid f
: 禁止FIQ
cpsie f
: 使能FIQ
或者使用寄存器
GICD_ISENABLERn
和GICD_ ICENABLERn
, 一个bit控制一个中断ID的使能, 16个GICD_ISENABLER寄存器来完成中断的使能。同理,也需要16个GICD_ICENABLER寄存器来完成中断的禁止。
GIC把所有的中断源分为了三种
- SPI: 共享中断, 所有的核共享的中断
- PPI: 私有中断, 独有的中断
- SGI: 软件终端
中断ID
为了区分不同的中断, 最多支持1020个, 号码为0-1019, 包含了以上的三大类, 0-15给SGI, 16-31给PPI, 剩下的SPI
6ULL支持128个中断ID, ,加上前面属于PPI和SGI的32个ID,I.MX6U的中断源共有160个中断
前32个作为CPU的中断,
中断函数编写
IRQ中断服务函数的编写, 另一个是在IRQ中断服务函数中查找运行的具体函数
编写按键中断例程
中断向量表
在start.s
文件中编写中断向量表, 复位中断函数, IRQ中断服务函数
编写复位中断函数
关闭I.D Cache和MMU
CP15协处理器: CP15寄存器一般用于存储系统管理, 在中断中也会用, 有16个32位寄存器
c0-c15有特定的指令访问
- MCR: 读取, 将CP15协处理器中的寄存器数据读到ARM寄存器中
- MRC: 写入, 将ARM寄存器的数据写入到CP15协处理器寄存器中
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
cond
:指令执行的条件码,如果忽略的话就表示无条件执行
opc1
:协处理器要执行的操作码
Rt:ARM
源寄存器,要写入到CP15寄存器的数据就保存在此寄存器中
CRn
:CP15协处理器的目标寄存器。
CRm
:协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将CRm
设置为C0,否则结果不可预测。opc2:可选的协处理器特定操作码,当不需要的时候要设置为0
在Cortex-A7 Technical… 手册中有每一个寄存器操作的指令, CRn=c1,opc1=0,CRm=c0,opc2=0
的时候就表示此时的c1就是SCTLR寄存器,也就是系统控制寄存器
读取SCTLR寄存器, 采用读改写, 系统控制寄存器, bit0打开关闭MMU, bit1对齐控制位, bit2D Cache打开关闭, bit 11分支预测, bit12 I Cache
中断向量偏移
把新的中断向量表首地址写入CP15协处理器的VBAR寄存器
从图中可以知道, CRn=c12,opc1=0,CRm=c0,opc2=0的时候就表示此时c12为VBAR寄存器
ldr r0, =0X87800000;r0=0X87800000
MCR p15, 0, r0, c12, c0, 0;将r0里面的数据写入到c12中
中断服务函数
保存寄存器
mrc p15, 4, r1, c15, c0, 0
读取CP15的寄存器CBAR, CBAR保存了GIC的控制寄存器首地址, GIC首地址偏移0x1000-0x1fff为GIC分发器, 0x2000-0x3fff为CPU接口端, 这意味着我们可以访问GIC控制器
分发器端口:处理各个中断事件的分发问题,也就是中断事件应该发送到哪个CPU Interface上去。分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到CPU接口端
- 全局中断使能控制
- 控制每一个中断的使能或者关闭
- 设置每个中断的优先级
- 设置每个中断的目标处理器列表
- 设置每个外部中断的触发模式:电平触发或边沿触发
- 设置每个中断属于组0还是组1
CPU Interface(CPU接口端):每个CPU Core都可以在GIC中找到一个与之对应的CPU Interface
从CPU相关的寄存器可以获得中断号, GICC_IAR的bit0-9保存中断ID, 可以使用这些ID得到对应的中断处理函数
- 使能或者关闭发送到CPU Core的中断请求信号
- 应答中断。
- 通知中断处理完成
- 设置优先级掩码,通过掩码来设置哪些中断不需要上报给CPU Core
- 定义抢占策略
- 当多个中断到来的时候,选择优先级最高的中断通知给CPU Core
得到之后先进入SVC模式, 允许其他的中断,保存lr(上一级返回地址)寄存器, 然后跳转到处理的函数, 返回以后出栈这一个, 因为不同模式栈不同, 然后返回到IRQ模式, 恢复现场
在处理完函数之后, 需要把GICC_IAR写入GICC_EOIR寄存器里面
最后在返回的时候
subs pc, lr, #4 /* 将lr-4赋给pc */
在ARM的指令是三级流水线:取指、译指、执行,pc指向的是正在取值的地址,这就是很多书上说的pc=当前执行指令地址+8, 就需要将lr-4赋值给pc,也就是pc=0X2004,从指令“MOV R2,R3”开始执行
GPIO中断设置
GPIO的设置
GPIOx_ICR1, GPIOx_ICR2
每一个寄存器使用两个位来控制中断, 分别控制低16高16位, 设置触发方式
- 00LOW_LEVEL — Interrupt n is low-level sensitive.低电平
- 01HIGH_LEVEL — Interrupt n is high-level sensitive.高电平
- 10RISING_EDGE — Interrupt n is rising-edge sensitive.上升沿
- 11FALLING_EDGE — Interrupt n is falling-edge sensitive下降沿
这里的按键设置为下降沿,
GPIOx_IMR
:掩码寄存器, 控制是不是屏蔽某一位的中断
- 0MASKED — Interrupt n is disabled.
- 1UNMASKED — Interrupt n is enabled
GPIOx_EDGE_SEL
:用来控制上升沿以及下降沿都会触发中断, 设置的时候ICR寄存器不会发挥作用
GPIOx_ISR
: 中断标志位, 处理完以后需要清除这个寄存器, 写1进行清除
GIC的设置
在手册的第三章查找对应的中断, 然后进行中断号使能
-
66 gpio1-Combined interrupt indication for GPIO1 signal 0 throughout15
-
67 gpio1-Combined interrupt indication for GPIO1 signal 16 throughout31
-
这里的数字应该加上32, 这里中断号为99
-
设置中断的优先级
-
添加中断的优先级
实现
完成GPIO中断初始化
使用寄存器号使能GICD_ISENABLER寄存器
关闭掩码
总结
中断向量表必须在程序开始的位置
然后设置向量表的偏移, 设置中断时候的栈
打开中断GIC的相关的中断, 设置中断产生的方法
在中断开始之后, 首先保护现场, , 获取中断的编号, 然后进入判断的函数, 根据得到的中断数进行中断函数调用
返回的时候清除中断的标志位
返回恢复现场