朝小闇的博客

海上月是天上月,眼前人是心上人

嵌入式系统笔记整理(一)

ARM汇编语言官方手册:链接:https://pan.baidu.com/s/1k-drPZBD41hSIXzzk4JA2Q 提取码:vjyf

1.计算机组成

  1. 嵌入式系统:以应用为中心,以计算机技术为基础,软硬件可裁剪,适应应用系统对功能、可靠性、成本、体积、功耗等严格要求的专用计算机系统。

  2. 计算机系统:

    1. 冯.诺伊曼结构(普林斯顿结构):计算机按照程序顺序执行程序(指令),程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置;

      组成:

      • CPU(中央处理单元) = 算术逻辑单元(运算器) + 控制电路
      • 存储器 Memory
        • ROM: Read Only Memory 只读存储器
        • RAM: Random Access Memory 随机访问存储器 “R/W”
      • 输入输出设备
    2. 哈佛架构(Harvard Architecture):是一种将程序存储和数据存储分开的存储器结构,数据存储器与程序存储器采用不同的总线。

  3. 各组件通信方式:

    1. 全双工:同一时刻一方即可以发送数据给对方,又可以从对方接收数据;
    2. 半双工:同一时刻一方只能发送数据给对方,或者是从对方接收数据;
    3. 单工:任意时刻 一方只能发送数据给对方,或者是从对方接收数据;
    • 总线bus:计算机系统内部各组件之间是通过“总线”的来通信的;
    • 总线特点:
      • 广播式:多个部件可以同时从总线上接收相同的信息;
      • 系统瓶颈:任意时刻只能有一个设备向总线发送信息;
    • 总线按功能分:
      • 数据总线(DB): 双向,宽度差别8bits/16bits/32bits
      • 地址总线(AB): 单向,CPU发地址,宽度与寻址空间有关 24bit==>16MB
      • 控制总线(CB): 命令和状态
    • 总线按位置分:
      • 片内总线
      • 系统总线
      • 通信总线(I/O总线)

2.ARM Cortex M4体系结构

2.1 Cortex M4总线接口

使用哈佛架构,共有三套总线。

总线:

  • Icode总线:用于访问代码空间的指令instruction, 32bits
    • 访问的空间为: 0x0000 0000 ~ 0x1FFF FFFF (512M);
    • 每次取4字节;
    • 指令只读;
  • Dcode总线:用于访问代码空间的数据data, 32bits
    • 访问的空间为: 0x0000 0000 ~ 0x1FFF FFFF(512M);
    • 非对齐的访问会被总线分割成几个对齐的访问;
    • “4字节对齐”:地址必须为4倍数;
  • System总线:用于访问其他系统空间。如: 各硬件控制器,GPIO……
    • 访问空间为 0x2000 0000 ~ 0xDFFF FFFF 和 0XE010 0000 ~ 0xFFFF FFFF;
    • 非对齐的访问会被总线分割成几个对齐的访问;
    • I/O总线,用于访问芯片上的各外设寄存器的地址空间的;

2.2 Cortex M4工作状态(处理器状态)

ARM公司设计的CPU,可以支持两种指令集:

  • ARM指令集:32bits,功能比较强大,通用;
  • Thumb指令集:
    • thumb 16bits, 功能也强大(Cortem M4只支持Thumb指令);
    • thumb-2 32bits,功能强大,增加了不少专用的DSP指令;

处理器状态:执行某种指令集即处于其状态,Cortem M4只支持Thumb指令即只存在Thumb处理器状态。在状态寄存器中专门有1bit表示处理器状态,1为Thumb,0为ARM。

2.3 Cortex M4寄存器

通用寄存器:没有特殊用途

  • R0~R7: 所有thumb thumb-2都可以访问;
  • R8~R12: 只有少量的thumb指令可以访问,thumb-2都可以访问;

