OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 6393|回复: 5

<新手向>第二期 裸机开发 P33 第15.4讲 IRQ中断函数编写 随堂笔记

[复制链接]

5

主题

17

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
254
金钱
254
注册时间
2020-3-22
在线时间
35 小时
发表于 2020-3-30 02:49:50 | 显示全部楼层 |阅读模式
/********************************************/
时间        :2020/03/29/
作者        :YOKI
导师        :正点原子 左忠凯
硬件        :正点原子 ALPHA I.MX LINUX 开发板
/*******************************************/
写在前面的话:
中断函数涉及到 ARM Cortex-A7内核 构架,建议学习本视频前回到 第6章 去 复习 一下构架的有关知识!
建议学习本视频前回到 第6章 去复习一下构架的有关知识!
回到 第6章 去复习 一下构架的有关知识!
重要的事情说三遍! 不然只有两个字:懵比!!!!



重要知识点:

ARM Cortex-A7 构架 提供了 16个 32位 通用寄存器( R0-R15)和2个 程序状态寄存器

IRQ(Interrupt request) 模式下各寄存器的含义:
R0-R12      通用寄存器
R13         SP_irq   SP指针(IRQ模式下)
R14         LR_irq   连接寄存器 (IRQ模式下)    存放 子函数的返回地址
R15         PC       程序计数器               将 LR_irq 的值 传递给 PC 可实现程序跳转

CPSR        当前程序状态寄存器
SPSR_irq    备份程序状态寄存器     

   

CP15协处理器 指令
MRC : 将 CP15协处理器 的寄存器数据 写到 ARM 寄存器
MCR : 将 ARM 处理器 的寄存器数据 写到 CP15 寄存器

MRC : 读 CP15 寄存器    MCR 写 CP15 寄存器
使用格式:
MCR p15,<opc1>,<Rt>,<CRn>,<CRm>,<opc12>
mrc p15,     4,        r1,      c15,      c0,        0      
@ 从CP15的 C0寄存器 内的值 写到 R1寄存器 中
@ Cortex-A7 Technical ReferenceManua.pdf P68 P138
@ 含义 读取 CBAR 寄存器(CBAR 寄存器保存了 GIC 寄存器的首地址)
@ GIC寄存器(通用中断控制寄存器)参考:《Cortex-A7 Technical ReferenceManua.pdf》 P178



IRQ中断服务的编写:
学习过 51 或者 STM32 的人 都知道 中断函数 是在 当前程序运行过程中,由中断事件(Event)触发后进入一段临时运行的程序,
这段程序执行完成后要回到中断发生前的程序运行状态继续执行原来的程序。这就要求,从当前程序进入到中断服务函数后首先要进行
的一件事是 保护现场 :保存当前程序运行的状态或数据 即保存CPU中相关寄存器的数据 IMX6ULL 采用 Cortex-A7 构架
Cortex-A7 构架 有 9 种运行模式,本节只以 IRQ(Interrupt request) 模式 进行讲解
参考:正点原子I.MX6U Linux 嵌入式驱动开发指南 第6章 Cortex-A7 MPCore构架 / 第17章 GPIO中断实验

IRQ(Interrupt request) 模式下各寄存器的含义:
R0-R12      通用寄存器
R13         SP_irq   SP指针(IRQ模式下)
R14         LR_irq   连接寄存器 (IRQ模式下)    存放 子函数的返回地址
R15         PC       程序计数器               将 LR_irq 的值 传递给 PC 可实现程序跳转

CPSR        当前程序状态寄存器
SPSR_irq    备份程序状态寄存器



