栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

嵌入式arm(六)关于中断和按键中断示例

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

嵌入式arm(六)关于中断和按键中断示例

文章目录
    • 零 前言
    • 一 ARM的异常与中断
      • 1 异常有哪些
        • 1.1 七种异常的信息收集表
        • 1.2 七种异常的产生
          • 1.2.1 复位异常
          • 1.2.2 未定义指令异常
          • 1.2.3 软中断异常
          • 1.2.4 预取异常
          • 1.2.5 数据异常
          • 1.2.6 外部中断异常
          • 1.2.7 快速中断异常
      • 2 异常的优先级
      • 3 异常与处理器模式
      • 4 异常的响应与返回
        • 4.1编程时需要知道的:
          • 4.1.1 异常发生时
            • 4.1.1.1 系统做了什么?
            • 4.1.1.2 程序需要做什么?
          • 4.1.2 当中断程序执行完之后, 程序需要做什么?
        • 4.2 来看详细的
          • 4.2.1 arm响应流程
          • 4.2.2 关于返回地址
    • 二 按键中断程序示例
      • 1 查硬件电路
      • 2 查寄存器
      • 3 程序

零 前言

本文只供参考按键中断程序示例,arm裸机编程始终不是最终的目标,能够完成简单的外设驱动,能理解裸机编程流程即可,要不求甚解,最后的目标还是系统移植,还是在linux下编程,所以不要对学不会裸机编程过于烦躁。
异常的相关概念和响应,到底哪个放在前面好理解呢?我认为先知道异常的相应原理再去看概念容易被接受,但是不知道有哪些概念,又无法知道响应时到底在干嘛。所以本文还是按先概念再相应原理的顺序来安排,但是本文读者应该是有基础的开发者,因此可以自由翻阅,或者先大致浏览概念,仔细看过异常相应之后再来详细看概念。

一 ARM的异常与中断

概述:当异常发生时,处理器会把pc设置为一个特定的地址,即让程序跳转到那个地址去执行那里的指令;这个地址位于中断向量表中,表中是固定好顺序的几个跳转指令;
存储器映射地址0x0是为向量表保留的,很多处理器的向量表都是从0x0地址开始;在某些操作系统如linux中,向量表地址可以选择到其他地址开始;

1 异常有哪些 1.1 七种异常的信息收集表

arm的异常总共有7种。

异常类型英文名处理器模式优先级返回地址返回地址的用途执行地址
复位异常resetsvc特权模式1-复位没有lr0x0
未定义指令异常undefined interruptundefined未定义指令中止模式7lr指向未定义指令的下一条指令0x04
软中断异常swisvc特权模式6lr指向swi指令的下一条指令0x08
预取异常prefetch abortabort指令预取中止模式5lr-4指向导致预取指令异常的那条指令0x0c
数据异常data abortabort数据访问中止模式2lr-8指向导致数据中止异常的指令0x10
外部中断异常irqirq外部中断请求模式4lr-4指向发生异常时正在执行的指令0x18
快速中断异常fiqfiq快速中断请求模式3lr-4指向发生异常时正在执行的指令0x1c

注:0x14地址未使用;

1.2 七种异常的产生 1.2.1 复位异常

通常用于系统上电和软硬件导致系统复位两种情况,产生复位异常中断。
复位即从头开始,复位异常中断处理程序进行的是初始化工作。下面列出一些常见的工作:

  • 初始化数据栈和寄存器;
  • 初始化存储系统,如系统的MMU;
  • 初始化关键的I/O设备;
  • 使能中断;
  • 将处理器切换到合适的模式;
  • 初始化C变量,跳转到应用程序执行;
1.2.2 未定义指令异常

见名知意,就是arm遇到了一条未定义的指令,这种情况不是应该报错吗?为啥还有专门的异常。
因为有协处理的存在,arm中有专门的协处理器指令来操作它,如果程序中写了协处理器指令,但是外部的协处理器未响应,或者根本没有协处理器时,无法执行,则发生未定义指令异常;也就是说,这是一种与硬件相关的异常,而不是一种语法错误,与编译器无关;
我们可以利用它的这种特点,来实现指令集的扩展:
协处理器有很多种,选择当前系统中没有的协处理器,运行其相关的指令,使之可以发生未定义指令异常,在异常处理程序中放上自己想要的代码,可以仿真这种协处理器的功能,也可以干别的事。
如果要仿真,步骤如下:
(1) 将仿真程序入口地址链接到向量表中0x4处。(在start.s中)
(2) 读取该未定义指令的bits[27:24]位,判断其是否是一条协处理器指令,0b1110或者0b110x是协处理器指令,然后根据[11:8]位判断具体是哪一条指令,然后实现其功能;
不仿真的话,就在中断处理程序中写自己的代码;