专用寄存器:有专门用途

  • R13(SP):栈顶指针寄存器

    • 堆栈区用于过程PROC保护现场和恢复现场;
    • 定义宏difine没有保护现场和恢复现场的额外开销;
    • Cortex M4有两个堆栈:
      • MSP:主堆栈指针;
      • PSP:进程堆栈指针;
      • 可以把操作系统堆栈和用户堆栈区分;
  • R14(LR):链接寄存器

  • 执行过程调用时用来存储返回地址,即该条指令的下一条指令的地址;

  • R15(PC):程序计数器

    • 保存下一条要执行的指令地址,在取指后自动增加到下一条指令地址;
    • 一般常将LR入栈保存,再用PC取出;
  • xPSR:程序状态寄存器

    • 保存程序运行过程中的一些状态,主要分为三类:

      • 应用状态寄存器 APSR:计算结果的标志——N Z C V Q;
      • 中断状态寄存器 IPSR:中断的编号 Exception Number 8bits;
      • 执行状态寄存器 EPSR:执行状态,如: Thumb/ARM;

      image-20210302110046157

    • 不是所有指令都会影响标志位,如MOV、ADD不影响,MOVS、ADDS影响;

    • 标志:

      • N:负数标志,如果xPSR.N == 1,表示上一条指令的操作结果为负数;

      • Z:零标志,结果所有bit位都为0,则xPSR.Z == 1,否则xPSR.Z == 0;

      • C:借位或进位标志:(进位是指32bit进位到33bit)

        • 进位:在做加法运算时,产生进位则C == 1;
        • 借位:在做减法运算时,产生借位则C == 0,R0 = 2,R1=-2,(R0=0x00000002,R1=0xFFFFFFFD)所以SUBS R4,R0,R1有借位,xPSR.C == 0;
        • 减法实质是加上此数的负数;
      • V:溢位,和C不同

        • 看最高位和次高位C标志的异或结果,1则溢出;
      • Q:饱和标志

        • 若数据没有超出最大范围允许值太多,就只为最大值;
      • T:状态标志:

        • ARM状态 xPSR.T == 0;
        • Thumb状态 xPST.T == 1;
      • ICI/IT:

        • IT:IF-THEN位,它们是if-then指令的执行状态位(最多4条);
        • ICI:Interruptible-Continuable Instrument 可中断-可继续指令位
          • 如果执行LDM/STM操作(对内存进行批量拷贝)时,数据没有操作完时,产生一个中断,中断的优先级很高,必须要打断这条指令的执行,这种情况,就需要记录被打断的寄存器的编号,在中断响应之后,处理器返回由该位指向的寄存器,并恢复操作,继续执行拷贝。

3.ARM指令系统(通用指令32位)

