STM32初探
一、计算机组成
1.嵌入式系统
1.1 什么是嵌入式系统
以应用为中心,以计算机技术为基础,软硬件可裁剪,适应应用系统对功能、可靠性、成本、体积、功耗等严格要求的专用计算机系统。
(通用计算机系统: PC, 服务器)
1.2 嵌入式系统组成
硬件:芯片(CPU+总线+外设控制器)、外围电路……
软件: bootloader 引导程序
作用:引导其他系统软件运行
【在上电的时候,大多数硬件是不能正常工作的,并且CPU等少数硬件,只能在比较低的频率下工作】
bootloader的作用:
(1) 初始化必要的硬件
(2) 把系统软件(如:OS)引导到内存中运行
【OS: 操作系统 (uCOS-III)
应用程序(包括文件系统)……】
2. 计算机系统
2.1 冯.诺伊曼结构(普林斯顿结构):
采用二进制、计算机按照程序顺序执行程序(指令) -> 存储
程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置
冯.诺伊曼把计算机系统分为如下几个部分:
- CPU(中央处理单元) = 算术逻辑单元(运算器) + 控制电路
- 存储器
- 输入输出设备
2.2 哈佛架构(Harvard Architecture)
是一种将程序存储和数据存储分开的存储器结构。
数据存储器与程序存储器采用不同的总线
C51,ARM9之后嵌入式系统芯片
3. 各组件是如何通信的?
总线bus:计算机系统内部各组件之间是通过“总线”的来通信的。
3.1 总线两个特点:
(1) 多个部件可以同时从总线上接收相同的信息 -> 广播式
(2) 任意时刻只能有一个设备向总线发送信息 -> 系统瓶颈
(总线协议规定发送规则)
3.2 总线分类
按功能分
数据总线(DB): 双向,宽度差别8bits/16bits/32bits
地址总线(AB): 单向,CPU发地址,宽度与寻址空间有关 24bit==>16MB
控制总线(CB): 命令和状态
按位置分
片内总线
系统总线
通信总线(I/O总线)
4. 存储器的逻辑结构和操作(需要理解)
如:存储器(内存)
64*64字节存储矩阵:字节编码,需要12 bits
5. CPU工作原理
CPU = ALU + Control Unit
Register(寄存器):CPU内部的存储区域,暂时存放参与运算的数据和运算结果。
寄存器:锁存器/触发器实现,只包含存储电路的时序逻辑电路。
6.指令流水线技术(略)
7. 相关基本概念
(1) 机器字长
CPU一次能处理数据的位数,通常与CPU寄存器位数(总线位数)有关。字长越大,数的表示范围就大,精度也越高,处理速度也就越快。
unsigned int、long、float…… 与机器字长 有没有关系?
前者是软件上编译器相关的一个概念
机器字长是一个 CPU内部结构相关概念
(2) bit:一个存储元件,寄存一位二进制代码
两个状态: 高电平 -> 逻辑 1 低电平 -> 逻辑 0
“D触发器,锁存器”
(3) byte :存储元件,包含8bits
一般存储器都是以字节为单位寻址的,每个存储器的每个byte一个地址,字地址。
(4) 基本的数电知识
(5) 芯片= CPU + 总线 + 各硬件控制器
(6) STM32F407 (芯片型号):采用内核是 ARM Cortex M4
ARM :公司名,只设计CPU,不生产芯片
ARM也是一个CPU型号的名字.
ARM7
ARM9 -> 三星的S3C2410, S3C2440
ARM10
ARM11 -〉三星的S3C6410,S3C6440
…
Cortex
Cortex A
Application(应用级产品),对性能要求比较高的产品
A7 A8 A9 A53…
Cortex R
Realtime(实时性),对实时要求比较高的产品,
工业级,军工类…
R3 R4 R6 R7
Cortex M
MCU(微处理器),“单片机”
M0 M1
M3 -> STM32F103 意(SGS)法(Thomson)半导体
M4 -> STM32F407
NXP MK60 恩智浦半导体
…
二、ARM Cortex M4体系结构
1.Cortex M4总线接口:哈佛架构,三套总线
(1) Icode总线,用于访问代码空间的指令instruction, 32bits
访问的空间为: 0x0000 0000 ~ 0x1FFF FFFF (512M).
每次取4字节
指令,只读
(2) Dcode总线,用于访问代码空间的数据data, 32bits
访问的空间为: 0x0000 0000 ~ 0x1FFF FFFF(512M)
非对齐的访问会被总线分割成几个对齐的访问。
“4字节对齐”:地址必须为4倍数。
从最底层来讲: 4字节对齐,CPU写出的地址必须为4的倍数
总线的最后两bit固定为0 可以“另作他用”. (注意R14)
0: 00 00 4: 01 00 8: 10 00
12: 11 00 16:100 00 ……
如果底层硬件(体系结构)规定是4字节对齐,相应的编译器也会
考虑到对齐的问题.否则效率会变低!
(3) System 总线,用于访问其他系统空间。如: 各硬件控制器,GPIO……。
访问空间为 0x2000 0000 ~ 0xDFFF FFFF 和 0XE010 0000 ~ 0xFFFF FFFF
非对齐的访问会被总线分割成几个对齐的访问
I/O总线,用于访问芯片上的各外设寄存器的地址空间的,各种查手册吧……。
- Cortex M4工作状态(处理器状态)
ARM公司设计的CPU,可以支持两种指令集:
ARM指令集:32bits,功能比较强大,通用
Thumb指令集:
thumb 16bits, 功能也强大(Cortem M4只支持Thumb指令)
thumb-2 32bits,功能强大,增加了不少专用的DSP指令
如何实现混合写?取32bits,还是16bits译码呢?
处理器状态:CPU正在执行何种指令集。
ARM状态:CPU正在执行ARM指令集
Thumb状态:CPU正在执行thumb指令 <---- Cortem M4只支持Thumb指令
在一个状态寄存器中,专门有一个bit为用来表示处理器的状态
T: 1 -> Thumb
0 -> ARM
3. Cortex M4寄存器
通用寄存器: 没有特殊用途
R0~R7: 所有thumb thumb-2都可以访问
R8~R12: 只有少量的thumb指令可以访问,thumb-2都可以访问
why?为什么thumb有些可以访问,有些不可以,而thumb-2都可以呢?
受bits位数的限制。
thumb 16bits thumb-2 32bits
如:
MOV R0, #3 ; 3 -> R0
MOV R1, #4 ; 4 -> R1
ADD R2, R1, R0 ; => R0 + R1 -> R2
MOV R3, #3
MOV R4, #4
ADD R7, R3, R4;
专用寄存器: 有专门的用途!
R13 R14 R15 xPSR
R13(SP): Stack Pointer 保存堆栈的栈顶地址。
“堆栈(Stack)是什么?” 是用“栈的思想”来管理的一段内存。
“栈的思想”:先进后出
为什么需要“堆栈”?为了支持过程调用(函数)。
“现场保护”
函数的具体功能的代码
“现场恢复”
过程调用?跳转
一个过程还没有结束,就去调用另外一个过程。
所有指令的操作数和结果都需要在寄存器中,CPU才能访问
一个过程的一些中间结果,保存在寄存器中的,
如果一个过程,去调用另外一段代码(过程调用),另外的那段代码
势必也要修改寄存器,如果另外那段代码不事先保存寄存器里面的
内容,在代码结束,返回到前面的那个过程时,结果就会不对。
过程调用(函数):
“现场保护”:把寄存器里面的内容保存到内存中去
“现场恢复”:把原先保存的内容还原到相应的寄存器中去。
这个过程,正好是 “先进后出”
思考:
(1) C
有时候,在C语言完成一个功能,我们可以定义一个宏,
也可以定义成一个函数,都可以。请问宏与函数有什么区别?
#define MIN(a,b) ((a) < (b) ? (a) : (b))
int min(int a, int b)
{
return ((a) < (b) ? (a) : (b));
}
main()
{
int m;
m = MIN(3,4); // => m = ((3) < (4) ? (3) : (4))
m = min(3,4);
}
有一点:
min函数有额外的开销:
现场保护
…
现场恢复
宏是没有
(2) C/C++
inline,为什么需要把一个函数声明成inline?
有一些功能或指令比较少的函数了,声明与inline.
inline: 建议编译器,如果可以,把它修饰的函数展开。
“宏”
inline int min(int a, int b)
{
return ((a) < (b) ? (a) : (b));
}
m = min(3, 4);
=>
m = (a = 3
b = 4;
((a) < (b) ? (a) : (b)) )
-----(2.24计算机专业师范类专业的断点)
所有支持过程调用的语言,都需要用到栈。
Cortex M4有两个堆栈,双堆栈
MSP 主堆栈指针
PSP 进程堆栈指针
为什么需要双堆栈呢?
为了支持操作系统。把操作系统用的堆栈和用户进程用的堆栈分开。
R14(LR): Linked Register 链接寄存器
在执行过程调用的指令的时候,我们需要保存该指令的下一条指针的地址,
因为这个地址,就是我过程结束后,需要返回的地址。
有一个专门的寄存器,用来保存过程调用调用的返回地址。->LR(R14)
例子:
MOV R0, #3
MOV R1, #4
BL sum ;
// BL:把下一条指令的地址(如下的: (A))存放在LR中
// 跳转是通过把:要跳到的那个地址,直接赋值给PC
// sum -> PC
(A) ADD R0, R1,
sum:
ADD R0,R0,R1
MOV PC, LR ; -> return 函数返回,过程返回。
R15(PC): Program Counter 程序计数器。 保存下一条要执行的指令的地址。
PC会在取指后,会自动增加指令所占的bits位数。
在ARM Cortex M4, PC + 4
在有“指令流水线”情况下,PC的值会有所不同
在程序实现的跳转,就是往PC中赋一个跳转的地址
“跳转”? 就是不按顺序执行,就是跳转。
xPSR: Program Status Register程序状态寄存器。
程序状态寄存器:保存程序运行过程中的一些状态,这些要保存的状态
分为三类:
应用状态寄存器 APSR: 计算结果的标志
N Z C V Q
中断状态寄存器 IPSR: 中断的编号 Exception Number 8bits
执行状态寄存器 EPSR: 执行状态,如: Thumb/ARM
=> 组合一个32bits的xPSR,如图:
N Z C V Q
我们每一条指令的执行都可以影响这些状态标志位。
N: 负数标志。 Negative is set o bit 31 of the result
xPSR.N < Ret[31] 最高位是符号位 1 -> 负数, 0 -> 非负数
如果 xSPR.N == 1,表示上一条指令的操作结果为负数。
N什么什么情况下会置位呢?
如果一条指令,影响状态标志位
指令可以不影响状态标志位,也可以设置为影响标志位
如:
MOV R0, #3 ; ->不影响状态标志位,
不管这条指令的执行结果如何,xPSR不变。
MOVS R0, #3; ->影响状态标志位
xPSR.N <- 结果寄存器[31]
如:
MOVS R0, #3
R0[31] -> xSPR.N == 0
Z : Zero 。零标志。结果所有bit位都为0,则xPSR.Z == 1
否则xPSR.Z == 0.前提也是指令结果影响状态标志。
如:
MOVS R0, #0;
-> xPSR.Z == 1
MOVS R1, #4 ;
-> xPSR.Z == 0
MOV R2, #0 ;没加S,表示指令的执行,不影响状态寄存器
-> xPSR.Z值不变。
C: Carry 借位或进位标志。
进位: 在做加法运算时,产生了进位。则C == 1,否则 C == 0
借位: 在做减法运算时,没产生借位。则C == 1,否则 C == 0
ADC, ADD, CMN 加法。如果产生了进位,则C == 1,否则 C == 0
SBC, SUB, CMP 减法。如果生生了借位,则C == 0,否则 C == 1
如:
MOV R0, #3
MOV R1, #4
SUBS R2, R0, R1; R0 - R1 -> R2
R0 - R1 产生借位。 C == 0
xPSR.C == 0
MOV R0, #5
MOV R1, #4
SUBS R2, R0, R1
R0 - R1 -> R2
R0 - R1没产生借位,C == 1
xPSR.C == 1
A - B
xPSR.C == 1
说明: A >= B
A - B
xPSR.C == 0
说明: A < B
r0 r1
CMP R0, R1
xPSR.C == 0 => R0 < R1
xPSR.C == 1 => R0 >= R1
A + B
xPSR.C == 1
说明产生了进位,是不是就说明 溢出啦?????
不一定。因为进位与溢出没有半毛钱关系。
V: oVerflow 溢出标志。
反映有符号数做加减运算所得结果是否溢出,如果运算结果超过当前
运算位数所能表示的范围,则溢出 xPSR.V = 1, 否则为0.
在有符号的运算中,进位(借位,C)与溢出是两个完全不同的概念。
例子: 假设寄存器是 8bits
CASE 1: 有进位,结果溢出
1011 0101
- 1000 1111
1 0100 0100
溢出:
负数 + 负数 = 正数(结果溢出)
C6 = 0
C7 = 1
V = C7 异或 C6 = 1 => 溢出
CASE 2: 有进位,结果未溢出
0100 0010 (66)
- 1100 1101 (-51)
1 0000 1111 (15)
CASE 3: 无进位,结果溢出
0100 0010
- 0110 0011
1010 0101
正数+ 正数 = 负数(结果溢出)
CASE 4: 无进位,结果未溢出
0100 1010
- 0010 1101
0111 0111
V = Cn 异或 Cn-1
Cn表示最高位的进位(或借位)标志
Cn-1表示次高位的进位(或借位)标志
Q: 饱和标志
饱和计算: 通过将数据强制置为最大(或最小)允许值,减小了
数据畸变,当然畸变仍然存在,不过若数据没有超过最大
允许范围太多,就不会有太大的问题。
8bits无符号的加法:
普通计算:
11111111 + 1 = 0
11111111
- 00000001
100000000
饱和计算
11111111 + 1 = 11111111
例子: 0111111111…11 => 0X7FFF FFFF
1000000000…00 => - (2^31)
ADD 普通加法
QADD 将两上有符号的32bits相加后,进行饱和操作
MOV R0, #0x7FFFFFFF
MOV R1, #1
ADD R2, R0, R1
QADD R3, R0,R1
T : xPSR[24]
表示处理器当前执行的状态。(工作状态/处理器状态)
ARM状态 xPSR.T == 0
Thumb状态 xPST.T == 1
IT
26:25 -> 该条件所管的指令有多少条 0 1 2 3
15:10 -> 执行的条件 GT或EQ …
IF-THEN 位,它们是if-then指令的执行状态位
包含了if then模块的指令数目和它们的执行条件
if (R0 > R1) // CMP R0,R1 ;// if R0 > R1 xPSR.C == 1 => GT >
{
r2 = 2 MOVGT R2, #2
r3 = 3 MOVGT R3, #3
r4 = 4 MOVGT R4, #4
}
=> IT
CMP R0, R1
ITGT TT -> GT这个条件管三条语句
MOV R2,#2
MOV R3,#3
MOV R4,#4
ICI : Interruptible-Continuable Instrument 可中断-可继续指令位
15:12
如果执行LDM/STM操作(对内存进行批量拷贝)时,产生一个中断,
中断的优先级很高,必须要打断这条指令的执行,那么你的数据
有可能只拷贝了一部分,对不对?这种情况怎么办呢?
这种情况,就需要记录被打断的寄存器的编号,在中断响应之后,
处理器返回由该位指向的寄存器,并恢复操作,继续执行拷贝。
中断屏蔽寄存器
中断
异常
PRIMASK[0] 片上外设的总中断开关
1 屏蔽所有的片上外设中断
0 响应所有外设的中断
FAULTMASK[0] 系统错误异常的中断总开关
1 屏蔽所有异常
0 响应所有异常
BASEPRI 为使中断屏蔽更加灵活,该寄存器根据中断优先级来屏蔽中断或异常。
当被设置为一个非0的数值时,它就会屏蔽所有相同或更低优先级的
异常(包括中断),则更高优先级的中断或异常还会被响应。
每一个异常或中断,都会有一个优先级。
CONTROL寄存器
用来控制选择哪个堆栈(主堆栈/进程堆栈)
选择线程模式的访问等级(特权等级/非特权等级)
特权等级可以访问所有东西;
非特权等级只能有限访问
CONTROL[0] :线程模式的访问等级
1 非特权等级
0 特权等级
CONTROL[1] :堆栈的选择
1 进程堆栈 PSP
0 主堆栈 MSP
- Cortex M4工作模式
“模式”: 不同环境,不同的角色
ARM cortex M4有两种工作模式:
Thread Mode: 线程模式
Handler Mode: 处理模式(异常中断模式)
异常/中断 是什么? 打断CPU指令执行顺序的事件,称为中断。
为什么要支持两种模式呢? 为什么不只用一种模式呢? Thread Mode
如果只用一种模式,thread mode,为了响应一些外部事件(比如说,用户是否
按下某个按键?):
轮询:轮流询问。 通过轮询,CPU也可能 响应外部事件,但是
轮询天生就有缺陷:
(1) 浪费CPU
(2) 占用总线, Bus is always busy.
(3) 轮询有一个时间差,轮询的时间间隔。不及时!!!
一些比较重要的事件,如果去轮询,不太合适,
所以,有人就提出,能不能在CPU内部设计一个 “中断模式”:
不去轮询,来了事件,你中断我,。处理模式。
为了提高效率和响应速度。
两种模式之间是怎么切换的呢? 很重要。如图:
Handler Mode :
中断模式,当一些比较重要的事件,产生时,CPU中止正在做的
事情,切换到Handler Mode下去执行,此时 “特权等级”
中断处理完成后,再返回到断点处,继续Thread Mode运行。
Thread Mode:
线程模式。
特权等级 : 可以跑一些如OS的代码
非特权等级: 可以跑一些如 "用户态"的代码
特权等级 -> 非特权等级
但是:
非特权等级 不可以 切换到 特权事件,除非产生“中断”