学习嵌入式的第18天

一.回忆昨天的内容

ARM A R M cortex-a53 8个CPU 1.4GHZ arm体系结构与编程 svc fiq irq undef abort user system

特权模式与非特权模式 arm 汇编指令

arm中的寄存器有 37个, 通用寄存器 31 个, 6个状态寄存器, 其中cpsr为程序状态寄存器, 5个为 spsr

  1. blx 分支跳转指  b cond  l link 带链接  x 带状态切换  ARM --> Thumb --> ARM
  2. 算数运算指令  add  adc  sub  sbc
  3. 数据传输指令  mov {cond} {s} <Rd>, <oprand>   oprand: 立即数 通用寄存器 通用寄存器移位后的结果
  4. 移位指令  LSL  LSR  ASR  ROR 循环右移  RRX 带扩展位的循环右移

二.shell编程框架 的第一版

获取干净的源码 : make clean 编译: make 链接脚本: shell.lds

MakeFile  目标: 依赖

编译好后, 放在板子上执行:

  1. 进入下位机 uboot 的命令行
  2. 通过修改tftp, 下载 shell.bin
  3. 修改开发板的ip:  setenv ipaddr 192.168.1.6  saveenv  修改pc的IP  sentenv serverip 192.168.6.1  saveenv
  4. 将虚拟机的ip也设置成
  5. 在下位机 ping 上位机  需要ping两次
  6. 下载 shell.bin: tftp 48000000 /ftfpboot/shell.bin 执行: go 0x48000000

三.ARM汇编

仿真软件 : qemu, 运行在pc机上, 模拟在ARM执行指令的过程

qemu的安装: sudo apt install qemu

  1. 重新编译程序: arm-cortex_a9-linux-gnueabi-as add.o -o add.o -g arm-cortex_a9-linux-gnueabi-ld add.o -o add

  2. 启动 qemu 服务端 qemu-arm -g 1234 add

  3. 启动客户端调试程序 arm-cortex_a9-linux-gnueabi-gdb add (gdb) 1 (gdb) target remote localhost:1234 (gdb) b 7 (gdb) info reg (gdb) n (gdb) info reg ro (gdb) info reg r1

3.1 位运算指令

  1. 按位与: and <cond> {s} <Rd>, <Rs>, <oprand> eg: and r0, r1, r2 @ r0 = r1&r2 eg: and r0, r1, r2 @ r0 = r1&8 eg: and r0, r1, r2, lsl #3 @r0 = r1 & (r2 << 3)

  2. 按位或: orr <cond> {s} <Rd>, <Rs>, <oprand> eg: orrgts r0, r1, r2 按位异或: eor <cond> {s} <Rd>, <Rs>, <oprand> eg: eor r0, r0, #8 @ r0 = r0 ^ 8 练习: 如何取反 32bit 整形变量的 bit15?  mov r1, #1eor r0, r0, r1, lsl, #15

  3. BIC指令: bic <cond> {s} <Rd>, <Rs>, <oprand> 第二个操作数对应的bit位为1, 结果为0 第二个操作数对应的bit位为0, 结果不变 eg : bic r0, r0, #0x08 @将r0的bit3位清0 练习: 如何清零 32bit 整形变量的 bit17, 其他位不变?  mov r1, #1bic r0, r0, r1, lsl #7

4.比较测试指令: cmp {cond} <Rn>, <operand> 特点:

  1. 不需要+s, 默认就影响 NZCV 位
  2. 操作结果不保存
cmp r1, #0x10 @alu_out = r1 - 0x10
              @r1 > 0x10 C = 1
              @r1 == 0x10, Z = 0
              @r0 < 0x10 C = 0>

tst {cond} <Rn>, <oprand>

tst r0, #0x08 @测试 r0 的 bit3 是否为 3
              @alu_out = r0 & 0x08
              @如果为0, Z = 1
              @如果非0, Z = 0

teq {cond} <Rn>, <oprand>

teq r0, #0x08   @测试r0 和 0x08 是否相等
                @ alu_out = r0 ^ 0x08
                @相等 Z = 1
                @不想等 Z = 0

3.3 加载存储指令

ARM中所有的运算都是在通用寄存器中完成的, 操作的对象是 通用寄存器 r0~r15

  1. 加载指令

加载指令

mov r1, 0x48000000
ldr r0, [r1]    @ 将内存 0x48000000 开始的 4byte 加载到 r0 中
                @ r0 = *((int *) 0x48000000)

ldrb r0, [r1]   @ 将内存 0x48000000 开始的 1byte 加载到 r0 中
                @ r0 = *((unsigned char *) 0x48000000)

ldrsb r0 , [r1] @ 将内存 0x48000000 开始的 1byte 加载到 r0 中, r0中的高 24bit 补符号位
                
ldrh r0 , [r1]  @ 将内存 0x48000000 开始的 2byte 加载到 r0 中, r0 的高16bit 补0

ldrsh r0 , [r1] @ 将内存 0x48000000 开始的 2byte 加载到 r0 中, r0 的高16bit 补符号位

