分享

ARM Cortex-M3 学习笔记

 waston 2017-10-24
最近在学ARM Cortex-M3,找了本号称很经典的书“An Definitive Guide to The ARM Cortex-M3”在看。这个系列学习笔记其实就是在学习这本书的过程中做的读书笔记。

第一章 简介

这一章的内容主要是介绍Cortex-M3内核是如何的强悍。还顺带着介绍了ARM系列的发展历史和路线。

ARM公司成立于1990年,ARM公司是Advanced RISC Machines Ltd.的缩写,当然ARM就是Advanced RISC Machines的缩写了,ARM 公司是由Apple,Acorn和VLSI三家共同出资创建的。

ARM 处理器内核的发展可以用一张图来说明:

图 1 ARM处理器内核的发展
从上图中可以看到,ARM 7 系列是基于 v4 内核的,ARM9系列是基于v5内核的,ARM11是基于v6内核的,而Cortex 系列则是基于v7内核的。

指令集的演化可以用下图来描述:

图 2指令集演进图

Cortex-M3 采用Thumb-2指令集,不支持ARM指令集,对Thumb-2指令集其实也只是部分的支持,有很少一部分Thumb-2指令是不支持的。由于不支持ARM指令集,也就没有了处理器状态在Thumb和ARM之间来回的切换,省去了很多麻烦。

第二章 Cortex-M3 概述
Cortex M3 内核的组成可以用一张简图来表示:

图 3 Cortex-M3内核简图

内核寄存器组包括R0-R15,R0-R12是通用寄存器,部分Thumb指令只能访问R0-R7。
R13是堆栈指针,实际上有两个,一个是主堆栈指针(MSP)另一个是进程堆栈指针(PSP),堆栈要求4字节对齐。
R14存放程序的返回地址。
R15是PC,记录程序当前的执行地址。

特殊功能寄存器包括:
程序状态字寄存器组(PSRs)
中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)
控制寄存器(CONTROL)

运行模式和特权级别
两种运行模式:handler mode和thread mode。
两种特权级别:特权级和用户级

中断例程工作在特权级,普通程序代码既可以工作在特权级也可以在用户级。
相互间的转换用下图来描述:

图 4 操作模式转换图
嵌套向量中断控制器NVIC(Nested Vectored Interrupt Controller)
 可嵌套中断支持
 向量中断支持
 动态优先级调整支持
后面有一章会对NVIC进行详细的介绍。

地址空间分配
与以前的ARM 内核不同,Cortex-M3对地址空间的分配有个大的框架。向NVIC寄存器等的地址也都被固定了下来,这就方便了在程序的移植。具体的地址分配可以用下图来简单的描述:

图 5 地址分配

总线
Cortex-M3内部有若干个总线接口,以使Cortex-M3能同时读取程序代码和访问内存,它们是:
  指令存储区总线(两条)
  系统总线
  私有外设总线

中断和异常
Cortex-M3支持11个内部异常和240个外部中断。当然,对具体的芯片,可以支持的外部中断数量肯定就要少的多了。

调试支持
在支持传统的JTAG基础上,还支持更新更好的串行线调试接口。
基于CoreSight调试解决方案,使得处理器哪怕是在运行时,也能访问处理器状态和存储器内容。
内建了对多达6个断点和4个数据观察点的支持。

第三章 Cortex-M3 基础

这章的内容有不少是和第二章重复的。重复的部分这里就不再提了。

堆栈

Cortex-M3 的堆栈是倒生的,实栈顶。也就是R13指向最后一次压入堆栈的内容。
下面是个简单的例子:
PUSH {R0} ; *(--R13)=R0。R13是long*的指针
POP {R0} ; R0= *R13++

PUSH 和 POP 可以一次操作多个寄存器:
subroutine_1
PUSH {R0-R7, R12, R14} ; 保存寄存器列表
… ; 执行处理
POP {R0-R7, R12, R14} ; 恢复寄存器列表
BX R14 ; 返回到主调函数
上面的例子中顺带也用到了R14。因此就不单独讲解R14了。

特殊功能寄存器组

访问特殊功能寄存器需要用专用指令:
MRS <gp_reg>, <special_reg> ;读特殊功能寄存器的值到通用寄存器
MSR <special_reg>, <gp_reg> ;写通用寄存器的值到特殊功能寄存器

