您当前的位置: 首页 > 

韦东山

暂无认证

  • 0浏览

    0关注

    506博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

5_LED程序涉及的编程知识

韦东山 发布时间:2022-01-13 16:02:20 ,浏览量:0

第五章 LED程序涉及的编程知识 5.1 ARM架构的简单介绍

​ 目前IMX6UL是使用Cortex-A7架构,本小节简单介绍一下Cortex-A7架构的基础知识,比如运行模式、寄存器组等。

​ 参考资料:

  • 文件原名DEN0013D_cortex_a_series_PG.pdf
  • 文档全名ARM® Cortex™-A Series Version: 4.0 Programmer’s Guide.pdf
  • 文档所在目录: 资料光盘 00_UserManual\参考资料\Arm架构参考资料\ARMv7编程手册(DEN0013D_cortex_a_series_PG).pdf
  • 参考章节: 3: ARM Processor Modes and Registers
5.1.1 运行模式

​ Cortex-A7架构的运行模式有9种,分别为User、Sys(System)、FIQ、IRQ、ABT(Abort)、SVC(Supervisor)、UND(Undef)、MON(Monitor)、Hyp模式,如下表:

模式描述User用户模式,非特权模式,大部分程序运行的 时候就处于此模式Sys(System)系统模式,用于运行特权级的操作系统任务FIQ快速中断模式,进入 FIQ 中断异常IRQ一般中断模式ABT(Abort)数据访问终止模式,用于虚拟存储以及存储保护SVC(Supervisor)超级管理员模式,供操作系统使用UND(Undef)未定义指令终止模式MON(Monitor)用于安全扩展模式Hyp用于虚拟化扩展

​ 除了User模式属于非特权模式,其它8种处理器模式都是特权模式。

​ 运行模式可以通过软件进行任意切换,也可以通过中断或者异常来进行切换。大多数的程序都运行在用户模式,用户模式下是不能访问系统所有资源的,有些资源是受限的,要想访问这些受限的资源就必须进行模式切换。但是用户模式是不能直接进行切换的,用户模式下需要借助异常来完成模式切换,当要切换模式的时候,应用程序可以产生异常,在异常的处理过程中完成处理器模式切换。

5.1.2 寄存器组

​ 本节我们要讲的是 Cortex-A7 内核寄存器组,而不是芯片外设寄存器。

​ 上一小节我们讲了 Cortex-A7 有 9 种运行模式,每一种运行模式都有一组与之对应的寄存器组,如下图:

​ 浅色字体是与 User 模式所共有的寄存器,浅蓝色背景是各个模式所独有的寄存器,即在所有的模式中,低寄存器组(R0~R7)是共享同一组物理寄存器的,只是一些高寄存器组在不同的模式有自己独有的寄存器,比如 FIQ 模式下 R8~R14 是独立的物理寄存器。

​ 如果某个程序处于 FIQ 模式下访问寄存器 R13(SP),那它实际访问的是寄存器 SP_fiq

​ 如果某个程序处于 SVC 模式下访问寄存器 R13(SP),那它实际访问的是寄存器 SP_svc

9 种运行模式的寄存器合计有34个,可以分为:

  1. 未备份寄存器,即 R0~R7
  2. 备份寄存器,即 R8~R14
  3. 程序计数器 ,即 R15
  4. 程序状态寄存器

下面一一介绍以上4类寄存器。

5.1.2.1 未备份寄存器

​ 未备份寄存器指的是 R0R7,因为在所有的运行模式下R0R7寄存器都是同一个物理寄存器,在不同的模式下,R0R7寄存器中的数据就会被破坏,所以R0R7寄存器并没有被用作特殊用途。

5.1.2.2 备份寄存器

​ 备份寄存器中的 R8~R12 寄存器有两种物理寄存器,在快速中断模式下(FIQ)它们对应着Rx_irq(x=8~12)物理寄存器,其他模式下对应着 Rx(8~12)物理寄存器。FIQ 是快速中断模式,这个中断模式要求快速执行!因为 FIQ 模式下的 R8~R12 是独立的,因此中断处理程序可以不用执行保存和恢复中断现场的指令,从而加速中断的执行过程。