1.2.3 软中断异常

软中断由swi指令产生,即由程序中的代码产生,与硬件电路无关;软中断进入的是svc模式;

1.2.4 预取异常

由系统存储器报告,当处理器去取一条被标记为预取无效的指令时,发生预取异常。
如果系统中不包含MMU,指令预取异常中断处理程序只是简单报告错误并退出,否则引起异常的指令的物理地址被存储到内存中。

1.2.5 数据异常

数据异常:当存储器访问指令load/store执行,但目标地址不存在或者该地址不允许访问时,由存储器发出数据终止信号。

1.2.6 外部中断异常

当处理器的外部中断请求引脚有效,而且CPSR的I位被清0,产生外部中断异常。

1.2.7 快速中断异常

当快速中断请求引脚有效且CPSR寄存器的F位被清0,发生快速中断异常。

2 异常的优先级

优先级高的优先执行;

3 异常与处理器模式

每种异常都会进入一种特定的模式。
用户模式和系统模式不可通过异常进入,只能通过编程来切换。也就是说,在中断处理程序的最后要切换回用户模式;

4 异常的响应与返回 4.1编程时需要知道的: 4.1.1 异常发生时 4.1.1.1 系统做了什么?

(1) 用spsr保存cpsr;
(2) 改CPSR寄存器:

  • 改工作模式;
  • 让处理器模式为:arm;
  • 禁止中断;(如果需要)
    (3) 保存返回地址到lr;
    (4) 设置pc为相应的中断向量;
4.1.1.2 程序需要做什么?

保存现场到栈区:
用连续地址访问指令ldrm把用到的寄存器和lr一次性保存到栈区我们自己申请的空间内;

4.1.2 当中断程序执行完之后, 程序需要做什么?

(1) 恢复现场–》恢复寄存器的值:用连续地址访问指令strm把栈区我们自己申请的空间内的数据放回到异常之前的寄存器和lr中;
(2) 将spsr的值恢复到cpsr–》恢复工作模式,工作模式不会自动回复,需要我们手动恢复;
(3) 恢复lr到pc—》继续执行之前的代码;那不如直接把栈中的数据返回到pc中,不要中间经过lr;

4.2 来看详细的 4.2.1 arm响应流程

1.判断处理器状态
上述4.1.1.1中的切换处理器状态,异常发生时,处理器自动切换到arm状态,所以在异常处理函数中要判断,在异常发生前,是arm还是thumb状态,通过spsr的T位可以判断。
一般只在swi处理函数中才需要知道异常发生前处理器的状态,所以在thumb状态下,调用swi软中断异常需要注意:发生异常的指令地址是lr-2而不是lr-4;thumb是16位指令,用LDRH指令来判断中断向量号;
2.向量表
每个异常发生时,程序都会从异常向量表中开始跳转,一般向量表中放的就是跳转指令;
其中FIQ向量地址是0x1c,也就是最后一个向量,那么,FIQ_Handler()可以直接从0x1c开始,省下一条跳转指令;

4.2.2 关于返回地址

1.1表中列出了每种异常在返回时需要跳转的地址;
解释:
根据三级流水线,假设有连续的三条指令a; b; c; d; 当处理器将要执行a指令时,pc已经在c指令的地址即pc+8,所以pc-8是a的地址,pc-4是b的地址;
当异常发生时,处理器会把正在执行的指令的下一条指令的地址保存在lr中,也就是pc-4;所有异常都是如此,但是根据不同异常的发生方式,在返回时,还需要对lr上的地址做一点处理;
(1) swi和未定义指令异常,在执行指令时处理异常,返回地址就是lr;
(2) IRQ和FIQ,处理器会把当前指令执行完再处理异常,处理中断时pc已经指到了d指令,所以保存在lr中的pc-4指的是c指令;而我们要返回到b指令,在返回时,需要再补-4才能回到b指令,所以返回地址是lr-4;
(3) 指令预取中止异常,比如b指令是一个无效指令,在预取b指令的时候,b会被标记为预取无效,同时b之前的指令仍然正常运行,当执行到b,pc指d,预取中止异常发生,pc-4也就是c的地址被写入lr,好像跟(1)中一样,但是预取中止异常时程序要返回到b指令那里取读b指令,要返回到b处,对lr补-4;所以返回地址是lr-4;
(4) 数据中止异常,实在访问数据出错时候产生的,也就是在数据访问指令执行结束后才发生异常,而所以返回地址应该为lr-4,但中止异常返回时,需要返回到导致异常的指令,因此再补-4;所以最后返回地址是lr-8;例如执行a访问数据,pc指c,访问出错发生异常是在a执行后,pc指d,而异常返回要求返回到a,所以lr-4指向b,所以要用lr-8返回a;
(5) 复位异常不需要返回地址;

