学习嵌入式的第17天

零.安装交叉编译器

  1. 创建共享目录
  2. 拷贝压缩包(arm-cortex_a9-eabi-4.7.....)
  3. 解压 tar xf ......
  4. 修改环境变量 PATH
  5. 打开 vi ~/.bashrc 末尾添加: export PATH=$PATH:/opt/arm-cortex_a9-eabi-4.7-eglibc-2.1........
  6. 验证 source ~/.bashrc and arm- + Tab键

一. 回忆昨天的内容

三大系统软件的烧录 EMMC 分区 (测试题) [  ] [ubookpak.bin] [uImage] [rootfs_ext4.img] [应用程序 本地库 驱动程序]

bootcmd : 引导 Linux内核 bootargs: 给 Linux内核 传参

setenv bootcmd mmc read 0x48000000 0x800 0x3000 \\; bootm 0x48000000
setenv bootargs root=/dev/mmcblk0p2 init=/linuxrc
console=ttySAC0, 115200 maxcpus=1 lcd=wy070ml tp=gslx680-linux
saveenv

二. ARM体系结构 及 汇编

2.1 什么是 ARM

ARM官网 : https://www.arm.com/

Advanced RISC Machines RISC : 精简指令集 ----> ARM MIPS LA IBM CISC : 复杂指令集 ----> Intal AMD

指令集 架构 soc
ARMV4T ARM7 s3c44b0
ARMV5TE ARM9 s3c2410
ARMV6 ARM11 s3c6410
ARMV7 Cortex-a sp5v210
ARMV8 Cortex-a53 s5p6818
Cortex-r 实时性较强的领域
Cortex-m 以单片机的价格实现32bit的性能

流水线: 三级流水线 取指令 解码指令 执行指令

以下哪种格式执行效率高?

//第一种写法
for(i = 0; i < 10; i++){
    for(i = 0; j < 10000; j++){
        ...
    }
}

//第二种写法
for(j = 0; j < 10000; j++){
    for(i = 0; i < 10; i++){
        ...
    }
}

//第一种执行效率更高

(测试题) CPI 怎么算?

指令周期/周期内执行指令的个数 BL : 带分支的跳转

2.2 ARM编程模型

2.2.1 ARM工作模式

七种工作模式:

  1. SVC : 管理模式, 系统上电, 执行软中断指令
  2. FIQ : 快速中断, 发生了高优先级中断
  3. IRQ中断: 发证了低优先级的中断
  4. Abort中止 : 发生在非法访问存储器时(需对其访问)
  5. Undef未定义 : 遇到了不认识的指令
  6. System : 与用户共用存储器, 需要特权
  7. User用户:应用程序和系统任务运行在用户模式

前5种是异常模式, 后两种是正常模式 前6种是特权模式, 后一种是非特权模式

2.2.2 ARM的工作状态

ARM 状态 : 执行ARM (32bit) 指令时, PC值字对齐 (32位) Tumb 状态: 执行 Tumb (16bit) 指令时, PC值半字对齐 (16位)  访问0x900000002 合法

2.2.3 寄存器的组织结构

寄存器和特殊功能寄存器的区别:

  1. 存放的位置不同:  寄存器位于 ARM core 内部  特殊功能寄存器: 位于 ARM core 外部

  2. 访问的方式不一样  特殊功能寄存器都有特定的物理地址  寄存器只有名字没有地址, C很难访问

* (volatile int *) (0xc001c000) ----> GPIOCOUT

ARM核中一共有 37 个32bit的寄存器, 其中有31个通用寄存器, 命名为r0-r15 r11 (fp, frame pointer 忽略) r13 (sp, stack pointer 重要) r14 (lr, 保存函数的指针) r15 (pc, 保存取指令的位置)

6个状态寄存器: 1个 cpsr 程序状态寄存器, spsr是cpsr的备份

[4 : 0] mode 模式位, 标识当前 ARM核 处于什么模式, 在特权模式下, 可以写该寄存器 [5] T位, 1 --处于 Thumb 状态, 0 -- ARM 状态 [6] F位, 1 -- 禁止FIQ, 0 -- 使能FIQ [7] I位, 1 -- 禁止RIQ, 0 -- 使能RIQ [28] V位, 有符号数据在做运算的时候进位 [29] C位, 运算结果最高位有进位 0xfffffffc + 10 => C = 1 [30] Z位, 运算结果为0, z位为1 [31] N位, 运算结果为负 =1 , 为正 = 0

