学习笔记(六)——ISR范例

  上一篇随笔中对中断进行了简要的总结,这一篇将结合程序加深对ISR的理解。

一、使用中断

  ISR执行之前将进行以下初始化工作。

1.1 建立堆栈

  前面讲到堆栈容量过小将导致栈溢出,因此计算容量很重要。较为简单的程序使用MSP,此时只需要开辟一个容量够大的堆栈,将MSP初始化到其顶即可。对于大型高性能程序,则需要两个堆栈配合使用,此时就需要谨慎计算两个堆栈的容量。(开发中一般在调试阶段把堆栈进程用过的最大的量输出,最终作为主堆栈的容量,以免浪费内存)

1.2 建立向量表

  大多数情况下,程序只给每个中断固定的ISR,此时把向量表放在ROM中即可。有时候,为了应付复杂情况,需要动态改变ISR,就需要重定位向量表了,此时向量表需保存在R/W寄存器中。

  重定位钱,首先将现有的向量表往新的位置复制一份,需要拷贝的主要是各种fault、NMI、SVC的服务例程。将所有必要向量填好之后,就可以启用新的向量表了。并往其中加入新的中断向量,程序如下:

;该子程序根据异常类型建立了相应的异常向量
;入口条件:R0=异常类型编号,R1=向量地址    
    PUSH  {R2, LR}
    LDR     R2,    =0xE000ED08         ;向量表偏移寄存器的地址
    LDR     R2,    [R2]                 ;获取向量表的首地址
    STR     R1,    [R2,RO,LSL #4]       ;在偏移地址vectblOffset(偏移
                                        ;量)+ExcpType(异常类型)*4地
                                        ;址处写入向量ExcpType*4

    POP     {R2, PC}

  另外,向量表写入的RAM是写缓冲的,那向量表更新可能会被延迟,因此建立所有向量后必须追加DSB指令。

1.3 分配各中断的优先级

  找到寄存器直接写入即可。若要确定优先级位数,可以在寄存器中写入0xFF,读出1的个数即是位数。(可以采用RBIT和CLZ来配合读出1的个数)

1.4 使能和除能中断

  使用SETENA/CLRENA,程序如下所示。由于4字节对齐,因此程序的主要内容需要计算位于8个寄存器中的哪一个32位寄存器和该32位寄存器中的哪一位。

;使能中断子程序
EnableIRQ
    ;入口条件:R0=中断号
    PUSH    {R0-R2, LR}
    AND.W   R1,    R0,    #0x1F     ;IRQ产生的移位量,.W表示强制转换为32位
    MOV     R2,    #1               
    LSL     R2,     R2,     R1      ;位旗标= (0x1 << (R0 & 0x1F)),即计算位于32位寄存器的哪一位
AND.W R1, R0, #0xE0  ; 若IRQ编号>31则为它生成下标偏移量,计算位于哪一个32位寄存器
LSR R1, R1, #3 ; 地址偏移量= (N/32)*4(每个IRQ一个位)
    LDR     R0,     =0xE000E100      ; 加载SETENA寄存器阵列的首地址
    STR     R2, [R0, R1]             ;写入该中断的位旗标,从而使能该中断
    POP     {R0-R2, PC}              
;----------------------------------------------------------------------
;除能中断子程序,与使能程序几乎一样
DisableIRQ
    ; 入口条件:R0=中断号
    PUSH  {R0-R2, LR}
    AND.W  R1,   R0,   #0x1F   ; 为该IRQ产生移位量
    MOV   R2,   #1
    LSL   R2,   R2,   R1     ; 位旗标= (0x1 << (N & 0x1F))
    AND.W  R1,   R0,   #0xE0   ; 若IRQ编号>31则为它生成下标偏移量
    LSR   R1,   R1,   #3     ; 地址偏移量= (N/32)*4(每个IRQ一个位)
    LDR   R0,   =0xE000E180    ; 加载CLRENA寄存器阵列的首地址
    STR   R2,   [R0, R1]      ; 写入该中断的位旗标,从而除能该中断
    POP   {R0-R2, PC}         ; 子程返回    

 二、中断服务例程

STACK_TOP    EQU   0x20002000   ; MSP初始值
NVIC_SETEN   EQU   0xE000E100   ; SETENA寄存器阵列的起始地址
NVIC_VECTTBL  EQU   0xE000ED08   ; 向量表偏移寄存器的地址
NVIC_AIRCR   EQU   0xE000ED0C   ; 应用程序中断及复位控制寄存器的地址
NVIC_IRQPRI   EQU   0xE000E400   ; 中断优先级寄存器阵列的起始地址
  AREA | Header Code|, CODE
  DCD STACK_TOP         ; MSP初始值
  DCD Start             ; 复位向量
  DCD Nmi_Handler        ; NMI服务例程
  DCD Hf_Handler         ; 硬fault服务例程
  ENTRY
Start                ; 主程序开始
; 初始化各寄存器
  MOV   r0,   #0
  MOV   r1,   #0
  ...
; 把各个向量拷贝到新向量表中
  LDR   r0,   =0        ;地址0处开始为复位向量
  LDR   r1,   =VectorTableBase
  LDMIA   r0!,  {r2-r5}     ; 拷贝4个向量(MSP, Reset, NMI, 硬fault)
  STMIA   r1!,   {r2-r5}
  DSB                 ; 数据同步隔离