二 按键中断程序示例

IRQ即中断请求,本节来看一下用于按键的外部中断;

1 查硬件电路

2 查寄存器

以下为IO引脚寄存器设置:


引脚中断设置:



如下图,k2接的EINT[9]的id=57,spi port no=25;


下面是中断相关寄存器:
下面是使能id对应位;第二张图中25号对应偏移地址0x104,所以要操作ICDISER1_CPU0;


下面选择分发的cpu接口:
下图可以看出spi_25号应该选择偏移地址0x838;

则为ICDIPTR14_CPU0:

第25号对应8-11位;

设置为0x0,接口0;

下图中使能分发器

使能CPU接口:

设置优先级过滤器:

3 程序

我们主要看start.s和main.c中的中断配置程序:

@start.s
.text
.global _start
_start:			@程序入口
b reset			@程序的开始,上电复位,跳转到复位程序;
nop
nop
nop				@向量表顺序不能乱,用nop占位;
nop
nop
b irq_handler	@向量表中的跳转指令,跳到irq处理程序;
nop

reset:
    @告诉cpu0x40008000就是0地址
	@那么cpu在找中断向量表时就会找这个位置去找
	ldr r0,=0x40008000
	mcr p15,0,r0,c12,c0,0			@协处理器指令,读协处理器中的寄存器数据到ARM处理器的r0里面。应该是ARM访问MMU,一般cp15就是MMU。
	
    @此时是svc模式
	@切换到irq模式,给sp赋值
	msr cpsr,0x52					@0x52是irq模式,arm状态,不禁止中断;
	ldr sp,=stack_irq_base			@此时sp是sp_irq;
	@切换为用户模式,注意不能禁止irq
	msr cpsr, #0x50 				@0x50是user模式,arm状态,不禁止中断;
    ldr sp,=stack_usr_base			@此时sp是sp_user;
	b main							@复位完成后开始执行main();
	
irq_handler:						@irq处理程序;
    @irq模式下的栈
	@进栈保存现场
	@中断产生时,是将pc的值赋给了lr
	sub lr,lr,#4
	stmfd sp!, {r0-r12,lr}
	@做中断要做的事情
	bl do_irq
irq_handler_end:
	@出栈恢复现场
	ldmfd sp!, {r0-r12, pc}^
.data
stack_irq:						@为irq申请栈空间;
	.space 100					@申请100字节空间;
stack_irq_base:					@stack_irq_base即栈顶是100字节空间最大地址;因为arm默认满减栈;
stack_usr:						@为usr申请栈空间;
	.space 100
stack_usr_base:
.end

#define GPA1CON (*(volatile unsigned int *)0x11400020)
#define ULCON2 (*(volatile unsigned int *)0x13820000)
#define UCON2 (*(volatile unsigned int *)0x13820004)
#define UBRDIV2 (*(volatile unsigned int *)0x13820028)
#define UFRACVAL2 (*(volatile unsigned int *)0x1382002C)
#define UTRSTAT2 (*(volatile unsigned int *)0x13820010)
#define UTXH2 (*(volatile unsigned int *)0x13820020)
#define URXH2 (*(volatile unsigned int *)0x13820024)

#define GPX1CON (*(volatile unsigned int *)0x11000C20)
#define EXT_INT41CON (*(volatile unsigned int *)0x11000E04)
#define EXT_INT41_MASK (*(volatile unsigned int *)0x11000F04)
#define ICDISER1_CPU0 (*(volatile unsigned int *)0x10490104)
#define ICDIPTR14_CPU0 (*(volatile unsigned int *)0x10490838)
#define ICDDCR (*(volatile unsigned int *)0x10490000)
#define ICCICR_CPU0 (*(volatile unsigned int *)0x10480000)
#define ICCPMR_CPU0 (*(volatile unsigned int *)0x10480004)
#define ICCIAR_CPU0 (*(volatile unsigned int *)0x1048000C)
#define EXT_INT41_PEND (*(volatile unsigned int *)0x11000F44)
#define ICDICPR1_CPU0 (*(volatile unsigned int *)0x10490284)
#define ICCEOIR_CPU0 (*(volatile unsigned int *)0x10480010)

