前言
本篇博客是根据通篇学完程启明的单片机原理及应用后,根据自己的理解进行的梳理,复盘。
一、什么是单片机
单片机顾名思义就是一将基础硬件集成到一块芯片上。通常单片机由CPU,ROM,RAM,I/O,定时器,计数器,中断,总线组成。单片机的英文缩写为MCU(micro control unit)或EMCU(embedded micro control unit)。它们之间的关系如下图所示,其中实线代表数据信号,虚线代表控制信号。
单片机从发行至今有许多的版本,但他们无非就是在同一架构下,例如挂载了更多的存储芯片等。我们可以讲他想象成电脑,当内存不在能满足我们的要求时我们可以在原有的基础上更换或增加内存条同理。
8031到8051到8751他们都是内存的升级从无片内ROM一路升级到EPROM。结尾为2的分别就是在1的基础上的性能升级版,如增加RAM,定时器,中断等。
二、硬件结构(51)
1.常用硬件与引脚介绍
首先,我们需要了解的是单片机内部是通过三总线互相连接的,他们分别是数据总线;控制总线;地址总线。
8051中含有:
- 5个中断:XTXTS
- 看门狗:他的实质就是一个计数器,用于防止程序跑飞或者陷入死循环。看门狗溢出会产生96个时钟的高电平。
- 特殊功能寄存器:对片内各个部件管理,控制和监视。
众所周知,单片机光有这些部件还不能运行还需要给他外加一个晶振,相当于给他加上一个心脏,他们就能运行了,通常有如下两种方法:
- 外接晶振,需要用到,这样就需要用到XTAL非17和18脚
- 外接时钟脉冲源,这样就只需要一个引脚就行了
其次,单片机不仅片内有片内存储器 还能外扩存储器,那如何选择这些存储器呢?这就需要用到EA非,当他为1是片内加片外,片外与片内地址相重复的部分将不被使用,当他为0时,则全部使用片外。因此片内片外的存储单元地址可以相同,但他们实际上不可能会冲突。
2.常用SFR的介绍
- PC:程序寄存器,能够指向吓一条需要执行的程序的地址。例如DPTR有16位,那么PC最大范围就是64KB。
- ACC:累加器,他相当于是一个中转站,当数据需要进行运算操作时,就一定会经过ACC。
- 寄存器B:常用于乘除法,乘法计算后B存放高八位,A存放低八位;除法时,A放整数位,B放小数位。
- PSW:程序状态字寄存器,他的格式如下: Cy-Ac-F0-RS1-RS0-OV-P, 其中Cy与Ac代表了进位标志;RS0与RS1代表着寄存器选择,共四组;OV代表了溢出位,P代表了奇偶标志位(奇1偶0)。
- SP:堆栈指针,我们可以将他想象成一个弹夹,先压入的数据最后才会弹出,因此,堆栈可以用做少量数据的对调类似于swap函数,当然她的主要功能是现场保护。
3.存储器结构
通常存储器有如下两种结构:
- 普林斯顿结构:程序存储器与数据存储器在一起
- 哈佛结构:程序存储器与数据存储器相互独立
一个单片机的存储器是由程序存储器+数据存储起+SFR共同组成。他们的寻址方式:
- 片内:使用PC
- 片外:使用DPTR
之前我们提到过RS0与RS1是用来选择不同的寄存器,他实质上就是将片内的低128B的内存空间分为四个区域,进行选择,他们与其他存储空间不同的是,他们能够进行位操作而其余的只能按字节存取。需要注意的是,程序运行时,有且只有一组作为当前工作寄存器。
如何判断一个寄存器是否能进行位寻址:看他地址末尾是否为0或者8。
4.I/O
我们可以从单片机的板子上看到单片机有P0,P1,P2,P3口,这些端口复位后都为高电平,但这些并不是我们都能够使用的,能供用户使用的只有P1和部分P3口。
当我们使用端口时,我们很有可能使用到串口,因此,将简单介绍一下单片机的几个周期:
- 时钟周期:T=1/f(T为时钟周期,f为晶振频率)
- 机器周期:12*T
- 指令周期:执行一条指令所需的时间(乘除法最长为4个机器周期)
三.指令系统
对于单片机编程主要有汇编与C51两种方法,C51语言与C语言相似,所以这里将简介一些常用的汇编指令。 首先按指令共能能够将各指令划分为:数据传送指令;算术操作指令;控制转移指令与位操作指令。
指令占用字节数的一个总结(不一定完整):
- 寄+寄===>1
- 寄+8地===>2
- 8地+8地===>3
- 寄+16地===>3
- 单8地===>2
一些常用容易混淆指令的介绍:
- 绝对XX指令,该指令只能进行2KB范围的寻址,因为他的高五位是被固定的。
- 互换指令:1.swap:他是同一个字节内部的交换(高八与低八)而XCH是两个字节之间的交换,XCHD则是两个字节低八位交换,高八位不变。
- 对于能够进行位操作的指令采用:寄存器地址.某一位。例如:20H.5就是指20H的第五位。
寻址方式的介绍:立即数(#);直接;寄存器间接;相对;位;基地址加变地址;寄存器。寻址方式越多单片机的性能就越强。
接下来就是一些重要指令的详细学习。
1.数据转移指令
我们知道数据传送指令有: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。
数据转移指令的另一个功能就是可以用作查表: 我们先想象一下,我们手里有一组数据,而我们有时需要的仅仅是其中的某一位数据,那我们是不是就得要知道该数据在这个数组中的具体位置。同理查表法和这个原理相同,不过他有两种索引方式:
- A+PC
- A+DPTR
这两种方式都能将指针指向对应位置的数据,但是他们也各有不同。
对于方法一,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占用的一个字节地址所引起的偏移量。
2.堆栈的指令
操作堆栈的指令就只有两条,压入,弹出,PUSH,POP,他们的作用分别如下:
- PUSH:先SP加一再装入数据
- POP:先出数据再SP减一
下面,我们就通过一个例子分析仪一下堆栈的应用:
压栈:
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
3.常用算数指令
首先在这里补充一个知识点,由于二进制码不直观,所以引入了一个叫做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语言他们之间的关系:
- 机器码:就是010101的组合,他们执行效率高但是可读性极低
- 汇编语言:采用一些英文作为注记符,他的执行效率也高,可读性相对较低
- C语言:执行效率低但是可读性高
下面我们将通过一些例子更为直观的理解汇编语言。
1.单分支程序
例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
当我们遇到这种稍微复杂一点的程序时,我们可以先按照自己的想法列些一个流程,这样对于后续的编写就会容易很多。
具体流程:
- 确定程序的起始位置
- 从30H取出
- 数值转换(高位*10加上低位)
- 输出
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使用的时候,只能从小到大顺序列写,不能交叉。
2.多分枝程序
例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正)
3. 循环程序
循环程序的组成:
- 循环初始化
- 循环主体
- 循环控制
- 循环结束
例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
流程:
- 确定程序位置
- 从40H开始逐个比较
- 数据输出到80H
ORG 2000H
MOV A,#3FH
MOV R1,#0FFH
LOOP:INC A
INC R1
CJNE A,#0AH,LOOP
MOV 80H,R1
SJMP $
4.定时程序
定时程序有两种方法,一种是定时器,他到预设时间后就会发出一个中断;另一种就是利用软件进行延时。他们之间存在些许区别:定时器会有时间长度的限制,但如果软件延时他可以想多就就多久,多重嵌套就能够实现,但软件延时中绝不允许有中断,因为这会打断这个延时去做别的事,从而影响延时的准确性。下面我们将会分别介绍这两种方式。
1.定时器
从本质上来说,定时器与计数器都是同一个,他们都是对系统脉冲进行计数,但不能对ALE计数,因为他在MOVX指令时,会缺少一拍。
TMOD的格式:GATE C/T M1 M0
- GATE门控位,用于测量某一脉冲宽度时使用,置于1。
- C/T,定时器或者计数器的选择:1计数,0定时
- M1,M0:模式选择,方式0是为了兼容以前的单片机,共13位;方式一,共16位;方式二,当计数结束时自动填充初值;方式三,如果计时器不够用的情况下采用。
当作为波特发生器时,常用方式二!
定时公式 原理:对12分频后的晶振频率进行计数 因此不同的方式他们的计算公式不同,主要区别在于初值的计算。
a表示位数,x表示初值 计时器的意思就是当你给定一个初值后他就会每当来一个机器周期就会记一次数,直到记满溢出发中断,于是这公式的含义就显而易见了。初值算出后就将它分为高低八位分别填入TH0,TL0。如果像方式二只有八位就高低相同就可以了。
定时器的程序设计:
- 设中断
- 填入初值(注意单位的一致性)
- 打开定时器(TCON)
- 计数完成产生中断
补充1:各中断的入口 注2:并不是中断想进就进他是有条件的
- 有中断标志位
- 中断发生时,不能有高级中断,同级中断也要按查询顺序排列
- 当前语句执行完毕,否则需要执行完成
- 如果正好在执行RET或IP这样的语句,需要额外再执行一条语句
2.软件延时
总结过定时器后我们发现,定时器只能定时一小段时间,一但溢出就会产生中断,而延时程序一旦有了中断就会影响延时的精确,因此对于需要长时间的定时就需要使用到软件延时。
原理:不断重复某一条无用的指令,因为我们知道每一条指令都有他的指令周期,因此,我们就可以重复他我们想要的次数从而达到延时的目的。
注:因为涉及到指令计数,他会使用到累加器ACC,而ACC最大八位,因此最多只能记256次,于是,如果超出次数就需要使用嵌套。但这种方法的精确度不高,因为不只是这条重复的指令会占用时间,比如使用到的其他跳转指令也会占用一定的时间,需要将她们减去,其实如果基数够大,这几纳秒的时间也能忽略不计。
5.子程序
为了使我们的程序简洁方便,容易阅读,因此就引入子程序的概念,首先我们就能够预先完成某一个功能的代码,当我们在程序中需要使用到这个共能时我们只需要跳转到该子程序即可。当我们写程序的时候,我喜欢将他们放在主程序的后边,这样只有跳转指令才能运行到这段子程序。
注:当执行跳转指令时,不管是什么跳转,都会自动执行现场保护,因此,有跳转就得有堆栈,用来存当前的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
6.查表程序
之前介绍过查表程序可分为以下两种方式:
- MOVC A,@A+PC
- MOVC A,@A+DPTR
他们两的唯一区别就是方式一需要考虑偏移量,而方式二就能放在任意位置,方式二较为简单,因此继续展示方式一。
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设备之间增加一个接口电路呢,原因有如下两点:
- CPU的运行速度非常的快,而I/O口的速度相对较慢,相对快速的CPU发出指令后,如果慢速的I/O不能及时收到数据,那么,这一帧数据就会丢失;相对的如果I/O要传入数据,CPU需要等待他准备完成并传完数据,就会造成时间上的浪费。
- 可能存在输入输出信号的形式不一致,那就需要这接口电路进行信号的转换,最常见的如电压电流转换。
I/O的一些特性:
- I/O信号有两种:面向CPU或者面向外设
- 接口电路芯片:三种,通用;专用面向CPU/外设
- 编址:首先要把送或取的地址通过AB送到CPU,再从CPU发出读写控制信号,进行操作
- 端口与存储器统一编制:我们前面说过,因为CPU与外设之间会存在速度不匹配的情况,所以端口就需要有锁存器存取该数值。统一编址就相当于把外设端口同时也看做一个存储单元(51单片机就采用此编址通过WR与RD来进行读写操作)
- 独立编址,与统一编址不同之处在于他的控制信号不同,且需要用到CPU寄存器进行中转。
读取外设的指令:MOVX
数据传送的方式:程序控制;直接存储器读写;I/O处理机 其中程序控制有如下三种方式:
- 无条件传送:默认外设已经准备好,常用于例如开关或者灯这样能够即时动作的器件
- 查询传送:当需要数据时,CPU就会询问外设是否准备好,直到外设准备好后就能接收数据,这种方法每次执行都需要查询一边,会造成时间上的浪费。
- 中断传送:当外设已经准备好数据,就会发出中断,这种方法效率高
因为51只有程序控制,因此,我们将详细介绍一下这三种方式。
1.无条件传送方式
数据的输入输出分别有两个缓冲器,当一个工作时,另一个就处于高阻状态,因此他们能够共用一个地址。 使用前提,必须提前通过软件延时将两者的速度匹配。
例:外部缓冲器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 ~~~~~~~ ;包含着各数字的数码管的二进制码
2.查询方式
查询方式需要两个端口及其原因:当数据准备好了才发出准备好了的状态,当传送完后自动将状态位清除。因此需要一个状态端口以及数据端口。
具体步骤如下:
- CPU向接口发送命令,要求进行数据的传输
- CPU从状态端口读取状态字,判断外设是否已经准备好数据
- 等待外设准备完毕
- 传输结束后,清零状态字(0未准备好,1准备就绪)
流程图: 从流程图我们能够看出,虽然查询方式的数据传输准确性得到了提升,但是,这个不断查询,直到外设准备完成会拖慢程序的速度。因此他仅适用于对于速度要求不高的场景。
**注:**一组八位的数据,其中一位是状态位,对于输入,位于最后一位,对于输出位于第一位。又因为只有SFR和一些特殊的寄存器能够进行位操作,因此我们可以使用带标志位的移位操作将状态位传递给Cy位就能够进行单独的判断。
3.中断传送
这将会在终端章节详细介绍,大致流程就是当数据准备好后给外部中断信号发送中断信息,请求CPU进行处理。
六.中断
中断的过程:中断请求;中断排队;中断响应;中断处理;中断返回(reti) 下面那我们就介绍一下中断的控制字:
- TCON:(启动就是将这个预备起来当有信号传来就立即开始计数)
- SCON:SCON只有RI与TI,他们分别代表,当数据装入发送缓冲器或者接收缓冲器,就将他们分别置一,启动发送,传送完成后必须通过软件清零。
- IE:中断允许:
中断响应的6个条件:
- 总中断打开
- 该中断对应的中断请求(TCON/SCON)为1
- 相对应的中断源打开
- 无同级或更高级的中断正在进行
- 当前处于所执行指令的最后一个机器周期
- 正在执行的程序不是RET/IE/IP,否则除了执行完这一句还要额外再执行下一句
正因为第六条,我们可以知道最短的以及最长的中断响应时间:
- 最短3个周期:中断请求1+长跳转2
- 最长八个:RETI或其他的2+正好乘除4+长跳转2
在程序编写的时候,如果是汇编,只要进入到相应的地址就相当于是进入了中断;对于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
例:利用外中断脉冲,来一次亮个灯
- 假设fosc为12,则一个机器周期为1微秒
- 那我们就选取最长的乘法 需要四个机器周期
- 两次嵌套就能足够时间看到灯的亮灭
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
我们知道单片机的各种资源都是十分有限的,但如果我们外部中断需要有三个的情况我们应当怎么办呢?这个时候,如果我们的定时计数器的中断没有被占用,我们就能利用他们扩展外部中断,具体做法如下所示:
- 利用定时器外扩:我们将外设中断信号赋给定时器的启动位,并将计时器的初值设满,这样子一计时定时器就会溢出发出中断。
- 计数器:与定时器方式相同也将计数值拉满,其实就和定时器是一个道理,一计数就溢出发生中断。
- 利用串行口:方式同上
- 查询方式:就像是外扩芯片一样,利用片选线确定是什么中断,但这个方法有一个缺点就是会占用P口。
七.定时计数器
我们常用的定时方式有:
-
软件延时:用于长时间的延时,精度不高 -
硬件延时:就类似以前的电风扇的定时旋钮 -
可编程定时计数器 定时计数器他的模式选择由TMOD控制
这里M0,M1可组成如下四种方式:
- 方式0:为了兼容从前的单片机,所以只有13位(16.384ms)
- 方式1:16位,但是计数满后需要手动填充初值(131.072ms)
- 方式2:8位,他的高八位用于溢出后自动填充初值,常用于波特发生器(512微秒)
- 方式3:T0分为两个8位计数器,T1停止计数
TCON在中断章节已经介绍过了,这里将不再赘述。下面我们就这四种方式进行介绍。
1.方式0/1
其实方式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 $
2.方式2
其实方式二唯一特别之处就是能够自动填充初值。因此由于这个特性就能够作为波特率发生器。 那么我们就举例一个最普通的定时器例子: 例:时钟频率为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.方式3
方式3他比较奇特,他采用将T0划分为两个8位的计数器,停用T1,T0可以按正常模式选择进行设置,就相当于是一个不会自动填充的方式二下的定时器;另一个八位只能借用T1的控制字来进行计数。
八.串口
在进行学习前,我们先了解一下什么是串行,什么是并行。从字面来看,串行就是一串,也就是数据一位一位出;而并行就是所有数据并排出,就是一次全出完。因为单片机出数据是串行通讯,因此我们就重点学习他的两种通信方式。
1.异步通信
定义:通信的发送与设备的接收分别使用他们各自的时钟,但这并不意味着可以传输波特率不同。这种方式的缺点就是传输效率不高。
数据格式:空闲位(高)+起始位(低)+内容+检验位(例如奇偶也可以没有)+停止位(高)
2.同步通信
定义:发送方与接收方使用同一个时钟信号。特点:传输的字符间不留空隙。
同步传输有两种不同的数据格式:
- 面向字符:
- 面向位流:这种方法能够传输任意多的数据,但信息位每五个连续的1之间会自动插入0,这是由单片机自动产生,也会由他自动消除。
3.串行通信
串行通信有如下三种传输方向:
- 单工:单一方向的传输(一根通信线)
- 半双工:能来回但是不能同时发生(一根通信线)
- 全双工:能够同时来回(两根)
如果遇到超远距离的信号传输,就像是我们电脑上网一样,有一个东西叫做调制解调器。 调制解调器就是将计算机发出的数字信号转换为通信电缆能够传输的模拟量信号,他们之间的关系如下图所示:
串行通信的错误校验 刚才我们在学习数据格式的时候我们就看到了在一帧完整的数据中会包含有检验位,那么,在单片机中存在有如下三种校验方式:
- 奇偶校验:在数据位尾随一个一位的校验位,就像是PSW的P
- 代码和校验:发送方将所发数据求和后产生一个字节的校验位尾随在数据块末尾
- 循环冗余校验:用一个数去除数据块将余数作为校验码放在数据块尾
串行的传输速率与距离
- 传输速率
首先我们来认识一下传输速率的单位也就是我们常说的波特率。 波特率:每秒钟调制信号的变化次数 **注:**只有在数字信号下波特率才等于比特率 - 传输距离
通常情况下传输距离越短则传输速率越快,在几种传输协议下,他们的距离从小到大排列为:
首先我们介绍一下,串行接口是用来干什么的:
- 数据格式化:就是将数据按我们之前介绍过的转换成相应的形式
- 进行串并转换:CPU处理并行数据,而串口进来的数据都是串行的,所以需要串并转化
- 控制数据传输速率:也就是设置波特率
- 进行传送错误检测:(校验位自动生成自动检验)
那么接下来我们就认识一下各传输协议的特点:
RS-232:
- 距离短速率低(15m,20kb/s)
- 有电平偏移(由收发双方电位差引起)
- 抗干扰能力差(为了提高信噪比需要加入较大的电压摆幅)
RS-422A:
- 收发双方各自接地,不会引起电平偏移
- 收发各有两条线,不适合长距离传输但是抗干扰能力强
- 传输距离长,速率快(1219m,10Mb/s)
RS-485: 他与422非常相似只是将收发双线砍成单线,常用于制作站点通讯,只是只能有一个主机,其余均为从机,并且只有主机才能给从机发送信息,从机之间互不交流。
4.内部串行接口
单片机内部串口的结构图如下所示: 串口就是由发送器电路+接收电路+控制电路组成。 由结构图我们能够发现内部包含有两个相同地址但是物理上独立的两个缓冲器,他们分别叫发送缓冲器与接收缓冲器。 那么为什么这两个寄存器需要使用同一个地址呢? 由于单片机的CPU对发送与接收寄存器不能同时进行操作所以给这两个缓冲寄存器赋予同一个特殊功能寄存器地址。也就是说,这个CPU是单线程的,他一次只能做一件事,在全双工的情况下,如果不是同一个地址,做不到同时收发。
注:在RXD前面还有一个移位寄存器,构成双缓冲结构可以避免重帧现象即在下一帧数据来时,前一帧数据还没被读走。发送方不需要,因为发送方是CPU主动的而接收方是被动的。
5.串行的控制寄存器
如同定时计数器的控制一样,串口也有为SCON: 工作方式:
-
方式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的波特率:
6.多机通讯
多机通讯通常使用方式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
7.双机通讯
这段程序如果用C51将会简便很多
甲机
- 设置波特率,一般使用T1方式2,因为自动填充不会引起误差
- 设置串口,方式1/3都行,方式二波特率是固定的,也不是不能用
- 进入主函数如果使用C51,就用指针一位一位往里面送,直到发出中断表明发完
乙机
- 设置波特率,应当与之保持同步
- 设置串口作为接收方应当将接受允许位打开
- 因为作为接收方,因此只需要将SBUF中的数据一位一位读入就行
九.并行扩展方法
1.概述
我们先回顾一下,对于单片机来说,所有的片内外设通过三总线挂载到CPU上,它们分别是:数据总线;地址总线;控制总线。那么如果想要外扩也是同样的方法。
那么,外扩的地址我们需要如何确定呢?这里我们需要用到P0作为低八位,P2作为高八位。这样就可以外扩到外部64KB大小。但P0口不仅承担了作为地址总线,他还充当数据总线,所有的数据都从这个P0口走。
控制总线包含有:PSEN;WR;RD;ALE;EA
只有数据总线是双向的,因此数据总线的驱动也必须是双向的。因为单片机内部的总线驱动能力有限,所以需要增加驱动器。通常情况下P0口的驱动能力是其他端口的两倍。 注:我们需要注意到的是驱动器有两种不同的电平分别为TTL与CMOS,但CMOS能够驱动TTL电路但是反过来却不行。
双向驱动器有74LS245,他的方向可以有DIR控制,不同器件只要百度就能找到接线方法。
2.存储扩展
对于单片机来说,内部的内存是十分珍贵的,因此常常会外扩存储器。外扩存储器的最大范围与地址总线的位宽有关,例如单片机的外部地址为DPTR共16位,因此最大能扩展到64KB。就像是电脑一样以前的32位电脑最多只能装4GB内存。
RAM有如下两种:
- SRAM:静态,不会丢失,单片机常用他扩展外部RAM,相对功耗较高。
- DRAM:他需要外接动态刷新电路,他常用在例如时钟这样要求功耗极低的地方,例如电脑主板上的那块小电池,特就是接到了时钟电路,为电脑能够在关机的时候他的时钟还是正确的。
当我们准备外扩存储时有三种扩展方式供我们选择:
- 位扩展:增加存储器的字长。就是说如果我的CPU是能够进行8位的寻址但是这个存储芯片只有4位的宽度,那么我们就将他替换成8位宽度的存储器,也就是换芯片。
- 字扩展:扩展存储容量。换句话来说就是我这个电脑原本可以插128G的内存,现在我只插了16G,也就是没有用满。
- 字位扩展:将上述两个混合起来。
芯片扩展的数量计算
- 若字长满足且相等:
- 若字长不满足,那么不仅需要位也需要字扩展:
那么,问题就来了,因为存储就是将数据存储到对应的存储空间,那么我们外扩了芯片,也许有很多块,那么我们如何才能将数据准确的存储到对应的单元呢,这里有两种方法:
- 字选法:这种方法看着就像是选择一个芯片内的不同地址,就是将低位地址线接到芯片上
- 片选法:这就是选择那么多外扩芯片的某一片他有线选;译码器;直接地
下面我们就详细的介绍一下这三种的方法:
- 线选法:就是直接高位地址线不经过译码直接接各芯片的片选端,简单方便
- 直接地:就是一直选中某一芯片,也就是当只有一个芯片时就能用这个方法
- 译码器:译码器又有全译码与部分译码之分。全译码就是全部高位地址参与译码剩余的参与字选。部分译码就是高位缺掉了一位,也就是参与译码的高位不是从头开始,或者中间跳过一位这样的。因此他的数据组成就是片选+字选组成完整的一组数据。
注:部分译码会造成地址重复的原因在于:因为有一位高位地址线或两位等情况,空缺的不管是0还是1都能选中同一个存储单元,于是这两个地址都指向同一个存储空间就造成了浪费。一个空就是两个地址对一个;两个就是4个地址对一个。。。。
存储空间大小的确定:
- 确定片选的例如110
- 片选信号就固定下来,那么后面全都跟0就是起始地址,全都跟1就是结束地址,他们之间所围成的范围就是他的存储空间大小
我们接下来看看他的时序图(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
3.I/O扩展
当我们学习完之前的内容后,我们发现,虽然说单片机拥有P0-P3这4个端口,但实际上,P0与P2口被占用为数据总线与地址总线,高位会被一些特殊功能寄存器占用,所以实际上我们能够自己支配的端口资源非常有限,所以我们需要外扩输入输出口。
此外,我们还了解到CPU需要和外设之间交换数据还需要通过I/O端口,那么我们就先了解一下端口的一些概念
- 功能要求(速度匹配;输出数据锁存;输入数据三态缓冲)
其实我感觉这三个功能都是为了速度匹配,因为CPU的速度很快,外设的速度就相对来说比较缓慢。也就是说当我CPU来了数据,外设还没来得及接收CPU就跑了,就相当于是这帧数据丢失了。相对的外设有数据要过来取,那么首先CPU通过地址总线找到这相应的位置,在通过数据总线等待数据,但由于外设很慢,这势必就造成时间上的浪费,所以就要存到端口寄存器,等数据出来后发个中断让CPU来取就很高效。 - 端口的编址
端口由独立编址与统一编址,独立编址就是将地址与存储器空间分开编址,这样觉需要CPU将这两部分映射起来比较麻烦不那么用,常用的统一编址我们就可以将每一个端口想象成一个单元的RAM。我们联想一下存储器,是不是就有两种结构,哈佛结构与普林斯顿结构,他们就是决定程序存储器单独分开还是混合在一起。 - 端口数据传送方式
第一种同步传送,也叫做无条件传送。这种方法常用于点灯或者步进电机这样的,他的特点就是外设的速度是恒定的,我们只需要匹配完外设的速度就能够实现数据传递。 第二异步传送,该方式也就是查询方式,也就是我在这个端口一直询问外设好了没,直到外设对我说好了,将数据交给我,才算完成。这种方式效率很低。 第三种就是中断传送方式。这种方式传递效率极高,也就是说当外设准备好数据存到了端口后就发出一个中断,通知CPU来收集数据,当没有数据的时候,CPU不需要执行查询任务,节约CPU资源。
4.常用的扩展芯片
如果说我们只需要将数据暂存到端口373就足够了,但如果说我们需要外扩端口那么我们就需要使用8255芯片。 首先我们来看一看8255长什么样 从图中我们可以看到这款芯片的控制引脚为A0;A1;RD*;WR*;CS*,也就是说,使用这块芯片我们就能仅用两根地址线就能够外扩到24个端口。又因为数据只能从P0口走,那么,用为有锁存器,让他分时复用,所以我们可以不像外扩存储器一样需要P0P2口作为高低地址组合起来,仅需要P0就能实现了。
了解了8255的具体引脚后,我们来看一看他的三种工作方式。 当然,在了解工作方式前,我们需要了解一下他的控制字,也就是如何进行方式的选择: 注:特别的是他将PC口划分为高低分别于AB搭配在一起 我们知道了如何选择方式后,我们就分别说一下这三方式是什么
- 方式0:基本输入输出
- 方式1:应答输入输出,其中C口作为应答信号
- 方式2:双向传输,这个方式仅有A有
下面我们就举例: 首先,我们需要控制这个芯片的输入还是输出将他设定完毕,之后才是控制各个端口。 因为控制所以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
|