Embeded theory and appliment

本文最后更新于:1 年前

嵌入式原理与应用

题型:

  1. 选择(判断)
  2. 简述(解释)
  3. 简答
  4. 硬件设计(画图扩展)
  5. 编程、分析

重点:

image-20221203145401819 image-20221203141024153 image-20221203141214880 image-20221203141457717 image-20221203141626775

1.绪论

1.1嵌入式系统定义

  • 广义微处理器+硬/软件系统

  • 狭义嵌入式微处理器+OS+特定功能+专用+硬/软件系统

  • 从技术的角度定义以应用为中心、以计算机技术为基础、软件硬件可裁剪、适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。

  • 从系统的角度定义嵌入式系统是设计完成复杂功能的硬件和软件,并使其紧密耦合在一起的计算机系统。术语嵌入式反映了这些系统通常是更大系统中的一个完整的部分,称为嵌入的系统。嵌入的系统中可以共存多个嵌入式系统。

1.2嵌入式系统特点及分类

1.特点

  • 系统内核小
  • 专用性强
  • 系统精简
  • 高实时性
  • 多任务的操作系统
  • 需要专门的开发工具和环境(自身不具备)

2.分类

由于嵌入式系统由硬件和软件两大部分组成,所以其分类也可以从硬件和软件进行划分.

嵌入式系统的硬件

嵌入式系统软件

1.3嵌入式微处理器

1.3.1 ARM处理器

特点

  1. 小体积、低功耗、低成本而高性能;
  2. 16/32位双指令集;
  3. 全球的合作伙伴众多。

1.3.2 MIPS

MIPS是Microprocessor without Inter— locked Pipeline Stages的缩写,是一种处理器内 核标准,它是由MIPS技术公司开发的。

1.4嵌入式操作系统

1.概述

2.实时操作系统

1.特点
  1. 异步的事件响应
  2. 切换时间和中断延迟时间确定
  3. 优先级中断和调度
  4. 抢占式调度
  5. 内存锁定
  6. 连续文件
  7. 同步
2.分类
  • 一般实时操作系统:应用于实时处理系统的上位机和实时查询系统等实时性较弱的实时系统,并且提供了开发、调试、运用一致的环境。

  • 嵌入式实时操作系统:统应用于实时性要求高的实时控制系统, 而且应用程序的开发过程是通过交叉开发来完成的, 即开发环境与运行环境是不一致。

嵌入式实时操作系统具有规模小(一般在几K~几十 K 内)、可固化使用实时性强(在毫秒或微秒数量级上) 的特点

3.必要性
  • 嵌入式实时操作系统在目前的嵌入式应用中用得越来 越广泛,尤其在功能复杂、系统庞大的应用中显得愈 来愈重要。
  • 在嵌入式应用中,只有把CPU嵌入到系统中,同时又 把操作系统嵌入进去,才是真正的计算机嵌入式应用。
  1. 嵌入式实时操作系统提高了系统的可靠性。
  2. 提高了开发效率,缩短了开发周期。
  3. 嵌入式实时操作系统充分发挥了32位CPU的多任务 潜力。
4.优缺点
  1. 优点

    • 在嵌入式实时操作系统环境下开发实时应用程序使程序的设计和扩展变得容易,不需要大的改动就可以增加新的功能。
    • 通过将应用程序分割成若干独立的任务模块,使应用程序的设计过程大为简化;而且对实时性要求苛刻的事件都得到了快速、可靠的处理。
    • 通过有效的系统服务,嵌入式实时操作系统使得系统资源得到更好的利用
  2. 缺点

  • 需要额外的ROM/RAM开销,2~5%的CPU额外负荷, 以及内核的费用

3.前后台系统

对基于芯片的开发来说,应用程序一般是 一个无限的循环,可称为前后台系统或超循环系统。

4.操作系统

1.概念
  • 实时操作系统是一段在嵌入式系统启动后首先执 行的背景程序,用户的应用程序是运行于RTOS之上的各个任务,RTOS根据各个任务的要求,进行资源(包括存储器、外设等)管理、消息管理、任务调度、异常处理等工作。

  • 在RTOS支持的系统中, 每个任务均有一个优先级,RTOS根据各个任务的优先级,动态地切换各个任务,保证对实时性的要求.

  1. 是计算机中最基本的程序。
  2. 负责计算机系统中全部软硬资源的分配与回收、控制与协 调等并发的活动;
  3. 提供用户接口,使用户获得良好的工作环境;
  4. 为用户扩展新的系统功能提供软件平台。
2.代码的临界区

代码的临界区也称为临界区,指处理时不可分割的代码,运行这些代码不允许被打断。一旦这部分代码开始执行,则不允许任何中断打入 (这不是绝对的,如果中断不调用任何包含临界区的代码,也不访问任何临界区使用的共享资源,这个中断可能可以执行)。

为确保临界区代码的执行,在进入临界区之前要关中断,而临界区代码执行完成以后要立即开中断。

3.资源

程序运行时可使用的软、硬件环境统称为资源。

  • 资源可以是输入输出设备,例如打印机、 键盘、显示器。
  • 资源也可以是一个变量、一个结构或一个数组等。
4.共享资源

可以被一个以上任务使用的资源叫做共享资源。 为了防止数据被破坏,每个任务在与共享资源打交道时,必须独占该资源,这叫做互斥

5.任务

一个任务,也称作一个线程,是一个简单的程序, 该程序可以认为CPU完全属于该程序自己。

实时应用程序的设计过程,包括:

  1. 如何把问题分割成多个任务
  2. 每个任务都是整个应用的某一部分
  3. 每个任务被赋予一定的优先级
  4. 有它自己的一套CPU寄存器和自己的栈空间
6.任务切换

当多任务内核决定运行另外的任务时,它保存正 在运行任务的当前状态,即CPU寄存器中的全部内容。 这些内容保存在任务的当前状态保存区,也就是任务 自已的栈区之中。入栈工作完成以后,就把下一个将 要运行的任务的当前状态从任务的栈中重新装入CPU 的寄存器,并开始下一个任务的运行。这个过程就称为任务切换

这个过程增加了应用程序的额外负荷。CPU的内部寄 存器越多,额外负荷就越重。做任务切换所需要的时间取决于CPU有多少寄存器要入栈。

7.内核

多任务系统中,内核负责管理各个任务,或者说为每个任务分配CPU时间,并且负责任务之间的通信

  • 内核提供的基本服务是任务切换
  • 使用实时内核可以大大简化应用系统的设计,是因为实时内核允许将应用分成若干个任务,由实时内核来管理它们。内核需要消耗一定的系统资源,比如2%~5%的 CPU运行时间、RAM和ROM等。
  • 内核提供必不可少的系统服务,如信号量、消息队列、 延时等。
8.调度

调度是内核的主要职责之一。调度就是决定该轮到哪个任务运行了。

  • 多数实时内核是基于优先级调度法的。每个任务根据其重要程序的不同被赋予一定的优先级。基于优先级的调度法指CPU总是让处在就绪态的优先级最高的任务先运行。
  • 然而究竟何时让高优先级任务掌握CPU的使用权, 有两种不同的情况,这要看用的是什么类型的内核, 是非占先式的还是占先式的内核。
9.非占先式内核
  1. 非占先式内核要求每个任务自动弃CPU 的所有权。 非占先式调度法也称作合作型多任务,各个任务彼此合作共享一个CPU。
  2. 异步事件还是由中断服务来处理。
  3. 中断服务可以使一个高优先级的任务由挂起状态变为就绪状态。但中断服务以后控制权还是回到原来被中断了的那个任务,直到该任务主动放弃CPU的使用权时,那个高优先级的任务才能获得CPU的使用权。
10.占先式内核
  1. 当系统响应时间很重要时,要使用占先式内核。 因此绝大多数商业上销售的实时内核都是占先式内核。 最高优先级的任务一旦就绪,总能得到CPU的控制权。
  2. 当一个运行着的任务使一个比它优先级高的任务进入了就绪状态,当前任务的CPU使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了CPU 的控制权。
11.任务优先级

任务的优先级是表示任务被调度的优先程度。每个任务都具有优先级。 任务越重要,赋予的优先级应越高,越容易被调度而进入运行态

12.中断
  1. 中断是一种硬件机制,用于通知CPU有个异步事件发生了。
  2. 中断一旦被识别,CPU保存部分或全部上下文 (即部分或全部寄存器的值),跳转到专门的子程序, 称为中断服务子程序(ISR)。中断服务子程序做事件处理,处理完成后,程序返回到:
    • 在前后台系统中,程序回到后台程序
    • 对非占先式内核而言,程序回到被中断了的任务;
    • 对占先式内核而言,让进入就绪态的优先级最高的任务开始运行
13.时钟节拍

时钟节拍是特定的周期性中断

  • 这个中断可以看作是系统心脏的脉动。
  • 中断之间的时间间隔取决于不同应用,一般在10ms到200ms之间。
  • 时钟的节拍式中断使得内核可以将任务延时 若干个整数时钟节拍,以及当任务等待事件 发生时,提供等待超时的依据。
  • 时钟节拍率越快,系统的额外开销就越大。

2.体系结构

1.ARM处理器

1.ARM简介

ARM(Advanced RISC Machines),既可以认为是一个公司的名字,也可以认为是对一类微处理器的通称,还可以认为是一种技术的名字

ARM处理器核简介——ARM7
• 该系列包括ARM7TDMI、ARM7TDMI-S、带有高速缓存处理器宏单元的ARM720T和扩充了Jazelle( java加速器)的ARM7EJ-S。该系列处理器提供Thumb 16位压缩指令集和EmbededICE软件调试方式,适用于更大规模的SoC设计中。
• ARM7系列广泛应用于多媒体和嵌入式设备,包括Internet设备、网络和调制解调器设备,以及移动电话、PDA等无线设备。

ARM处理器核简介——Cortex
ARM11以后的产品改用Cortex命名,并分成A、R和M三类:
“A”系列面向尖端的基于虚拟内存的操作系统和用户应用——-如:智能手机、平板电脑等
“R”系列针对实时系统——如:汽车制动系统等
“M”系列针对微控制器—— STM32基于Cortex-M0/M3/M4核

ARM处理器核简介——SecurCore
• 该系列涵盖了SC100、SC110、SC200和SC210处理核。
• 该系列处理器主要针对新兴的安全市场,以一种全新的安全处理器设计为智能卡和其它安全IC开发提供独特的32位系统设计,并具有特定反伪造方法,从而有助于防止对硬件和软件的盗版。

2.RISC(Reduced Instruction Set Computer,精简指令集计算机)体系结构

RISC特点如下:
精简指令集计算机RISC结构的产生是相对于传统的复杂指令集计算机CISC结构而言的。

1
2
3
4
5
6
7
指令规整、对称、简单。指令小于100条,基本寻址方式有2~3种。
■ 单周期指令。
■ 指令字长度一致,单拍完成,便于流水操作;
■ ARM7 三级流水线:取址、译码、执行;
■ ARM9 五级流水线;
■ ARMl0 六级流水线。
■ 大量的寄存器。寄存器不少于32个。数据处理器的指令只对寄存器的内容操作。只有加载/存储指令可以访问存储器。

(1)使计算机的结构更加简单;
(2)合理地提高运算速度。

3.ARM和Thumb状态

正在执行Thumb指令集的处理器是工作在Thumb状态下——16位。
正在执行ARM指令集的处理器是工作在ARM状态下——32位。

4.寄存器

ARM处理器有37(31+6)个物理寄存器,有18个可编程访问的寄存器。

  • 寄存器被安排成部分重叠的组。每种处理器模式都有不同的寄存器组。
  • 分组的寄存器在异常处理和特权操作时,可得到快速的上下文切换。