; 执行向量表重定位:
  LDR   r0,   =NVIC_VECTTBL     ;将拷贝的向量保存到偏移地址处
  LDR   r1,   =VectorTableBase
  STR   r1,   [r0]
  ...
; 设置优先级组寄存器,划分抢占优先级与亚优先级  
  LDR   r0,   =NVIC_AIRCR
  LDR   r1,   =0x05FA0500     ; 从位5处划分(共2个位表达抢占优先级)
  STR   R1,   [r0]
; 建立IRQ0的向量
  MOV   r0,   #0           ; IRQ#0
  LDR   r1,   =Irq0_Handler
  BL   SetupIrqHandler
; 建立IRQ #0的优先级
  LDR   r0,   =NVIC_IRQPRI
  LDR   r1,   =0xC0          ; IRQ#0的优先级
  STRB   r1,   [r0,#0]        ; 写入优先级寄存器中,用了按字节传送
  DSB                    ; 数据同步隔离,保证开中断前一切都已各就各位
  MOV   r0,   #0            ; 选择IRQ #0
  BL   EnableIRQ
  ...
;------------------------
; 各函数
SetupIrqHandler
; 在向量表中建立ISR入口地址
; 入口条件:R0 = IRQ编号 ; 入口条件:R1 = IRQ服务例程的入口地址   PUSH   {R0, R2, LR}   LDR   R2,   =NVIC_VECTTBL     ; 获取向量表的地址   LDR   R2,   [R2]   ADD   R0,   #16           ; 异常号 = IRQ编号+ 16   LSL   R0,   R0,   #2       ; 乘以4 (每个向量4字节)   ADD   R2,   R0            ; 找出向量地址   STR   R1,   [R2]           ; 写入服务例程   POP   {R0, R2, PC}          ; 返回 EnableIRQ ; 入口条件:R0=中断号   PUSH   {R0-R2, LR}   AND.W  R1,   R0,   #0x1F      ; 为该IRQ产生移位量   MOV   R2,   #1   LSL   R2,   R2,   R1        ; 位旗标= (0x1 << (N & 0x1F))   AND.W  R1,   R0,   #0xE0     ; 若IRQ编号>31则为它生成下标偏移量   LSR   R1,   R1,   #3       ; 地址偏移量= (N/32)*4(每个IRQ一个位)   LDR   R0,   =NVIC_SETEN       ; 加载SETENA寄存器阵列的首地址   STR   R2,   [R0, R1]       ; 写入该中断的位旗标,从而使能该中断   POP   {R0-R2, PC}            ; 子程返回 ;------------------------ ; 异常服务程序 Hf_Handler   ...                     ; 在此添加硬fault的处理代码   BX   LR Nmi_Handler   ...                     ; 在此添加NMI的响应代码   BX   LR Irq0_Handler   ...                     ; 在此添加IRQ #0的响应代码   BX   LR ; Return ;------------------------   AREA | Header Data|, DATA   ALIGN 4 ; 重定位的向量表 VectorTableBase   SPACE   256       ; 保留256字节作向量表 VectorTableEnd                  ; (256 / 4 = 最多支持64个异常) MyData1   DCD   0              ; 定义变量 MyData2   DCD   0   END                      ; 文件结尾

 三、SVC服务例程

  在SVC服务例程中,需要读取本次触发SVC异常的SVC指令,首先通过EXC_RETURN判断使用的是MSP还是PSP,并提取出8位立即数所在的位段,来判断系统调用号。

svc_handler
;开始读取参数
  TST   LR,   #0x4     ; 测试EXC_RETURN的比特2
  ITE   EQ           ; 如果为0,
  MRSEQ   R0,   MSP      ; 则使用的是主堆栈,故把MSP的值取出
  MRSNE   R0,   PSP      ; 否则, 使用的是进程堆栈,故把MSP的值取出
  LDR   R0,   [R1,#0]    ; 从堆栈中读取R0的值
  LDR   R1,   [R1,#24]   ; 从堆栈中读取当时的PC
  LDRB    R1,   [R1,#-2]   ; 提取SVC指令中的8位立即数
; 现在:R0存储了参数,R1存储了服务代号
  PUSH   {LR}          ; 保护LR的值,因为后面将使用的BL指令
  CBNZ   R1, svc_handler_1
  BL     Puts          ; 调用Puts
  B      svc_handler_end
svc_handler_1
  CMP   R1, #1
  BNE   svc_handler_2
  BL    Putc           ; 调用Putc
  B    svc_handler_end
svc_handler_2
  CMP   R1,   #2
  BNE   svc_handler_3
  BL    PutHex          ; 调用PutHex
  B    svc_handler_end
svc_handler_3
  CMP   R1,   #3
  BNE   svc_handler_4
  BL    PutDec          ; 调用PutDec
  B     svc_handler_end
svc_handler_4
  B    error           ; 未能识别的服务代号
  ...
svc_handler_end
  POP   {PC}            ; Return

 

转载于:https://www.cnblogs.com/catinblack/p/8554904.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值