公司网站建设的请示,企业网站系统设计与实现,张戈博客 wordpress同步新浪微博,公司注册代理免费咨询我们在学习 STM32 的时候几乎没有用到过汇编#xff0c;可能在学习 UCOS 、 FreeRTOS 等 RTOS 类操作系统移植的时候可能会接触到一点汇编。但是我们在进行嵌入式 Linux 开发的时候是绝 对要掌握基本的 ARM 汇编#xff0c;因为 Cortex-A 芯片一上电 SP 指针还… 我们在学习 STM32 的时候几乎没有用到过汇编可能在学习 UCOS 、 FreeRTOS 等 RTOS 类操作系统移植的时候可能会接触到一点汇编。但是我们在进行嵌入式 Linux 开发的时候是绝 对要掌握基本的 ARM 汇编因为 Cortex-A 芯片一上电 SP 指针还没初始化 C 环境还没准备 好所以肯定不能运行 C 代码必须先用汇编语言设置好 C 环境比如初始化 DDR 、设置 SP 指针等等当汇编把 C 环境设置好了以后才可以运行 C 代码。所以 Cortex-A 一开始肯定是汇 编代码其实 STM32 也一样的一开始也是汇编以 STM32F103 为例启动文件 startup_stm32f10x_hd.s 就是汇编文件只是这个文件 ST 已经写好了我们根本不用去修改所 以大部分学习者都没有深入的去研究。汇编的知识很庞大本章我们只讲解最常用的一些指令 满足我们后续学习即可。 一.GNU 汇编语法 如果大家使用过 STM32 的话就会知道 MDK 和 IAR 下的启动文件 startup_stm32f10x_hd.s 其中的汇编语法是有所不同的将 MDK 下的汇编文件直接复制到 IAR 下去编译就会出错因 为 MDK 和 IAR 的编译器不同因此对于汇编的语法就有一些小区别。我们要编写的是 ARM 汇编编译使用的 GCC 交叉编译器所以我们的汇编代码要符合 GNU 语法。 GNU 汇编语法适用于所有的架构并不是 ARM 独享的 GNU 汇编由一系列的语句组成 每行一条语句每条语句有三个可选部分如下 label instruction comment label 即标号表示地址位置有些指令前面可能会有标号这样就可以通过这个标号得到 指令的地址标号也可以用来表示数据地址。注意 label 后面的“”任何以“”结尾的标识 符都会被识别为一个标号。 instruction 即指令也就是汇编指令或伪指令。 符号表示后面的是注释就跟 C 语言里面的“ /* ”和“ */ ”一样其实在 GNU 汇编文 件中我们也可以使用“ /* ”和“ */ ”来注释。 comment 就是注释内容。 比如如下代码 add: MOVS R0, #0X12 设置 R00X12 上面代码中“ add: ”就是标号“ MOVS R0,#0X12 ”就是指令最后的“ 设置 R00X12 ”就是 注释。 .data 初始化的数据段。 .bss 未初始化的数据段。 .rodata 只读数据段。 我们当然可以自己使用 .section 来定义一个段每个段以段名开始以下一段名或者文件结 尾结束比如 .section .testsection 定义一个 testsetcion 段 汇编程序的默认入口标号是 _start 不过我们也可以在链接脚本中使用 ENTRY 来指明其它 的入口点下面的代码就是使用 _start 作为入口标号 .global _start _start: ldr r0, 0x12 r00x12 上面代码中 .global 是伪操作表示 _start 是一个全局标号类似 C 语言里面的全局变量一 样常见的伪操作有 .byte 定义单字节数据比如 .byte 0x12 。 .short 定义双字节数据比如 .short 0x1234 。 .long 定义一个 4 字节数据比如 .long 0x12345678 。 .equ 赋值语句格式为 .equ 变量名表达式比如 .equ num, 0x12 表示 num0x12 。 .align 数据字节对齐比如 .align 4 表示 4 字节对齐。 .end 表示源文件结束。 .global 定义一个全局符号格式为 .global symbol 比如 .global _start 。 GNU 汇编还有其它的伪操作但是最常见的就是上面这些如果想详细的了解全部的伪操 作可以参考《 ARM Cortex-A(armV7) 编程手册 V4.0.pdf 》的 57 页。 GNU 汇编同样也支持函数函数格式如下 函数名 : 函数体 返回语句 GNU 汇编函数返回语句不是必须的如下代码就是用汇编写的 Cortex-A7 中断服务函数 示例代码 7.1.1.1 汇编函数定义 /* 未定义中断 */ Undefined_Handler : ldr r0 , Undefined_Handler bx r0 /* SVC 中断 */ SVC_Handler : ldr r0 , SVC_Handler bx r0 /* 预取终止中断 */ PrefAbort_Handler : ldr r0 , PrefAbort_Handler bx r0 上述代码中定义了三个汇编函数 Undefined_Handler 、 SVC_Handler 和 PrefAbort_Handler 。以函数 Undefined_Handler 为例我们来看一下汇编函数组成 “ Undefined_Handler ”就是函数名“ ldr r0, Undefined_Handler ”是函数体“ bx r0 ”是函数 返回语句“ bx ”指令是返回指令函数返回语句不是必须的。 二.处理器内部数据传输指令 三个指令 1 、 MOV 指令 MOV 指令用于将数据从一个寄存器拷贝到另外一个寄存器或者将一个立即数传递到寄 存器里面使用示例如下 MOV R0 R1 将寄存器 R1 中的数据传递给 R0 即 R0R1 MOV R0, #0X12 将立即数 0X12 传递给 R0 寄存器即 R00X12 2 、 MRS 指令 MRS 指令用于将特殊寄存器 ( 如 CPSR 和 SPSR) 中的数据传递给通用寄存器要读取特殊 寄存器的数据只能使用 MRS 指令使用示例如下 MRS R0, CPSR 将特殊寄存器 CPSR 里面的数据传递给 R0 即 R0CPSR 3 、 MSR 指令 MSR 指令和 MRS 刚好相反 MSR 指令用来将普通寄存器的数据传递给特殊寄存器也就 是写特殊寄存器写特殊寄存器只能使用 MSR 使用示例如下 MSR CPSR, R0 将 R0 中的数据复制到 CPSR 中即 CPSRR0 三.存储器访问指令 1 、 LDR 指令 LDR 主要用于从存储加载数据到寄存器 Rx 中 LDR 也可以将一个立即数加载到寄存器 Rx 中 LDR 加载立即数的时候要使用“ ”而不是“ # ”。在嵌入式开发中 LDR 最常用的就是读 取 CPU 的寄存器值比如 I.MX6UL 有个寄存器 GPIO1_GDIR 其地址为 0X0209C004 我们 现在要读取这个寄存器中的数据示例代码如下 示例代码 7.2.2.1 LDR 指令使用 1 LDR R0 , 0X0209C004 将寄存器地址 0X0209C004 加载到 R0 中即 R0 0X0209C004 2 LDR R1 , [ R0 ] 读取地址 0X0209C004 中的数据到 R1 寄存器中 上述代码就是读取寄存器 GPIO1_GDIR 中的值读取到的寄存器值保存在 R1 寄存器中 上面代码中 offset 是 0 也就是没有用到 offset 。 2 、 STR 指令 LDR 是从存储器读取数据 STR 就是将数据写入到存储器中同样以 I.MX6UL 寄存器 GPIO1_GDIR 为例现在我们要配置寄存器 GPIO1_GDIR 的值为 0X20000002 示例代码如下 示例代码 7.2.2.2 STR 指令使用 1 LDR R0 , 0X0209C004 将寄存器地址 0X0209C004 加载到 R0 中即 R0 0X0209C004 2 LDR R1 , 0X20000002 R1 保存要写入到寄存器的值即 R1 0X20000002 3 STR R1 , [ R0 ] 将 R1 中的值写入到 R0 中所保存的地址中 LDR 和 STR 都是按照字进行读取和写入的也就是操作的 32 位数据如果要按照字节、 半字进行操作的话可以在指令“ LDR ”后面加上 B 或 H 比如按字节操作的指令就是 LDRB 和 STRB 按半字操作的指令就是 LDRH 和 STRH 。 四. 压栈和出栈指令 通常会在 A 函数中调用 B 函数当 B 函数执行完以后再回到 A 函数继续执行。要想 再跳回 A 函数以后代码能够接着正常运行那就必须在跳到 B 函数之前将当前处理器状态保存 起来 ( 就是保存 R0~R15 这些寄存器值 ) 当 B 函数执行完成以后再用前面保存的寄存器值恢复 R0~R15 即可。保存 R0~R15 寄存器的操作就叫做现场保护恢复 R0~R15 寄存器的操作就叫做 恢复现场。在进行现场保护的时候需要进行压栈 ( 入栈 ) 操作恢复现场就要进行出栈操作。压栈 的指令为 PUSH 出栈的指令为 POP PUSH 和 POP 是一种多存储和多加载指令即可以一次 操作多个寄存器数据