Excel如何設置動態求和 Excel設置動態求和方法
1094
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
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小時內刪除侵權內容。