我们都知道stm32单片机芯片,但是关于这个芯片是谁家的,你清楚吗,在芯片的丝印上面有arm的字样,但是这个又听说这款单片机的厂家叫做stm-意法半导体(STMicroelectronics),他和arm是什么关系呢?在上学的时候,有很多名词混合起来,让人摸不着头脑。
ARM、STM32、CPU、单片机、芯片、SOC
这些名词之间互相的关系是怎么样子,其实每一个名词具体的含义已经不太好单一化,不同领域,不同的人,对于同一个次囊括的范围都有不同,这里根据我的理解,我的看法、我的叫法来梳理一下一个芯片内部到底有什么。
其实在说芯片有什么之前,我们可以先思考一下芯片可以干什么,芯片能做的事情那么多,怎么可以数的过来呢,满世界都是芯片,到处都是芯片,不过我们还是从最本质的地方去思考,可以抽象出来一些共性的地方,芯片的功能是通过其指令来实现的,如果我们把常用的指令列出来,然后分个类,就知道芯片最基本的功能可以干啥了:
1. 访存类的指令,可以读写内存,寄存器
str 存储
ldr 加载
2. 数学计算类的指令
add 加法
sub 减法
mul 乘法
UDIV 除法
3. 逻辑运算类的指令
lsl 逻辑左移
lsr 逻辑右移
and 与操作
orr 或操作
4. 访问系统寄存器
msr 写系统寄存器
mrs 读系统寄存器
5. 跳转类指令
b 无条件跳转
bl 跳转时候把下一条指令地址保存到lr
br 跳转到寄存器地址
上面列出的几种主要的指令,其实关键的就是2种类型,访存类指令和计算类指令,体现了cpu的2个主要功能。
访存类指令用来访问内存,访问寄存器。也就是我们编程时候读写变量,读写寄存器的功能是通过访存类指令完成
数学计算类指令,我们的一些算法计算的过程,加法减法乘法除法,计算一个公式,是通过数学计算类指令完成
提到了指令,就不得不提cpu,芯片内部就可以划分成为2个部分,CPU和外设 ,这2个部分通过总线相连接(AXI、APB、AHB)。
在stm32芯片内部,以stm32f103系列为例,就包含了一个arm公司的cpu和一系列的外设。外设最常用的如串口,定时器等等。
cpu执行各种各样的指令,通过执行仿存类指令去配置外设的寄存器,达到控制外设工作的目的。完成我们用C语言编写的程序逻辑。
下面我们展示一下从C语言到汇编语言处理器所执行的指令,看一下C语言编写的程序对应的汇编语言
1. 一个仿存的演示程序
void fun_reg(void)
{
uint32_t *addr = 0x1234;
uint32_t val = 0x01;
*addr = val;
}
0000000040000ce8 <fun_reg>:
40000ce8: d10043ff sub sp, sp, #0x10
40000cec: d2824680 mov x0, #0x1234 // #4660
40000cf0: f90007e0 str x0, [sp, #8]
40000cf4: 52800020 mov w0, #0x1 // #1
40000cf8: b90007e0 str w0, [sp, #4]
40000cfc: f94007e0 ldr x0, [sp, #8]
40000d00: b94007e1 ldr w1, [sp, #4]
40000d04: b9000001 str w1, [x0] //*addr = val 仿存
40000d08: d503201f nop
40000d0c: 910043ff add sp, sp, #0x10
40000d10: d65f03c0 ret
2. 一个乘法的演示程序
void fun_sum(void)
{
int a = 1;
int b = 2;
int c;
c = a * b;
}
0000000040000d14 <fun_sum>:
//函数入口操作,可以先不用理会
40000d14: d10043ff sub sp, sp, #0x10
/下面2句指令对应 int a = 0;
//给w0寄存器写1
40000d18: 52800020 mov w0, #0x1 // #1
//变量a定义为局部变量,所以可以通过堆栈指针相对寻址访问到
//这一句就是把w0寄存器的内容写入到内存中变量a所在的位置
40000d1c: b9000fe0 str w0, [sp, #12]
//与上面同理,对应int b = 2;
40000d20: 52800040 mov w0, #0x2 // #2
40000d24: b9000be0 str w0, [sp, #8]
//从内存中取出变量a到w1寄存器
40000d28: b9400fe1 ldr w1, [sp, #12]
//从内存中取出变量b到w0寄存器
40000d2c: b9400be0 ldr w0, [sp, #8]
//使用寄存器计算w0乘以w1 计算结果保存到w0寄存器
40000d30: 1b007c20 mul w0, w1, w0
//把w0寄存器得到的计算结果写入到内存中变量c的位置
40000d34: b90007e0 str w0, [sp, #4]
//返回调用函数
40000d38: d503201f nop
40000d3c: 910043ff add sp, sp, #0x10
40000d40: d65f03c0 ret
我们可以看到在汇编指令中,还有很多 x0 w0这样的东西,这个是cpu的系统通用寄存器,没有特别的功能,可以用来临时存放变量。其中x0和w0是相同的寄存器,他们表示的位宽不同,x0寄存器是64位的寄存器,w0表示其低32位的数据内容。
arm架构的处理器无法直接把内存中的数据当做操作数去计算,如完成c=a*b计算,需要按照下面的步骤执行:
- 从内存中加载操作数a和b到寄存器w1和w0中
- 对a和b所在的寄存器相乘,结果保存到寄存器w0中
- 把保存结果的寄存器w0写入到内存中c的位置
结合我们的任务切换来思考一下,任务切换的过程中为什么要保存寄存器现场,任务切换可能发生在任意2条指令之间。如果在上面的计算过程1和过程2之间发生了任务切换,如果我们在切换执行其他任务的过程中没有做寄存器现场保存的功能,如果其他任务也同样用到了w0和w1寄存器,并且当任务返回后重新执行时候,w0和w1寄存器的值已经被改写,那么c=a*b的结果还正确吗,有可能这个计算结果就会出错。所以我们必须保证在任务切换过程中,同一个任务切换前和切换后,任务的状态保持不变,包括任务的寄存器。所以这也就是我们操作系统中任务切换复杂的过程,通过保护任务执行现场,才能保证任务切换的同时处理器。