OpenEdv-开源电子网

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

<新手向>第二期 裸机开发 P29-P32 第15.5讲 通用中断服务函数编写 随堂笔记

[复制链接]

5

主题

17

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
254
金钱
254
注册时间
2020-3-22
在线时间
35 小时
发表于 2020-4-9 00:45:15 | 显示全部楼层 |阅读模式
本帖最后由 YOKI 于 2020-4-9 00:54 编辑

/********************************************/
时间        :2020/04/05/
作者        :YOKI
导师        :正点原子 左忠凯
硬件        :正点原子 ALPHA I.MX LINUX 开发板
写在前面的话:中断函数涉及到 ARM Cortex-A7内核 构架,建议学习本视频前回到第6章去复习一下构架的有关知识
/*******************************************/
写在前面的话:
本节是 C语言环境下 通用中断服务在上一篇,我们了解了 IMX6ULL 的中断响应机制
IMX6ULL (Core-A7内核)的中断向量表中 只有8个异常中断
相比STM32 这8个中断明显不够用,但是(Core-A7内核)设定了一个专门的中断工作模式 IRQ 用来响应各种外设产生的中断
管理中断的 这个模式下共有 160 个保留中断每个中断对应一个中断_ID(中断号)
CPU 通过 GIC控制器(通用中断控制器)获取 中断ID 来进入具体事件中断处理函数
例如 GPIO1 发生了中断事件,那么CPU接口端的 CICC_IAR 寄存器中就保存了对应的中断ID(中断号)
中断服务函数通过访问 GICC_IAR 寄存器 获取 中断ID(中断号)然后进入具体的 GPIO1 中断处理函数
在汇编环境下 我们通过 CP15 协处理器 进行寄存器 Rx 和 GIC(通用中断控制器)中 相关寄存器的数据交换
重要知识点(复习):
@ CP15协处理器 指令
@ MRC : 将 CP15协处理器 的寄存器数据 写到 ARM 寄存器  
@ MCR : 将 ARM 处理器 的寄存器数据 写到 CP15 寄存器
@ MRC : 读 CP15 寄存器    MCR 写 CP15 寄存器
@ 使用格式:
@ MCR{cond},<opc1>,<Rt>,<CRn>,<CRm>,<opc12>
@ cond :指令执行的条件码 (若为空则为无条件执行)
@ <opc1>:协处理器要执行的操作码
@ <Rt>: ARM 源/目标 寄存器
@ <CRn>:CP15协处理器的 目标 寄存器
@ <CRm>:CP15协处理器 的 附加目标寄存器(若不需要附加则写c0,否则结果不可预知)
@ <opc12>: 可选的特定操作码,不需要时写 0
使用格式:
MCR p15,<opc1>,<Rt>,<CRn>,<CRm>,<opc12>
mrc p15,   4,   r1,  c15,  c0,     0      
@ 从CP15的 C15寄存器 内的值 写到 R1寄存器 中
@ 含义 读取 CBAR 寄存器( GIC 的首地址)
参考:《Cortex-A7 Technical ReferenceManua.pdf》:4.2.16 P68  P138
而本节是在 C语言环境下 完成上述工作,所以首先 左萌主在教程德开始添加了一个头文件:core_ca7.h
该头文件包含一些 GICC相关的 C 函数 用C语言实现了 MRC指令 和 MCR指令
例如: 获取 CICC_CBAR  寄存器的值
FORCEDINLINE __STATIC_INLINE uint32_t __get_CBAR(void)
{
  return __MRC(15, 4, 15, 0, 0);
}
可以看到 return 后面和 汇编中的 MRC 指令很像:
读 CP15 寄存器 操作:
__MRC( 15,    4,         15,   0,    0 )
   MRC p15,    4,   r1,  c15,  c0,    0  
