ARM新一期第五天

一、中断
1.中断过程引入

  • 保存现场
  • 调用相应处理函数
  • 恢复现场

在这里插入图片描述

2 cpu模式状态与寄存器

  1. 模式

在这里插入图片描述

 user模式不可直接进入其他模式
 其他六种模式可以通过操作协处理器cpsr进入其他模式
 在不同模式下有专属的寄存器
 R13是SP,用做栈,每种异常可以有不同的栈
 R14是LR,中断返回地址

在这里插入图片描述

  1. state
    在这里插入图片描述
    区别和联系链接:
    https://blog.csdn.net/houyichaochao/article/details/80781289
    程序状态寄存器
    在这里插入图片描述
    SPSR :Saved Program Status Registers 用来保存被中断模式下的CPSR
    CPSR :Current Program Status Register
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

thumb指令程序实例

 /* 怎么从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  /* 用到有初始值的数组, 不是位置无关码 */

adr指令讲解:
https://blog.csdn.net/batoom/article/details/6658603

3 undefined 异常

.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}   
 
 /* 保存现场 */
 /* 处理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  /* 未定义指令 */
 bl print2
 
 //bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
 ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
halt:
 b halt
 

.world指令分析:
http://www.eeworld.com.cn/mcu/article_2016050926207.html
.string文档
http://web.mit.edu/gnu/doc/html/as_7.html

代码里面的print1和print2是调试程序用的

改进代码:
b命令是相对跳转,
b und_addr bl printException 这个函数可能在4K之外,保险起见,应该跳到SDRAM执行,用绝对跳转。

定义字符串的时候,可能占用的字节不是4的整数倍,会影响后面代码,更改代码:加上 .align 4

 .string "undefined instruction exception"
.align 4

但是用伪指令,还是用指令呢?
伪指令(就是有=号)查看反汇编发现do_undef放在了汇编文件的后面(编译器一般会把伪指令放在最后面,执行时要去读内存的值,那个值可能大于4k).
所以要做以下更改

_start:
 b reset  /* vector 0 : reset */
 ldr pc, und_addr /* vector 4 : und */
 
und_addr:
 .word do_und//其实就是在und_addr 存入do_und的值
 
do_und:

手动指定在前面读取内存值。在0X3000 0008的地方,读取到do_und为0X3000 000C。

初始化串口更改:后面代码可能超过4K,用绝对跳转,跳到sdram中执行。初始化好内存了,才能绝对跳转

 ldr pc, =sdram
sdram:
 bl uart0_init

在这里插入图片描述

4 swi异常

.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, =0x340000000
 
 /* 在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字节对齐*/

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 /*这里先保存 lr 到 r4,因为c函数会破坏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"
 
.align 4/*这里一样要写上,只要定义了字符串,会使得后面不是按照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
 
 /* 复位之后, cpu处于svc模式
  * 现在, 切换到usr模式
  */
 mrs r0, cpsr      /* 读出cpsr */
 bic r0, r0, #0xf  /* 修改M4-M0为0b10000, 进入usr模式 */
 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
 

5 按键中断程序
使能全局中断,清除cpsr 第七位,上面有图

 mrs r0, cpsr         /* 读出cpsr */
 bic r0, r0, #0xf     /* 修改M4-M0为0b10000, 进入usr模式 */
 bic r0, r0, #(1<<7)  /* 清除I位, 使能中断 */
 msr cpsr, r0

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
 /* 配置GPIO为中断引脚 */
 GPFCON &= ~((3<<0) | (3<<4));
 GPFCON |= ((2<<0) | (2<<4));   /* S2,S3被配置为中断引脚 */
 
 GPGCON &= ~((3<<6) | (3<<11));
 GPGCON |= ((2<<6) | (2<<11));   /* 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的相应位
 */

在这里插入图片描述
外部中断是(without sub-register)
在这里插入图片描述
在这里插入图片描述

从上面可以看到,EINT4_7是合用一条中断线,来通知中断控制器
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述这个寄存器和SRCPND寄存器的bit内容是一样的
在这里插入图片描述

/* 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));
}

start.s加入irq的引导:

.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/* irq优先级较高,发生中断,还未执行当前指令,就去做中断处理,所以-4*/
 stmdb sp!, {r0-r12, lr}   
 
 /* 处理irq异常 */
 bl handle_irq_c 
 
 /* 恢复现场 */
 ldmia sp!, {r0-r12, pc}^  /* ^会把spsr_irq的值恢复到cpsr里 */

handle_irq_c 函数编写:

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); 
}

6 定时器程序
在这里插入图片描述
TCNTn == TCMPn 的时候不会产生中断

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

timer 初始化代码:

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 */
 /* 设置中断 */
}

使能定时器中断:

INTMSK &= ~(1<<10);  /* enable timer0 int */

改进代码:
函数指针数组实现回调函数

typedef void(*irq_func)(int);
irq_func irq_array[32];

处理总函数

void handle_irq_c(void)
{
 /* 分辨中断源 */
 int bit = INTOFFSET;
 
 /* 调用对应的处理函数 */
 irq_array[bit](bit); 
 
 /* 清中断 : 从源头开始清 */
 SRCPND = (1<<bit);
 INTPND = (1<<bit); 
}

start.S中直接调用此函数,此函数直接调用回调函数,回调函数的注册怎么实现?

void register_irq(int irq, irq_func fp)
{
 irq_array[irq] = fp;/*注册处理函数*/
 INTMSK &= ~(1<<irq);/*使能中断控制器中断位*/
}

注册函数在初始化中断源的时候进行注册

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值