嵌入式系统复习--基于ARM的嵌入式程序设计

2024-01-08 04:29:31

上一篇

嵌入式系统复习–Thumb指令集

编译环境

ADS/SDT IDE开发环境:它由ARM公司开发,使用了CodeWarrior公司的编译器

一般的:集成了GNU开发工具的IDE开发环境:它由GNU的汇编器as、交叉编译器gcc、和链接器ld等组成

  • 伪操作:ARM汇编语言程序里的特殊指令助记符,主要作用是完成汇编程序各种准备工作,在源程序进行编译时由汇编程序处理,而不是在计算机运行期间执行
  • 宏指令:是一段独立的代码、可插在源程序中,它通过伪操作来定义。通过实际指令替代宏体实现相关的一段代码
  • 伪指令:ARM汇编语言里的特殊指令助记符,不在处理器运行期间由机器执行。它们在编译时将被合适的机器指令替代

ADS编译环境下的伪操作

常用伪操作可分为:

  • 符号定义伪操作
    1. 定义全局变量

      伪操作语法格式作用
      GBLAGBLA Variable声明一个全局的算术变量,初始化为0
      GBLLGBLL Variable声明一个全局的逻辑变量,初始化为false
      GBLSGBLS Variable声明一个全局字符串变量,初始化为空串
    2. 定义局部变量

      伪操作语法格式作用
      LCLALCLA Variable声明一个局部的算术变量,初始化为0
      LCLLLCLL Variable声明一个局部的逻辑变量,初始化为false
      LCLSLCLS Variable声明一个局部字符串变量,初始化为空串
    3. 为变量赋值

      伪操作语法格式作用
      SETAVariable SETA expr给一个算术变量赋值
      SETLVariable SETL expr给一个逻辑变量赋值
      SETSVariable SETS expr给一个字符串赋值
    4. 给一串寄存器命名

      伪操作语法格式作用
      RLISTname RLIST {list of registers}为一个通用寄存器列表定义名称
  • 数据定义伪操作
    1. 为一个变量分配内存地址
      伪操作语法格式作用
      DCB{label} DCB expr分配一段字节的内存单元,用字节的方式放expr
      DCD{label} DCD expr分配一段的内存单元,用字的方式放expr
    2. 定义数据块的起始地址
      伪操作语法格式作用
      LTORGLTORG声明一个数据缓冲池的开始(说明此指令后面不是代码,而是数据了)
  • 汇编控制伪操作
    1. 条件
      IF 逻辑表达式
      {
      ELSE	
      }
      ENDIF
      
    2. 循环
      WHILE 逻辑表达式
      ...
      WEND
      
    3. 宏定义
      类似子程序调用,但是他不是跳转到子程序,而是直接将宏定义的代码,直接复制到宏名称那里
      MACRO
      {$label} macroname {$parameter}
      MEND
      
  • 信息报告伪操作
    在程序运行不同阶段可以设置让他弹出信息
    伪操作语法格式作用
    OPTOPT n通过OPT可以在源程序中设置列表选项
  • 其他伪操作
    1. 告诉编译器要选择的指令
      伪操作语法格式作用
      CODE 16CODE 16告诉编译器后面是Thumb指令
      CODE 32CODE 32告诉编译器后面是ARM指令
    2. 定义常量(给变量或标号赋值)
      伪操作语法格式作用
      EQUname EQU expr将name赋值为expr
    3. 程序结构指令
      伪操作语法格式作用
      AREAAREA sectioname {, attr}定义一个代码段或数据段
      ENTRYENTRY指定程序的入口点
      ENDEND告诉编译器已经到了源程序结尾
    4. 混合调用用到的指令
      伪操作语法格式作用
      EXPORT/GLOBALEXPORT symbol {[WEAK]}声明一个文件可以被其他文件引用,相当于声明以个全局变量
      IMPORT/EXTERNIMPORT/EXTERN symbol {[WEAK]}告诉编译器当前符号不是在本源文件中定义的,而是在其他文件中,而在本文件中可能使用

GNU编译环境下的伪操作

常用伪操作为:

  • 常量编译控制伪操作

    伪操作语法格式作用
    .byte.byte expr分配一段字节空间,并用字节的方式放入expr
    .hword/ .short.hword expr分配一段半字节空间,并用半字的方式放入expr
    .ascii.ascii expr定义字符串expr(非零结束符)
    .asciz/.string.asciz定义字符串(以 /0 为结束符)
    .word.word expr分配一段字内存空间
  • 汇编程序代码控制伪操作

    伪操作语法格式作用
    .section.section expr定义域中包含的段
    .text.text{subsection}代码
    .data.data {subsection}数据
    .code 16.code 16表明后面是Thumb指令集
    .code 32.code 32表明后面是ARM指令集
    .end.end结束标识符
    .include.include “filename”将一个源文件包含到当前源文件中
  • 宏及条件编译控制伪操作

    .macro以及.endm

    .macro condition
    .endm
    

    .ifdef, .else及.endif

    .ifdef condition
    .else
    .endif
    
  • 其他伪操作

    伪操作语法格式作用
    .print.print string打印信息到标准输出

