51这种货色,学了最多就所谓的垃圾科创利用一下,但是想一下这门课我也要考试,还是写一点东西顺便放博客上吧。
这一系列主要参考《单片微机原理与接口技术》这本书的内容(这本书的特点就是废话特别多,中国式特色教科书)+ 一点CSAPP,还有老师的课件。
0. 机器码的表示
简单的原码,反码和补码的表示相信大家一定很熟了,下面我们来聊下BCD码的计算和IEEE标准的浮点数
BCD码的表示与计算:
BCD码是用4位二进制码来表示十进制数中的0-9这10个数码,BCD码有很多种形式,如8421码、余3码、5421码和2421码等,其中8421码应用最为广泛。
对于8421 BCD码计算我们要注意一个trick,由于大部分的芯片都是按照二进制加法的规则来进行运算的,包括51也是一样的,所以51提供了一个DA指令来处理这种BCD码的调整(注意DA指令一般要在ADD或者ADC指令之后,注意51是没有关于BCD码的减法指令的,需要自己设计)。
DA本质上是对A寄存器的调整,调整的具体步骤是:若A的低4位大于9或者辅助进位标志AC为“1”,则低4位加6,同样,A中的高4位大于9或者仅为标志CY为1,高4位加6。
比如简单的98H + 03H 做加法后为9BH(CY = 0,AC = 0)
1001 1000
+ 0000 0011
= 1001 1011
9BH显然不是正确结果,需要我们调整,低4为需要+6,变为0001,同时高四位由于调整后进位变成了1010
1001 1011
+ 0000 0110
= 1010 0001
这个时候我们可以看到高四位还是大于10,继续调整
1010 0001
+ 0110 0000
= 10000 0001
这个BCD码就是101了,我们得到了正确的答案,这个时候在51单片机的AC位为0,CY = 1
51单片机里面的程序可以这样写:
MOV A, #98H
ADD A, #03H
DA A
至于说为什么是+6,其实很简单,因为BCD码是表示的是0-9的数一共十个,但是16进制数是有16个数字的,所以相对于BCD码而言大于1001的6个码都是非法的,当我们加6以后即会产生(10 + X +6)mod 6(0<X<6),这样所有数的表示都落在0~1001之内了。
IEEE码的表示和计算(软件工程必会,非科班了解一下即可):
当然这一部分教科书上讲的太浅了,IEEE码其实是一个很巧妙的设计,我们来看下CSAPP的例子:
IEEE浮点数的表示总共分为三个部分:符号(sign),尾数(significand),阶码(exponent)
总规则:V = (-1)^S * M * 2^E
其中根据编码值可以分为四种情况:(CSAPP的插图)
情况1(规格化的值):exp的值不全为0,也不全为1(单精度255,双精度2047)时,都属于这种情况,在这种情况下,阶码字段被解释为偏置(biased)形式表示的有符号整数,阶码的值为E = e - Bias,其中e为无符号数2^k个数,而Bias是一个等于2^(k - 1) - 1)(单精度是127,双精度是1023),指数的取值范围,对于单精度是-126~+127,双精度是-1022~1023。
frac字段是一个描述小数值f的位置(0 <= f < 1)但是IEEE把尾数定义为M = 1 + f(M是一个二进制表达式1.xxx的数)。
情况2(非规格化的数):在这种情况下,阶码值是E = 1 - Bias,尾数是M = f(没有隐藏1),这个设计很巧妙,等下我们就可以看到其精妙之处了。同时非规格化数可以表示0(-0.0和+0.0),也可以表示那些逐渐溢出(gradual underflow)的数,其中,可能的数值分布均匀接近0.0。
情况3(特殊值):当小数域全为0时,我们知道0的n次方是没有意义的,IEEE把这种值定义为无穷大(s = 0是正无穷,s = 1是负无穷)当我们把很多大的数相乘或者除以0的时候,无穷能表示溢出的情况。NAN表示“不是一个数”(Not a Number),一些运算的结果不能使实数或者是无穷的时候,同样会表示NAN(典型例子-1开根号,无穷-无穷)。
根据规则我们可以写出上面的表,从表中我们可以看出IEEE浮点数的设计的精妙性,从非规格化数到规格化数的变化是平滑的!这就给计算机判断数的大小提供方便,计算机只用负责二进制的位就可以了,不用额外的规则,让浮点数也可以像定点数那样用整数排序函数来排序。
注意浮点数的运算规则和整数的不一样,不满足阿贝尔群,所以浮点数的加法和乘法都不具有群特性。举个例子:(1e20 *1e20)*1e-20 的值是NAN,但是1e20 * (1e20*1e-20)的值是1e20。
1. 微机原理概念
1. 微机系统由CPU、存储器(ROM和RAM)、输入/输出接口电路(IO)和系统总线(BUS,按所传送信息的不同类型可分为数据总线、地址总线和控制总线)构成。
地址总线(AD)总是三态,单向的;
控制总线(CB)的信号线,可为单向或双向,可为三态或非三态;
数据总线(DB)总是双向的,单态的。
2. 微机基本组成(5个部分)
算术逻辑单元(Arithmetic Logic Unit,ALU):顾名思义,拿来处理算术指令的。
累加器(Accounter,ACC):来中转各种计算结果。
标志寄存器(Flag Register,FR):存储状态标记(51里面是8位)。
程序计数器(PC,Program Counter):存放下一条指令位置的寄存器,无法寻址(当然了,可以通过压栈的trick来获得值),CPU自动修改(按照指令长度)。
指令寄存器( Instruction Register,IR):保存当前指令的寄存器。
2. 微机存储器
ROM分为5种类型:掩模式(Masked)(出厂后不可修改),可编程(Programmable)(允许一次性编程),可擦除(Erasable)(紫外光照射擦除),电可擦除(Electrically Erasable),闪存(Flash Memory)。
RAM可分为两种:静态(一般用在寄存器上),动态(就是我们的电脑的内存)。
3. 80C51基本结构与原理
首先80C51是总线结构的,数据总线和地址总线经常要复用。
上图是一个标准的80C51的引脚图,这些引脚的功能如下:
1:VCC是电源端,VSS是接地端
2:XTAL1(TTL时钟接地)和XTAL2(TTL时钟输入端)是接晶振的部分。
3:ALE地址锁存器,对P0口进行地址锁存用的。
4:外部程序选通信号PSEN:专门拿来做拓展用的外部ROM的(拓展外部RAM请用RD和WD,PSEN对外部RAM无效),低电平有效,每个机器周期中有效两次。
5:EA相当于是外部ROM的选通位,当其是高电平时,选通片内存存储器指令。当其是低电平时,只执行片外的。需要注意的是,当EA为高电平时,当PC的值超过0FFFH时,将自动转向执行片外的程序指令。
6. RST为复位信号。
7. IO口(P0,P1,P2,P3)
80C51内部组成:
1. CPU:包括运算器电路(ALU,ACC,B寄存器,状态寄存器,暂存器1和暂存器2),控制器电路(PC,PC+1寄存器,IR(指令寄存器),指令译码器,数据指针DPTR,栈指针SP,缓存器以及定时的控制电路。)
2. 定时/计数器(16位)
3. 存储器(RAM和ROM)
4. 并行IO(4个P口)
5. 串行口(一个异步全双工串行通信接口(异步:接和收用不同时钟,全双工:数据双向传输可以同时(区别于半双工)))。
6. 5个中断源(外部中断0、1,定时器中断0、1,串口中断)
4. 80C51四大IO口(都是并口)
0. P0口:
这个口相当重要,往后面会经常用到这个口,这个口是相当标准的输入输出口,我们看整个电路的右边,可以发现它的电路是少了一个电阻的(上拉电阻),在4个并口中这是P0口最特殊的地方,P0口作为一个双向口,其作为一个输入口时,是不需要上拉电阻的,但是其作为一个是输出口的时候,是需要上拉电阻的。(有个很典型的例子就是P0口点灯泡时候只能是低电平点亮)
同时P0是一个真正的双向口,这个体现在它用作拓展电路时是需要负责传数据还有地址的,在外部拓展时记得给P0口加锁存芯片。比如下图拓展2732这块存储器时加入了373锁存器。
1. P1口:
可以看到P1口比P0口要简单一点,是准双向口,但是没有P0口的控制电路的的。和P0一样,在输入数据时,必须给P1口先写1,不然输入将无效(后面的有些作业就是因为忽略了这个问题导致了很多奇怪的问题的发生。),P1口可以驱动任何电路(MOS,TTL....)。和P0口不一样,P1口作为标准输出口的时候不需要上拉电阻的。
2. P2口:
P2口也是一个准双向口,在拓展外部电路时,和P0一起组成16位的地址,当然了P2口也可以拿来做标准输入输出。
3. P3口:
P3口是一个准IO口,功能和P1是类似的,可以作为标准IO口使用,但是P3口有其独特的功能,如下:对于输出特殊功能而言:相应的锁存器由CPU自动输出为1。
注意下面的RD和WR,这两个口是拿来拓展外部存储器的。
P0~P3口作为通用的输入口的时候,必须使电路的锁存器写入1。
要区分两种读端口的方式(读锁存器和读引脚),一般来说,读锁存器的操作大多数都要读写端口,而读引脚的操作一般只会读端口而不写(读引脚不会影响锁存器的状况)。
P0口的每个IO口可以驱动8个LS TTL的输入,而P1~P3的每个IO口只能驱动4个。
(题外话:这个驱动个数其实是很重要的,因为在实际使用的时候,80c51这种垃圾芯片会因为驱动个数太多导致电流太大而导致不断重启(会发生“哒哒哒哒哒”的叫声),这种坑爹的事情就发生我的项目上,当时我低电平驱动太多白光LED直接出问题,我还不知道什么情况,虽然可以加上拉电阻,但是会导致亮度不够,最后换高电平驱动才解决问题。)
5. 80C51的内部数据存储器(低128字节)
80c51的内部存储器是SRAM(256字节),低128字节是内部数据RAM区,高128字节是特殊寄存器区(Special Function Registers,SFR)。
1. 工作寄存器组(00H - 1FH)
最低的32个字节是寄存器的,每一组通用寄存器都有R0-R77个8位寄存器,通过标记位(就是特殊寄存器里面的PSW寄存器的RS0和RS1两个位)指定使用哪一组
汇编代码如下(如果看不懂的可以先去看一下指令再来看):
MOV R0, #FFH ;假定寄存器现在是0
SETB RS0 ;给RS0置位,即切换到寄存器组1
LCALL SHIT ;调用一个过程
CLR RS0 ;切换回寄存器组0 SHIT:
MOV R0, #00H ;在SHIT方法中改变R0,这个时候改变的是寄存器组1的内容,寄存器组0的R0的内容不变
RET
2. 位寻址区(20H - 2FH)
这里一共16个字节,也就是128位,注意这个区域即可以通过字节操作,也可以位操作,比如:
MOV A, 20H ;操作的是字节
MOV C, 20H ;操作的是位
3. 用户RAM区(30H - 7FH) 一共80个字节,这些地方随便使用。
6. 80C51的内部数据存储器(高128字节)
需要注意的是,80c51一共是有22个专用寄存器的,但是PC是一个不可以寻址的寄存器,所以不属于SFR区,其他21个都在SFR区(可以寻址(直接打名字),但是不一定可以位寻址)。
这是51单片机手册上的配图(题外话:其实挺建议大家学51看手册和一本靠谱的书来学的,比看那些所谓51单片机教学的垃圾视频有用的多,书上的那个图其实是手册图的简化版,有些特殊的芯片,比如在上面手册图对应的芯片上,我们可以看到这个芯片比书上的的那个图多了IPH这个东西(我一开始学的时候就很疑惑为什么80C51只有0和1两个优先级,其实这是设计缺陷,新的80c51加多了这个东西),当然了为了应付考试我还是建议大家当看不见这个东西。)
上面特殊寄存器的功能和英文是一一对应的,同时,上面有一些寄存器的地址,不能被8整除(比如DPTR的高8位DPH和低八位DPL),这些寄存器就是不能位寻址的那些寄存器。在这里先调一些特殊的讲一下(其他寄存器用到再说):
A寄存器(累加器,Accounter):所有有关ALU运算指令都是必须用到A的,用的最频繁的寄存器
PC寄存器(程序计数器,Program Counter):这是一个16位寄存器(最大寻址范围64KB,范围0000H~0FFFFH),永远指向下一条指令的地址。
PSW(程序状态字,Program Status Word):这是一个8位寄存器,从高到低位为CY AC F0 RS1 RS0 OV (空) P
AC位为辅助进位标志:当操作结果的低四位向高4位进位或者借位的时候,这个标志位由硬件自动置1(比如上面的DA指令就用到这个标志);
F0是用户标志位:这个东西其实拿来辅助复位的,这个东西和看门狗有点关系,可以用F0标志位和外置复位电路结合。
RS0和RS1标记:标记当前使用的寄存器组。
P是奇偶标志位:标识结果的1的个数是偶数还是奇数。
CY和OV这两个标志位就很有意思了,
CY是最高位的进位标志,有进位/借位就CY=1,否则为0。
OV标志位对于加减法来说,是运算结果是否超过-128~127,是的话就置1,否则为0。
如果是乘法,则超过255 OV为1,结果分别存在A和B寄存器上,否则为0,结果只存在A寄存器上。
如果是除法,则OV=1则表示除数为0,否则除数不为0。如结果A中有奇数个1,则置P=1;否则P=0。常用于校验串行通信中的数据传送是否出错。
一道例题:
若:(A)=78H ,(R0)=64H执行ADD A,R0 后,结果及PSW=?
(A):78H= 01111000 B
+ (R0):64H= 01100100 B
(A):DCH= 11011100 B
标志位: CY=0, AC=0, OV=1, P=1, 即 PSW=05H
结果:(A)=DCH (R0)=64H
这个时候CY异或OV为1,表明这个结果溢出了(检验加减法有没有溢出的快速方法)。
DPTR寄存器(数据指针,Data Pointer):16位寄存器,可以分为DPH和DPL。经常和MOVX指令和MOVC指令一起用。
SP栈堆指针(Stack Pointer):学过数据结构的同学很清楚这个东西了,不说了,只是要注意的是80c51的栈堆是向上生长的,而且一个push指令是先+1,然后再压入数据,pop指令是先pop数据,再指针-1。
其他寄存器以后再说。(TMOD,TCON,PCON...)。
7. 80C51的内部程序存储器
EA接高时,程序先从内部程序存储器取指令0x0000开始,内部内容超过0xfff时,去外部程序存储器执行指令。外部程序存储器从0x1000开始编址。当然了对于增强型的PC内容超过0x1fff时才会执行外部程序存储器的内容。
EA接低时,程序直接从外部程序存储器取指令,从0x0000开始编址。
程序存储器的一些低端地址被固定用于一些特定的入口地址:0x0000~0x0002一般存放跳转指令,紧接着就是8个中断入口地址:
002BH后面的单元才是可以随意使用的程序存储器单元(有点像8086前面那些中断向量啥的)。