3.1 汇编指令类别

  1. ARM汇编指令(ARM公司定):

    • 一条汇编指令唯一对应一条机器指令;
    • 由操作码+操作数组成,如MOV R0,R1,MOV为操作码,R0和R1分别为第一、二操作数;
  2. 伪指令(由编译器厂商定的,keil环境下有keil的伪指令,gnu环境有gnu的伪指令)

    keil环境下常用的伪指令:

    • 顶格写的是标号,标号表示一个地址;

    • int a = 5;

      • a DCD 5 //DCD X : 分配4bytes空间并且把X的值,填入此处;
    • int b[10];

      • b SPACE 40 //SPACE X : 在此处开辟 X 个字节的空间,内容不定;
    • int c[10] = {2,3};

      • ```asm
        c
        DCD 2
        DCD 3
        SPACE 8*4
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38

        `start.s`:

        - EQU 指令,为符号赋值,相当于#define,此处是为stack_size赋值0x200,意为分配栈大小为2^9=512bit
        - AREA 指令,标记一节开始,对节/段进行命名并设置其属性,属性在名称后以","分割,如下方即对该节命名为mystack并设置DATA、READWRITE属性
        - DATA意为包含数据而不包含指令,READWRITE是缺省值即默认选项
        - SPACE DCD区别,前者申请一片内存空间但不赋初值,DCD申请一个字32bit的内存空间并赋初值
        - PRESERVE8设置代码保持堆栈8字节对齐模式,不需要时带参数FALSE
        - ALIGN表示对齐代码
        - 顶格写的标号也就是变量,标号一定要顶格写,表示一个地址
        ```asm
        ; define a final named stack_size with 512bit(0x200)
        stack_size EQU 0x200
        ; define segment mystack with DATA/READWRITE attribute
        AREA mystack,DATA,READWRITE
        stack_start
        SPACE stack_size
        stack_end
        ; end a segment or start of the segment
        ; set 8bit align mode for code
        PRESERVE8

        ; define vectors
        AREA RESET,DATA,READONLY
        vectors
        ; define a stack-top point with a 4bit space first
        DCD stack_end
        ; define a begin code point with a 4bit space
        DCD test_start
        vectors_end

        ; define a segment for code with align 2^3(8bit)/must
        AREA mycode, CODE,READONLY,ALIGN=3
        test_start
        MOV R1,#1
        ; while(1), B means jump, . means local
        B .
        END
        image-20210309094837149
    • 0x08000000是代码区开始部分,然后由中断向量vectors起始,0x20000000是堆栈区开始部分,由于栈大小最初定义为0x200即512bit,所以DCD stack_end即栈顶指针指向0x20000200;

    • 其次是代码开始段,DCD test_start指向地址为0x08000009,此处是因为最后一个字节的最后两位(DCD分配四个字节,即每增加一个DCD定义增加四个字节,而地址显示最后两位只能增加到3,所以用不到;如上,代码开始处左边地址必须是4的倍数增加,此处是8开始,无论是4或8或12……都和最后两位没关系)没有用到(前面被DCW使用),所以最后一位用于标识ARM或Thumb指令,即异或1,所以最后一个字节是9,实质值是0x08000008即代码段开始部分;

    • 由图地址知:MOV等指令占4个字节,而B跳转指令占2个字节;

3.2 ARM指令的格式

1
2
<opcode>{<cond>}{S} <Rd>, <operand1> {, <operand2>}

  • <>内的必须要的 {} 可选的;

  • opcode:operator code 操作码,指令助记符,表示哪条指令;

  • cond:condition 条件。该指令执行的条件。如果省略不写,则表示该条件指令无条件执行(必须执行);

  • 条件码:

    • 条件码 含义 xPSR中的标志位
      EQ Equal(相等) Z==1
      NE Not Equal(不相等) Z==0
      CS/HS Carry Set (C == 1)
      unsigned Higher or Same
      >=
      a >= b
      a - b无须借位。=> a >= b
      “无须借位” 表示 做减法时 不需要借位
      C == 1
      C == 1表示在做减法时,没有借位。
      C == 1
      CC/LO Carry Clear(C == 0)
      unsigned Lower
      <
      a < b
      a - b就需要借位(C == 0)
      C == 0
      MI MInous(负数)
      a < b
      a - b < 0(N == 1)
      N == 1
      PL Positive or Zero(非负数)
      a >= b
      N == 0
      VS V Set(溢出) V == 1
      VC V Clear(没溢出) V == 0
      HI unsigned HIgher
      a > b
      a - b > 0
      a - b 没有借位(C == 1)并且结果不为0(Z == 0)
      (C == 1) && (Z == 0)
      LS unsigned Lower or Same
      a <= b
      a - b <= 0
      (C == 0) || (Z == 1)
      GE signed Greater or Equql
      >=
      N == V
      LT Less Than
      <
      N != V
      GT Greater Than
      >
      (N == V) && (Z == 0)
      LE Less than or Equal
      <=
      (Z==1) || (N != V)
  • S:Status 表示该指令执行结果是否影响xPSR(程序状态寄存器)的标志位

    • MOV R0, #0:不会影响任何状态标志
    • MOVS R0, #0 :会影响状态标志位
    • CMP, CMN, TEQ 等不加S也影响状态标志位
  • Rd:Register Desatiation 目标寄存器,用来保存运算的结果

  • operand1:第一个操作数

  • operand2:第2个操作数(有些指令没有第二个操作数)

  • 操作数:

    • 立即数(常量表达式),立即数的生成有要求,一般使用LDR R0, =0x20001000语句获取地址;

    • Rm 寄存器,操作数可以是一个寄存器;

    • Rm,shift 寄存器移位方式,操作数可以是一个寄存器加移位方式:

      • LSL #n Logic逻辑移位,无论是左移还是右移,空出的位,都补0
        LSR #n Logic Shift Right 逻辑右移n位,在移出的位为0的情况下,
        LSL #n 就相当于在原寄存器值 乘以 2的n次方
        LSR #n 就相当于在原寄存器值 除以 2的n次方
        ASR #n 算术右移n位
        算术移位,不应该改变它的符号。
        最高n位补符号位的值
        ASL #n 无此方式
        ROR #n ROtate Right 循环右移
        把右边移的n位,补到最左边
        RRX 带扩展的循环右移1位
        带C(xPSR.C) 那个位
        type Rs type为上述移位方式的一种,如: LSL, LSR,ASR, ROR, RRX…
        Rs偏移量寄存器,低8位有效,要移的bit位数保存在Rs中

