06:2440----异常与中断
目录
一:概念的引入
1:ARM系统
????????中断控制器:?中断控制器的主要作用是管理和控制可屏蔽中断,对可屏蔽中断进行优先权判定,并发送中断向量号给CPU。
? ? ? ? 中断源:按键,定时器,网络数据,其他
????????指令不对,访问数据有问题,RESET被称为异常;注意中断也是异常的一部分
2:CPU处理中断的过程
A:保存现场(各种寄存器的工作)
B:处理异常(中断属于一种异常)
C:恢复现场(在CPU处理完异常,需要做之前没有做完的工作)
3: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代码
//保护现场,调用处理函数,恢复现场
4:软/硬件工作分配
CPU--->0x18对应我们------D:协同工作的第一个
二:CPU模式状态与寄存器
1:ARM CPU的七种工作模式
- 用户模式(usr):这是正常的程序执行状态。此模式下,程序不可以访问所有的系统资源。只有特权模式才能访问所有的地址空间。用户模式如果需要访问硬件,必须切换到特权模式下,才允许访问硬件。
- 系统模式(sys):运行具有特权的操作系统任务。
- 未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。
- 管理模式(svc):操作系统使用的保护模式。在系统复位或执行软件中断指令SWI时进入。
- 数据访问中止模式(abt):当数据或指令预取中止时进入该模式,可用于虚拟存储及存储保护。
- 外部中断模式(irq):用于通用的中断处理。当触发外部中断时进入此模式。
- 快速中断模式(fiq):用于高速数据传输或通道处理。当触发快速中断时进入此模式。
? ? ? ? 3~7:称为异常模式;2~7:被称为特权模式;
????????特权模式:处理器可以访问所有的存储器空间和执行所有的指令,这使得操作系统可以控制系统中的所有硬件和软件资源。此外,在特权模式下,还可以对处理器进行更深入的配置和控制,例如关闭或打开中断、配置存储器映射等。可以直接进入其他模式,可以直接修改CPSR。
????????用户模式:应用程序只能访问有限的存储器空间和执行一部分指令,无法直接访问硬件或执行特权操作。此模式下不能直接进去其他模式,不能直接修改CPSR。
2:state?
ARM state:ARM指令集,每个指令4byte
Thumb state:Thumb指令集,每个指令2type
对于ARM指令集要占据4个字节:机器码
对于Thumb指令集占据2个字节:机器码
引入Thumb减少存储空间
ARM指令集和Thumb指令集的区别主要体现在以下几个方面:
- 代码密度:Thumb指令集是16位的,相对于ARM指令集(32位),其代码密度更高。
- 跳转指令:在程序相对转移方面,Thumb指令集的跳转指令与ARM代码下的跳转相比,在范围上有更多的限制。转向子程序是无条件的转移。
- 数据处理指令:数据处理指令是对通用寄存器进行操作。在大多数情况下,操作的结果须放入其中一个操作数寄存器中,而不是第三个寄存器中。此外,Thumb指令集中的数据处理操作比ARM状态的要少,访问寄存器R8—R15受到一定限制。
- 单寄存器加载和存储指令:在Thumb状态下,单寄存器加载和存储指令只能访问寄存器R0—R7。
- 批量寄存器加载和存储指令:LDM和STM指令可以将任何范围为R0——R7的寄存器子集加载或存储。
总的来说,ARM指令集和Thumb指令集的主要区别体现在代码密度、跳转指令、数据处理指令、单寄存器加载和存储指令以及批量寄存器加载和存储指令等方面。
3:寄存器
A:寄存器情况
ARM CPU七种模式下的寄存器的情况
????????灰色三角形表示-----该模式下的专属寄存器
?????????在这五种异常模式中每个模式都有自己专属的R13 R14寄存器,R13用作SP(栈) R14用作LR(返回地址),LR是用来保存发生异常时的指令地址(可以理解为我们上面说的保存现场,用于我们在处理完中断,之后继续处理我们的程序)
????????R14寄存器在ARM指令集中的作用是保存子程序的返回地址或异常返回地址。当使用BL指令调用子程序时,返回地址将自动存入R14中。当发生异常时,R14对应的异常模式版本会被设置为异常返回地址。在其他情况下,R14可以作为通用寄存器使用。
????????快中断(FIQ)的专属寄存器,被称为备份寄存器
????????就比如说我们的程序正在系统模式/用户模式下运行,当你发生中断时,需要把R0 ~ R14这些寄存器全部保存下来,让后处理异常,最后恢复这些寄存器
????????但如果是快中断,那么我就不需要保存 系统/用户模式下的R8 ~ R12这几个寄存器,在FIQ模式下有自己专属的R8 ~ R12寄存器,省略保存寄存器的时间,加快处理速度
????????CPSR(当前程序状态寄存器)和SPSR(程序状态保存寄存器)都是ARM架构中的状态寄存器,用于存储处理器的各种状态信息。以下是它们之间的主要区别:
- CPSR:CPSR在用户级编程时用于存储条件码。它包含了条件码标志、中断禁止位、当前处理器模式以及其他状态和控制信息。CPSR在任何处理器模式下都可以被访问,它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。
- SPSR:SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。每一种处理器模式下都有一个专用的物理状态寄存器,称为SPSR(备份程序状态寄存器)。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。由于用户模式和系统模式不是异常中断模式,所以它们没有SPSR。当用户在用户模式或系统模式访问SPSR,将产生不可预知的后果。
????????总的来说,CPSR和SPSR都是ARM系统中的状态寄存器,但它们在数量、功能和使用场景上存在明显的区别。?
CPSR(当前程序状态寄存器)和SPSR(程序状态保存寄存器)的区别主要体现在以下三个方面:
- 数量:在整个ARM系统中,CPSR只有一个,而SPSR有五个。
- 功能:CPSR主要用于存储处理器的状态信息,如条件码标志、中断禁止位、当前处理器模式等。而SPSR则用于在异常发生时保存和恢复CPSR的状态,以在返回普通模式时恢复原来的CPSR。
- 使用场景:CPSR在任何处理器模式下都可以被访问,而SPSR则是在异常中断模式下使用,每种处理器模式对应一个专用的物理状态寄存器SPSR。
总的来说,CPSR和SPSR的主要区别在于它们的用途和功能。CPSR主要用于存储当前程序的状态信息,而SPSR则用于在异常发生时保存和恢复CPSR的状态。这些状态寄存器在ARM架构中起着重要的作用,它们可以帮助处理器管理中断、切换工作模式,以及实现条件执行等功能。
B:CPSR
CRSR当前程序状态寄存器,这是一个特别重要的寄存器,SPSR程序状态保存寄存器,
CRSR格式如下:
????????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是状态位 (在下面解释)
????????M4 ~ M0 :表示当前CPU处于哪一种模式(Mode),我们可以读取这5位来判断CPU处于哪一种模式,也可以修改这一种模式位,让其修改这种模式,假如你当前处于用户模式下,是没有权限修改这些位的。
M[4:0]位对应的值
C:状态位和保留位
状态位
??cmp
函数的作用是比较两个元素的大小,从而实现排序。cmp
函数的返回值为整型,当返回值小于0时,表示第一个元素小于第二个元素;当返回值等于0时,表示两个元素相等;当返回值大于0时,表示第一个元素大于第二个元素。?
????????如果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
D:协同工作
发生异常的时候CPU的协同工作-----硬件为我们做,不需要我们的软件做
1:把下一条指令的地址保存在LR寄存器里(某种异常模式的LR等于被中断的下一条指令的地址),它有可能是PC + 4有可能是PC + 8,到底是那种取决于不同的情况
????????当他在读取地址A指令的时候
????????已经在对地址A+4的指令进行译码
????????已经在读取地址A+8的指令------01:2440----点灯大师5:ARM知识补充有讲到
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 清中断(如果是中断的话,对于其他异常不用设置)
E:异常向量表
三:Thumb指令集
????????Thumb并不重要,这节重点在于普及知识。在linux系统几乎不会使用Thumb指令集,因为linux指令集没有必要节省这点空间,单片机中可能会使用到
makefile文件
all: led.o uart.o init.o main.o start.o
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 $@ $<
%.o : %.S
arm-linux-gcc -c -o $@ $<
Strat.S
.text
.global _start
.code 32
_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_func:
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr r0, =main /* 绝对跳转, 跳到SDRAM */
mov pc, r0
halt:
b halt
禁用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++;
}
}
四:swi异常
1:und异常
A:简单尝试
未定义指令异常---und
汇编函数知识的补充
stmdb:
db-----先减后储;!-------最终的值等于被修改的值
stmdb sp!,{fp,ip,lr,pc} =====>{里面的是高编号的寄存器在高地址}
MRS 指令:?对状态寄存器
CPSR
和SPSR
进行读操作。通过读CPSR
可以获得当前处理器的工作状态。读SPSR
寄存器可以获得进入异常前的处理器状态(因为只有异常模式下有SPSR
寄存器)---------MRS指令的作用是将程序状态寄存器(CPSR或SPSR)的内容传送到通用寄存器中。MSR指令:?对状态寄存器
CPSR
和SPSR
进行写操作。与MRS
配合使用,可以实现对CPSR
或SPSR
寄存器的读、写和修改操作,可以切换处理器模式、或者允许、禁止IRQ/FIQ中断等。
^
---->
表示将spsr的值复制到cpsr,即将中断模式下的程序状态寄存器的值复制到当前程序状态寄存器中。
PC指针
????????在ARM处理器中,PC(Program Counter)是一个特殊的寄存器,用于存储下一条指令的地址。在ARM的流水线中,PC指针的作用非常重要,因为它决定了下一条指令的执行位置。
????????PC指针在ARM的流水线中起着至关重要的作用。在取指阶段,PC指针被用来确定下一条指令的地址,并将该指令从内存中读取到指令缓存中。在译码阶段,PC指针被用来确定指令的类型和操作数,并将这些信息传递给执行阶段。在执行阶段,PC指针被用来确定下一条指令的地址,并将执行结果写回到寄存器或内存中。
????????由于ARM的流水线是一种并行执行的方式,每个阶段都可以同时处理多条指令。因此,PC指针需要及时更新,以确保下一条指令的执行位置正确。在ARM中,PC指针的更新是通过特殊的指令(如B、BL等)来实现的。这些指令可以修改PC指针的值,从而改变下一条指令的执行位置。
????????综上所述,ARM中的PC主要用于存储下一条指令的地址,并在流水线的不同阶段中确定指令执行的顺序和位置。
makefile
all: start.o led.o uart.o init.o main.o exception.o
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 $@ $<
相关的makefile请参考我的:? ?01:linux基础---gcc/makefile/gdb?
?sdram.lds
?
SECTIONS
{
. = 0x30000000;
__code_start = .;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
_end = .;
}
?
exception.c
#include "uart.h"
void printException(unsigned int cpsr, char *str)
{
puts("Exception! cpsr = ");
printHex(cpsr);
puts(" ");
puts(str);
puts("\n\r");
}
start.S
.text
.global _start
_start:
b reset /* vector 0 : reset */
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} /* R14用作LR(返回地址),LR是用来保存发生异常时的指令地址*/
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =und_string
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 /* 未定义指令 当CPU执行到这里面的时候,发现未定义指令异常跳转到了*/
/*b do_und vector 4 : und 执行do_und里面的汇编码*/
bl print2
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
现象:
B:改进?
汇编知识的补充
????????b是普通的跳转语句,跳到某一个函数之后不会回来
????????而bl跳转会把下一句程序(isb)的地址放到lr寄存器中,到某一个函数执行完成之后,执行mov pc, lr(将lr的数据移动到pc中)将lr寄存器中的值赋给pc指针,继续运行isb指令,也就值跳转执行完成后会回来。
?
????????这里又用BL指令来跳转。如果我们是NAND启动,如果这个函数是在4k之外。那么,这个指令必定出错.
bl printException
前面已经重定位到SDARM中去了(全部的代码已经copy到SDARM中去了)
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
为了保险,你应该跳转到SDRAM里面去执行这些处理函数。
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
und_addr:
.word do_und
改进代码:
.text
.global _start
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* 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} /* R14用作LR(返回地址),LR是用来保存发生异常时的指令地址*/
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =und_string
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
ldr pc, =sdram
sdram:
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xdeadc0de /* 未定义指令 当CPU执行到这里面的时候,发现未定义指令异常跳转到了*/
/*b do_und vector 4 : und 执行do_und里面的汇编码*/
bl print2
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
处理过程:
2:SWI异常?
汇编知识的补充
BIC 指令-----清位
指令格式bic{条件}{S} ?Rd,Rn,operand
根据operand哪个位为1,清除Rn对应的位,然后将结果存入Rd。例子:
????????bic?? ?r0, r0, #0x00002000 ? ?// clear bit[13] ? 0010 0000 0000 0000
????????bic?? ?r0, r0, #0x00000007 ? ?// clear bit[2:0] ?0000 0000 0000 0111
第1条汇编代码表示将 r0 寄存器的 bit13 清0,其它bit不变。
第2条汇编代码表示将 r0 寄存器的bit0-bit2清0,气他bit不变
start.S?
.text
.global _start
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
ldr pc ,swi_addr /*vector 8 : swi*/
und_addr:
.word do_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} /* R14用作LR(返回地址),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
do_swi:
/* 执行到这里之前:
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_svc保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 4. 跳到0x08的地方执行程序
*/
/* sp_und未设置, 先设置它 */
ldr sp, =0x33e00000
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr} /* R14用作LR(返回地址),LR是用来保存发生异常时的指令地址*/
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
swi_string:
.string "swi exception"
.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
/*切换模式为usr模式
*复位之后,cpu处于svc模式
* 现在切换为usr模式---1 0000
*/
mrs r0,cpsr
bic r0,r0, #0xf
msr cpsr,r0
/*设置SP_usr栈*/
ldr sp, =0x33f00000
ldr pc, =sdram
sdram:
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xdeadc0de /* 未定义指令 当CPU执行到这里面的时候,发现未定义指令异常跳转到了*/
/*b do_und vector 4 : und 执行do_und里面的汇编码*/
bl print2
swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
?注意要4字节边界对齐,否则会出现未知的错误
.align 4
B:改进
????????应用程序可以根据swi指令传入的value来判断,为什么调用hwl指令。现在我们使用异常处理函数,把value读取出来
void printSWIVal(unsigned int* pSWI)
{
puts("SWI val = ");
printHex(*pSWI & ~0xff000000);
puts("\n\r");
}
start.S
.text
.global _start
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
ldr pc ,swi_addr /*vector 8 : swi*/
und_addr:
.word do_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} /* R14用作LR(返回地址),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
do_swi:
/* 执行到这里之前:
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_svc保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 4. 跳到0x08的地方执行程序
*/
/* sp_und未设置, 先设置它 */
ldr sp, =0x33e00000
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr} /* R14用作LR(返回地址),LR是用来保存发生异常时的指令地址*/
mov r4, lr /*保存lr的地址,以为在下面会把lr内存破坏*/
/* 保存现场 */
/* 处理und异常 */
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"
.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
/*切换模式为usr模式
*复位之后,cpu处于svc模式
* 现在切换为usr模式---1 0000
*/
mrs r0,cpsr
bic r0,r0, #0xf
msr cpsr,r0
/*设置SP_usr栈*/
ldr sp, =0x33f00000
ldr pc, =sdram
sdram:
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xdeadc0de /* 未定义指令 当CPU执行到这里面的时候,发现未定义指令异常跳转到了*/
/*b do_und vector 4 : und 执行do_und里面的汇编码*/
bl print2
swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
为什么要单独保存lr?为什么使用r4保存
bl printException 会破坏lr的值所以单独保存lr
mov r4, lr
r4为什么要减4
swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
????????当发生swi异常的时候lr会保存??ldr pc, =main ?/* 绝对跳转, 跳到SDRAM */? 地址的值。使用给lr寄存器-4,就可以找到他的内存地址?
?五:按键中断
1:中断的处理流程
<1> 中断初始化:
????????我们需要设置中断源,让它能够发出中断喜好
????????设置中断控制器,让它能发出中断给CPU
????????设置CPU,CPSR有I位,是总开关
????????我们需要这样设置,中断源才能发送给CPU
(流程在一:概念的引入? 1:ARM系统)
<2> 处理完要清中断
<3> 处理时,要分辨中断源,对于不同的中断源要执行不同的处理函数
2:按键引脚初始化
设置按键为我们的中断源
设置触发沿
x=====》表示不在乎;我们这里就把他设置为1好了,方便我们的书写。
外部中断屏蔽寄存器
????????被设置为1====》禁止向CPU发送中断信号 ;只有EINTMASK相应的位设置为0外部中断才能给中断控制器发信号
? ? ? ? EINT0~EINT3并不需要通过外部中断屏蔽寄存器就可以发送给中断控制器
void key_eint_init(void)
{ /*
设置中断引脚
*/
/*ENIT0---对应GPF0; ENIT2---对应GPF2*/
GPFCON &= ~((3 << 0) | (3 << 4));
GPFCON |= ((2 << 0) | (2 << 4));
/*ENIT11---对应GPG3; ENIT19---对应GPG11*/
GPGCON &= ~((3 << 6) | (3 << 22));
GPGCON |= ((2 << 6) | (2 << 22));
/*设置触发沿----双沿触发*/
EXTINT0 |= ((7 << 0) | (7 <<8 )); /*ENIT0,ENIT2*/
EXTINT1 |= (7 << 12); /*ENIT11*/
EXTINT2 |= (7 << 12);
/*ENIT11和ENIT19引脚使能*/
EINTMASK &= ~((1 < 11) | (1 << 19));
}
/* 读EINTPEND分辨率哪个EINT产生(eint4~23)
* 清除中断时, 写EINTPEND的相应位
*/
3:中断控制器
????????我们使用中断来源不含子源,所以我们这里面不需要经过 SUBSRCPND,SUBMASK 这2个寄存器
SRCPND
????????SRCPND是一个中断源未决寄存器,有效位为32位,每一位对应一个中断源。当某个位被置一时,表示相应的中断被触发。在同一时间内,可以有多个中断被触发,只要中断触发了,相应的位就被置一,直到该位被清除为止。也就是说,在同一时刻SRCPND寄存器可以有多个位被同时置1,该位写1后被清0。
? ? ? ? 他用于显示某一个中断是否发生了;在执行完之后我们需要清除这个SRCPND
/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
?* bit0-eint0
?* bit2-eint2
?* bit5-eint8_23
?*/
MASK
MASK是中断屏蔽寄存器的简称,它用于控制是否屏蔽某些中断。当某些位被置一时,对应的中断被屏蔽,即不会被处理。通过设置MASK寄存器的值,可以控制哪些中断被屏蔽,哪些中断被允许
?
/* INTMSK 用来屏蔽中断, 1-masked
?* bit0-eint0
?* bit2-eint2
?* bit5-eint8_23
?*/?
INTPND
????????INTPND是一个中断挂起寄存器,用于标识哪些中断正在挂起等待处理。当某个中断被触发并且未被屏蔽时,相应的位会被置1,表示该中断正在挂起等待处理。通过读取INTPND寄存器的值,可以确定哪些中断正在挂起,从而决定是否需要处理这些中断。
? ? ? ? 当有多个中断发生时,在SRCPND中会有多个位被设置为1,多个中断通过优先级(Priority)以后,只会有一个优先级最搞的中断发生给CPU。这里我们INTPN的作用为查看那个中断的优先级最高,为当前正在处理的优先级。
?
/* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
?* bit0-eint0
?* bit2-eint2
?* bit5-eint8_23
?*/?
INTOFFSET?? ?
用来显示???INTPND哪一个正在等待处理;该位可以通过清除SRCPND和INTPND自动清除。
/* INTOFFSET : 用来显示INTPND中哪一位被设置为1
?*/?
void interrupt_init(void)
{
INTMSK &= ~((1 << 0) | (1 << 2) | (1 << 5));
}
4:代码
main.c
main函数大体一样的,只是加上了几个函数的初始化
led_test();
interrupt_init();/* 初始化中断控制器 */
key_eint_init();/* 初始化按键, 设为中断源 */
start.S
.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 */
ldr pc, irq_addr /* vector 0x18 : irq */
b halt /* vector 0x1c : fiq */
und_addr:
.word do_und
swi_addr:
.word do_swi
irq_addr:
.word do_irq
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} /* R14用作LR(返回地址),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
do_swi:
/* 执行到这里之前:
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_svc保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 4. 跳到0x08的地方执行程序
*/
/* sp_und未设置, 先设置它 */
ldr sp, =0x33e00000
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr} /* R14用作LR(返回地址),LR是用来保存发生异常时的指令地址*/
mov r4, lr /*保存lr的地址,以为在下面会把lr内存破坏*/
/* 保存现场 */
/* 处理und异常 */
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"
.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的值恢复到cpsr里 */
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
/*切换模式为usr模式
*复位之后,cpu处于svc模式
* 现在切换为usr模式---1 0000
*/
mrs r0,cpsr
bic r0,r0, #0xf
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 /* 未定义指令 当CPU执行到这里面的时候,发现未定义指令异常跳转到了*/
/*b do_und vector 4 : und 执行do_und里面的汇编码*/
bl print2
swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
interrupt.c
#include "s3c2440_soc.h"
/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
* bit0-eint0
* bit2-eint2
* bit5-eint8_23
*/
/* INTMSK 用来屏蔽中断, 1-masked
* bit0-eint0
* bit2-eint2
* bit5-eint8_23
*/
/* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
* bit0-eint0
* bit2-eint2
* bit5-eint8_23
*/
/* INTOFFSET : 用来显示INTPND中哪一位被设置为1
*/
/* 初始化中断控制器 */
void interrupt_init(void)
{
INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
}
/* 初始化按键, 设为中断源 */
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));
}
/* 读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;
}
void handle_irq_c(void)
{
/* 分辨中断源 */
int bit = INTOFFSET;
/* 调用对应的处理函数 */
if (bit == 0 || bit == 2 || bit == 5) /* eint0,2,eint8_23 */
{
key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
}
/* 清中断 : 从源头开始清 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
六定时器
1:流程图
? ? ? ?
TCNTn的值来自于-----TCNTBn;? ? ?TCMPn的值来自于-----TCMPBn;
流程:
????????1: 每来一个clk(时钟)这个TCNTn减去1
????????2 :当TCNTn == TCMPn时,可以产生中断,也可以让对应的SPWM引脚反转,(比如说原来是高电平,发生之后电平转换成低电平)
????????3 :TCNTn继续减1,当TCNTn == 0时,可以产生中断,pwm引脚再次反转
TCMPn 和 TCNTn的初始值来自 TCMPBn,TCNTBn
????????4: TCNTn == 0时,可自动加载初始
怎么使用定时器:
1:设置时钟
2:设置初值
3:加载初值,启动time定时器
4:设置为自动加载并启动
????????详情可以看我的? 03:TIM定时器https://blog.csdn.net/m0_74739916/article/details/132391890
2:寄存器
A:设置时钟
????????在这里面我们使用的是定时器0,在下面不做解释
可以看到我们设置TCFG0寄存器的【7:0】位可以设置我们定时器0的预分频系数
?设置TCFG1的MUX0的【3:0】为可以设置分频值
/*设置时钟*/
/*Timer = PCLK / {prescaler value+1} / {divider value}
*prescaler value 预分频系数
*divider 分频值---这里我们选择1/16的
50 000 000/(99+1)/(16)=31250
*/
TCFG0 =99; //预分频系数
TCFG1 &= ~0x0000000f;
TCFG1 |= 0x3; /*1/16*/
B:设置初值
/*设置time的初值*/
TCNTB0 = 15625; //0.5s
C:加载初值,和启动
加载初值,启动time定时器? 和??设置为自动加载并启动
加载初值,启动time定时器:
/* 加载初值, 启动timer0 */
TCON |= (1 << 1); /* Update from TCNTB0 & TCMPB0 */
置为自动加载并启动:
/* 设置为自动加载并启动 */
TCON &= ~(1 << 1);
TCON |= (1 << 0) | (1 << 3); /* bit0: start, bit3: auto reload */
3:代码
interrupt.c?
大体的一样加了一点
/* 初始化中断控制器 */
void interrupt_init(void)
{
INTMSK &= ~((1 << 0) | (1 << 2) | (1 << 5));
INTMSK &= ~(1 << 10); /* enable timer0 int */
}
void handle_irq_c(void)
{
/*分辨中断源*/
int bit = INTOFFSET;
/*调用对应的处理函数*/
if (bit == 0 | bit == 2 | bit == 5)
{
key_eint_irq(bit);
}
else if (bit == 10)
{
timer_irc();
}
/*清中断*/
SRCPND = (1 << bit);
INTPND = (1 << bit);
}
time.c
#include "s3c2440_soc.h"
void timer_init(void)
{
/*设置时钟*/
/*Timer = PCLK / {prescaler value+1} / {divider value}
*prescaler value 预分频系数
*divider 分频值---这里我们选择1/16的
50 000 000/(99+1)/(16)=31250
*/
TCFG0 =99; //预分频系数
TCFG1 &= ~0x0000000f;
TCFG1 |= 0x3; /*1/16*/
/*设置time的初值*/
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 */
}
void timer_irc(void)
{
/* 点灯计数 */
GPFDAT |= (1 << 4);
GPFDAT &= ~(1 << 4);
}
关于定时器时间的计算:
?
1.时钟频率 = 50MHZ
2.预分频系数 = 99
3.分频系数 =16
4.定时器频率 = 时钟频率/(分频系数 + 1)/ 分频系数 = 50MHz /(99 + 1)/ 16 = 312.5kHZ=31250HZ5.定时器周期 = 1/定时器频率 = 1/31250Hz =0.000032s
6.定时器计数值 = 定时器周期 x 需要的时间 (秒) = 0.000032s x 15625 = 0.5s
改进:
1、函数指针(Function Pointer):
????????函数指针是指向函数的指针变量,它可以存储函数的地址,并可以通过该指针来调用相应的函数。
????????函数指针是一个变量,它存储了一个函数的地址。函数指针可以用于调用函数,也可以用于传递函数作为另一个函数的参数
函数指针的定义如下:
return_type (*pointer_name)(parameters);
return_type 是函数返回值的类型。
pointer_name 是函数指针的名称。
parameters 是函数的参数列表
typedef void(*irq_func)(int); /*指针函数*/
interrupt.c?
#include "s3c2440_soc.h"
typedef void(*irq_func)(int); /*指针函数*/
irq_func irq_array[32];
void key_eint_irq(int irq);
void handle_irq_c(void);
void register_irq(int irq, irq_func fp);
/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
* bit0-eint0
* bit2-eint2
* bit5-eint8_23
*/
/* INTMSK 用来屏蔽中断, 1-masked
* bit0-eint0
* bit2-eint2
* bit5-eint8_23
*/
/* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
* bit0-eint0
* bit2-eint2
* bit5-eint8_23
*/
/* INTOFFSET : 用来显示INTPND中哪一位被设置为1
*/
/* 读EINTPEND分辨率哪个EINT产生(eint4~23)
* 清除中断时, 写EINTPEND的相应位
*/
/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{ /*
设置中断引脚
*/
/*ENIT0---对应GPF0; ENIT2---对应GPF2*/
GPFCON &= ~((3 << 0) | (3 << 4));
GPFCON |= ((2 << 0) | (2 << 4));
/*ENIT11---对应GPG3; ENIT19---对应GPG11*/
GPGCON &= ~((3 << 6) | (3 << 22));
GPGCON |= ((2 << 6) | (2 << 22));
/*设置触发沿----双沿触发*/
EXTINT0 |= ((7 << 0) | (7 << 8)); /*ENIT0,ENIT2*/
EXTINT1 |= (7 << 12); /*ENIT11*/
EXTINT2 |= (7 << 12);
/*ENIT11和ENIT19引脚使能*/
EINTMASK &= ~((1 << 11) | (1 << 19));
register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);
}
/*
* 中断处理函数
*/
void key_eint_irq(int irq)
{
unsigned int val = EINTPEND;
unsigned int val1 = GPFDAT;
unsigned int val2 = GPGDAT;
unsigned int num = 0;
unsigned int num1 = 0;
unsigned int num2 = 0;
unsigned int num3 = 0;
if (irq == 0) /* eint0 : s2 控制 D12 */
{
num = val1 & (1 << 0); /*检测LED_4是否按下;0--按下;1没有按下*/
if (num==1) /* s2 --> gpf6 按键按下为0*/
{
/* 松开---熄灭 */
GPFDAT |= (1 << 6);
}
else
{
/* 按下---点亮 */
GPFDAT &= ~(1 << 6);
}
}
else if (irq == 2) /* eint2 : s3 控制 D11 */
{
num1 = val1 & (1 << 2);
if (num1 == 0) /* s3 --> gpf5 按键按下为0 */
{
/* 按下 */
GPFDAT &= ~((1 << 5) | (1 << 4));
}
else
{
/* 松开 */
GPFDAT |= ((1 << 5)| (1 << 4));
}
}
else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */
{
GPFDAT |= (1 << 6);
//num2 = val & (1 << 11);
if (val & (1 << 11)) /* eint11 num2 !=0*/
{
num3 = val2 & (1 << 3);
if (num3==0) /* s4 --> gpf4 */
{
/* 按下: 点亮所有LED */
GPFDAT &= ~((1 << 4) | (1 << 5) | (1 << 6));
}
else
{
/* 松开 */
/* 熄灭所有LED */
GPFDAT |= ((1 << 4) | (1 << 5) | (1 << 6));
}
}
else if (val & (1<<19)) /* eint19 */
{
if (val2 & (1<<11))
{
/* 按下 */
GPFDAT &= ~(1 << 4);
}
else
{
/* 按下 */
GPFDAT &= ~(1 << 4);
}
}
}
EINTPEND = val;
}
void handle_irq_c(void)
{
/*分辨中断源*/
int bit = INTOFFSET;
/*调用对应的处理函数*/
/* 调用对应的处理函数 */
irq_array[bit](bit);
/*清中断*/
SRCPND = (1 << bit);
INTPND = (1 << bit);
}
/*注册函数
*irq :注册中断号
* irq_func fp 注册函数
*/
void register_irq(int irq, irq_func fp)
{
irq_array[irq] = fp;
INTMSK &= ~(1 << irq); /*INTMSK--屏蔽中断 1--屏蔽;0--解除*/
}
time.c
#include "s3c2440_soc.h"
void timer_irc(void);
void timer_init(void)
{
/*设置时钟*/
/*Timer = PCLK / {prescaler value+1} / {divider value}
*prescaler value 预分频系数
*divider 分频值---这里我们选择1/16的
50 000 000/(99+1)/(16)=31250
*/
TCFG0 =99; //预分频系数
TCFG1 &= ~0x0000000f;
TCFG1 |= 0x3; /*1/16*/
/*设置time的初值*/
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_irc);
}
void timer_irc(void)
{
/* 点灯计数 */
GPFDAT |= (1 << 4);
GPFDAT &= ~(1 << 4);
}
?
irq_array[bit](bit);
【bit】调用发生相关中断的函数;(bit)给刚才调用的中断函数传入参数bit
eg:
【0】发生0号中断来调用相关的函数,register_irq(0, key_eint_irq); 调用key_eint_irq函数
(0)给调用的key_eint_irq函数传入参数:0
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!