1. 中断的理解
1.1 什么是中断
中断: 通常指 某种事件(中断源) 触发了 需要打断CPU , 让CPU暂停当前处理的(保存现场) 任务(usr模式下) 打断(irq异常) 转而去处理 这个事件(在irq模式中) ,事件处理结束后 需要回到(恢复现场) 打断处继续向后执行
1.2 中断控制器的作用
中断控制器作用: 通用中断控制器
1. 扩展中断接口 CPU--irq 中断控制器(GIC) ----- 中断源
2. 中断源管理 使能或不使能 某些中断
3. 管理中断源 优先级
4. 管理 中断送哪个CPU处理 (多核处理器)
(NVIC) 嵌套向量中断控制器
5. 中断嵌套 高优先级的中断 将 可以打断 低优先级的程序执行
中断系统: 是CPU实现 异步功能的 必要设备
1.3 中断控制器: 原理
如图所示 内部结构
1.4 中断挂起
指中断触发时, 中断控制器或中断源本身, 将中断信号记录到一个寄存器(挂起寄存器)中若挂起寄存器中 有中断被挂起了, 表示 该中断触发了,但还没有处理或正在处理中 处理完中断后 应立即清除中断挂起, 否则该挂起的中断 将会持续尝试打断CPU
若有多个地方存在中断挂起 清除中断时 应 先清除先挂起的中断寄存器
3. 实验4 按键中断实验
3.1 原理图
实验4: 按键中断实验 : 使用中断法 实现 按键点击 控制LED 亮灭
1. 硬件设备
KEY 按键 使用 K2 ===> GPX1_1
2. 电路图
使用 K2 ===> GPX1_1
3. 控制器
外部中断: 中断信号来源于芯片外部 通过GPIO进入芯片内部 如 按键 一些模块
内部中断: 中断信号来源于芯片内部 由控制器触发的 如 串口中断 定时器中断 ....
外部中断 经过了 GPIO控制器 传入
3.2 配置寄存器
配置寄存器 参考图片
中断号: 就是芯片制造厂商 对 芯片中 所有的中断源 进行的 一个编号
749 页 9.2.2 章节 Table 9-2 GIC Interrupt Table (SPI[127:0])
3.2.1 寄存器配置: GPX1_1
GPX1_1 中断输入模式
GPX1CON[1] [7:4] = 0xF = WAKEUP_INT1[1] 外部中断输入模式
EXT_INT41_CON 中断触发模式
GPX1 对应 EXT_INT41_CON (子啊比如GPX2----EXT_INT40_CON,以此类推)
EXT_INT41_CON [1]: 367 页 配置中断触发模式
低电平触发
高电平触发
上升沿触发 [6:4] = 0x3 上升沿触发
下降沿触发
边沿触发
EXT_INT41_MASK 中断屏蔽寄存器
EXT_INT41_MASK: GPIO中断屏蔽寄存器
EXT_INT41_MASK [1] = 0 使能中断
3.2.2 GIC配置 面向中断源
ICDDCR 总中断使能
ICDDCR 总中断使能寄存器 = 1使能 打开总中断
ICDISER 端口中断使能
ICDISER_CPU: 配置端口中断使能寄存器 ,就是配置哪些中断源能够使能,哪些不能
该寄存器 存储了 所有的 中断端口的 使能情况 共 0-4 个寄存器
其中 0 号寄存器存储 CPU 的 PPI (私有中断) SGI (软件中断)
1-4号 存储的 是 SPI 共享中断的 使能情况
GPX1_1 的SPI 端口是什么???
通过 中断源表 查询可得 GPX1_1 --- EINT[9] ---- ID(57) --- SPI中断端口号 25
57号 中断ID 在 ICDISER_CPU 的1号寄存器 的 [25]bit
若中断ID为 x 问 ICDISER_CPU 配置 第几号 的 多少bit位
寄存器号 = 中断ID / 32 的商 对应bit = 中断ID % 32
ICDIPR 端口优先级配置寄存器
ICDIPR: 端口优先级配置寄存器 每个端口 8bit 宽度 优先级范围 0-255
0-39 共40个寄存器
0-7 用于配置 SGI 和 PPI
8-38 配置 SPI
实际管脚对应的 寄存器号 = 中断ID / 4 的商 对应bit位域 = 中断ID % 4
余0 [7:0] 余1 [15:8] 余2[23:16] 余3 [31:24]
ICDIPTR 中断源 送哪个cpu
ICDIPTR_CPU: 用于配置 中断源 送哪个或哪些CPU处理
寄存器分步格局 与ICDIPR 完全一样
8bit 每个bit 分别表示要送哪个CPU
3.2.3 GIC 面向cpu
ICCICR 中断响应使能寄存器
ICCICR CPU中断响应使能寄存器 = 1 使能 =0 不使能
ICCPMR 优先级过滤寄存器
ICCPMR 优先级过滤寄存器
3.3 写程序
1. 配置必要寄存器
2. 打开 do_irq 的 注释
3. 实现 do_irq 函数
注意
打开start.S文件修改,如下
ICCIAR 获取中断号
访问CPU0.ICCIAR寄存器得到中断号
EXT_INT41_PEND 清除中断 源头的挂起
ICDICPR 在清除GIC分配器层中断挂起
ICCEOIR 清除cpu中断挂起
key_exit-----> main
#include"exynos_4412.h"
#include"uart.h"
void key_exit_init(){
//GPIO key2 GPX1_1
//1. 配置引脚为外部中断输入模式
GPX1.CON = (GPX1.CON & ~(0xf<<4)) | (0xf << 4);
//2. 配置中断触发模式 这里配置为上升沿触发 [6:4] = 0x3
EXT_INT41_CON = (EXT_INT41_CON & ~(0x7<<4)) | (0x3 << 4);
//3. 配置中断掩码屏蔽寄存器
//配置为使能中断 [1] = 0
EXT_INT41_MASK &= ~(1<<1);
// gic 面向中断源
//4. 打开总中断
//[0] = 1
ICDDCR = 1;
//5. 配置端口中断使能寄存器
//就是配置哪些中断源能够使能,哪些不能
//找到中断源的 中断号(57)寄存器编号1=57/32(即ICDISER1) 端口号(25=57%32)
//配置为 1 使能
ICDISER.ICDISER1 |= 1<<25;
//6. 端口优先级设置寄存器 ,这里我们设置优先级为 5
//14 = 57/4 1=57%4 ---->[15:8]
ICDIPR.ICDIPR14 = (ICDIPR.ICDIPR14 & ~(0xff<<8)) | (5 << 8);
//7. 配置中断源送去哪个cpu处理 0x1表示直送cpu0
//寄存器分步格局 与ICDIPR 完全一样
ICDIPTR.ICDIPTR14 = (ICDIPTR.ICDIPTR14 & ~(0xff<<8)) | (0x1 << 8);
//GIC 面向cpu
//8. cpu响应中断使能 =1 使能 =0 不使能
CPU0.ICCICR = 1;
//9. cpu优先级过滤寄存器 这里我们设置为255 只有比255优先级还低的会被过滤,因为255至最低的优先级
//我们前面设置的优先级为5 优先级高于255 所以不会被过滤
CPU0.ICCPMR = 255;
}
//中断响应,c语言入口函数,在汇编汇总调用,当irq异常触发时
void do_irq(){
// 1.获取中断号,用于区分哪个中断源触发的中断
int irq_id = 0;
//访问CPU0.ICCIAR寄存器得到中断号 说明CPU0.ICCIAR的[9:0]存储了中断号
irq_id = CPU0.ICCIAR;
printf("irq_id = %d\n",irq_id);
//2.根据中断id来处理对应的事件
switch(irq_id)
{
case 57:
printf("key2 clicl!!!\n");
//清除中断挂起
//先清除中断 源头的挂起 //GPX1_1 写1清除
EXT_INT41_PEND |= 1<<1;
//在清除GIC分配器层中断挂起 与ICDISER_CPU 结构一样 id:57
//置为1 清除
ICDICPR.ICDICPR1 |= 1<<25;
break;
}
//最后清除cpu中断挂起
//写入中断id清除对应中断挂起
CPU0.ICCEOIR = irq_id;
}
int main()
{
int a = 100;
//初始化串口
uart_init();
//初始化中断
key_exit_init();
printf("hello!a=%d\r\n",a);
while(1);
return 0;
}
4. 使用中断 按下k2 控制led3亮灭
key2_led3---main.c
#include"exynos_4412.h"
#include"uart.h"
//中断的使用
//初始化led3
void led3_init(){
//配置引脚模式
GPX1.CON = (GPX1.CON & ~(0xf<<0)) | (0x1 << 0);
//配置数据寄存器
// GPX1.DAT |= 1;
GPX1.DAT &= ~1;
//配置上下拉寄存器
GPX1.PUD &= ~(0x3<<0);
}
//初始化中断
void key_exit_init(){
//GPIO key2 GPX1_1
//1. 配置引脚为外部中断输入模式
GPX1.CON = (GPX1.CON & ~(0xf<<4)) | (0xf << 4);
//2. 配置中断触发模式 这里配置为上升沿触发 [6:4] = 0x3
EXT_INT41_CON = (EXT_INT41_CON & ~(0x7<<4)) | (0x3 << 4);
//3. 配置中断掩码屏蔽寄存器
//配置为使能中断 [1] = 0
EXT_INT41_MASK &= ~(1<<1);
// gic 面向中断源
//4. 打开总中断
//[0] = 1
ICDDCR = 1;
//5. 配置端口中断使能寄存器
//就是配置哪些中断源能够使能,哪些不能
//找到中断源的 中断号(57)寄存器编号1=57/32(即ICDISER1) 端口号(25=57%32)
//配置为 1 使能
ICDISER.ICDISER1 |= 1<<25;
//6. 端口优先级设置寄存器 ,这里我们设置优先级为 5
//14 = 57/4 1=57%4 ---->[15:8]
ICDIPR.ICDIPR14 = (ICDIPR.ICDIPR14 & ~(0xff<<8)) | (5 << 8);
//7. 配置中断源送去哪个cpu处理 0x1表示直送cpu0
//寄存器分步格局 与ICDIPR 完全一样
ICDIPTR.ICDIPTR14 = (ICDIPTR.ICDIPTR14 & ~(0xff<<8)) | (0x1 << 8);
//GIC 面向cpu
//8. cpu响应中断使能 =1 使能 =0 不使能
CPU0.ICCICR = 1;
//9. cpu优先级过滤寄存器 这里我们设置为255 只有比255优先级还低的会被过滤,因为255至最低的优先级
//我们前面设置的优先级为5 优先级高于255 所以不会被过滤
CPU0.ICCPMR = 255;
}
//中断响应,c语言入口函数,在汇编汇总调用,当irq异常触发时
void do_irq(){
// 1.获取中断号,用于区分哪个中断源触发的中断
int irq_id = 0;
//访问CPU0.ICCIAR寄存器得到中断号 说明CPU0.ICCIAR的[9:0]存储了中断号
irq_id = CPU0.ICCIAR;
printf("irq_id = %d\n",irq_id);
//2.根据中断id来处理对应的事件
switch(irq_id)
{
case 57:
printf("key2 clicl!!!\n");
GPX1.DAT ^= (1<<0);
//清除中断挂起
//先清除中断 源头的挂起 //GPX1_1 写1清除
EXT_INT41_PEND |= 1<<1;
//在清除GIC分配器层中断挂起 与ICDISER_CPU 结构一样 id:57
//置为1 清除
ICDICPR.ICDICPR1 |= 1<<25;
break;
}
//最后清除cpu中断挂起
//写入中断id清除对应中断挂起
CPU0.ICCEOIR = irq_id;
}
int main()
{
int a = 100;
//初始化串口
uart_init();
led3_init();
//初始化中断
key_exit_init();
printf("hello!a=%d\r\n",a);
while(1);
return 0;
}
5. 实现 K2 K3 K4的 中断程序
key_led---main.c
#include"exynos_4412.h"
#include"uart.h"
//实现 K2 K3 K4的 中断程序
//延时函数
void delay(int m)
{
int i;
while(m--)
for(i=0; i < 10000 ; i++);
}
//中断初始化
void key_exti_init()
{
//gpio
//K2 --- GPX1-1 ----- ID 57
//K3 --- GPX1-2 ----- ID 58
//K4 --- GPX3-2 ----- ID 64
//GPX1CON[1] [7:4] = 0xF = WAKEUP_INT1[1] 外部中断输入模式
GPX1.CON |= 0xf<<4;
GPX1.CON |= 0xf<<8;
GPX3.CON |= 0xf<<8;
//EXT_INT41_CON [1]: [6:4] = 0x3 上升沿触发
EXT_INT41_CON = (EXT_INT41_CON & ~ ( 0x7<< 4)) | ( 0x3 << 4);
EXT_INT41_CON = (EXT_INT41_CON & ~ ( 0x7<< 8)) | ( 0x3 << 8);
EXT_INT43_CON = (EXT_INT43_CON & ~ ( 0x7<< 8)) | ( 0x3 << 8);
//GPIO中断屏蔽寄存器 [1] = 0 使能中断
EXT_INT41_MASK &= ~(1<<1);
EXT_INT41_MASK &= ~(1<<2);
EXT_INT43_MASK &= ~(1<<2);
//gic 面向中断源
//ICDDCR 总中断使能寄存器 = 1使能
ICDDCR = 1;
//ICDISER_CPU: 中断使能, GIC端口使能 中断ID 57
//寄存器编号1 = 57/32 商 25 = 57 % 32
ICDISER.ICDISER1 |= 1<<25; // 57
ICDISER.ICDISER1 |= 1<<26; // 58
ICDISER.ICDISER2 |= 1<<0 ; // 64
//端口优先级设置寄存器 14 == 57 /4商
ICDIPR.ICDIPR14 = (ICDIPR.ICDIPR14 & ~(0xff << 8))|(5 << 8);
ICDIPR.ICDIPR14 = (ICDIPR.ICDIPR14 & ~(0xff << 16))|(5 << 16);
ICDIPR.ICDIPR16 = (ICDIPR.ICDIPR16 & ~(0xff << 0))|(5 << 0);
//ICDIPTR_CPU: 用于配置 中断源 送哪个或哪些CPU处理 1表示只送CPU0
ICDIPTR.ICDIPTR14 = (ICDIPTR.ICDIPTR14 & ~(0xff <<8)) | ( 0x1<<8);
ICDIPTR.ICDIPTR14 = (ICDIPTR.ICDIPTR14 & ~(0xff <<16)) | ( 0x1<<16);
ICDIPTR.ICDIPTR16 = (ICDIPTR.ICDIPTR16 & ~(0xff <<0)) | ( 0x1<<0);
//GIC 面向CPU
//使能CPU0相应中断
CPU0.ICCICR = 1;
//优先级过滤寄存器 只有当优先级高于该值的中断源才可以送该CPU
CPU0.ICCPMR = 255;
}
//中断相应 C语言入口函数 在汇编中被调用,当irq异常触发时
void do_irq( )
{
//如何区分哪个中断源触发的中断
//1. 获得中断号
int irq_id = 0;
irq_id = CPU0.ICCIAR;
printf("irq_id=%d\r\n",irq_id); //打印调试 /r可以使超级终端光标回到起始位置
//2. 根据中断id来处理对应的事件
switch(irq_id)
{
case 57: // K2 id=57
delay(1);//延时消抖
//因为前面已经延时了一段时间,这里再判断一下管脚的电平,几乎可以确认到这里的是松开手的高电平了
if(GPX1.DAT & (1<<1))//这里读取一下管脚的电平只有松开手时,触发高电平才可以进入if
{
printf("K2 click!\r\n");
}
// printf("K2 click!\n");
//3. 中断处理完后 清楚中断挂起
//先清楚中断源头的挂起
EXT_INT41_PEND |= 1<<1; //GPX1_1
//在清楚GIC分配器层中断挂起 与 ICDISER_CPU 结果一样 id:57
ICDICPR.ICDICPR1 |= 1<<25;
break;
case 58:
delay(1);//延时消抖
if((GPX1.DAT & (1<<2))){
printf("K3 click!\n");
}
//先清楚中断源头的挂起
EXT_INT41_PEND |= 1<<2; //GPX1_2
//在清楚GIC分配器层中断挂起 与 ICDISER_CPU 结果一样 id:57
ICDICPR.ICDICPR1 |= 1<<26; // id 58
break;
case 64:
delay(1);//延时消抖
if((GPX3.DAT & (1<<2))){
printf("K4 click!\n");
}
//先清楚中断源头的挂起
EXT_INT43_PEND |= 1<<2; //GPX3_2
//在清楚GIC分配器层中断挂起 与 ICDISER_CPU 结果一样 id:57
ICDICPR.ICDICPR2 |= 1<<0; // id 64
break;
//case ....
}
//最后清除CPU中断挂起
//写入中断id 清除对应中断挂起
CPU0.ICCEOIR = irq_id;
}
int main()
{
int a = 100;
uart_init();
key_exti_init();
printf("hello!a=%d\r\n",a);
while(1);
return 0;
}