每一种模式下, 只能访问其中的一个子集.

2.2.4 异常向量与异常向量表

裸板程序:

main(){
    xxx_init();
    ...
    while(1){
        //其中为周期性事件
    }
}
xxx //+ 异常处理

ARM Core 支持 7 种工作模式, 2 种异常 7 种异常会导致 ARM 进入 5种异常模式:

  1. reset: (复位异常) 按下复位键
  2. undef: (未定义的异常) 执行到不认识的指令
  3. swi : (软中断异常) 执行汇编指令 swi
  4. prefetch abort : (预取值终止异常) 取指令时进行了非法的存取器访问
  5. data abort : 取数据时进行了非法访问存储器
  6. irq: (中断异常) 按键中断
  7. fiq : (快速中断) 高优先级中断发生时触发

(测试题)当异常产生时, ARM核 硬件上自动做 4 件事:

  1. 备份 cpsr   spsr_<mode> = cpsr
  2. 修改 cpsr
    1. 切换为 ARM 工作模式 CPSR[T]
    2. 切换为 异常工作模式 CPSR[4:0]
    3. 禁止中断 CPSR[I] = 1, CPSR[F] = 1
  3. 保存返回地址到 LR_\<mode>
  4. 给 PC 赋值  reset 异常 ---> pc = 0x00...fiq 异常 ---> pc = 0x1C

从异常返回的时候, 软件上需要处理:

  1. 恢复 cpsr
  2. 返回被打断的位置
fun(){
    ...
    return;
}

main(){//ARM 保存 i++ 的值
    func(); // i++指令的地址 ----> LR
    i++;
}

2.2.5 ARM 支持的数据类型

byte half word word double word 对齐方式:  4 字节对齐, 存储的地址可以被 4 整除

struct stu{
    int a;
    char c;
    short d;
    int b;
};
//sizeof (struct stu) = 12;

大小端的判断:  int a = 0x12345678;  字节序:

大端 小端
地址
0x00 12 78
0x01 34 56
0x02 56 34
0x03 78 12

权重最大的在低地址叫大端, 编程实现ARM处理器大小端的判断?

//endian.c
//法1:
#include<stdio.h>

int main(void){
    int a = 0x12345678;
    char * p = (char *)&a;

    if(0x12 == *p)
        printf("BIG-Endian");
    else
        printf("Little-Endian");

    return 0;
}

  使用交叉编译器 见:零.安装交叉编译器

//法2:
#include<stdio.h>

union{
    int a;
    char c[4];
} endian;

int main(void){
    int i = 0;
    endian.a = 1;

    if(endian.c[0] == 1)
        printf("Little Endian\n")
    else
        printf("BIG Endian\n");

    return 0;
}

三. ARM 汇编

3.1 基本概念

 arm汇编语言(又叫助记符语言), 大多数系统设计的主要工作都集中在编译代码(C), 一般不需要了解指令集, 但是以下情况会用到汇编:

  1. 嵌入式系统中需要初始化和中断服务程序
  2. 所有的系统都需要调试, 可能会用到汇编调试, 可以用汇编语言来提升系统性能
  3. 有些指令编译器没有办法产生, 只能通过汇编指令完成

3.2 ARM 汇编指令

ARM 汇编指令的特点

  1. 大多数汇编指令时单周期的
  2. 大多数ARM汇编指令可以条件执行 PPT中条件码看一下

3.2.1 分支跳转指令

语法规则: b {cond} <目标地址> 类似于 goto b main b 指令应用实例

start:
    cmp r0, r1
    beq not_copy
    move r0, r1
not_copy:
    b.

语法规则: bl {cond} <目的地址> //函数调用 l 是 Link bl main //在向 main 跳转之前, 记录下下一条指令的地址到 LR 注意: 目标地址的范围:土32M <cond> 为指令的条件码, EQ, NE, CC, CS, LT... <target> 为指令跳转的目标地址 为什么为32M: 将指令中的 24位 带符号的补码立即数拓展为 32位 , 将 32位 的数 左移两位, 所得到的数值加到PC寄存器中, 得到目的地址。

指令编码格式: 31    28   25   24   23    0   cond   B   L     target

