ARM裸机开发输入中断(arm内核支持7种中断)

网友投稿 845 2022-05-30

文章目录

ARM裸机开发:输入中断

一、硬件平台:

二、原理图分析

三、程序编写

3.1 移植相关文件

3.2 编写启动文件

3.3 中断处理程序

3.4 开启输入中断

3.5 按键中断编写

3.6 编写Makefile脚本

四、实验现象

ARM裸机开发:输入中断

一、硬件平台:

正点原子I.MX6U阿尔法开发板

二、原理图分析

输入中断是配置GPIO作为输入IO口,检测按键引脚电平,当目标电平来到时产生中断,进入中断服务函数处理程序,I.MX6U的按键引脚如下:

可以看到按键引脚接到 GPIO1_IO18 口,按键的原理就是默认接一个上拉电阻,按键按下接地,可以有效控制 IO 电平

三、程序编写

程序编写前先复制上一节按键输入的工程作为本小节的开始工程

3.1 移植相关文件

在 NXP 提供的 SDK 包内 core_ca7.h 有相关的定义文件,为了节省开发时间,我们将其移植到本地工程目录;注意该文件要做一些修改,删除一些不必要的内容,不然会保存,这里我直接复制正点原子修改后的文件到工程目录下:

该文件下面我们只需要注意 10 个API函数,函数如下:

文件添加后使用如下头文件调用

#include "core_ca7.h"

1

3.2 编写启动文件

SDK 添加完成之后就是修改启动文件,定义系统中断服务函数,修改 IRQ 中断,判断中断类型,进入不同的中断服务函数,启动文件编写如下:

首先编写全局标号,进入 _start 函数,在里面创建中断向量表

.global _start /* 全局标号 */ /* * 描述: _start函数,首先是中断向量表的创建 * 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器) * ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常) */ _start: ldr pc, =Reset_Handler /* 复位中断 */ ldr pc, =Undefined_Handler /* 未定义中断 */ ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */ ldr pc, =PrefAbort_Handler /* 预取终止中断 */ ldr pc, =DataAbort_Handler /* 数据终止中断 */ ldr pc, =NotUsed_Handler /* 未使用中断 */ ldr pc, =IRQ_Handler /* IRQ中断 */ ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

编写对应的中断服务函数,这里除了 Reset_Handler 和 IRQ_Handler 我们需要关注一下,其他的都暂时先编写为死循环:

/* 未定义中断 */ Undefined_Handler: ldr r0, =Undefined_Handler bx r0 /* SVC中断 */ SVC_Handler: ldr r0, =SVC_Handler bx r0 /* 预取终止中断 */ PrefAbort_Handler: ldr r0, =PrefAbort_Handler bx r0 /* 数据终止中断 */ DataAbort_Handler: ldr r0, =DataAbort_Handler bx r0 /* 未使用的中断 */ NotUsed_Handler: ldr r0, =NotUsed_Handler bx r0 /* FIQ中断 */ FIQ_Handler: ldr r0, =FIQ_Handler bx r0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

这些中断服务函数是可以编写一些处理代码,方便用户判断错误的来源的,暂时先不研究

下面编写复位中断服务函数:

/* 复位中断 */ Reset_Handler: /* 关闭全局中断 */ cpsid i /* 关闭I、DCache和MMU 采取读-改-写的方式*/ /* 读取CP15的C1寄存器到R0中*/ mrc p15, 0, r0, c1, c0, 0 /* 清除C1寄存器的bit12位(I位),关闭I Cache*/ bic r0, r0, #(0x1 << 12) /* 清除C1寄存器的bit2(C位),关闭D Cache*/ bic r0, r0, #(0x1 << 2) /* 清除C1寄存器的bit1(A位),关闭对齐*/ bic r0, r0, #0x2 /* 清除C1寄存器的bit11(Z位),关闭分支预测*/ bic r0, r0, #(0x1 << 11) /* 清除C1寄存器的bit0(M位),关闭MMU*/ bic r0, r0, #0x1 /* 将r0寄存器中的值写入到CP15的C1寄存器中*/ mcr p15, 0, r0, c1, c0, 0 #if 0 /* 汇编版本设置中断向量表偏移 */ ldr r0, =0X87800000 dsb isb mcr p15, 0, r0, c12, c0, 0 dsb isb #endif /* 设置各个模式下的栈指针, * 注意:IMX6UL的堆栈是向下增长的! * 堆栈指针地址一定要是4字节地址对齐的!!! * DDR范围:0X80000000~0X9FFFFFFF */ /* 进入IRQ模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x12 /* r0或上0x13,表示使用IRQ模式 */ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ ldr sp, =0x80600000 /* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */ /* 进入SYS模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x1f /* r0或上0x13,表示使用SYS模式 */ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ ldr sp, =0x80400000 /* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */ /* 进入SVC模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ ldr sp, =0X80200000 /* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */ cpsie i /* 打开全局中断 */ #if 0 /* 使能IRQ中断 */ mrs r0, cpsr /* 读取cpsr寄存器值到r0中 */ bic r0, r0, #0x80 /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */ msr cpsr, r0 /* 将r0重新写入到cpsr中 */ #endif b main /* 跳转到main函数 */

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

