• 1. 第六章:基于ARM的嵌入式程序设计 目标: 本章主要讲述如何运用前面学过的汇编指令并结合C语言来进行嵌入式程序设计。通过本章的学习,学员应该掌握如下知识: ARM汇编下的伪操作、宏指令和伪指令 ARM汇编语言程序设计 嵌入式C语言程序设计基础 嵌入式C语言程序设计技巧 C语言与汇编语言的混合编程学时:7.0学时 教学方法:讲授ppt+点评+案例分析
  • 2. 6.1 ARM汇编下的伪操作、宏指令和伪指令ARM汇编语言程序中的语句一般由指令、伪操作、宏指令和伪指令组成。 伪操作(directive):ARM汇编语言程序里一些特殊指令助记符,其作用是为完成汇编程序做各种准备,在源程序汇编时由汇编程序处理,而不是在运行期间由机器执行。 伪指令(pseudo-instruction): ARM汇编语言程序里一些特殊指令助记符,在源程序汇编时由汇编程序将其置换为能完成其功能的一条或几条机器指令,从而实现真正的指令操作。 宏指令(Macro):即宏定义,是一段独立的程序代码,编辑是作为一条指令或一个函数使用,编译时展开为原来的代码。一般在子程序比较短,而需要传递的参数比较多的情况下使用效率较高,主要是省去了保护和恢复现场的操作和时间。 本节包括如下内容: 6.1.1 三种常见的ARM编译开发环境 6.1.2 ADS编译环境下的伪操作和宏指令 6.1.3 ARM汇编语言伪指令
  • 3. 6.1.1 三种常见的ARM编译开发环境ADS/SDT IDE开发环境:它由ARM公司开发,使用了CodeWarrior公司的编译器; 集成了GNU开发工具的IDE开发环境:它由GNU的汇编器as、交叉编译器gcc、和链接器ld等组成; IAR 提供的ARM开发环境WORKBENCH。
  • 4. 6.1.2 ADS编译环境下的伪操作和宏指令ADS编译环境下的伪操作可分为以下几类: 1 符号定义(Symbol Definition)伪操作 2 数据定义(Data Definition)伪操作 3 汇编控制(Assembly Control)伪操作 4 框架描述(Frame Description)伪操作 5 信息报告(Reporting)伪操作 6 其他(Miscellaneous)伪操作
  • 5. 1 符号定义伪操作 伪操作语法格式作 用GBLA GBLA Variable 声明一个全局的算术变量,并将其初始化成0。 GBLL GBLL Variable 声明一个全局的逻辑变量,并将其初始化成{FALSE}。 GBLS GBLS Variable 声明一个全局的字符串变量,并将其初始化成空串“”。 LCLA LCLA Variable 声明一个局部的算术变量,并将其初始化成0。 LCLL LCLL Variable 声明一个局部的逻辑变量,并将其初始化成{FALSE}。 LCLS LCLS Variable 声明一个局部的串变量,并将其初始化成空串“”。 SETA SETA Variable expr 给一个全局或局部算术变量赋值。 SETL SETL Variable expr 给一个全局或局部逻辑变量赋值。 SETS SETS Variable expr 给一个全局或局部字符串变量赋值。 RLIST Name RLIST{list of registers}为一个通用寄存器列表定义名称。 CN name CN expr 为一个协处理器的寄存器定义名称。 CP name CP expr 为一个协处理器定义名称。 DN/SN name DN/SN expr DN/SN为一个双精度/单精度的VFP寄存器定义名称。 FN name FN expr 为一个FPA浮点寄存器定义名称。
  • 6. 符号定义伪操作—例子GBLA Arithmetic ;声明一个全局的算术变量 Arithmetic SETA 0xEF ;向该变量赋值 SPACE Arithmetic ;引用该变量 GBLL Logical ;声明一个全局的逻辑变量Logical Logical SETL{TURE} ;向该变量赋值局部变量定义举例(局部变量作用范围在宏内)MACRO ;声明一个宏 $label message $a ;宏名称为message,有一个参数$a LCLS string ;声明一个全局串变量string string SETS “error” ;向该变量赋值 $lable ;代码 INFO 0,” string”:CC::STR:$a ;在汇编第2遍扫描时打印字符串 MEND ;宏定义结束RLIST伪操作举例全局变量定义举例(全局变量作用范围在包含该变量的源程序)List RLIST ;定义寄存器列表名称为list STMFD SP! List
  • 7. 2 数据定义伪操作 伪操作语法格式作 用LTORG LTORG 声明一个数据缓冲池(也称为文字池)的开始。 MAP MAP expr{,base-register} 定义一个结构化的内存表(Storage Map)的首地址。 FIELD {label} FIELD expr 定义一个结构化内存表中的数据域。 SPACE {label} SPACE expr 分配一块连续内存单元,并用0初始化。 DCB {label} DCB expr{,expr} 分配一段字节内存单元,并用expr初始化。 DCD/DCDU {label} DCD expr {,expr}… 分配一段字内存单元。 DCDO {label} DCDO expr{,expr}… 分配一段字对齐的字内存单元。 DCFD/ DCFDU {label} DCFD {U}fpliteral{,fpliteral}… 为双精度的浮点数分配字对齐的内存单元。 DCFS/ DCFSU {label} DCFS {U} fpliteral {,fpliteral}… 为单精度的浮点数分配字对齐的内存单元。 DCI {label} DCI expr{,expr}… 在ARM代码中分配一段字对齐的内存单元; 在Thumb代码中,分配一段半字对齐的半字内存单元。 DCQ/ DCQU {label} DCQ{U}{﹣}literal{,{﹣}literal}… 分配一段以双字(8个字节)为单位的内存 DCW/ DCWU {label} DCW{U}expr{,expr}… DCW用于分配一段半字对齐的半字内存单元。
  • 8. 数据定义伪操作—例子LTORG伪操作举例:用LTORG定义数据缓冲池。 AREA Example,CODE,READONLY ;声明一个代码段,名称为Example,属性为只读 Start BL Func1 …… Func1 LDR R1,=ox8000 ;R1<-0X8000 LDR伪指令 …… MOV PC,LR ;子程序返回 LTORG ;定义数据缓冲池 Data SPACE 40 ;从当前位置开始分配40字节的内存单元并初始化为0 END ;程序结束指令中的使用方式
  • 9. 指令中的使用方式LDR R0,consta ;将consta处的对应内容加载到R0 MAP 8192|{0,R9} ;内存表的首地址为8192(0x2000) consta FIELD 4 ;consta长度为4字节,相对位置为0 constb FIELD 4 ;constb长度为4字节,相对位置为4 x FIELD 8 ;x长度为8字节,相对位置为8 y FIELD 8 ;y长度为8字节,相对位置为16 string FIELD 16 ; string长度为16字节,相对位置为24MAP与FIELD伪操作举例(定义一个内存表,其首地址为固定地址8192即 0x2000,该内存表中包含5个数据域:consta长度为4字节;constb长度为4 字节;x长度为8字节;y长度为8字节;string长度为16字节。这种内存表称 为绝对地址的内存表。)
  • 10. 3 汇编控制伪操作 伪操作 语法格式 作 用 IF,ELSE及ENDIF IF logical expression … {ELSE …} ENDIF 能够根据条件把一段源代码包括在汇编语言程序内或者将其排除在程序之外。 WHILE及WEND WHILE logical expression … WEND 能够根据条件重复汇编相同的一段源代码。 MACRO 、MEND及MEXIT MACRO {$label} macroname {$parameter{,$parameter}…} … ;宏代码 MEND MACRO标识宏定义的开始,MEND标识宏定义的结束。MEXIT用于从宏中跳转出去。用MACRO和MEND定义的一段代码,称为宏定义体。通过宏名称来调用宏。
  • 11. 汇编控制伪操作—例子IF条件编译伪操作举例 IF Variable = 16 ;如果Variable = 16成立,则编译下面的代码 BNE SUB1 LDR R0,=SUB0 BX R0 ELSE BNE SUB0 …… ENDIFWHILE条件编译伪操作举例count SETA 1 ;设置循环计数变量为1 WHILE count<=4 count SETA count +1 …… WEND
  • 12. 4 框架描述伪操作 框架描述伪操作也称为栈数据帧描述,这类伪操作主要用于调试。这里不介绍这部分内容。需要了解和掌握这部分内容的学员可以参考ARM的相关资料。
  • 13. 5 信息报告伪操作 伪操作 语法格式 作 用 ASSERT ASSERT logical expression 对汇编程序的第二遍扫描中,如果其中ASSERT中条件不成立,ASSERT伪操作将报告该错误信息。 INFO INFO numeric-expression,string-expression numeric-expression为0,第二遍扫描时INFO伪操作打印string-expression;否则,在汇编处理过程的第一遍扫描时打印string-expression并终止编译 。 OPT OPT n 通过OPT伪操作可以在源程序中设置列表选项。 TTL TTL title 在列表文件的每一页的开头插入一个标题。 SUBT SUBT subtitle 在列表文件的每一页的开头插入一个子标题。
  • 14. 6 其他伪操作伪操作语法格式作 用CODE16 CODE16 告诉汇编编译器后面的指令序列为16位的Thumb指令CODE32 CODE32 告诉汇编编译器后面的指令序列为32位的ARM指令。EQU name EQU expr{,type} 为数字常量、基于寄存器的值和程序中的标号(基于PC的值)定义一个字符名称。 AREA AREA sectionname{,attr}{,attr}…定义一个代码段或者数据段。ENTRY ENTRY 指定程序的入口点。END END 告诉编译器已经到了源程序结尾。ALIGN ALIGN {expr{,offset}} 通过添加补丁字节使当前位置满足一定的对齐方式。EXPORT/ GLOBAL EXPORT symbol {[WEAK]} 声明一个符号可以被其他文件引用,相当于声明了一个全局变量。IMPORT IMPORT symbol {[WEAK]} 告诉编译器当前的符号不是在本源文件中定义的,而是在其他源文件中定义的,在本源文件中可能引用该符号。 EXTERN EXTERN symbol {〔WEAK〕} 告诉编译器当前的符号不是在本源文件中定义的,而是在其他源文件中定义的,在本源文件中可能引用该符号。 GET/ INCLUDE GET filename   将一个源文件包含到当前源文件中,并将被包含的文件在其当前位置进行汇编处理。 INCBIN INCBIN filename 将一个文件包含到当前源文件中,被包含的文件不进行汇编处理。KEEP KEEP{symbol} 告诉编译器将局部符号包含在目标文件的符号表中。NOFP NOFP 禁止源程序中包含浮点运算指令。REQUIRE REQUIRE lable 指定段之间的相互依赖关系。RN name RN expr 为一个特定的寄存器定义名称。ROUT {name} ROUT 定义局部变量的有效范围。
  • 15. 6.1.3 ARM汇编语言伪指令伪指令 语法格式 作 用 ADR ADR {cond} register,expr 将基于PC或基于寄存器的地址值读取到寄存器中。小范围的地址读取。 ADRL ADRL {cond} register,expr 将基于PC或基于寄存器的地址值读取到寄存器中。中等范围的地址读取。 LDR LDR {cond} register,=[expr | label-expr] 将一个32位的立即数或者一个地址值读取到寄存器中。大范围的地址读取。 NOP NOP 在汇编时将被替换成ARM中的空操作。
  • 16. 1 ARM汇编语言伪指令-ADRADR ADR{cond} register,expr 其中: register为目的寄存器 expr 当地址值不是字对齐时,其取值范围为-255255; 当地址值是字对齐时,其取值范围为-10201020; 当地址值是16字节对齐时,其取值范围更大;Start MOV r0, #0x10 ADR R1,start在汇编编译器处理源程序时替换为 SUB R1,pc,#0xc
  • 17. 2 ARM汇编语言伪指令-ADRLADRL ADRL{cond} register,expr 其中: register为目的寄存器 expr 当地址值不是字对齐时,其取值范围为-64KB64KB; 当地址值是字对齐时,其取值范围为-256KB256KB; 当地址值是16字节对齐时,其取值范围更大;Start MOV r0, #0x10 ADRL R4,start+60000在汇编编译器处理源程序时替换为(GNU环境下) ADD R4,PC, #84 ADD R4, R4, #59904 ;0xea60=60000
  • 18. 3 ARM汇编语言伪指令-LDRLDR LDR{cond} register,=[expr|label-expr] register为目的寄存器 expr 为32位常量。 当expr表示的地址值在MOV或MVN指令中地址范围内时,用一条MOV 或MVN代替; 当expr表示的地址值超过了MOV或MVN指令中地址范围内时,编译器 将该常数放在数据缓冲池中,同时用一条基于PC的LDR指令代替。 Label-expr为基于PC的地址表达式或外部表达式。
  • 19. LDR—例子LDR示例 LDR R1,=0xFF0 汇编后的结果 MOV R1,#0xFF0 LDR R1,=0xFFF 汇编后的结果 LDR R1,[PC,offset_to_LPOOL] …… LTORG ;声明数据缓冲池 LPOOL DCD 0xFFF ;0xFFF放在数据缓冲池中 LDR R1,=ADDR1 汇编后的结果 LDR R1,[PC,offset_to_LPOOL] …… ;声明数据缓冲池 LTORG LPOOL DCD ADDR1 ;ADDR1是标号,作为一个地址放在数据 ;缓冲池中(DCD用的是ADS下的伪指令)
  • 20. 4 ARM汇编语言伪指令-NOP空操作 语法格式 NOP 一般编译器替换成 MOV R0,R0 NOP伪指令不影响CPSR中的条件标志
  • 21. 6.2 ARM汇编语言程序设计6.2.1 ARM文件格式 6.2.2 ARM汇编语言语句格式 6.2.3 ARM汇编语言编程的重点 6.2.4 ARM汇编程序实例
  • 22. 6.2.1 ARM文件格式 ARM源程序文件(可简称为源文件)可以由任意一种文本编辑器来编写程序代码,它一般为文本格式。在ARM程序设计中,常用的源文件可简单分为以下几种:源程序文件文件名说 明汇编程序文件 *.S 用ARM汇编语言编写的ARM程序或Thumb程序。 C程序文件 *.C 用C语言编写的程序代码。 头文件 *.H 为了简化源程序,把程序中常用到的常量命名、宏定义、数据结构定义等等单独放在一个文件中,一般称为头文件。
  • 23. 6.2.2 ARM汇编语言程序设计ARM汇编语言语句格式如下所示: {symbol} {instruction | directive | pseudo-instruction} {;comment} instruction为指令,在一行语句中,指令前必须有 空格或符号。 directive为伪操作。 pseudo-instruction为伪指令。 symbol为符号。 comment为语句的注释。
  • 24. 1 ARM汇编语言符号符号的命名规则: 符号可代表地址、变量和数字常量。当符号代表地址时又称为标号。 符号包括变量、数字常量、标号和局部标号。 符号由大小写字母、数字以及下划线组成; 局部标号以数字开头,其他符号都不能以数字开头; 符号区分大小写; 符号中的所有字符都是有意义的; 符号在其作用范围内必须唯一; 程序中的符号不能与系统内部变量或者预定义符号同名; 程序中的符号不要与指令助记符或伪操作同名。
  • 25. 1 ARM汇编语言符号(续)变量: 变量有数字变量、逻变量和串变量3种。 全局变量由GBLA、GBLL、GBLS伪操作声明。 局部变量由LBLA、LBLL、LBLS伪操作声明。 变量由SETA、SETL、SETS伪操作赋值。数字常量: 数字常量是32位整数。 数字常量由EQU定义,其数值一经定义就不能再修改。标号: 标号是指令或数据的地址的符号表示。 基于PC的标号 基于寄存器的标号 绝对地址局部标号: 局部标号与ROUT伪操作配合使用。 定义:N{routname} ;N为099的数字 引用:%{F|B}{A|T} N{routname}
  • 26. 2 ARM汇编语言中的表达式表达式优先级括号内的表达式优先级最高 各种操作符有一定的优先级 相邻的单目操作符的执行顺序为由右到左,单目操作符优先级高于其他操作符 优先级相同的双目操作符执行顺序为由左到右表达式执行时的顺序表达式由符号、数值、单目或多目操作符以及括号组成
  • 27. 字符串表达式字符串表达式由字符串、字符串变量、操作符以及括号组成 字符串 例子:“this is a string” 字符串变量, 由GBLS或LBLS声明,由SETS赋值 操作符: 名称语法格式说明LEN:LEN: A返回字符串长度CHR:CHR: A将0255之间的整数作为含一个ASCII字符的字符串STR:STR: A将一个数字量或逻辑表达式转换成串LEFTA :LEFT: N返回A左端N个字符的子串RIGHTA :RIGHT: N返回A右端N个字符的子串CCA :CC: B字符串A与字符串B连接成一个字符串
  • 28. LEN操作符举例GBLS STR STR SETS “AAA” : LEN: STR ;LEN=3
  • 29. 数字表达式表达式由数字常量、数字变量、操作符以及括号组成 整数数字量 十进制数 十六进制数,以0x或&开头 n进制数,形式为n_base-n-digits 浮点数字量 {-} digits E{-} digits 如:1E308 {-} digits. digits E{-} digits 如:3.725e15 以0x或&开头的十六进制数 数字变量 名称语法格式说明NOT:NOT: A按位取反双目A:MOD:B+,-,,/,MOD逻辑A:AND:BAND,OR,EOR移位A:ROR:NROL,ROR,SHL,SHR
  • 30. 3 ARM汇编语言程序格式 ARM汇编语言是以段(section)为单位来组织源文件的。段是相对独立的、具有特定名称、不可分割的指令或者数据序列。段又可以分为代码段和数据段,代码段存放执行代码,数据段存放代码运行时需要用到的数据。一个ARM源程序至少需要一个代码段,大的程序可以包含多个代码段和数据段。 ARM汇编语言源程序经过汇编处理后生成一个可执行映像文件,它通常包括3部分: 一个或多个代码段。代码段通常是只读的。 0或多个包含初值的数据段。通常是可读/写的。 0或多个不包含初值的数据段。初始化为0,通常是可读/写的。
  • 31. ARM汇编语言程序格式-例子AREA EXAMPLE,CODE,READONLY ENTRY start MOV r0,#10 MOV r1,#3 ADD r0,r0,r1 END举例说明ARM汇编语言源程序的基本结构
  • 32. 6.2.3 ARM汇编语言编程的重点1 ARM数据处理操作 2 设置条件码 3 汇编语言子程序调用及返回 4 跳转表
  • 33. 1 ARM数据处理操作ARM中数据的处理有以下三种形式: 简单的寄存器操作 立即数操作 寄存器移位操作 其中32位立即数在32位指令中的编码以及ARM特有的寄存器移位操作是数据处理方面的难点。
  • 34. 1 ARM数据处理操作(续)简单的寄存器操作 ADD R0,R1,R2 立即数操作 ADD R3,R3,#1 AND R8,R7,#&FF 寄存器移位操作 ADD R3,R2,R1,LSL #3 ADD R5,R5,R3,LSL R2
  • 35. 2 设置条件码 ARM的任何数据处理指令都能通过增加“S”操作码来设置条件码(N,Z,C和V)。 条件执行 ARM指令集不同寻常的特征是每条指令(除了某些v5T指令)都可以是条件执行的。 条件转移 在程序中可以通过条件码的使用让微处理器决定是否进行转移,还可用来控制循环的退出。
  • 36. 2 设置条件码(续)条件执行 Example CMP r0, #5 ; BEQ BYPASS ; if (r0!=5) { ADD r1, r1, r0 ; r1:=r1+r0-r2 SUB r1, r1, r2 ; } BYPASS ... 使用条件执行 CMP r0, #5 ; ADDNE r1, r1, r0 ; SUBNE r1, r1, r2 ; ... 例子2 ; if ((a==b) && (c==d)) e++;下面汇编程序段实现此语句功能 CMP r0, r1 CMPEQ r2, r3 ADDEQ r4, r4, #1
  • 37. 2 设置条件码(续)条件转移 MOV R0,#0 LOOP ADD R0,R0,#1 CMP R0,#10 BNE LOOP …… 所有转移条件见书表5-3
  • 38. 设置条件码 MOV R0,#0 LOOP ADD R0,R0,#1 CMP R0,#10 BNE LOOP ……
  • 39. 3 汇编语言子程序调用及返回 子程序的调用 在ARM汇编语言中,子程序调用是通过BL指令来完成的。BL指令的语法格式如下: BL subname 其中,subname是被调用的子程序的名称。 子程序的返回 在返回调用子程序时,转移链接指令保存到LR寄存器(r14)中的值需要拷贝回程序寄存器PC(r15)。
  • 40. 3 汇编语言子程序调用及返回(续)SUB2 ….. MOV PC,R14 ;把R14拷贝到R15来返回SUB1 STMFD R13!,{R0-R12,R14} ;保存工作寄存器和链接 BL SUB2 …… LDMFD R13!,{R0-R12,PC} ;恢复工作寄存器并返回 AREA EXAMPLE,CODE,READONLY ENTRY Start MOV R0,#10 ;设置输入参数R0 MOV R1,#3 ;设置输入参数R1 BL Doadd ;调用子程序Doadd …… Doadd ADD R0,R0,R1 ;子程序 MOV PC,LR ;从子程序中返回 END ;结束汇编
  • 41. 4 跳转表思想 在程序设计中,有时为使程序完成一定的功能,需要调用一系列子程序中的一个,而决定究竟调用哪一个由程序的计算值确定。跳转表是解决该问题的有效方案。跳转表是利用程序计数器PC在通用寄存器文件中的可见性来实现的,如下例所示:
  • 42. 6.2.4 ARM汇编程序实例AREA ARMex,CODE,READONLY ;设置本段程序的名称及属性,代 ;码段的名称ARMex Data equ 30 ;设置常量 ENTRY ;标记要执行的第一条指令 Start MOV RO,#Data ;设置参数 MOV R1,#3 ADD R0,R0,R1 ;R0=R0+R1 Stop MOV R0,#&18 ;软中断参数设置 LDR R1,=&20026 ;软中断参数设置 SWI 0x123456 ;将CPU的控制权交给调试器 END ;文件的结束标志例1 完整程序模块
  • 43. 把C函数转换为汇编函数打印整数0~9的平方数 #include int square(int i) int main(void) { int i; for(i=0;i<10;i++) { printf(“squrare of %d is %d\n”,i,square(i)); } } int square(int i) { return i*I; }将square函数改写成执行结果相同的汇编函数 AREA |.text|, CODE,READONLY EXPORT square Square MUL r1,r0,r0 ;r1=r0*r0 MOV r0,r1 ;r0=r1 MOV pc,lr ;return END
  • 44. 条件执行时的汇编代码if (i<10) { c=i+’o’; } else { c=i+’A’-10; } 用汇编语言的条件执行来重 写这个例子。 CMP i,#10 ADDLO c,i,#’o’ ADDHS c,i,#’A’-10
  • 45. 条件执行时的汇编代码字符c是元音字母时的情况 if(c==‘a’||c==‘e’||c==‘i’||c==‘o’||c==‘u’) { vowel++; }利用条件执行编写的汇编代码如下: TEQ c,#‘a’ TEQNE c,#‘e’ TEQNE c,#‘i’ TEQNE c,#‘o’ TEQNE c,#‘u’ ADDEQ vowel,vowel,#1
  • 46. 循环结构的汇编实现减计数循环,循环计数值i计数为N~1,循环的结束条件是i=0. MOV i,N Loop SUBS i,i,#1 BGT loop
  • 47. 小结ARM汇编语言程序中的语句一般由指令、伪操作、宏指令和伪指令组成。 伪操作的6个类型:符号定义(Symbol Definition)伪操作 ,数据定义(Data Definition)伪操作 ,汇编控制(Assembly Control)伪操作,框架描述(Frame Description)伪操作 ,信息报告(Reporting)伪操作 ,其他(Miscellaneous)伪操作。 伪指令ADR,ADRL,LDR及NOP的使用。 4. 在ARM程序设计中,常用的源文件可简单分为以下几种:汇编程序文件,C程序文件,头文件。 5. ARM的任何数据处理指令都能通过增加“S”操作码来设置条件码(N,Z,C和V)。 6. 汇编语言的子程序调用返回语句。
  • 48. 6.3 嵌入式C语言程序设计基础嵌入式C语言程序设计首先是C语言程序设计,必须符合C语言基本语法。嵌入 式C语言程序设计又是面向嵌入式的应用,目标是开发安全可靠且按特定目标优 化的嵌入式应用程序。6.3.1 C语言“预处理伪指令”在嵌入式程序设计中的应用 6.3.2 嵌入式程序设计中的函数及函数库 6.3.3 嵌入式程序设计中常用的C语言语句 6.3.4 嵌入式程序设计中C语言的变量、数组、结构和联合
  • 49. 6.3.1 C语言“预处理伪指令” 在嵌入式程序设计中的应用 在C语言中,若#号是某行的第一非空字符,则表示该行是预处理程序指令,“预处理命令”可以改进程序设计的环境,提高编程效率,可分为以下三种 :1 文件包含 2 宏定义 3 条件编译
  • 50. 1 文件包含伪指令 文件包含伪指令可将头文件包含到程序中,头文件中定义的内容包括符号常量、复合变量原型、用户定义的变量类型原型和函数的原型说明等。编译器编译预处理时用文件包含的正文内容替换到实际程序中。 文件包含伪指令的格式: #include<头文件名.h> ;标准头文件 #include“头文件名.h” ;自定义头文件 #include 宏标识符
  • 51. 1文件包含伪指令(续)#ifdef __BIG_ENDIAN #define rRTCCON (*(volatile unsigned char *)0x57000043) #define rTICNT (*(volatile unsigned char *)0x57000047) #define rRTCALM (*(volatile unsigned char *)0x57000053) #define rALMSEC (*(volatile unsigned char *)0x57000057) …… #else #define rRTCCON (*(volatile unsigned char *)0x57000040) #define rTICNT (*(volatile unsigned char *)0x57000044) #define rRTCALM (*(volatile unsigned char *)0x57000050) #define rALMSEC (*(volatile unsigned char *)0x57000054) …… #endif
  • 52. 1文件包含(续) 宏定义标示符的使用#define MYINCLUDE “C:\Embeded\Samsung\Arm2410\def.h”#include MYINCLUDE 宏经扩展后为#include “C:\Embeded\Samsung\Arm2410\def.h”
  • 53. 2 宏定义伪指令宏定义伪指令分为:简单宏、参数宏、条件宏、预定义宏及宏释放。 1)简单宏: # define 宏标识符 宏体 (通常把简单宏定义的宏标识符称为符号常量 ) 2)参数宏: # define 宏标识符(形式参数表) 宏体 3)条件宏定义: #ifdef 宏标识符 #ifndef 宏标识符 #undef 宏标识符 #define 宏标识符 宏体 #define 宏标识符 宏体 #else #else #undef 宏标识符 #define 宏标识符 宏体 #define 宏标识符 宏体 #endif #endif
  • 54. 2宏定义伪指令(续)用条件宏实现块大小的定义#ifndef BLOCK-SIZE #define __ BLOCK-SIZE 128__ #else #undef BLOCK-SIZE #define BLOCK-SIZE 128 #endif4)宏释放: # undef 宏标识符#define BLOCK-SIZE 512 …… buf= BLOCK-SIZE * blks /* buf=512*blks */ …… #undef BLOCK-SIZE #define BLOCK-SIZE 128 …… buf= BLOCK-SIZE * blks /* buf=128*blks */
  • 55. 3条件编译条件编译伪指令是写给编译器的,指示编译器在满足某一条件时仅编译源文件中与之相应的部分。其格式如右框中所示: #if(条件表达式1) … #elif(条件表达式2) … #elif(条件表达式n) … #else … #endif
  • 56. 3条件编译—例子#if OS_CRITICAL_METHOD == 3 OS_CPU_SR cpu_sr; cpu_sr = 0; #endif #if OS_TASK_CREATE_EXT_EN > 0 void *OSTCBExtPtr; OS_STK *OSTCBStkBottom; INT32U OSTCBStkSize; INT16U OSTCBOpt; INT16U OSTCBId; #endif
  • 57. 6.3.2 嵌入式程序设计中的函数及函数库 函数是C语言程序设计的核心。一个较大的C语言程序一般是由一个主函数和若干个子函数组成,每个函数完成一个特定的功能。函数之间也可以相互调用。 函数的格式: 1)定义性说明格式 : [存储类说明符] 类型说明符 [修饰符] 标识符(参数表){函数体}; 2)原型说明格式 : extern 类型说明符 [修饰符] 标识符 (参数表){函数体}
  • 58. 6.3.2嵌入式程序设计中的函数及函数库 函数库是为了减少编程工作量,将一些常用的功能的函数放在函数库中供公共使用.它包括C的标准库函数,也包括一些用户自己编写非标准库。 例如, uhal.h 是根据基于S3C2410X处理器的开发板及其功能模块编写的一个C语言函数库头文件。该头文件对程序开发中所用到的函数进行了声明,这些函数构成了一个基本函数库uhal.c,用于用户的程序开发,它不属于标准的C语言库。
  • 59. 6.3.3 嵌入式程序设计中常用的C语言语句 C语言语句格式为: [标号:] 语句[;] C语言语句很多,常用到的有以下几种: 1.条件语句 2.swith语句 3.循环语句
  • 60. 6.3.4 嵌入式程序设计中C语言的 变量、数组、结构、联合 变量 [存储类型] 类型说明符 [修饰符] 标识符 [=初值] [,标识符[=初值]]…; 数组 一维数组: 类型说明符 标识符 [常量表达式][={初值,初值,…}]; char 标识符[ ] =“字符串”; 二维数组: 类型说明符 标识符[m][n] [={{初值表},{初值表}…}]; 指针数组和数组指针 类型说明符 *标志符 [常量表达式] [={地址,地址,…}]; 类型说明符 (*标志符)[ ][=数组标识符];
  • 61. 结构结构说明 [存储类说明符] struct [结构原型名] { 类型说明标识符[,标识符…]; 类型说明标识符[,标识符…]; … }标识符[={初值表} [,标识符[={初值表}]…];
  • 62. 联合联合说明 [存储类说明符] union[联合原型名] {类型说明符 标识符[,标识符…]; 类型说明符 标识符[,标识符…]; … }标识符 ={初值表}[,标识符[={初值表}]…];
  • 63. 6.4 嵌入式C程序设计技巧 开发高效率的程序涉及很多方面,包括编程风格、算法实现、针对目标的特定优化等。 4.6.1 变量定义 4.6.2 参数传递 4.6.3 循环条件
  • 64. 4.6.1变量定义 在变量声明的时候,最好把所有相同类型的变量放在一起定义,这样可以优化存储器布局。由下例可以看出: 对于局部变量类型的定义,使用short或char来定义变量并不是总能节省存储空间。有时使用32位int或unsinged int局部变量更有效率一些,如下图所示: 变量定义中,为了精简程序,程序员总是竭力避免使用冗余变量。但有时使用冗余变量可以减少存储器访问的次数这可以提高系统性能。
  • 65. 冗余变量例子int f(viod); int g(viod); int errs; void test1(void) { errs += f(); errs += g(); }int f(viod); int g(viod); int errs; void test2(void) { int localerrs = errs; localerrs += f(); localerrs += g(); errs= localerrs; }
  • 66. 4.6.2 参数传递 为了使单独编译的C语言程序和汇编程序能够互相调用,定义了统一的函数过程调用标准ATPCS。ATPCS定义了寄存器组中的{R0~R3}作为参数传递和结果返回寄存器,如果参数数目超过四个,则使用堆栈进行传递。 内部寄存器的访问速度是远远大于存储器的,所以要尽量使参数传递在寄存器里面进行,即应尽量把函数的参数控制在4个以下。
  • 67. 参数传递—例子 AREA StrCopy,CODE,READONLY EXPORT strcopy ENTRY strcopy LDR R2,[R1],#1 STR R2,[R0],#1 CMP R2,#0 BNE strcopy MOV PC,LR END 从C语言中直接调用汇编语言函数extern void strcopy(char *d, const char *s); int main(viod) { const char src=“Source”; char dest[10]; …… strcopy(dest, src); …… }汇编程序模块
  • 68. 4.6.3 循环条件 计数循环是程序中十分常用的流程控制结构,一般有以下两种形式: for (loop=1;loop<=limit;loop++) for (loop=limit;loop!=0;loop--) 这两种循环形式在逻辑上并没有效率差异,但是映射到具体的体系结构中时,就产生了很大的不同,如下图所示。
  • 69. 4.5 C语言与汇编语言混合编程4.6.1 ATPCS介绍 4.6.2 内嵌汇编 4.6.3 C语言和ARM汇编程序间的相互调用
  • 70. 4.6.1 ATPCS介绍 ATPCS(ARM-Thumb Produce Call Standard)是ARM程序和Thumb程序中子程序调用的基本规则,目的是为了使单独编译的C语言程序和汇编程序之间能够相互调用。这些基本规则包括子程序调用过程中寄存器的使用规则、数据栈的使用规则和参数的传递规则。
  • 71. 1 寄存器的使用规则寄存器别名特殊名使用规则R0a1  参数/结果/scratch寄存器1R1a2  参数/结果/scratch寄存器2R2a3  参数/结果/scratch寄存器3R3a4  参数/结果/scratch寄存器4R4v1  ARM状态局部变量寄存器1R5v2  ARM状态局部变量寄存器2R6v3  ARM状态局部变量寄存器3R7v4wrARM状态局部变量寄存器4 Thumb状态工作寄存器R8v5  ARM状态局部变量寄存器5R9v6sbARM状态局部变量寄存器6, 在支持RWPI的ATPCS中为静态基址寄存器R10v7slARM状态局部变量寄存器7, 在支持数据栈检查的ATPCS中为数据栈限制指针R11v8fpARM状态局部变量寄存器8/帧指针R12  ip子程序内部调用的scratch寄存器(IP)R13  sp数据栈指针(SP)R14  lr连接寄存器(LR)R15  pc程序计数器(PC)
  • 72. 2 数据栈的使用规则 根据堆栈指针指向位置的不同 和增长方向的不同可以分为以下4种数据栈 : ATPCS规定数据栈为FD(满递减)类型,并且对数据栈的操作是8字节对齐的(栈指针加2个字)。 FD (Full Descending) 满递减 ED (Empty Descending)空递减 FA (Full Ascending) 满递增 EA (Empty Ascending) 空递增
  • 73. 3 参数的传递规则参数个数固定的子程序参数传递规则: 参数个数可变的子程序参数传递规则: 子程序结果返回规则 第一个整数参数,通过寄存器R0~R3来传递。其他参数通过数据栈递。当参数不超过4个时,可以使用寄存器R0 ~ R3来传递参数;当参数超过4个时,还可以使用数据栈来传递参数。结果为一个32位的整数时,可以通过寄存器R0返回;结果为一个64位 整数时,可以通过寄存器R0和R1返回,依次类推。
  • 74. 4 ARM程序和Thumb程序混合使用的ATPCS通过使用选项interwork情况: 程序中存在ARM程序调用Thumb程序的情况 程序中存在Thumb程序调用ARM程序的情况 需要链接器来进行ARM状态和Thumb状态切换的情况通过使用选项nointerwork情况: 程序中不包含Thumb程序 用户自己进行ARM状态和Thumb状态切换
  • 75. 4.6.2 内嵌汇编 在C程序中嵌入汇编程序可以实现一些高级语言没有的功能,并可以提高执行效率。armcc和armcpp内嵌汇编器支持完整的ARM指令集。内嵌的汇编指令包括大部分的ARM指令和Thumb指令,但是不能直接引用C的变量定义,数据交换必须通过ATPCS进行。嵌入式汇编在形式上表现为独立定义的函数体。
  • 76. 1 内嵌汇编指令的语法格式 __asm(“指令[;指令]”); ARM C汇编器使用关键字“__asm"。如果有多条汇编指令需要嵌入,可以用“{}”将它们归为一条语句。如: __asm { 指令[;指令] … [指令] } 需要特别注意的是__asm是两个下划线。
  • 77. 2 内嵌汇编指令的特点 操作数 可以是寄存器、常量或C表达式。它们可以是char、short或者int类型,而且是作为无符号数进行操作 。 物理寄存器 内嵌的汇编指令中使用物理寄存器有一些限制。 常量 常量前的符号“#”可以省略 标号 只有指令B可以使用C程序中的标号,指令BL不能使用C程序中的标号。 不支持汇编语言中用于内存分配的伪操作。 指令中如果包含常量操作数,该指令可能会被汇编器展开成几条指令。
  • 78. 不能直接向PC寄存器中赋值,程序的跳转只能通过B指令和BL指令实现。 在使用物理寄存器的内嵌汇编指令中,不要使用过于复杂的C语言表达式。 编译器可能会使用R12寄存器或R13寄存器存放编译的中间结果,在计算表达式值时可能会将寄存器R0~R3、R12以及R14用于子程序调用。 在内前的汇编指令中使用物理寄存器时,如果有C语言变量使用了该物理寄存器,则编译器将在合适的时候保存并恢复该变量的值。 通常在内嵌的汇编指令中不要指定物理寄存器,因为这些可能会影响编译器分配寄存器,进而可能影响代码的效率。
  • 79. 内嵌汇编器与armasm汇编器的区别 不支持LDR Rn,= expression伪指令,而使用MOV Rn, expression指令向寄存器赋值; 不支持标号表达式; 不支持ADR和ADRL伪指令; 不支持BX和BLX指令; 不可以向PC赋值; 使用0x前缀替代“&”表示十六进制数。 内嵌汇编器不支持通过“·”指示符或PC获取当前指令地址;
  • 80. 3 内嵌汇编注意事项 必须小心使用物理寄存器,如R0R3,LR和PC。 不要使用寄存器寻址变量。 使用内嵌汇编时,编译器自己会保存和恢复它可能用到的寄存器,用户无须保存和恢复寄存器。 LDM和STM指令的寄存器列表只允许物理寄存器。 汇编汇编语言用“ ,”作为操作数分隔符
  • 81. 内嵌汇编指令应用举例(1)字符串复制#include void my_strcopy(char *scr, connst char *dst) { int ch; __asm { loop: #ifndef _arm //ARM版本 LDRB ch,[src],#1 STRB ch,[dst],#1 #else //Thumb版本 LDRB ch,[src] ADD src,#1 STRB ch,[dst] ADD dst,#1 #endif CMP ch,#0 BNE loop } } int main(viod) { const char *a=“Hello world!”; char b[20]; __asm { MOV R0,a //设置入口参数 MOV R1,b BL my_strcopy(R0, R1) //调用函数 } Printf(“Original string: %s\n”,a); Printf(“Copied string: %s\n”,b); return 0; }
  • 82. 内嵌汇编指令应用举例(2)使能和禁止中断__inline void enable_IRQ(void) { int tmp; __asm { MRS tmp,CPSR BIC tmp,tmp,#0x80 //修改CPSR位7为 0 MSR CPSR_c,tmp } } __inline void disable_IRQ(void) { int tmp; __asm { MRS tmp,CPSR ORR tmp,tmp,#0x80 MSR CPSR_c,tmp } } int main(viod) { disable_IRQ(); printf(“\nThis is in critical section.”); enable_IRQ(void); }
  • 83. 4.6.3 C和ARM汇编程序间相互调用 在C和ARM汇编程序之间相互调用必须遵守ATPCS(ARM-Thumb Procedure Call Standard)规则。 C和汇编之间的相互调用从以下三方面介绍:1、汇编程序对C全局变量的访问 2、在C语言程序中调用汇编程序 3、在汇编程序中调用C语言程序
  • 84. 1 汇编程序对C全局变量的访问 汇编程序可以通过地址间接访问在C语言程序中声明的全局变量。通过使用IMPORT关键词引人全局变量,并利用LDR和STR指令根据全局变量的地址可以访问它们。 对于不同类型的变量,需要采用不同选项的LDR和STR指令,如下所示: unsigned char LDRB/STRB unsigned short LDRH/STRH unsigned int LDR/STR char LDRSB/STRSB short LDRSH/STRSH
  • 85. 汇编程序访问全局C变量—例子 AREA globals,CODE,READONLY EXPORT asmsubroutine ;用EXPORT伪操作声明该变量可被其他文件引用,相当于声明 ;了一个全局变量 IMPORT globvar ;用IMPORT伪操作声明该变量是在其他文件中定义的,在本文件中可能 ;要用到该变量 ENTRY asmsubroutine LDR R1,= globvar ;从文字池读globvar的地址,并将其保存到R1 LDR R0,[R1] ;再将其值读入到寄存器R0中 ADD R0,R0,#2 STR R0,[R1] ;修改后再将寄存器R0的值赋予变量globvar MOV PC,LR END C语言全局变量在汇编程序中的访问
  • 86. 2 在C语言程序中调用汇编程序 为了保证程序调用时参数的正确传递,汇编程序的设计要遵守ATPCS。在汇编程序中需要使用EXPORT伪操作来声明,使得本程序可以被其它程序调用。同时,在C程序调用该汇编程序之前需要在C语言程序中使用extern关键词来声明该汇编程序。
  • 87. 在C语言程序中调用汇编程序—例子#include Extern void strcopy(char *d, connst char *s) ;(1) Int main(void) { const char *srcstr=“First string-source”; char *dststr=“Second string-destination”; printf(“before copying: \n”); printf(“%s\n %s\n”, srcstr, dststr); strcopy(dststr,srcstr) ; (2) printf(“After copying: \n”); printf(“%s\n %s\n”, srcstr, dststr); return 0; } AREA Scopy,CODE,READONLY EXPORT strcopy (3) ENTRY strcopy (4) LDRB R2,[R1],#1 (5) STRB R2,[R0],#1 (6) CMP R2,#0 (7) BNE strcopy (8) MOV PC,LR (9) END C语言程序模块ARM汇编语言程序模块
  • 88. 3 在汇编程序中调用C语言程序 为了保证程序调用时参数的正确传递,汇编程序的设计要遵守ATPCS。在C程序中不需要使用任何关键字来声明将被汇编语言调用的C程序,但是在汇编程序调用该C程序之前需要在汇编语言程序中使用IMPORT伪操作来声明该C程序。在汇编程序中通过BL指令来调用子程序。
  • 89. 在汇编程序中调用C语言程序—例子int g(int a,int b,int c,int d,inte) { return a+b+c+d+e; } EXPORT f AREA function,CODE,READONLY IMPORT g ENTRY f STR LR,[SP,#-4]! (1) ADD R1,R0,R0 (2) ADD R2,R1,R0 (3) ADD R3,R1,R2 (4) STR R3,[SP,#-4]! (5) ADD R3,R1,R1 (6) BL g (7) ADD SP,SP,#4 (8) LDR PC,[SP],#4 (9) END C语言程序模块ARM汇编语言程序模块
  • 90. 6.6 嵌入式软件开发流程编写代码仅是嵌入式软件开发的第一步,还要学会在具体的编程环境下完成工程创建、源文件的编写、编译、链接、调试、固化等一系列工作。这就涉及到程序编译、链接、调试的具体配置以及开发板上硬件的初始化、程序下载等问题。 嵌入式软件开发的整个过程,如下图所示: 软件开发过程实践在实验活动中解决
  • 91. 小结1.C语言预处理伪指令的使用。 2.嵌入式程序中常用的C语言语句,如条件语句,switch语句及循环 语句等。 3.嵌入式C程序设计中的技巧:如变量定义,冗余变量的使用等。 4. ATPCS(ARM-Thumb Produce Call Standard)是ARM程序和 Thumb程序中子程序调用的基本规则,目的是为了使单独编译的C语 言程序和汇编程序之间能够相互调用。这些基本规则包括子程序调用 过程中寄存器的使用规则、数据栈的使用规则和参数的传递规则。 5.内嵌汇编的使用。
  • 92. 问与答谢谢! 如有问题 请提问
  • 93. Copyright © 2008 版权所有 东软集团