2015年(29)
分类: 嵌入式
2015-04-12 20:50:40
P0口的结构如下图所示
其中输入缓冲器是一个三态门,三态门和二极管的区别在于,三态门比二极管多一个控制端,所以三态门作为输出时,有3中状态,输出高电平、低电平和高阻态。
下面来分析当控制信号为0时,此时,V1为断开状态,引脚P0.x为开漏输出。
A、P0口作为GPIO输入
CPU通过控制”读锁存器”控制端和”读引脚“控制端来控制输入到内部总线上的数据来源。
如果总线数据从锁存器上得到,叫做读端口;如果总线数据从引脚上得到,叫做读引脚。
读端口的操作是由单片机自动完成的,直白点说,就是读锁存器的控制对于程序员是透明的,这个控制过程在51内核的指令集中没有提供接口。
那CPU何时使读锁存器有效呢,即CPU何时读端口?8051内核单片机对于P0-P3口的输入有如下约定:凡是属于”读-修改-写“方式的指令,从锁存器读入信号,其他指令则从端口引脚线上读入信号。
那么,什么是”读-修改-写“方式的指令?
”读-修改-写”指令的特点是,从端口输入信号(读),在单片机上加以运算(修改),最后输出到该端口(写);比如:
ANL P0,#立即数; P0与立即数相与后,回传到P0
ORL P0,A; P0与A寄存器中的值相与后,回传到P0
INC P1; P0自增
DEC P3; P3自减
CPL P2; P2按位取反
可以看出,这些操作都是将P0端口看做一个寄存器在操作,将P0做处理后,重新传给P0。
那为什么在这些操作中就不可以读引脚的值呢?
那只有一种情况,就是读引脚的值可能和读端口的值存在不同。
举个例子,当P0口作为普通IO口使用,P0口某个引脚先输出高电平,MOS管V2处于断开状态,由于上拉电阻的作用,此时,输出引脚为高电平,然而,由于外部电路也可能将该引脚的电平拉低,所以,此时,引脚上的电平为低,而锁存器输出的电平Q为高,这是很有可能发生的,别忘了,P0-P3作为GPIO口时,是准双向口,在读取这些端口的引脚数据时,首先要对这些位写1。
还不信,好吧,下面给出给位看官想要的证据:
在Keil中编写下列程序,够简单的吧:
#include
int main()
{
unsigned char a;
P0 = 0x0f;
a = P0;
P0++;
}
下面进行单步调试,
上图所示,先解释一下右侧的对话框是怎么出来的,点击菜单栏”Peripherals(外设)“->”I/0-Ports(IO端口)”->”Port0”,看这个对话框,很有意思哈,由对话框标题可以知道P0口是一个并行口,P0其实就是代表了端口的值,而ins,我自己的理解是inputs,就是引脚输入,如果你点击P0后面的复选框,ins的值也会跟着变化,而如果你点击ins复选框,P0是不会随之变化的,这和我们的工作原理图就对应起来了。
如下图
你可以将输出为1 的端口拉低,P0作为普通输入时我们就是这么干的。
但是,如果你打算将输出为低电平拉高,如上图,你点击ins的bit4-bit7中的任意一个,很不幸,会出现下列错误:
单片机的意思很明显,如果输出为0,V2为导通状态,引脚直接和GND相连,此时,如果你打算输入高电平,直接和GND短路,单片机发出抗议,你丫懂不懂,是不是想烧死我?这也进一步说明,如果要输入,先要往P0口写1。
Ok,继续下一步,执行完a = P0,结果如下图所示:
很明显,如果是内存中某个变量需要得到P0的输入值,它会去取引脚上的值,因为,这种语句并不是”读-修改-写指令”。
Ok,继续下去指令,在执行完P0++后,结果如下图所示:
可以看出,P0口以端口中存储的值作为原值进行操作,并进行了对引脚的写操作。
分析完了,那还有一个问题,如果第三句是”a = P0++”呢,ok,我们来试试,其实学习的过程就是探索的过程,从书本上得来的太容易了,容易得来的东西,大家就不会珍惜,所以,相信一句话” 纸上得来终觉浅 绝知此事要躬行“。
在执行完”a = P0++”后,结果如下图:
现在a的值为3,也就是说,a也是先得到P0引脚上的值,然后P0再进行自增操作的。即”a = P0++;”等价于“a=P0;P0+=1;”
现在,基本上将”端口输入”和“引脚输入”讲清楚了,再来总结一下:
总结:端口输入,P0的值改变时,使用的是端口输入,如果是内存中的变量希望得到P0或者进过P0运算后得到的值,使用的是引脚输入。
其实,这些码农们都不用关心,因为,你根本不能控制”读锁存器”和”读引脚”这两个控制端口,ok,你说C语言不行,那我用汇编总行吧;很抱歉,汇编也不行,因为干这个是IC电路设计人员,除非您老打算自己出个芯片,或者用FPGA写个IP核,那所有的控制器都对您老开放,你可以为所欲为,哈哈。
讲到这里,给大家普及一下CPU的层次,有兴趣的同学可以去看《大话处理器》这本书,非常之不错。
如下图所示:
其中,ISA是软件与硬件的交接处。而ISA通常是由CPU厂商以汇编指令的形式提供,为什么说汇编语言与硬件关系密切,移植行差,就是因为它所处的分层模型的位置比较特殊。ISA定义了该CPU有哪些功能,比如,有add功能,sub功能等等,但是这些加操作和减操作如何实现,那就是下一层微构架的事了,微构架是对指令集功能如何实现的设计,设计完成后,就可以由硬件厂商去生产了,也就是IC的物理实现过程,总的说来,对微构架的设计比较费脑,而物理的实现对环境与精度的要求不是一般的高,本科时候学微电子时候,虽然吊儿郎当,这制片过程中要求超净的环境还是记忆深刻的。多的不说了,有兴趣的强烈建议看看《大话处理器》,当小说茶余饭后看也挺好的。
B、P0口作为GPIO的输出
在P0口作为IO线输出时,其电路通路如下图所示:
此时,控制信号为0,内部总线为0,P0.x直接接地,能够输出低电平,如果内部总线为1,P0.x为悬空状态,此时,需要一个上拉电阻将引脚电平拉高,所以,P0作为GPIO使用时,还是需要在外围电路中配置一个上拉电阻,输出比较简单,就说这些吧。
C、P0口作为外部存储器的扩展
当控制信号为1时,输出端组成一个推挽输出的结构,此时,P0口为真正的双向口,作为地址的输出和数据的输入输出。在外部扩展RAM或ROM时,就需要使用P0作为数据和地址进行数据传递,8051有256Byte的内部RAM和4K的内部ROM,8052有8K的内部ROM。
那么问题来了,控制信号到底是由谁发出的?
先来介绍一下与扩展存储器有关的3个引脚:
/PSEN(29pin):全称为程序存储器允许输出控制端(Program Store Enable),在读外部程序存储器时,/PSEN低电平有效,以实现外部程序存储器单元的读操作。注意,该端口为51处理器对外输出端,就是给外部ROM的信号,与ROM的OE脚相接。
/EA(31pin):该端口为输入端口,用来控制使用内部还是外部的ROM,当/EA接高电平时,单片机读取内部程序存储器,例如8051的片内ROM为4KB,超过4K的部分,处理器自动到外部ROM上取,此时,可以说内部ROM和外部ROM为统一编址,外部ROM的前一部分空间被内部ROM所屏蔽;当/EA为0时,使用外部ROM,即程序开始时,就跳到外部ROM的0x00地址进行执行,此时,片内ROM就成为了摆设。
总结:/EA控制是否使用外部ROM,在读取内部ROM时,因为没外部ROM什么事,所以和外部ROM的OE端相接的/PSEN无动作;当进行外部ROM读取时,每个机器周期都会有两次动作,也就是一个周期进行两次外部ROM取指令。
ALE/PROG(30pin):在单片机外部扩展存储器时,ALE用于控制把P0口输出的低8位地址送入锁存器锁存起来,以实现低位地址和数据的隔离。
由上面可以知道,外部ROM是通过/EA和实际的地址值确定是访问外部ROM还是内部ROM,那RAM由谁决定访问对象呢?
是由伟大的程序员决定的,单片机默认用“MOVX @dptr,A”实现对外部ARM的写数据,用“MOVX A,@dptr”实现外部RAM写;MOV由于访问内部RAM。另外,MOVC是访问ROM的指令。
那在C中如何表示?
1.C51中的存储器类型 (单片机原理及应用 王景景 第78页)
data 直接寻址访问的片内RAM的低128B,访问速度快
bdata 片内RAM的可寻址区(20H~2FH),允许字节和位混合访问
idata 间接寻址访问片内RAM,允许访问全部片内RAM
pdata 用Ri间接访问的片内RAM的低256B
xdata 用DPTR间接访问的片外RAM,允许访问全部64K片外RAM
code 程序存储器ROM的64KB空间
2.C51中的预定义宏指令(81页)
放在#include
CBYTE 以字节形式对code区寻址
DBYTE 以字节形式对data区寻址
PBYTE 以字节形式对pdata区寻址
XBYTE 以字节形式对xdata区寻址
CWORD 以字形式对code区寻址
DWORD 以字形式对data区寻址
PWORD 以字形式对pdata区寻址
XWORD 以字形式对xdata区寻址
使用方法是:宏名[地址值];
下图为P0作为”地址/数据”时的结构图:
再次总结:P0口的控制信号是由处理器自动给出的,如果你用了外部存储器,并在程序中使用了读写操作,P0就会使上图中的控制信号为1。也就是说,如果你用来外部存储器,那么,您老就不要使用P0口为GPIO功能了。
功能复用,我的理解是可以在不同的时间可以转变不同的功能,而不是在同一时间使用双功能,而读指令是每个机器周期都是存在的,所以P0口的地址/数据线功能始终被占用,所以,GPIO功能就用不了了。
OK,P0口终于讲完了。