IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> [007] [ARM-Cortex-M3/4] C与汇编深入分析 -> 正文阅读

[嵌入式][007] [ARM-Cortex-M3/4] C与汇编深入分析

ARM
Cortex-M3/4
子程序(函数)
调用规则
C函数的反汇
编代码分析
Flash中烧录的内
容与启动流程
Flash中烧录的内容
启动流程
纯汇编点灯

1 子程序(函数)调用规则

[ARM-Cortex-M3/4] ATPCS子程序调用规则规定了函数调用过程中参数的传递、局部变量的保存、数据栈的使用等规则。

寄存器别名特殊名称使用规则
r15PC程序计数器
r14LR连接寄存器
r13SP数据栈指针
r12IP子程序间调用的暂存寄存器scratch register.
r11v8FPARM状态局部变量寄存器8 / 栈帧指针
r10v7SLARM状态局部变量寄存器7 在支持数据栈检查的ATPCS中为数据栈限制指针
r9v6SBARM状态局部变量寄存器6 在支持RWPI的ATPCS中为静态基址寄存器
r8v5ARM状态局部变量寄存器5
r7v4WR局部变量寄存器4 Thumb状态工作寄存器
r6v3局部变量寄存器3
r5v2局部变量寄存器2
r4v1局部变量寄存器1
r3a4参数/结果/暂存寄存器scratch register 4
r2a3参数/结果/暂存寄存器scratch register 3
r1a2参数/结果/暂存寄存器scratch register 2
r0a1参数/结果/暂存寄存器scratch register 1
  • r0~r3传递参数,被调用的子程序在返回前无需恢复寄存器r0~r3的内容。
  • r4~r11保存局部变量,若被调程序使用了其中的某些寄存器,则在进入子程序时必须保存它们的值,在返回前必须恢复这些寄存器的值。在Thumb程序中,只能使用r4~r7。
  • r12~r15为特殊寄存器。

2 C函数的反汇编代码分析

keil软件会对名为main的函数添加很多额外的汇编代码(与链接脚本有关),为了简化分析,将main修改为mymain

  • start.s
				PRESERVE8
                THUMB

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
				EXPORT  __Vectors
					
__Vectors       DCD     0                  
                DCD     Reset_Handler              ; Reset Handler

				AREA    |.text|, CODE, READONLY

; Reset handler
Reset_Handler   PROC
				EXPORT  Reset_Handler             [WEAK]
                IMPORT  mymain			; 修改为mymian

				LDR SP, =(0x20000000+0x10000)
				BL mymain

                ENDP 
                
                END

▲ C函数与反汇编代码

程序烧录到flash中的即为机器码

在启动文件使用BL指令跳转到mymain函数:

  • 使能GPIOB