IRQ 中断服务函数,进入中断服务函数后,先进行现场保护,然后获取 GIC 的基地址,偏移后操作其寄存器,获取当前中断号,保存到寄存器 r0 和 r1,接着调用一个c语言中断处理函数,将参数从 r0-r3 四个寄存器传入函数

汇编调用 C 函数的时候建议形参不要超过 4 个,形参可以由 r0~r3 这四个寄存器来传递,如果形参大于 4 个,那么大于 4 个的部分要使用堆栈进行传递。

所以 r0 寄存器写入中断号就可以了传入到函数 system_irqhandler;接着该函数进行对应中断的调用和处理,处理完成后向 GICC_EOIR 寄存器写入其中断号表示中断处理完成;

/* IRQ中断!重点!!!!! */ IRQ_Handler: # 现场保护 push {lr} /* 保存lr地址 */ push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */ mrs r0, spsr /* 读取spsr寄存器 */ push {r0} /* 保存spsr寄存器 */ mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中*/ add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */ ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,*/ /* GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据*/ /* 这个中断号来绝对调用哪个中断服务函数*/ push {r0, r1} /* 保存r0,r1 */ cps #0x13 /* 进入SVC模式,允许其他中断再次进去 */ push {lr} /* 保存SVC模式的lr寄存器 */ ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/ blx r2 /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */ pop {lr} /* 执行完C语言中断服务函数,lr出栈 */ cps #0x12 /* 进入IRQ模式 */ pop {r0, r1} # 向 GICC_EOIR 寄存器写入刚刚处理完成的中断号, # 当一个中断处理完成以后必须向 GICC_EOIR 寄存器 # 写入其中断号表示中断处理完成 str r0, [r1, #0X10] /* 中断执行完成,写EOIR */ pop {r0} msr spsr_cxsf, r0 /* 恢复spsr */ pop {r0-r3, r12} /* r0-r3,r12出栈 */ pop {lr} /* lr出栈 */ subs pc, lr, #4 /* 将lr-4赋给pc */

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

ARM裸机开发:输入中断(arm内核支持7种中断)

26

27

28

29

30

31

32

33

34

之后就是进行现场恢复,返回到中断位置!注意,此处恢复现场传递的是 lr - 4 的寄存器值,而不是pc,因为 ARM 的指令是三级流水线:取指、译指、执 行,pc 指向的是正在取值的地址,比如下面一段代码

0X2000 MOV R1, R0 ;执行 0X2004 MOV R2, R3 ;译指 0X2008 MOV R4, R5 ;取值 PC

1

2

3

当前正在执行 0X2000 地址处的指令 “MOV R1, R0” ,但 PC 里面已经保存了 0X2008 地址处的指令“MOV R4, R5”。若发生中断,中断发生的时候保存在 lr 中的是 pc 的值,即地址 0X2008。当中断处理完成如果直接跳转到 lr 里面保存的地址处(0X2008) 开始运行,那么就有一个指令没有执行,所以就需要将 lr-4 赋值给 pc,即 pc=0X2004,从第二级正在译指的指令 “MOV R2, R3” 开始执行

3.3 中断处理程序

我们在中断服务函数 IRQ_Handler 中调用了 C 函数 system_irqhandler 来处理具体的中断,该函数的具体细节需要我们自己实现,所以要编写中断处理程序来实现,同时因为中断数量较多,所以我们引入一些其他的数据结构单元辅助管理中断服务函数,编写如下:

新建一个新的模块文件

头文件插入如下代码

#ifndef __BSP_INT_H #define __BSP_INT_H #include "imx6ul.h" typedef void (* system_irq_handler_t) (unsigned int giccIar,void *param); typedef struct _sys_irq_handle { /* data */ system_irq_handler_t irqHandler; void *userParam; } sys_irq_handle_t; void int_init(void); void system_irqtable_init(void); void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam); void system_irqhandler(unsigned int giccIar); void default_irqhandler(unsigned int giccIar,void *userParam); #endif

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

代码解释:

typedef void (* system_irq_handler_t) (unsigned int giccIar,void *param);

1

创建一个函数指针,用 typedef 定义修饰别名为 system_irq_handler_t

typedef struct _sys_irq_handle { /* data */ system_irq_handler_t irqHandler; void *userParam; } sys_irq_handle_t;

1

2

3

4

5

6