image-20221203150113185
ARM状态下各模式寄存器
image-20230122162025400
  1. 在汇编语言中寄存器R0~R13为保存数据或地址值的通用寄存器。它们是完全通用的寄存器,不会被体系结构作为特殊用途,并且可用于任何使用通用寄存器的指令.

  2. 其中R0~R7为未分组的寄存器,也就是说对于任何处理器模式,这些寄存器都对应于相同的32位物理寄存器.

  3. 寄存器R8~R14为分组寄存器。它们所对应的物理寄存器取决于当前的处理器模式,几乎所有允许使用通用寄存器的指令都允许使用分组寄存器.

  4. 寄存器R13常作为堆栈指针(SP)。在ARM指令集当中,没有以特殊方式使用R13的指令或其它功能,只是习惯上都这样使用。但是在Thumb指令集中存在使用R13的指令.

  5. R14为链接寄存器(LR),在结构上有两个特殊功能: 在每种模式下,模式自身的R14版本用于保存子程序返回地址; 当发生异常时,将R14对应的异常模式版本设置为异常返回地址(有些异常有一个小的固定偏移量).

R14寄存器注意要点:

相同异常嵌套时,外部中断处理程序保存在R14_irq中的任何值都将被嵌套中断的返回地址所覆盖

IRQ服务程序A执行完毕, 将R14_irq寄存器的内容减去某个常量后存入PC,返回之前被中断的程序;

image-20230122164851577 image-20230122165002004

读R15的限制

  1. 正常操作时,从R15读取的值是处理器正在取指的地址,即当前正在执行指令的地址加上8个字节(两条ARM指令的长度)。 由于ARM指令总是以字为单位,所以R15寄存器的最低两位总是为0。
image-20230122165358890
  1. 当使用STR或STM指令保存R15时,会有一个例外: 这些指令可能将当前指令地址加8字节或加12字节保存(将来可能还有其它数字)。偏移量是8还是12取决于具体的ARM芯片,但是对于一个确定的芯片, 这个值是一个常量。 所以最好避免使用STR和STM指令来保存R15, 如果很难做到,那么应当在程序中计算出该芯片的偏移量。

  2. 正常操作时,写入R15 的值被当作一个指令地址,程序从这个地址处继续执行(相当于执行一次无条件跳转)。

寄存器CPSR为程序状态寄存器,在异常模式中,另外一个寄存器“程序状态保存寄存器(SPSR)”可以被访问。每种异常都有自己的SPSR,在因为异常事件而进入异常时它保存CPSR的当前值, 异常退出时可通过它恢复CPSR。

堆栈指针SP对应ARM状态的寄存器 R13。每个异常模式都有其自身的SP分组版本,SP通常指向各异常模式所专用的堆栈。 注意:在发生异常时,处理器自动进入ARM状态。

链接寄存器LR对应ARM状态寄存器 R14,在结构上有两个特殊功能:子程序返回地址、异常返回地址。 注意:在发生异常时,处理器自动进入ARM状态。

image-20230122192204555

5.指令集概述

  1. ARM指令集

ARM指令集可分为5大类指令,所有指令都可以条件执行,其中一些指令还可以根据执行结果更新CPSR寄存器的相关标志位.

  • 数据处理指令
  • 加载和存储指令
  • 分支指令
  • 协处理器指令
  • 杂项指令
  1. Thumb指令集

Thumb指令集可分为4大类指令:

  • 分支指令;
  • 数据处理指令;
  • 寄存器加载和存储指令;
  • 异常产生指令。

总结

1
2
3
4
ARM—公司名称、微处理器名称和嵌入式技术名称;
特点—高性能、小体积、低功耗、紧代码密度、多供应商、高占有率;
系列—ARM7、ARM9(E)、ARM10、ARM11、ARM Cortex、Xscale、StrongARM和SecurCore等;
结构—两种CPU工作状态、32位RISC结构、多寄存器、多种处理器模式 、两种指令集;

6.数据类型

ARM处理器支持下列数据类型:

  1. 字节 8位
  2. 半字 16位(必须分配为占用两个字节)
  3. 字 32位(必须分配为占用4个字节)

7.ARM微处理器的工作状态

ARM微处理器的工作状态一般有两种:

  1. ARM状态—处理器执行32位的、字对齐的 ARM指令;
  2. Thumb状态—处理器执行16位的、半字对齐的 Thumb指令。

处理器状态切换

  1. 进入Thumb状态:

    当操作数寄存器的状态位(位[0])为1时,执行 BX(带状态切换分支指令)进入Thumb状态。 如果处理器在Thumb状态进入异常,则当异常 处理返回时,自动转换到Thumb状态。

    1
    2
    3
    ;从Arm状态切换到Thumb状态
    LDR R0,=Lable+1
    BX R0
  2. 进入ARM状态:

    当操作数寄存器的状态位(位[0])为0时 执行BX指令进入ARM状态。 当处理器进行异常处理时,进入ARM状态,从异常向量地址处开始执行。

    1
    2
    3
    ;从Thumb状态切换到ARM状态
    LDR R0,=Lable
    BX R0

8.存储器格式

ARM体系结构可以用两种方法存储字数据,称为大端格式(Big-Endian)小端格式(Little-Endian )

小端格式

image-20221203153053759

大端格式

image-20221203153153991

示例

image-20230125161454291

大端反(高地址对应低字节),小端同(高地址对应高字节)

ARM默认小端格式,但用户可设置大、小端格式

9.处理器模式

ARM体系结构支持7种处理器模式,分别为:
用户模式、快中断模式、中断模式、管理模式、中止模式、未定义模式和系统模式。

image-20221203160553300
特权模式
image-20230122160458920
异常模式
image-20230122160528448
用户和系统模式
image-20230122161341215

10.异常

异常进入

在异常发生后,ARM7TDMI内核会(自动)做以下工作

  1. 在适当的LR中保存下一条指令的地址,当异常入口来自: ARM状态,那么ARM7TDMI将当前指令地址加4或加8复制(取决于异常的类型)到LR中;若为Thumb状态,那么保存当前PC值到LR中;这样异常处理程序就不需要确定异常是从何种状态进入的。

  2. 将CPSR复制到适当的SPSR中;

  3. 将CPSR模式位强制设置为与异常类型相对应的值;

  4. 强制PC从相关的异常向量处取指。

  • ARM7TDMI内核在中断异常时置位中断禁止标志,这样可以防止不受控制的异常嵌套。

  • 异常总是在ARM状态中进行处理,若在thumb状态,在异常向量地址装入PC时会自动切换到ARM状态

异常返回

当异常结束时,异常处理程序必须

  1. 将LR中的值减去偏移量后存入PC(手动调整!),偏移量根据异常的类型而有所不同;

  2. 将SPSR的值复制回CPSR;

  3. 若在进入异常处理时设置了中断禁止标志则清零该标志。

  • 恢复CPSR的动作会将T、F和I位自动恢复为异常发生前的值。
image-20230210163615090
异常向量表
image-20230122192828603
异常的入口和出口处理

如果异常处理程序已经把返回地址拷贝到堆栈, 那么可以使用一条多寄存器传送指令来恢复用户寄存器并实现返回。

1
2
3
4
SUB LR,LR,#4 ;计算返回地址(偏移-4)
STMFD SP!,{R0-R3,LR} ;保存使用到的寄存器
. . .
LDMFD SP!,{R0-R3,PC}^ ;中断返回(加载从左到右)

注意:中断返回指令的寄存器列表(其中必须包括PC)后的“^”符号表示这是一条特殊形式的指令。这条指令在从存储器中装载PC的同时(PC是最后恢复的), CPSR也得到恢复。 这里使用的堆栈指针SP(R13)是属于异常模式的寄存器,每个异常模式有自己的堆栈指针。 这个堆栈指针应必须在系统启动时初始化。

进入异常

在异常发生后,ARM7TDMI内核会(自动)做以下工作

  1. 在适当的LR中保存下一条指令的地址,当异常入口来自:
  • ARM状态,那么ARM7TDMI将当前指令地址加4或加8复制(取决于异常的类型)到LR中;

  • Thumb状态,那么保存当前PC值到LR中;这样异常处理程序就不需要确定异常是从何种状态进入的。

  1. 将CPSR复制到适当的SPSR中;

  2. 将CPSR模式位强制设置为与异常类型相对应的值;

  3. 强制PC从相关的异常向量处取指。

(还可以设置中断禁止位,以禁止其他中断)

注:

  • ARM7TDMI内核在中断异常时置位中断禁止标志,这样可以防止不受控制的异常嵌套。

  • 异常总是在ARM状态中进行处理。当处理器处于Thumb状态时发生了异常,在异常向量地址装入PC时,会自动切换到ARM状态。

退出异常

当异常结束时,异常处理程序必须

1.将LR中的值减去偏移量后存入PC(手动调整!),偏移量根据异常的类型而有所不同;

2.将SPSR的值复制回CPSR;

3.若在进入异常处理时设置了中断禁止标志则清零该标志。

注:

  • 恢复CPSR的动作会将T、F和I位自动恢复为异常发生前的值。
异常种类
快速中断请求(FIQ)

快速中断请求(FIQ)适用于对一个突发事件的快速响应,这得益于在ARM状态中,快中断模式有8个专用的寄存器可用来满足寄存器保护的需要(这可以加速上下文切换的速度)。

不管异常入口是来自ARM状态还是Thumb状态, FIQ处理程序都会通过执行下面的指令从中断返回:

SUBS PC,R14_fiq,#4

在一个特权模式中,可以通过置位CPSR中的F位来禁止FIQ异常。

中断请求(IRQ)

中断请求(IRQ)异常是一个由nIRQ输入端的低电平所产生的正常中断(在具体的芯片中,nIRQ由片内外设拉低,nIRQ是内核的一个信号,对用户不可见)。

IRQ的优先级低于FIQ。进入FIQ处理时FIQ和IRQ都被禁。在一个特权模式下,可通过置位CPSR中的I位来禁止IRQ。

不管异常入口是来自ARM状态还是Thumb状态,IRQ处理程序都会通过执行下面的指令从中断返回:

SUBS PC,R14_irq,#4

中止(ABT)

中止发生在对存储器的访问不能完成时, 中止包含两种类型:

  1. 预取中止: 发生在指令预取过程中

    当发生预取中止时,ARM7TDMI内核将预取的指令标记为无效,但在指令到达流水线的执行阶段时才进入异常。

    如果指令在流水线中因为发生分支而没有被执行,中止将不会发生。

    在处理中止的原因之后,不管处于哪种处理器操作状态,处理程序都会执行下面的指令恢复PC和CPSR并重试被中止的指令:

    SUBS PC,R14_abt,#4

  2. 数据中止: 发生在对数据访问时

​ 当发生数据中止后,根据产生数据中止的指令类型作出不同的处理:

​ 数据转移指令(LDR、STR)—–回写到被修改的基址寄存器。中止处理程序必须注意这一点;

​ 交换指令(SWP)——中止好像没有被执行过一样(中止必须发生在SWP指令进行读访问时);

​ 块数据转移指令(LDM,STM)完成。当回写被设置时,基址寄存器被更新。在指示出现中止后, ARM7TDMI内核防止所有寄存器被覆盖。这意味着ARM7TDMI内核总是会保护被中止的LDM指令中的R15(总是最后一个被转移的寄存器)。

​ 在修复产生中止的原因后,不管处于哪种处理器操作状态,处理程序都必须执行下面的返回指令 :

SUBS PC,R14_abt,#8

软件中断指令(SWI)

使用软件中断(SWI)指令可以进入管理模式, 通常用于请求一个特定的管理函数。

SWI处理程序通过执行下面的指令返回:

MOVS PC,R14_svc

这个动作恢复了PC和CPSR并返回到SWI之后的指令。SWI处理程序读取操作码以提取SWI函数编号。

未定义的指令(UND)

当ARM7TDMI处理器遇到一条自己和系统内任何协处理器都无法处理的指令时,ARM7TDMI 内核执行未定义指令陷阱。

软件可使用这一机制通过模拟未定义的协处理器指令来扩展ARM指令集。

注:ARM7TDMI处理器完全遵循ARM结构v4T,可以捕获所有分类未被定义的指令位格式。

在模拟处理了失败的指令后,陷阱程序执行下面的指令:

MOVS PC,R14_und

这个动作恢复了PC和CPSR并返回到未定义指令之后的指令。

异常向量
image-20230122202516763
异常优先级
image-20230122202621615

注意: 未定义的指令和SWI异常互斥。因为同一条指令不能既是未定义的,又能产生有效的软件中断;