@ 将 CP15 寄存器 C15 的值传递给 R1
@ 含义 读取 CBAR 寄存器( GIC 的首地址)
可以看到,与 汇编的MRC 指令相比较,缺失ARM 寄存器 R1 了
C 语言环境下 不适合直接对 Rn 寄存器进行操作(因为不知道这些寄存器是否存储着数据)
再看一个写 CP15 寄存器的函数:
//写GICC_SCTLR 操作
FORCEDINLINE __STATIC_INLINE void __set_SCTLR(uint32_t sctlr)
{
  __MCR(15, 0, sctlr, 1, 0, 0);
}
这个函数只有一条语句:
__MCR( 15, 0, sctlr,   1,  0, 0)
  MCR p15, 0,    r0,  c1, c0, 0   
  @ 将 R0寄存器 的数据写入 SCTLR (CP15协处理器)
  @ SCTLR是系统控制寄存器 前面关闭 MMU 和I/D Cache  使用过这条指令(复位中断函数Reset_Handler:)
可以看到,与 汇编的MCR 指令相比较,ARM 寄存器 R0 变成了 函数的参数:sctrl(具体值由用户写入)
读取 GICC_IAR(存储着 中断ID ) 的函数:
FORCEDINLINE __STATIC_INLINE uint32_t GIC_AcknowledgeIRQ(void)
{
   GIC_Type *gic = (GIC_Type *)(__get_CBAR() & 0xFFFF0000UL);
   return gic->C_IAR & 0x1FFFUL;
}
返回了gic 结构体成员 C_IAR 的值
以上是预备知识点,接下来是本节代码的注解:
首先是定义中断处理函数的格式:
typedef void(*system_IRQ_Handler_t)(unsigned int gicc_IAR, void *param );
// 指针 *system_IRQ_Handler_t
// gicc_IAR 用来存储 中断ID
// *param 是用户传递给 *system_IRQ_Handler_t 的参数
接下来定义中断处理函数结构体:
typedef struct _sys_IRQ_Handle
{
    system_IRQ_Handler_t  IRQ_Interrupt_Handler; // 中断处理函数
    void *User_Param;                            // 中断处理函数的参数(用户写入)
}sys_IRQ_Handler_t;
接下来准备了一个数组(为160 个中断ID 存储对因的函数入口地址):
/* 中断处理函数表 */
static sys_IRQ_Handler_t IRQ_table[NUMBER_OF_INT_VECTORS];
// 这是一个数组,数组元素由结构体构成
// 宏定义 NUMBER_OF_INT_VECTORS  160
// IMX6ULL 有160个中断: 32 个内核中断+128个外设中断 (见 MCIMX6Y2.h)
// 结构体: _sys_IRQ_Handle(包含两个成员 )
// 成员1:    IRQ_Interrupt_Handler   具体的 中断处理函数的入口地址(函数名)
// 成员2:    User_Param              用户向 中断处理函数  传递的参数
// 这实际上构成了一个二维的表格
然后对这个数组初始化(写默认数据)
/* 初始化中断处理函数表 */
void systrem_IRQ_table_init ()
{
unsigned int i=0;
    IRQ_Nesting=0; //中断嵌套计数器清零
for( i=0 ; i < NUMBER_OF_INT_VECTORS ; i++ )
    {
    IRQ_table.IRQ_Interrupt_Handler = default_IRQ_Handler;  
   
IRQ_table.User_Param = NULL;
    }       //对中断处理函数表 的每一个元素 写入默认值                          
}
/* 默认中断处理函数*/
void default_IRQ_Handler(unsigned int gicc_IAR, void *User_param)
{
while (1);
} //未进行任何操作
/* 中断初始化函数 */
void Interrupt_init (void)
{
    GIC_Init();                 //GIC初始化
    __set_VBAR(0X87800000);     //设置中断向量偏移()
}
当系统发生了 GPIO1 中断  GICC_IAR寄存器中自动获取到中断ID
进入了通用中断服务函数:
void system_IRQ_handler (unsigned int gicc_IAR)
{   
    uint32_t int_ID = gicc_IAR & 0X3ff;
    if( int_ID >=  NUMBER_OF_INT_VECTORS )  // 中断 ID 超出范围则不作任何操作
    {
        return;
    }
     IRQ_Nesting++;//中断嵌套计数器+1 (每进入一次中断处理函数 +1)
    /* 根据中断 ID  */
    IRQ_table[int_ID].IRQ_Interrupt_Handler(int_ID, IRQ_table[int_ID].User_Param);
    IRQ_Nesting--;//中断嵌套计数器-1 (执行完中断处理函数 +1)
}
// 这里传递参数和调用函数都使用了 中断处理函数表
// (内层)函数的参数传递: IRQ_table[int_ID].User_Param
// (外层)根据 中断ID 调取对应的中断函数:  IRQ_table[int_ID].IRQ_Interrupt_Handler()
//  前面说过 数组 IRQ_table[]的元素 由结构体: _sys_IRQ_Handle 构成
//  结构体: _sys_IRQ_Handle(包含两个成员)
//  成员1:    IRQ_Interrupt_Handler   具体的 中断处理函数的入口地址(函数名)
//  成员2:    User_Param              用户向 中断处理函数  传递的参数
//  我们接下来要写的 中断事件响应函数按照 默认中断函数的格式有两个参数
//  参数1 : 中断ID      int_ID
//  参数2 : 用户参数     User_Param