​ 备份寄存器 R13(SP) ,也叫栈指针,有 8 个物理寄存器,其中一个是User和Sys模式共用的,剩下的 7 个分别对应 7 种不同的模式。

​ 备份寄存器 R14(LR) ,也叫链接寄存器,有 7 个物理寄存器,其中一个是User、Sys和Hyp模式所共有的,剩下的 6 个分别对应 6 种不同的模式,主要有如下用途:

​ 使用 R14(LR)来存放当前子程序的返回地址,如果使用 BL 或者 BLX来调用子函数的话,R14(LR)被设置成该子函数的返回地址,在子函数中,将 R14(LR)中的值赋给 R15(PC)即可完成子函数返回,如mov pc,lr

5.1.2.3 程序计数器

​ 程序计数器 R15(PC),保存着当前执行指令地址值加 8 个字节

​ 因为ARM处理器是三级流水线:取指->译码->执行,循环执行。比如当前正在执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出存放在 R15(PC)中,即 R15(PC)总是指向当前正在执行指令地址再加上 2 条指令的地址,对于 32 位的 ARM 处理器,每条指令是 4 个字节,

​ 所以R15(PC) = 当前执行指令地址 + 8个字节

5.1.2.4 程序状态寄存器

​ 程序状态寄存器PSR可以分成当前程序状态寄存器CPSR与备份程序状态寄存器SPSR。

​ 所有运行模式都共用一个 CPSR 物理寄存器,因此 CPSR 可以在任何模式下被访问,该寄存器包含条件标志位、中断禁止位、当前运行模式标志等一些状态位以及一些控制位。但是所有运行模式都共用一个 CPSR 必然会导致冲突,因此除了 User 和 Sys 模式以外,其他 7 个模式都配备一个专用的物理状态寄存器,叫做 备份程序状态寄存器(SPSR),当特定异常中断发生时,SPSR用来保存CPSR的值,当异常退出以后可以用 SPSR 中保存的值来恢复 CPSR。

​ 由于 SPSR 是 CPSR 的备份,因此 SPSR 和 CPSR 的寄存器结构相同,如下图:

​ N(bit31):当两个有符号整数运算(补码表示)时,结果用N表示,N=1/0 表示 负数/正数

​ Z(bit30):对于 CMP 指令,Z=1 表示进行比较的两个数大小相等

​ C(bit29):

​ 在加法指令中,当结果产生了进位,则C=1,表示无符号数运算发生上溢,其它情况下 C=0

​ 在减法指令中,当运算中发生借位,则C=0,表示无符号数运算发生下溢,其它情况下 C=1

​ 对于包含移位操作的非加/减法运算指令,C 中包含最后一次溢出的位的数值

​ 对于其它非加/减运算指令,C 位的值通常不受影响

​ V(bit28):对于加/减法运算指令,当操作数和运算结果表示为二进制的补码表示的带符号数时,V=1 表示符号位溢出,通常其他位不影响 V 位

​ Q(bit27):仅 ARM v5TE_J 架构支持,表示饱和状态,Q=1/0 表示累积饱和/累积不饱和

​ IT1:0 和 IT7:2一起组成 IT[7:0],作为 IF-THEN 指令执行状态

​ J(bit24)和T(bit5):控制指令执行状态,表明本指令是ARM指令还是Thumb指令,如表

JT描述00ARM01Thumb11ThumbEE10Jazelle

​ GE3:0:SIMD 指令有效,大于或等于

​ E(bit9):大小端控制位,E=1/0 表示大/小端模式

​ A(bit8):禁止异步中断位,A=1 表示禁止异步中断

​ I(bit7):I=1/0 代表 禁止/使能 IRQ

​ F(bit6):F=1/0 代表 禁止/使能 FIQ

​ M[4:0]:运行模式控制位,如表

M[4:0]运行模式10000User 模式10001FIQ 模式10010IRQ 模式10011Supervisor(SVC)模式10110Monitor(MON)模式10111Abort(ABT)模式11010Hyp(HYP)模式11011Undef(UND)模式11111System(SYS)模式 5.2 汇编与机器码、汇编指令

