第一天 认识单片机
一、什么是单片机
什么是单片机?按照比较正规的定义,所谓单片机,就是把中央处理器、存储器、定时器、I/O接口等一些计算机功能部件集成在一块电路芯片上的微型计算机。
什么又是中央处理器、存储器、定时器和I/O接口呢?简单来说,中央处理器就是所谓的CPU,就相当于人的大脑。存储器顾名思义就是一种能存储数据的元件,比如我们写的程序就可以存放在这里面。I/O接口就是输入/输出端口,数据可以通过这样的端口输入或输出CPU。我们把这些东西整合到一块很小的芯片上,就成为了单片机。
二、单片机可以做什么
单片机用来干什么呢?用最通俗的话讲,它的作用就是实现智能控制。什么是智能控制呢?举个简单的例子,让单片机按照我自己的想法来控制一个发光二极管的亮灭就是智能控制,那么我如何来做到呢?我将发光二极管的正极分别连在单片机一个引脚上,当连正极的引脚输出高电平(可以暂时理解为高电压),连负极的引脚输出低电平(同上,低电压)时,由于二极管两端电压差,因此二极管点亮,当两个引脚都是相同电压,发光二极管熄灭。单片机如何知道我希望二极管什么时候点亮,亮多久呢?这就需要我们将自己的意愿通过编写程序下载进入单片机中进行执行,让它按照我们的意愿在引脚输出高低电平,从而控制发光二极管的亮灭。
三、单片机结构
单片机长什么样?如图所示就是一个单片机的外形,我们可以看到,单片机不过就是一块有很多脚的芯片,我们称这些“脚”叫做“引脚”。单片机就是通过这些引脚对外界进行智能控制的。
图1- 1
我们学习单片机,首先要知道这些引脚的作用是什么,如图所示,让我们一起来认识这些引脚。
1、区分芯片引脚序号
我们可以在单片机表面上找到一个凹进去的小圆坑,这个小圆坑所对应的引脚就是第1引脚,然后按逆时针方向数下去,一直到最后一个引脚。如图所示一共有40个引脚。
2、 电源引脚和时钟引脚
图中第20脚GND和第40脚Vcc为电源引脚,给单片机供电。第18脚和19脚为时钟引脚,大家可以暂时不用了解。
3、I/O口引脚
图1-2
四、学习单片机必备基础知识
1、电平的概念
什么是电平?“电平”就是指电路中两点在相同阻抗(“阻抗”可以理解为电阻)下电量的相对比值。举个例子,人们在初学“电”的时候,往往把往往把抽象的电学概念用水的具体现象进行比喻。如水流比电流、水压似电压。解释“电平”不妨如法炮制。我们用“水平”来比喻“电平”如人们常说到张某工作很有水平、李某办事水平很差。这样的话都知其含义所在。即指“张某”与“李某”相比而言。电平就是指电路中两点电量(“电量”可理解为电压)的比较。
学习单片机只需了解两种电平:高电平和低电平,我们定义单片机输入与输出的为TTL电平,其中高电平为+5V,低电平为0V。什么是TTL电平?TTL电平采用二进制规定,+5V等价于逻辑“1”,0V等价于逻辑“0”。这样一来,我们在编写程序中如果对某个引脚赋值为“1”那个引脚就输出高电平,同理,若我们赋值为“0”,对应的引脚就输出低电平。
2、二进制与十六进制 逻辑运算
大家都知道,十进制的特点是“逢十进一,借一当十”。同理,二进制的特点就是“逢二进一,借一当二”。在二进制中只有“0”和“1”,比如11100101B(“B”为二进制后缀)就是二进制。那么,二进制和十进制如何转化呢?举个例子,二进制11010011B转化为十进制就是
十六进制与二进制大同小异,十六进制为“逢十六进一,借一当十六”。十进制中0-15用十六进制表示为0-9,A,B,C,D,E,F,即十进制中10对应十六进制A,11对应B……当我们写的是十六进制时,要在前面加上0x(注意0是“零”不是字母O),举个例子,十六进制0x7f换算成二进制为“01111111”换算成十进制为715
十进制 | 二进制 | 十六进制 | 十进制 | 二进制 | 十六进制 |
0 | 0 | 0 | 8 | 1000 | 8 |
1 | 1 | 1 | 9 | 1001 | 9 |
2 | 10 | 2 | 10 | 1010 | A |
3 | 11 | 3 | 11 | 1011 | B |
4 | 100 | 4 | 12 | 1100 | C |
5 | 101 | 5 | 13 | 1101 | D |
6 | 110 | 6 | 14 | 1110 | E |
7 | 111 | 7 | 15 | 1111 | F |
表1- 1
逻辑运算
“与”运算是实现“必须都有,否则就没有”这种逻辑关系的运算,其运算符为“&”,运算规则为:0&0=0,0&1=1&0=0,1&1=1.
“或”运算是实现“只要其中之一有就有”这种逻辑关系的运算,其运算符为“|”,运算规则为:0|0=0,0|1=1|0=1,1|1=1
“非”运算是实现“求反”这种逻辑关系的运算,其运算符为“!”,运算规则为:!0=1,!1=0
3、单片机最小系统
很多初学者容易把单片机开发板误认作单片机,实际上这是不对的,单片机只是一块小小的集成芯片,但如果只是一块芯片无法实现其功能,我们要想要单片机运行起来,至少应该给它提供一个电源吧?下面所讲的单片机最小基本电路就是一个单片机能运行的最基本条件。我们平时所使用的单片机开发板,无非就是把这个基本电路和一些其他电路整合在一块电路板上,有了这些基本电路,你们自己都可以设计制作出单片机开发板。
图1- 3
在上图中,除了单片机外实际上只有三个电路,分别是电源电路、晶振电路和复位电路,下面简单介绍这三个电路,大家只需要知道其作用就行了。
1)电源电路
电源电路非常简单,就是给单片机提供电源。
2)晶振电路
晶振电路的作用就是为系统系统提供基本的时钟信号,形象的说,晶振电路就像单片机的心脏一样,如果没有晶振,单片机就停滞在那里不能一步步执行程序。
3)复位电路
复位电路就是让单片机能够进行清零复位,让单片机重新开始从头开始执行程序(从头工作),它分为上电复位和按键复位两种,也就是说,当你刚刚给单片机通电时,它就进行了一次复位,当有电时你按下复位键,它也能进行复位。
第二天 I/O口介绍及其应用
一、什么是I/O口
我们的单片机能接收到外部的输入信号(包括数据、指令等),并能对其进行处理,再输出信号,控制其外围连接电路,以实现我们想要实现的功能。但单片机怎样与外围电路进行通讯呢?这就需要用到I/O。
什么又是I/O口呢?它的中文全称叫作输入与输出端口,也就是说我们的数据是通过I/O口传输的。单片机包括四个I/O口,分别是P0,P1,P2,P3,每个口有8个引脚。什么又是引脚呢?所谓单片机的引脚,就是连接单片机内部电路和外部电路的桥梁,就像电阻的两支管脚。但单片机的引脚不止2个,我们较常用的AT89C51型号的单片机就有40个引脚(如图2.1)。
图2- 1 AT89C51单片机的引脚
以AT89C51型号的单片机为例(较常用的都是40支引脚的51单片机,但也有20,28,32,44等不同引脚数的51单片机,这些大家在以后的学习过程中也要了解,不要只见了40引脚的芯片才认为它是51单片机)。四个I/O口,共32支引脚。其余8支引脚有着其他的功能,如电源引脚(VCC,GND)、时钟引脚(XTAL1、XTAL2)等。在这儿,我们主要了解I/O口引脚。
二、I/O口的工作原理
I/O口它们是怎样工作的呢?单片机是一种数字集成芯片,而数字电路只认两种电平:高电平(二进制代码为1)、低电平(二进制代码为0)。我们就是通过给I/O口赋高低电平来控制外围电路。
举个点亮发光二极管的简单例子来说明I/O口的工作原理。大家都知道二极管具有单向导电性,只有当二极管正极电压高于负极电压,发光二极管才能发光。如图:
图2- 2 单片机控制发光二极管电路
二极管的正极连在电源上,负极接在单片机的P2.0引脚上,若此时P2.0为低电平(0V),则二极管导通,二极管发光。若P2.0为高电平(+5V),二极管不能导通,则不能发光。
从点亮一个发光二极管的例子,我们了解到,通过给单片机的一个I/O口的引脚赋高、低电平这种控制手段,我们可以使单片机控制一个简单的外围电路(发光二极管电路)。但我们怎么来控制复杂的外围电路呢?上面的例子怎么才用到32支I/O口引脚中的一支呢?4个I/O口共32个引脚,试想,一个引脚控制一个简单的外围电路,一个外围电路不外乎就两种情况,引脚为高电平或低电平。4个I/O口(32个引脚)同时用上,用排列组合的知识就可以算出,这4个I/O口控制的结果将会是多少种情况,控制的电路将会是多么的复杂,由此我们也可以看出单片机的功能是多么的强大。
上面说了这么多,我们可以将其归根到一点,就是:我们是通过给I/O口赋值(赋1或0),使I/O口输出高、低电平,从而控制外围电路。
三、怎样使用I/O口
上面我们介绍了什么是I/O口及其工作原理,下面我们就来实践演练一下。
1、先来看点亮一个发光二极管的实例,硬件连接如图
图2- 3 LED连接图
分析:二极管负极连在P1.0上,我们只需给P1.0一个低电平即可点亮它。
程序如下:
#include//52系列单片机头文件
sbitled1=P1^0;//声明单片机P1口的第一位
voidmain()//主函数
{
Led1=0;
}
也许大家看到这个简单的C程序,也是一头雾水。它为什么要声明单片机P1口的第一位P1.0呢?(注:P1.0口在C程序中是用P1^0来表示的)。为什么不直接使P1^0=0呢?这是因为我们若要控制某个发光二极管(上图是D1),也就是要控制单片机I/O口的某一位(上图是P1.0),必定要声明这一位,否则单片机不知道我们要操作的是什么东西,所以我们在程序中声明了单片机P1口的第一位(sbit是一个声明位的操作,相当于C程序中要用到整型时,用int来声明)。比如,我们想给P1.0一个高电平,我们直接给led1赋值1就行了。
由于LED正常发光时其两端的电压约为1.7V,如果不接限流电阻,LED两端的电压要么为5V,要么为0V,当5V时很可能烧坏LED,所以我们还得给它加上一个限流电阻R(一般为1K)。
2、点亮多个LED
如图,我们要控制第1, 3,5,7个亮,第2,4,6,8个灭该怎么办呢?当然,我们可以以上面的方法(单独控制一个引脚)来实现,下面我们介绍一种更为简单的方法(同时控制一个I/O口的8个引脚)。
图2- 4 单片机控制流水的电路图
P1口共8个引脚,每个引脚对应一个二进制位。8个引脚即八个二进制位。P1.0对应最低位,P1.7对应第8位。如我们对P1口赋值10111110,则P1口的第一引脚和第七引脚为低电平,其它引脚为高电平。
上面的电路图我们只画了控制外围电路部分,而单片机自身的电源电路等都没画出来,但不等于没有,因为它也需要电源给其供电才能正常工作。如上图所示,8个LED正极端已于+5V的直流电源相连(我们称之为共阳极接法),都获得了一个高电平,它们的负极端又分别与P1端口的8个引脚相连,只要我们控制P1端口八个引脚的电平输入,我们就能分别控制8个LED的亮与灭。
程序如下:
#include//52系列单片机头文件
voidmain()//主函数
{
P1=0xaa;
}
注意区别上一个C程序,这里不再对P1口的某一位进行声明,而是在主函数中直接对单片机P1口的8个引脚进行操作,“0x”表示十六进制数,转换成二进制是10101010,那么对应的发光二极管负极端获得的高低电平如表1.4。
LED | D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 |
负极端电平 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
表2- 1
这就实现了1, 3,5,7亮,2,4,6,8灭。我们将0xaa转换成十进制后为170,也可以直接对P1口进行十进制数的赋值,如“P1=170;”,其效果是一样的,只是麻烦了许多。因为无论是几进制的数,在单片机内部都是以二进制数形式存在的,只要是同一个数值的数,其在单片机内部都是用二进制的同一种形态来表示。
上面介绍了如何点亮一个发光二极管,如何控制流水灯的亮与灭。如果你能控制流水灯,使它们能按你的意愿亮与灭,可以这么说,你对单片机已经入门了。
第三天 按键原理及其应用
一、什么是按键
提到按键大家都不会陌生,如图所示为51单片机开发板上的按键实物图和电路图,它可以通过跳帽(就是一根短接线)切换成独立按键或矩阵键盘两种模式,J11是由三根插针(图中标号1、2、3)组成。当我们使用独立按键时,应该把跳帽跳到左端(相当于让1、2短接),当我们使用矩阵键盘时,应该把跳帽跳到右端(相当于让2、3短接)。
图3- 1 按键实物图
图3- 2 按键电路图
大家可能会问,什么又是独立按键和矩阵按键呢?当我们使用独立按键时,只有S1、S2、S3、S4四个按键能够工作,其他按键是不工作的。当我们需要多于四个按键的时候,我们可以使用矩阵键盘,也就是一共4*4=16个按键可以工作。当然,矩阵键盘的程序要复杂的多。
二、按键是如何工作的
按键原理非常简单,当你用手按下时开关闭合,线路导通,松手时开关断开,线路断开。它是如何与单片机联系起来,达到控制的目的呢?这里我们就需要按键检测程序,我们知道,I/O口既可以做输入口又可以做输出口,在这里我们就要用到其输入功能,我们可以把按键的一端接地,另一端与单片机某个I/O口相连,开始时先给该I/O口赋一高电平(实际上如果没有对I/O口赋值,I/O口都默认为高电平),然后让单片机不断检测该I/O口是否变为低电平,当按键闭合时,即相当于该I/O口通过按键与地相连,变为低电平,一旦程序检测到I/O口变为低电平则说明按键被按下,然后执行相应的指令。
按键连接方法很简单,如图所示,按键一端接地,另一端接与单片机任一I/O口相连,按键在被按下时,其触电电压变化过程如图
图3- 3
从图中可以看出,在实际情况下,我们在按下和释放按键的瞬间都会有抖动现象,抖动时间一般在5-10ms。在抖动的这段时间里电压是不稳定的,因此单片机在检测键盘是否按下时都要加上去抖动操作,也就是把抖动时间给排除掉,一般我们用软件延时的方法就能解决这个问题。在编写按键程序时,需要在检测按下时加入去抖动的延时函数。
三、如何编写按键检测程序
如图所示为按键检测流程图
开始 |
延时 |
检测是否有键按下 |
检测是否有键按下 |
执行相应程序 |
Y |
Y |
N |
N |
四、按键检测实例程序
图3- 4 按键控制LED电路图
如图3-4所示是一个用S1、S2两个按键控制LED亮灭的电路仿真图,它实现的功能是:按下S1,LED亮,按下S2,LED灭。
我们结合这个电路的程序,让大家对按键有一个基本的认识。程序如下:
#include//包含头文件,一般情况不需要改动,写在程序最前面
sbitS1=P1^0;//定义按键S1位置,将S1与P1.0口相连
sbit S2=P1^1;//定义按键S2位置,将S2与P1.1口相连
sbit LED1=P2^0;//定义LED1位置,将其与P2.0相连
void delay(unsigned int i)
{
while(i--);//循环i次,当i减小到0时跳出循环
}
main()
{
while(1)
{
if(S1==0)//如果S1等于0,说明按键1被按下
{
delay(1000);//进行去抖动处理
if(S1==0)LED1=0;//检测按键确实按下,点亮LED1
}
if(S2==0)//如果S2等于0,说明按键2被按下
{
delay(1000);//去抖动处理
if(S2==0)LED1=1;//检测按键确实按下,熄灭LED1
}
}
}
程序分析:
主程序进来就一个while(1)死循环,循环中检测是S1还是S2按下,如果第一个按键S1按下,则S1等于0,然后延时一段时间再检测S1是否还等于0,即去抖处理,如果S1还等于0,说明S1确实按下,就点亮LED1。如果第二个按键S2按下,则S2等于0,r然后延时一段时间再检测S2是否还等于0,即去抖处理,如果S2还等于0,说明S2确实按下,就熄灭LED1。
延时函数的原理就是,当单片机执行一个while(i--)循环语句时,就消耗了一部分时间,时间的多少有i的初始值决定。
第四天 数码管介绍及其应用
一、什么是数码管
图4- 1 数码管实物图
数码管是单片机系统中常用的显示器件,每个数码管由8段LED构成,组合起来可以显示0,1,2,3,4,5,6,7,8,9和部分英文字母。
二、数码管的硬件结构
共阳极 |
共阴极 |
图(a)是数码管外观图,一位数码管含有10个引脚,a,b,c,d,e,f,g,dp,和两个GND,两个GND是连在一起的。图(b)是数码管的内部结构图。图(b)中又有两种接法,一种是将8段LED的阴极(负极)一起连在接地端,称为共阴极接法,一种是将8段LED的阳极(正极)一起连在+5V的电源上,称为共阳极接法。
三、数码管的显示原理
使用LED显示器时,要注意区分这两种不同的接法。下面我们以共阳极数码管为例介绍数码管的编码原理。
假设我们要数码管显示2,那么就是要让a,b,d,e,g亮,其它的不亮。
若显示8,那么就让a,b,c,d,e,f,g亮,dp不亮。
为了显示数字或字符,必须对数字或字符进行编码。七段数码管加上一个小数点,共计8段。因此为LED显示器提供的编码正好是一个字节(8位)。每一位控制一段LED,对应关系如下表:
D8 | D7 | D6 | D5 | D4 | D3 | D2 | D1 |
dp | g | f | e | d | c | b | a |
表4- 1
其中,D1表示8位二进制位的第一位,D2表示8位二进制位的第二位,以此类推。
如果我们要数码管显示2,即a,b,d,e,g亮,其它的不亮。因为数码管是共阴的,所以给a,b,d,e,g赋0才亮,即10100100,即0xa4。 如果显示8,即a,b,c,d,e,f,g亮,dp不亮。编码10000000,即0x80。
以下是共阳极编码列表:
显示的数 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
编码 | 0xc0 | 0xf9 | 0xa4 | 0xb0 | 0x99 | 0x92 | 0x82 | 0xf8 | 0x80 | 0x90 |
表4- 2
会编共阳的,就会编共阴的, 共阴极编码列表如下:
显示的数 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
编码 | 0x3f | 0x06 | 0x5b | 0x4f | 0x66 | 0x6d | 0x7d | 0x07 | 0x7f | 0x6f |
表4- 3
四、程序示例
让数码管重复显示0,1,2…8,9。数码管(共阳)接在P2口上。
硬件示意图如下:
图4- 2 数码管硬件电路图
分析:我们控制数码管的显示,数码管的八个引脚是连在P2口的,所以就是对P2口赋值的过程。
程序如下:
#include
void delay(intx)//延时函数
{
while(x--);
}
main()
{
while(1)
{
P2=0xc0;//显示0
delay(8000);//延时
P2=0xf9;//显示1
delay(8000);//延时
P2=0xa4;//显示2
delay(8000);//延时
P2=0xb0;//显示3
delay(8000);//延时
P2=0x99;//显示4
delay(8000);//延时
P2=0x92;//显示5
delay(8000);//延时
P2=0x82;//显示6
delay(8000);//延时
P2=0xf8;//显示7
delay(8000);//延时
P2=0x80;//显示8
delay(8000);//延时
P2=0x90;//显示9
delay(8000);//延时
}
}
程序分析:
主程序中就一个大循环,循环为P2口赋值,从而显示0到9。每两个数之间有个延时,为什么要延时呢?是因为我们的单片机处理速度是非常快的,不延时我们根本没法看清数码管显示的是什么,所以必须要有延时。
五、数码管的动态扫描
上面我们只讲到了用一个I/O口控制1位数码管的显示,若是要控制两个数码管的显示呢?很多人说用两个I/O口,这个是可以实现的,但我们要控制8个数码管呢,岂不是要8个I/O口吗?51单片机只有4个I/O口,那不是不能控制8个数码管了?所以为了节约51单片机的引脚资源,我们引入了数码管的动态扫描。
1、什么是动态扫描
数码管的动态扫描就是先让第一位数码管显示,然后第一位灭,再让第二位显示,然后第二位灭,……第n位数码管显示,然后第八位灭。然后重复上述状态,当显示速度非常快的时候,我们看到的就是n位数码管同时显示。
2、怎么实现动态扫描
下面我们以四个数码管(共阳极)的显示为例讲述动态扫描的使用方法
四个数码管的引脚连接图如下:
图4- 3 四位数码管电路图
四个数码管的a脚连在一起,b脚连在一起,……dp脚连在一起,每个数码管的公共引脚(com)独立,com脚接在一个三极管(Q3)上,大家先不用管三极管是怎么用的,你只需知道扫描信号有高电平时,扫描信号的那个端子就与(com)连通了,如扫描信号的A端为高电平,那么A端就与第四个数码管的(com)接通了。
如果我们要第一个数码管显示1,第一个数码管显示2,第一个数码管显示3,第一个数码管显示4。我们该怎么做?
用动态扫描的方法
第一步:先让第一个数码管显示1,其它的关闭。
给a,b,c,d,e,f,g,dp赋值,即0xf9,再给D端一个高电平(打开第一个数码管),给ABC端低电平(关闭其它3个数码管)。
第二步:先让第二个数码管显示2,其它的关闭。
给a,b,c,d,e,f,g,dp赋值,即0xa4,再给C端一个高电平(打开第二个数码管),给ABD端低电平(关闭其它3个数码管)。
第三步:先让第三个数码管显示3,其它的关闭。
给a,b,c,d,e,f,g,dp赋值,即0xb0,再给B端一个高电平(打开第三个数码管),给ACD端低电平(关闭其它3个数码管)。
第四步:先让第四个数码管显示4,其它的关闭。
给a,b,c,d,e,f,g,dp赋值,即0x99,再给A端一个高电平(打开第四个数码管),给BCD端低电平(关闭其它3个数码管)。
重复上面四步,这就实现了动态扫描。
3、应用实例
假设a,b,c,d,e,f,g,dp接在P2口,信号端ABCD分别接在P3.0,P3.1,P3.2,P3.3上,硬件连接如图所示:
图4- 4 四位数码管仿真电路图
实现动态扫描的源程序如下:
#include
main()
{
int i;
intdat[]={0xf9,0xa4,0xb0,0x99};//数组定义显示数据1,2,3,4的编码
intwei[]={0x01,0x02,0x04,0x08};//数码管位数的编码
for(i=0;i<4;i++)
{
P3=wei[i];//只打开第i个数码管
P2=dat[i];//给数码管显示i的编码
if(i==4) i=0;//当i等于4时,让i=0,从而不让程序跳出for循环
}
}
程序分析:
主函数进来定义了两个数组,dat[]用来显示数据,wei[]用来控制显示哪个数码管。
dat[0]表示数组中第一个数据,即0xf9,dat[1]表示数组中第二个数据,即0xa4,
dat[2]表示数组中第三个数据,即0xb0,dat[3]表示数组中第四个数据,即0x99,
对于wei[]也是同样的道理。
然后有个for循环,i等于0时,第一个数码管显示1,i等于1时,即第二个数码管显示2,i等于2时,即第三个数码管显示3,i等于3时,即第一个数码管显示4,当i等于4时,给i赋值,让i等于0,继续下一次循环。因为单片机运算速度非常快,如此循环下去,我们看到的就是四个数码管同时显示1234。
第五天 中断系统
一、什么是中断
单片机CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理(中断发生);CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务);待CPU将事件B处理完毕后,再回到原来事件A被中断的地方继续处理事件A(中断返回),这一过程称为中断。
例如:当你正在洗衣时,突然手机响了(中断发生),你暂时中断洗衣的工作,转去接电话(中断响应和中断服务),待你接完后,再回来继续洗衣(中断返回),这一过程就是中断。
二、中断系统
中断系统结构图如下:
图5- 1 中断示意
引起单片机的CPU中断的根源,称为中断源。中断源向CPU提出的中断请求。CPU暂时中断原来的事务A,转去处理事件B,对事件B处理完毕后,再回到原来被中断的地方(即断点),称为中断返回。实现上述中断功能的部件称为中断系统(中断机构)。
80C51的中断系统有5个中断源,也就是说有5种情况发生时,会使单片机停下来去处理中断程序,5种中断源分别为外部中断0(INT0),外部中断1(INT1),定时器中断0(T0),定时器中断1(T1)和串行口中断。
三、中断的优先级
但两个中断同时发生时,我们的CPU会先响应哪个中断呢?这就涉及到中断优先级的问题。默认状态下,CPU会优先响应中断优先级别高的中断,当把优先级高的中断执行完后,在回来执行优先级级别低的中断。
图5- 2 中断优先级次示意图
51单片机的中断级别如下:
中断源 | 中断优先级级别 | 中断序号 |
INT0(外部中断0) | 最高 | 0 |
T0(定时器中断0) | 第2 | 1 |
INT1(外部中断1) | 第3 | 2 |
T1(定时器中断1) | 第4 | 3 |
TI/RI(串行口中断) | 最低 | 4 |
表5- 1
80C51单片机的中断优先级有三条原则:
Ø CPU同时接收到几个中断时,首先响应优先级别最高的中断请求。
Ø 正在进行的中断过程不能被新的同级或低优先级的中断请求所中断。
Ø 正在进行的低优先级中断服务,能被高优先级中断请求所中断。
四、中断的优点
对于单片机初学者会问到个问题:“中断就是处理一事件(中断子程序),用户自定义一个子函数也可以处理呀,这中断不就和子函数没区别吗?那还有什么用呢?”其实中断的功能远远强于普通的子函数,下面我们来看看中断的优点
Ø 分时操作:CPU可以分时为多个I/O设备服务,提高了计算机的利用率
Ø实时响应:CPU能够及时处理应用系统的随机事件,系统的实时性大大增强;
Ø可靠性高:CPU具有处理设备故障及掉电等突发性事件能力,从而使系统可靠性提高。
五、中断响应条件
Ø 中断源有中断请求
Ø 此中断源的中断允许位为1
Ø CPU开中断(即EA=1)
以上三条同时满足时,CPU才有可能响应中断。
六、怎么使用中断
要使用中断,首先要学会使用中断允许寄存器,首先解释一下什么叫寄存器。
寄存器:在单片机中,寄存器是个高频词,很多初学者不理解它,寄存器实质是一个存储单元,51单片机的寄存器是8位的,可以存00000000~11111111,但寄存器的功能又比普通的存储单元的功能强,向寄存器写如不同的数,就能完成不同的动作,让单片机实现不同的功能,这是普通存储器所不能做到的。
中断允许寄存器如下:
位序号 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
位符号 | EA | — | ET2 | ES | ET1 | EX1 | ET0 | EX0 |
中断 允许位名称 | 总中断 | 无效位 | Timer2中断 | 串行口中断 | Timer0中断 | 外部中断0 | Timer0中断 | 外部中断0 |
表5- 2
中断寄存器共8位,每一位表示控制不同的子中断。我们要开哪个中断,就将该中断的允许位赋1,要关哪个中断,就将该中断允许位赋0。如我们开串行口中断,首先开总中断,即EA=1;再开串行口中断,即ES=1。这样我们就完成了开串行口中断的动作。
大家很可能会问到:当中断发生时,CPU怎么处理呢?这就涉及到中断子程序了,中断发生时CPU就立即停下当前动作,立刻跳至中断子程序中。中断子程序就是中断时CPU要做的动作。
下面介绍中断子程序的写法
void 函数名() interrupt 中断号
{
中断服务程序的内容
}
说明:中断函数无返回值,所以最前面用void,中断函数无任何参数,所以()中无任何内容,interrupt为中断的英文名,中断号是指单片机中几种中断源的序号。中断的具体运用(定时器中断)我们将在下一章介绍。
第六天 定时器/计数器
一、什么是定时器/计数器
定时器/计数器是集成在单片机内部,能够实现定时和计数的硬件结构(8051有两个定时器Timer0和Timer1)。与软件定时相比,增大了定时精确度,提高了CPU的利用率。
二、定时器是怎么定时的
定时器的实质是一个加1计数器(16位),加1计数器每个机器周期完成加1的动作,它由高8位和低8位两个寄存器组成,最多能装11111111 11111111。计数器从初始值开始不断的加1,加到溢出值(上限值),定时时间就到了,此过程经历的时间就是我们所定的时间。
说明:1个机器周期等于12个振荡周期,振荡周期即晶振振荡的周期,也就是1/晶振频率。
1、 计数原理
加1计数器(16位)每隔一个机器周期就加1,当加到计数器为全1时,即11111111 11111111。再过一个机器周期(再加1)就使计数器就溢出(溢出就是计数已经满了,再加1就计数器容纳不下了,这就是溢出),如果定时/计数器工作于定时模式,则表示定时时间已到;如果工作于计数模式,则表示计数值已满。
可见,由溢出时计数器的值减去计数初值才是加1计数器的计数值。
说明:读者可能有疑问,上面不是说了寄存器是8位的吗,这计数器为什么就16位呢?这其实是两个寄存器(TLX和THX)组成一个16位的计数器。TLX计数器的低8位(16位二进制位的后8位),THX计数器的高8位(16位二进制位的前8位)
说明:X可以是0或1,若计数器是Timer0,则X为0;若计数器是Timer1,则X为1
2、 定时原理
设置为定时器模式时,加1计数器是对内部机器周期计数。计数值N乘以机器周期Tcy就是定时时间t 。
例 利用定时/计数器T1的方式1,产生10ms的定时,设晶振频率为12MHz,计算计数初值X
解:由于晶振为12 MHz,所以机器周期Tcy为1 ms。工作方式1,查表4-1知计数上限值为65536。
所以:
计数值N=t/ Tcy =10000/1=10000
X=计数上限值-N=65536-10000=55536
三、怎么使用定时器/计数器
1、8051有两个定时器Timer0和Timer1,每个定时器有四种工作模式。不同模式的计数范围不同,其它区别大家现在不用关心。
模式 | 位数 | 计数范围 |
Mode0 | 13 | 0~2^13(8191) |
Mode1 | 16 | 0~2^16(65536) |
Mode2 | 8 | 0~2^8(255) |
Mode3 | 8 | 0~2^8(255) |
表6- 1
从上一章中断的使用中,大家知道控制中断就是向中断允许寄存器写数,那么同理可知,控制定时器就是向定时器控制寄存器(TCON)中写数。我们可以单独取一位处理写,也可对TCON整体赋值。
定时器控制寄存器如下
位序号 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
控制位名称 | TR1 | TR0 |
表6- 2
TR1(TCON.6):T1(定时器1)运行控制位。TR1置1时,T1开始工作;TR1置0时,T1停止工作。TR1由软件置1或清0。所以,用软件可控制定时/计数器的启动与停止。
TR0(TCON.4):T0(定时器0)运行控制位,其功能与TR1类同。
我们只需掌握这两位就够了。
2 、定时器是使用步骤:
1)设置定时器的工作方式。
2)计算初值,并将其写入计数器。
3)开中断。
4)开定时器。
在此我们只介绍Timer0的工作模式1的使用,这个会了其它的也都会了,因为使用方法类似。
四、程序实例:
控制LED每间隔50ms闪烁一次,即亮50ms灭50ms。LED连在P1.0上。用Timer0的工作方式1控制,晶振频率12MHz。
程序流程如如下:
开始 |
定时器初始化(开定时器) |
定时时间是否到 ? |
while(1)等待中断 |
中断子程序 (LED取反) |
Y |
N |
首先,要计算定时器的计算初值,由于晶振为12 MHz,所以机器周期Tcy为1ms。工作方式1,查表5-3知计数上限值为65536。
所以:
计数值N=t/ Tcy =50ms/1us=50000
X=计数上限值-N=65536-50000=15536=0x3CB0
将高8位赋给TH0,即TH0=0x3C,将低8位赋给TL0,即TL0=0xB0。
程序如下:
#include //包含头文件,一般情况不需要改动。
sbitLED=P1^0;//定义LED端口
void Init_Timer0(void)
{
TMOD =0x01;//设定定时器为工作方式1,TMOD是设置定时器工作方式的寄存器,设置定时器方式的过程就是给TMOD赋值的过程。
TH0=0x3C;//将计数初值写入计数器,这里使用定时器从15536开始计数一直到65535溢出
TL0=0xB0;
EA=1;//打开总中断,EA是总中断的允许位,给EA赋1,就完成了开总中断的动作
ET0=1;//打开定时器0中断,E是中断英文的首字母,T0是Timer0.组合以来就是ET0,ET0是定时器0中断的一个允许位,给它赋1,就打开定时器0中断
TR0=1;//打开定时器,同上,TR0是定时器0的允许位,给它赋1,打开定时器0
}
main()
{
Init_Timer0();//定时器初始化,利用函数调用的知识
while(1);//等待定时器中断
}
void Timer0_isr(void) interrupt 1
{
TH0=0x3C; //重新赋值,高8位为0x3C,即0011 1100
TL0=0xB0;// 低8位为0xB0,即1011 0000
LED=~LED;//指示灯反相,可以看到闪烁
程序解释:
main()主函数进来
首先执行Init_Timer0()定时器初始化子函数,即初始化定时器,什么是初始化定时器呢,就是设置好定时器的初始状态。我们来看看Init_Timer0()是怎样初始化的,首先,我们定义了Init_Timer0(),在定时器初始化子程序中,第一行TMOD= 0x01用于设定定时器工作模式为工作模式1,即16位计数器。TH0=0x3C定时器计数初值的高8位为00111100,TL0=0xB0定时器计数初值的低8位为1011 0000,TH0和TL0组合起来就16位,即计数器初值为
0011 1100 1011 0000, 从0开始计数一直到1111 1111 11111111,即65535溢出。我们用到了定时器中断,所以要开中断,开中断的顺序是先开总中断,即EA=1,再开定时器中断,即ET0=1;最后打开定时器,让定时器开始计时,即TR0=1.
主函数第二条语句,while(1);是一个死循环,一直执行;即一直执行空语句,什么也不做。既然什么都不做,那还要什么用呢,用处当然大了,它的作用是让程序停止这里一直等待定时器中断,即等到时间到后去执行定时器中断子程序。
那么有人会问,时间到了干什么呢?这就要说到我们中断子程序的作用了,定时时间一到,程序就不管while(1)这个死循环了,而是马上跳到中断子程序voidTimer0_isr(void) interrupt1中,跳进去后执行TH0=0x3C;TL0=0xB0; 为计数器重新赋值,时间到后是1111 1111 1111 1111,我们要它重新计时,当然要重新赋初值00111100 10110000。重赋初值后LED=~LED;~是取反运算符,若起始状态LED为1,取反后则为0,即实现了由亮到灭或由灭到亮的翻转。
执行完LED=~LED后跳出中断等待下一次中断的到来,从而实现了灯间隔1s闪烁的功能。
第七天 串行口
一、什么是串行口
串行口(也叫串口)就是51单片机用于与其他外部设备通讯的一个接口,所以它的作用就是向外部发送数据和接收外部设备发来的数据。
二、串行口是怎么工作的
51单片机的串行口的基本工作原理是:通过数据发送引脚(TXD)将数据发送出去,通过数据接收引脚(RXD)接收外部数据。
串行口的TXD(或RXD)只是一个引脚,是一位的,所以收发数据只能一位一位的进行。这就好比火车进隧道一样,隧道的进口相当于RXD,最先进隧道的是火车头(起始位),然后进去的是第一节车厢,第二节车厢……第八节车厢,最后一节车厢(停止位),这样一个过程就是接收数据的过程。隧道的出口相当于TXD,最先出隧道的是火车头(起始位),然后出去的是第一节车厢,第二节车厢……第八节车厢,最后一节车厢(停止位),这样一个过程就是发送数据的过程。
三、串行口的工作方式
51单片机的串行口的工作方式有四种,方式0、方式1、方式2和方式3。最常用的是方式1,所以我们只介绍方式1的应用,实际方式1也完全够用了。
方式1是10位数据的异步通信口(就是说单片机每次只能接受或者发送10位数据)。10位数据中有1位起始位,8位数据位(实际传送的数据),1位停止位。传送一帧数据(一次发送的数据叫一帧)的格式如图所示。
图7- 1数据传送示意图
说明:串行口实际传输的数据是中间8位(D0~D7),数据位前面一位(D0前一位)是起始位,表明这帧数据的开始,数据位后面一位(D7后一位)是停止位,表明数据传送的结束。空闲的地方表示没有数据的传输。
方式1的数据输出时序图
图7- 2 方式1的数据输出时序图
单片机要发送数据出去,必须先把要发送的数据写入发送缓冲器(SBUF)中,再由SBUF将数据传到TXD引脚上,最后把数据发送出去。如图,首先看图的最上面,写入SBUF,左边有个突起的部分,这表示我们把数据写入SBUF中了,然后看图的中间部分TXD,写入数据到SBUF后,TXD开始发一帧数据的起始位,然后发D0、D1、D2…D7、停止位。最后看图的最下方TI,当数据发送到停止位时TI的图像就有个上升的变化,其实是TI置1,TI起初是0,到停止位后就置1.,向CPU请求中断。表示数据发送完毕。
方式一的输入时序图
图7- 3方式一的输入时序图
单片机接收数据,是通过RXD接收的,首先接收一帧数据的起始位,然后接收D0、D1、D2…D7、停止位。图的中间有个位采样脉冲,在数据的每一位下面都对应有采样的脉冲。为什么要采样?采样是为了检测我们接收到的数据是否有错。在接收每一位数据的过程中,我们的单片机要采样很多次,这样才能保证接收的数据的正确性。
图的最下面也有个中断标志RI,当数据接收到停止位时就置1,向CPU请求中断。
四、波特率的计算
在串行通信中,收发双方对发送或接收数据的速率要有约定,双方收发数据的速率一定要相同。数据的传输速率我们用波特率来表示,波特率就是每秒传输的二进制位数,单位b/s。,方式1的波特率是可变的,由定时器T1的溢出率(定时器1秒内溢出的次数)来决定。
方式1的波特率=(2SMOD/32)·(T1溢出率)
当T1作为波特率发生器时,最典型的用法是使T1工作在自动再装入的8位定时器方式(即方式2,且TCON的TR1=1,以启动定时器)。这时溢出率取决于TH1中的计数值。
T1 溢出率 = fosc /{12×[256-(TH1)]}
在单片机的应用中,常用的晶振频率为:12MHz和11.0592MHz。所以,选用的波特率也相对固定。常用的串行口波特率定时器1的参数关系如表所示。
波特率(b/s) | fosc(MHz) | SMOD | 定时器T1 | |
工作方式 | 初值(TH1) | |||
62.5k | 12 | 1 | 2 | 0xff |
19.2k | 11.0592 | 1 | 2 | 0xfd |
9600 | 11.0592 | 0 | 2 | 0xfd |
4800 | 11.0592 | 0 | 2 | 0xfa |
2400 | 11.0592 | 0 | 2 | 0xf4 |
1200 | 11.0592 | 0 | 2 | 0xe8 |
表7- 1
说明:定时器的初值先装在TH1中,然后再将数自动存入TL1中,当TL1溢出时定时器1自动将TH1中的数装入TL1中。也就是说TH1中的数(初值)是个常数,而TL1中的数是变化的,从初值到255不断循环。
五、串行口的控制寄存器(SCON)
在前面已经提到,控制51单片机就是控制引脚高低电平,而控制引脚高低电平就是控制寄存器。所以,控制串行口,就是控制串行口的寄存器SCON。
位序号 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
位符号 | SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |
表7- 2
这么多位中我们只需知道SM0,SM1,REN,TI和RI。
1)SM0、SM1组合起来设置串行口的工作方式
串行口工作方式表如下:
SM0 | SM1 | 方式 | 说明 |
0 | 0 | 0 | 移位寄存器 |
0 | 1 | 1 | 10位异步收发器(8位数据) |
1 | 0 | 2 | 11位异步收发器(9位数据) |
1 | 1 | 3 | 11位异步收发器(9位数据) |
表7- 3
我们在这介绍最常用的工作方式1,所以以后我们就把SM0=0,SM1=1就OK了。
2)REN,允许串行接收位。由软件置REN=1,则启动串行口接收数据;若软件置REN=0,则禁止接收。
3)TI,发送中断标志位。方式1时,串行发送停止位的开始时,由内部硬件使TI置1(自动置1,不用我们软件置1,其实软件也不能置1),向CPU发中断申请。在中断服务程序中,必须用软件将其清0,取消此中断申请。
4)RI,接收中断标志位。方式1时,串行接收停止位的中间时,由内部硬件使RI置1,向CPU发中断申请。也必须在中断服务程序中,用软件将其清0,取消此中断申请。
六、怎么使用串行口
串行口工作之前,应对其进行初始化,主要是设置产生波特率的定时器1、串行口控制和中断控制。具体步骤如下:
1)确定T1的工作方式(编程TMOD寄存器);
2)计算T1的初值,装载TH1、TL1;
3)启动T1(编程TCON中的TR1位);
4)确定串行口控制(编程SCON寄存器);
5)串行口在中断方式工作时,要进行中断设置。
下面我们以51单片机与计算机通讯为例来说明怎么使用串口。
例:在计算机上用串口调试小助手向单片机发送一串字符***,单片机收到字符串 后将字符串“I get***”返回到计算机,波特率为9600bps。
#include
main()
{
TMOD=0x20;//设置定时器1为工作方式2,查表得给TMOD赋0x20就是工作方式2
TH1=0xfd;//载入初值,因为波特率为9600bps,查表知初值为0xfd
TL1=0xfd;
TR1=1;//打开定时器1
REN=1;//启动串行接收数据
SM0=0;// SM0,SM1组合起来设定串行口为工作方式1
SM1=1;
EA=1;//开总中断
ES=1;//开串行口中断
while(1);//让程序停在此等待串口中断
}
void UART_SER () interrupt4//串行口中断服务程序
{
unsignedcharTemp;//定义临时变量
if(RI==1)//如果RI等于1(说明已经接收到数据)
{
RI=0;//接收标志位RI清零(必须软件清零)
Temp=SBUF;//读入接收缓冲器的值
SBUF=Temp;//把接收到的值赋给发送缓冲器,再发回电脑端
}
if(TI==1)//如果是发送标志位,清零
TI=0;//发送中断标志位TI也需软件清零
}
程序分析:
主函数进来第一行(TMOD=0x20)至第九行(ES=1)都是对串口进行初始化,步骤就是按照我们前面所说那样写。然后第十行while(1);死循环,目的就是让程序停在此等待串口中断,这与前面介绍的定时器中断类似。
当接收到数据时,触发串口中断,程序跳出while循环,进入串行口中断服务程序,中断服务程序有两个分支,第一个分支:如果RI等于1(说明单片机收到数了),那么将接收缓冲器SBUF中的数赋给一个变量Temp,然后再把Temp的数赋给发送缓冲器SBUF,由发送缓冲器将数传到发送引脚TXD,最终传到电脑端。第二个分支:如果TI等于1(说明发完数了),那么(TI=0)软件清零,必须软件清零,否则一直触发中断,就跳不出这个中断服务程序。