void init_uart()
{
	//设置GPA1_1这个管脚的功能为UART_TXD  
	//将4-7位设置为0x2
	GPA1CON = GPA1CON & ~(0xf << 4) | (0x2 << 4);
	
	//设置GPA1_0管脚功能为UART_RXD
	//将0-3位设置为0x2
	GPA1CON = GPA1CON & ~(0xf << 0) | (0x2 << 0);
	//设置uart的功能模块
    	
	//寄存器名称:ULCON2  UCON2  UBRDIV2  UFRACVAL2
	//数据位为8,停止位1位,奇偶校验位无
	ULCON2 = 0x03;
	//数据发送方式设置为轮询
	//UCON2的2-3位设置为0x01
	UCON2 = UCON2 & ~(0x3 << 2) | (0x1 << 2);
	
	//波特率为115200
	
	//前提:每个数据位会采样16次,取中间几次,才能确定接收到的是0还是1
	//也就是需要有一个节拍来指挥采样,也就是每采样一次需要(1/115200)/16s,其实也就是一个时钟周期
	//所以需要的节拍频率(1s会产生多少个时钟周期)是多少呢
	//所以:节拍频率 = 115200*16
	//经过查芯片手册第7章得知给UART分的工作频率是100MHZ
	//UBRDIV2 = 100000000/(115200*16)-1 = 53
	//UFRACVAL2 = 0.253*16 = 4
	UBRDIV2 = 53;
    UFRACVAL2 = 4;
}

void putc(char c)
{
	//如果发送缓冲区不为空,就一直循环判断
	//UTRSTAT2寄存器的第2位如果为0表示发送缓冲区不为空
	while(!(UTRSTAT2 & 0x02));	
	//否则就往发送缓冲区放数据
	UTXH2 = c;
}

char getc()
{
	//判断接收缓冲区是否收到了数据
	//如果收到就从接收缓冲区获得这个数据
	while(!(UTRSTAT2 & 0x01));
	char c = (char)URXH2;
	return c;
}
void delay()
{
	int i;
	for(i = 0; i < 1000000; i++);
}
void init_irq_k2()
{
	
	//设置GPX1_1为中断模式
	//将4-7位设置为0xF
	GPX1CON = GPX1CON & ~(0xf << 4) | (0xf << 4);
	//将中断触发方式设置为下降沿触发
	//将4-6位设置为0X2
	EXT_INT41CON = EXT_INT41CON & ~(0x7 << 4) | (0x2 << 4);
	//使能中断
	//将1位设置为0
	EXT_INT41_MASK &= ~0x02;
	
	
	//查芯片手册得知
	//k2(EINT9)的ID:57  SPI port no:25
	//使能相应中断到分发器
	//将ICDISER1_CPU0的第25位设置为1,每个寄存器的每一位刚好对应了一个SPI port no
	ICDISER1_CPU0 |= 0x1 << 25;
	
	//选择cpu接口(分发)
	//ICDIPTR14_CPU0的8-15位设置为00000001,表示选择的是cpu0
	ICDIPTR14_CPU0 |= (0x01 << 8);
	
	//使能分发器
	ICDDCR = 1;
	
	//接口使能
	ICCICR_CPU0 = 1;
	//设置优先级,数字越大,优先级越低  设置能够处理所有中断
	ICCPMR_CPU0 = 255;
}

void do_irq()
{
	//通过ID号判断是什么中断
	//如果ID号是57,往串口发送'i'
	
	//ICCIAR_CPU0这个寄存器的低10位为中断ID号
	int irqId = ICCIAR_CPU0 & 0x3ff;
	switch(irqId)
	{
		case 57:
			putc('i');
			//清GPX1_1中断标志
			EXT_INT41_PEND |= (0x1 << 1);
			//清GIC的中断标志
			ICDICPR1_CPU0 = ICDICPR1_CPU0 | (0x1 << 25);
			//cpu0级,将处理完成中断ID号写入一个寄存器,表示中断处理完成
			ICCEOIR_CPU0 = ICCEOIR_CPU0 & ~(0x3ff) | irqId;
			break;
		default:
			putc('e');
	}
}
//现象为串口一直发送a,按下按键发送i;
int main()
{
	init_uart();
	init_irq_k2();
	while(1)
	{
		putc('a');
		delay();
	}
	return 0;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/974843.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号