ARM汇编语言的伪指令

伪操作语法格式作用
ADRADR {cond} register, expr将基于PC或基址寄存器的地址读到寄存器中,小范围的地址读取
ADRLADRL {cond} register,expr将基于PC或基址寄存器的地址读到寄存器中,中等范围的地址读取
LDRLDR {cond} register将一个32位的立即数或一个地址值读取到寄存器中,大范围地址读取
NOPNOP汇编是替换成ARM的空指令(占用一个操作时间但什么到不做)

汇编语言程序设计

  1. 文件格式
    ARM源程序文件可以有任意一种纯文本文件编写程序代码
    文件格式对应与

    源程序文件文件名说明
    汇编程序文件*.S用ARM汇编语言编写的ARM程序或Thumb程序
    C程序文件*.C用C语言编写的程序代码
    头文件*.H通常将常量命名、宏定义、数据结构定义等放在头文件中
  2. ARM汇编语言语句格式如下:

    {符号} {指令 | 伪操作 | 伪指令} {; 注释}
    

    ;为注释符

    • 符号可以代表地址、变量和数字常量
      命名规则:符号由大小写字母、数字以及下划线组成(区分大小写)
      局部标号以数字开头,其他符号都不能以数字开头

    • 变量: 三种变量:数字变量、逻辑变量、串变量

    • 数字常量:表示方式:十进制,十六进制,n进制

    • 标号:表示程序中的指令或地址的标号,分为基于PC的标号、基于寄存器的标号、绝对地址

    • 局部标号:相对当前位置

      % {F|B} {A|T} N{routname}
      

      其中:%表示引用操作、F表示只先向前搜索、B表示向后搜索、A表示编译器搜索宏的搜索嵌套层次、T表示宏的当前层次

相关运算操作符

  1. 字符串表达式相关操作符
    • LEN:返回字符串的长度
      例:

      GBLS STR
      STR SETS "AAA"
      :LEN: STR    ;LEN = 3
      
    • CHR: 返回一个字符的ASCII值

      :CHR:A
      
    • STR: 将对应变量(数字量或逻辑量)的表示形式用字符串的形式表示
      例:

      GBLA A1
      A1 SETA 15
      :STR: A1
      
    • LEFT :返回一个字符从最左端的一定长度的字串
      格式:

      A :LEFT: B
      

      A为源字符串;B为数字量,表示返回字符个数
      例:

      GBLS STR1
      GBLS STR2
      SETS STR1 "AAAABBBB"
      SETS STR2 STR1 :LEFT:3 ;结果为STR2为 “AAA”
      

      RIGHT同理,返回最右端一定长度的字串

    • CC:连接2个字符串

      A :CC: B
      

      A为第一个源字符串;B为第二个源字符串
      例:

      GBLS STR1
      GBLS STR2
      STR1 SETS "AAACCC"
      STR2 SETS "BBB" :CC: (STR1 :LEFT: 3) ; 结果为STR2 为"BBBAAA"
      
  2. 数字表达式相关操作符
    • NOT:按位取反
      :NOT: A
      
    • +、-、x、/及MOD: 算术操作符
      A + B ; 
      A - B;
      A x B;
      A / B;
      A :MOD: B;
      
    • ROL、ROR、SHL、SHR:循环移位操作
      A :ROL: B ;将整数A循环左移B位
      A :ROR: B;将整数A循环右移B位
      A :SHL: B ;将整数A左移B位空位补0
      A :SHR: B;将整数A右移B位空位补0
      
    • AND、OR、EOR:按位逻辑操作符
      A :AND: B ;按位与操作 
      A :OR: B;按位或操作
      A :EOR: B;按位异或操作
      
  3. 逻辑表达式
    • 关系操作符
      =、>、<、
      不等于 /= 或则 <>
    • 逻辑操作符
      :LNOT: ;A 取反
      A :LAND: B;逻辑与
      A :LOR: B;逻辑或
      A :LEOR: B;逻辑异或
      

汇编语言格式

ARM汇编语言是以段(section)为单位来组织源文件的。
段是相对独立的、具有特定名称的、不可分割的指令或者数据序列。
段又可以分为代码段和数据段。一个源程序至少需要一个代码段。

例:

AREA EXAMPLE(段的名字), CODE(代码段), READONLY(只读)
ENTRY(程序入口点)
start
...
END

