单片机(Micro Control Unit):全称微型控制单元,简称单片机(MCU),从字面意思我们可以了解到,它就是一个微型的计算机系统。然后我们下面通过对比来了解下它为什么时一个微型计算机系统。
电脑主板
我们首先来看下我们熟悉的电脑主板,可能有人会问,我是来学单片机的,你给我介绍主板干嘛?我们这里是用主板来做一个类比,我们看了上面一大堆东西,估计很多人用了很长时间的也不知道单片机和代码是怎么回事。
我这里找了一张电脑主板的电路框图,这个是比较老的一个主板,DDR2内存的,从下面框图中我们大概可以看到大的板块包括CPU,北桥,南桥;其中南桥下面主要掌管着这些低速设备USB,PCI,SATA,BIOS等外设。
电脑主板实物
电脑主板原理框图
手机"CPU"
这里CPU之所以加了引号是因为CPU指的是中央处理单元,而我们手机的处理器不仅仅只有中央处理功能。这是2021年5月最新版的联发科天玑900处理器,我们再看下左边的介绍,有CPU,GPU,5G和WIFI模组,还有人工智能APU;对比下主板是不是发现有什么共同点。没错,手机处理器将这些外设全部集成到一个芯片里面去了,其实像现在最新CPU也是朝着这个方向做的,目前已经有很多CPU把显卡集成进去了。
那么我们可以思考一下,我其实并不需要电脑CPU和手机处理器这么强悍的性能和诸多功能,我只要控制个I/O、串口通信等。那些不要的东西给它裁剪掉,加上简单的计算单元,然后把它的可靠性提高,你说可以不?答案肯定是可以的,这也代表着我们思考的问题跟这些计算机科学家思考的问题是一致的 。我们接着往下看。
联发科天玑处理器
单片机内部组成
我们看下图中在校期间和教科书上用的最多的51单片单片机,同样包含CPU(8051内核),SRAM(内存)可怜的1K,闪存(硬盘)也只有区区的几十K,然后外围的模块基本就是下载代码用的ISP,EEPROM,看门狗,复位模块,串口模块,中断模块,基本输入输出I/O模块。
STC89C52RC内部框图
PIC16单片机内部框图
MSP430单片机内部框图
典型的STM32内部框图
因为单片机主要应用在一些对价格比较敏感的领域,所以这类单片机种类也比较多,像51是我们常用的单片机,主要应用的场合有数码家电(洗衣机,电饭锅,电磁炉,微波炉),工业现场数据采集与传输等,但是随着它的价格优势下降和易上手程度,慢慢的市场被32位单片机被取代,我们看下图大家熟知的STM32是不是感觉比51单片机的内部结构复杂很多,51单片机有的外设它都有,并且这些模块更强大的;51单片机没有的模块它也有,调试功能模块,RTC模块,USB模块,CAN总线模块,ADC,SPI,I2C等等 。
我们再来看下MCU与人的一个对比,CPU相当于是人的大脑,用来处理来自各个模块的信息,然后I/O输出就相当于人的手,CPU+RAM+FLASH相当于人的大脑,I/O输入和A/D则相当于人的皮肤和眼睛用来接收外界的温度,图像信息输入;而串口、SPI、I2C等总线则相当于耳朵和嘴巴的配合,一方面接收其它控制器发送过来的信息,另外一方面将要传递的信息发送出去。
单片机是如何工作的?
先举一个例子:我们要住酒店,我们是通过什么来找到你要住哪个位置的,是的,那就是房间号,这个房间号是酒店预先编好的。然后我们通过唯一的房间号,可以找到这个房间;如果这是我们的公司,我们每个大的房间里面都是一些职能小组在里面办公,然后我们的项目管理员接受到领导的指令后,就不断的去每个房间找相应的职能部门来完成任务。
那么我们单片机也一样,我们所有的外设都是挂在单片机的可以访问的地址上,我们要对单片机进行预先定义地址,不然我们就找不到哪个设备对应那个地址,也就没办法操作。其实单片机地址是我们虚构出来的,为了便于实际使用中的方便而已。
CPU是如何执行程序的?
1、我们先来看下两位全加器
A = 01B = 1H
B = 11B = 3H
A + B = 1 + 3 = 4 = 100B
Proteus仿真文件:
MCU-CPU_2bit_Fulladder.pdsprj
16.1K
·
百度网盘
为了方便更多比特位的运算,我们使用内部集成8个全加器芯片74283来运算
DATA1 = 0000 0101B = 5H
DATA2 = 0000 1010B = 10H
SUM = 0000 1111B = 15H
2、那么数字电路是如何进行加减乘除运算的?
注意:我们这里不考虑一些特殊的情况,我们8位数最大能表示的数为255,所以只考虑数值小于255的情况,更深入的探讨大家可以去寻找更专业的资源学习。
加法运算:
假设我们要进行8+13运算:
十进制运算:8+13= 21;
二进制运算:0000 1000 + 0000 1101 = 00010101;
我们通过8位全加器来验证下
Proteus仿真文件:
MCU-CPU_8bit.pdsprj
17.4K
·
百度网盘
减法运算:
加法是进位,减法需要考虑的则是借位,小学时对加减法的经验是这样的,但是计算机不是这么处理的。计算机只有加法,没有减法。那么 int a = b - c 是怎么得出来结果的呢?首先要了解一个概念——补码。
计算机中对于有符号数,用最高位作为符号位,“0” 代表 “+” ,“1” 代表 “负号-” ;其余数位用作数值位,代表数值。比如 Byte 类型的取值范围为 -128 ~ 127。其中,表示数值的只有 7 位,首位表示正负。
补码规定,正数和 0 的补码就是其原码(原码、反码的定义这里就不多赘述),负数的补码是其正数的原码取反再加 1 。
举个例子,求负 -10 的补码:十进制 10 的原码(按 8 位举例)为 0000 1010,其反码为 1111 0101,取反后再加 1 即为其补码1111 0110。因此,负10 的补码为 1111 0110。
不知道写到这里,大家有没有发现什么端倪?我们再回到减法计算来,a = b - c 实际上等同于a = b + ( -c )。
情形1,减数>被减数
12 - 5= 0000 1100 + 1111 1011= (1)0000 0111= 7 括号里为进位,因为只有 8 位,所以高于 8 位的进位要去掉。
(-5 )二进制: 000 0101;
反码:1111 1010;
补码 = 反码+1:1111 1011
最终通过加入反相电路和加1电路得到的最终结果,进位去掉后为0000 0111换算成10近制就是7
Proteus仿真文件:
MCU-CPU_8Bit_NOT.pdsprj
18.7K
·
百度网盘
情形2,减数>被减数
再来计算下7 - 9= 0000 0111 + 1111 0111= 1111 1110= -2
(-9)二进制:0000 1001,
反码:1111 0110,
补码:1111 0111
然后得到的是负数,我们又要把它反回来
补码-1:1111 1110-1 = 1111 1101
反码反过来:0000 0010
0000 0111 + 1111 1101
通过这两个例子是不是就清楚了计算机是如何计算减法的了?
乘法运算:
通过说减法,我们是不是对乘法也有一定的启发了呢?乘法其实就是循环的加法。比如 5 * 3
实际上就是 5 + 5 + 5
。貌似就说完了。实际上不仅仅如此的。现在有一个电子器件叫做乘法器,其可以实现二进制的乘法、除法等运算。我们同样以 5 * 3
做为例子,讲解一下乘法器计算乘法的流程。
5 * 3 = 0000 0101 * 0000 0011
第一步:5 + 5 = 10
00000 0101 + 0000 0101 = 0000 1010
第二步:10 + 5 = 15
0000 0101 + 0000 1010 = 0000 1111
小提示:当然也可以通过移位电路来运输也是可以的,这里就不举例子了
虽然CPU中有乘法器,但是我们发现实际的最终操作流程还是加法和位移操作计算的乘法运算。我们写的代码中的乘法到底是用乘法器运算还是转化成加法运算,我们也并不太确定,有些编译器编译的时候会对代码进行优化,选取最优的一种算法来计算结果。
除法运算:
除法可以通过减法来实现,比如 10 / 3
等价于 10
一直减 3
直到被减数小于 3 ,减了 3 次,那么 10 / 3
的结果就为 3
了,余数为减完剩下的值 1
。
其实上面已经提到了乘法器,除法的原理同样也类似(这里不说浮点数的除法,只说整数的除法),但是稍微复杂一点。
同样我们举个例子来说明一下。
10 / 3 = 000 1010 / 0000 0011
第一步:10 - 3 = 7
000 1010 - 0000 0011 = 0000 0111
第二步:7 - 3 = 4
0000 0111 - 0000 0011 = 0000 0100
第三步:4 - 3 = 1,通过比较大小,不能再继续减下去。
0000 0100 - 0000 0011 = 0000 0001
小提示:这里只是展示其中一种除法运算,大家可以去思考其他方式的除法运算,比如说使用移位方式。
然后我们再来看一个完整的CPU计算电路
感兴趣的小伙伴可以参考下面这个链接:
这里面包含有完整的8位与运算电路,或运算电路,右移运算电路,左移运算电路,还有加法运算电路等,我们实际运算的时候就通过取到的指令通过内部解码来决定当前操作是进行加法操作,还是移位操作,又或是对外部I/O进行锁存操作。
然后这里面我们发现复杂的运算会产生中间数据,比如说乘法,除法,要计算好几次才能得到结果,所以我们就需要寄存器来临时存储这些数据,这样我们就可以计算了.
CPU执行整个程序流程分解
我们以这个代码为例来详细讲解下。
#includesbit LED = P0^0;void delay( unsigned int nTime){ unsigned char i; while(nTime--) for(i=10; i>0; i--);}void main(){ while(1) { LED = 0; delay(10); LED = 1; delay(10); }}
我们再来看下汇编指令,我们的程序从main函数开始执行
C:0x0000 02002D LJMP C:002D5: void delay( unsigned int nTime)6: {7: unsigned char i;8: while(nTime--)C:0x0003 EF MOV A,R7C:0x0004 1F DEC R7C:0x0005 AA06 MOV R2,0x06C:0x0007 7001 JNZ C:000AC:0x0009 1E DEC R6C:0x000A 4A ORL A,R2C:0x000B 600B JZ C:00189: for(i=10; i>0; i--);C:0x000D 7D0A MOV R5,#0x0AC:0x000F ED MOV A,R5C:0x0010 D3 SETB CC:0x0011 9400 SUBB A,#0x00C:0x0013 40EE JC delay(C:0003)C:0x0015 1D DEC R5C:0x0016 80F7 SJMP C:000F10: }11:C:0x0018 22 RET12: void main()13: {14: while(1)15: {16: LED = 0;C:0x0019 C280 CLR LED(0x80.0)17: delay(10);C:0x001B 7F0A MOV R7,#0x0AC:0x001D 7E00 MOV R6,#0x00C:0x001F 120003 LCALL delay(C:0003)18: LED = 1;C:0x0022 D280 SETB LED(0x80.0)19: delay(10);C:0x0024 7F0A MOV R7,#0x0AC:0x0026 7E00 MOV R6,#0x00C:0x0028 120003 LCALL delay(C:0003)20: }C:0x002B 80EC SJMP main(C:0019)C:0x002D 787F MOV R0,#0x7FC:0x002F E4 CLR AC:0x0030 F6 MOV @R0,AC:0x0031 D8FD DJNZ R0,C:0030C:0x0033 758107 MOV SP(0x81),#0x07C:0x0036 020019 LJMP main(C:0019)
小提示:我们的单片机默认都是跳转到main函数处执行
第一步:取指令
C:0x0019 C280
PC计数器指向读取指令的地址,然后存储到指令寄存,通过指令解码器解码后输送给运算单元,执行相应操作. 示例:我们的单片机程序一般都是直接跳转到main函数,这时PC = 0x0019地址里面的指令是C280
第二步:解码
CLR LED(0x80.0)
指令寄存器中的指令通过译码电路进行译码,然后CPU根据译码的结果来执行加减乘除,移位等操作,示例:通过译码器电路我们得知C280代表的意思是C2表示清除位指令
第三步:执行
在执行阶段会根据指令的类型,将算数/逻辑单元(ALU)用于不同的目的。对其他指令,它会作为一个加法器来计算增加或减少栈指针,或者计算有效地址,或者只是简单地加0,将一个输入传递到输出
示例:这里相当于是清除0x80的第0位,也就是将P0.0置低,然后PC更新PC的值,没有对PC值进行操作的话,它就自动累加这里自动累加后是0x001B,然后继续第一步循环操作就完成了我们整个程序的一个执行。
这里只是给出大致的程序执行方式,省略了很多细节操作,大家可以进一步参考更详细的程序执行过程。
晶振在单片机中是起什么作用?
上面的运算,我们都是通过手动的方式来进行的, 我们知道数字电路本身是不工作的,它也不能工作,否则电路就会出乱子,它必须依赖其它电路产生的高低电平才能驱动它工作,我们把这种有规律的高低电平定为脉冲,单片机也是数字电路,它又是怎么工作的?我们必须要有一个能源源不断产生脉冲的电路,它才能持续不断的工作下去,这也就是晶振在单片机电路中为什么被称作为“心脏”的原因,我们把这个“心脏”去掉,相应的整个单片机系统也会罢工。
为什么有些单片机没有晶振?我们知道单片机工作只要有持续不断的脉冲输入就行,我们有些场合对单片机没有精确的时间要求,所以一般使用内部自带的振荡器为单片机提的时钟脉冲也能确保单片机正常工作,所以在有些地方我们就看不到晶振。那是因为内部自带了RC振荡电路。
复位:CPU执行程序需要有一个初始状态,通过复位可以进入到一个初始状态。这样单片机知道自己的起始位置,它才能按照既定的方式运行程序。