程序状态寄存器在其内部又被分为三个子状态寄存器:
 应用程序PSR(APSR)
 中断号PSR(IPSR)
 执行PSR(EPSR)
参看下图:

图 6 Cortex-M3中的程序状态寄存器(xPSR)

PRIMASK,FAULTMASK和BASEPRI 控制中断和异常的开启和屏蔽
PRIMASK = 1后只有NMI和硬fault可以响应,默认为0
FAULTMASK = 1后只有NMI和硬fault可以响应,默认为0
BASEPRI:优先级号大于等于此值的中断都被屏蔽
只有在特权级下,才允许访问这3个寄存器。

控制寄存器(CONTROL)
CONTROL[0]=1,用户级的线程模式
CONTROL[0]=0,特权级的线程模式
CONTROL[1]=1,选择使用MSP
CONTROL[1]=0,选择使用PSP

中断向量表

默认的中断向量表在地址0处,利用NVIC的重定位寄存器可以将其重定位。
另外,中断向量表的第一个向量的位置存放的是MSP的初始值。

第四章 指令系统

基本语法

这里介绍的汇编语法是ARM汇编器的语法,如果采用其他的汇编器,比如gcc中的as,语法是不同的。

汇编指令的典型模式如下所示:

Label

opcode operand1, operand2, … ;comment

标号是可选的,如果有,它必须顶格写,标号后面不需要“:”。

操作码是指令的助记符,它的前面必须有至少一个空白符。

立即数必须以“#”开头,16进制数字表示与C语言的方法相同,比如:

MOV R0, #0x12 ; R0  0x12

使用EQU指示字来定义常数:

NVIC_IRQ_SETEN0 EQU 0xE000E100 ; 注意:常数定义必须顶格写

NVIC_IRQ0_ENABLE EQU 0x1

LDR R0, =NVIC_IRQ_SETEN0 ;在这里的LDR是个伪指令,它会被汇编器转换成

;一条“相对PC的加载指令”

MOV R1, #NVIC_IRQ0_ENABLE ; 把立即数传送到R1中

DCI 可以在汇编代码中插入1 个half word (2个字节),通常用这条指令插入汇编器不支持的指令。

DCB来定义一串字节常数

DCD来定义一串32位整数

比如下面的例子:

LDR R3, =MY_NUMBER ; R3= MY_NUMBER

LDR R4, [R3] ; R4= *R3

LDR R0, =HELLO_TEXT ; R0= HELLO_TEXT

BL PrintText ; 呼叫PrintText以显示字符串,R0传递参数

MY_NUMBER

DCD 0x12345678

HELLO_TEXT

DCB ”Hello\n”,0

指令后缀

后缀

含义

S

要求更新APSR中的相关标志,例如:

ADDS R0, R1 ; 根据加法的结果更新APSR中的标志

EQ,NE,LT,GT等

有条件地执行指令。

EQ=Euqal, NE= Not Equal, LT= Less Than, GT= Greater Than,例如:

BEQ <Label> ; 仅当EQ满足时转移

统一汇编语言(UAL)

与Thumb-2指令集同时出现的还有新的汇编语法(统一汇编语言)。利用统一汇编语言的语法,我们可以方便的选择当前的语句是被编译为16位的指令还是编译为32位的指令。

ADD R0, R1 ; 使用传统的Thumb语法

ADD R0, R0, R1 ; 引入UAL后允许的等效写法(R0=R0+R1)

如果使用传统的Thumb语法,有些指令会默认地更新APSR。

如果使用UAL语法,则必须指定S后缀才会更新。例如:

AND R0, R1 ;传统的Thumb语法

ANDS R0, R0, R1 ;等值的UAL语法(必须有S后缀)

.W(Wide)后缀指定32位指令,.N后缀制定采用16位指令。如果没有给出后缀,汇编器会先试着用16位指令以给代码瘦身,如果不行再使用32位指令。例如:

ADDS R0, #1 ;汇编器将为了节省空间而使用16位指令

ADDS.N R0, #1 ;指定使用16位指令(N=Narrow)

ADDS.W R0, #1 ;指定使用32位指令(W=Wide)

32位的Thumb-2指令可以half word 对齐。不用word对齐使得代码中混用16位指令和32位指令变得很容易了。

