ARM编程四--->中断编写流程
中断的产生与捕获(ARM 处理器)
- 概念
中断是一种用于响应外部或内部事件的机制,能够暂时打断当前正在执行的程序,去处理紧急事件。中断使得处理器能够优先处理重要的任务,如外设的数据读取、定时器溢出等。 - 中断的分类
- FIQ(Fast Interrupt Request,快速中断):FIQ 是一种优先级较高的中断类型,用于处理时间要求严格、响应速度快的中断。它具有专用的寄存器组,避免了中断进入时的保存操作,提高了处理效率。
- IRQ(Interrupt Request,普通中断):IRQ 是一种较为常见的中断类型,优先级低于 FIQ,通常用于一般的设备或系统事件处理。
- 中断的处理流程
- 中断的产生
- 当外设或定时器等模块检测到中断事件时,会向中断控制器(如 GIC)发出中断请求。
- 中断控制器处理中断信号,将中断请求传递给 CPU。
- 中断捕获与响应
- 保存当前上下文:当 CPU 收到中断请求后,立即中断当前的任务执行,保存当前的上下文(包括 R0-R12 寄存器和 CPSR 状态寄存器)。
- 跳转到中断处理程序:CPU 根据中断类型,从中断向量表中获取中断处理程序的入口地址,并跳转到中断服务例程(ISR)。
- 中断优先级处理:如果同时存在多个中断,GIC 通过优先级机制决定先处理哪个中断。
- 中断处理
- 中断处理程序执行特定任务。
- 例如:读取外设的数据。清除中断标志位。更新计数器或内存状态等。
- 中断服务程序可以使用寄存器 R0-R12,这些寄存器会在进入中断时保存到内存。
- 中断处理程序执行特定任务。
- 中断处理完成
- 恢复上下文:当中断处理程序完成后,CPU 恢复之前保存的寄存器和程序状态寄存器(SPSR 恢复到 CPSR)。
- 返回中断前的代码:CPU 跳回到被中断的代码继续执行。这是通过将程序计数器(PC)恢复到中断前的地址来实现的。
- 中断的产生
- 中断控制器(GIC)
- 功能:ARM 系统中通常包含一个通用中断控制器(GIC,Generic Interrupt Controller),负责管理所有中断请求。它不仅扩展了中断源,还负责:
- 中断优先级的管理:根据中断的优先级来决定哪个中断优先响应。
- 中断屏蔽:在处理较高优先级的中断时,较低优先级的中断可能会被屏蔽。
- 中断分发:在多核系统中,GIC 可以将中断请求分发给不同的 CPU 核。
- 中断的上下文切换
- 中断处理期间,CPU 需要对上下文进行切换,这包括:
- 保存当前的寄存器内容(R0-R12、PC、CPSR)。
- 切换到相应的中断模式,执行中断服务程序。
- 处理完毕后,恢复之前保存的寄存器值,继续执行被中断的任务。
- ARM 中断向量表
ARM 处理器的中断向量表是一个固定地址表,通常存储在内存的开始部分。该表包含不同类型中断的入口地址
中断类型 | 描述 | 触发条件 |
---|---|---|
Reset(复位) | 处理器启动后的第一个地址 | 处理器上电或复位后触发 |
Undefined(未定义指令) | 执行未定义指令时触发 | 当执行未定义的指令时 |
Software Interrupt(软件中断) | 通过 SWI 指令触发 | 执行 SWI 指令时 |
Prefetch Abort(预取异常) | 指令预取阶段发生的异常 | 处理器在指令预取阶段遇到异常时 |
Data Abort(数据异常) | 数据访问阶段发生的异常 | 处理器在数据访问阶段遇到异常时 |
IRQ(普通中断) | 外设或其他设备触发的普通中断 | 处理器的中断请求线触发 |
FIQ(快速中断) | 优先级更高的快速中断 | 处理器的快速中断请求线触发 |
按键中断ARM编程流程
1.编写main.c
- 看电路图找到CPU对应的控制管脚 GPX1_1
- 看芯片手册,找到对应寄存器
a. 配置管脚为interrupt模式
b. 功能块设置 - 编程
代码如下
//初始化串口通信,通过 UART 发送和接收字符
//初始化 GPIO 外部中断,配置下降沿触发的中断模式
//当发生 GPIO 中断时,处理器会跳转到中断处理程序 do_irq(),执行相应的中断处理逻辑,并通过串口发送特定字符来表示中断已处理
#define GPX1CON (*(volatile unsigned int *)0x11000C20) // GPX1组管脚控制寄存器
#define EXT_INT41CON (*(volatile unsigned int *)0x11000E04) // 外部中断41组配置寄存器,配置中断触发方式
#define EXT_INT41_MASK (*(volatile unsigned int *)0x11000F04) // 外部中断41组掩码寄存器,控制中断使能
#define ICDISER1_CPU0 (*(volatile unsigned int *)0x10490104) // GIC中断使能寄存器1,启用中断号对应的中断源
#define ICDDCR (*(volatile int *)0x10490000) // GIC分发控制寄存器,使能中断分发
#define ICCICR_CPU0 (*(volatile int *)0x10480000) // CPU接口控制寄存器,启用CPU0中断处理
#define ICCPMR_CPU0 (*(volatile int *)0x10480004) // CPU接口优先级掩码寄存器,设置中断优先级门槛
#define ICDIPTR14_CPU0 (*(volatile int *)0x10490838) // 中断目标处理器寄存器,指定中断目标处理器
#define ICCIAR_CPU0 (*(volatile int *)0x1048000C) // 中断激活ID寄存器,获取中断ID号
#define EXT_INT41_PEND (*(volatile int *)0x11000f44) // 外部中断41组挂起寄存器,用于清除中断标志
#define ICDICPR1_CPU0 (*(volatile int *)0x10490284) // GIC中断清除挂起寄存器1,清除中断
#define ICCEOIR_CPU0 (*(volatile int *)0x10480010) // 中断处理结束寄存器,表示中断处理完成#define GPA1CON (*(volatile unsigned int *)0x11400020) // GPA1组管脚控制寄存器
#define ULCON2 (*(volatile unsigned int *)0x13820000) // UART2控制寄存器,设置数据位、校验和停止位
#define UCON2 (*(volatile unsigned int *)0x13820004) // UART2控制寄存器,设置发送和接收模式
#define UBRDIV2 (*(volatile unsigned int *)0x13820028) // UART2波特率寄存器,设置波特率整数部分
#define UFRACVAL2 (*(volatile unsigned int *)0x1382002c) // UART2波特率寄存器,设置波特率小数部分
#define UTXH2 (*(volatile unsigned int *)0x13820020) // UART2发送缓冲寄存器,用于发送数据
#define UTRSTAT2 (*(volatile unsigned int *)0x13820010) // UART2状态寄存器,检查发送和接收状态// UART初始化函数
void uart_init(void)
{GPA1CON = 0x22; // 将 GPA1_0 和 GPA1_1 设置为 UART TX 和 RX 模式ULCON2 = 0x03; // 配置 UART2:8 位数据位,无校验,1 位停止位UCON2 = 0x05; // 设置 UART2 为轮询模式进行发送和接收UBRDIV2 = 53; // 设置波特率整数部分(波特率为 115200)UFRACVAL2 = 4; // 设置波特率小数部分
}// 简单的微秒级延时函数
void usleep(int us)
{while(us--){int i = 10000; // 内部计数,用于产生较短时间的延时while(i--); }
}// 发送单个字符
void putc(char c)
{while(1){if( UTRSTAT2 & 0x02) // 检查 UART2 是否可以发送(发送缓冲区是否空)break;}UTXH2 = c; // 向发送缓冲寄存器写入字符,发送数据
}// 中断初始化函数
void interrupt_init()
{GPX1CON = (GPX1CON & ~(0x0F<<4)) | (0x0F<<4); // 将 GPX1_1 设置为中断模式EXT_INT41CON = (EXT_INT41CON & ~(0x07<<4)) | (0x02<<4); // 将 GPX1_1 配置为下降沿触发EXT_INT41_MASK = (EXT_INT41_MASK & ~(0x01<<1)); // 使能 GPX1_1 的外部中断ICDISER1_CPU0 = ICDISER1_CPU0 | (1<<25); // 启用 GIC 中断使能(EINT9 对应的中断号 57)ICDIPTR14_CPU0 = 0x01010101; // 设置中断目标处理器为 CPU0ICDDCR = ICDDCR | 1; // 启用 GIC 中断分发器ICCICR_CPU0 = 1; // 启用 CPU0 中断处理ICCPMR_CPU0 = 0xFF; // 设置 CPU0 的中断优先级门槛为最低(允许所有优先级的中断)
}// 中断处理函数
void do_irq(void)
{int irq_num;irq_num = ICCIAR_CPU0 & 0x3FF; // 从 ICCIAR 寄存器中读取中断 ID 号switch(irq_num){case 57: // 检查中断 ID 号是否为 57(GPX1_1 对应的中断号)putc('i'); // 发送字符 'i' 表示中断被触发EXT_INT41_PEND = EXT_INT41_PEND | (1<<1); // 清除 GPX1_1 的外部中断挂起标志ICDICPR1_CPU0 = ICDICPR1_CPU0 | (1<<25); // 清除 GIC 中 GPX1_1 中断标志break;default: // 如果中断 ID 号不是 57putc('e'); // 发送字符 'e' 表示异常中断break;}ICCEOIR_CPU0 = (ICCEOIR_CPU0 & 0x3FF) | irq_num; // 写入中断 ID,表示中断处理结束
}// 主函数
int main(void)
{uart_init(); // 初始化 UARTinterrupt_init(); // 初始化中断while(1){putc('a'); // 在循环中不断通过串口发送字符 'a'usleep(10); // 每次发送后延时 10 微秒}return 0;
}
2. 编写start.S
- 这段代码是 ARM 汇编语言中的裸机程序,主要用来演示 ARM 处理器的中断处理机制及其初始化过程。它设置了异常向量表,初始化了栈,并实现了一个简单的 IRQ 中断处理程序。
- 提供裸机编程基础
.global delay1s @ 定义全局符号 delay1s,用于延时函数的跳转
.text @ 指定代码段
.global _start @ 定义全局符号 _start,程序的入口点_start:b reset @ 跳转到 reset 函数(复位处理)nop @ 占位符,无操作指令nop @ 占位符,无操作指令nop @ 占位符,无操作指令nop @ 占位符,无操作指令nop @ 占位符,无操作指令ldr pc, _irq @ 从地址 _irq 加载中断处理程序的入口地址到 PC 寄存器nop @ 占位符,无操作指令_irq: .word irq_handler @ 定义中断向量表中的 IRQ 处理程序的地址irq_handler: @ IRQ 中断处理程序的入口sub lr, lr, #4 @ 返回到中断前的地址,修正链接寄存器 (LR)stmfd sp!, {r0-r12, lr} @ 保存现场,使用全递减堆栈保存 r0-r12 和 lrbl do_irq @ 跳转到 do_irq 处理函数ldmfd sp!, {r0-r12, pc}^ @ 恢复现场,恢复 r0-r12 和 pc,使用异常返回(^表示带 S 标志位)reset: ldr r0, =0x40008000 @ 将异常向量表基地址设置为 0x40008000mcr p15, 0, r0, c12, c0, 0 @ 将基地址写入 VBAR(Vector Base Address Register)ldr r0, =stacktop @ 加载栈顶地址,初始化栈指针/********svc mode stack********/mov sp, r0 @ 将栈顶地址赋值给 svc 模式的栈指针sub r0, #128*4 @ 预留 512 字节空间用于 irq 模式的栈/****irq mode stack****/msr cpsr, #0xd2 @ 切换到 IRQ 模式mov sp, r0 @ 设置 IRQ 模式的栈顶sub r0, #128*4 @ 预留 512 字节空间用于 fiq 模式的栈/***fiq mode stack***/msr cpsr, #0xd1 @ 切换到 FIQ 模式mov sp, r0 @ 设置 FIQ 模式的栈顶sub r0, #0 @ FIQ 栈不需要更多空间/***abort mode stack***/msr cpsr, #0xd7 @ 切换到 Abort 模式mov sp, r0 @ 设置 Abort 模式的栈顶sub r0, #0 @ Abort 模式的栈不需要更多空间/***undefine mode stack***/msr cpsr, #0xdb @ 切换到 Undefined 模式mov sp, r0 @ 设置 Undefined 模式的栈顶sub r0, #0 @ Undefined 模式栈不需要更多空间/*** sys mode and usr mode stack ***/msr cpsr, #0x10 @ 切换到系统/用户模式mov sp, r0 @ 设置用户模式栈顶,预留 1024 字节的栈空间b main @ 跳转到 main 函数执行主程序.align 4 @ 地址对齐到 4 字节.data @ 数据段,定义变量
stack: .space 4*512 @ 为栈分配 4 * 512 字节的空间
stacktop: @ 栈顶指针.end @ 程序结束标志
3. 加入map.lds
定义链接规则,该文件在资源里面
4. 定义Makefile,定义编译流程
all:arm-none-linux-gnueabi-gcc -fno-builtin -nostdinc -c -o start.o start.S
# 使用交叉编译器编译 start.S 汇编文件,生成目标文件 start.o。
# -fno-builtin: 禁用内置函数,避免使用 GCC 的内置库函数。
# -nostdinc: 不使用标准的 include 头文件,适合裸机编程。
# -c: 只编译,不链接。
# -o start.o: 输出目标文件名为 start.o。arm-none-linux-gnueabi-gcc -fno-builtin -nostdinc -c -o main.o main.c
# 使用交叉编译器编译 main.c C 源文件,生成目标文件 main.o。
# 其参数与前一个命令相同,只是编译的文件不同(C 文件而非汇编文件)。arm-none-linux-gnueabi-ld start.o main.o -Tmap.lds -o main.elf
# 使用交叉链接器 ld,将目标文件 start.o 和 main.o 链接在一起,生成 ELF 可执行文件 main.elf。
# -Tmap.lds: 指定链接脚本 map.lds,用于控制程序在内存中的布局。
# -o main.elf: 输出文件名为 main.elf。arm-none-linux-gnueabi-objcopy -O binary main.elf main.bin
# 使用 objcopy 工具,将 ELF 文件 main.elf 转换为纯二进制文件 main.bin。
# -O binary: 输出格式为纯二进制文件。
# main.elf: 输入文件是 ELF 格式的可执行文件。
# main.bin: 输出的纯二进制文件名。arm-none-linux-gnueabi-objdump -D main.elf > main.dis
# 使用 objdump 工具对 main.elf 文件进行反汇编,并将结果保存到 main.dis 文件中。
# -D: 反汇编整个 ELF 文件。
# main.elf: 输入文件为 ELF 格式。
# main.dis: 输出的反汇编结果文件。clean:rm -rf *.bak *.o *.elf *.bin *.dis
# 清理命令,删除所有编译生成的中间文件和输出文件。
# rm -rf: 强制删除,不提示错误。
# *.bak: 删除备份文件(如果有)。
# *.o: 删除目标文件。
# *.elf: 删除 ELF 可执行文件。
# *.bin: 删除生成的二进制文件。
# *.dis: 删除反汇编输出文件。