ldr r0, [r1] @ [r1] ---> r0
-------------------------------
ldr r0, [r1, #0x08] @ [r1 + 0x08] --> r0
ldr r0, [r1 + r2]   @ [r1 + r2]   --> r0
ldr r0, [r1, r2, lsl #2] @ [r1 + r2 << 2] --> r0
-------------------------------------------------------
ldr r0, [r1, #0x08]! @ [r1 + 0x08] ---> r0 r1 = r1 + 0x08 加!会更新基址
ldr r0, [r1, r2]!    @ [r1+ r2] --> r0 r1 = r1 + r2
ldr r0, [r1, r2, lsl #2]! @ [r1+r2*4] --> r0 r1 = r1 + r2*4
---------------------------------------------------------------、
ldr r0, [r1], #0x08 @[r1] --> r0  r1 = r1+o0x08
ldr r0, [r1] r2  @ [r1] --> r0 r1 = r1 + r2
ldr r0, [r1], r2, lsl #2 @ [r1] --> r0, r1 = r1 + r2*8
--------------------------------------------------------

2.存储指令 str 从寄存器 ---> 内存

mov r1, 0x48000000
str r0, [r1]  @将 r0的 4Byte 数据 写入到内存0x48000000中
strb r0, [r1] @将 r0的(低 8位) 1Byte 数据 写入到内存0x48000000中
strh r0, [r1] @将 r0的(低16位)  1Byte 数据 写入到内存0x48000000中

str r0, [r1]      @ r0 ----> r1
str r0, [r1+0x08] @ r0 ----> r1+0x08
str r0, [r1, r2]  @ r0 ----> [r1 + r2]
str r0, [r1, r2, lsl #2]   @ r0 -----> [r1 + r2 *4]
----------------------------------------------------------
str r0, [r1+0x08]!    @ r0 ---> [r1 + 0x08] r1 = r1 + 0x08
str r0, [r1]!, r2     @ r0 ---> [r1 + r2]   r1 = r1 + r2
str r0, [r1]!, r2, lsl #2   @ r0 ---> [r1 + r2*4]   r1 = r1 + r2*4
-------------------------------------------------------------------
str r0, [r1], #0x08   @ r0 ---> [r1]  r1 = r1 + 0x08
str r0, [r1], r2      @ r0 ---> [r1]  r1 = r1 + r2
str r0, [r1], r2, lsl #2  @r0 ----> [r1]  r1 = r1 + r2*4

练习: memcpy, 内存0x8e00开始的64字节数据拷贝到0x8f00开始的内存空间:

.text
.code 32
.globel _start

_start:
  mov r0, #0x00008e00
  mov r1, #1
  mov r2, #16
  
  //init memory
loop:
  str r1, [r0], #4
  subs r2, r2, #1
  bne loop

mem_cpy:
  mov r0, #0x00008e00
  mov r1, #0x00008f00
  mov r2, #16

mem_cpy_loop:
  ldr r3, [r0], #4
  str r3, [r1], #4
  subs r2, r2, #1
  bne mem_cpy_loop

mem_cpy_ok:
  b .

3.4 栈操作指令

注意: 满栈和空栈指的是指针上面或下面的空间有没有数据, 或者说, 接下来要存数据的时候, 先放数据还是先改变 栈指针 sp 的值.

ldmxx sp!, {r0~r5, r8} @将 sp 指向的栈空间数据加载到r0, r1, r2, r3, r4, r5, r8中  XX: FD FA ED DA stmxx sp!, {r0~r5, r8} @将 r0, r1, r2, r3, r4, r5, r8 中的数据加载到 sp 指向的栈空间中  XX: FD FA ED DA

stmfd sp!, {r0~r5, r8} 等价于 push {r0~r5, r8} ldmfd sp!, {r0~r5, r8} 等价于 pop {r0~r5, r8} 按满减栈压入栈, 也按满减栈弹出数据, 就可以恢复寄存器

局部变量如何在栈里面分配空间? C语言的运行离不开栈:

  1. 局部变量在栈中分配空间
  2. 函数嵌套调用时 lr 需要压入栈

#include <stdio.h>

int add(int x, int y, int z){
  return x + y + z;
}

int main(void){
  int a = 10;
  int b = 20;
  int c = 30;

  int ret = add(a, b, c);

  return 0;
}

3.5 伪指令

  1. ldr伪指令 伪指令本身不被CPU识别, 但能被汇编器编译成一条或多条指令CPU所识别的指令, 这种伪指令一般实现一个小功能 eg : ldr r0, [r1] //汇编指令 有 [] 是加载指令, 没有则为伪指令.
.code 32
.global_start
.text

_start:
  @ mov r0, @0x1ff
  ldr r0, =0x1ff //伪指令
  ldr r0, [pc]
  add r3, r4, r5
  b.
.word 0x1ff

.end

ldr r1, =text //将立即数本身放到r1中去 ldr r1, test //将立即数指向的内存中的数据放到r1 2. nop伪指令 作用: mov r0, r0 3. adr伪指令 小范围的地址加载指令 忽略

3.6 伪操作

汇编文件中以"."开头的都是伪操作, 伪操作是给汇编器使用的, 一旦编译完成就消失

eg: .text .bss .equ .data .global .extern .byte .word .string .ascii eg: .equ NUM, #20 //类似于C语言的宏 eg: .space 1024 //分配1024byte 的空间 eg: .code 32 == .arm .code16 == .thumb eg: .section //自定义段


作业: 用汇编语言实现: strcmp(比较两个字符串) r0 <---- "abcefg" ldrb r2, [r0], #1 r1 <---- "aabced" ldrb r3, [r1], #1 cmp r2, r3 beq


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