IRQ_Interrupt_Handler:
    push {lr}                                      @ 保存 lr 地址 压栈 (存放IRQ_Interrupt_Handler 子程序返回地址)            压栈(栈号:0)
    push {r0-r3,r2}                             @ 保存 r0-r3, r12 寄存器 (其实是为了腾出R0-R3,R12的空间给后面使用)  压栈(栈号:1)
                                                       @ 保护 当前现场
    mrs  r0,spsr                                  @ 将 spsr寄存器数据存入寄存器r0
    push {r0}                                     @ 保存 r0 即 保存 spsr 寄存器 (保存 备份程序状态寄存器)                        压栈(栈号:2)
                                                       @ 保护 当前现场

    mrc p15, 4, r1, c15, c0, 0               @ 从CP15的C0寄存器内的值到R1寄存器中  (读取 CBAR 寄存器)
                                                       @ 含义 读取 CBAR 寄存器(CBAR 寄存器保存了 GIC 寄存器的首地址)
                                                       @ 获取了 GIC寄存器(通用中断控制寄存器)的基地址 就可以设置GIC相关的寄存器了
                                                       @ R1 中当前存放着 GIC 寄存器的首地址
    参考:《Cortex-A7 Technical ReferenceManua.pdf》:4.2.16 P68  P138
                                
    /* GIC 内部存储器映射表 */  
    0X0000——0X0FFF          Reserved        保留位
    0X1000——0X1FFF          Distributor     分发器(分配器)
    0X2000——0X3FFF          CPU interface   CPU接口
    0X4000——0X4FFF          Virtual interface control/common base address   虚拟接口控制/公共基地址
    0X5000——0X5FFF          Virtual interface control/processsor-specific base address   虚拟接口控制/专用基地址
    0X5000——0X5FFF          Virtual CPU interface    虚拟CPU接口控制
    参考:《Cortex-A7 Technical ReferenceManua.pdf》:8.2.1 P178



    add r1, r1, #0X2000                         @ GIC基地址加0X2000,也就是GIC的CPU接口端基地址  
    ldr r0, [r1, #0XC]                             @ GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
                                                          @ GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
                                                          @ 这个中断号来决定调用哪个中断服务函数
                                                          @ 将 GICC_IAR寄存器 的值 存入 R0

    /* CPU interface CPU接口 概要表 */  
     地址      寄存器名称     权限       复位值                           简介
    0X0000   GICC_CTLR    读/写       复位值:0X00000000     CPU接口控制寄存器
    0X0004   GICC_PMRn   读/写       复位值:0X00000000     中断优先级标志寄存器
    0X0008   GICC_BPR      读/写       复位值:0X00000002     
                                                                0X00000003     二进制点位寄存器?

    0X000C   GICC_IAR       只读        复位值:0X000003FF     中断确认寄存器                              
    参考:《Cortex-A7 Technical ReferenceManua.pdf》:8.3.3 P188
   
    /* GICC_IAR中断确认寄存器说明 */
    GICC_IAR (32bit)
    bit[31-13]:保留位    bit[10-12]:CPU ID    bit[9-0]:Interrupt ID
    也就是说 GICC_IAR寄存器的 bit0-bit9 用来保存中断号
    参考:《ARM Generic Interrupt controller (ARM GIC控制器)V2.0.pdf》:4.4.4 P135



    push {r0, r1}                            @ 压栈 保存r0,r1 内容(腾出 R0 R1 内存空间)                              压栈(栈号:3)
                                                   @ R0 保存着 GICC_IAR寄存器 的值
                                                   @ R1 保存着 GIC的CPU接口端基地址

    cps #0x13                                @ 对 cpsr 寄存器 写入 0X13  进入SVC模式,允许其他中断再次进去

    push {lr}                                  @ 保存SVC模式的lr寄存器                                                             压栈(栈号:4)
    ldr r2, =system_irqhandler         @ 加载C语言中断处理函数到r2寄存器中                       
    blx r2                                       @ 调用 C语言中断处理函数system_irqhandler 带有一个参数,保存在R0寄存器中
                                                   @ C语言编写的 system_irqhandler(unsigned int giccIar) 是具体的中断处理函数
                                                   @ 这个函数的参数 giccIar: GICC_IAR寄存器 的值



    pop {lr}                                    @ 执行完C语言中断服务函数,lr出栈                                            出栈(栈号:4)
    cps #0x12                                 @ 对 cpsr 寄存器 写入 0X12  进入IRQ模式



    pop {r0, r1}                               @ R0 保存着 GICC_IAR寄存器 的值                                             出栈(栈号:3)
                                                    @ R1 保存着 GIC的CPU接口端基地址
    str r0, [r1, #0X10]                      @ 将 R1 的地址加上一个偏移量 0X10 (GICC_EOIR 地址)
                                                     @ 把 R0 的内容(GICC_IAR寄存器的值) 写入 R1 (GICC_EOIR 地址)  
   
    /* CPU interface CPU接口 概要表 */
    0X0010   GICC_EOIR    只写       复位值:无     中断结束寄存器                              
    参考:《Cortex-A7 Technical ReferenceManua.pdf》:8.3.3 P189
   
    /* GICC_E0I 中断结束寄存器 说明 */
    GICC_E0I (32bit)
    bit[31-13]:保留位    bit[10-12]:CPU ID    bit[9-0]:EOIINT ID
    GICC_E0I寄存器的 bit0-bit9 和 GICC_IAR寄存器的 bit0-bit9 对应
    即 当中断程序执行完后要把 GICC_IAR寄存器的值(重点是 Interrupt ID )写入 GICC_E0I寄存器         标志中断结束
    参考:《ARM Generic Interrupt controller (ARM GIC控制器)V2.0.pdf》:4.4.5 P138



    pop {r0}                                @ 出栈(栈号:2)                                                                              出栈(栈号:2)  
                                                @ (栈号:2) r0 保存着中断执行前spsr寄存器 (备份程序状态寄存器)的值
    msr spsr_cxsf, r0                    @ 将 r0 的值写入 spsr寄存器  (恢复现场)
    pop {r0-r3, r12}                     @ 出栈(栈号:1)   恢复中断执行前 r0-r3,r12 的值 (恢复现场)                出栈(栈号:1)  
    pop {lr}                                 @ 出栈(栈号:0) 恢复中断执行前 lr 地址                                             出栈(栈号:0)
    subs pc, lr, #4                        @ 将lr-4赋给pc
   
    /*为什么是lr-4 而不是lr直接返回?*/
    /*ARM 构架下为了提高运行效率存在:取指——译指——执行(三级流水线)
    即:      
    0X1004: xxxxx  当前执行指令
    0X1008: xxxxx  提前翻译下1条指令
    0X100C: xxxxx  提前获取下2条指令 <---PC
    也就是说当系统执行 0X1004地址中存放的指令时,程序计数器 PC 已经指向了 0X100C 地址了,
    为了保证中间 0X1008 地址 不被漏掉 需要将 PC指针减一个字节。
/**********************************************************************************************/
个人总结:  中断系统 精讲注释  
中断系统的部分确实难啃,视频至少得看2-3遍,目前刚刚看懂了这部分中断函数 梳理一下IRQ_Interrupt_Handler的流程
1、保护现场:设获取当前程序指针位置 设置 中断程序执行完成后的  返回地址
2、保护现场:将 Rn寄存器、CPSR(程序状态寄存器)、SPSR_irq(程序状态备份寄存器)的值 先通过压栈保存起来
   压栈时最好注释一下存储的内容和栈号,方便后面恢复现场时按 相反的顺序 出栈(弹夹原理)
3、获取 GIC(通用中断控制)的基地址 进入 GIC寄存器
4、操作 读取 GICC_IAR寄存器 获取中断号 bit[9-0]:Interrupt ID (压栈保存GICC_IAR 数据和GIC入口地址)
5、操作 根据 中断号 判断需要调用的 中断服务函数(跳转到 C语言编写的中断处理函数中进行)
6、中断执行完成 操作 GICC_E0I 寄存器标志中断结束(写入之前存储 GICC_IAR寄存器 的中断号 bit[9-0]:Interrupt ID)
7、现场恢复:逆序 出栈 恢复 SPSR_irq(程序状态备份寄存器)、CPSR(程序状态寄存器)、Rn寄存器数据     (检查出栈顺序(弹夹原理): 先进后出,后进先出)
8、现场恢复:程序指针恢复为中断程序执行前的位值(注意三级流水线)



再次强调一下:

中断函数涉及到 ARM Cortex-A7内核 构架,建议学习本视频前回到第6章去复习一下构架的有关知识,要不然学起来很吃力。
两个字形容:懵比!

本节我还没编写 中断服务函数 system_irqhandler(unsigned int giccIar) 所以就不放工程文件夹了

以上注释和代码都是在学习 《正点原子 linux 第二期 裸机开发视频 P33 第15.4讲 IRQ中断函数编写 》 过程中
跟着 左萌主 学习并加入了一点点自己的思考写成的,作为一个小菜鸡,我大着胆子发出来跟大家一起记录学习
如果有帮助请大家自行参考、下载,如果 转载 请注明出处,并在论坛和我联系。如果有错误请大神指正!左萌主赛高!
作者        :YOKI
导师        :正点原子 左忠凯 (请允许我叫你一声导师吧)
硬件        :正点原子 ALPHA I.MX LINUX 开发板
/*********************************************************************************************/


正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

2

主题

712

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2178
金钱
2178
注册时间
2018-8-27
在线时间
258 小时
发表于 2020-3-30 10:54:12 | 显示全部楼层
森罗万象
回复 支持 反对

使用道具 举报

5

主题

17

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
254
金钱
254
注册时间
2020-3-22
在线时间
35 小时
 楼主| 发表于 2020-3-30 12:01:13 | 显示全部楼层
xiaotaotao 发表于 2020-3-30 10:54
优秀!  总结的不错

回复 支持 反对

使用道具 举报

88

主题

7377

帖子

5

精华

资深版主

Rank: 8Rank: 8

积分
14980
金钱
14980
注册时间
2013-11-13
在线时间
1823 小时
发表于 2020-3-31 10:15:46 | 显示全部楼层
总结的不错,支持
回复 支持 反对

使用道具 举报

5

主题

17

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
254
金钱
254
注册时间
2020-3-22
在线时间
35 小时
 楼主| 发表于 2020-4-24 23:34:35 | 显示全部楼层
zuozhongkai 发表于 2020-3-31 10:15
总结的不错,支持

这篇里面有一个错误,导致我后面查了好久的错
就是在恢复现场时把寄存器输错了,
导致我最后的程序可以正常进入中断,但是无法返回现场

错误点:
push {r0-r3,r2}         @保护现场   压栈 (栈号1)
······
pop{r0-r3,r12}     @恢复现场   出栈 (栈号1)
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
5
金钱
5
注册时间
2019-7-21
在线时间
1 小时
发表于 2022-9-18 13:58:18 | 显示全部楼层
想问一下楼主怎么理解irq中断里切换到了svc模式执行中断服务函数?笔记里是写切换到svc允许其它中断打断,但是ARM处理器在进IRQ中断的时候自动屏蔽了IRQ中断(修改了CPSR的I位),而代码中并没有再次使能IRQ,所以是执行IRQ中断的时候只允许FRQ中断打断的意思吗?如果是这样的话为什么不直接在IRQ模式下写中断服务函数呢?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-25 09:58

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表