当FIQ使能,并且FIQ和数据中止异常同时发生时:

(1)内核首先进入数据中止处理程序; (2)然后立即跳转到FIQ向量; (3)在FIQ处理结束后返回到数据中止处理程序。

数据中止的优先级必须高于FIQ以确保数据转移错误不会被漏过。

3.指令集

注意:

  • 所有的ARM指令都是可以有条件执行,而Thumb指令集只有一条指令(B)具有条件执行的功能。

  • ARM指令和Thumb指令可以相互调用,两者之间的状态切换所用的开销几乎为0

  • 立即数合法判断:
    立即数必须由 1 个 8 位的常数通过进行 32 位循环右移偶数位得到,其中循环右移的位数由一个 4 位二进制的两倍表示。即一个 8 位的常数通过循环右移 2*rotate_4 位(即 0,2,4,。。。30)得到
    1、大于8位的数右移出来的数不合法,
    2、小于等于8位的数移动奇数位也不合法

ARM处理器寻址方式

ARM处理器具有9种基本寻址方式。

寻址方式 注释 示例
立即寻址 立即寻址指令中后面的地址码部分为立即数(常量或常数),多用于给寄存器赋初值 MOV R0, #1234 ;指令执行后R0=1234,表十六进制数值以0x开头,如#0x20
寄存器寻址 寄存器寻址,操作数的值在寄存器中,指令执行从寄存器取值操作 MOV R0, R1 ;指令执行后,R0=R1
寄存器移位寻址 在操作前需要对源寄存器操作数进行移位操作, 支持五种移位操作:
LSL: 逻辑左移,移位后寄存器空出的低位补0;
LSR: 逻辑右移,移位后寄存器空出的高位补0;
ASR: 算术右移,移位过程中符号位保持不变,如果源操作数为正数,则移位后空出的高位补0,否则补1.
ROR:循环右移,移位后移出的低位填入空出的高位。
RRX:带扩展的循环右移,操作数右移一位,移位空出的高位用C标志的值填充。
MOV R0, R1, LSL #2 ;将R1寄存器左移2位,即R1<<2后赋值给R0寄存器
寄存器间接寻址 寄存器间接寻址是操作数的地址指针,所需的操作数保存在寄存器指定地址的存储单元 LDR R0, [R1];将R1寄存器的数值作为地址,取出此地址中的值赋值给R0寄存器
基址寻址 基址寻址是将基址寄存器与偏移量相加,形成操作数的有效地址。基址寻址多用于查表、数组访问等操作 LDR R0, [R1, #-4] ;将R1寄存器的数值减4作为地址,取出此地址的值赋给R0寄存器
多寄存器寻址 多寄存器寻址一条指令最多可以完成16个通用寄存器值的传送 LDMIA R0, {R1, R2, R3, R4}
;LDM是数据加载指令,指令IA表示每次执行完加载操作后R0寄存器的值自增1个字(ARM指令字表示一个32位数值),R1=[R0], R2=[R0+#4], R3=[R0+#8], R4=[R0+#12]
STMIA R0,{R1,LR} 先存LR,再存R1
STMDA R0,{R1,LR} 先存LR,再存R1
堆栈寻址(用于堆栈) 堆栈寻址需要使用特定的指令来完成,指令有个LDMFA/STMFA、LDMEA/STMEA、LDMFD/STMFD、LDMED/STMED. LDM和STM为指令前缀表示多寄存器寻址,一次可以传送多个寄存器值,FA/EA/FD/ED为指令后缀。
FD:满递减堆栈,堆栈向低地址生长,堆栈指针指向最后一个入栈的有效数据项
FA:满递增堆栈,堆栈向高地址生长,堆栈指针指向最后一个入栈的有效数据项
ED:空递减堆栈,堆栈向低地址生长,堆栈指针指向下一个要放入的空地址
EA:空递增堆栈,堆栈向高地址生长,堆栈指针指向下一个要放入的空地址
STMFD SP!, {R1-R7, LR} ;将R1R7,LR入栈。多用于保存子程序现场(从右到左)
LDMFD SP!, {R1-R7, LR} ;将数据出栈,放入R1\
R7,LR寄存器。多用于恢复子程序现场
块拷贝寻址(用于数据块传输) 快拷贝寻址可实现连续地址数据从存储器的某一位置拷贝到另一位置。指令有个LDMIA/STMIA、LDMDA/STMDA、LDMIB/STMIB、LDMDB/STMDB. LDM和STM为指令前缀表示多寄存器寻址,一次可以传送多个寄存器值,IA/DA/IB/DB为指令后缀.
IA:(Increase After)每次传送后地址加4,其中的寄存器从左到右执⾏
IB:(Increase Before)每次传送前地址加4,同上
DA:(Decrease After)每次传送后地址减4,其中的寄存器从右到左执⾏
DB:(Decrease Before)每次传送前地址减4,同上
LDMIA R0!, {R1-R3} ;从R0寄存器指向的存储单元中读取3个字数据到R1-R3寄存器
STMIA R0!, {R1-R3} ;存储R1-R3寄存器的内容到R0寄存器指向的存储单元
!:表示最后的地址写回到Rn中
相对寻址 相对寻址以程序计数器PC的当前值为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址 BL NEXT ;跳到NEXT标号处执行
……
NEXT: ;标号NEXT就是偏移量
……

ARM指令集

1.指令格式

image-20230123133643864

2.条件码

所有的ARM指令都可以条件执行,而Thumb指令只有B(跳转)指令具有条件执行功能。如果指令不标明条件代码,将默认为无条件(AL)执行

image-20230123135204279

3.ARM存储器访问指令

ARM处理器是典型的RISC处理器,对存储器的访问只能使用加载和存储指令实现。

ARM处理器是冯•诺依曼存储结构,程序空间、RAM空间及I/O映射空间统一编址,除对RAM操作以外,对外围IO、程序数据的访问均要通过加载/存储指令进行。

存储器访问指令分为单寄存器操作指令和多寄存器操作指令。

单寄存器加载指令
image-20230123135934463
单寄存器存储指令
image-20230123135952113

LDR/STR指令用于对内存变量的访问、内存缓冲区数据的访问、查表、外围部件的控制操作等。若使用LDR指令加载数据到PC寄存器,则实现程序跳转功能,这样也就实现了程序散转。

说明:

  • 所有单寄存器加载/存储指令可分为“字和无符号字节加载存储指令”,“半字和有符号字节加载存储指令“。

  • T为可选后缀。若指令有T,那么即使处理器是在特权模式下,存储系统也将访问看成是在用户模式下进行的。

image-20230123141137872
  • 有符号位半字/字节加载是指用符号位加载扩展到32位无符号半字加载是指用零扩展到32位

  • 地址对齐——半字读写的指定地址必须为偶数,否则将产生不可靠的结果

多寄存器存取

LDM和STM指令可以实现在一组寄存器和一块连续的内存单元之间传输数据。

LDM为加载多个寄存器; STM为存储多个寄存器。 允许一条指令传送16个寄存器的任何子集或所有寄存器。

指令格式如下:

LDM{cond}<模式> Rn{!},reglist{^}

STM{cond}<模式> Rn{!},reglist{^}

多寄存器存取LDM和STM的主要用途是现场保护、数据复制、常数传递等

高编号寄存器位于高地址,低编号寄存器位于低地址。

LDM从最低编号寄存器开始处理,与书写顺序无关,高编号寄存器总是在最后

“STM从最高编号寄存器开始处理,与书写顺序无关,高编号寄存器总是在最后

多寄存器加载/存储指令的8种类型如下表所示,右边四种为堆栈操作、左边四种为数据传送操作。

image-20230123142102284

进行数据复制时,先设置好源数据指针和目标指针,然后使用块拷贝寻址指令LDMIA/STMIA、LDMIB/STMIB、LDMDA/STMDA、 LDMDB/STMDB进行读取和存储 。

进行堆栈操作时,要先设置堆栈指针(SP),然后使用堆栈寻址指令 STMFD/LDMFD 、 STMED/LDMED 、 STMFA/LDMFA 和 STMEA/LDMEA实现堆栈操作。

LDM{cond}<模式> Rn{!},reglist{^}

STM{cond}<模式> Rn{!},reglist{^}

  • 指令格式中,寄存器Rn为基址寄存器, 装有传送数据的初始地址,Rn不允许为R15。

  • 后缀“!”表示最后的地址写回到Rn中。

  • 寄存器列表reglist可包含多于一个寄存器或包含寄存器范围,使用“ ,”分开,如{R1,R2,R6-R9},寄存器按由小到大排列。

  • 后缀“^”不允许在用户模式或系统模式下使用。

  • 若在LDM指令且寄存器列表中包含有PC时使用,那么除了正常的多寄存器传送外,将SPSR也拷贝到CPSR中,这可用于异常处理返回

  • 使用后缀“^”进行数据传送且寄存器列表不包含PC时,加载/存储的是用户模式的寄存器,而不是当前模式的寄存器。

  • 当Rn在寄存器列表中且使用后缀“!”时,对于STM指令,若Rn为寄存器列表中的最低数字的寄存器,则会将Rn的初值保存; 其它情况下Rn的加载值和存储值不可预知。地址对齐—这些指令忽略地址位[1:0]。

1
2
3
4
LDMIA R0!,{R3 - R9};加载R0指向地址上的多字数据,保存到R3~R9中,R0值更新 
STMIA R1!,{R3 - R9};将R3~R9的数据存储到R1指向的地址上,R1值更新
STMFD SP!,{R0 - R7,LR} ;现场保存,将R0~R7、LR人栈
LDMFD SP!,{R0 - R7,PC} ;恢复现场,异常处理返回
寄存器和存储器交换指令

SWP指令用于将一个内存单元(该单元地址放在寄存器Rn中)的内容读取到一个寄存器Rd中,同时将另一个寄存器Rm的内容写入到该内存单元中。使用SWP可实现信号量操作。 指令格式如下:

image-20230123143753566

其中,B为可选后缀,若有B,则交换字节,否则交换32位字; Rd用于保存从存储器中读入的数据;Rm的数据用于存储到存储器中,若Rm与Rd相同,则为寄存器与存储器内容进行交换;Rn为要进行数据交换的存储器地址,Rn不能与Rd和Rm相同。

SWP R1,R1,[R0] ;将R1的内容与R0指向的存储单元的内容进行交换

SWPB R1,R2,[R0] ;将R0指向的存储单元内容读取一字节数据到R1中 ;(高24位清零),并将R2的内容写入到该内存单元中 ;(最低字节有效)

4.ARM数据处理指令

数据处理指令只能对寄存器的内容进行操作,而不能对内存中的数据进行操作。

所有ARM数据处理指令均可选择使用S后缀,并影响状态标志

比较指令CMP、CMN、TST和TEQ不需要后缀S,它们会直接影响状态标志

1.数据传送指令
image-20230123144712628
2.算术逻辑运算指令
image-20230123150144961
1
2
3
4
5
6
7
8
9
求64位加法
ADDS R0,R0,R2 ;使用ADC实现64位加法
ADC R1,R1,R3 ;(R1、R0)=(R1、R0)+(R3、R2)
求64位减法
SUBS R0,R0,R2 ;使用SBC实现64位减法
SBC R1,R1,R3 ; (R1、R0)=(R1、R0)-(R3、R2)
求64位数值负数
RSBS R2,R0,#0
RSC R3,R1,#0 ;使用RSC指令实现求64位数值的负数
image-20230123151253591
1
2
3
4
5
6
7
8
9
10
11
逻辑与
ANDS R0,R0,#0x01 ;R0←R0&0x01,取出最低位数据
AND R2,R1,R3 ;R2←R1&R3
逻辑或
ORR R0,R0,#0x0F ;将R0的低4位置1
逻辑异或
EOR R1,R1,#0x0F ;将R1的低4位取反
EORS R0,R5,#0x01 ; 将R5和0x01进行逻辑异或,
;结果保存到R0,并影响标志位
位清除
BIC R1,R1,#0x0F ;将R1的低4位清零,其它位不变
3.比较指令
image-20230123151910240
1
2
3
4
5
6
7
8
9
10
比较指令
CMP R1,#10 ; R1-10,R1与10比较,设置相关标志位
负数比较指令
CMN R0,#1 ;R0+1,判断R0是否为 -1 的补码。若是,
; 则Z位置1。
位测试指令(按位逻辑与)
TST R0,#0x01 ; 判断R0的最低位是否为0
TST R1,#0x0F ; 判断R1的低4位是否为0
相等测试指令(按位异或)
TEQ R0,R1 ; 比较R0与R1是否相等 (不影响V位和C位)

TST指令与ANDS指令的区别在于TST指令不保存运算结果。 TST指令通常与EQ、NE条件码配合使用,当所有测试位均为0时,EQ有效, 而只要有一个测试位不为0, 则NE有效

TEQ指令与EORS指令的 区别在于TEQ指令不保存运算结果。使用TEQ进行相等测试时, 常与EQ、NE条件码配合使用。当两个数据相等时,EQ有效,否则NE有效。

5.乘法指令

image-20230123153407347
1
2
3
4
5
6
7
8
9
32乘法
MUL R1,R2,R3 ;R1=R2×R3
MULS R0,R3,R7 ;R0=R3×R7,同时影响CPSR中的N位和Z位
32位乘加
MLA R1,R2,R3,R0 ; R1=R2×R3+R0
64位无符号乘法
UMULL R0,R1,R5,R8 ; (R1、R0)=R5×R8
64位无符号乘加
UMLAL R0,R1,R5,R8 ;(R1、R0)=R5×R8+(R1、R0)

前低字后高字

6.ARM分支指令

在ARM中有两种方式可以实现程序的跳转:

  • 一种是使用分支指令直接跳转

  • 另一种则是直接向PC寄存器赋值实现跳转

image-20230123154217855

分支指令——B指令,该指令跳转范围限制在当前指令的±32M字节地址内(ARM指令为字对齐,最低2位地址固定为0)。指令格式如下:

B WAITA ; 跳转到WAITA标号处

带链接的分支指令——BL指令,限制在当前指令的±32MB的范围内,BL指令常用与子程序调用。

BL DELAY ; 调用子程序DELAY

带状态切换的分支指令——BX指令,该指令可以根据跳转地址(Rm) 的最低位来切换处理器状态,bit[0]=0为ARM代码,否则为Thumb。其跳转范围限制在当前指令的±32M字节地址内(ARM指令为字对齐,最低2位地址固定为0)。

ADRL R0,ThumbFun+1 ;将Thumb程序的入口地址加1 ;存入R0(ADRL地址读取伪指令)

BX R0 ;跳转到R0指定的地址,并根据R0的最低位来切换处理器状态

7.协处理器指令

image-20230123154836944

8.杂项指令

image-20230123154952114

Thumb指令集和ARM指令集区别

  1. 分支指令:

    程序相对转移,特别是条件跳转与ARM代码下的跳转相比,在范围上有更多的限制,转向子程序是无条件的转移。

  2. 数据处理指令:

    • 数据处理指令是对通用寄存器进行操作。在大多数情况下,操作的结果须放入其中一个操作数寄存器中,而不是第3个寄存器中。

    • 数据处理操作比ARM状态的更少。

    • 访问寄存器R8~R15受到一定限制。

    • 除MOV和ADD指令访问器R8~R15外,其它数据处理指令总是更新CPSR中的ALU状态标志。

    • 访问寄存器R8~R15的Thumb数据处理指令不能更新CPSR中的ALU状态标志。

3.单寄存器加载和存储指令,在Thumb状态下,单寄存器加载和存储指令只能访问寄存器R0~R7。

4.多寄存器加载和存储指令,LDM和STM指令可以将任何范围为R0R7的寄存器子集加载或存储,多寄存器加载和存储指令只有LDMIA和STMIA指令。PUSH和POP指令使用堆栈指令R13作为基址实现满递减堆栈。 除R0R7外,PUSH指令还可以存储LR, 并且POP指令可以加载PC。

4.汇编程序设计

1.ARM伪指令

与单片机汇编程序设计一样,在ARM汇编语言程序里, 有一些特殊指令助记符,这些助记符与指令系统的真正的指令不同,没有相对应的操作码,通常称这些特殊指令助记符为伪指令,他们所完成的操作称为伪操作。 伪指令在源程序中的作用是为完成汇编程序作各种准备工作的,这些伪指令仅在汇编过程中起作用,一旦汇编结束,伪指令的使命就完成。 在ARM的汇编程序中,有如下几种伪指令:符号定义伪指令、数据定义伪指令、汇编控制伪指令、宏指令以及其他伪指令

常用伪指令(常考)

  • ADR(小范围的地址读取伪指令)

    ADR{cond} register, expr

  • ADRL(中等范围的地址读取伪指令)

    ADRL{cond} register, expr

  • LDR(大范围的地址读取伪指令)

    LDR{cond} register, =expr | label-expr

  • NOP空操作伪指令
    NOP

符号定义伪指令(常考)

符号定义伪指令用于定义ARM汇编语言程序中的变量、对变量赋值以及定义寄存器的别名等操作。常见的符号定义伪指令有如下几种:

● 用于定义全局变量的GBLA、GBLL和GBLS。

● 用于定义局部变量的LCLA、LCLL和LCLS。

● 用于对变量赋值的SETA、SETL、SETS。

● 为通用寄存器列表定义名称的RLIST。

● 为一个协处理器的寄存器定义名称的伪指令:CN

● 为一个协处理器定义名称的伪指令:CP

● 为一个VFP寄存器定义名称的伪指令:DN和SN

● 为一个FPA浮点寄存器定义名称的伪指令:FN

GBLA、GBLL和GBLS

GBLA伪指令用于定义一个全局的数字变量,并初始化为0

GBLL伪指令用于定义一个全局的逻辑变量,并初始化为F(假)

GBLS伪指令用于定义一个全局的字符串变量,并初始化为

以上三条伪指令用于定义全局变量,因此在整个程序范围内变量名必须唯一

1
2
3
GBLA Number1;定义一个全局的数字变量,变量名为Number1
Number1 SETA 0xaa;将Number1变量赋值为0xaa
GBLL True1;定义一个全局的逻辑变量,变量名为True1
LCLA、LCLL和LCLS

LCLA伪指令用于定义一个局部的数字变量,并初始化为0;

LCLL伪指令用于定义一个局部的逻辑变量,并初始化为F(假);

LCLS伪指令用于定义一个局部的字符串变量,并初始化为空;

以上三条伪指令用于声明局部变量,在其作用范围内变量名必须唯一。

1
2
3
4
5
6
LCLA Number2;声明一个局部的数字变量,变量名为Number2
Number2 SETA 0xaa ;将Number2变量赋值为0xaa
LCLL Logic2 ;声明一个局部的逻辑变量,变量名为Logic2
Logic2 SETL {TRUE} ;将Logic2变量赋值为真
LCLS String2 ;定义一个局部的字符串变量,变量名为String2
String2 SETS “Testing” ;将String2变量赋值为“Testing”
SETA、SETL和SETS

SETA伪指令用于给一个数学变量赋值;

SETL伪指令用于给一个逻辑变量赋值;

SETS伪指令用于给一个字符串变量赋值;

其中,变量名为已经定义过的全局变量或局部变量,表达式为将要赋给变量的值

1
2
3
4
LCLA Number3 ;声明一个局部的数字变量,变量名为Number3
Number3 SETA 0xaa ;将Number3变量赋值为0xaa
LCLL Logic3;声明一个局部的逻辑变量,变量名为Logic3
Logic3 SETL {TRUE};将Logic3变量赋值为真
RLIST

RLIST伪指令可用于对一个通用寄存器列表定义名称, 使用该伪指令定义的名称可在ARM指令LDM/STM中使用。 在LDM/STM指令中,列表中的寄存器访问次序为根据寄存器的编号由低到高,而与列表中的寄存器排列次序无关。

1
2
3
4
5
6
RegList RLIST{R0-R5,R8,R10} ;将寄存器列表
名称定义为RegList,可在ARM指令LDM/STM中通过该
名称访问寄存器列表。
……
STMFD SP!,RegList ;保存寄存器列表RegList到
堆栈

数据定义(Data Definition)伪指令(DCB(=),DCW,DCD(&),SPACE(%),MAP(^),FIELD(#)常考)

数据定义伪指令一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。常见的数据定义伪指令有如下几种:

DCB 用于分配一片连续的字节存储单元并用指定的数据初始化。

DCW(DCWU)用于分配一片连续的半字存储单元并用指定的数据初始化。

DCD(DCDU)用于分配一片连续的字存储单元并用指定的数据初始化。

● DCFD(DCFDU)用于为双精度的浮点数分配一片连续的字存储单元并用指定的数据初始化。

● DCFS(DCFSU) 用于为单精度的浮点数分配一片连续的字存储单元并用指定的数据初始化。

● DCQ(DCQU)用于分配一片以8字节为单位的连续的存储单元并用指定的数据初始化。

● DCDO 用于分配一段字的内存单元,将每个单元的内容初始化为该单元相对于基址寄存器的偏移量

● DCI 用于分配一段字的内存单元,并用单精度的浮点数据初始化,指定内存单元存放的是代码,而不是数据

SPACE 用于分配一片连续的字节存储单元,并初始化为0

MAP 用于定义一个结构化的内存表首地址

FIELD 用于定义一个结构化的内存表的数据域

LTORG 用于声明一个文字池(缓冲池)

DCB

DCB伪指令用于分配一片连续的字节存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为0~255 的数字或字符串。DCB也可用“=“代替

1
2
3
String DCB “This is a test!” ;分配一片连续的字节存储单元并初始化。
Parameter DCB 0x33,0x44,0x55
DCB –1,-2,0,1,2 ;分配一片连续的字节存储单元并初始化。
DCW(或DCWU)

DCW(或DCWU)伪指令用于分配一片连续的半字存储单元并用伪指令中指定的表达式初始化。 其中,表达式可以是程序标号或数字表达式。 用DCW分配的字存储单元是半字对齐的,而用DCWU分配的字存储单元并不严格半字对齐。

1
Data DCW 0,1,2,3 ;分配一片连续的半字存储单元并初始化。
DCD(或DCDU)

DCD(或DCDU)伪指令用于分配一片连续的字存储单元并用伪指令中指定的表达式初始化。 其中,表达式可以为程序标号或数字表达式。DCD也可用 “&”代替。 用DCD分配的字存储单元是字对齐的,而用DCDU分配的字存储单元并不严格字对齐。

1
Data DCD 3,4,5,6 ;分配一片连续的字存储单元并初始化。
DCFD(或DCFDU)

DCFD(或DCFDU)伪指令用于为双精度的浮点数分配一片连续的字存储单元并用伪指令中指定的表达式初始化。 每个双精度的浮点数占据两个字单元。 用DCFD分配的字存储单元是字对齐的,而用

DCFDU分配的字存储单元并不严格字对齐

1
FData DCFD 0,2E115,-5E7 ;分配一片连续的字存储单元并初始化为指定的双精度数。
DCFS(或DCFSU)

DCFS(或DCFSU)伪指令用于为单精度的浮点数分配一片连续的字存储单元并用伪指令中指定的表达式初始化。每个单精度的浮点数占据一个字单元。 用DCFS分配的字存储单元是字对齐的,而用DCFSU分配的字存储单元并不严格字对齐。

1
Sdata DCFS 1,2E5,-5E-7 ;分配一片连续的字存储单元并初始化为指定的单精度数。
DCQ(或DCQU)

DCQ(或DCQU)伪指令用于分配一片以8个字节为单位的连续存储区域并用伪指令中指定的表达式初始化。 用DCQ分配的存储单元是字对齐的,而用DCQU分配的存储单元并不严格字对齐。

1
Data DCQ 100,1000 ;分配一片连续的存储单元并初始化为指定的值。
DCDO

DCDO用于分配一段字内存单元,并将每个单元的内容初始化为该单元相对于静态基址寄存器的偏移量。 DCDO伪指令作为静态基址寄存器R9的偏移量分配内存单元,该指令需要内存字对齐。

1
2
IMPORT externsys
Data DCDO externsys;分配32位的字单元,其值为标号externsys相对于R9的偏移量
DCI

DCI用于分配一段字内存单元,并用伪指令中指定的表达式初始化。指定内存单元存放的是代码而不是数据。在Thumb代码中,DCI分配的是半字内存代码单元。

1
2
3
4
MACRO ; 这个宏指令将newinstr Rd,Rm定义为相应的机器指令
newinstr $Rd,$Rm
DCI 0Xe15f0f10 ; 这里存放的是指令
MEND
SPACE

SPACE伪指令用于分配一片连续的存储区域并初始化为0。 其中,表达式为要分配的字节数SPACE也可用“%”代替

1
DataSpace SPACE 1000 ;分配连续1000字节的存储单元并初始化为0。
MAP

MAP伪指令用于定义一个结构化的内存表的首地址。(MAP也可用“^”代替)。表达式可以为程序中的标号或数学表达式,基址寄存器为可选选项.

•当基址寄存器选项不存在时,表达式的值即为内存表的首地址,

•当该选项存在时,内存表的首地址为表达式的值与基址寄存器的和。

•MAP伪指令通常与FIELD伪指令配合使用来定义结构化的内存表。

1
MAP 0x100 ;定义结构化内存表首地址的值为0x100。
FIELD

FIELD伪指令用于定义一个结构化内存表中的数据域。(FIELD也可用“#”代替)。

•表达式的值为当前数据域在内存表中所占的字节数

•FIELD伪指令常与MAP伪指令配合使用来定义结构化的内存表。MAP伪指令定义内存表的首地址,FIELD伪指令定义内存表中的各个数据域,并可以为每个数据域指定一个标号供其他的指令引用。

•注意MAP和FIELD伪指令仅用于定义数据结构,并不实际分配存储单元。

1
2
3
4
MAP 0x100 ;定义结构化内存表首地址的值为0x100。
A FIELD 16 ;定义A的长度为16字节,位置为0x100
B FIELD 32 ;定义B的长度为32字节,位置为0x110(0x100 后存放了16个字节)
S FIELD 256 ;定义S的长度为256字节,位置为0x130(0x110后存放了32个字节)
LTORG

LTORG用于声明一个文字池(数据缓冲池),在使用LDR伪指令时,要在适当的地址加入LTORG声明文字池, 这样就会把要加载的数据保存在文字池中,再用ARM的加载指令读出数据。如果没有使用LTORG声明文字池,则汇编器会在程序末尾自动声明。使用LTORG声明文字池的目的可以在程序代码的任何位置存储加载的数据。

1
2
3
4
5
6
……
LDR R0,=0x12345 ;有效二进制数位超过8,先放进文字池
ADD R1,R1,R0
MOV PC,LR
LTORG ;声明文字池,此处存放0x12345
…… ;其他代码

汇编控制伪指令

汇编控制伪指令用于控制汇编程序的执行流程,常用的汇编控制伪指令包括以下几条:

● IF、ELSE、ENDIF

● WHILE、WEND

● MACRO、MEND

● MEXIT

IF、ELSE、ENDIF

IF、ELSE、ENDIF伪指令能根据条件的成立与否决定是否执行某个指令序列。当IF后面的逻辑表达式为真,则执行指令序列1,否则执行指令序列2。其中,ELSE及指令序列2可以没有,此时,当IF后面的逻辑表达式为真,则执行指令序列1,否则继续执行后面的指令。 IF、ELSE、ENDIF伪指令可以嵌套使用。

1
2
3
4
5
6
7
8
GBLS Version ;定义一个全局的字符串变量,
变量名为Version
……
IF Version =“V1”
指令序列1
ELSE
指令序列2
ENDIF
WHILE、WEND

WHILE、WEND伪指令能根据条件的成立与否决定是否循环执行某个指令序列。当WHILE后面的逻辑表达式为真,则执行指令序列,该指令序列执行完毕后,再判断逻辑表达式的值,若为真则继续执行,一直到逻辑表达式的值为假。 WHILE、WEND伪指令可以嵌套使用。

1
2
3
4
5
6
GBLA Counter ;声明一个全局的数字变量,变量名为
Counter,作为循环计数器
……
WHILE Counter < 10
指令序列
WEND
MACRO、MEND

MACRO、MEND伪指令可以将一段代码定义为一个整体,称为宏指令,然后就可以在程序中通过宏指令多次调用该段代码。

1
2
3
4
5
6
7
MACRO
CODE_1; 宏名为CODE_1,无参数
LDR R0,=rPDATG; 读取PG0口的值
LDR R1,[R0]
ORR R1,R1,#0X01; CSI置位
STR R1,[R0]
MEND

宏定义中,**$标号在宏指令被展开时,标号会被替换为用户定义的符号,宏指令可以使用一个或多个参数**, 当宏指令被展开时,这些参数被相应的值替换。

1
2
3
4
5
6
7
8
MACRO ;宏定义
CALLSubfunction $Function,$dat1,$dat2 ;
宏名为CALLSubfunction,带3个参数
IMPORT $Function ;声明外部子程序名
MOV R0,$dat1 ;设置子程序参数R0=$dat1
MOV R1,$dat2
BL Function ;调用子程序
MEND ;宏定义结束

其他常用的伪指令

其他的一些使用较频繁的伪指令:

● AREA

● ALIGN

● CODE16、CODE32

● ENTRY

● END

● EQU

● EXPORT(或GLOBAL)

● IMPORT

● EXTERN

● GET(或INCLUDE)

● INCBIN

AREA

AREA伪指令用于定义一个代码段或数据段。ARM汇编程序设计采用分段式设计,一个ARM汇编源程序至少需要一个代码段,大的程序可以包含多个代码段和数据段。 当程序太长时,也可以将程序分为多个代码段和数据段。使用AREA伪指令将程序分为多个ELF(Executable and Linkable Format)格式的段,段名可以相同,这时同名的段被放在同一个EFL段中。

1
2
3
AREA Init,CODE,READONLY
指令序列
;该伪指令定义了一个代码段,段名为Init,属性为只读
ALIGN

ALIGN伪指令可通过添加填充字节的方式,使当前位置满足一定的对齐方式。其中,表达式的值用于指定对齐方式,可能的取值为2的幂,如1、2、4、8、16等。 若未指定表达式,则将当前位置对齐到下一个字的位置。 偏移量也为一个数字表达式,若使用该字段,则当前位置的对齐方式为:2的表达式次幂+偏移量。

1
2
3
AREA Init,CODE,READONLY,ALIGN=3 ;指定后面的指令为8字节对齐。
指令序列
END
CODE16、CODE32

CODE16伪指令通知编译器,其后的指令序列为16位的Thumb指令。

CODE32伪指令通知编译器,其后的指令序列为32位的ARM指令。

CODE16和CODE32伪指令只告诉编译器后面的指令是16位或32位的类型,指令本身不能进行程序状态的切换,如果要进行状态的切换,可以使用BX指令进行操作。

1
2
3
4
5
6
7
8
9
10
11
AREA Init,CODE,READONLY
……
CODE32 ;通知编译器其后的指令为32位的ARM指令
LDR R0,=NEXT+1 ;将跳转地址放入寄存器R0
BX R0 ;程序跳转到新的位置执行,并将处理器切换到Thumb工作状态
……
CODE16 ;通知编译器其后的指令为16位的Thumb指令
NEXT
LDR R3,=0x3FF
……
END ;程序结束
ENTRY

ENTRY伪指令用于指定程序的入口点。在一个完整的汇编语言程序中至少要有一个ENTRY(也可以有多个,当有多个ENTRY时,程序的真正入口点由链接器指定),但在一个源文件里最多只能有一个ENTRY(可以没有)。

1
2
3
AREA Init,CODE,READONLY
ENTRY ;指定应用程序的入口点
……
END

END伪指令用于通知编译器已经到了源程序的结尾。每一个汇编源文件都需要使用一个END伪指令,指示本源程序结束

1
2
3
AREA Init,CODE,READONLY
……
END ;指定应用程序的结尾
EQU

EQU伪指令用于为程序中的常量、标号等定义一个等效的字符名称,类似于C语言中的#define。其中EQU可用 “*”代替。名称为EQU伪指令定义的字符名称,当表达式为32位的常量时,可以指定表达式的数据类型,可以有以下三种类型:CODE16、CODE32和DATA。

1
2
3
ABCE EQU label+8; 定义地址标号ABCE为label+8
Test EQU 50 ;定义标号Test的值为50
Addr EQU 0x55,CODE32 ;定义Addr的值为0x55,且该处为32位的ARM指令。
EXPORT(或GLOBAL)

EXPORT伪指令用于在程序中声明一个全局的标号该标号可在其他的文件中引用。EXPORT可用GLOBAL代替。标号在程序中区分大小写。

1
2
3
4
AREA Init,CODE,READONLY
EXPORT main ;声明一个可全局引用的标号main
……
END
IMPORT

IMPORT伪指令用于通知编译器要使用的标号在其他的源文件中定义但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中

1
2
3
4
AREA Init,CODE,READONLY
IMPORT main ;通知编译器当前文件要引用标号main,但main在其他源文件中定义
……
END
EXTERN

EXTERN伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,如果当前源文件实际并未引用该标号,该标号就不会被加入到当前源文件的符号表中。注意: 该伪指令与IMPORT的区别是,IMPORT伪指令无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中

1
2
3
4
5
6
7
AREA Init,CODE,READONLY
EXTERN main ;通知编译器当前文件要引用标号main,
main在其他源文件中定义,如果本文件
中没有使用main,则main就不会被加
入到当前源文件的符号表中。
……
END
GET(或INCLUDE)

GET伪指令用于将一个源文件包含到当前的源文件中,并将被包含的源文件在当前位置进行汇编处理。可以使用INCLUDE代替GET。使用方法与C语言中的 “include”相似。 GET伪指令只能用于包含源文件,如果需要包含经过编译后的二进制目标文件,需要使用INCBIN伪指令。

1
2
3
4
5
AREA Init,CODE,READONLY
GET a1.s ;通知编译器当前源文件包含源文件a1.s
GET C:\a2.s;通知编译器当前源文件包含源文件C:\ a2.s
……
END
INCBIN

INCBIN伪指令用于将一个二进制目标代码文件或任意格式的数据文件包含到当前的源文件中,被包含的文件不作任何变动地存放在当前文件中,编译器从其后开始继续处理。

1
2
3
4
5
6
AREA Init,CODE,READONLY
INCBIN a1.dat ;通知编译器当前源文件包含文件a1.dat
INCBIN C:\a2.txt;通知编译器当前源文件包含文件C:\a2.txt
INCBIN a3.bin ;通知编译器当前源文件包含文件a3.bin
……
END

2.汇编语法

在一个项目设计中:至少需要有一个汇编源文件或C程序文件,可以有多个汇编文件或多个C程序文件,或者C语言和汇编语言混合编程的文件。汇编程序源文件的扩展名必须是“.s”。

image-20230123210515574

汇编语句语法

汇编语句格式

ARM(Thumb)汇编语言的语句格式为: {标号} {指令或伪指令} {;注释}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
正确的例子:
……
String1 SETS “My string1”
Count RN R0 ;给R0寄存器定义别名
START
LDR R0,=0x12345
MOV R1,#0
LOOP
MOV R2,#3
……
错误的例子:
START MOV R0,#1 ;标号START没有顶头写
ABC: MOV R1,#2 ;标号后不能带:
MOV R2,#3 ;指令不允许顶头写
Loop Mov R2,#3 ;指令中大小写混合
B loop ;无法跳转到Loop去
标号

在ARM汇编中,标号代表一个地址,段内标号的地址在汇编时确定,而段外标号的地址在连接时确定,根据标号的生成方式,可以有以下3种方式:

(1)基于PC的标号是位于目标指令前的标号或程序中的数据定义伪指令前的标号,这种标号在汇编时被处理成PC值加上或减去一个数字常量,它常用于跳转指令的目标地址,或代码段中所嵌入的少量数据。

(2)基于寄存器的标号通常由MAP和FIELD伪指令定义,也可以用EQU伪指令定义。这种标号在汇编时被处理成寄存器的值加上或减去一个数字常量。它通常被用于访问位于数据段中的数据。

(3)绝对地址是一个32位的数字,它可以寻址的范围是0~2^32-1,可以直接寻址整个内存空间。

常用符号

● 符号由大、小写字母、数字以及下划线组成。

● 除局部标号以数字开头外,其他的符号不能以数字开头

● 符号区分大小写,同名的大、小写符号会被编译器认为是两个不同的符号。

● 符号在其作用范围内必须唯一。

● 自定义的符号名不能与系统的保留字相同。

● 符号名不应与指令或伪指令同名。

3.汇编程序设计

在ARM(Thumb)汇编语言程序中,以程序段为单位组织代码。段是相对独立的指令或数据序列,具有特定的名称。段可以分为代码段和数据段,代码段的内容为执行代码,数据段存放代码运行时需要用到的数据。一个汇编程序至少应该有一个代码段,当程序较长时,可以分割为多个代码段和数据段,多个段在程序编译链接时最终形成一个可执行的映象文件。 可执行映象文件通常由以下几部分构成: ● 一个或多个代码段,代码段的属性为只读

● 零个或多个包含初始化数据的数据段,数据段的属性为可读写

● 零个或多个不包含初始化数据的数据段,数据段的属性为可读写。 链接器根据系统默认或用户设定的规则,将各个段安排在存储器中的相应位置。因此源程序中段之间的相对位置与可执行的映象文件中段的相对位置一般不会相同

汇编语言源程序的基本结构:

1
2
3
4
5
6
7
8
9
10
11
AREA Init,CODE,READONLY
ENTRY
Start
LDR R0,=0x3FF5000
MOV R1,#0xFF
STR R1,[R0]
LDR R0,=0x3FF5008
MOV R1,#0x01
STR R1,[R0]
……
END

在汇编语言程序中,用AREA伪指令定义一个段,并说明所定义段的相关属性,本例定义一个名为Init的代码段,属性为只读。ENTRY伪指令标识程序的入口点,接下来为指令序列,程序的末尾为END伪指 令,该伪指令告诉编译器源文件的结束,每一个汇编语言程序段都必须有一条END伪指令,指示代码段的结束。

子程序调用

在ARM汇编语言程序中,子程序的调用一般是通过BL指令来实现的。在程序中,使用指令“BL子程序名”即可完成子程序的调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
AREA Init,CODE,READONLY
ENTRY
Start
LDR R0,=0x3FF5000
MOV R1,#0xFF
STR R1,[R0]
LDR R0,=0x3FF5008
MOV R1,#0x01
STR R1,[R0]
BL PRINT_TEXT
……
PRINT_TEXT
……
MOV PC,LR
……
END

宏定义及其作用

• 使用宏定义可以提高程序的可读性,简化程序代码和同步修改。

• ARM宏定义与标准C语言的#define相似,只在源程序中进行字符的简单替代。

• 宏定义从MACRO伪指令开始,到MEND结束,并可以使用参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MACRO ;宏定义
CALLSubfunction $Function,$dat1,$dat2 ;宏名为;CALLSubfunction,带3个参数
IMPORT $Function ;声明外部子程序名
MOV R0,$dat1 ;设置子程序参数R0=$dat1
MOV R1,$dat2
BL Function ;调用子程序
MEND ;宏定义结束
……
CALLSubfunction FADD1,#3,#2 ;宏调用
……
汇编处理后,宏调用将被展开,程序如下:
……
IMPORT FADD1
MOV R0,#3
MOV R1,#2
BL FADD1
……

数据比较跳转

汇编程序可以使用CMP指令进行两个数的比较,然后根据比较结果实现程序的跳转,代码如下:

1
2
3
4
5
6
7
8
9
CMP R5,#10
BEQ BRANCH1 ;如果R5为10,则跳转到;BRANCH1
……
CMP R1,R2
ADDHI R1,R1,#1 ;如果R1>R2,则R1=R1+1
ADDLS R1,R1,#2 ;如果R1<=R2,则R1=R1+2
……
ANDS R1,R1,#0x80 ;R1=R1&0x80,并设置相应的标志位
BNE WAIT ;如果R1的第7位1,则跳转到WAIT

循环

下面的程序代码为汇编循环程序的例子,指定了循环的次数,每循环一次进行减1操作,并判断结果是否为0,如果为0则退出循环。

1
2
3
4
5
6
MOV R0,#10
LOOP
……
SUBS R0,R0,#1
BNE LOOP
……

数据块复制

可以使用存储器访问指令LDM/STM进行读取和存储,进行数据块的复制,示例代码如下:

1
2
3
4
5
6
7
8
LDR R0,=DATA_DST ;指向数据的目标地址
LDR R1,=DATA_SRC ;指向数据源地址
MOV R10,#10 ;复制数据块的大小为10*N个字
LOOP LDMIA R1!,{R2-R9}
STMIA R0!,{R2-R9}
SUBS R10,R10,#1
BNE LOOP
……

堆栈操作

可以使用存储器访问指令LDM/STM实现堆栈操作,用于子程序的寄存器保护。在使用堆栈前,首先需要分配好堆栈空间,设置好寄存器R13(即堆栈指针SP),否则操作失败。

1
2
3
4
5
6
OUTDAT
STMFD SP!,{R0-R7,LR} ;寄存器入栈,压栈从右到左
……
BL DELAY
……
LDMFD SP!,{R0-R7,PC}^ ;寄存器出栈,出栈从左到右

查表操作

查表操作是汇编程序经常使用的一种功能,代码如下:

1
2
3
4
5
6
7
8
……
LDR R3,=DISP_TAB ;字模表的首地址
LDR R2,[R3,R5,LSL #2] ;根据R5的值查表,取出相应的值。
……
下面的表为0-F的字模
DISP_TAB DCD 0xC0,0xF9,0xA4,0x99,0x92
DCD 0x82,0xF8,0x80,0x90,0x88,0x83
DCD 0xC6,0xA1,0x86,0x8E,0xFF

长跳转

• ARM的B指令和BL指令无法进行整个内存空间范围内的跳转(仅±32MB)

• 但可以通过对PC寄存器的赋值实现32位地址的跳转和调用。

1
2
3
4
LDR PC,=JUMP_FUNC ;跳转到JUMP_FUNC处
……
JUMP_FUNC …….;跳转到这里
……

对信号量的支持

ARM提供一条内存与寄存器交换的指令SWP用于多线程环境下支持信号量的操作,实现系统任务之间的同步与互斥,代码如下:

1
2
3
4
5
6
7
DISP_SEM EQU 0x40002A00
……
DISP_WAIT MOV R1,#0 ;0作为信号量的值
LDR R0,=DISP_SEM
SWP R1,R1,[R0] ;取出信号量,设置为0
CMP R1,#0
BEQ DISP_WAIT ;如果信号量没到,则等待

片上特殊寄存器定义及应用

对ARM芯片的片上外设的特殊功能寄存器进行访问时,可以使用下面的代码对其寄存器进行定义并应用:

1
2
3
4
5
WDTCNT EQU 0x01D30008 ;看门狗计数器寄存器定义
……
LDR R0,=WDTCNT ;寄存器地址传给R0
MOV R1,#12
STR R1,[R0] ;用十进制12设置看门狗计数器寄存器

片外部件控制

• 设置“置位/复位”寄存器

• 对外围部件的控制寄存器进行操作时可以使用存储/保存指令的偏移功能,避免了每次都加载寄存器地址的操作

1
2
3
4
5
LDR R0,=GPIO_BASE
MOV R1,#0X00
STR R1,[R0,#0x04] ;基地址+0x04=IOSET,将IOSET设置为0
MOV R1,#0x10
STR R1,[R0,#0x0C] ;基地址+0x0C=IOCLR,将IOCLR设置为0x10

5.嵌入式C语言程序设计基础

1.嵌入式C语言的预处理伪指令(考察宏)

1.文件包含伪指令

​ 文件包含伪指令可将头文件包含到程序中, 头文件中定义的内容有符号常量,复合变量原型、用户定义的变量原型和函数的原型说明等。 编译器编译预处理时,用文件包含的正文内容替换到实际程序中。

  1. 文件包含伪指令的格式

    1
    2
    # include <头文件名.h> /*标准头文件*/
    # include "头文件名.h " /*自定义头文件*/
  2. 包含文件伪指令的说明

    • 常在头文件名后用.h作为扩展名,可带或不带路径
    • 头文件可分为标准头文件自定义头文件
    • 尖括号内的头文件为标准头文件,由开发环境或系统提供。
    • 双引号内的头文件为用户自定义头文件。 搜索时,首先在当前目录中搜索,其次按环境变量include指定的目录顺序搜索
    • 搜索到头文件后,就将该伪指令直接用头文件内容替换

2.宏定义伪指令

​ 宏定义伪指令分为:简单宏、参数宏、条件宏、预定义宏及宏释放

  1. 简单宏格式如下:
1
# define 宏标识符 宏体
  1. 说明:

    • 宏体是由单词序列组成。宏体超长时,允许使用续行符 “\”进行续行,续行符和其后的换行符 \n 都不会进入宏体。
    • 在定义宏时,应尽量避免使用C语言的关键字预处理器的预定义宏,以免引起灾难性的后果。
    • 在源文件中,用预处理器伪指令定义过宏标识符之后, 就可用宏标识编写程序。当源文件被预处理器处理时,每遇到该宏标识符,预处理器便将宏展为宏体。
  2. 参数宏格式如下:

    1
    # define 宏标识符(形式参数表) 宏体
  3. 说明:

    • 形式参数表为逗号分割的形式参数。
    • 宏体是由单词序列组成。宏体超长时,允许使用 续行符“\”进行续行,续行符和其后的换行符 \n 都 不会进入宏体。
    • 使用参数宏时,形式参数表应换为同样个数的实 参数表,这一点类似于函数的调用。参数宏与函数 的区别在于参数宏的形参数表中没有类型说明符。
    • 预处理器在处理参数宏时使用2遍宏展开。第1遍展开宏体,第2遍对展开后的宏体用实参数替换形式参数。

示例:

1
2
3
4
5
6
7
#define bNAND_CTL(Nb) __REG(0x4e000000 + (Nb))
#define NFCONF bNAND_CTL(0x00)
#define NFCMD bNAND_CTL(0x04)
#define NFADDR bNAND_CTL(0x08)
#define NFDATA bNAND_CTL(0x0c)
#define NFSTAT bNAND_CTL(0x10)
#define NFECC bNAND_CTL(0x14)
  1. 条件宏格式:

    • 格式1

      1
      2
      3
      4
      5
      6
      # ifdef 宏标识符 //若标识符已定义
      # undef 宏标识符//释放宏
      # define 宏标识符 宏体//重定义宏
      # else
      # define 宏标识符 宏体
      # endif
    • 格式2

      1
      2
      3
      4
      5
      6
      # ifndef 宏标识符 //若标识符未定义
      # define 宏标识符 宏体
      # else
      # undef 宏标识符//释放宏
      # define 宏标识符 宏体
      # endif

    说明:

    • 格式1是测试存在,格式2是测试不存在
    • else可有,也可没有。
  2. 宏释放

用于释放原先定义的宏标识符。经释放后的宏标识 符可再次用于定义其他宏体。

  • 格式:

    1
    # undef 宏标识符

3.条件编译伪指令

格式:

1
2
3
4
5
6
7
8
9
10
11
# if(条件表达式1)

# elif (条件表达式2)

# elif (条件表达式3)

# elif (条件表达式n)

# else

# endif

示例:

1
2
3
4
5
6
7
# if _B0SIZE==B0SIZE_BYTE
typedef unsigned char PB0SIZE;
# elif _B0SIZE==B0SIZE_SHORT
typedef unsigned short PB0SIZE;
# elif _B0SIZE==B0SIZE_WORD
typedef unsigned long PB0SIZE;
# endif

2.嵌入式C语言的基本数据类型

1.数据类型与表达式

1.类型修饰符
2.访问修饰符

C语言有两个用于控制访问和修改变量方式的修饰符, 分别是常量(const)易变量(volatile)

const修饰符定义出的常量在程序运行过程中始终保持不变。

volatile修饰符用于提醒编译程序,该变量的值可以不通过程序中明确定义的方法来改变,而是从内存中读取最新数据(抑制编译器优化)

例如:

1
2
3
const int num; 
const int num=100
volatile int num=0//num的值由内存中实际数据决定,初始值不一定为0

static

作用:static的作用有2条:(1)隐藏; (2)赋初始值一次有效

a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static, main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量, 而不必担心命名冲突.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int fun(void){
static int count = 10; //赋初值,一次有效
return count--;
}
int count = 1;
int main(void)
{
printf("global\t\tlocal static\n");
for(; count <= 10; ++count)
printf("%d\t\t%d\n", count, fun());
return 0;
}
3.位运算符

3.函数

内联函数(相当于宏)

  • 与一般函数不同的是,它不是在调用时发生转移,而是在编译时将函数体嵌入在每一个调用语句处
  • 这样就相对节省了参数传递、系统栈的保护与恢复等的开销。

格式:

1
2
3
4
<inline> <类型标识符> <被调函数名>(含类型说明的形参表)
{
函数体
}

示例:

1
2
3
4
5
6
7
8
9
#include <iostream.h>
#include <iomanip.h>
inline int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}

说明:

  1. 宏调用并不执行类型检查,甚至连正常参数也不检查,但是函数调用却要检查。

  2. C语言的宏使用的是文本替换,可能导致无法预料的后果,因为需要重新计算参数和操作顺序。

  3. 以下情况不宜使用内联:

(1)如果函数体内的代码比较长(>10句),使用内联将导致内存消耗代价较高。

(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

内嵌汇编的局限性(作业题)

(1)操作数

• ARM 开发工具编译环境下内嵌汇编语言,指令操作数可以是寄存器、常量或 C 语 言表达式。可以是 char、short 或 int 类型,而且是作为无符号数进行操作。

• 当表达式过于复杂时需要使用较多的物理寄存器,有可能产生冲突。

• GNU ARM 编译环境下内嵌汇编语言 ARM 开发工具稍有差别,不能直接引用 C 语 言中的变量。

(2)物理寄存器

不要直接向程序计数器 PC 赋值,程序的跳转只能通过 B 或 BL 指令实现。 一般将寄存器 R0~R3、R12 及 R14 用于子程序调用存放中间结果,因此在内嵌汇 编指令中,一般不要将这些寄存器同时指定为指令中的物理寄存器。 在内嵌的汇编指令中使用物理寄存器时,如果有 C 语言变量使用了该物理寄存器, 则编译器将在合适的时候保存并恢复该变量的值。需要注意的是,当寄存器 SP、 SL、FP 以及 SB 用作特定的用途时,编译器不能恢复这些寄存器的值。 通常在内嵌汇编指令中不要指定物理寄存器,因为有可能会影响编译器分配寄存 器,进而可能影响代码的效率。

(3)标号、常量及指令展开

• C 语言程序中的标号可以被内嵌的汇编指令所使用。但是只有 B 指令可以使用 C 语言程序中的标号,BL 指令不能使用 C 语言程序中的标号。

(4)内存单元的分配

• 内嵌汇编器不支持汇编语言中用于内存分配的伪操作。所用的内存单元的分配都是 通过 C 语言程序完成的,分配的内存单元通过变量以供内嵌的汇编器使用。

(5)SWI 和 BL 指令

• SWI 和 BL 指令用于内嵌汇编时,除了正常的操作数域外,还必须增加如下 3 个可 选的寄存器列表:

• 用于存放输入的参数的寄存器列表。

• 用于存放返回结果的寄存器列表。

• 用于保存被调用的子程序工作寄存器的寄存器列表。

4.汇编语言与C/C++的混合编程

1.相互调用规则—ATPCS

过程调用标准ATPCS(ARM-Thumb Procedure Call Standard)规定了子程序间相互调用的基本规则, ATPCS规定子程序调用过程中寄存器的使用规则数据栈的使用规则参数的传递规则

2007年,ARM公司推出了新的过程调用标准AAPCS(ARM Architecture Procedure Call Standard),它只是改进了原有的ATPCS的二进制代码的兼容性。

1.寄存器的使用规则

子程序间通过寄存器R0~R3来传递参数,记为A1~A4。 被调用的子程序在返回前无须恢复寄存器R0~R3的内容。

  • 子程序中使用寄存器R4~R11来保存局部变量。记为V1V8。 如果在子程序中使用到了寄存器V1V8中的某些寄存器,则子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值

  • 对于子程序中没有用到的寄存器,则不必进行这些操作。

  • 在Thumb程序中,通常只能使用寄存器R4~R7来保存局部变量。

  • 寄存器R12做子程序间调用时临时保存栈指针,常用于子程序间的连接代码段中,记作IP。

  • 寄存器R13用做数据栈指针,记作SP。在子程序中寄存器R13不能用做其他用途。寄存器SP在进入子程序时的值和退出子程序时的值必须相等。

  • 寄存器R14称为链接寄存器,记作LR。它用于保存子程序的返回地址。如果在子程序中保存了返回地址,则寄存器R14可作其他用途。

  • R15是程序计数器,记作PC。不能作其他用途。

image-20221128140943017
2.数据栈的使用规则

FD(Full Descending) 满递减

ED(Empty Descending) 空递减

FA(Full Ascending) 满递增

EA(Empty Ascending) 空递增

ATPCS规定数据栈为FD(满递减)类型, 并且对数据栈的操作是8字节对齐的。 异常中断处理程序可使用中断程序的数据栈

  • 数据栈指针(Stack Point):最后一个写入栈的数据的内存地址。
  • 数据栈的基地址(Stack Base):数据栈的最高地址。 ATPSC中的数据栈是FD型,最早入栈的数据所占的内存单元是基地址的下一个内存单元。
  • 数据栈界限(Stack Limit):数据栈中可使用的最低的内存单元地址。
  • 数据栈中的数据帧(Stack Frames):数据栈中为子程序分配用来保存寄存器和局部变量的区域。
3.参数传递规则

(1)参数个数固定的子程序参数传递规则若系统含浮点运算硬件部件,浮点参数传递规则:

各个浮点参数按顺序处理。为每个浮点参数分配FP寄存器。方法:满足该浮点参数需要的且编号最小的一组连续的FP寄存器。 第一个整数参数,通过寄存器R0~R3来传递。其他参数通过数据栈传递。

(2)参数个数可变的子程序参数传递规则

当参数不超过4个,用R0~R3传递参数,当参数超过4个时,使用数据栈来传递参数。在参数传递时,所有参数看作是存放在连续的内存字单元中的字数据。然后,依次将各字数据传递到寄存器R0~R3中。 如果参数多于4个,将剩余的字数据传送到数据栈中,入栈的顺序与参数顺序相反,即最后一个字数据先入栈。

(3)子程序结果返回规则

结果为一个32位整数,可通过寄存器R0返回;结果为一个64位整数,可通过寄存器R0,R1返回,依次类推;结果为一个浮点数时,可通过运算部件的寄存器F0、D0或者S0来返回;结果为复合型的浮点数(如复数)时,可通过寄存器F0~Fn或者D0~Dn来返回。对于位数更多的结果,需通过内存来传递,如通过数据栈来传递。

2.C/C++代码中嵌入汇编指令

内嵌汇编指令:

  • 表现为独立定义的函数体

  • ADS中可直接引用C语言的变量定义

  • 数据交换必须通过ATPCS(ARM-Thumb Procedure Call Standard)进行

1.内嵌汇编指令的语法格式
1
2
3
4
5
6
7
__asm{
instruction

instruction
}; //ADS中支持
特别注意:
“__asm” 是两个下划线。
2.内嵌汇编指令的特点

(1)操作数

作为操作数的寄存器和常量可以是C/C++表达式。 是char、short、int类型,而且这些表达式都是作为无符号数进行操作。

编译器将会计算这些表达式的值,并为其分配寄存器。

(2)物理寄存器 内嵌汇编指令中使用物理寄存器的限制:

不能直接向PC寄存器中赋值,程序的跳转只能 通过B指令和BL指令实现。

在内嵌汇编指令中,不要使用过于复杂的 C/C++表达式。

编译器可能会使用R12寄存器或R13寄存器存放 编译的中间结果,在计算表达式值时可能会将 寄存器R0到R3、R12以及R14用于子程序调用。 一般不要指定物理寄存器(会影响编译器分配 寄存器)。

(3)常量 常量前的符号#可省略。若表达式前使用了符号#, 则必须是一个常量。

(4)指令展开 如果包含常量操作数,该指令可能会被汇编器展 开成几条指令。

例如指令:

ADD R0,R0,#1023 可能会被展开成下面的指令序列:

ADD R0,R0,#1024 SUB R0,R0,#01 MUL指令会被展开成一系列加法和移位操作。

(5)标号 C/C++程序中的标号可被内嵌的汇编指令使用。但只有B指令可使用C/C++程序中的标 号,指令BL不能使用C/C++程序中的标号。 指令B使用C/C++程序中的标号格式: B{cond} label

(6)内存单元的分配 所用的内存单元的分配都是通过C/C++程序 完成的,分配的内存单元通过变量供内嵌的汇编器使用。

(7)SWI和BL指令的使用 内嵌SWI和BL指令中3个可选寄存器列表:

  • 第1个寄存器列表用于存放输入的参数。
  • 第2个寄存器列表用于存放返回的参数。
  • 第3个寄存器列表的内容供被调用的子程序作为工作寄存器。
3.内嵌的汇编 与 armasm的区别

在功能和使用方法上主要有以下特点:

  • 不能写PC (MOV PC, LR)
  • 不支持伪指令LDR Rn,=expression,但可用指令: MOV Rn,expression来代替。
  • 除NOP外,不支持ADR、ADRL等伪指令。
  • 指令中的C变量不要与任何物理寄存器重名
  • LDM/STM指令中的寄存器列表只能使用物理寄存器,不 能使用C表达式
  • 不支持指令BX/BLX
  • 用户不用维护数据栈
  • 不要轻易改变处理器模式。
  • 不支持内存分配操作

例5.15 使能和禁止中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
__inline void 
enable_IRQ(void)
{
int tmp;
__asm
{
MRS tmp,CPSR
BIC tmp, tmp, #0x80
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(void)
{
disable_IRQ( );
enable_IRQ( ) ;
}

3.变量互访

在C程序中声明的全局变量可被汇编程序通 过地址间接访问。具体访问方法如下:

使用IMPORT伪操作声明该全局变量。

使用LDR伪指令读取该全局变量的内存地 址,通常该全局变量的内存地址值存放在程序 的数据缓冲池中(literal pool)。

根据该数据的类型, 使用相应的LDR指令读取该全局变量的值; 使用相应的STR指令修改该全局变量的值。

各数据类型及对应的LDR/STR指令如下:

  • 对于无符号的char类型变量通过指令LDRB/STRB来读/写。
  • 对于无符号的short类型变量通过指令LDRH/STRH来读/写。
  • 对于int类型的变量通过指令LDR/STR来读/写
  • 对于有符号的char类型的变量通过指令LDRSB来读取。
  • 对于有符号的char类型的变量通过指令STRB来写入。
  • 对于有符号的short类型的变量通过指令LDRSH来读取。
  • 对于有符号的short类型的变量通过指令STRH来写入。
  • 对于小于8个字的结构型变量,可以通过一条LDM/STM指令 来读/写整个变量。
  • 对于结构型变量的数据成员,可以使用相应的LDR/STR指令 来访问,这时必须知道该数据成员相对于结构型变量开始地 址的偏移量。

【例5.16 】从汇编程序中访问C程序全局变量 在汇编程序中访问C程序全局变量。

  • 程序中变量globvl是在C程序中声明的全局变量。
  • 在汇编程序中首先用IMPORT伪操作声明该变量;
  • 将其内存地址读入到寄存器R1中;
  • 再将其内存单元中的值读入到寄存器R0中;
  • 修改后再将寄存器R0的值赋于变量globvl。
1
2
3
4
5
6
7
8
9
10
AREA globals, CODE, READONLY
EXPORT asmsub
IMPORT globvl
asmsub
LDR R1, =globvl ;获得变量地址
LDR R0, [R1] ;读入
ADD R0, R0, #2 ;修改变量的内容
STR R0, [R1] ;回存
MOV PC, LR
END

4.相互调用

1.C语言程序调用汇编语言程序

汇编语言程序的设计要遵守ATPCS。在汇 编语言中需用EXPORT伪操作来说明,使得本程序可被其他程序调用。 C语言程序调用该汇编语言程序之前,需要在C语言程序中使用EXTERN关键词来声明该汇编语言程序。

1
2
3
4
5
6
7
8
9
10
AREA SCopy, CODE, 
READONLY
EXPORT my_strcpy
my_strcpy
LDRB R2, [R1],#1
STRB R2, [R0],#1
CMP R2, #0
BNE my_strcpy
MOV PC, LR
END

6. ARM开发工具的使用

嵌入式调试系统应包括调试主机、仿真器和目标板3个部分。

调试方法一般有如下4种:

1) 指令集模拟器(软调试)

2) 驻留监控软件

3) JTAG仿真器

4) 在线仿真器(仿真头)

7.硬件系统设计

1.嵌入式硬件系统的结构

image-20221203161059013 image-20221210141503201

1.S3C44B0芯片概述

1.1简介

Samsung公司推出的S3C44B0微处理器是基于16/32位的RISC结构,为手持设备和一般类型的应用提供了高性价比和高性能的解决方案。为了降低成本,S3C44B0提供了丰富的内置资源,包括:

  • 8KB Cache、
  • 内部SRAM、LCD控制器、
  • 带自动握手的2通道UART、
  • 4通道DMA、
  • FP/EDO/SDRAM控制器、
  • 带PWM功能的5通道定时器、
  • RTC、
  • 8通道的10位ADC、IIC、IIS总线接口、同步SIO接口和PLL倍频器等。
  1. S3C44B0采用了ARM7TDMI内核,附带0.25um工艺的CMOS标准宏单元和存储编译器。
  2. 它的低功耗和出色的静态设计特别适用于对成本和功耗敏感的场合。
  3. S3C44B0的杰出特性是它的内部CPU核,由ARM公司设计的ARM7TDMI的RISC处理器,主频高达66MHz
  4. 它集成了Thumb代码压缩器,片上的ICE (In-Circuit Emulator)断点调试支持和一个32位的硬件乘法器。

1.2特性

1.2.1体系结构

● 集成了手持设备和通用嵌入式系统应用的解决方案;
● 强大的指令集;
● Thumb代码压缩机制,最大化代码密度的同时保持了32位指令的性能;
● 基于JTAG的片上集成ICE调试解决方案;
● 32位的硬件乘法器;
● 实现低功耗的新型总线结构

1.2.2系统管理

● 支持大/小端模式;
● 寻址空间每bank32M字节,总共达256M字节;
● 支持每bank可编程的8/16/32位数据总线宽度;
● 7个bank具有固定的bank起始地址和可编程的bank大小;
● 1个bank具有可编程的起始地址和大小;
● 8个存储器bank,包含6个ROM、SRAM,2个SROM/SDRAM/DRAM存储空间;
● 所有的存储bank具有可编程的操作周期;
● 支持外部等待信号延长总线周期;
● 支持掉电时DRAM/SDRAM的自刷新模式;
● 支持均匀/非均匀的DRAM地址。

1.2.3 Cache和内部SRAM

● 一体化的8k字节Cache;
● 未用的Cache空间可以用做0/4/8k字节的SRAM空间;

1.2.4时钟和电源管理

● 低功耗;
● 片上PLL使CPU工作频率最大达到66MHz;
● 可以通过软件设置各功能模块的输入时钟;
● 电源模式包括正常、
慢速(不经PLL低频时钟)、
空闲(停CPU时钟)、
停止模式(停所有时钟)
—通过EINT[7:0]或RTC报警中断从停止模式唤醒。

1.2.5中断控制器

● 30个中断源(看门狗定时器、6个定时器、6个UART、8个外部中断、4个DMA、2个RTC、1个ADC、1个IIC、1个SIO);
● 采用向量化的IRQ中断模式,可以减少中断延迟
● 可选的电平/边沿模式触发外部中断;
● 电平/边沿模式具有可编程的优先级;
● 支持FIQ为紧急的中断请求进行服务。

1.3引脚信号描述

image-20230125123146117 image-20230125131839534 image-20230125132646155 image-20230125133411513 image-20230125133811640 image-20230125134541013 image-20230125135339987 image-20230125135457708 image-20230125135931987 image-20230125140133440

2.单元电路设计

2.1电源电路

在系统中,需要使用5V、3.3V和2.5V的直流稳压电源。为了提高供电的稳定性和可靠性,通常采用集成线性稳压电源对CPU和外围电路供电。

  1. CPU内核工作需要供给的**+2.5V电源采用LM1117-2.5**产生
  2. CPU 的I/O和外设工作所需要的**+3.3V电源是采用LM1117-3.3** 产生
  3. 其他的外围芯片如串行口接口电路、PS/2键盘接口电路以及93C46IIC接口芯片等需要5V的直流电源,5V的直流电源由外部直接提供
image-20230125141408059

2.2晶振电路

S3C44B0的时钟源可以用外部晶体(无源晶振)来产生,也可以直接输入外部时钟(有源晶振),这由CPU的OM[3:2]位的状态决定。 OM[3:2]位的状态在nRESET引脚的上升沿由OM3和OM2脚的电平决定:

OM[3:2]=00时使用无源晶振,通过内部的振荡电路与外部的电容和无源晶振一起振荡产生CPU所需要的时钟;

OM[3:2]=01时使用外部有源晶振直接提供时钟,不需要通过CPU内部的振荡电路产生时钟;

OM[3:2]=其它时处于测试模式,一般不使用。

在复位后PLL启动,但在用S/W(软件)设置有效值到PLLCON寄存器之前,PLL的输出(FOUT)不能被使用,这时FOUT直接输出Crystal Clock或外部时钟。晶振电路用于向CPU及其他电路提供工作时钟。 在系统中,S3C44B0可以使用有源晶振,也可以使用无源晶振。

image-20230125141422229

2.3看门狗与复位电路

  • 硬件看门狗(WDT, WATCHDOG TIMER)是利用了一个定时器,来监控主程序的运行
  • 也就是说在主程序的运行过程中,CPU要在定时时间到来之前对定时器进行复位(喂狗)
  • 如果出现死循环,或者说PC指针不能回来。那么定时时间到达后,如果CPU还没有产生喂狗信号给WDT,WDT就会输出信号使CPU复位。

嵌入式系统中两类看门狗:

1、CPU内部自带的看门狗:将一个芯片中的定时器来作为看门狗,通过程序的初始化,写入初值,设定溢出时间,并启动定时器。程序按时对定时器赋初值(或复位),以免其溢出。

优点:可以通过程序改变溢出时间;可以随时禁用
缺点:需要初始化;如果程序在初始化、启动完成前跑飞或在禁用后跑飞,看门狗就无法复位系统,这样看门狗的作用就没有了,系统恢复能力降低。

2、独立的看门狗芯片:这种看门狗主要有一个用于喂狗的引脚(一般与CPU的GPIO相连)和一个复位引脚(与系统的RESET引脚相连),如果没有在一定时间内改变喂狗脚的电平,复位引脚就会改变状态复位CPU。此类看门狗一上电就开始工作,无法禁用。

优点:无须配置,上电即用。系统必须按时喂狗,系统恢复能力高
缺点:无法灵活配置溢出时间,无法禁用,灵活性降低

image-20221207151332982

硬件看门狗与复位电路中,按键S1是手动复位按键,ADM706TAR芯片的第7脚周期性的按设定的时间间隔检查该引脚的输入信号,如果CPU在规定的时间内没有输入高电平(又称为喂狗),则说明程序跑飞了,ADM706TAR的第6脚便产生一个复位信号,使CPU复位。

2.4 存储单元电路

一个bank–32M字节,共8个bank,每个bank初地址最高最高位相差2。

image-20221207151656687 image-20221212140103371
  • 在程序空间flash ROM 内(在主板上对应2M 字节大小的HY29LV160器件)可以固化一段启动系统并对系统进行初始化的程序——Boot Loader 程序。
  • 上图中Flash ROM 存储器映射在了系统的bank0 上,也就是说,系统上电时处理器即从Flash ROM 的0x00000000 地址处取得指令开始运行。这个地址上的Boot Loader程序完成了时钟设置初始化、中断矢量的定义、存储器的参数设置、堆栈地址定义等工作,这些设置对于系统正常启动是非常重要的。
  • 由于Flash ROM 是非易失性的存储器,因此程序就算掉电也不会丢失。但是如果由于某个误操作覆盖了Flash ROM 中启动程序的内容,系统就将无法正常启动,这时就需要重新将Boot Loader程序烧写到Flash ROM中
1.Flash存储器接口电路设计

FLASH的地址线[A19~A0]与S3C44B0的地址总线[ADDR20~ADDR1]相连,这是因为S3C44B0的存储系统在存储代码时需要半字对齐方式(16位方式)

  • 16位数据总线[DQ15~DQ0]与S3C44B0的低16位数据总线[XDATA15~XDATA0]相连。
  • 注意此时应将S3C44B0的BWSCON寄存器的DW0[2:1]位置为“01”,这两位指示BANK0的数据总线宽度,由OM[1:0]脚上、下拉确定。
image-20230125145639678
2.SDRAM存储器接口电路设计
  • 目前常用的SDRAM为8位/16位的数据宽度,工作电压一般为3.3V。
  • HY57V641620的BA1、BA0(Bank Address)(选择4个Bank) 接S3C44B0的地址总线ADDR<22>、ADDR<21>;
  • HY57V641620的DQ15~DQ0接S3C44B0的数据总线的低 16位XDATA<15>~XDATA<0>;
  • HY57V641620的LDQM、UDQM分别接S3C44B0的DQM0 (Pin11)、DQM1(Pin12)(SDRAM数据屏蔽信号)。
  • 将总线宽度与等待状态控制寄存器BWSCON的DW6[25:24]=01b,这两位确定BANK6的数据总线宽度为16位。
image-20230125151037912
3.总结

存储系统的数据总线位宽决定。

  • 位宽为8,则S3C44B0的A0连接存储器的A0
  • 位宽为16,则S3C44B0的A1连接存储器的A0
  • 位宽为32,则S3C44B0的A2连接存储器的A0

8.程序设计题模板

1
2
3
4
5
6
7
8
9
10
11
12
     AREA Fctrl,CODE,READONLY ;声明代码段 Fctrl
EXPORT/IMPORT/EXTERN 标号 ;声明或引用全局标号(可以没有)
ENTRY ;标识程序入口
CODE32 ;声明 32 位 ARM 指令
START
;初始化寄存器

;中间处理程序(包括循环,分支等等)

Stop
B Stop ;文件结束
END

中断

向量中断是一种中断方式。每个外设预先被设置一个中断识别码,当CPU响应某外设中断请求时,由硬件电路向CPU提供该外设的中断识别码,CPU由此获得中断服务程序入口地址,转入中断服务,这种方式称为向量中断。

中断向量指的是在向量中断方式中,中断服务程序的入口地址。

两者的区别为:向量中断是由中断事件自己通过硬件提供中断服务程序的入口地址的中断;非向量中断是中断事件不能直接提供中断服务程序的入口地址的中断

ARM指令与伪指令的区别

  伪指令经过汇编编译后就不存在了,而指令依旧存在。伪操作只是汇编过程中起作用, ,一旦汇编结束 ,伪操作也就随之消失。另外,ARM 伪指令不属于 ARM 指令集中的指令,是为了编程方便而定义的。

  总结:其实LDR、ADRL、ADR的作用和用法基本类似,主要区别是加载数的范围大小不同,LDR加载范围最大,ADRL次之,ADR最小。另外,使用LDR伪指令时,操作数要带“=”号,ADRL和ADR则不需要。


Embeded theory and appliment
https://alleyf.github.io/2022/11/8134dd087a1a.html
作者
alleyf
发布于
2022年11月21日
更新于
2023年7月4日
许可协议