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

发布于:2024-10-12 ⋅ 阅读:(127) ⋅ 点赞:(0)

中断的产生与捕获(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文件入开发板

网站公告

今日签到

点亮在社区的每一天
去签到