//  中断嵌套计数器: IRQ_Nesting  
//  因为IRQ 有 160个中断 ID,中断间存在优先级关系,所以要记录当前中断的嵌套层数

//  例如: 在执行 GPIO1 中断服务时 发生了 正好发生了 uart1 中断事件
//  如果设定 uart1 中断的响应优先级高于 GPIO1 中断 那么系统就会先执行 uart1的中断服务函数,
//  uart1 中断 执行完成后在回到 GPIO1 中断服务 函数 ,GPIO1 中断执行完成后回到主函数。
//  uart1 中断 和 GPIO1 中断 都属于 IRQ中断 因此都要执行 system_IRQ_handler() 此时就发生了嵌套,要记录当前所处的中断层级。

对涉及到的中断进行注册

/* 中断注册函数 */
void Systerm_register_IRQ_Handler( IRQn_Type  IRQ_ID,system_IRQ_Handler_t  Handler,void *User_Param)
{
    IRQ_table[IRQ_ID].IRQ_Interrupt_Handler = Handler;  
    IRQ_table[IRQ_ID].User_Param = User_Param;
}
/**********************************************************************************************/

个人总结:  中断系统 精讲注释  

回顾整个 通用中断服务函数 的编写过程 首先是通过 数组 准备了一个  中断处理函数表 IRQ_table
这个表(数组)中的由元素 是由  结构体 构成
数组[标号] 对应 [中断ID]  
结构体成员有两个:
  IRQ_Interrupt_Handler   :中断函数名(中断函数的入口地址)
  User_Param              :用户参数
  为了对 IRQ_table 表进行初始化 编写了一个默认 中断函数 default_IRQ_Handler(未执行任何操作)
  为了使用中断 又编写了一个 中断注册函数
    Systerm_register_IRQ_Handler( IRQn_Type  IRQ_ID,system_IRQ_Handler_t  Handler,void *User_Param)
  这个函数的参数有三个:
  IRQn_Type IRQ_ID                     : 中断ID   IRQn_Type将各个中断函数名(地址)和 中断ID关联起来
  system_IRQ_Handler_t  Handler   :中断函数名(中断函数的入口地址)
  void *User_Param                       :用户参数
  通用中断服务函数根据 中断ID 获取参数 并执行ID 对应的中断处理函数。
  关于中断的初始化Interrupt_init ()  
  1、读取了GICC_CBAR 寄存器(获取GIC 地址)使用户可以使用 GIC
  2、设置了中断向量偏移 __set_VBAR(0X87800000);   
/**********************************************************************************************/
后记:最近复工,事情很多,所以隔了这么久才更新这一篇,也是因为时间跨度太久导致这篇的质量可能不如前几篇。
向在疫情中牺牲的英雄和逝去的同胞们表达深切的哀悼!

/*********************************************************************************************/

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

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

使用道具 举报

9

主题

890

帖子

0

精华

资深版主

Rank: 8Rank: 8

积分
2390
金钱
2390
注册时间
2019-9-25
在线时间
397 小时
发表于 2020-4-18 20:48:43 | 显示全部楼层
回复 支持 反对

使用道具 举报

0

主题

201

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2552
金钱
2552
注册时间
2019-12-5
在线时间
352 小时
发表于 2020-4-23 09:12:38 | 显示全部楼层
向你学习了,赞一个
回复 支持 反对

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

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

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

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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