ARM开发基础

一、ARM 基础知识

说明:面试会涉及到前5点知识,必须牢记;
(1)ARM工作模式
(2)每种模式的寄存器资源
(3)ARM异常处理
(4)ARM指令部分
(5)汇编与C语言混合编程
(6)硬件外设操作(LED/KEY/PWM/INTERRUPT/ADC/CLOCK/UART/WDT/IIC/SPI)


二、ARM工作模式


U(usr用户模式)     S(sys系统模式)   A(abt终止模式)    U(udf 未定义模式)  F(fiq 快中断模式)
I(IRQ 慢中断模式)  S(svc管理模式)   M(mon安全监控模式[cotex-A])

问题1:  ARM核为什么需要设计这么多模式?
      为了更好的处理各种异常(例如:中断 ...)
 
问题2:  什么特权模式,什么是非特权模式?
            特权模式下,权限最大化,特权模式可以自己手动切换到其他模式
            非特权模式,权限受限,不能自己手动切换到其他模式      

(1)非特权模式
   usr(用户模式)
  
(2)特权模式
           除用户模式外,其他都是
          
(3)异常模式
           IRQ,FIQ,ABT,SVC,UDF

三、ARM寄存器资源


R0-R15,CPSR,SPSR

[1] r0-r12   普通寄存器使用
[2] r13 (sp)  栈指针寄存器
[3] rl4 (lr)  在跳转之前保存PC值,便于返回(异常处理,子函数调用)
[4] r15 (pc)  告诉ARM核,需要取的指令所在的地址   
[5] CPSR     反映ARM核状态

-------------------------------------------------------------------------
31  30  29  28                          7   6   5   4:0
-------------------------------------------------------------------------
N   Z   C   V                           I   F   T   MODE
-------------------------------------------------------------------------
 
<1>MODE  当前所在的模式
<2>T     1:thumb状态(指令16bit宽度)    0:ARM状态(指令32bit宽度)
<3>F     1 : 禁用FIQ异常                 0 : 允许FIQ异常
<4>I      1 : 禁用IRQ异常                 0 : 允许IRQ异常
<5>V     有符号溢出 [两个整数相加结果为负数]
<6>C     加法产生进位则置1(计算结果超过32bit),否则置0  / 减法没有产生借位, 则置1,否则置0
<7>Z     计算结果为0
<8>N     计算结果为负数

问题:开发板复位,ARM核的状态?
     SVC模式,ARM态,IRQ/FIQ异常禁用
    
    
(1)所有模式共用的寄存器                                 10个
   r0-r7,pc,cpsr                                              
 
(2)r8-r12寄存器       
   FIQ模式的r8-r12是私有的,其他模式共用          10个
  
(3)异常模式私有
   sp,lr,spsr                                                       3*5 = 15个
  
(4)用户和系统模式共用同一套寄存器                     2个            // 是:sp(r13)、lr(r14)

说明:用户模式(usr)和系统模式(sys)无spsr寄存器,因为只有异常模式才考虑用spsr备份CPSR

(5)cotex-A系列 安全监控模式私有寄存器:sp,lr,spsr

总结:
如果不考虑cotex-A系列,共有37个寄存器;
算上cotex-A系列,多了3个安全监控模式私有寄存器:sp,lr,spsr,共有40个寄存器;

四、ARM异常处理


说明:5种异常模式、7种异常类型,这7种异常类型对应的是这5种异常模式
(1)ARM的异常类型

  复位异常         :  开发板复位     ->  SVC模式
  未定义指令异常   :  执行了未定义的指令 ->  UDF模式
  软中断异常       :  执行了swi 指令 (系统调用使用:每个系统调用接口都对应一个软中断号) -> SVC模式
  预取指令终止异常 :  没有取到指令(从虚拟地址取指令,而虚拟地址没有进行映射到指定的物理地址)->  ABT模式
  数据终止异常     :  没有取到数据 ->  ABT模式
  慢中断异常       :  产生了中断   ->  IRQ模式
  快中断异常       :  产生了中断   ->  FIQ模式
  