; pReg = (unsigned int *)(0x40021000 + 0x18);
LDR  	r3,[pc,#52] 	; pc+52=0x8000058, 将该地址的中的内容0x40021018加载到 r3 (r3即为指针pReg)
; *pReg |= (1<<3);
LDR  	r0,[r3,#0]		; r0 = *r3  (*即为解引用, 表示取r3地址中的值)
ORR     r0,r0,#8		; r0 |= (1 << 3)
STR     r0,[r3,#0]		; *r3 = r0 	
  • 设置GPIOB0为输出引脚
; pReg = (unsigned int *)(0x40010C00 + 0x00);
LDR     r3,[pc,#48] 	; 从0x0800005c内存地址读出0x40010c00加载到r3
; *pReg |= (1<<0);
LDR     r0,[r3,#0]		; r0 = *r3
ORR     r0,r0,#1		; r0 |= 1
STR     r0,[r3,#0]		; *r3 = r0
; pReg = (unsigned int *)(0x40010C00 + 0x0C);
LDR     r3,[pc,#36] 	; r3 = 0x40010c00
ADDS    r3,r3,#0xc		; r3 += 0xc, S表示需要修改CPSR状态寄存器
  • while循环
0x08000038:   B        0x8000056 	; 跳转到最后一句0x8000056处
; *pReg |= (1<<0);
0x0800003a:   LDR      r0,[r3,#0]	; r0 = *r3 = *(0x40010c00 + 0xc)
0x0800003c:   ORR      r0,r0,#1		; r0 |= (1 << 0)
0x08000040:   STR      r0,[r3,#0]	; *r3 = r0
; delay(100000);
0x08000042:   LDR      r0,[pc,#28]  ; r0 = *0x08000060 = 0x186a0 = 100000, 根据ATPCS规则r0保存delay函数的入口参数
0x08000044:   BL       delay 		; 跳转到0x8000014处的delay函数↓, 并将下一条指令的地址0x08000048加载到LR
; *pReg &= ~(1<<0);
0x08000048:   LDR      r0,[r3,#0]	; r0 = *r3 = *(0x40010c00 + 0xc)
0x0800004a:   BIC      r0,r0,#1		; r0 &= ~(1 << 0)
0x0800004e:   STR      r0,[r3,#0]	; *r3 = r0
; delay(100000);
0x08000050:   LDR      r0,[pc,#12]  ; r0 = 0x186a0 = 100000
0x08000052:   BL       delay 		; 跳转到0x8000014处的delay函数↓
0x08000056:   B        0x800003a 	; 跳回到第二句0x800003a处

; 以下是为一些常量分配的地址与值, 不属于while部分
0x08000058:    40021018    DCD    1073877016
0x0800005c:    40010c00    DCD    1073810432
0x08000060:    000186a0    DCD    100000
  • delay函数
0x08000014:   NOP      				; 空操作伪指令,用于延时
0x08000016:   SUBS     r1,r0,#0		; r1 = r0 - 0, S表示会更新cpsr
0x08000018:   SUB      r0,r0,#1		; r0 -= 1
0x0800001c:   BNE      0x8000016 	; not equal(Z=0)执行,即若r0不为0, 跳转到0x8000016处继续执行
0x0800001e:   BX       lr			; 调转到 while循环 中BL指令的下一条指令处继续执行

start.s中设置了栈LDR SP, =(0x20000000+0x10000),为体现函数栈的调用,将delay函数变种(加volatile):

int delay(volatile int d)
{
	while(d--);
	return 0x55;
}
delay
    0x08000014:    PUSH     {r0,lr}			; 将1r压入到sp-4, r0压入到sp-8, sp = sp - 8
    0x08000016:    NOP      
    ; 因为加了volatile(从栈中读r0到r1 -> 修改r1-> 将r1再写回栈中r0
    0x08000018:    LDR      r0,[sp,#0]		; r0 = sp, sp存储原r0
    0x0800001a:    SUBS     r1,r0,#1		; r1 = r0 - 1, 并改变cpsr
    0x0800001c:    STR      r1,[sp,#0]		; *sp = r1, 即*r0 = r1
    0x0800001e:    CMP      r0,#0			; 比较r0是否为0
    0x08000020:    BNE      0x8000018 		; 如果r0不为0, 跳转到0x8000018处
    0x08000022:    MOVS     r0,#0x55		; 用r0保存函数返回值0x55
    0x08000024:    POP      {r3,pc}			; r3 = r0, pc = lr 跳转
    0x08000026:    MOVS     r0,r0			; 无用语句->字节对齐

不加volatile

0x08000016:   SUBS     r1,r0,#0		; r1 = r0 - 0, S表示会更新cpsr
0x08000018:   SUB      r0,r0,#1		; r0 -= 1

volatile

0x08000018:   LDR      r0,[sp,#0]	; r0 = sp, sp存储原r0
0x0800001a:   SUBS     r1,r0,#1		; r1 = r0 - 1, 并改变cpsr
0x0800001c:   STR      r1,[sp,#0]	; *sp = r1, sp存储原r0, 即*r0 = r1

3 Flash中烧录的内容与启动流程

3.1 Flash中烧录的内容

▲ 反汇编文件

反汇编文件的框选部分即为机器码,将其烧录到对应的flash地址上(四字节对齐):

地址Flash内容
0x0800000000000000
0x0800000408000009
0x08000008f8dfd004
0x0800000cf000f808
0x0800001020010000
0x08000014bf001e10
0x08000018f1a00001
…………

3.2 启动流程

分散加载文件中指定了flash起始地址为0x80000000:

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}
  • 设置栈:CPU会从0x08000000读取值,用来设置SP指向flash起始地址(示例的启动文件中后面再次设置了SP,让其指向RAM末尾地址)
  • 跳转:CPU从0x08000004得到地址值0x08000009,即PC=0x08000009,PC的LSB为1表示Thumb状态(0为ARM)
    • 对于cortex M3/M4,它只支持Thumb状态,所以0x08000004上的值的LSB总为1
    • 跳到Reset_Handler(0x08000008地址处)复位中断服务例程中执行

4 纯汇编点灯

引脚PA8

				PRESERVE8
                THUMB

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
				EXPORT  __Vectors
					
__Vectors       DCD     0                  
                DCD     Reset_Handler              ; Reset Handler
				AREA    |.text|, CODE, READONLY

; Reset handler
Reset_Handler   PROC
				EXPORT  Reset_Handler             [WEAK]
					
DEALY_TIME      EQU     500000	; 定义延时时间常量
				; 使能GPIOA时钟, RCC寄存器基地址0x4002 1000, 0x40021000 + 0x18为RCC->RCC_APB2ENR
				LDR	R0, =(0x40021000 + 0x18)
				LDR R1, [R0]
				ORR R1, R1, #(1<<2)
				STR R1, [R0]
				; 配置CRH寄存器, 设置PA8为推挽输出, GPIOA基地址0x4001 0800, 0x40010800 + 0x04为GPIOA->CRH
				LDR	R0, =(0x40010800 + 0x04)
				LDR R1, [R0]
				BIC R1, R1, #0xf	; 先清除
				ORR R1, R1, #0x3	; 推挽输出, 50mhz
				STR R1, [R0]
				; 操作ODR寄存器控制GPIO电平, 0x40010800 + 0x0C为GPIOA->ODR)
				LDR	R0, =(0x40010800 + 0x0C)
LOOP				
				; 拉高
				LDR R1, [R0] 
				ORR R1, R1, #(1<<8)
				STR R1, [R0]
				; 延时
				LDR R2, =DEALY_TIME
				BL DELAY
				
				; 拉低
				LDR R1, [R0] 
				BIC R1, R1, #(1<<8)
				STR R1, [R0]
				; 延时
				LDR R2, =DEALY_TIME
				BL DELAY
				
				B LOOP
                ENDP 
DELAY
				SUBS R2, R2, #1
				NOP		; vb字节对齐
				BNE DELAY
				BX LR	; MOV PC, LR
                END

实验现象:PA8红色LED正常闪烁。

END

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-03-11 22:23:57  更:2022-03-11 22:24:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 6:53:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码