参考资料:

  • 文件原名DDI0406C_d_armv7ar_arm.pdf
  • 文档全名ARM® Architecture Reference Manual ARMv7-A and ARMv7-R edition
  • 文档所在目录: 资料光盘 00_UserManual\参考资料\Arm架构参考资料\ armv7 ar架构参考手册 学习CPU架构、内存及系统架构(DDI0406C_d_armv7ar_arm).pdf
  • 参考章节: A5: ARM Instruction Set Encoding

根据指令复杂度来区分,所有CPU可以分为2类:

  1. CISC

    复杂指令集计算机,Complex Instruction Set Computer,比如x86

  2. RISC

    精简指令集计算机,Reduced Instruction Set Computing,比如ARM,RISC-V

比如,对于加法运算:a = a + b,它涉及4个步骤的操作:读出a,读出b,计算a+b,把结果写回a。

  1. 使用CISC(复杂指令集计算机,比如x86)提供的加法指令,只需要一条指令即可完成这4步操作。当然,这一个指令需要多个CPU周期才可以完成。

  2. 而RISC不提供“一站式”的加法指令,需调用四条单CPU周期指令完成两数相加:内存a加载到寄存器,内存b加载到寄存器,两个寄存器中数相加,寄存器结果存入内存a

​ ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:

  1. 对内存只有读、写指令

  2. 对于数据的运算是在CPU内部实现

  3. 使用RISC指令的CPU复杂度小一点,易于设计

5.2.1 汇编与机器码

​ 上面的例子中,数值a原来是保存在内存里的,执行了某条指令后,它的值被读入内存,那问题来了:

  1. 什么指令,可以让CPU从内存里把数据读进来?

    比如:

mov r0, #addr_a // 把变量a的地址传给CPU寄存器r0
ldr r1, [r0]    // 从r0所指的内存把数值读进CPU寄存器r1
  1. 读进来后,这个数保存在哪里?

    当然是保存在CPU内部了,存在某个寄存器里,上面的代码用寄存器r1来保存该值

  2. 如何处理数据?

    CPU执行加法指令,比如:

add r1, r1, r2 // 在CPU内部,r1=r1+r2
  1. 最终数据怎么写入内存?

    CPU执行指令,比如:

str r1, [r0]   // 将r1的值写入r0所指的内存

​ 上面例子中,mov、add、ldr、str等都是汇编指令,或者说它们是“助记符”──帮助我们记忆的。记忆什么呢?这些指令其实是一个一个数值,我们去记这些数值有难度,所以就用mov表示某个指令的数值,用add表示某个指令的数值,对应的,这些指令的数值就是机器码,即汇编指令是机器码的助记符

ARM指令机器码是有一定格式,如下:

condop1op指令类型not 111100x-数据处理和杂项指令,如MOVnot 1111010-加载/存储指令,如LDR/STRnot 11110110加载/存储指令,如LDR/STRnot 11110111媒体指令(英文:Media instructions)not 111110x-分支指令,如B、BL; 块数据传输指令,如 LDM/STM、POP/PUSHnot 111111x-协处理器指令1111--无条件指令,如BL

​ 下面讲解几种常用的汇编指令。

​ 参考资料: ARM® and Thumb®-2 Instruction Set Quick Reference Card.pdf (ARM指令快速参考卡)

​ 文档所在目录: 资料光盘 00_UserManual\参考资料\Arm架构参考资料\ ARM® and Thumb®-2 Instruction Set Quick Reference Card.pdf

5.2.2 汇编指令

​ 汇编指令的格式,如下:

label:                  
	instruction @ comment

​ label,即标签,表示地址位置,可以通过label得到指令/数据地址

​ instruction,即指令,表示汇编指令或伪指令

​ @ comment,@表示后面是注释,comment表示注释内容

​ 比如:

add:                                
	mov r0, #0 @ 将R0寄存器设置成0

​ 上面汇编代码中,add表示标签,mov r0, #0表示指令,@ 将R0寄存器设置成0 表示 注释

​ 常用的汇编指令一般有mov、bl/b、add/sub、ldm/stm、push/pop等等,下面一一介绍。

5.2.2.1 mov
mov r1, #10  @ 将10赋值给寄存器r1,即r1=10