(2)异常产生,需要做的事情
   <1>ARM核硬件自动做的事情
      [1]将CPSR拷贝到异常模式的SPSR
      [2]设置CPSR相应位
          .进入ARM状态
          .进入相应的异常模式
          .禁用中断[复位异常的异常]
      [3]将pc的值保存在异常模式的lr
      [4]跳转到异常向量表      
 
   问题:   什么是异常向量表?有什么作用 
  异常向量表:  存放的是一些跳转指令;   
  当异常产生的时候,ARM核会将PC指向异常向量表的相应位置,从异常向量表取出指令执行,
  这些指令执行的结果是跳转到异常处理函数
 
   问题:   异常向量表存放的位置 ?
  在cotex-A8之前的ARM核,异常向量表可以存放在0x0000,0000或0xffff,0000
  在cotex-A8系列中,异常向量表可以存放在任何位置,只需要将异常向量表的基地址写到
  协处理器cp15的c12寄存器就可以了。
 
  问题:Linux 操作系统中,异常向量表的位置?
 
  用户态[0~3G ]      0x0000,0000 ~ 0xc000,0000
  ------------------------------
  内核态[3G~4G]      0xc000,0000 ~ 0xffff,ffff
 
 
  <2>程序需要做的事情:异常函数编写
     [1]保存共用的寄存器(把共用寄存器的值压入栈)
     [2]异常处理
     [3]异常返回(恢复CPSR,PC,共用的寄存器)


五、ARM常用指令


1.ARM指令格式
 
   opcode{cond}{s}   Rd,Rn,Rm
  
(1)opcode  指令助记符号 ,例如:ADD, MOV ...
(2)cond    大于(gt),大于等于(ge),小于(lt),小于等于(le),相等(eq),不相等(ne)
(3)s       指令执行的结果影响CPSR的N,Z,C,V
(4)Rd      目标寄存器(r0-r15)
(5)Rn      第一个操作数,必须是寄存器(r0-r15)
(6)Rm      第二个操作数,有多种形式
           [1]立即数,表示: #数字
           [2]寄存器
           [3]寄存器移位  lsl(逻辑左移) ,lsr(逻辑右移), asr(算术右移)
           
           
问题:合法立即数?
     如果可以拿一个8bit数字[0~255]循环右移偶数位得到当前的数字,则当前数字为合法立即数

问题:寄存器移位?
     r0,lsl #4   => r0 << 4 
     r0,lsr #2   => r0 >> 2 
    
2.数据传送指令
  指令格式:MOV/MVN Rd,Rm
 
  mov  r0,#5   => r0 = 5
  mvn  r0,#5   => r0 = ~5
          mov  r0,r0,lsl #4  => r0 = r0 << 4
 
  mov   r0,#0x12345678
  错误,因为0x12345678不是合法立即数
 
  问题:如何将任意数字装载到寄存器?
  ldr  Rd,=数字
  ldr  r0,=0x12345678 [正确]
 
3.数据运算指令
  ADD/SUB/MUL  Rd,Rn,Rm
 
  ADD r0,r1,r2  => r0 = r1 + r2
  ADD r0,r0,r1,lsr #4 => r0 = r0 + (r1 >> 4)
  SUB r0,#5,r1 [错误:第一个操作数必须是寄存器]
  ADD r0,r1,#5,lsl #4[错误:只能是寄存器移位,不能是立即数]
 
  MUL r0,r1,r2  => r0 = r1 * r2
  MUL r0,r1,#4 [错误:对于乘法指令,所有的操作数必须是寄存器]
  MUL r1,r1,r0 [错误:对于乘法指令, 第一个操作数寄存器不能与目标寄存器相同]
 
  练习:0x1235 + 3 * 4 - 8,最终结果存放在r0中
 
  mov r1,#3
  mov r2,#4
  mul r0,r1,r2
  sub r0,r0,#8
 
  ldr r1,=0x1235
  add r0,r0,r1
 
4.位运算指令
  AND/ORR/EOR/BIC   Rd,Rn,Rm
 
  AND r0,r0,r1  => r0 = r0 & r1
  ORR r0,r0,r1,lsl #4 => r0 = r0  | (r1 << 4)
  BIC r0,r0,r1  => r0 =r0 &  ~r1
 
  BIC r0,r0,#3 =>  r0 = r0 & ~3   清除了r0的低两位
 
  问题:如何清除r0的[5-8]位(从第0bit开始),其他位不变?
 
  mov r1,#0xf
 
  #r0 =r0 & ~(r1 << 5)
  bic r0,r0,r1,lsl #5
 
  练习:
  (1)0x12345678,将它的高16bit和低16bit相加结果存放在r0中 
  (2)将r0的14-12位变成101,其他位不变
 
  x= 0x12345678;
  r0 = (x >> 16)  +  (x & 0xffff)
  r0 = r0 & ( ~(7 << 12))  | (0x5 << 12)
 
  -------------------------------------------------------------------------
  ldr r0,=0x12345678
  ldr r1,=0xffff
 
  and r2,r0,r1 //获得低16bit
  //r1 = r0 >> 16
  mov r1,r0,lsr #16//获得高16bit
 
  add r0,r1,r2 //高和低相加
 
  mov r3,#7
  //r0 =r0 & ~(r3 << 12)
  bic r0,r0,r3,lsl,#12 //将r0的[14:12]清除
 
  mov r4,#5
  orr r0,r0,r4,lsl #12 //将r0的[14:12]设置为101
 