数据传送类指令
寄存器到寄存器传送:MOV 指令、MVN指令
MOV R8, R3; R8 = R3
MVN R8, R3; R8 = -R3
学过微机原理的都应记得,x86中一条MOV 指令存储器和寄存器间的任意传送。ARM 中是不行的,这也是CISC和RISC 内核的一个比较明显的区别。

存储器到寄存器传送:LDRx 指令、LDMxy指令
寄存器到存储器:STRx 指令、STMxy指令

LDRx 指令的x可以是B(byte)、H(half word)、D(Double word)或者省略(word),具体的用法如下:

示例

功能描述

LDRB Rd, [Rn, #offset]

从地址Rn+offset处读取一个字节送到Rd

LDRH Rd, [Rn, #offset]

从地址Rn+offset处读取一个半字送到Rd

LDR Rd, [Rn, #offset]

从地址Rn+offset处读取一个字送到Rd

LDRD Rd1, Rd2, [Rn, #offset]

从地址Rn+offset处读取一个双字(64位整数)送到Rd1(低32位)和Rd2(高32位)中。

STRx 指令的x同样可以是B(byte)、H(half word)、D(Double word)或者省略(word),具体的用法如下:

示例

功能描述

STRB Rd, [Rn, #offset]

把Rd中的低字节存储到地址Rn+offset处

STRH Rd, [Rn, #offset]

把Rd中的低半字存储到地址Rn+offset处

STR Rd, [Rn, #offset]

把Rd中的低字存储到地址Rn+offset处

STRD Rd1, Rd2, [Rn, #offset]

把Rd1(低32位)和Rd2(高32位)表达的双字存储到地址Rn+offset处

LDRx和STRx指令还有一种带预索引的格式,下面举个例子(注意语句中的“!”):

LDR.W R0,[R1, #20]! ;预索引

上面语句的意思是先把地址R1+offset处的值加载到R0,然后,R1 ßR1+ 20

还有一种后索引形式,注意与上面的预索引的区别(还要注意语句中没有“!”):

STR.W R0, [R1], #-12 ;把R0的值存储到地址R1处。完毕后, R1ßR1+(-12)

LDMxy指令和STMxy指令可以一次传送更多的数据。

X可以为要I或D,I表示自增(Increment),D表示自减(Decrement)。

Y可以为A或B,表示自增或自减的时机是在每次访问前(Before)还是访问后(After)。

另外,指令带有“.W”后缀表示这条指令是32位的Thumb-2指令,否则是16位的指令。

示例

功能描述

LDMIA Rd!, {寄存器列表}

从Rd处读取多个字,并依次送到寄存器列表中的寄存器。每读一个字后Rd自增一次,16位指令

LDMIA.W Rd!, {寄存器列表}

从Rd处读取多个字,并依次送到寄存器列表中的寄存器。每读一个字后Rd自增一次

STMIA Rd!, {寄存器列表}

依次存储寄存器列表中各寄存器的值到Rd给出的地址。每存一个字后Rd自增一次,16位指令

STMIA.W Rd!, {寄存器列表}

依次存储寄存器列表中各寄存器的值到Rd给出的地址。每存一个字后Rd自增一次

LDMDB.W Rd!, {寄存器列表}

从Rd处读取多个字,并依次送到寄存器列表中的寄存器。每读一个字前Rd自减一次

STMDB.W Rd!, {寄存器列表}

存储多个字到Rd处。每存一个字前Rd自减一次

这里需要特别注意!的含义,它表示要自增(Increment)或自减(Decrement)基址寄存器Rd的值,时机是在每次访问前(Before)或访问后(After)。比如:

假设 R8=0x8000,则

STMIA.W R8!, {R0-R3} ; R8值变为0x8010

STMIA.W R8, {R0-R3} ; R8值不变

上面两行代码都是将R0-R3共16个字节的数据存储到从0x8000开始的16个字节空间中,唯一的区别是第一条指令执行完后R8被更新为0x8010,而第二条指令不更新R8。

立即数的加载

MOV支持8位立即数加载,比如:

MOV R0, #0x12

32位指令MOVW(加载到寄存器的低16位)和MOVT(加载到寄存器的高16位)可以支持16位立即数加载。如果要加载32位的立即数,必须先使用MOVW,再使用MOVT,因为MOVW会清零高16位。

LDR 和ADR的区别

LDR和ADR都是伪指令,都可以用来加载一个立即数(也可以是一个地址),如果加载的是程序地址,LDR会自动地把LSB置位,ADR则不会:

LDR R0, =address1 ; R0= 0x4000 | 1

ADR R1, address1 ; R1= 0x4000。注意:没有“=”号

address1

0x4000: MOV R0, R1

特殊功能寄存器只能用MSR/MRS指令访问:

MRS <gp_reg>, <special_reg> ;读特殊功能寄存器的值到通用寄存器

MSR <special_reg>, <gp_reg> ;写通用寄存器的值到特殊功能寄存器

下面是两个例子:

MRS R0, PRIMASK ; 读取PRIMASK到R0中

MSR BASEPRI, R0 ;写入R0到BASEPRI中

但是需要注意大多数的特殊功能寄存器都只能在特权级下访问,非特权级下只能访问APSR

数据处理指令

Cortex-M3支持的数据处理指令非常多,这里就捡重要的、常用的来介绍。

四则运算指令

基本的加、减法运算有四条指令,分别是ADD、SUB、ADC、SBC

ADD Rd,Rn, Rm ; Rd = Rn+Rm

ADD Rd,Rm ; Rd += Rm

ADD Rd,#imm ; Rd += imm

ADC Rd,Rn, Rm ; Rd = Rn+Rm+C

ADC Rd,Rm ; Rd += Rm+C

ADC Rd,#imm ; Rd += imm+C

SUB Rd,Rn ; Rd -= Rn

SUB Rd,Rn, #imm3 ; Rd = Rn-imm3

SUB Rd,#imm8 ; Rd -= imm8

SUB Rd,Rn, Rm ; Rd = Rm-Rm

SBC Rd,Rm ; Rd -= Rm+C

SBC.W Rd,Rn, #imm12 ; Rd = Rn-imm12-C

SBC.W Rd,Rn, Rm ; Rd = Rn-Rm-C

除此之外,还有反向减法指令RSB:

RSB.W Rd,Rn, #imm12 ; Rd = imm12-Rn

RSB.W Rd,Rn, Rm ; Rd = Rm-Rn

乘、除法指令包括 MUL、UDIV/SDIV 等。

MUL Rd,Rm ; Rd *= Rm

MUL.W Rd,Rn, Rm ; Rd = Rn*Rm

UDIV Rd,Rn, Rm ; Rd = Rn/Rm (无符号除法)

SDIV Rd,Rn, Rm ; Rd = Rn/Rm (带符号除法)

一条指令可以实现乘加运算(通常只在DSP中才有):

MLA Rd, Rm, Rn, Ra ; Rd = Ra+Rm*Rn

MLS Rd, Rm, Rn, Ra ; Rd = Ra-Rm*Rn

还能进行32位乘32位的乘法运算(结果为64位):

SMULL RL, RH, Rm, Rn ;[RH:RL]= Rm*Rn,带符号的64位乘法

SMLAL RL, RH, Rm, Rn ;[RH:RL]+= Rm*Rn,带符号的64位乘法

UMULL RL, RH, Rm, Rn ;[RH:RL]= Rm*Rn,无符号的64位乘法

SMLAL RL, RH, Rm, Rn ;[RH:RL]+= Rm*Rn,无符号的64位乘法

由于有了这些指令,Cortex-M3具有了相当的计算能力,可以采用Cortex-M3代替曾经只能用DSP才能完成的计算。

逻辑运算指令

逻辑运算相关的指令也很多,常用的包括AND,ORR, BIC(位段清零), ORN(按位或反码), EOR(异或),LSL(逻辑左移), LSR(逻辑右移), ASR(算数右移), ROR(圆周右移), RRX(带进位右移一位)

;按位与

AND Rd, Rn ; Rd &= Rn

AND.W Rd, Rn, #imm12 ; Rd = Rn & imm12

AND.W Rd, Rm, Rn ; Rd = Rm & Rn

;按位或

ORR Rd, Rn ; Rd |= Rn

ORR.W Rd, Rn, #imm12 ; Rd = Rn | imm12

ORR.W Rd, Rm, Rn ; Rd = Rm | Rn

;按位清零

BIC Rd, Rn ; Rd &= ~Rn

BIC.W Rd, Rn, #imm12 ; Rd = Rn & ~imm12

BIC.W Rd, Rm, Rn ; Rd = Rm & ~Rn

;按位或反

ORN.W Rd, Rn, #imm12 ; Rd = Rn | ~imm12

ORN.W Rd, Rm, Rn ; Rd = Rm | ~Rn

;按位异或

EOR Rd, Rn ; Rd ^= Rn

EOR.W Rd, Rn, #imm12 ; Rd = Rn ^ imm12

EOR.W Rd, Rm, Rn ; Rd = Rm ^ Rn

;逻辑左移

LSL Rd, Rn, #imm5 ; Rd = Rn<<imm5

LSL Rd, Rn ; Rd <<= Rn

LSL.W Rd, Rm, Rn ; Rd = Rm<<Rn

;逻辑右移

LSR Rd, Rn, #imm5 ; Rd = Rn>>imm5

LSR Rd, Rn ; Rd >>= Rn

LSR.W Rd, Rm, Rn ; Rd = Rm>>Rn

;算术右移

ASR Rd, Rn, #imm5 ; Rd = Rn>> imm5

ASR Rd, Rn ; Rd =>> Rn

ASR.W Rd, Rm, Rn ; Rd = Rm>>Rn

;循环右移

ROR Rd, Rn ;

ROR.W Rd, Rm, Rn ;

符号扩展指令

SXTB Rd, Rm ; Rd = Rm的带符号扩展,把带符号字节整数扩展到32位

SXTH Rd, Rm ; Rd = Rm的带符号扩展,把带符号半字整数扩展到32位

字节序反转指令

REV.W Rd, Rn; 在字中反转字节序

REV16.W Rd, Rn; 在高低半字中反转字节序

REVSH.W; 在低半字中反转字节序,并做带符号扩展

其他计算类指令

带符号扩展指令:

SXTB Rd, Rm ; Rd = Rm的带符号扩展

SXTH Rd, Rm ; Rd = Rm的带符号扩展

数据序翻转指令

REV.W Rd, Rn ;在字中反转字节序

REV16.W Rd, Rn ;在高低半字中反转字节序

REVSH.W ; 在低半字中反转字节序,并做带符号扩展

饱和运算

饱和运算指令在其他单片机中很少见。这类指令的初衷非常好,但是C语言并不直接支持这类运算,要在C程序中使用要么采用内联汇编要么就要将其封装成个函数,都不是很方便。这可能会限制这类指令的使用。关于饱和运算指令的作用,可以用下图来形象的展示:


图 1 饱和运算指令的作用

下面是相关指令的用法:

SSAT.W Rd, #imm5, Rn, {,shift}; 以带符号数的边界进行饱和运算(交流)

USAT.W Rd, #imm5, Rn, {,shift}; 以无符号数的边界进行饱和运算(带纹波的直流)

无条件跳转指令

跳转指令分为无条件跳转和有条件跳转两大类。无条件跳转类指令非常简单,常见的就四种形式。

B Label ;跳转到Label处对应的地址, 无条件跳转指令

BX reg ;跳转到由寄存器reg给出的地址, 无条件跳转指令

BL Label ;跳转到Label对应的地址,并且把跳转前的下条指令地址保存到LR

BLX reg ;跳转到由寄存器reg给出的地址,并根据REG的LSB切换处理器状态,还要把转移前的下条指令地址保存到LR

标志位与条件转移指令

在讲解条件跳转指令之前。先要讲讲APSR中的四个四个标志位:N, Z, C, V。

实际上,Cortex-M3中的APSR的标志位共有5个,但只有NZCV这四个可以用于条件跳转指令。下图中先给出Cortex-M3中的程序状态寄存器(xPSR)的位图。


图 1 Cortex-M3中的程序状态寄存器(xPSR)

可以看出,NZCV这四位位于xPSR 的最高四位。这四位的作用分别如下:

标志位

作用

N

负数(上一次操作的结果是个负数)。N=操作结果的MSB

Z

零(上次操作的结果是0)。当数据操作指令的结果为0,或者比较/测试的结果为0时,Z置位。

C

进位(上次操作导致了进位)。C用于无符号数据处理,最常见的就是当加法进位及减法借位时C被置位。此外,C还充当移位指令的中介(详见v7M参考手册的指令介绍节)。

V

溢出(上次操作结果导致了数据的溢出)。该标志用于带符号的数据处理。比如,在两个正数上执行ADD运算后,和的MSB为1(视作负数),则V置位。

关于C和V这两位我要多说几句。Cortex-M3中的进位标志与其他一些单片机有些不同。对加法运算它表示的是结果有进位,这与其他单片机中的含义是相同的。对减法运算,它表示的是结果借位,与有一些单片机(比如Freescale 的68HC11/12系列)中的含义正好相反。之所以这里这样定义进位标志,我想是这样考虑的。整数的减法运算A-B实际是转化为了A+(-B),-B用补码表示。进位标志C指示的是A与(-B)相加时是否有进位。A-B无进位等价于A+(-B)有进位。当然,后来我发现有个特例,就是当B=0时,(-B)=0,A-0 是没有进位的,但A+(-0)也没有进位。这时可以这样理解,对0取反操作时,也就是得到-0时已经产生的进位(取反加1,加1时进位了)。所以结果也认为是进位了。

溢出位(V)置位有四种情况:

1.        两个整数相加结果为负数时

2.        两个负数相加结果为正数时

3.        一个正数减一个负数结果为负数时

4.        一个负数减一个正数结果为正数时

这四种情况与我们的直观是一致的,因此不需要特殊记忆。

担任条件跳转及条件执行的判据时,这4个标志位既可单独使用,又可组合使用,以产生共15种跳转判据,如下表所示。

符号

条件

关系到的标志位

EQ

相等(EQual)

Z==1

NE

不等(NotEqual)

Z==0

CS/HS

进位(CarrySet)

无符号数大于等于

C==1

CC/LO

未进位(CarryClear)

无符号数小于

C==0

MI

负数(MInus)

N==1

PL

非负数

N==0

VS

溢出

V==1

VC

未溢出

V==0

HI

无符号数大于

C==1 && Z==0

LS

无符号数小于等于

C==0 || Z==1

GE

带符号数大于等于

N==V

LT

带符号数小于

N!=V

GT

带符号数大于

Z==0 && N==V

LE

带符号数小于等于

Z==1 || N!=V

AL

总是

-

上面的表格将各种情况都罗列的很清楚,但为什么是这样还是值得详细地说说的。

EQ、NE、MI、PL、VS、VC和AL 很好理解,不用多说。

值得细说的是CS/HS、CC/LO、HI、LS、GE、LT、GT、LE。

首先,我们知道在计算机中,整数分为有符号型和无符号型。这两种类型的判别是不同的。先说无符号数。假设有两个无符号整数A和B。他们之间的关系可以为:

A==B、A!=B、A>B、A>=B、A<B、A<=B

判断的方法就是两数字相减A-B=D,然后看标志位。

A==B、A!=B 看 Z 位就可以了,这里不详述。

对于A>B,首先Z==0(表明两数不相等),然后得到的结果必须满足D<=A,也就是进位标志C==1(表示减法时没有产生借位),合起来就是Z==0&& C==1,这时用后缀HI。这里啰嗦一句,进位标志置1的含义是加法时产生了进位或减法时没有产生借位。

对于A>=B,只用进位标志C==1(没有产生借位)就可以了,用后缀HS或CS。

对于A<B,只要进位标志C==0(产生借位了肯定就是A<B),用后缀LO。

对于A<=B,要么就是Z==1(两数相等),要么C==0(A<B),合起来是C==0||Z==1,用后缀LO。

假设A和B是有符号整数。他们之间的关系同样可以为:

A==B、A!=B、A>B、A>=B、A<B、A<=B

A==B、A!=B 看 Z 位就可以了。

其他的比较稍微困难一些,我们要用到溢出位V。

对于A>B,有三种可能的情况

A、B都是正数,结果D是正数。Z==0 && V==0 && N==0

A、B都是负数,结果D是正数。Z==0 && V==0 && N==0

A是正数、B是负数,结果D可能是正数(Z==0 && V==0 && N==0)也可能是负数(V==1 && N==1)

对于A<B,有三种可能的情况

A、B都是正数,结果D是负数。V==0 && N==1,不用考虑Z,因为N==1决定了Z==0

A、B都是负数,结果D是负数。V==0 && N==1,不用考虑Z,因为N==1决定了Z==0

A是负数、B是正数,结果D可能是正数(V==1 && N==0)也可能是负数(V==0&& N==1),与上面的情况类似,V如果等于1了,Z必然等于0,所以还是不用考虑Z。

综合上面六种情况,我们可以得到:

A>B 等价于 Z==0 && V==N

A<B 等价于 V!=N

有了上面的分析,下面两种情况就很容易得到答案了。

对于A>=B,V==N 就足够了

对于A<=B,Z==1 || V!= N

IF-THEN 指令块

IF-THEN(IT)指令块在其他的单片机中没有见过,这里值得讲一讲。

IF-THEN(IT)指令围起一个块,里面最多有4条指令,它里面的指令可以条件执行。

IT的使用形式如下:

IT <cond> ;围起1条指令的IF-THEN块

IT<x> <cond> ;围起2条指令的IF-THEN块

IT<x><y> <cond> ;围起3条指令的IF-THEN块

IT<x><y><z> <cond>;围起4条指令的IF-THEN块

其中<x>, <y>,<z>的取值可以是“T”或者“E”。下面是个例子:

要实现如下的功能:

if (R0==R1)

{

R3 = R4 + R5;

R3 = R3 / 2;

}

else

{

R3 = R6 + R7;

R3 = R3 / 2;

}

可以写作:

CMP R0, R1 ; 比较R0和R1

ITTEE

ADDEQ R3, R4, R5 ; 相等时加法EQ ; 如果R0 == R1,Then-Then-Else-Else

ASREQ R3, R3, #1 ; 相等时算术右移

ADDNE R3, R6, R7 ; 不等时加法

ASRNE R3, R3, #1 ; 不等时算术右移

IT指令块的初衷应该是避免了在执行转移指令时,对流水线的清洗和重新指令预取的开销,但是最多只能有四条指令,使它的使用范围也很受限。可能也就是C语言中用到“:?”运算符的地方比较容易汇编为IT指令块了。还有个边很短小的if判断,能够被这么优化。

Barrier 指令

DMB, DSB, ISB

这三个指令的区别如下表所示。

指令名

功能描述

DMB

数据存储器隔离。DMB指令保证: 仅当所有在它前面的存储器访问操作都执行完毕后,才提交(commit)在它后面的存储器访问操作。

DSB

数据同步隔离。比DMB严格: 仅当所有在它前面的存储器访问操作都执行完毕后,才执行在它后面的指令(亦即任何指令都要等待存储器访问操作——译者注)

ISB

指令同步隔离。最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令。

其他一些有用的指令

Cortex-M3中支持的指令很多。这里不可能全都介绍到。下面就再提几个我认为比较有特色的指令来介绍。

REV, REVH,REV16以及REVSH

REV反转32位整数中的字节序,REVH则以半字为单位反转,且只反转低半字。

REVSH在REVH的基础上,还把转换后的半字做带符号扩展。

这几条指令主要用于大端系统与小端系统相互通讯时的字节序转换。

RBIT指令

RBIT是按位反转的,相当于把32位整数的二进制表示法水平旋转180度。其格式为:

RBIT.W Rd, Rn

看到按位反转,我就想到了FFT计算的蝶形运算。那里是最需要这种指令的。

TBB,TBH 指令

可以用于C语言中的switch case 结构的汇编。具体的用法这里不详细介绍了,需要用的还是看书吧。

对内存的互斥访问

Cortex-M3 中提供了三对用于互斥访问的内存的指令,分别是:LDREX/STREX, LDREXH/STREXH, LDREXB/STREXB,这三对指令分别对应于字、半字、字节 的取出与写入。

LDREX 的基本指令格式为:

LDREX Rxf, [Rn, #offset]

这条指令与LDR Rxf,[Rn,#offset] 的作用是相同的,唯一的区别是这条指令还会通知内核对它所访问的内存空间特殊关照。如何特殊关照,在STREX 指令执行时显现出来。

STREX的基本指令格式为:

STREX Rd, Rxf, [Rn, #offset],作用是将Rxf 的内容写入到 Rn+#offset 地址处的内存,并且将Rd的值改写为0,当然这些操作的前提是这条指令是LDR Rxf,[Rn, #offset]指令执行之后的第一条对Rn+#offset地址处执行写入操作的指令。如果在STREX指令执行之前就有其他的指令对Rn+#offset 地址处的内存进行了写入操作,那么STREX指令将不会改动Rn+#offset 地址处内存,并将Rd的值改写为1以此来表明写入操作不成功。这样,通过在程序中判断Rd的值就可以确定STREX指令是否成功了,如果不成功可以重新再试以此。通过这种机制,就可以实现对资源的保护了。

第五章 存储器系统

地址空间分配

对比更早版本的ARM内核,Cortex-M3的地址空间分配相对来说是固定的。尤其是内部的私有外设,地址分配是固定不变的。这为软件移植提供很大的方便。

 

图 1 Cortex-M3 地址空间分配

片内SRAM地址空间和片内外设地址空间中各有1MB是所谓的位带区。这个区数据可以按位访问。外部SRAM和外部外设地址空间中没有位带区。

 

RAM地址空间与外设地址空间最大的区别是RAM地址空间中的存储的数据是可以作为程序代码运行的,而外设地址空间中的数据却不能运行。当然,程序最好还是放到片内的代码区,因为对这个区域的访问有专用的总线,因此读取程序代码与读取RAM区的数据可以同时进行,效率最高。

Bit-Band 操作

在0x20000000和0x40000000 地址处开始的1MB空间被称为bit-band region。这个区域内的数据的每一位都被映射了到了一个32位宽的word的最低一位,被映射到的地址空间称为bit-band alias address range。比如说,0x20000000对应字节第0位映射到了0x22000000的第0位。0x20000000的第1位映射到了0x22000004的第0位,其他的以此类推。这样,读取0x22000004 就相当于读取0x20000000的第1位。写0x22000004的第0位就相当于写0x20000000的第1位。对bit-band aliasaddress range 中数据的读写都是原子操作。

特别要注意的是:bit-band alias address range 中的数据支持字节和半字访问,但是数据的访问不能跨越4字节边界,否则结果不确定。

Endian Mode

Cortex-M3内核本身对大端和小端都支持,但是对于具体的一款基于Cortex-M3内核的单片机来说就不一定两种模式都支持了。常见的Cortex-M3内核的单片机基本都是小端模式的。

Cortex-M3内核中对大端模式的定义和ARM7中还有些不同,具体怎么个不同我还没搞明白,等明白了再补上。

另外,具体使用哪种Endian Mode 是在reset时确定的,Cortex-M3不支持运行中动态切换Endian Mode。

第六章 Cortex-M3的全景概貌

这一章的内容大体有个了解就行了,后面章节中会对这里讲到的各个部分深入讲解。

3级流水线,流水线的3个级分别是:取指,解码和执行

Cortex M3 内部构成可以用下面的这个框图来说明。


图 1 Cortex-M3内部框图

 

各个部件的简单介绍如下。

NVIC:嵌套向量中断控制器

SYSTICK Timer:一个简易的周期定时器,用于提供时基,亦被操作系统所使用

MPU:存储器保护单元(可选)

CM3BusMatrix:内部的AHB互连

AHB to APB:把AHB转换为APB的总线桥

SW-DP/SWJ-DP:串行线调试端口/串行线JTAG 调试端口。通过串行线调试协议或者是传统的JTAG协议(专用于SWJ-DP),都可以用于实现与调试接口的连接

AHB-AP:AHB访问端口,它把串行线/SWJ接口的命令转换成AHB数据传送

ETM:嵌入式跟踪宏单元(可选组件),调试用。用于处理指令跟踪

DWT:数据观察点及跟踪单元,调试用。这是一个处理数据观察点功能的模块

ITM:仪器化跟踪宏单元

TPIU:跟踪单元的接口单元。所有跟踪单元发出的调试信息都要先送给它,它再转发给外部跟踪捕获硬件的。

FPB:Flash地址重载及断点单元。

ROM表:一个小的查找表,其中存储了配置信息。

 

这里给出个Cortex-M3内核与其他片内外设的连接示例。


图 2 Cortex-M3总线连接样板范例

 

Cortex-M3 内核有三种复位信号,分别如下:

复位信号

描述

上电复位(nPORESET)

在器件上电时需要把复位置为有效(assert),把处理器核心和调试系统一起复位

系统复位(nSYSRESET)

只影响处理器核心、NVIC(与调试相关的除外)以及MPU,不复位调试系统

测试复位(nTRST)

只复位调试系统

 

这些复位信号在芯片内部的流向与作用见下图。


图 3 典型的Cortex-M3芯片内部复位信号和其作用范围示意图


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多