本篇博客是根据通篇学完程启明的单片机原理及应用后,根据自己的理解进行的梳理,复盘。
单片机顾名思义就是一将基础硬件集成到一块芯片上。通常单片机由CPU,ROM,RAM,I/O,定时器,计数器,中断,总线组成。单片机的英文缩写为MCU(micro control unit)或EMCU(embedded micro control unit)。它们之间的关系如下图所示,其中实线代表数据信号,虚线代表控制信号。
单片机从发行至今有许多的版本,但他们无非就是在同一架构下,例如挂载了更多的存储芯片等。我们可以讲他想象成电脑,当内存不在能满足我们的要求时我们可以在原有的基础上更换或增加内存条同理。
8031到8051到8751他们都是内存的升级从无片内ROM一路升级到EPROM。结尾为2的分别就是在1的基础上的性能升级版,如增加RAM,定时器,中断等。
首先,我们需要了解的是单片机内部是通过三总线互相连接的,他们分别是数据总线;控制总线;地址总线。
8051中含有:
众所周知,单片机光有这些部件还不能运行还需要给他外加一个晶振,相当于给他加上一个心脏,他们就能运行了,通常有如下两种方法:
其次,单片机不仅片内有片内存储器 还能外扩存储器,那如何选择这些存储器呢?这就需要用到EA非,当他为1是片内加片外,片外与片内地址相重复的部分将不被使用,当他为0时,则全部使用片外。因此片内片外的存储单元地址可以相同,但他们实际上不可能会冲突。
通常存储器有如下两种结构:
一个单片机的存储器是由程序存储器+数据存储起+SFR共同组成。他们的寻址方式:
之前我们提到过RS0与RS1是用来选择不同的寄存器,他实质上就是将片内的低128B的内存空间分为四个区域,进行选择,他们与其他存储空间不同的是,他们能够进行位操作而其余的只能按字节存取。需要注意的是,程序运行时,有且只有一组作为当前工作寄存器。
如何判断一个寄存器是否能进行位寻址:看他地址末尾是否为0或者8。
我们可以从单片机的板子上看到单片机有P0,P1,P2,P3口,这些端口复位后都为高电平,但这些并不是我们都能够使用的,能供用户使用的只有P1和部分P3口。
当我们使用端口时,我们很有可能使用到串口,因此,将简单介绍一下单片机的几个周期:
对于单片机编程主要有汇编与C51两种方法,C51语言与C语言相似,所以这里将简介一些常用的汇编指令。
首先按指令共能能够将各指令划分为:数据传送指令;算术操作指令;控制转移指令与位操作指令。
指令占用字节数的一个总结(不一定完整):
一些常用容易混淆指令的介绍:
寻址方式的介绍:立即数(#);直接;寄存器间接;相对;位;基地址加变地址;寄存器。寻址方式越多单片机的性能就越强。
接下来就是一些重要指令的详细学习。
我们知道数据传送指令有:MOV;MOVC;MOVX他们都能将数据传输到指定位置,不过他们的不同点在于:C代表程序存储器,X代表片外的存储器(16为地址,通常用DPTR)。下面我们通过一个例子,体会一下这条指令。
MOV R0,#40H
MOV A,@R0
MOV R1,A
因为我们之前说过,当程序运行时,四组寄存器当中只能有一组被选中,因此R所对应的有且只有唯一一组字节。首先,第一句指令就是将40H这个立即数传送给R0即(R0)=40H。第二句指令是间接寻址,就是将R0的内容作为地址,将该地址中的内容送给A。第三句就是很普通的将A的内容送给R1。
数据转移指令的另一个功能就是可以用作查表:
我们先想象一下,我们手里有一组数据,而我们有时需要的仅仅是其中的某一位数据,那我们是不是就得要知道该数据在这个数组中的具体位置。同理查表法和这个原理相同,不过他有两种索引方式:
对于方法一,PC指的是当下下一条程序所在的地址,只有A是可变的,因此这个数据表不能离该指令过远,因为A只有8位,最大寻址范围只有256B,而且也需要考虑到该程序与表之间的偏移量,比较麻烦。
方法二则将表首地址传送给DPTR,这样他的最大寻址范围为64K,也就是能放在任意的位置,也不用考虑偏移量的问题相对方便的多。
我们尝试用一下使用PC的方法,做一个简单的查表共能:
MOV A,#02H ;2
ADD A,#01H ;2
MOVC A,@A+PC ;1
RET ;1
TAB:DB 30H,31H,32H
这段代码是想将第三个数值取出,因为是从0开始计数的。第二句语句就是为了填补RET占用的一个字节地址所引起的偏移量。
操作堆栈的指令就只有两条,压入,弹出,PUSH,POP,他们的作用分别如下:
下面,我们就通过一个例子分析仪一下堆栈的应用:
压栈:
MOV SP,#90H ;将90h作为首地址的堆栈
MOV DPTR,#1234H
PUSH DPH ;将SP+1,将高位送入堆栈91H
PUSH DPL ;将低位送入92H
出栈:
MOV SP,#33H
MOV 33H,#23H
MOV 32H,#44H
POP DPH ;将23H送给DPH
POP DPL ;将44H送给DPL
首先在这里补充一个知识点,由于二进制码不直观,所以引入了一个叫做BCD码的数,他把二进制9以上的数都舍去,因此当作常规加减法,如果低位或高位过九都需要加6。
例1:将50H,51H地址中的内容相加
CLR C ;似乎当会涉及到某些特殊位时第一步就是将他清零
MOV R1,#50H
MOV A,@R1
INC R1
ADD A,@R1
MOV 40H,A ;将计算结果送入40H
RET
END
首先我们需要梳理一下机器码,汇编语言以及C语言他们之间的关系:
下面我们将通过一些例子更为直观的理解汇编语言。
例1:将21H的低三位和20H的低五位合并为一个字送30H,三位在高
MOV 30H,20H
ANL 30H,#1FH ;取低五位
MOV R1,#21H
ANL @R1,#03H
SWAP @R1
RL @R1
ORL 30H,@R1
RET
END
例2:测量一个以回车为结尾的字符串的长度
;回车是ODH
START:MOV R0,#59H ;确定取字符串的地址
MOV R1,#FFH ;用于计数
LOOP:INC R0
INC R1
CJNE @R0,#0DH,LOOP ;若不相等下一字节继续进行比较,直到相等
RET
END
注:RET与RETI的区别:RETI能够将引起中断的标志位清零而RET只能很普通的回到跳回到开始子程序的位置。
例3: 两位压缩BCD码位于30H转化为2进制数保存到40H
当我们遇到这种稍微复杂一点的程序时,我们可以先按照自己的想法列些一个流程,这样对于后续的编写就会容易很多。
具体流程:
ORG 1000H
MOV A,30H
ANL A,#0FH
MOV 40H,A ;低位先传给目标地址
MOV A,30H
ANL A,#0F0H
MUL A,#0A0H
ADD 40H,A
SJMP $
注:ORG使用的时候,只能从小到大顺序列写,不能交叉。
例1: SGN符号函数,从40H经过符号函数取送到41H
流程:
判断方法:取出后与0比较,若相等直接输出,若不想等,从0减去,如果Cy为1,则表示为正,反之为负。
ORG 2000H
START:MOV A,40H
MOV R1,#OOH
LOOP:CJNE A,R1,CP
MOV 41H,RO
CP: CLR C
SUB R1,A
JC ZH
JNC FU
END
ZH: MOV 41H,#01H
FU: MOV 41H,#81H
注:对于复数,再单片机中用补码表示,取反加一或者使用标志位(最高1负0正)
循环程序的组成:
例1: 将30H~4FH转移到外部存储器2000H开始
流程:
ORG 1000H
START:MOV R0,#30H
MOV DPTR,#2000H
LOOP:MOV DPTR,@R0
INC R0
INC DPTR
CJNE R0,#50H,LOOP
SJMP $
END
注:是否使用RET自己的一些理解***:就如果这个子程序运行完成后需要回到原来的断点,继续运行主程序,就需要用到,如果类似多分枝,直接结束就好了。
**例2:**统计数据长度:在40H中有一个以0AH结尾的字符串,统计长度输入80H
流程:
ORG 2000H
MOV A,#3FH
MOV R1,#0FFH
LOOP:INC A
INC R1
CJNE A,#0AH,LOOP
MOV 80H,R1
SJMP $
定时程序有两种方法,一种是定时器,他到预设时间后就会发出一个中断;另一种就是利用软件进行延时。他们之间存在些许区别:定时器会有时间长度的限制,但如果软件延时他可以想多就就多久,多重嵌套就能够实现,但软件延时中绝不允许有中断,因为这会打断这个延时去做别的事,从而影响延时的准确性。下面我们将会分别介绍这两种方式。
从本质上来说,定时器与计数器都是同一个,他们都是对系统脉冲进行计数,但不能对ALE计数,因为他在MOVX指令时,会缺少一拍。
TMOD的格式:GATE C/T M1 M0
当作为波特发生器时,常用方式二!
定时公式
原理:对12分频后的晶振频率进行计数
因此不同的方式他们的计算公式不同,主要区别在于初值的计算。
a表示位数,x表示初值
计时器的意思就是当你给定一个初值后他就会每当来一个机器周期就会记一次数,直到记满溢出发中断,于是这公式的含义就显而易见了。初值算出后就将它分为高低八位分别填入TH0,TL0。如果像方式二只有八位就高低相同就可以了。
定时器的程序设计:
总结过定时器后我们发现,定时器只能定时一小段时间,一但溢出就会产生中断,而延时程序一旦有了中断就会影响延时的精确,因此对于需要长时间的定时就需要使用到软件延时。
原理:不断重复某一条无用的指令,因为我们知道每一条指令都有他的指令周期,因此,我们就可以重复他我们想要的次数从而达到延时的目的。
注:因为涉及到指令计数,他会使用到累加器ACC,而ACC最大八位,因此最多只能记256次,于是,如果超出次数就需要使用嵌套。但这种方法的精确度不高,因为不只是这条重复的指令会占用时间,比如使用到的其他跳转指令也会占用一定的时间,需要将她们减去,其实如果基数够大,这几纳秒的时间也能忽略不计。
为了使我们的程序简洁方便,容易阅读,因此就引入子程序的概念,首先我们就能够预先完成某一个功能的代码,当我们在程序中需要使用到这个共能时我们只需要跳转到该子程序即可。当我们写程序的时候,我喜欢将他们放在主程序的后边,这样只有跳转指令才能运行到这段子程序。
注:当执行跳转指令时,不管是什么跳转,都会自动执行现场保护,因此,有跳转就得有堆栈,用来存当前的PC以及需要被保护的数据,子程序的最后一定有RET用来返回恢复现场。
两种现场保护的区别(数据):
子程序的特性:
注:理论上可以有无数的子程序嵌套,但实际上,需要考虑到堆栈的深度,不会无限。
例:P1口8个LED依次闪烁十次(假设共阴极链接)。
程序流程:
实例(以书上为准,此为自己编写)
ORG 1000H
MOV A,#08H
MOV R3,#08H
MOV SP,#60H
ORG 2000H
MAIN:MOV R2,#0AH
PUSH A
MOV A,R2
LCALL LT
POP A
RR A
MOV R3,A
LJMP MAIN
DELAY:MOV R0,#012AH
DJNZ R0,DELAY
RET
LT:MOV P1,R3
LCALL DELAY
MOV P1,#0FFH
DJNZ A,LT
RET
之前介绍过查表程序可分为以下两种方式:
他们两的唯一区别就是方式一需要考虑偏移量,而方式二就能放在任意位置,方式二较为简单,因此继续展示方式一。
ORG 1000H ;1000
START:MOV A,30H ;30H中装有要求的数 1002
ADD A,#02H ;1004
MOVC A,@A+PC ;1006
MOV 31H,A ;1007 将找到的值传给31H
TABLE:DB 0,1,4,9,16 ;0的地址为1008 = 1006+02
首先我们来回顾一个知识点:单片机的三总线分别为:数据;地址;控制。数据总线能够传输不同的数据,他们分别有:数据,状态以及命令,他们分别对应三个同名的端口,但他们分别拥有不同的地址,因此可以用地址来区分这三组数据。
那么,为什么我们需要在单片机与I/O设备之间增加一个接口电路呢,原因有如下两点:
I/O的一些特性:
读取外设的指令:MOVX
数据传送的方式:程序控制;直接存储器读写;I/O处理机
其中程序控制有如下三种方式:
因为51只有程序控制,因此,我们将详细介绍一下这三种方式。
数据的输入输出分别有两个缓冲器,当一个工作时,另一个就处于高阻状态,因此他们能够共用一个地址。
使用前提,必须提前通过软件延时将两者的速度匹配。
例:外部缓冲器8000H输入一个BCD码并点亮对应的数码管数字位于8002H
START:MOV DPTR,#8000H
MOVX A,@DPTR
ANL A,#0FH ;DPTR为16位而输入只有四位
MOV R1,A
DEC A,#09H
JNC ER
MOV A,R1
MOV DPTR,#TABLE
MOVC A,@A+DPTR
MOV DPTR,#8002H
MOVX DPTR,A
SJMP DELAY
ER:MOV DPTR,#8002H
MOV A,#06H
MOVX @DPTR,A
SJMP START
TABLE:DB ~~~~~~~ ;包含着各数字的数码管的二进制码
查询方式需要两个端口及其原因:当数据准备好了才发出准备好了的状态,当传送完后自动将状态位清除。因此需要一个状态端口以及数据端口。
具体步骤如下:
流程图:
从流程图我们能够看出,虽然查询方式的数据传输准确性得到了提升,但是,这个不断查询,直到外设准备完成会拖慢程序的速度。因此他仅适用于对于速度要求不高的场景。
**注:**一组八位的数据,其中一位是状态位,对于输入,位于最后一位,对于输出位于第一位。又因为只有SFR和一些特殊的寄存器能够进行位操作,因此我们可以使用带标志位的移位操作将状态位传递给Cy位就能够进行单独的判断。
这将会在终端章节详细介绍,大致流程就是当数据准备好后给外部中断信号发送中断信息,请求CPU进行处理。
中断的过程:中断请求;中断排队;中断响应;中断处理;中断返回(reti)
下面那我们就介绍一下中断的控制字:
中断响应的6个条件:
正因为第六条,我们可以知道最短的以及最长的中断响应时间:
在程序编写的时候,如果是汇编,只要进入到相应的地址就相当于是进入了中断;对于C51就用下面这条语句:void intr1() interupt2 using 0
这句话的意思就是我们假设一个中断函数,他的名字叫做INTR1,我们用到中断二,并使用到0区的寄存器,中断二就是按XTXTS从0开始数的第二个,using就是我们之前提到过RS0;RS1可以用来选择低128位的寄存器,就是那个。
中断处理的流程:当接收到中断信号后,首先关闭中断,保护现场,避免此时恰巧又来其他中断的影响,然后打开中断执行中断程序,执行完毕后关闭中断,恢复现场,再打开中断,中断返回。
当我们看到这时,我们发现,中断关闭的时候仅在现场保护和恢复的时候,之前我们还提及过中断也能够被打断,这是在现场保护完成之后才能被打断,因为如果还在保护的时候进入打断会把现场数据打乱。
中断请求的撤销:
汇编程序示例:
ORG 0000H ;org对应的起始地址应当从小到大有序排列,不能错乱
LJMP MAIN
ORG 0003H
LJMP INT
中断服务程序示例:
CLA EA ;关闭总中断
PUSH PSW
PUSH A ;现场保护,pc似乎是自动存起来的
SET EA ;打开总中断
...... ;中断服务程序
CLA EA ;关闭中断
POP A
POP PSW
SET EA
RETI
例:利用外中断脉冲,来一次亮个灯
ORG 0000H
LJMP MAIN
ORG 0013H
LJMP INT
ORG 1000H
MAIN:MOV A,#00H
MOV R2,#64H
MOV R3,#FFH
MOV SP,60H
MOV IT,#01H ;脉冲我觉得使用跳沿触发更好
SETB EX0
SETB EA
MOV P0,#00H
SJMP $ ;主函数就是设定好这些初值后等待外部中断
INT:MOV P0,#0FFH
LCALL DELAY
MOV P0,#00H
LCALL DELAY
MOV P0,#0FFH
RETI
DELAY:MUL A,#O2H
DJNZ R3,DELAY
LJMP ER
RET
ER:DJNZ R2,ER
RET
我们知道单片机的各种资源都是十分有限的,但如果我们外部中断需要有三个的情况我们应当怎么办呢?这个时候,如果我们的定时计数器的中断没有被占用,我们就能利用他们扩展外部中断,具体做法如下所示:
我们常用的定时方式有:
这里M0,M1可组成如下四种方式:
TCON在中断章节已经介绍过了,这里将不再赘述。下面我们就这四种方式进行介绍。
其实方式0和方式1两者几乎相同只有在初值上有少许差别。方式一就是将16位全部填满,而方式零是为了兼顾兼容性,共13位为低5+高8,下面我们就解释一下初值公式的原理。
我们需要单片机进行一段时间的计时后发出中断,也就是说,如果我们要求的计时时间,在这个计时器的计时范围内,又因为我们知道,其实计时器就相当于是一个计数器,不过他就是对于机器周期进行计时,因此我们就能利用初值,将初值到计数溢出一共有多少次确定出来,就能确定即使时间,这里计数溢出值就是我们所说的多少位,例如方式二就是2的16次方。最后得出的初值就存放到TH中。
那么,如果作为计数器呢,他究竟是什么样的呢?如果我们此时需要测量一个外部脉冲的正脉冲有多少次,我们就可以将此信号接到单片机的引脚上(P3.4),当来一个脉冲就记一次数从而达到计数的共能。但他真的什么频率的脉冲都能够测量吗?显然这是否定的。
首先,我们知道对于单片机来说,一个机器周期,单片机就相应的作动一次,那么就对于采样来说,就需要两次采样才能判断他是否产生跳变,也就是说他需要花两个机器周期才能判断出信号的跳变才能够进行计数,因此外部输入最小周期为两个机器周期
因此外部待计数信号的最大频率就是:
那么还有最后一个门控位我们还没介绍到,他的主要功能就是测量正脉冲宽度。
我们就从上图来分析,我们先将第一个多路开关选择到定时器模式,因为待测量信号不能接到T0口因为那是计数的,其次我们看到,门控位后面接了一个非门,也就是说,如果此时门控位为0,那这模拟开关一定闭合,因此普通模式下,只需要将门控位置零就行,但如果当门控位为1时我们注意到只有当外部中断引脚输入高电平才能打开进行计时。因此,如果我们在外中断引脚外加一个信号,我们就能测量他的正脉冲宽度。
那么,我们测量一个脉冲宽度的具体做法就是按照我们上述的分析将信号接到对应引脚,设置定时器,将TH清零,那么当正脉冲来临时,开始计时存到TH中当低电平时信号和关断停止计时,我们只需要读出TH中的数就能知道他的正脉冲宽度。
下面我们就举一个例子:
例:测脉冲宽度
ORG 1000H
LJMP MAIN
ORG 3000H
MAIN:MOV TMOD,#09H ;运用T0来测量门控位置一,方式1,定时模式
MOV TH0,#00H
MOV TL0,#00H
LP:JB P3.2 LP ;等待信号的低电平,打开了启动位,随时准备计时
SETB TR0
LOOP:JNB P3.2 LOOP ;等待他高电平
HERE:JB P3.2 HERE ;高电平计时
CLR TR0 ;高电平结束关断计时
MOV 30H,TL0 ;读出计时次数就能反推出计时时间
MOV 31H,TH0
SJMP $
其实方式二唯一特别之处就是能够自动填充初值。因此由于这个特性就能够作为波特率发生器。
那么我们就举例一个最普通的定时器例子:
例:时钟频率为12兆赫兹,利用T0,产生20毫秒的方波输出在P0
分析:因为20ms的定时所以只能使用方式一,不然不够用,其次就是初值计算。
ORG 0000H
LJMP MAIN
ORG 000BH
LJMP FB
ORG 1000H
MAIN:MOV TMOD,#01H ;T0的方式一定时
MOV TH0,#0D8H
MOV TL0,#0F0H
MOV SP,#60H ;既然有中断那么一定需要堆栈进行现场保护
SETB EA
SETB ET0 ;打开中断允许
SETB TR0 ;开启定时器
SJMP $
BL:CLP P1.0 ;取反P1.0
MOV TH0,#0D8H
MOV TL0,#0F0H ;因为是方式一,所以要手动填入初值
RETI
方式3他比较奇特,他采用将T0划分为两个8位的计数器,停用T1,T0可以按正常模式选择进行设置,就相当于是一个不会自动填充的方式二下的定时器;另一个八位只能借用T1的控制字来进行计数。
在进行学习前,我们先了解一下什么是串行,什么是并行。从字面来看,串行就是一串,也就是数据一位一位出;而并行就是所有数据并排出,就是一次全出完。因为单片机出数据是串行通讯,因此我们就重点学习他的两种通信方式。
定义:通信的发送与设备的接收分别使用他们各自的时钟,但这并不意味着可以传输波特率不同。这种方式的缺点就是传输效率不高。
数据格式:空闲位(高)+起始位(低)+内容+检验位(例如奇偶也可以没有)+停止位(高)
定义:发送方与接收方使用同一个时钟信号。特点:传输的字符间不留空隙。
同步传输有两种不同的数据格式:
串行通信有如下三种传输方向:
如果遇到超远距离的信号传输,就像是我们电脑上网一样,有一个东西叫做调制解调器。
调制解调器就是将计算机发出的数字信号转换为通信电缆能够传输的模拟量信号,他们之间的关系如下图所示:
串行通信的错误校验
刚才我们在学习数据格式的时候我们就看到了在一帧完整的数据中会包含有检验位,那么,在单片机中存在有如下三种校验方式:
串行的传输速率与距离
首先我们介绍一下,串行接口是用来干什么的:
那么接下来我们就认识一下各传输协议的特点:
RS-232:
RS-422A:
RS-485:
他与422非常相似只是将收发双线砍成单线,常用于制作站点通讯,只是只能有一个主机,其余均为从机,并且只有主机才能给从机发送信息,从机之间互不交流。
单片机内部串口的结构图如下所示:
串口就是由发送器电路+接收电路+控制电路组成。
由结构图我们能够发现内部包含有两个相同地址但是物理上独立的两个缓冲器,他们分别叫发送缓冲器与接收缓冲器。
那么为什么这两个寄存器需要使用同一个地址呢?
由于单片机的CPU对发送与接收寄存器不能同时进行操作所以给这两个缓冲寄存器赋予同一个特殊功能寄存器地址。也就是说,这个CPU是单线程的,他一次只能做一件事,在全双工的情况下,如果不是同一个地址,做不到同时收发。
注:在RXD前面还有一个移位寄存器,构成双缓冲结构可以避免重帧现象即在下一帧数据来时,前一帧数据还没被读走。发送方不需要,因为发送方是CPU主动的而接收方是被动的。
方式0:固定波特率:就是晶振的12分频。主要用于扩展I/O
我们可以看到TXD发出移位脉冲,RXD存放数据,来一个脉冲就发一个数据,当一帧完整的八位数据发送完后,发送中断置一。同样的接收方收完后RI置一,但唯一有一个不同点就是作为接收方他的REN打开在能接收数据。
方式1:可变波特率,他是十位数据,没有校验位。
从公式我们能看出,如果我们想改变波特率仅需要改变溢出率就可以。这溢出率的就是T1放在方式2下的所以是256-x。
由这个时序图我们可以看到,发送的时钟脉冲将不再依据机器周期,而是设置后的波特率。当执行到MOVX后就会产生发送信号。我们还注意到相对于方式0,方式1还多了两位,起始位与结束位。起始位会被串口自动清除,当传输到结束位,才会引起中断。
但,这个方式下接收也会有所不同,第一种就是按发送方的波特率收数据。另一种方法就是以波特率的16倍频对于每一位数据进行采样就能有效的消除干扰,就像是按键防抖一样。
在这种方式下,我们如何确定当一帧数据发送完后,受否发送下一帧数据呢?
第一:RI=0,代表了数据已经由SBUF全部被取出;第二:SM2=0/RB8=1。当SM2=0的情况下,只要发送了都接受,反之,只有RB8也同时为1才能接收。如果不满足上述两个条件,数据就会被抛弃。
方式2/3
这两种模式都是11位数据,多了一位校验位,在第九位,位于SCON中,也就是说这第九位数据是由硬件电路从TB8直接送发送缓冲器的第九位,由此开启串行发送。
方式3与方式1几乎完全相同,就是多加了一个校验位,波特率也相同。
方式2的波特率:
多机通讯通常使用方式2,如485通讯。他的原理就是:首先设定完PCON(里面包含有SMOD),打开SM2(多机),接收方打开REN。常规设置完成后,接下来就是一些判别我的主机具体要给和谁通讯的问题。
首先呢,我们需要给各个从机一个地址,这样主机就能够按照这个地址去寻找他想要通信的从机。因为我们之前就将SM2置一了,就代表现在是多机通信的模式,此时RB8就用来区分主机发送的这帧数据具体是地址还是数据。由于所有的主机他的SM2均为1,因此此时主机向所有的从机发送一帧地址数据(RB8=1),从机收到数据后与自己的地址进行对比,如果是,就将自己的SM2置零,这样就能接受主机传来的数据。
注:其实各从机还应当有一个共同的广播地址,这样就能为下一帧数据做好准备。
例:外扩I/O点灯用CD4094
首先这是外扩所以应当选用方式0,所以SCON应当设置为00H,另外这个串并转换有一个STB控制他是否出数据还是锁存,这里我们假设接P1.0。
MOV SCON,#00H
MOV A,#80H ;第一个亮
LOOP:CLR P1.0 ;先把数据锁存住
MOV SBUF,A
JNB TI,$ ;MOV就自动传出,等他传送完成
CLR TI
SETB P1.0
RR A
LJMP LOOP
这段程序如果用C51将会简便很多
甲机
乙机
我们先回顾一下,对于单片机来说,所有的片内外设通过三总线挂载到CPU上,它们分别是:数据总线;地址总线;控制总线。那么如果想要外扩也是同样的方法。
那么,外扩的地址我们需要如何确定呢?这里我们需要用到P0作为低八位,P2作为高八位。这样就可以外扩到外部64KB大小。但P0口不仅承担了作为地址总线,他还充当数据总线,所有的数据都从这个P0口走。
控制总线包含有:PSEN;WR;RD;ALE;EA
只有数据总线是双向的,因此数据总线的驱动也必须是双向的。因为单片机内部的总线驱动能力有限,所以需要增加驱动器。通常情况下P0口的驱动能力是其他端口的两倍。
注:我们需要注意到的是驱动器有两种不同的电平分别为TTL与CMOS,但CMOS能够驱动TTL电路但是反过来却不行。
双向驱动器有74LS245,他的方向可以有DIR控制,不同器件只要百度就能找到接线方法。
对于单片机来说,内部的内存是十分珍贵的,因此常常会外扩存储器。外扩存储器的最大范围与地址总线的位宽有关,例如单片机的外部地址为DPTR共16位,因此最大能扩展到64KB。就像是电脑一样以前的32位电脑最多只能装4GB内存。
RAM有如下两种:
当我们准备外扩存储时有三种扩展方式供我们选择:
芯片扩展的数量计算
那么,问题就来了,因为存储就是将数据存储到对应的存储空间,那么我们外扩了芯片,也许有很多块,那么我们如何才能将数据准确的存储到对应的单元呢,这里有两种方法:
下面我们就详细的介绍一下这三种的方法:
注:部分译码会造成地址重复的原因在于:因为有一位高位地址线或两位等情况,空缺的不管是0还是1都能选中同一个存储单元,于是这两个地址都指向同一个存储空间就造成了浪费。一个空就是两个地址对一个;两个就是4个地址对一个。。。。
存储空间大小的确定:
我们接下来看看他的时序图(ROM扩展):
我们来仔细分析一下,因为我们知道P0口是分时复用即走数据又走地址。ALE信号又是给锁存器使能用的,当他为高电平时,锁存器打开读入数据,我们从时序图上可以看到,高电平的时间段正好对应这P0口的低八位地址,也就是说当要取出数据时就用到了MOVC,ALE为高持续两个周期就是为了确定地址然后就是数据。ALE是以系统的6分频出现的。在这种情况下PSEN*就有效,他连接到输出允许端同时ALE也像上边一样,就能确定地址。因为是ROM因此只能取出数据。
注:MOVC就是从程序存储器取东西,因为他是ROM是只读存储器,因此一般用作程序存储器,存放一些精简指令集,用来需要的时候取出使用。
那么如果是可编程存储器呢,EPROM。与ROM不同的是,它能够用紫外线擦除内容重新编程。
那么这里就会有一个小问题,就是我们如何确定外扩芯片的大小呢?我们仅需要查看编号的后几位能被8除尽的,最后除出来的值就是存储器的大小。
EPROM 的工作方式:
我们可以注意到,不管是什么种类的ROM他终究都是用来存放程序指令,纵使可编程,也只是将程序指令写入其中运行的时候只参与将程序从中读出,因此他的控制指令都是MOVC,并且只有PSEN*在执行这条指令时,才会有效,才能将ROM上输出允许置一,读出数据。
RAM扩展
与ROM不同,RAM存储器需要存储数据,因此他的控制信号就是WD和RD。所以如果有两片ROM与两篇RAM来控制,因为他们的控制信号不同,分别由不同指令控制所以他们不会串。
C51的外部RAM 的定义:
xdata unsigned char databuf[256]_at_0x5000
当我们学习完之前的内容后,我们发现,虽然说单片机拥有P0-P3这4个端口,但实际上,P0与P2口被占用为数据总线与地址总线,高位会被一些特殊功能寄存器占用,所以实际上我们能够自己支配的端口资源非常有限,所以我们需要外扩输入输出口。
此外,我们还了解到CPU需要和外设之间交换数据还需要通过I/O端口,那么我们就先了解一下端口的一些概念
如果说我们只需要将数据暂存到端口373就足够了,但如果说我们需要外扩端口那么我们就需要使用8255芯片。
首先我们来看一看8255长什么样
从图中我们可以看到这款芯片的控制引脚为A0;A1;RD*;WR*;CS*,也就是说,使用这块芯片我们就能仅用两根地址线就能够外扩到24个端口。又因为数据只能从P0口走,那么,用为有锁存器,让他分时复用,所以我们可以不像外扩存储器一样需要P0P2口作为高低地址组合起来,仅需要P0就能实现了。
了解了8255的具体引脚后,我们来看一看他的三种工作方式。
当然,在了解工作方式前,我们需要了解一下他的控制字,也就是如何进行方式的选择:
注:特别的是他将PC口划分为高低分别于AB搭配在一起
我们知道了如何选择方式后,我们就分别说一下这三方式是什么
下面我们就举例:
首先,我们需要控制这个芯片的输入还是输出将他设定完毕,之后才是控制各个端口。
因为控制所以A0A1为11,将空位用一补全就是7FH,当然,如果全用0或者其他你想用的01组合都是可以的,因为他们不起作用,所得到的数字就是控制地址。其次,我们看到A是输入口,B是输出口。我们只需要最普通的方式0就可以。具体代码如下
MOV R0,#7FH
MOV A,#90H
MOVX @R0,A ;当执行到这个时,ALE打开将控制字存入锁存器
LOOP:MOV R0,#7CH ;A口
MOVX A,@R0 ;读取外设信息
INC R0
MOVX @R0,A ;点灯
SJMP LOOP