5.比较指令

  指令格式:CMP Rd,Rm
  注意:CMP指令比较的结果会自动影响CPSR的NZCV位 (也即不需要添加S来影响标志位)
 
  cmp r0,r1
  cmp r0,#4
  cmp r0,r1,lsl #4
 
 练习:   a:r0
 if(a == 4 || a == 10) [C语言的短路特性]
    a ++;
 
 cmp r0,#4
 cmpne r0,#10
 addeq r0,r0,#1 [条件:最近影响CPSR的N,Z,C,V位]
 
 if(a > b)
   a ++;
 else
   b ++;
  
 cmp r0,r1
 addgt r0,r0,#1
 addle r1,r1,#1


6. 跳转指令

指令格式:B/BL    标签
注意    :
[1].跳转范围 +/- 32M
[2].BL在跳转到指定标签之前,会将pc的值保存到lr寄存器

问题:如何实现长跳转(>32M)?
     直接给pc赋值[将地址直接赋值给pc]

练习:求1 + 2 + ... + 100 累加和?
                             
                             int sum = 0,i;         
                             for(i = 1;i <= 100;i ++)
                             {
                                      sum = sum + i;
                             }
---------------------------------------------------------------------
     mov r0,#0
     mov r1,#1
    
loop:    
     cmp   r1,#100
     addle r0,r0,r1
     addle r1,r1,#1
     ble   loop
loop:            
                   add r0,r0,r1
                   add r1,r1,#1
                   cmp r1,#100
                   ble loop


