第014课 异常与中断
目录
第001节_概念引入与处理流程
取个场景解释中断 假设有个大房间里面有小房间,婴儿正在睡觉,他的妈妈在外面看书。 问:这个母亲怎么才能知道这个小孩醒? 1 过一会打开一次房门,看婴儿是否睡醒,让后接着看书 2 一直等到婴儿发出声音以后再过去查看,期间都在读书
第一种 叫做查询方式:
- 优点:简单
- 缺点: 累
写程序如何:
while(1)
{
1 read book(读书)
2 open door(开门)
if(睡)
return(read book)
else
照顾小孩
}
第二种叫中断方式: 优点:不累 缺点:复杂
写程序:
while(1)
{
read book
中断服务程序()//如何被调用?
{
处理照顾小孩
}
}
我们还是看看母亲被小孩哭声打断如何照顾小孩?
母亲的处理过程 1 平时看书 2 发生了各种声音,如何处理这些声音 有远处的猫叫(听而不闻,忽略) 门铃声有快递(开门收快递) 小孩哭声(打开房门,照顾小孩) 3 母亲的处理 只会处理门铃声和小孩哭声 a 现在书中放入书签,合上书(保存现场) b 去处理 (调用对应的中断服务程序) c 继续看书(恢复现场)
不同情况,不同处理 a 对于门铃:开门取快件 b 对于哭声:照顾小孩
我们将母亲的处理过程抽象化 母亲的头脑相当于CPU 耳朵听到声音会发送信号给脑袋,声音来源有很多种,有远处的猫叫,门铃声,小孩哭声。这些声音传入耳朵,再由耳朵传给大脑,除了这些可以中断母亲的看书,还有其他情况,比如身体不舒服,有只蜘蛛掉下来,对于特殊情况无法回避,必须立即处理
对比我们得arm系统
---》图2
![](https://i.imgur.com/Yg9tRdq.png)
有CPU 有中断控制器,我们的中断源发送 中断控制器可以发信号给CPU告诉它发生了那些紧急情况 中断源有 按键 定时器 有其它的(比如网络数据) 这些信号都可以发送信号给中断控制器,再由中断控制器发送信号给CPU表明有这些中断产生了,这些成为中断(属于一种异常)
还有什么可以中断CPU运行 指令不对,数据访问有问题 reset信号,这些都可以中断CPU 这些成为异常中断
重点在于 保存现场以及恢复现场
处理过程 a 保存现场(各种寄存器) b 处理异常(中断属于一种异常) c 恢复现场
arm对异常(中断)处理过程
1 初始化:
a 设置中断源,让它可以产生中断
b 设置中断控制器(可以屏蔽某个中断,优先级)
c 设置CPU总开关,(使能中断)
2 执行其他程序:正常程序
3 产生中断:按下按键--->中断控制器--->CPU
4 cpu每执行完一条指令都会检查有无中断/异常产生 5 发现有中断/异常产生,开始处理 对于不同的异常,跳去不同的地址执行程序 这地址上,只是一条跳转指令,跳去执行某个函数 (地址) 指的是异常向量 如下就是异常向量表 对于不同的异常都有一条跳转指令
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq //发生中断时,CPU跳到这个地址执行该指令 **假设地址为0x18**
ldr pc, _fiq
//我们先在0x18这里放 ldr pc ,__irq,于是cpu最终会跳去执行__irq代码
//保护现场,调用处理函数,恢复现场
3-5都是硬件强制做的
6 这些函数做什么事情()软件做的 a 保存现场(各种寄存器) b 处理异常(中断): 分辨中断源 再调用不同的处理函数 c 恢复现场
对比母亲的处理过程来比较arm中断的处理过程
中断处理程序怎么被调用? CPU--->0x18 --跳转到其他函数->
做保护现场 调用函数
分辨中断源 调用对应函数
恢复现场
cpu到0x18是由硬件决定的,跳去执行更加复杂函数(由软件决定)
第002节_CPU模式(Mode)_状态(State)与寄存器
这节课我们来讲CPU的工作模式(Mode) 状态(State)寄存器 7种Mode:
usr/sys undefined(und) Supervisor(svc) Abort(abt) IRQ(irq) FIQ(fiq)
2种State:
ARM state Thumb state
寄存器:
通用寄存器 备份寄存器(banked register) 当前程序状态寄存器(Current Program Status Register);CPSR CPSR的备份寄存器:SPSR(Save Program Status Register)
我们仍然以这个母亲为例讲解这个CPU模式 这个母亲无压力看书 -->(正常模式) 要考试,看书--->(兴奋模式) 生病---->(异常模式)
可以参考书籍 《ARM体系结构与编程》作者:杜春雷
对于ARM CPU有7种模式 1 usr :类比 正常模式 2 sys :类比的话兴奋模式 3 5种异常模式:(2440用户手册72页) 3.1 und :未定义模式 3.2 svc :管理模式 3.3 abt :终止模式 a 指令预取终止(读写某条错误的指令导致终止运行) b 数据访问终止 (读写某个地址,这个过程出错) 都会进入终止模式 3.4 IRQ: 中断模式 3.5 FIQ: 快中断模式
我们可以称以下6种为特权模式
und :未定义模式 svc :管理模式 abt :终止模式 IRQ :中断模式 FIQ :快中断模式 sys :系统模式
usr用户模式(不可直接进入其他模式) 可以编程操作CPSR直接进入其他模式
这个图是有关各个模式下能访问寄存器的,再讲这个图之前我们先引入 2种state
CPU有两种state 1 ARM state 使用ARM指令集,每个指令4byte 2 Thumb state 使用的是Thumb指令集,每个指令2byte
比如同样是 mov R0, R1 编译后 对于ARM指令集要占据4个字节:机器码 对于Thumb指令集占据2个字节:机器码 引入Thumb减少存储空间
百度搜索ARM指令集和Thumb指令的区别
现在先区分下ARM指令集与Thumb指令集
Thumb 指令可以看作是 ARM 指令压缩形式的子集,是针对代码密度的问题而提出的,它具有 16 位的代码密度但是它不如ARM指令的效率高 .Thumb 不是一个完整的体系结构,不能指望处理只执行Thumb 指令而不支持 ARM 指令集.因此,Thumb 指令只需要支持通用功能,必要时可以借助于完善的 ARM 指令集,比如,所有异常自动进入 ARM 状态.在编写 Thumb 指令时,先要使用伪指令 CODE16 声明,而且在 ARM 指令中要使用 BX指令跳转到 Thumb 指令,以切换处理器状态.编写 ARM 指令时,则可使用伪指令 CODE32声明.
下节课会演示使用Thumb指令集编译,看是否生成的bin文件会变小很多
在每种模式下都有R0 ~ R15 在这张图注意到有些寄存器画有灰色的三角形,表示访问该模式下访问的专属寄存器 比如
mov R0, R8 mov R0, R8
在System 模式下访问的是R0 ~ R8,在所有模式下访问R0都是同一个寄存器
mov R0,R8_fiq
但是在FIQ模式下,访问R8是访问的FIQ模式专属的R8寄存器,不是同一个物理上的寄存器
在这五种异常模式中每个模式都有自己专属的R13 R14寄存器,R13用作SP(栈) R14用作LR(返回地址) LR是用来保存发生异常时的指令地址
为什么快中断(FIQ)有那么多专属寄存器,这些寄存器称为备份寄存器
回顾一下中断的处理过程
1 保存现场(保存被中断模式的寄存器)
就比如说我们的程序正在系统模式/用户模式下运行,当你发生中断时,需要把R0 ~ R14这些寄存器全部保存下来,让后处理异常,最后恢复这些寄存器
但如果是快中断,那么我就不需要保存 系统/用户模式下的R8 ~ R12这几个寄存器,在FIQ模式下有自己专属的R8 ~ R12寄存器,省略保存寄存器的时间,加快处理速度
但是在Linux中并不会使用FIQ模式
2 处理
3 恢复现场
CRSR当前程序状态寄存器,这是一个特别重要的寄存器
SPSR保存的程序状态寄存器,他们格式如下
首先 M4 ~ M0 表示当前CPU处于哪一种模式(Mode)
我们可以读取这5位来判断CPU处于哪一种模式,也可以修改这一种模式位,让其修改这种模式
假如你当前处于用户模式下,是没有权限修改这些位的
M4 ~ M0对应什么值,会有说明
查看其他位
Bit5 State bits表示CPU工作与Thumb State还是ARM State用的指令集是什么 Bit6 FIQ disable当bit6等于1时,FIQ是不工作的 Bit7 IRQ disable当bit5等于1时,禁止所有的IRQ中断,这个位是IRQ的总开关 Bit8 ~ Bit27是保留位 Bite28 ~ Bit31是状态位,
什么是状态位,比如说执行一条指令
cmp R0, R1
如果R0 等于 R1 那么zero位等于1,这条指令影响 Z 位,如果R0 == R1,则Z = 1
beq跳转到xxx这条指令会判断Bit30是否为1,是1的话则跳转,不是1的话则不会跳转 使用 Z 位,如果 Z 位等于1 则跳转,这些指令是借助状态位实现的
SPSR保存的程序状态寄存器
表示发生异常时这个寄存器会用来保存被中断的模式下他的CPSR
就比如我我的程序在系统模式下运行 CPSR是某个值,当发生中断时会进入irq模式,这个CPSR_irq就保存系统模式下的CPSR
我们来看看发生异常时CPU是如何协同工作的
进入异常的处理流程(硬件)
我们来翻译一下: 发生异常时,我们的CPU会做什么事情 1把下一条指令的地址保存在LR寄存器里(某种异常模式的LR等于被中断的下一条指令的地址) 它有可能是PC + 4有可能是PC + 8,到底是那种取决于不同的情况 2 把CPSR保存在SPSR里面(某一种异常模式下SPSR里面的值等于CPSR) 3 修改CPSR的模式为进入异常模式(修改CPSR的M4 ~ M0进入异常模式) 4 跳到向量表
1 让LR减去某个值,让后赋值给PC(PC = 某个异常LR寄存器减去 offset)
减去什么值呢?
也就是我们怎么返回去继续执行原来的程序,根据下面这个表来取值
如果发生的是SWI可以把 R14_svc复制给PC 如果发生的是IRQ可以把R14_irq的值减去4赋值给PC 2 把CPSR的值恢复(CPSR 值等于 某一个一场模式下的SPSR) 3 清中断(如果是中断的话,对于其他异常不用设置)
第003节_不重要_Thumb指令集程序示例
在上节视频里说ARMCPU有两种状态 ARM State 每条指令会占据4byte Thumb State 每条指令占据2byte
我们说过Thumb指令集并不重要,本节演示把一个程序使用Thumb指令集来编译它 使用上一章节的重定位代码 打开Makefile和Start.S
Makefile文件
all:
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o init.o init.c
arm-linux-gcc -c -o main.o main.c
arm-linux-gcc -c -o start.o start.S
#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
对于使用Thumb指令集
all:
arm-linux-gcc -mthumb -c -o led.o led.c//只需要在arm-linux-gcc加上 mthumb命令即可
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o init.o init.c
arm-linux-gcc -c -o main.o main.c
arm-linux-gcc -c -o start.o start.S
#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
改进
all: led.o uart.o init.o main.o start.o //all依赖led.o uart.o init.o main.o start.o
#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
%.o : %.c
arm-linux-gcc -mthumb -c -o $@ $< //对于所有的.c文件使用规则就可以使用thumb指令集编译 $@表示目标 $<表示第一个依赖
%.o : %.S
arm-linux-gcc -c -o $@ $<
对start.S需要修改代码
原重定位章节Start.S文件
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
使用thumb指令集的Start.S文件
.text
.global _start
.code 32 //表示后续的指令使用ARM指令集
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
/* 怎么从ARM State切换到Thumb State? */
adr r0, thumb_func //定义此标号的地址
add r0, r0, #1 /* bit0=1时, bx就会切换CPU State到thumb state */
bx r0
.code 16 //下面都使用thumb指令集
thumb_func: //需要得到这个标号的地址
/*下面就是使用thumb指令来执行程序*/
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr r0, =main /* 绝对跳转, 跳到SDRAM ,先把main的地址赋值给R0 */
mov pc, r0 /*让后再移动到PC*/
halt:
b halt
上传代码编译测试
出现错误,如下
init.o(.text+0x6c):In function 'sdram_init2';
undefined reference to 'memcpy'
发现是init,o里sdram——init2使用的了memcpy函数
查看init.c
#include "s3c2440_soc.h"
void sdram_init(void)
{
BWSCON = 0x22000000;
BANKCON6 = 0x18001;
BANKCON7 = 0x18001;
REFRESH = 0x8404f5;
BANKSIZE = 0xb1;
MRSRB6 = 0x20;
MRSRB7 = 0x20;
}
#if 0
/**************************************************************************
* 设置控制SDRAM的13个寄存器
* 使用位置无关代码
**************************************************************************/
void memsetup(void)
{
unsigned long *p = (unsigned long *)MEM_CTL_BASE;
p[0] = 0x22111110; //BWSCON
p[1] = 0x00000700; //BANKCON0
p[2] = 0x00000700; //BANKCON1
p[3] = 0x00000700; //BANKCON2
p[4] = 0x00000700; //BANKCON3
p[5] = 0x00000700; //BANKCON4
p[6] = 0x00000700; //BANKCON5
p[7] = 0x00018005; //BANKCON6
p[8] = 0x00018005; //BANKCON7
p[9] = 0x008e07a3; //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
p[10] = 0x000000b2; //BANKSIZE
p[11] = 0x00000030; //MRSRB6
p[12] = 0x00000030; //MRSRB7
}
#endif
/*下面函数使用了memcpy函数,显然是编译器的操作,使用了memcpy把数组里的值从代码段拷贝到了arr局部变量里 是否可以禁用掉memcpy*/
void sdram_init2(void)
{
unsigned int arr[] = {
0x22000000, //BWSCON
0x00000700, //BANKCON0
0x00000700, //BANKCON1
0x00000700, //BANKCON2
0x00000700, //BANKCON3
0x00000700, //BANKCON4
0x00000700, //BANKCON5
0x18001, //BANKCON6
0x18001, //BANKCON7
0x8404f5, //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
0xb1, //BANKSIZE
0x20, //MRSRB6
0x20, //MRSRB7
};
volatile unsigned int * p = (volatile unsigned int *)0x48000000;
int i;
for (i = 0; i < 13; i++)
{
*p = arr[i];
p++;
}
}
文章说没有什么方法禁用memecpy但是可以修改这些变量
比如说将其修改为静态变量,这些数据就会放在数据段中,最终重定位时会把数据类拷贝到对应的arr地址里面去
void sdram_init2(void)
{
const static unsigned int arr[] = { //加上const 和static
0x22000000, //BWSCON
0x00000700, //BANKCON0
0x00000700, //BANKCON1
0x00000700, //BANKCON2
0x00000700, //BANKCON3
0x00000700, //BANKCON4
0x00000700, //BANKCON5
0x18001, //BANKCON6
0x18001, //BANKCON7
0x8404f5, //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
0xb1, //BANKSIZE
0x20, //MRSRB6
0x20, //MRSRB7
};
volatile unsigned int * p = (volatile unsigned int *)0x48000000;
int i;
for (i = 0; i < 13; i++)
{
*p = arr[i];
p++;
}
}
拷贝进行实验
得出bin文件有1.4k左右
查看反汇编代码
sdram.elf: file format elf32-littlearm
Disassembly of section .text:
/*前面这些ARM指令还是占用4个字节*/
30000000 <_start>:
30000000: e3a00453 mov r0, #1392508928 ; 0x53000000
30000004: e3a01000 mov r1, #0 ; 0x0
30000008: e5801000 str r1, [r0]
3000000c: e3a00313 mov r0, #1275068416 ; 0x4c000000
30000010: e3e01000 mvn r1, #0 ; 0x0
30000014: e5801000 str r1, [r0]
30000018: e59f005c ldr r0, [pc, #92] ; 3000007c <.text+0x7c>
3000001c: e3a01005 mov r1, #5 ; 0x5
30000020: e5801000 str r1, [r0]
30000024: ee110f10 mrc 15, 0, r0, cr1, cr0, {0}
30000028: e3800103 orr r0, r0, #-1073741824 ; 0xc0000000
3000002c: ee010f10 mcr 15, 0, r0, cr1, cr0, {0}
30000030: e59f0048 ldr r0, [pc, #72] ; 30000080 <.text+0x80>
30000034: e59f1048 ldr r1, [pc, #72] ; 30000084 <.text+0x84>
30000038: e5801000 str r1, [r0]
3000003c: e3a01000 mov r1, #0 ; 0x0
30000040: e5910000 ldr r0, [r1]
30000044: e5811000 str r1, [r1]
30000048: e5912000 ldr r2, [r1]
3000004c: e1510002 cmp r1, r2
30000050: e59fd030 ldr sp, [pc, #48] ; 30000088 <.text+0x88>
30000054: 03a0da01 moveq sp, #4096 ; 0x1000
30000058: 05810000 streq r0, [r1]
3000005c: e28f0004 add r0, pc, #4 ; 0x4
30000060: e2800001 add r0, r0, #1 ; 0x1
30000064: e12fff10 bx r0
30000068 <thumb_func>:
30000068: f94ef000 bl 30000308 <sdram_init>
3000006c: f9fef000 bl 3000046c <copy2sdram>
30000070: fa24f000 bl 300004bc <clean_bss>
/**下面的thumb指令占据2个字节**/
30000074: 4805 ldr r0, [pc, #20] (3000008c <.text+0x8c>)
30000076: 4687 mov pc, r0
30000078 <halt>:
30000078: e7fe b 30000078 <halt>
3000007a: 0000 lsl r0, r0, #0
3000007c: 0014 lsl r4, r2, #0
3000007e: 4c00 ldr r4, [pc, #0] (30000080 <.text+0x80>)
30000080: 0004 lsl r4, r0, #0
30000082: 4c00 ldr r4, [pc, #0] (30000084 <.text+0x84>)
30000084: c011 stmia r0!,{r0, r4}
30000086: 0005 lsl r5, r0, #0
30000088: 1000 asr r0, r0, #0
3000008a: 4000 and r0, r0
3000008c: 04fd lsl r5, r7, #19
3000008e: 3000 add r0, #0
如果你的flash很小的话可以考虑使用Thumb指令集
烧写进去看是否可以运行 测试结果没有任何问题 Thumb指令集后面没有任何作用,只是简单作为介绍
第004节_und异常模示程序示例
写一个程序故意让其发生未定义异常,让后处理这个异常 查看uboot中源码 uboot\u-boot-1.1.6\cpu\arm920t 打开start.S
/*code: 28 -- 72*/
#include <config.h>
#include <version.h>
/*
*************************************************************************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************************
*/
#define GSTATUS2 (0x560000B4)
#define GSTATUS3 (0x560000B8)
#define GSTATUS4 (0x560000BC)
#define REFRESH(0x48000024)
#define MISCCR (0x56000080)
#define LOCKTIME 0x4C000000 /* R/W, PLL lock time count register */
#define MPLLCON 0x4C000004 /* R/W, MPLL configuration register */
#define UPLLCON 0x4C000008 /* R/W, UPLL configuration register */
#define CLKCON 0x4C00000C /* R/W, Clock generator control reg. */
#define CLKSLOW 0x4C000010 /* R/W, Slow clock control register */
#define CLKDIVN 0x4C000014 /* R/W, Clock divider control */
/******下面这些就是异常向量表*****/
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
.balignl 16,0xdeadbeef
.text
.global _start
_start:
b reset /* vector 0 : reset */ //一上电复位,是从0地址开始执行,跳到reset处
b do_und /* vector 4 : und */ //如果发生未定义指令异常,就会跳到0x04地址未定义指令异常处,执行do_und程序
/*假设一上电从0地址开始执行,reset,做一系列初始化之后
*故意加入一条未定义指令
und_code:
.word 0xdeadc0de /* 未定义指令 */
当CPU发现无法执行此条指令时,就会发生未定义指令异常,就会执行do_und
bl print2,
*/
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
//需要从新设置sp栈,指向某一块没有使用的地址
/* sp_und未设置, 先设置它 */
ldr sp, =0x34000000
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* 发生异常时,当前被中断的地址会保存在lr寄存器中 先减后存*/
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr//把cpsr的值读入r0
ldr r1, =und_string//把下面的字符串地址赋值给r1
bl printException
/* 这些寄存器保存在栈中,把他读取出来就可以了*/
/* 恢复现场 */
/* 先读后加*/
/* 把r0 ~ r12的值从栈中都取出来,并且把原来保存的lr值,赋值到pc中去*/
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
/*
- 如何定义字符串,可以百度搜索 arm-linux-gcc 汇编 定义字符串
- 官方的说明文档
- http://web.mit.edu/gnu/doc/html/as_7.html
.string "str"
Copy the characters in str to the object file. You may specify more than one string to copy, separated by commas. Unless otherwise specified for a particular machine, the assembler marks the end of each string with a 0 byte. You can use any of the escape sequences described in section Strings. 我们使用.str会自动加上结束符
- /
und_string:
.string "undefined instruction exception"
reset:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xff123456 /* 未定义指令 */
bl print2
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
如何处理这个异常呢? 直接print打印一句话,新建一个 exception.c文件
#include "uart.h"
void printException(unsigned int cpsr, char *str) //cpsr打印相应的寄存器,str打印一个字符串
{
puts("Exception! cpsr = ");\\打印cpsr
printHex(cpsr);//输出cpsr的值
puts(" ");//输出空格
puts(str);//输出str值
puts("\n\r");//回车,换行
}
我们打开之前编译过的程序的反汇编文件 里面一定包含了保存恢复
30000084 <delay>:
30000084: e1a0c00d mov ip, sp
30000088: e92dd800 stmdb sp!, {fp, ip, lr, pc} //保存 d是减 b是存
3000008c: e24cb004 sub fp, ip, #4 ; 0x4
30000090: e24dd004 sub sp, sp, #4 ; 0x4
30000094: e50b0010 str r0, [fp, #-16]
30000098: e51b3010 ldr r3, [fp, #-16]
3000009c: e2433001 sub r3, r3, #1 ; 0x1
300000a0: e50b3010 str r3, [fp, #-16]
300000a4: e51b3010 ldr r3, [fp, #-16]
300000a8: e3730001 cmn r3, #1 ; 0x1
300000ac: 0a000000 beq 300000b4 <delay+0x30>
300000b0: eafffff8 b 30000098 <delay+0x14>
300000b4: e89da808 ldmia sp, {r3, fp, sp, pc}//恢复,先读后加
上传编译
修改makefile添加文件
all: start.o led.o uart.o init.o main.o exception.o
#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-ld -T sdram.lds $^ -o sdram.elf
#用$ ^来包含所有的依赖
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
%.o : %.c
arm-linux-gcc -c -o $@ $<
%.o : %.S
arm-linux-gcc -c -o $@ $<
*.dis
编译成功烧写 没有输出我们想要的字符串 很多同学想学会如何调试程序 这里我们演示
sdram:
bl print1 //添加print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xdeadc0de /* 未定义指令 */
bl print2 //添加print2,实现这两个函数,来打印
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
实现print1 print2这两个打印函数,在uart.c这个文件里
void print1(void)
{
puts("abc\n\r");
}
void print2(void)
{
puts("123\n\r");
}
上传代码烧写,发现print1 print2并未执行成功
发现在start.S并未初始化 uart0_init(),删除main.c中的uart0_init()初始化函数
ldr pc, =sdram
sdram:
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xff123456 /* 未定义指令 */
bl print2
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
加上uart0_init,再次编译烧写 程序正常运行,print1 print2全部打印,表明未定义指令并未运行,难道这个地址是一个已经定义的地址
发现竟然是SWI指令,CPU可以识别出来,他不是一条未定义指令 我们得找到一条CPU不能识别的指令,定义为0x03000000
ldr pc, =sdram
sdram:
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0x03000000 /* 未定义指令 */
bl print2
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
编译烧写执行
打印了未定义指令异常CPSR地址,打印了字符串,最后执行main函数 .word 0xdeadcode /* 也是一条未定义指令 只要指令地址对不上上表就是未定义指令*/
我们查看下cpsr是否处于未定义模式
bit[4:0]表示CPU模式 11011 果然处于und模式
我们看看这个程序做了什么事情
.text
.global _start
/*一上电复位,从0地址开始执行
跳到 reset:
做了一系列初始化
当执行到0xdeadc0de这条指令时候,CPU根本就不知道这条指令什么意思
und_code:
.word 0xdeadc0de /* 未定义指令 */
bl print2
让后就发生未定义指令异常,他会把下一条指令的地址保存到异常模式的LR寄存器
/* 执行到这里之前已经发生了很多事情
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*
* 设置栈 sp是指und的地址
* sp_und未设置, 先设置它
* /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
* lr是异常处理完后的返回地址, 也要保存 */
* 保存现场 */
* 处理und异常 */
* 恢复sp
* cpu就会切换到之前的模式
*/
*/
.text
.global _start
_start:
b reset /* vector 0 : reset */
b do_und /* vector 4 : und */
und_addr:
.word do_und
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/* sp_und未设置, 先设置它 */
ldr sp, =0x34000000
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =und_string
bl printException
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
und_string:
.string "undefined instruction exception"
程序改进
源程序
.text
.global _start
_start:
b reset /* vector 0 : reset */
/*使用b命令跳转 相对跳转*/
b do_und /* vector 4 : und */
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/* sp_und未设置, 先设置它 */
ldr sp, =0x34000000
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =und_string
/*这里又使用bl指令跳转,如果是nand启动,这个函数在4k之外,这个函数必定出错 为了保险,跳转到sdram中执行程序*/
bl printException
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
und_string:
.string "undefined instruction exception"
reset:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xdeadc0de /* 未定义指令 */
bl print2
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
改进后代码
.text
.global _start
_start:
b reset /* vector 0 : reset */
/*跳转到sdram执行这个函数,那么这个函数一定在sdram中
我们需要指定让他去前面这块内存去读这个值,担心如果这个文件很大,超过4Knand就没法去读这个文件*/
ldr pc, und_addr /* vector 4 : und */
/*增加如下 查看反汇编,在08的地址读让后跳到3c*/
und_addr:
.word do_und
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/* sp_und未设置, 先设置它 */
ldr sp, =0x34000000
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =und_string
bl printException
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
und_string:
.string "undefined instruction exception"
/**如果你的程序长度稍有变化,就不能保证运行
加上 .align 4才能保证后面的程序以4字节对齐,保证程序运行
**/
.align 4
reset:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
/*把链接地址赋值给pc 直接就跳转到sdram中*/
ldr pc, =sdram
sdram:
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xdeadc0de /* 未定义指令 */
bl print2
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
第005节_swi异常模示程序示例
这节我们再来演示swi的处理流程 swi软件中断:software interrupt 在前面的视频中我们讲过ARMCPU有7中模式,除了用户模式以外,其他6种都是特权模式,这些特权模式可以直接修改CPSR进入其他模式
usr用户模式不能修改CPSR进入其他模式 Linux应用程序一般运行于用户模式 APP运行于usermode,(受限模式,不可访问硬件)
APP想访问硬件,必须切换模式,怎么切换 发生异常3种模式 中断是一种异常 und也是 swi + 某个值(使用软中断切换模式)
现在start.S把要做的事情列出来
/*********1************************/ /* 复位之后, cpu处于svc模式 * 现在, 切换到usr模式 * 设置栈 * 跳转执行 */
/********2 故意引入一条swi指令*********/
/******3 需在_start这里放一条swi指令**********/
查看异常向量表swi异常的向量地址是0x8
我们先切换到usr模式下
usr模式下的 M0 ~ M4是10000
/********5 先进入usr模式*/ mrs r0, cpsr /* 读出cpsr 读到r0 */ /使用bic命令 bitclean 把低4位清零/ bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */ msr cpsr, r0
/************6 设置栈*/ /* 设置 sp_usr */ ldr sp, =0x33f00000
编译运行
发现可以处理und指令
添加 swi异常,仿照未定义指令做
.text
.global _start
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
/*****************1
添加swi指令
*/
ldr pc, swi_addr /* vector 8 : swi */
und_addr:
.word do_und
/***************2
仿照und未定义添加指令
*/
swi_addr:
.word do_swi
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/* sp_und未设置, 先设置它 */
ldr sp, =0x34000000
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =und_string
bl printException
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
und_string:
.string "undefined instruction exception"
/*************3
复制do_und
修改为swi
*/
do_swi:
/* 执行到这里之前:
* 3.1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 3.2. SPSR_svc保存有被中断模式的CPSR
* 3.3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 3.4. 跳到0x08的地方执行程序
*/
/* 3.5 sp_svc未设置, 先设置它 */
ldr sp, =0x33e00000
/* 3.6 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* 3.7 lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 3.8 保存现场 */
/* 3.9 处理swi异常 只是打印 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
/*3.10 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
/*****
swi处理函数
*/
swi_string:
.string "swi exception"
上传代码实验 烧写 发现没有执行 我们先把下面这些代码注释掉
/*************3
复制do_und
修改为swi
*/
/* 执行到这里之前:
* 3.1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 3.2. SPSR_svc保存有被中断模式的CPSR
* 3.3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 3.4. 跳到0x08的地方执行程序
*/
/* 3.5 sp_svc未设置, 先设置它 */
ldr sp, =0x33e00000
/* 3.6 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* 3.7 lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 3.8 保存现场 */
/* 3.9 处理swi异常 只是打印 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
/*3.10 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
/*****
swi处理函数
*/
swi_string:
.string "swi exception"
上传编译 烧写执行 可以正常运行
循环打印 swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */ 执行后继续执行 ldr pc, swi_addr /* vector 8 : swi */
表明问题出现在 do_swi:函数中 先把下面这句话注释掉 .string "swi exception"
编译烧写运行 程序可以正常运行
显然程序问题出现在 .string "swi exception" 这句话,为什么加上这句话程序就无法执行,查看一下反汇编
30000064 <swi_string>: //这里地址是64
30000064: 20697773 rsbcs r7, r9, r3, ror r7
30000068: 65637865 strvsb r7, [r3, #-2149]!
3000007c: 6f697470 swivs 0x00697470
30000070: 0000006e andeq r0, r0, lr, rrx
30000082 <reset>: //我们使用的是ARM指令集,应该是4字节对齐,发现这里并不是,问题就在这里
30000082: e3a00453 mov r0, #1392508928 ; 0x53000000
30000086: e3a01000 mov r1, #0 ; 0x0
3000008a: e5801000 str r1, [r0]
3000008e: e3a00313 mov r0, #1275068416 ; 0x4c000000
30000092: e3e01000 mvn r1, #0 ; 0x0
因为这个字符串长度有问题
前面und_string 那里的字符串长度刚刚好
我们不能把问题放在运气上面 添加
/******* 以4字节对齐
- /
.align 4
do_swi:
/* 执行到这里之前:
* 3.1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 3.2. SPSR_svc保存有被中断模式的CPSR
* 3.3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 3.4. 跳到0x08的地方执行程序
*/
/* 3.5 sp_svc未设置, 先设置它 */
ldr sp, =0x33e00000
/* 3.6 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* 3.7 lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 3.8 保存现场 */
/* 3.9 处理swi异常 只是打印 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
/*3.10 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
/*****
swi处理函数
*/
swi_string:
.string "swi exception"
.align 4
/**************
表明下面的标号要放在4字节对齐的地方
*/
上传代码编译运行查看反汇编
30000068 <swi_string>:
30000068: 20697773 rsbcs r7, r9, r3, ror r7
3000006c: 65637865 strvsb r7, [r3, #-2149]!
30000070: 6f697470 swivs 0x00697470
30000074: 0000006e andeq r0, r0, lr, rrx
...
30000080 <reset>: //现在reset放在4自己对齐的地方
30000080: e3a00453 mov r0, #1392508928 ; 0x53000000
30000084: e3a01000 mov r1, #0 ; 0x0
30000088: e5801000 str r1, [r0]
3000008c: e3a00313 mov r0, #1275068416 ; 0x4c000000
30000090: e3e01000 mvn r1, #0 ; 0x0
30000094: e5801000 str r1, [r0]
30000098: e59f0084 ldr r0, [pc, #132] ; 30000124 <.text+0x124>
3000009c: e3a01005 mov r1, #5 ; 0x5
300000a0: e5801000 str r1, [r0]
300000a4: ee110f10 mrc 15, 0, r0, cr1, cr0, {0}
300000a8: e3800103 orr r0, r0, #-1073741824 ; 0xc0000000
300000ac: ee010f10 mcr 15, 0, r0, cr1, cr0, {0}
300000b0: e59f0070 ldr r0, [pc, #112] ; 30000128 <.text+0x128>
300000b4: e59f1070 ldr r1, [pc, #112] ; 3000012c <.text+0x12c>
300000b8: e5801000 str r1, [r0]
300000bc: e3a01000 mov r1, #0 ; 0x0
300000c0: e5910000 ldr r0, [r1]
下载烧写 程序执行完全没有问题 程序备份修改代码
swi可以根据应用程序传入的val来判断为什么调用swi指令,我们的异常处理函数能不能把这个val值读出来
do_swi:
/* 执行到这里之前:
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_svc保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 4. 跳到0x08的地方执行程序
*/
/* sp_svc未设置, 先设置它 */
ldr sp, =0x33e00000
/* 保存现场 */
/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/********2
我们得把lr拿出来用
我们要把lr拿出来保存
因为bl printException会破坏lr
mov rX, lr
我把lr保存在那个寄存器?
这个函数 bl printException 可能会修改某些寄存器,但是又会恢复这些寄存器,我得知道他会保护那些寄存器
我们之前讲过ATPCS规则
对于 r4 ~ r11在C函数里他都会保存这几个寄存器,如果用到的话就把他保存起来,执行完C函数再把它释放掉
我们把lr 保存在r4寄存器里,r4寄存器不会被C语言破坏
*/
mov r4, lr
/* 处理swi异常 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
/********1
跳转到printSWIVal
如何才能知道swi的值呢?
我们得读出swi 0x123指令,这条指令保存在内存中,我们得找到他的内存地址
执行完0x123指令以后,会发生一次异常,那个异常模式里的lr寄存器会保存下一条指令的地址
我们把lr寄存器的地址减去4就是swi 0x123这条指令的地址
*/
/**********3
我再把r4的寄存器赋给r0让后打印
我们得写出打印函数
mov r0, r4
指令地址减4才可以
swi 0x123
下一条指令bl main 减4就是指令本身
*/
sub r0, r4, #4
bl printSWIVal
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
swi_string:
.string "swi exception"
<syntaxhighlight lang="c" >
在uart.c添加printSWIVal打印函数
void printSWIVal(unsigned int *pSWI)
{
puts("SWI val = ");
printHEx(*pSWI & ~0xff000000); //高8位忽略掉
puts("\n\r");
}
编译实验运行没有问题
我们再来看看这个程序是怎么跳转的
/**************1 发生swi异常,他是在sdram中,CPU就会跳到0x8的地方 swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
- /
/************2
_start: b reset /* vector 0 : reset */ ldr pc, und_addr /* vector 4 : und */ 执行这条读内存指令 ldr pc, swi_addr /* vector 8 : swi */
读到swi_addr地址跳转到sdram执行代码 do_swi那段代码 swi_addr: .word do_swi
- /
/************3 这段代码被设置栈保存现场 调用处理函数恢复现场,让后就会跳到sdram执行 swi 0x123的下一条指令
do_swi:
/* 执行到这里之前:
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_svc保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 4. 跳到0x08的地方执行程序
*/
/* sp_svc未设置, 先设置它 */
ldr sp, =0x33e00000
/* 保存现场 */
/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
mov r4, lr
/* 处理swi异常 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
sub r0, r4, #4
bl printSWIVal
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
swi_string:
.string "swi exception"
*/
这节视频我们讲解了swi的处理流程
第006节_按键中断程序示例_概述与初始
在前面的视频里我们举了一个例子,母亲看书被声音打断,远处的声音来源有多种多样,声音传入耳朵,再由耳朵传入大脑,整个过程涉及声音来源耳朵大脑,为了确保这个母亲看书的过程能够被声音打断, 我们必须保证声音来源可以发出声音,耳朵没有聋,脑袋没有傻
- 类比嵌入式系统我们可以设置中断源,让他发出中断信号,还需要设置中断控制器,让他把这些信号发送给CPU,还需要设置CPU让他能够处理中断
中断的处理流程
1中断初始化: 1 我们需要设置中断源,让它能够发出中断喜好 2 设置中断控制器,让它能发出中断给CPU 3 设置CPU,CPSR有I位,是总开关 我们需要这样设置,中断源才能发送给CPU
2处理完要清中断
3处理时,要分辨中断源,对于不同的中断源要执行不同的处理函数
下面开始写代码
打开start.S 先做初始化工作,先做第 3 设置CPU,CPSR有I位,是总开关
我们需要把CPSR寄存器 bit7给清零,这是中断的总开关,如果bit7设置为1 CPU无法响应任何中断
/* 清除BSS段 */
bl clean_bss
/* 复位之后, cpu处于svc模式
* 现在, 切换到usr模式
*/
mrs r0, cpsr /* 读出cpsr */
bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */
/***************1
把bit7这一位清零
*/
bic r0, r0, #(1<<7) /* 清除I位, 使能中断 */
msr cpsr, r0
/* 设置 sp_usr */
ldr sp, =0x33f00000
ldr pc, =sdram
sdram:
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xdeadc0de /* 未定义指令 */
bl print2
swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
/**************2
调用两个中断
*/
bl interrupt_init /*初始化中断控制器*/
bl eint_init /*初始化按键,设为中断源*/
/*******需要初始化上面这两个函数*/
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
添加一个 interrupt.c文件
这个程序稍微有些复杂,我们先画个流程图
我们想达到按下按键灯亮松开按键灯灭 把下面四个按键全部配置为外部中断按键
打开芯片手册找到第九章 IO ports
直接搜索
EINT0号中断和EINT2号中断
找配置寄存器 GPFCON
为了简单操作
/* 初始化按键, 设为中断源 */ void key_eint_init(void) { /*1 配置GPIO为中断引脚 */ //先把eint0和eint2这两个引脚清零 GPFCON &= ~((3<<0) | (3<<4)); GPFCON |= ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */
通过电路图得知 S4 S5按键为EINT11号中断引脚和EINT19号中断引脚
GPGCON &= ~((3<<6) | (3<<11)); GPGCON |= ((2<<6) | (2<<11)); /* S4,S5被配置为中断引脚 */
/*2 设置中断触发方式: (按下松开,从低电源变为高电源,或者从)双边沿触发 */
//设置EINT0 EINT2为双边沿触发
EXTINT0 |= (7<<0) | (7<<8); /* S2,S3 */
EXTINT1 |= (7<<12); /* S4 */
EXTINT2 |= (7<<12); /* S5 */
/*外部中断屏蔽寄存器EINTMASK,设置为1的话就禁止向外部发出中断信号,只有EINTMASK相应的位设置为0外部中断才能给中断控制器发信号
我们需要设置这个寄存器
把EINT11设置为0 把EINT19设置为0对于EINT0 和EINT2显示为保留,默认时使能的,可以直接发送给中断控制器,无需设置
/* 设置EINTMASK使能eint11,19 */ EINTMASK &= ~((1<<11) | (1<<19)); }
/* 读EINTPEND分辨率哪个EINT产生(eint4~23)(并且要清除他)
* 清除中断时, 写EINTPEND的相应位
我们接下来需要阅读第14章 Interrupt Controller章节设置中断控制器我们只需要按照下面这张流程图设置就可以了
我们需要设置
MASK 屏蔽寄存器
INTPND 等待处理,我们可以读这个寄存器,确定是那个中断产生了
SRCPND不同的中断类型不可以直接到达这里执行
我们来看一下外部中断属于哪一种
打开芯片手册,从上往下读
由上图可得 EINT4_7 EINT8_23合用一条中断线ARB1
也就是可以直接到达SRCPND不需要设置SUBSRCPND和SUBMASK这两个寄存器
我们使用的外部中断源只需要设置SRCPND MASK INTPND这三个就可以
/* SRCPND 用来显示哪个中断产生了, 需要清除对应位,我们只需要关心 * bit0对应eint0 * bit2对应eint2 * bit5对应eint8_23(表明bit5等于1的时候 eint8_23中的某一个已经产生,我们需要继续分辨 * 读EINTPEND分辨率哪个EINT产生) */
/* INTMSK 用来屏蔽中断, 1对应masked屏蔽中断,我们需要设置相应位设置为0 * bit0-eint0 * bit2-eint2 * bit5-eint8_23 */
同时可能有多个中断产生,这么多个中断经过优先级以后,只会有一个通知CPU,是哪一个中断优先级最高,可以读INTPAD就能知道当前处理的唯 一 一个中断是那一个
/* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位 * bit0-eint0 * bit2-eint2 * bit5-eint8_23 */
INTOFFSET是用来显示INTPND寄存器中哪一位正在等待处理 ![](https://i.imgur.com/XnkkjrY.png) INTPAD中bit0等于1的话INTOFFSET就等于0 INTPAD中bit1等于1的话INTOFFSET值就等于1 /* INTOFFSET : 用来显示INTPND中哪一位被设置为1
*/
某一位等于1时INT_UART0它的来源可能有多个,这是串口0的中断,串口0的中断产生时有可能是接收到了数据(INT_RXD0),有可能是发送了数据(INT_TXD0),也有可能是产生了错误,那么到底是哪一个呢? 需要去读取SUBSRCPND下一级的源寄存器
我们只需要设置INTMSK这个寄存器
SRCPND和INTPND只有发生中断才需要设置
/* 初始化中断控制器 */ void interrupt_init(void) { //1是屏蔽我们需要清零,外部中断0 外部中断2 外部中8_23里面还有外部中断11到19 INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
}
修改start.S删除 bl interrupt_init /* 初始化中断控制器 */ bl key_eint_init /* 初始化按键, 设为中断源 */
能使用c语言就使用C语言
在main.c文件中添加调用C函数
int main(void)
{
/************1/
interrupt_init(); /* 初始化中断控制器 */
key_eint_init(); /* 初始化按键, 设为中断源 */
*******/
puts("\n\rg_A = ");
printHex(g_A);
puts("\n\r");
第007节_按键中断程序示例_完善
首先main.c中 我们初始化中断控制器 初始化中断源
假设按键按键就会产生中断,CPU就会跳到start.S 执行
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
ldr pc, swi_addr /* vector 8 : swi */
IRQ模式的话跳到0x00000018地方
b halt /* vector 0x0c : prefetch aboot */ b halt /* vector 0x10 : data abort */ b halt /* vector 0x14 : reserved */ ldr pc, irq_addr /* vector 0x18 : irq */ b halt /* vector 0x1c : fiq */
/*************3/ do_irq: /* 执行到这里之前: * 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址 * 2. SPSR_irq保存有被中断模式的CPSR * 3. CPSR中的M4-M0被设置为10010, 进入到irq模式 * 4. 跳到0x18的地方执行程序 */
/* sp_irq未设置, 先设置它 */ /************ 分配不冲突的没有使用的内存就可以了 4*/ ldr sp, =0x33d00000
/************5
- /
/* 保存现场 */ /***********7 发生中断时irq返回值是R14 -4 为什么要减去4,硬件结构让你怎么做就怎么做
- /
/* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 */ /* lr-4是异常处理完后的返回地址, 也要保存 */ sub lr, lr, #4 stmdb sp!, {r0-r12, lr}
/* 处理irq异常 */ /***********6 在这C函数里分辨中断源,处理中断
- /
bl handle_irq_c
/***********8*/ /* 恢复现场 */ ldmia sp!, {r0-r12, pc}^ /* ^会把spsr_irq的值恢复到cpsr里 */
接下来我们在interrupt.c中写出 handle_irq_c处理函数
这个是处理中断的C函数
void handle_irq_c(void)
{
/*1 分辨中断源 */
/*读INTOFFSET在芯片手册里找到这个寄存器,它里面的值表示INTPND中哪一位被设置成1*/
![](https://i.imgur.com/jwKlc5N.png)
int bit = INTOFFSET;
/*2 调用对应的处理函数 */
if (bit == 0 || bit == 2 || bit == 5) /* 对应eint0,2,eint8_23 */
{
/*我们会调用一个按键处理函数*/
key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
}
/*3 清中断 : 从源头开始清
*先清除掉中断源里面的某些寄存器
*再清 SRCPND
*再清 INTPND
*/
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
/* 读EINTPEND分辨率哪个EINT产生(eint4~23)
* 清除中断时, 写EINTPEND的相应位 */
void key_eint_irq(int irq)
{
/**4清的时候我先把这个值读出来,清的时候我再把他写进去/
unsigned int val = EINTPEND;
unsigned int val1 = GPFDAT;
unsigned int val2 = GPGDAT;
if (irq == 0) /*1 外部中断eint0对应s2按键 */
{
![](https://i.imgur.com/5t92Jpe.png) /*1.1我们使用s2来控制那盏灯? *之前我们写过按键控制led灯的程序,它使用的是s2控制gpf6 *也就是s2控制led4 D12 ![](https://i.imgur.com/isOUAbD.png)
*/
/****1.2如何知道是按下还是松开,我们需要读值*/
if (val1 & (1<<0)) /* s2 --> gpf6 */
{
/*1.3 松开 */
GPFDAT |= (1<<6);
}
else
{
/*1.4 按下 */
GPFDAT &= ~(1<<6);
}
}
else if (irq == 2) /*2 eint2对应s3 控制 D11 LED2 */
{
if (val1 & (1<<2)) /* s3 --> gpf5 */
{
/* 松开 */
GPFDAT |= (1<<5);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<5);
}
}
else if (irq == 5) /*3 eint8_23, eint11对应s4 控制 D10 LED1, eint19对应s5 控制所有LED */
{
/*3.1到底是发生哪一种中断,我们需要读取 EINTPND来判断是那个中断产生*/ ![](https://i.imgur.com/4dF7cca.png) /*如果bit19等于1的话表明外部中断EINT19产生了,如果bit11等于1表用外部中断11产生,这里我们需要判断*/
if (val & (1<<11)) /* 表明外部中断eint11产生 */
{
if (val2 & (1<<3)) /* s4 --> gpf4 */
{
/* 松开 */
GPFDAT |= (1<<4);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<4);
}
}
else if (val & (1<<19)) /* 表用外部中断eint19 */
{
if (val2 & (1<<11))
{
/* 松开 */
/* 熄灭所有LED 输出高电平 */
GPFDAT |= ((1<<4) | (1<<5) | (1<<6));
}
else
{
/* 按下: 点亮所有LED */
GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));
}
}
}
/**5 再把值写进去就达到了清除的效果*/
EINTPEND = val;
}
上传代码测试
我们需要包含头文件
#include "S3c2440_soc.h"
编译通过,开发板上电测试发现按键s5无法控制 查看 interrupt.c文件中的按键初始化
/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
/* 配置GPIO为中断引脚 */
GPFCON &= ~((3<<0) | (3<<4));
GPFCON |= ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */
发现外部中断19的bit位配置不正确应该是22
![](https://i.imgur.com/EWZaHFx.png)
GPGCON &= ~((3<<6) | (3<<22));
GPGCON |= ((2<<6) | (2<<22)); /* S4,S5被配置为中断引脚 */
上传代码从新编译执行 重新烧写看是否可以使用 回顾中断处理流程
我们start.s 一上电从 _start: 运行 做一些初始化工作
reset:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
/* 复位之后, cpu处于svc模式
* 现在, 切换到usr模式
*/
mrs r0, cpsr /* 读出cpsr */
bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */
bic r0, r0, #(1<<7) /* 清除I位, 使能中断 */
msr cpsr, r0
/* 设置 sp_usr */
ldr sp, =0x33f00000
ldr pc, =sdram
sdram:
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xdeadc0de /* 未定义指令 */
bl print2
swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
让后设置CPSR开中断
让后调到mian函数,做一些中断初始化
int main(void)
{
led_init();
interrupt_init(); /* 初始化中断控制器 */
key_eint_init(); /* 初始化按键, 设为中断源 */
puts("\n\rg_A = ");
printHex(g_A);
puts("\n\r");
/*****让后在main函数里一直循环输出串口*/
while (1)
{
putchar(g_Char);
g_Char++;
putchar(g_Char3);
g_Char3++;
delay(1000000);
}
这个时候按下按键就会产生中断,让后进入start.s
跳到0x18 irq模式
ldr pc, irq_addr /* vector 0x18 : irq */
它是一条读内存的执行,从这里读地址赋给pc
irq_addr:
.word do_irq
就跳到sdram执行do_irq函数
do_irq:
/* 执行到这里之前:
* 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_irq保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
* 4. 跳到0x18的地方执行程序
*/
/* sp_irq未设置, 先设置它 */
ldr sp, =0x33d00000
/* 保存现场 */
/* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr-4是异常处理完后的返回地址, 也要保存 */
sub lr, lr, #4
stmdb sp!, {r0-r12, lr}
/* 处理irq异常 */
bl handle_irq_c
/* 恢复现场 */
它怎么处理
/* 读EINTPEND分辨率哪个EINT产生(eint4~23)
* 清除中断时, 写EINTPEND的相应位
*/
void key_eint_irq(int irq)
{
unsigned int val = EINTPEND;
unsigned int val1 = GPFDAT;
unsigned int val2 = GPGDAT;
if (irq == 0) /* eint0 : s2 控制 D12 */
{
if (val1 & (1<<0)) /* s2 --> gpf6 */
{
/* 松开 */
GPFDAT |= (1<<6);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<6);
}
}
else if (irq == 2) /* eint2 : s3 控制 D11 */
{
if (val1 & (1<<2)) /* s3 --> gpf5 */
{
/* 松开 */
GPFDAT |= (1<<5);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<5);
}
}
else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */
{
if (val & (1<<11)) /* eint11 */
{
if (val2 & (1<<3)) /* s4 --> gpf4 */
{
/* 松开 */
GPFDAT |= (1<<4);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<4);
}
}
else if (val & (1<<19)) /* eint19 */
{
if (val2 & (1<<11))
{
/* 松开 */
/* 熄灭所有LED */
GPFDAT |= ((1<<4) | (1<<5) | (1<<6));
}
else
{
/* 按下: 点亮所有LED */
GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));
}
}
}
EINTPEND = val; } 处理完之后清中断,从源头开始清 这完全是按照中断流程操作的 ![](https://i.imgur.com/e6NzQmH.png)
第008节_定时器中断程序示例
这节课我们来写一个定时器的中断服务程序 使用定时器来实现点灯计数 查考资料就是第10章PWM TIMER 可以参考书籍《嵌入式Linux应用程序开发完全手册》第10章
我们先把这个结构图展示出来 ![](https://i.imgur.com/CnRZtkp.png) 这个图的结构很好
这里面肯定有一个clk(时钟), 1 每来一个clk(时钟)这个TCNTn减去1 2 当TCNTn == TCMPn时,可以产生中断,也可以让对应的SPWM引脚反转,(比如说原来是高电平,发生之后电平转换成低电平) 3 TCNTn继续减1,当TCNTn == 0时,可以产生中断,pwm引脚再次反转
TCMPn 和 TCNTn的初始值来自 TCMPBn,TCNTBn
4 TCNTn == 0时,可自动加载初始
怎么使用定时器?
1 设置时钟 2 设置初值 3 加载初始,启动Timer 4 设置为自动加载 5 中断相关
由于2440没有引出pwm引脚,所以pwm功能无法使用,也就无法做pwm相关实验,所谓pwm是指可调制脉冲 ![](https://i.imgur.com/JE1L704.png) T1高脉冲和T2低脉冲它的时间T1, T2可调整,可以输出不同频率不同占控比的波形,在控制电机时特别有用
我们这个程序只做一个实验,当TCNTn这个计数器到0的时候,就产生中断,在这个中断服务程序里我们点灯
写代码 打开我们的main函数
int main(void)
{
led_init();
interrupt_init(); /* 初始化中断控制器 */
//我们初始化了中断源,同样的,我们初始化timer
key_eint_init(); /* 初始化按键, 设为中断源 */
//初始化定时器
timer_init();
我们需要实现定时器初始化函数
新建一个 timer.c ,我们肯定需要操作一堆寄存器,添加头文件
#include "s3c2440_soc.h"
void timer_init(void) {
/*1 设置TIMER0的时钟 */ /*2 设置TIMER0的初值 */ /*3 加载初值, 启动timer0 */ /*4 设置为自动加载并启动(值到0以后会自动加载) */ /*5 设置中断,显然我们需要提供一个中断处理函数void timer_irq(void) *在这里面我们需要点灯*/
打开芯片手册,我们想设置timer0的话 1 首先设置8-Bit Prescaler 2 设置5.1 MUX(选择一个时钟分频) 3 设置TCMPB0和TCNTB0 4 设置TCONn寄存器 ![](https://i.imgur.com/bCwRifz.png)
看手册上写如何初始化timer
![](https://i.imgur.com/E5nzTTF.png)
1 把初始值写到TCNTBn 和TCMPBn寄存器 2 设置手动更新位 3 设置启动位
往下看到时钟配置寄存器 ![](https://i.imgur.com/T6PKMdj.png) 有个计算公式
Timer clk = PCLK / {(预分频数)prescaler value+1} / {divider value(5.1MUX值)}
PCLK是50M = 50000000/(99+1)/16 = 31250 也就是说我们得TCON是31250的话,从这个值一直减到0
Prescaler0等于99
TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */
TCFG1 MUX多路复用器的意思,他有多路输入,我们可以通过MUX选择其中一路作为输出
![](https://i.imgur.com/pc5YS0e.png) 根据上面mux的值,我们要把MUX0 设置成0011 只需要设置这4位即可,先清零 再或上 0011 就是3
TCFG1 &= ~0xf; TCFG1 |= 3; /* MUX0 : 1/16 */
再来看看初始值控制寄存器
![](https://i.imgur.com/tqMCbTt.png)
一秒钟点灯太慢了 ,就让0.5秒
TCNTB0 = 15625; /* 0.5s中断一次 */
![](https://i.imgur.com/A2vz5Xn.png) 这个寄存器是用来观察里面的计数值的,不需要设置
现在可以设置TCON来设置这个寄存器了 ![](https://i.imgur.com/EA8N6i8.png) 现在需要设置Timer0
![](https://i.imgur.com/bZzEJdR.png) 开始需要手工更新 TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 */ 把这两个值放到TCNTB0 和 TCMPB0中 ![](https://i.imgur.com/CnRZtkp.png) 注意:这一位必须清楚才能写下一位
设置为自动加载并启动,先清掉手动更新位,再或上bit0 bit3
TCON &= (1<<1); TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload */
设置中断,显然我们需要提供一个中断处理函数void timer_irq(void) 在Timer里没有看到中断相关的控制器,我们需要回到中断章节去看看中断控制器,看看有没有定时器相关的中断 我们没有看到更加细致的Timer0寄存器
![](https://i.imgur.com/uvV4q8Y.png)
当TCNTn=TCMPn时,他不会产生中断,只有当TCNTn等于0的时候才可以产生中断,我们之前以为这个定时器可以产生两种中断,那么肯定有寄存器中断或者禁止两种寄存器其中之一,那现在只有一种中断的话,就相对简单些 设置中断的话,我们只需要设置中断控制器
设置interrupu.c中断控制器 /* 初始化中断控制器 */ void interrupt_init(void) { INTMSK &= ~((1<<0) | (1<<2) | (1<<5)); //把定时器相应的位清零就可以了,哪一位呢? INTPND的哪一位? INT_TIMER0第10位即可 ![](https://i.imgur.com/jJQsjZh.png) INTMSK &= ~(1<<10); /* enable timer0 int */ }
当定时器减到0的时候就会产生中断,就会进到start.s这里一路执行do_irq
do_irq:
/* 执行到这里之前:
* 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_irq保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
* 4. 跳到0x18的地方执行程序
*/
/* sp_irq未设置, 先设置它 */
ldr sp, =0x33d00000
/* 保存现场 */
/* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr-4是异常处理完后的返回地址, 也要保存 */
sub lr, lr, #4
stmdb sp!, {r0-r12, lr}
/* 处理irq异常 */
bl handle_irq_c
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr_irq的值恢复到cpsr里 */
让后进入irq处理函数中处理,处理这个irq
void handle_irq_c(void)
{
/* 分辨中断源 */
int bit = INTOFFSET;
/* 调用对应的处理函数 */
/***************/
if(bit ==0 || bit == 2 || bit == 5)/*eint0,2,rint8_23*/
{
key_eint_irq(bit);/*处理中断,清中断源EINTPEND*/
}else if(bit == 10)//如果等于10的话说明发生的是定时器中断,这时候就调用我们得timer_irq
{
timer_irq();
}
/* 清中断 : 从源头开始清 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
回到timer.c文件中,在这个定时器处理函数中我们需要点灯
void timer_irq(void)
{
/* 点灯计数 循环点灯*/
static int cnt = 0;
int tmp;
cnt++;
tmp = ~cnt;
tmp &= 7;
GPFDAT &= ~(7<<4);
GPFDAT |= (tmp<<4);
}
代码写完我们实验一下,上传代码,在MAKEFILE中添加timer.o,进行编译 编译后进行烧写 现象灯没有闪烁 他不是有一个观察寄存器么? 我们不断的打印这个值,看是否有变化 ![](https://i.imgur.com/bhId6v6.png)
在main函数中不断打印
putchar(g_Char3); g_Char3++; delay(1000000); /************/ printHex(TCNTO0); } 编译实验 打印结果全都是0,发现我们的定时器根本就没有启用,在timer.c文件void timer_init(void)函数里
设置为自动加载并启动,先清掉手动更新位,再或上bit0 bit3
TCON &= ~(1<<1);//我们没有设置取反 TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload */ 再次实验 发现灯已经开始闪,就可以把调试信息去除了 对程序进行改进 进入main函数中执行 timer_init();
还需要修改interrupt.c 初始化函数 void interrupt_init(void) 还需要调用中断处理函数 void handle_irq_c(void) 每次添加一个中断我都需要修改handle_irq这个函数,这样太麻烦,我能不能保证这个interrupt文件不变,只需要在timer.c中引用即可,这里我们使用指针数组
在interrupt.c中定义函数指针数组
typedef void(*irq_func)(int); 定义一个数组,我们来卡看下这里有多少项,一共32位,我们想把每一个中断的处理函数都放在这个数组里面来,当发生中断时,我们可以得到这个中断号,让后我从数组里面调用对应的中断号就可以了 irq_func irq_array[32];
那么我们得提供一个注册函数
void register_irq (int irq, irq_func fp)
{
irq_array[irq] = fp;
INTMASK &= ~(1 << irq)
}
以后我直接调用对应的处理函数
void handle_irq_c(void)
{
/* 分辨中断源 */
int bit = INTOFFSET;
/***********/
/* 调用对应的处理函数 */
irq_array[bit](bit);
/* 清中断 : 从源头开始清 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
按键中断初始化函数需要注册
/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
/* 配置GPIO为中断引脚 */
GPFCON &= ~((3<<0) | (3<<4));
GPFCON |= ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */
GPGCON &= ~((3<<6) | (3<<22));
GPGCON |= ((2<<6) | (2<<22)); /* S4,S5被配置为中断引脚 */
/* 设置中断触发方式: 双边沿触发 */
EXTINT0 |= (7<<0) | (7<<8); /* S2,S3 */
EXTINT1 |= (7<<12); /* S4 */
EXTINT2 |= (7<<12); /* S5 */
/* 设置EINTMASK使能eint11,19 */
EINTMASK &= ~((1<<11) | (1<<19));
register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);
}
在timer.c中也需要设置中断
void timer_init(void)
{
/* 设置TIMER0的时钟 */
/* Timer clk = PCLK / {prescaler value+1} / {divider value}
= 50000000/(99+1)/16
= 31250
*/
TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */
TCFG1 &= ~0xf;
TCFG1 |= 3; /* MUX0 : 1/16 */
/* 设置TIMER0的初值 */
TCNTB0 = 15625; /* 0.5s中断一次 */
/* 加载初值, 启动timer0 */
TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 */
/* 设置为自动加载并启动 */
TCON &= ~(1<<1);
TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload */
/* 设置中断 */
register_irq(10, timer_irq);
}
把interrupt.c中按键的初始化放在最后面
我们来看看我们做了什么事情, 1 我们定义了一个指针数组 typedef void(*irq_func)(int); 这个指针数组里面放有各个指针的处理函数 irq_func irq_array[32]; 当我们去初始化按键中断时,我们给这按键注册中断函数
register_irq(0, key_eint_irq); register_irq(2, key_eint_irq); register_irq(5, key_eint_irq);
这个注册函数会做什么事情,他会把这个数组放在注册函数里面,同时使能中断
void register_irq(int irq, irq_func fp)
{
irq_array[irq] = fp;
INTMSK &= ~(1<<irq);
}
我们的timer.c中
timer_init();
也会注册这个函数
/* 设置中断 */
register_irq(10, timer_irq);
把这个中断irq放在第10项里同时使能中断,以后我们只需要添加中断号,和处理函数即可,再也不需要修改函数 烧写执行
我们从start.s开始看, 一上电从 b reset运行做一列初始化
.text
.global _start
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
ldr pc, swi_addr /* vector 8 : swi */
b halt /* vector 0x0c : prefetch aboot */
b halt /* vector 0x10 : data abort */
b halt /* vector 0x14 : reserved */
reset:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
/* 复位之后, cpu处于svc模式
* 现在, 切换到usr模式
*/
mrs r0, cpsr /* 读出cpsr */
bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */
bic r0, r0, #(1<<7) /* 清除I位, 使能中断 */
msr cpsr, r0
/* 设置 sp_usr */
ldr sp, =0x33f00000
ldr pc, =sdram
sdram:
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xdeadc0de /* 未定义指令 */
bl print2
swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
/***最后执行main函数***/
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
进入main.c做一系列初始化
int main(void)
{
led_init();
//interrupt_init(); /* 初始化中断控制器 */
key_eint_init(); /* 初始化按键, 设为中断源 */
timer_init();
puts("\n\rg_A = ");
printHex(g_A);
puts("\n\r");
进入按键初始化程序 interrupt.c
/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
/* 配置GPIO为中断引脚 */
GPFCON &= ~((3<<0) | (3<<4));
GPFCON |= ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */
GPGCON &= ~((3<<6) | (3<<22));
GPGCON |= ((2<<6) | (2<<22)); /* S4,S5被配置为中断引脚 */
/* 设置中断触发方式: 双边沿触发 */
EXTINT0 |= (7<<0) | (7<<8); /* S2,S3 */
EXTINT1 |= (7<<12); /* S4 */
EXTINT2 |= (7<<12); /* S5 */
/* 设置EINTMASK使能eint11,19 */
EINTMASK &= ~((1<<11) | (1<<19));
/***注册中断控制器****/
register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);
}
时钟初始化程序 timer_init();
void timer_init(void)
{
/* 设置TIMER0的时钟 */
/* Timer clk = PCLK / {prescaler value+1} / {divider value}
= 50000000/(99+1)/16
= 31250
*/
TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */
TCFG1 &= ~0xf;
TCFG1 |= 3; /* MUX0 : 1/16 */
/* 设置TIMER0的初值 */
TCNTB0 = 15625; /* 0.5s中断一次 */
/* 加载初值, 启动timer0 */
TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 */
/* 设置为自动加载并启动 */
TCON &= ~(1<<1);
TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload */
/* 设置中断 */
register_irq(10, timer_irq);
}
让后main.c函数一直循环执行 输出串口信息
while (1) { putchar(g_Char); g_Char++; putchar(g_Char3); g_Char3++; delay(1000000); //printHex(TCNTO0); }
定时器减到0的时候就会产生中断,start.S 跳到 0x18的地方执行
ldr pc, irq_addr /* vector 0x18 : irq */
b halt /* vector 0x1c : fiq */
.align 4
do_irq:
/* 执行到这里之前:
* 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_irq保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
* 4. 跳到0x18的地方执行程序
*/
/* sp_irq未设置, 先设置它 */
ldr sp, =0x33d00000
/* 保存现场 */
/* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr-4是异常处理完后的返回地址, 也要保存 */
sub lr, lr, #4
stmdb sp!, {r0-r12, lr}
/* 处理irq异常 */
bl handle_irq_c
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr_irq的值恢复到cpsr里 */
看看怎么处理irq
void handle_irq_c(void)
{
/* 分辨中断源 */
int bit = INTOFFSET;
/* 调用对应的处理函数执行 */
irq_array[bit](bit);
/* 清中断 : 从源头开始清 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}