bl指令应用实例:

start:
    mov ro, #1
    mov r1, #2
    bl DoAdd
    b .

DoAdd:
    add r0, r0, r1
    mov pc, lr

bx指令编码格式: bx or blx or b {1} {cond} <target> or b {1} {cond} <Rn> x 带状态切换 Rn, 一定是通用寄存器 r0~r15 注意:跳转的范围不受限制(0~4G)

bx指令应用实例:

.code 32
ARM_CODE:
    ADR RO, THUMB_CODE + 1
    BX R0

.code 16
THUMB_code:
    ADR R0, ARM_code
    BX R0

3.2.2 数据处理指令

1) 移位指令

LSL : logical shift left 逻辑左移, 空出的位填0 <Rn>, LSL #<shift_imm> or <Rn>, LSL, <Rs> 移位的位数由Rs寄存器的bit[7:0]

LSR : logical shift right 逻辑右移, 空出的位填0 <Rn>, LSR #<shift_imm>

ASR : Arithmetic shift right 算术右移, 最高位补符号位

ROR : 循环右移

RRX: 带扩展位的循环右移, 新的最高位由 CPSR 的C位填充, 而且更新C位

2) 数据传输指令

mov {cond} {s} <Rd>, <oprand> cond : 可以条件执行 s : 操作结果影响 cpsr 的 NZCV 位 mov R0, #10 @ r0 = 10or movs R0, #10 @ r0 = 10, N = 0 Z = 0 Rd 一定是通用寄存器 operand : 有三种表现形式  mov r0, #10 注意立即数的合法性问题, 存在一个 8bit 的立即数, 循环右移偶数位得到, 那么这个立即数就是合法的.   mov r0, r2 @ r0 = r2   mov r0, r2, ls1 #2 @r0 = r2 << 2

moveqs r0, r1 @ if(CPSR.Z == 1) 
              movs r0, r1 
              @r0 = r1 if(r0 == 0) Z = 1 else Z = 0
              @N = r0[31]

mvn {cond} {s} <Rd>, <oprand> 将 operand 表示的数据的反码传送到目标寄存器 r0 中, 并根据操作的结果影响 CPSR 的 NZCV 位.

3.2.3 算数运算指令

add {cond} {s} <Rd>, <Rn>, <oprand> cond 可以条件执行 s 操作结果影响 NZCV 位   N = r[31]   if(Rn == 0) Z = 1 else Z = 0   最高位有进位 C = 1 else C = 0

Rd, Rn 一定是通用寄存器 operand 有三种表现形式

add r0, #10 add r0, r1, r2 add r0, r1, r2, ls1 #3 @ro = r1+r2*8

adc {cond} {s} <Rd>, <Rn>, <oprand>   eg : adc r0, r1, r2 @r0 = r1 + r2 + C   eg : 64bit 加法运算

被加数 r0 r1
加数 r2 r3
r0 r1

adds r1, r1, r3 addc r0, r0, r2

sub {cond} {s} <Rd>, <Rn>, <oprand> s 注意对C位的影响 Rn > Operand C = 1 else C = 0

减法指令: sbc {cond} {s} <Rd>, <Rn>, <oprand>sbc r0, r1, r2 @ r0 = r1 - r2 -NOT(C)

 eg: 64位 减法运算

被减数 r4 r5
减数 r6 r7
r8 r9

subs r9, r5, r7 @if(r5 > r7) C = 1 else C = 0 sbc r8, r4, r6 @ r8 = r4 - r6 - NOT(C) @出现了负负得正的效果

逆向减法指令: rsb {cond} {s} <Rd>, <Rn>, <oprand>   eg : rsb r0, r1, r2 @ r0 = r2 - r1   eg : rsb r0, r1, #8 @ r0 = 8 - r1   eg : rsb r0, #8, r1 @ 不对, 语法错误   eg : rsb r0, r0, #0 @ 取相反数

练习: 求 1 到 10 的 累加和, 结果保存到 r0中

@add.s

.text
.code 32
.global _start
_start:
    mov r1, #10
    mov r0, #0


sum:
    add r0, r0, #1
    sub r1, r1, #1
    cmp r1, #0

    beq sum

    b .

.end

作业: 对照 数据手册 和 电路原理图 读懂01工程文件


本文章使用limfx的vscode插件快速发布