再上一篇:14.0 译者添加的引子
上一篇:14.2 MPU 的寄存器组
主页
下一篇:14.4 MPU 的典型设置
再下一篇:第15章
文章列表

14.3 启用MPU

《Cortex-M3 权威指南》,嵌入式处理器开发教程。

MPU寄存器看起来比较复杂,那是自然了,毕竟已经上升到存储器管理的高度。但如果我们胸 有成竹——已经想好了对存储器如何划分,这就只是一些繁琐和考验细心的体力活。典型情况下, 在启用MPU的系统中,都会有下列的regions。
 特权级的程序代码(如OS内核和异常服务例程)
 用户级的程序代码
 特权级程序的数据存储器,位于代码区中(data_stack)
 用户级程序的数据存储器,位于代码区中(data_stack)
 通用的数据存储器,位于其它存储器区域中(如,SRAM)
 系统设备区,只允许特权级访问,如NVIC和MPU的寄存器所有的地址区间
 常规外设区,如UART,ADC等。 对于CM3来说,绝大多数region中,都有TEX=0,C=1,B=1。系统设备(如NVIC)必须“严格顺
序”(strongly ordered)访问;另一方面,外设regions则可以共享(TEX=0, C=0, B=1)。如果想要在 某个region中,确保所有的总线fault都是精确的,就必须把该region严格顺序化(TEX=0, C=0, B=0)。 这样一来写缓冲被除能,但也因此产生性能损失的代价。
图14.2给出了MPU初始化序列的流程模式图。在使能MPU前,或者把向量表重定位到了RAM, 一定不要忘记为MemManage fault建立向量,并且在NVIC的系统handler控制及状态寄存器SHCSR中使 能MemManage fault。只有这样做了,才能在产生MPU违例时,让MemManage fault服务例程得以执 行。

图14.2 MPU初始化序列
下面举一个简单的例子,它只有4个region,则配置代码如下所演示:

LDR MOV

STR

R0, R1,

R1,

=0xE000ED98

#0 [R0]

; Region号寄存器

; 选择region 0

LDR STR

LDR

R1, R1,

R1,