创建一个结构体,其有两个参数,一个是函数指针的入口指针,另外一个则是一个用户参数,创建这个结构体用于保存中断的信息,保存其中断处理函数入口因为有160个中断源,所以我们在.c文件中可以定义一个结构体数组用于存储所有中断的信息

其他的就是一些函数声明了:

// 中断系统(GIC)初始化 void int_init(void); // 中断信息结构体数组初始化 void system_irqtable_init(void); // 注册中断,修改目标中断的结构体的信息 //要使用某个外设中断,那就必须调用此函数来给这个中断注册一个中断处理函数 void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam); // _start 文件中调用的的中断号处理函数 void system_irqhandler(unsigned int giccIar); // 默认中断处理函数 void default_irqhandler(unsigned int giccIar,void *userParam);

1

2

3

4

5

6

7

8

9

10

11

12

13

.c 模块文件代码如下,具体功能注释写在代码中:

#include "bsp_int.h" /* 中断嵌套计数器,计算中断嵌套信息 */ static unsigned int irqNesting; /* 中断服务函数表, 用于存放中断的信息*/ static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS]; /* * @description : 中断初始化函数 * @param : 无 * @return : 无 */ void int_init(void) { GIC_Init(); /* 初始化GIC*/ system_irqtable_init(); /* 初始化中断表*/ __set_VBAR((uint32_t)0x87800000); /* 中断向量表偏移,偏移到起始地址*/ } /* * @description : 初始化中断服务函数表 * @param : 无 * @return : 无 */ void system_irqtable_init(void) { unsigned int i = 0; irqNesting = 0; /* 先将所有的中断服务函数设置为默认值 */ for(i = 0; i < NUMBER_OF_INT_VECTORS; i++) { //给每个中断的数组改变传入参数和数值 system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL); } } /* * @description : 给指定的中断号注册中断服务函数 * @param - irq : 要注册的中断号 * @param - handler : 要注册的中断处理函数 * @param - usrParam : 中断服务处理函数参数 * @return : 无 */ void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) { irqTable[irq].irqHandler = handler; irqTable[irq].userParam = userParam; } /* * @description : C语言中断服务函数,irq汇编中断服务函数会 调用此函数,此函数通过在中断服务列表中查 找指定中断号所对应的中断处理函数并执行。 * @param - giccIar : 中断号 * @return : 无 */ void system_irqhandler(unsigned int giccIar) { uint32_t intNum = giccIar & 0x3FFUL; /* 检查中断号是否符合要求 */ if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS)) { return; } irqNesting++; /* 中断嵌套计数器加一 */ /* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数 */ irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam); irqNesting--; /* 中断执行完成,中断嵌套寄存器减一 */ } /* * @description : 默认中断服务函数 * @param - giccIar : 中断号 * @param - usrParam : 中断服务处理函数参数 * @return : 无 */ void default_irqhandler(unsigned int giccIar, void *userParam) { while(1) ; }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

3.4 开启输入中断

这里 GPIO 配置代码直接使用正点原子的驱动方案,有关的注释我写在代码内

bsp_gpio.h

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

bsp_gpio.c

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

3.5 按键中断编写

有了 GPIO 驱动代码后,我们就可以新建一个新的模块代码,用于配置外部触发中断,新建模块如下:

bsp_exit.h 代码:

#ifndef __BSP_EXIT_H #define __BSP_EXIT_H #include "imx6ul.h" // 外部中断初始化 void exit_init(void); // 外部中断回调函数 void gpio1_io18_irqhandler(void); #endif

1

2

3

4

5

6

7

8

bsp_exit.c 代码如下:

#include "bsp_exit.h" #include "bsp_gpio.h" #include "bsp_int.h" #include "bsp_delay.h" #include "bsp_beep.h" void exit_init(void) { //设定GPIO模式 gpio_pin_config_t key_config; IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080); //设定按键中断 key_config.direction=kGPIO_DigitalInput; key_config.interruptMode=kGPIO_IntFallingEdge; key_config.outputLogic=1; gpio_init(GPIO1,18,&key_config); //使能GIC中断,注册按键触发中断 GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); //使能按键触发中断 gpio_enableint(GPIO1, 18); } void gpio1_io18_irqhandler(void) { static unsigned char state = 0; //延时消抖(中断中严禁使用死延时,这里是为了IO稳定) delay(10); if(gpio_pinread(GPIO1,18) == 0) { state = !state; beep_switch(state); } //清除中断标志 gpio_clearintflags(GPIO1,18); }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

以上代码准备完成后,我们在 main.c 中分别调用代码进行初始化

3.6 编写Makefile脚本

在 Makefile 里面添加上对应文件的文件夹就可以完成编译,添加位置如下:

编译一下,成功通过:

四、实验现象

按下按键 LED 的灯光效果切换

ARM 单片机

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:2022金三银四前端面试题预告
下一篇:*转载*耶鲁大学校长给计算机学生的建议(耶鲁大学 计算机)
相关文章