汇编语言程序重点

  1. 子程序的调用
    子程序的调用通过BL指令来完成
    语法格式为

    BL subname(被调用子程序的名称)
    
  2. 子程序的返回
    在返回调用子程序时,将LR寄存器(R14)中的值拷贝回程序寄存器(R15)
    当没有嵌套时

    SUB2 ...
    	MOV PC, R14
    

    当发生嵌套时, 对于在子程序中出现嵌套调用时,链接寄存器LR中的返回地址可能会在第二次调用时被覆盖,所以需要将返回地址压入堆栈来进行保存

    SUB1 STMFD SP!, {R0 - R7, R14}; 保存工作寄存器和链接
    BL SUB2
    ...
    LDMFD SP!, {R0 - R7, PC}; 恢复工作寄存器并返回 
    
  3. 跳转表
    类似于c++中的switch
    调用一系列子程序中的一个,而决定调用哪一个由程序的计算值决定。
    例:

    BL JUMPTAB; R0存放对应的跳转信息 0表示0号程序...
    
    JUMPTAB ADR R1, SUBTAB; R1 <- SUBTAB
    		CMP R0, #SUBMAX; 检查是否超限
    		LDRLS PC, [R1, R0, LSL #2]; 如果OK则跳转到表中
    		B ERROR; 否则发生错误
    		
    SUBTAB DCD SUB0
    	   DCD SUB1
    	   DCD SUB2
    	   ...
    SUB MAX;散转表结束地址
    
  4. ARM与Thumb间的状态转换
    状态切换是通过一条专用的转移交换指令BX来实现的。
    当Rn寄存器中的目的地址的最后一位为0时转换到ARM状态
    当Rn寄存器中最后一位为1时,转换到Thumb状态
    Thumb地址是半字对齐的末尾一定是0

    CODE32
    	ADR R0, Into_Thumb + 1
    	BX R0
    	...
    CODE16
    	Into_Thumb:
    	...
    	ADR R5, BACK_TO_ARM
    	BX R5
    	...
    CODE32
    	Back_to_ARM
    	...
    

应用实例:

  1. 简单的ARM指令程序
    在这里插入图片描述
  2. 数据块复制
AREA STRINGCOPY, CODE, READ ONLY
NUM EQU 20
CODE 32
ENTRY
start
	LDR R0, =src
	LDR R1, =dst	
	LDR R2, #NUM; 移动字符串个数
	LDR SP, #0x400 ; 初始化堆栈指针
BACK; 备份
	STMFD SP!, {R4-R11}; 将R4与R11中的内容提前保护
	MOVS R3, R2, LSR #3; 移动次数NUM整除8的商
	BEQ YUSHU; 如果不满8个字符,看是否有余数
BLOCKCOPY ; 按块移动每次移动8个字符
	LDMIA R0!, {R4-R11} ;将src中8个字符块移动到R4到R11中
	STMIA R1!, {R4-R11} ;将移入的字符移到dst中
	SUBS R3, R3, #1
	BNE BLOCKCOPY; 未把商移动完就循环
	LDMFD SP!, {R4-R11}; 将保护的寄存器出栈
YUSHU
	ANDS R2, R2, #7; 将R2只保留8以内的余数了,方便后边一个字符的搬运
	BEQ stop
WCOPY ; 一个字符一个字符的移动
	LDR R3, [R0], #4; 字对齐每次移动4
	STR R3, [R1], #4
	SUBS R2, R2, #1
	BNE WCOPY
stop
	MOV R0, #0x18
	LDR R1, #0x20026
	SWI 0x123456
AREA DATA1, DATE, READWRITE
src DCD 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4
dst DCD 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
END
  1. 利用跳转表实现程序跳转
    在这里插入图片描述

C语言的一些技巧

  1. 变量定义

    • 把所有相同类型的变量放在一起定义,这样可以优化存储器空间
    • 局部变量使用32位int或unsinged int有时会更有效率。因为每个寄存器是32位的
    • 变量定义为了精简是要避免使用冗余变量。但有时使用冗余变量可以减少存储器的访问次数,提高系统性能,冗余变量一般在寄存器,一般变量在存储器
  2. 参数传递
    为了单独编译C语言程序和汇编程序能够相互调用,定义了同一的函数过程调用标准ATPCS
    ATPCS规则:

    • 寄存器的使用规则
      子程序通过寄存器R0 ~ R3来传递参数, 使用R4 ~ R11来保存局部变量,因此在进入子程序前要保存这些寄存器的值,返回后恢复,在Thumb中,只使用R4 ~ R7
    • 数据栈的使用规则
      ATPCS规定数据栈为FD(满递减)类型,并且对数据栈的操作时8字节对齐的
    • 参数的传递规则
      当参数固定的情况下,第一个整数参数通过寄存器R0 ~ R3来传递。其他参数通过数据栈传递
      当参数个数可变的子程序:当不超过4个时用R0 ~ R3 来传递,超过4个时,使用数据栈来传递参数
      结果返回是以32位为界,每多一个32就从R0 ~ R3中多增加一个来传递
  3. 循环时使用i -- 可以省一些汇编指令,更有效率

下一篇

未完待续

文章来源:https://blog.csdn.net/m0_64372178/article/details/135415946
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。