=0x00000000 [R0, #4]

=0x0307002F

; 基址 = 0x00000000

; MPU Region 基址寄存器

; R/W, TEX=0,S=1,C=1,B=1, 16MB, Enable=1

STR MOV STR

R1, R1, R1,

[R0, #8]

#1 [R0]

; MPU Region 属性及容量寄存器

; 选择region 1

LDR

R1,

=0x08000000

;

基址 = 0x08000000

STR

R1,

[R0, #4]

;

MPU Region 基址寄存器

LDR

STR

R1,

R1,

=0x0307002B

[R0, #8]

;

;

R/W, TEX=0,S=1,C=1,B=1, 4MB, Enable=1

MPU Region 属性及容量寄存器

MOV STR

R1, R1,

#2 [R0]

; 选择 region 2

LDR STR

LDR

R1, R1,

R1,

=0x40000000 [R0, #4]

=0x03050039

; 基址 = 0x40000000

; MPU Region 基址寄存器

; R/W, TEX=0,S=1,C=0,B=1, 512MB, Enable=1

STR

R1,

[R0, #8]

; MPU Region 属性及容量寄存器

MOV STR

R1, R1,

#3 [R0]

; 选择 region 3

LDR STR

LDR

R1, R1,

R1,

=0xE0000000 [R0, #4]

=0x03040027

; 基址 = 0xE0000000

; MPU Region 基址寄存器

; R/W, TEX=0,S=1,C=0,B=0, 1MB, Enable=1

STR

R1,

[R0, #8]

; MPU Region 属性及容量寄存器

MOV

R1,

#1

; 准备使能MPU

STR

R1,

[R0,#-4]

; 使能MPU(0xE000ED98-4=0xE000ED94)

这段代码执行后,生成如下的4个regions:

特权级代码

0x0000_0000-0x00FF_FFFF(16MB)

全访问

缓存可

特权极数据

0x0800_0000-0x0803_FFFF(4MB)

全访问

缓存可

外设

0x4000_0000-0x5FFF_FFFF(512MB)

全访问

共享设备

系统控制

0xE000_0000-0xE00F_FFFF(1MB)

特权级访问

严格顺序,XN

通过使用基址寄存器的VALID和REGION位段,可以把region选择和基址设置的两个动作合并成一
个,从而缩短代码,如下所示:

LDR LDR STR

LDR

R0, R1, R1,

R1,

=0xE000ED9C

=0x00000010 [R0, #0]

=0x0307002F

; MPU region基址寄存器

; 基址=0x00000000, region=0, valid=1

; 设置region 0的基址

; R/W, TEX=0,S=1,C=1,B=1, 16MB, Enable=1

STR

R1,

[R0, #4]

; MPU Region 属性及容量寄存器

LDR STR

LDR

R1, R1,

R1,

=0x08000011 [R0, #0]

=0x0307002B

; 基址=0x08000000, region=1, valid=1

; MPU Region 基址寄存器

; R/W, TEX=0,S=1,C=1,B=1, 4MB, Enable=1

STR

R1,

[R0, #4]

; MPU Region 属性及容量寄存器

LDR STR

LDR

R1, R1,

R1,

=0x40000012 [R0, #0]

=0x03050039

; 基址=0x40000000, region=2, valid=1

; MPU Region基址寄存器

; R/W, TEX=0,S=1,C=0,B=1, 512MB, Enable=1

STR

R1,

[R0, #4]

; MPU Region属性及容量寄存器

LDR

R1,

=0xE0000013

;

基址=0xE0000000, region=3, valid=1

STR

R1,

[R0, #0]

;

MPU Region 基址寄存器

LDR

STR

R1,

R1,

=0x03040027

[R0, #4]

;

;

R/W, TEX=0,S=1,C=0,B=0, 1MB, Enable=1

MPU Region 属性及容量寄存器

MOV

R1,

#1

; 使能MPU

STR

R1,

[R0,#-8]

; MPU控制寄存器(0xE000ED9C-8=0xE000ED94)

看,代码变短了吧!不过,还有比这更厉害的,让代码更短更快。这要通过使用MPU别名寄存
器的地址来完成。在MPU属性及容量寄存器(MPUASR)的后面,有3组MPU基址寄存器(MPUBAR)和 MPU属性及容量寄存器的别名,连同真实的MPUBAR与MPUASR,它们共有4组,分布在一个连续的8 字空间中,于是就可以使用LDM/STM指令来“串烧”,如下所示:

LDR R0, =0xE000ED9C ; MPU reigon基址寄存器 LDR R1, =MPUconfigTab ; 预定义的MPU初始化数值表 LDMIA R1!, {R2-R9} ; 一气从表中读完8个字 STMIA R0!, {R2-R9} ; 一气初始化4个region

B MPUconfigEnd

ALIGN 4 ; 此汇编指示字可以确保下述的字定义一定是对齐到字

MPUconfigTab ; 边界的,因为在使用LDM/STM时,地址必须按字对齐

DCD DCD

0x00000010

0x0307002F

; 基址=0x00000000, region=0,valid=1

; R/W, TEX=0,S=1,C=1,B=1, 16MB, Enable=1

DCD

DCD

0x08000011

0x0307002B

; 基址=0x08000000, region=0,valid=1

; R/W, TEX=0,S=1,C=1,B=1, 4MB, Enable=1

DCD

DCD

0x40000012

0x03050039

; 基址=0x40000000, region=0,valid=1

; R/W, TEX=0,S=1,C=0,B=1, 512MB, Enable=1

DCD DCD

0xE0000013

0x03040027

; 基址=0xE0000000, region=0,valid=1

; R/W, TEX=0,S=1,C=0,B=0, 1MB, Enable=1

MPUconfigEnd

LDR

R0,

=0xE000ED94

; MPU 控制寄存器

MOV

R1,

#1

; 使能MPU

STR

R1,

[R0]

若用此法,显然必须保证:region配置早已安排好了,否则就只能用上面的更通用的办法。为
了使软件更有模块化,可以把建立region的工作包装到一个子程序中,不妨名为MpuRegionSetup。 它接受相关参数(编号,基址,容量/属性),并执行建立region的工作。主程序通过呼叫它若干次 来逐一设置好每个region。
上面的小凉菜吃了三次,想必读者已经腻了吧。下面就上主菜,使用模块化的思路,代码如下 所示。这段代码的后面部分还精彩地演示了新好指令BFI和UBFX的使用:

MpuSetup ; 入口函数,它内部呼叫若干子程序来完成MPU设置

PUSH {R0-R6,LR}

LDR

R0,

=0xE000ED94

; MPU 控制寄存器

MOV STR

R1, R1,

#0 [R0]

; 配置前先除能MPU

; --- Region #0 ---

LDR R0, =0x00000000 ; Region 0: 基址 = 0x00000000

MOV MOV MOV

R1, R2, R3,

#0x0

#0x17

#0x3

;

;

;

Region Region Region

0: Region号 = 0

0: 容量 = 0x17 (16MB)

0: AP = 0x3 ( 全访问)

MOV

R4,

#0x7

;

Region

0: MemAttrib = 0x7

MOV

R5,

#0x0

;

Region

0: 子region除能=0

MOV

R6,

#0x1

;

Region

0: {XN, Enable} = 0,1

BL MpuRegionSetup

; --- Region #1 ---

LDR

R0,

=0x08000000

;

Region

1: 基址 = 0x08000000

MOV

R1,

#0x1

;

Region

1: Region号 = 1

MOV

R2,

#0x15

;

Region

1: 容量 = 0x15 (4MB)

MOV

R3,

#0x3

;

Region

1: AP = 0x3 (全访问)

MOV

R4,

#0x7

;

Region

1: MemAttrib = 0x7

MOV

R5,

#0x0

;

Region

1: 子region除能= 0

MOV

R6,

#0x1

;

Region

1: {XN, Enable} = 0,1

BL MpuRegionSetup

... ; 以相同的方法建立region #2和region #3

; --- Region #4-#7 除能 ---

MOV

R0, #4

BL

MpuRegionDisable

MOV

R0, #5

BL

MpuRegionDisable

MOV

R0, #6

BL

MpuRegionDisable

MOV

R0, #7

BL LDR

MpuRegionDisable

R0, =0xE000ED94

; MPU 控制寄存器

MOV STR

R1, #1

R1, [R0]

; 使能MPU

POP

{R0-R6,PC}

; 返回

MpuRegionSetup

; MPU region 设置及启用子程

; 入口条件:

; R0 = 基址

; R1 = Region号

; R2 = 容量

; R3 = AP (访问许可)

; R4 = MemAttrib ({TEX[2:0], S, C, B})

; R5 = 子region除能

; R6 = {XN,Enable}

PUSH BIC BFI ORR

{R0-R1, LR} R0, R0, R0, R1, R0, R0,

#0x1F

#0, #4

#0x10

; 清零基址中铁定不会用到的位段

; 把region号插入到R0[3:0]

; 置位VALID位

LDR STR

R1, R0,

=0xE000ED9C [R1]

; 加载MPU Region基址寄存器的地址

; 填写之

AND

UBFX

R0,

R1,

R6,

R6,

#0x01

#1, #1

; 读取使能位

; 读取XN位

BFI BFI BFI BFI BFI

R0, R0, R0, R0, R0,

R1, R2, R3, R4, R5,

#28, #1

#1 , #5

#24, #3

#16, #6

#8, #8

; 把 XN 插入到 R0[28]

; 把region容量(R2[4:0])插入到R0[5:1]中

; 把AP(R3[2:0])插入到R0[26:24]中

; 把memattrib(R4[5:0])插入到R0[21:16]中

; 把子SRD(R5[7:0])插入到R0[15:8]中

LDR STR

R1, R0,

=0xE000EDA0 [R1]

; 加载MPU Region属性及容量寄存器的地址

; 填写之

POP {R0-R1, PC} ; 返回

MpuRegionDisable

; 该子程序用于除能一个region

; 入口条件: R0 = 待除能的region号

PUSH AND ORR

{R1, R0, R0,

LR}

R0, #0xF R0, #0x10

; region号只取低4位

; 设置VALID位

LDR STR

R1, R0,

=0xE000ED9C [R1]

; 加载MPU Region 基址寄存器的地址

; 填写之

MOV LDR

R0, R1,

#0

=0xE000EDA0

; 加载MPU Region 属性及容量寄存器的地址

STR

R0,

[R1]

; 把它归零,这也蕴涵了除能的命令

POP

{R1,

PC}

; 返回

在本例中,我们还添加了一个用于除能和“复位”无用region的子程序。当你不知道某个region
是否被用过时,使用它来使其“归零”是最安全不过的了。 注意代码中位段操作的几行,想想看,如果用普通的移位和数据传送指令,将会繁琐成什么样
子!