7.内存访问指令
          
          指令格式:ldr/str  Rd , [Rn]
          
          ldr r0,[r1]  =>将r1保存地址中的4byte读到r0中  => r0  = *r1
          str r0,[r1]  =>将r0的值写到r1保存的地址       => *r1 = r0
          
          data:r0 , p:r1
          
          data = *p;    =>ldr r0,[r1] =>读内存
          *p   = data;  =>str r0,[r1] =>写内存
          
          ----------------------------------------------------------------------------------------------
          常用的写法:
          ldr/str r0,[r1]      =>r0 = *r1 / *r1 = r0
          ldr/str r0,[r1],#4   =>r0 = *r1 && r1 = r1 + 4 / *r1 = r0 && r1 = r1 + 4
          
          ldr/str r0,[r1,#4]   =>r0 = *(r1 + 4) / *(r1 + 4) = r0
          ldr/str r0,[r1,#4]!  =>r0 = *(r1 + 4) && r1 = r1 + 4 / *(r1 + 4) = r0 && r1 = r1 + 4
          --------------------------------------------------------------------------------------------
          data:r0   p:r1
          
          data = *p ++;       =>ldr r0,[r1],#4 
          data = *(p + 4);    =>ldr r0,[r1,#4]
          *p ++ = data;       =>str r0,[r1],#4

  
  思考:将0x20000开始的地址,存放0x00,0x10,0x20,0x30,..,0x90     
 
  int i = 0;
  int *p = (int *)0x20000;
 
  for(i = 0;i < 10;i ++)
  {
          *p++ = i << 4;      
  }
 
  ------------------------------------------------------------------------------------
  mov r1,#0x20000
  mov r0,#0
 
loop:
          mov r2,r0,lsl #4
  str r2,[r1],#4
 
  add r0,r0,#1
  cmp r0,#9
  ble loop
 
 练习:
 (1)向0x20000地址写入0x00,0x11,0x22,....,0x99
 (2)将0x20000开始地址的10个数据拷贝到0x40000
 
    mov r1,#0x20000
          mov r0,#0
                   mov r2,#0
 
loop1:
           str r0,[r1],#4
           add r2,r2,#1
           orr r0,r2,r2,lsl #4 //r0 = r2 | (r2 << 4)
           cmp r0,#0x99
           ble loop1
------------------------------------------------------------------------------
           mov r1,#0x20000
           mov r2,#0x40000
           mov r3,#10
          
loop2:
          ldr  r0,[r1],#4
          str  r0,[r2],#4
          sub  r3,r3,#1
          cmp  r3,#0
          bgt  loop2   
或:
loop2:
          ldr  r0,[r1],#4
          str  r0,[r2],#4
          subs r3,r3,#1
          bgt  loop2   


8. 多寄存器装载指令

LDM/STM{cond}<mode>  Rn{!},{reglist}^

LD:loadr  ST:store   M:many

(1)cond  指令执行的条件(le,get,....),可选
(2)mode  IA,IB,DA,DB

I :基地址寄存器向地址大的方向移动
D :基地址寄存器向地址小的方向移动
A :先操作数据,后操作地址
B :先操作地址,后操作数据

IA:increase  after
IB:increase  before
DA:decrease  after
DB:decrease  before

(3)Rn       基地址寄存器:它的初始值为内存地址
(4)!        最后更新基地址寄存器值
(5)reglist  {r0,r5,r8-r12}
(6)^        寄存器列表中没有pc寄存器的时候,此时操作的寄存器用户模式的寄存器
            对于LDM指令,寄存器列表有pc的时候,除了正常寄存器传送外,还将异常模式SPSR拷贝到CPSR,
            用户异常返回
     
例如:r10的值为0x20000 ,r0:100,r1:200,r2:300,r5:400
    
     stmia r10!,{r0-r2,r5}

|       |               大
|       |r10
|  400  |
|  300  |
|  200  |      
|  100  |0x20000        小
|       |

思考:如何读取上面内存的值到r4-r6,r8?
     ldmdb r10!,{r4-r6,r8}
    
     r8:400  r6:300  r5:200  r4:100
    
注意:
[1]寄存器列表中的寄存器编号,一定是从小到大
[2]大地址对应大寄存器编号,小地址对应小寄存器编号     

9.堆栈操作指令
  ldm<mode>/stm<mode>  sp{!},{reglist}
 
mode:FD,FA,ED,EA     

F:full   满    [压栈结束后,SP指向有效数据的地址]
E:empty  空    [压栈结束后,sp指向无效数据的地址]
A:add    递增  [sp向大地址移动]
D:del    递减  [sp向校地址移动]

ATPCS标准规定堆栈操作的方式为:满递减栈FD

stmfd  sp!,{r0-r12}  //进栈
ldmfd  sp!,{r0-r12}  //出栈

注意:对于堆栈操作,怎么进栈怎么出栈
  

六、常用的ARM伪指令


ARM伪指令:编译器设计,目的是为了操作灵活,编译器最终会将其翻译成ARM核所能理解的指令      
          
_start:
          ldr r0,=0x12345678   //将0x12345678装载到r0
          ldr r1,=lable        //将lable标签表示的地址装载到r1
          ldr r2,lable         //将lable标签表示的地址内容装载到r2
          adr r3,lable         //将pc  +/-  label所在的偏移量

lable:
          .word 0x11223344
          
Disassembly of section .text:

00020000 <_start>:
   20000:      e59f000c      ldr      r0, [pc, #12] ; 20014 <lable+0x4>
   20004:      e59f100c      ldr      r1, [pc, #12] ; 20018 <lable+0x8>
   20008:      e59f2000     ldr      r2, [pc, #0 ]  ; 20010 <lable>
   2000c:       e24f3004     sub    r3, pc, #4

00020010 <lable>:
   20010:      11223344     teqne r2, r4, asr #6
   20014:      12345678     eorsne         r5, r4, #125829120 ; 0x7800000
   20018:      00020010     andeq          r0, r2, r0, lsl r0                 
      
思考:如何知道当前代码是在IRAM运行还是在DDR2运行的基地址?
                    adr r3,_start
                    如果是在IRAM运行:r3 -> 0x20000
                    如果是在DDR2运行:r3 -> 0x2000,0000                    
  

七 、汇编与C语言混合编程

 
(1)ATPCS标准规定

[1]参数传递
   参数小于等于4个时候,通过r0~r3来传递
   参数大于4个时候,前面4个参数通过r0~r3传递,后面参数通过压入栈中传递

[2]函数返回值带回
           函数返回值小于32bit的时候,通过r0带回 ,如果大于32bit,则通过r0,r1带回,依次类推
          
注意:汇编在调用C语言代码,必须先设置SP         
 
                     

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值