​ 指令执行过程,如下:

  1. 取指

    ​ 假设从内存的addrA地址取机器码e3a0100a(即mov r1, #10指令)

  2. 译码

    ​ 原来是MOV指令

  3. 执行

    ​ CPU内部寄存器R1等于10

    ​ 其中,机器码e3a0100a,MOV指令各位的解析如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-klSoVHWv-1642059925428)(https://cdn.jsdelivr.net/gh/DongshanPI/HomeSite-Photos@main/IMX6ULL-BareMetal/LED_Program_Knowdge_image006.png)]

​ [31:28]位是条件码0xe;[15:12]位是寄存器R1,即0x1;[12:0]位是立即数10,即0x00a

5.2.2.2 bl
1 bl test_tag
2 mov r1, #10
3 
4 test_tag:
5 	mov r3, #0
6 	mov pc, lr

​ 第1行,跳转到test_tag标签处执行mov r3, #0指令,并且将mov r1, #10指令的地址存储到 LR 寄存器

​ 第6行,返回到mov r1, #10指令地址,并且执行mov r1, #10指令

​ 指令执行过程,如下:

  1. CPU从内存的addrA地址取机器码eb000000(即bl test_tag指令),执行后,PC跳转到test_tag标签位置,即内存的addrA+8地址,从上图可知,其实test_tag标签的地址是mov r3, #0指令的地址。同时自动将内存的addrA+4地址存储在寄存器LR中

  2. CPU从内存的addrA+8地址取机器码e3a03000(即mov r3, #0指令),执行,CPU内部寄存器R3等于0

  3. CPU从内存的addrA+12地址取机器码e1a0f00e(即mov pc, lr指令),执行,PC跳转到内存的addrA+4地址

  4. CPU从内存的addrA+4地址取机器码e3a0100a(即mov r1, #10指令),执行,CPU内部寄存器R1等于10

    其中,机器码eb000000,BL指令各位的解析如下:

​ imm[23:0]是PC值与标签的偏移值除以4,但是此处的偏移值是0,为什么尼?这是因为ARM采用三级流水线的方法,即取指、译码、执行指令。所以当ARM执行addrA地址的bl test_tag指令时,但是PC已经指向addrA+8地址进行取mov r3, #0指令,所以此处的偏移值是0

5.2.2.3 b
1 b test_tag
2 mov r1, #10
3
4 test_tag:
5 	mov r3, #0

​ 第1行,只是跳转到test_tag标签处执行mov r3, #0指令,没有跳转回去执行mov r1, #10指令

​ 指令B与指令BL,大同小异,此处就不一一分析了,可以参数指令BL,它们的区别:是否将B/BL指令的下一条指令的地址存储到寄存器LR,BL指令会存储,B指令不会存储。

5.2.2.4 add/sub
1 mov r1, #10
2 add r2, r1, #4
3 sub r2, r1, #4

​ 第1行,将寄存器r1加上4后,赋值给寄存器r2

​ 第2行,将寄存器r1减去4后,赋值给寄存器r2

​ 指令执行过程,如下:

​ CPU从内存的addrA+4地址取机器码e2812004(即add r2, r1, #4指令),执行后,CPU内部寄存器R2等于14

​ CPU从内存的addrA+8地址取机器码e2412004(即sub r2, r1, #4指令),执行后,CPU内部寄存器R2等于6

​ 其中,机器码e2812004,ADD指令各位的解析如下:

​ [19: 16]位是源寄存器R1,即1;[15: 12]位是目标寄存器R2,即2;[11: 0]位是立即数4,即0x004;

​ 其中,机器码e2412004,SUB指令各位的解析如下:

​ [19: 16]位是源寄存器R1,即1;[15: 12]位是目标寄存器R2,即2;[11: 0]位是立即数4,即0x004;

5.2.2.5 ldr/str
1 mov r0, #400H @ 0x400
2 mov r1, #aH   @ 0xa
3 str r1, [r0]
4 ldr r2, [r0]

​ 第3行,将寄存器R1的值0xa存储到寄存器R0指向的地址0x400

​ 第4行,将寄存器R0指向地址0x400的数据赋值给寄存器R2

​ 指令执行过程,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o1GEe5XB-1642059925431)(https://cdn.jsdelivr.net/gh/DongshanPI/HomeSite-Photos@main/IMX6ULL-BareMetal/LED_Program_Knowdge_image012.png)]

  1. CPU从内存的addrA地址取机器码e3a00b01(即mov r0, #400H指令),执行后,CPU内部寄存器R0等于0x400

  2. CPU从内存的addrA+4地址取机器码e3a0100a(即mov r1, #aH指令),执行后,CPU内部寄存器R1等于0xa

  3. CPU从内存的addrA+8地址取机器码e5801000(即str r1, [r0]指令),执行后,寄存器R1的0xa数据存储到寄存器R0指向的地址0x400,即内存的0x400地址的值为0xa

  4. CPU从内存的addrA+12地址取机器码e5902000(即ldr r2, [r0]指令),执行后,寄存器R0指向的地址0x400的数据存储到CPU内部寄存器R2,即CPU内部寄存器R2等于0xa

    其中,机器码e5801000,STR指令各位的解析如下:

​ [19: 16]位是目标寄存器R0,即0;[15: 12]位是源寄存器R1,即1;

​ 其中,机器码e5902000,LDR指令各位的解析如下:

​ [19: 16]位是源寄存器R0,即0;[15: 12]位是目标寄存器R2,即2;

ldr sp,=0x80200000

​ 这个是一条伪指令,即实际中并不存在这个指令,它会被拆分成几个真正的ARM指令,实现一样的效果,将0x80200000赋值给寄存器sp,即sp=0x80200000

​ 指令执行过程,如下:

​ ldr sp,=0x80200000这条伪指令,被翻译成两条指令来执行,先将0x80200000存储到内存地址addrA+4处,然后通过LDR指令把寄存器SP设置成0x80200000。

​ 如何分析ldr sp, [pc, #-4]指令的机器码e51fd004?读者可以根据上图LDR指令机器码的格式,自行进行分析。温馨提示:imm12[11: 0]位是源寄存器Rn的偏移值。

5.2.2.6 ldm/stm

​ ldm,多数据加载,将某地址的值赋值给某寄存器

​ stm,多数据存储,将某寄存器的值存储到某地址

​ 格式:

ldm{cond} Rn{!}, reglist
stm{cond} Rn{!}, reglist

参数说明:

​ cond:前四个条件是用于数据块操作,后四个条件是用于堆栈操作

​ IA : 每次传送后地址加4,其中寄存器从左到右执行,例如:STMIA R0,{R1,LR} 先存R1,再存LR

​ IB : 每次传送前地址加4,同上

​ DA : 每次传送后地址减4,其中寄存器从右到左执行,例如:STMDA R0,{R1,LR} 先存LR,再存R1

​ DB : 每次传送前地址减4,同上

​ FD : 满递减堆栈

​ FA : 满递增堆栈

​ ED : 空递减堆栈

​ EA : 空递增堆栈

​ Rn:基址寄存器,即寄存器的值是起始地址

​ !:表示最后的地址写回到Rn中

​ reglist:表示寄存器范围,用 , 隔开,如{R1,R2,R6-R9}

​ 数据块操作:

1 ldr r1,=0x10000000     
2 
3 ldmib r1!, {r0,r4-r6}
4 stmda r1!, {r0,r4-r6}

​ 第1行,将起始地址0x10000000赋值给r1

​ 第3行,因为使用ib,所以每次传送前地址加4,具体操作如下:

​ 将0X10000004地址的内容赋值给R0

​ 将0X10000008地址的内容赋值给R4

​ 将0X1000000C地址的内容赋值给R5

​ 将0X10000010地址的内容赋值给R6

​ 由于!,最后的地址写回到R1中,R1=0X10000010

​ 第4行,因为使用da,所以每次传送后地址减4,具体操作如下:

​ 将R6存储到0X10000010地址

​ 将R5存储到0X1000000C地址

​ 将R4存储到0X10000008地址

​ 将R0存储到0X10000004地址

​ 由于!,最后的地址写回到R1中,R1=0X10000000

​ 如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PI07EznT-1642059925434)(https://cdn.jsdelivr.net/gh/DongshanPI/HomeSite-Photos@main/IMX6ULL-BareMetal/LED_Program_Knowdge_image016.png)]

​ 堆栈操作:满递减堆栈

1 ldr sp,=0x80200000
2
3 stmfd sp!, {r0-r2} @ 入栈
4 ldmfd sp!, {r0-r2} @ 出栈

​ 第1行,将0x80200000赋值给sp,作为堆栈的起始地址

​ 第3行,入栈,具体操作如下:

​ 将R2存储到0X80200000地址

​ 将R1存储到0X801FFFFC地址

​ 将R0存储到0X801FFFF8地址

​ 第4行,出栈,具体操作如下:

​ 将0X801FFFF8地址的内容赋值给R0

​ 将0X801FFFFC地址的内容赋值给R1

​ 将0X80200000地址的内容赋值给R2

​ 如下图所示:

​ 上述第3,4行汇编代码,就是所谓的入栈,出栈。也可以用push,pop指令完成入栈,出栈,如下

1 ldr sp,=0x80200000
2
3 push {r0-r2} @ 入栈
4 pop {r0-r2}  @ 出栈
5.3 进制

​ 目前计算机对数据的表示方式,有十六进制、十进制、八进制与二进制。

5.3.1 如何理解它们的区别?

​ 十六进制,逢十六进一,每一位由0~F组成,习惯用0x前缀表示或用H后缀表示

0xA或AH

​ 十进制,逢十进一,每一位由0~9组成,无前缀或用D后缀表示

10或10D

​ 八进制,逢八进一,每一位由0~7组成,习惯用0前缀表示或用O后缀表示

012或12O

​ 二进制,逢二进一,每一位由0~1组成,习惯用0b前缀表示或用B后缀表示

0b1010或1010B
5.3.2 在C语言中怎么表示这些进制呢?
十六进制:int a = 0xA;   // 0x前缀
十进制:  int a = 10;
八进制:  int a = 012;   // 0前缀
二进制:  int a = 0b1010;// 0b前缀
5.3.3 十六进制与二进制转换关系

​ 在嵌入式开发中经常需要对十六进制与二进制进行转换

​ 如何快速的转换2/16进制? 首先记住8 4 2 1 ——>二进制权重

​ 将二进制0b01101110101转换成十六进制:将二进制从右到左,每四个分成一组:

​ 结果就是0x375

​ 将十六进制0xABC1转换成二进制:将十六进制从右到左,每个分成四位:

​ 结果就是1010 1011 1100 0001

5.4 大/小端模式与位操作 5.4.1 大/小端模式

​ 大端模式(Big-endian),是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中

​ 小端模式(Little-endian),是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中

​ 比如:0x12345678,在大/小端模式的存储位置如下:

内存地址大端模式小端模式addr+30x780x12addr+20x560x34addr+10x340x56addr0x120x78 5.4.2 位操作 5.4.2.1 移位
1 int a = 0x6; // 二进制是0b0110
2 int b = a1;

​ 第2行,对a左移一位,从0b0110->0b1100,即b=0xC

​ 第3行,对a右移一位,从0b0110->0b0011,即b=0x3

5.4.2.2 取反
1 int a = 0x6; // 二进制是0b0110
2 int b = ~a;

​ 第2行,对a按位取反,从0b0110->0b1001,即b=0x9

5.4.2.3 位与

​ 只有对应的两个二进位都为1时,结果位才为1

1 int a = 0x6; // 二进制是0b0110
2 int b = 0x7; // 二进制是0b0111
3 
4 int c = a&b;

​ 第4行,a&b,二进制是0b0110,即c=0x6

5.4.2.4 位或

​ 只要对应的二个二进位有一个为1时,结果位就为1

1 int a = 0x6; // 二进制是0b0110
2 int b = 0x7; // 二进制是0b0111
3 
4 int c = a|b;

​ 第4行,a|b,二进制是0b0111,即c=0x7

5.4.2.5 置位
1 int a = 0x6;     // 二进制是0b0110
2 
3 int a |= (1            
关注
打赏
1658827356
查看更多评论
0.0439s