4.ARM指令

  1. LDR/STR:加载/存储

    • 把数据从存储器 -> 寄存器 加载 Loader LDR
    • 把数据从寄存器 -> 存储器 存储 Store STR
    1
    2
    3
    4
    LDR{cond} {S}	{B/H}	Rd, <地址>
    STR{cond} {B/H} Rd, <地址>SSS
    cond 条件
    S 有符号
    • 地址确定方式: 基址寄存器 + 偏移量

      • [Rn] Rn 加载或存储后
        [Rn, 偏移量] Rn + 偏移量 Rn值不变
        [Rn, 偏移量]! Rn + 偏移量 Rn值+偏移量
        [Rn], 偏移量 Rn Rn值=Rn + 偏移量

    实测:

    • 寄存器访问地址:

      1
      2
      3
      4
      test_start
      MOV R0,#3
      LDR R1,=0x20001000 ;是立即数赋值
      STR R0,[R1]
    • char ch =0x80; //编译时刻或运行时刻,为ch分配一个存储器空间 0x2000 1000
      int a;    //编译时刻或运行时刻,为a分配一个存储器空间  0x2000 1004
      a =    ch;
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      ```asm
      test_start
      MOV R0,#0x80
      LDR R1,=0x20001000
      STRB R0,[R1]

      LDRSB R2,[R1]
      STR R2,[R1,#4]!
      ;改变寄存器值
    • 将R0-R3放到存储器单元0x20000200开始的递减连续单元存放,然后再恢复:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      test_start
      MOV R0,#1
      MOV R1,#2
      MOV R2,#3
      MOV R3,#4
      LDR R4,=0x20001000
      STMIA R4!,{R0-R3}

      MOV R0,#10
      MOV R1,#20
      MOV R2,#30
      MOV R3,#40
      LDMDB R4!,{R0-R3}
  2. PUSH/POP:压栈/出栈

    • 压栈:PUSH <reglist>

    • 出栈:POP <reglist>

    • 堆栈有四种类型:

      • EA: 空递增;
      • FA: 满递增;
      • ED: 空递减;
      • FD: 满递减:ARM采用的堆栈形式是FD满递减堆栈;
      • A:Add递增,D:Dec递减;
      • SP栈顶指针,指向F:Full满堆栈,E:Empty空堆栈;
  3. 数据传送指令

    • MOV{cond}{S} Rd, operand2 ;Rd <-- operand2
    • MVN{cond}{S} Rd, operand2 ;Rd <--- ~operand2(取反)
  4. 算术运算: 加减

    • ADD{cond}{S} Rd, Rn, operand2 ;Rd <--- Rn + operand2
    • ADC{cond}{S} Rd, Rn, operand2 ;Rd <--- Rn + operand2 + xPSR.C
    • SUB{cond}{S} Rd, Rn, operand2 ;Rd <--- Rn - operand2
    • SBC{cond}{S} Rd, Rn, operand2 ;Rd <--- Rn - operand2 - !xPSR.C 带借位的减法
    • RSB 逆向减法指令:
      • RSB{cond}{S} Rd, Rn, operand2 ;operand2 - Rn -> Rd
      • RSC{cond}{S} Rd, Rn, operand2 ;operand2 - Rn - !xPSR.C -> Rd 带借位的逆向减法
  5. 逻辑运算指令 (按位)

    • AND{cond}{S} Rd, Rn, operand2 ;AND 与, Rn & operand2 -> Rd 按位与

    • ORR{cond}{S} Rd, Rn, operand2 ;OR 或, Rn | operand2 -> Rd 按位或

    • EOR{cond}{S} Rd, Rn, operand2 ;EOR 异或 Rn ^ operand2 -> Rd 按位异或

    • BIt Clear 位清零,把一个指定寄存器的中,指定的bit位给清掉:

      • BIC{cond}{S} Rd, Rn, operand2 ;Rn & (~operand2) -> Rd把Rn中 operand2中的为1的哪些位置上的bit位清零
      • R0 低4位清零:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      test_start
      MOV R0,#-3
      ;AND R0,R0,#0xfffffff0
      BIC R0,R0,#0xf

      ;357bit清零
      MOV R0,#-3
      BIC R0,R0,#0xf
      ;BIC R0,R0,#0x54
      BIC R0,R0,#(1<<3) | (1<<5) | (1<<7)
      • 取寄存器R0中的b7-b10,赋值给R1
      1
      2
      3
      4
      5
      6
      7
      test_start
      MOV R0,#-3
      BIC R0,R0,#0xf
      BIC R0,R0,#(1<<3) | (1<<5) | (1<<7)

      MOV R1,R0,LSR #7
      AND R1,R1,#0xf
  6. 比较指令,并不执行动作,而是将大小放到xPSR.Z状态中

    • CMP Rn, operand2 ;比较Rn与operand2的大小 Rn - operand2
    • CMN Rn, operand2 ;比较Rn与operand2的大小, Rn + operand2(负数比较)
    • TST Rn, operand2 ;Rn & operand2测试Rn中特定的bit位是否为1
    • TEQ Rn, operand2 ;Rn ^ operand2 测试是否相等

for循环实现:

1
2
3
4
5
6
7
8
9
10
test_start
MOV R0,#1 ;i
MOV R1,#0 ;sum
loop_sum
CMP R0,#10
BGT loop_sum_end
ADD R1,R1,R0
ADD R0,R0,#1
B loop_sum
loop_sum_end

C与汇编交互使用:

1
2
3
4
extern int sum_two(int,int);
int sum_three(int a,int b,int c){
return c+sum_two(a,b);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
stack_size EQU 0x200
AREA mystack,DATA,READWRITE
stack_start
SPACE stack_size
stack_end

IMPORT sum_three
EXPORT sum_two

PRESERVE8
AREA RESET,DATA,READONLY
vectors
DCD stack_end
DCD test_start
vectors_end
AREA mycode, CODE,READONLY,ALIGN=3
test_start PROC

MOV R0,#6 ;a
MOV R1,#7 ;b
MOV R2,#2
BL sum_three
B .
ENDP

sum_two PROC
PUSH {R2-R12,LR}
ADD R0,R0,R1
POP {R2-R12,PC}
ENDP
  • C引用汇编过程:
    • 外部引用函数申明:extern int sum_two(int,int);
    • 调用函数:return c+sum_two(a,b);
    • 汇编中申明导出,将该函数调用结果导出:EXPORT sum_two
  • 汇编调用C过程:
    • 汇编申明函数导入:IMPORT sum_three
    • 汇编申明函数调用,注意此处是C中main函数入口的原因:BL sum_three

image-20210323090302541

  • 只存R4-R6是因为只是用了这几个寄存器;
  • 不使用R3是因为R0-R3是系统自定的四个传参寄存器,这四个寄存器要用来下一次传参,所以在函数中间不能再使用;
  • 通过这种模式也可以从C得到汇编语句;
-------- 本文结束 感谢阅读 --------