ARM编程四--->中断编写流程

中断的产生与捕获(ARM 处理器)

  1. 概念
    中断是一种用于响应外部或内部事件的机制,能够暂时打断当前正在执行的程序,去处理紧急事件。中断使得处理器能够优先处理重要的任务,如外设的数据读取、定时器溢出等。
  2. 中断的分类
  • FIQ(Fast Interrupt Request,快速中断):FIQ 是一种优先级较高的中断类型,用于处理时间要求严格、响应速度快的中断。它具有专用的寄存器组,避免了中断进入时的保存操作,提高了处理效率。
  • IRQ(Interrupt Request,普通中断):IRQ 是一种较为常见的中断类型,优先级低于 FIQ,通常用于一般的设备或系统事件处理。
  1. 中断的处理流程
    1. 中断的产生
      • 当外设或定时器等模块检测到中断事件时,会向中断控制器(如 GIC)发出中断请求。
      • 中断控制器处理中断信号,将中断请求传递给 CPU。
    2. 中断捕获与响应
      • 保存当前上下文:当 CPU 收到中断请求后,立即中断当前的任务执行,保存当前的上下文(包括 R0-R12 寄存器和 CPSR 状态寄存器)。
      • 跳转到中断处理程序:CPU 根据中断类型,从中断向量表中获取中断处理程序的入口地址,并跳转到中断服务例程(ISR)。
      • 中断优先级处理:如果同时存在多个中断,GIC 通过优先级机制决定先处理哪个中断。
    3. 中断处理
      • 中断处理程序执行特定任务。
        • 例如:读取外设的数据。清除中断标志位。更新计数器或内存状态等。
      • 中断服务程序可以使用寄存器 R0-R12,这些寄存器会在进入中断时保存到内存。
    4. 中断处理完成
    • 恢复上下文:当中断处理程序完成后,CPU 恢复之前保存的寄存器和程序状态寄存器(SPSR 恢复到 CPSR)。
    • 返回中断前的代码:CPU 跳回到被中断的代码继续执行。这是通过将程序计数器(PC)恢复到中断前的地址来实现的。
  2. 中断控制器(GIC)
  • 功能:ARM 系统中通常包含一个通用中断控制器(GIC,Generic Interrupt Controller),负责管理所有中断请求。它不仅扩展了中断源,还负责:
    • 中断优先级的管理:根据中断的优先级来决定哪个中断优先响应。
    • 中断屏蔽:在处理较高优先级的中断时,较低优先级的中断可能会被屏蔽。
    • 中断分发:在多核系统中,GIC 可以将中断请求分发给不同的 CPU 核。
  1. 中断的上下文切换
  • 中断处理期间,CPU 需要对上下文进行切换,这包括:
    • 保存当前的寄存器内容(R0-R12、PC、CPSR)。
    • 切换到相应的中断模式,执行中断服务程序。
    • 处理完毕后,恢复之前保存的寄存器值,继续执行被中断的任务。
  1. ARM 中断向量表
    ARM 处理器的中断向量表是一个固定地址表,通常存储在内存的开始部分。该表包含不同类型中断的入口地址
中断类型描述触发条件
Reset(复位)处理器启动后的第一个地址处理器上电或复位后触发
Undefined(未定义指令)执行未定义指令时触发当执行未定义的指令时
Software Interrupt(软件中断)通过 SWI 指令触发执行 SWI 指令时
Prefetch Abort(预取异常)指令预取阶段发生的异常处理器在指令预取阶段遇到异常时
Data Abort(数据异常)数据访问阶段发生的异常处理器在数据访问阶段遇到异常时
IRQ(普通中断)外设或其他设备触发的普通中断处理器的中断请求线触发
FIQ(快速中断)优先级更高的快速中断处理器的快速中断请求线触发

按键中断ARM编程流程

1.编写main.c
  1. 看电路图找到CPU对应的控制管脚 GPX1_1
  2. 看芯片手册,找到对应寄存器
    a. 配置管脚为interrupt模式
    b. 功能块设置
  3. 编程
    代码如下
//初始化串口通信,通过 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;  // 设置中断目标处理器为 CPU0
  ICDDCR = 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 号不是 57
		  putc('e');  // 发送字符 'e' 表示异常中断
		  break;
	}
	ICCEOIR_CPU0 = (ICCEOIR_CPU0 & 0x3FF) | irq_num;  // 写入中断 ID,表示中断处理结束
}

// 主函数
int main(void) 
{
	uart_init();           // 初始化 UART
	interrupt_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 和 lr
    bl   do_irq                     @ 跳转到 do_irq 处理函数
    ldmfd sp!, {r0-r12, pc}^        @ 恢复现场,恢复 r0-r12 和 pc,使用异常返回(^表示带 S 标志位)

reset: 
	  ldr	r0, =0x40008000          @ 将异常向量表基地址设置为 0x40008000
	  mcr	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: 删除反汇编输出文件。
5. 执行make,烧录bin文件入开发板
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值