第六章 中断的学习.docx
- 文档编号:28000883
- 上传时间:2023-07-07
- 格式:DOCX
- 页数:22
- 大小:28.91KB
第六章 中断的学习.docx
《第六章 中断的学习.docx》由会员分享,可在线阅读,更多相关《第六章 中断的学习.docx(22页珍藏版)》请在冰豆网上搜索。
第六章中断的学习
第六章中断的学习
中断是单片机系统的重点中的重点,因为有了中断,单片机就具备了快速协调多模块工作的能力,大家对本章节内容要多研究,最终要完全理解并且掌握。
6.1 C语言的数组
6.1.1 数组的基本概念
我们第四章学过变量的基本类型,比如char、int等等。
这种类型描述的数据是比较有限的,当我们要处理非常大量数据的时候,就可以用到数组了,比如我们上节课的那个数码管的真值表,我们就可以用一个数组来表达。
从概念上讲,数组是具有相同数据类型的有序数据的组合,一般来讲,数组定义后满足以下三个条件。
(1)具有相同的数据类型;
(2)具有相同的名字;
(3)在存储器中是被连续存放的。
比如我们上节课定义的那个数码管真值表,如果我们把关键字code去掉,数组元素将被保存在RAM中,在程序中可读可写,同时我们也可以在中括号里边标明这个数组元素的个数,比如:
unsigned char LedChar[16] = {
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e};
在这个数组中的每个值都称之为数组的一个元素,这些元素都具备相同的数据类型就是unsigned char型,他们有一个共同的名字LedChar,不管放到RAM中还是FLASH中,他们都是存放在一块连续的存储空间里的。
有一点要特别注意,这个数组一共有16(中括号里面的数值)个元素,但是数组的元素的表达方式下标是从0开始,因此实际上上边这个数组的首个元素LedChar[0]的值是0xC0,而LedChar[15]的值是0x8e,下标从0到15一共是16个元素。
LedChar这个数组只有一个下标,我们称之为一维数组,还有两个下标或者多个下标的,我们称之为多维数组。
比如unsigned char a[2][3];表示这是一个2行3列的二维数组。
在大多数情况下我们使用的是一维数组,对于初学来说,我们先来研究一维数组,多维数组遇到了再了解。
6.1.2 数组的声明
一维数组的声明格式如下:
数据类型 数组名[数组长度];
(1)数组的数据类型声明的是该数组的每个元素的类型,即一个数组中的元素具有相同的数据类型。
(2)数组名的声明要符合C语言固定的标识符的声明要求,只能由字母、数字、下划线这三种符号组成,且第一个字符只能是字母或者下划线。
(3)方括号中的数组长度是一个常量或常量表达式,并且必须是正整数。
6.1.3 数组的初始化
数组在进行声明的同时可以进行初始化操作,格式如下:
数据类型 数组名[数组长度] = {初值列表};
还是以上节课我们用的数码管的真值表为例来讲解注意事项。
unsigned char LedChar[16] = {
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e};
(1)初值列表里的数据之间要用逗号隔开。
(2)初值列表里的初值的数量必须小于或者等于数组长度,当小于数组长度时,数组的后边没有赋初值的元素由系统自动赋值0。
(3)若给数组的所有元素赋初值,可以省略数组的长度,上节课的例子中我们实际上已经省略了数组的长度。
(4)系统为数组分配连续的存储单元的时候,数组元素的相对次序由下标来决定,就是说LedChar[0]、LedChar[1]... ... LedChar[15]是按照顺序排下来的。
6.1.4 数组的使用和赋值
在C语言程序中,是不能一次使用整个数组的,只能使用单个数组元素。
一个数组元素相当于一个变量,使用数组元素的时候与使用相同数据类型的变量的方法一样。
比如这个LedChar这个数组,如果没加code关键字,那么它可读可写,我们可以写成a = LedChar[0]这样来把数组的一个元素的值送个a这个变量,也可以写成LedChar[0] = a这样把a这个变量的值送给数组的一个元素,以下三点要注意:
(1)引用数组的时候,那个方括号里的数字代表的是数组元素的下标,而数组初始化的时候方括号里的数字代表的是这个数组元素的个数。
(2)数组元素的方括号里的下标可以是整型常数,整型变量或者表达式,而数组初始化的时候方括号里的数字必须是常数不能是变量。
(3)数组整体赋值只可以在初始化的时候操作,功能程序只能对单个元素赋值。
6.2 if语句
if语句已经不陌生了,前边程序我们其实已经用过了,这里我们系统的介绍一下,方便后边的深入学习。
if语句有两个关键字:
if和else,把这两个关键字翻译一下就是:
“如果”和“否则”。
if语句一共有三种格式,我们分别来看。
1.if语句的默认形式。
if (条件表达式)
{语句 1;}
其执行过程是,if(如果)条件表达式的值为“真”,则执行语句1;如果条件表达式的值为“假”,则不执行语句1。
真和假的概念不再赘述,参考第五章。
这里要提醒一句,C语言一个分号表示一句语句的结束,因此如果if后边只有一条执行语句的时候,可以省略大括号,但是如果有多条执行语句的话,必须加上大括号。
我们上节课的语句就很好理解了if(16 ==j) { j = 0;},如果j等于16的时候,括号里的值才是“真”,那么就执行j=0这一句,如果j不等于16,那么里边就为“假”,就不执行这一句。
2.if...else语句
有些情况下,我们除了判断一下if括号里的是否满足条件,执行相应的语句,在不满足条件的时候,我们又要执行另外相应的语句,这个时候就用到了if...else语句,它的基本的语法形式是:
if (条件表达式)
{语句 1;}
else
{语句 2;}
比如上节课的后半段程序我们也可以写成:
P0 = LedChar[j]; //把数组里的对应值送给P0
if(15 == j) //当显示到F后,归0重新开始
{j = 0;}
else
{j++;}
这个程序大家可以改改下载进去试试,程序逻辑大家自己动脑分析一下,我就不解释了。
3.if....else if语句
if...esle语句是一个二选一的语句,或者执行if条件下的语句,或者执行else条件下的语句。
还有一种多选一的用法就是if...else if语句。
他的基本语法格式是:
if (条件表达式1) {语句 1;}
else if (条件表达式2) {语句 2; }
else if (条件表达式3) {语句 3; }
... ...
else {语句 n;}
他的执行过程是:
依次判断条件表达式的值,当出现某个值为“真”时,则执行相对应的语句,然后跳出整个if的语句块,执行“语句n”后边的程序;如果所有的表达式都为“假”,则执行“语句n”后,再执行“语句n”后边的程序。
if语句在C语言编程的过程中使用频率很高,用法也简单,所以必须要熟练掌握。
6.3 switch语句
用if....else语句在处理多分支的时候,分支太多就会显得不方便,且容易出现if和else配对出现错误的情况,在C语言中提供了另外一种多分支选择的语句——switch语句,它的基本语法格式如下:
switch (表达式)
{
case 常量表达式1:
执行语句1;
case 常量表达式2:
执行语句2;
......
case 常量表达式n:
执行语句n;
default:
执行语句n+1;
}
它的执行过程是:
首先计算“表达式”的值,然后从第一个case开始,与“常量表达式x”进行比较,如果与当前常量表达式的值不相等,那么就不执行冒号后边的程序,一旦发现和一个常量表达式的值相等了,那么他会执行之后所有的,注意是所有的“执行语句”,显然这不是我们想要的结果。
在C语言中,有一条break语句,作用是跳出当前循环语句,不管是for和while循环,还是switch循环,都可以用其搭配使用跳出循环。
switch语句一共有n+1种可能,而我们希望要的是一条多选一的语句,只执行其中一条然后直接退出该循环,不再执行下边的任何语句,这个时候就需要用到break语句,比如我们在switch表达式上加上break语句,如下:
switch (表达式)
{
case 常量表达式1:
执行语句1;break;
case 常量表达式2:
执行语句2;break;
......
case 常量表达式n:
执行语句n;break;
default:
语句n+1;
}
加了这个break语句后,一旦“常量表达式x”与“表达式”相等了,那就执行“执行语句x”,执行完毕后,由于有了break,直接跳出switch语句,执行switch语句循环后边的程序了,这样就可以避免执行不必要的语句。
了解了这个switch语句,我们将会在本章程序中使用巩固。
6.4 数码管的动态显示
6.4.1 动态显示的基本原理
我们在上一章学习数码管静态显示的时候说到,74HC138只能在同一时刻导通一个三极管,而我们的数码管是靠了6个三极管来控制,那我们如何来让数码管同时显示呢?
这就用到了我们这节课的动态显示。
多个数码管显示数字的时候,我们实际上是轮流点亮数码管(一个时刻内只有一个数码管是亮的),利用人眼的视觉暂留现象(也叫余辉效应),就可以做到看起来是所有数码管都同时亮了,这就是动态扫描显示的含义。
例如:
我们有2个数码管,我们要显示“12”这个数字,让高位的位选三极管导通,然后给它赋值“1”,延时一定时间后让低位的位选三极管导通,然后给它赋值“2”。
把这个流程以一定的速度循环运行就可以让数码管显示出“12”,由于交替速度非常快,人肉眼识别到的就是“12”这个数字。
那么一个数码管需要点亮多长时间呢?
也就是说要多长时间完成一次全部数码管的扫描呢(很明显:
整体扫描时间=单个数码管点亮时间*数码管个数)?
答案是:
10ms以内。
当电视机和显示器还处在CRT(电子显像管)时代时,有一句很流行的广告语——“100Hz无闪烁”,没错,只要刷新率大于100Hz,即刷新时间小于10ms,就可以做到无闪烁,这也就是我们的动态扫描的硬性指标。
那么你也许会问,有最小值的限制吗?
理论上没有,但实际上做到更快的刷新却没有任何进步的意义了,因为已经无闪烁了,再快也还是无闪烁,只是徒然增加CPU的负荷而已(因为1秒内要执行更多次的扫描程序)。
所以,通常我们设计程序的时候,都是取一个接近10ms,又比较规整的值就行了。
我们板子上有6个数码管,我们下面用程序来验证一下数码管动态显示程序。
#include
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //用数组来表示数码管真值表
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e,
};
void main()
{
unsigned int counter = 0;
unsigned char j = 0;
unsigned long stopwatch = 0;
unsigned char LedNumber[6] = {0};
ENLED = 0; ADDR3 = 1;P0 = 0XFF; //74HC138和P0初始化部分
TMOD = 0x01; //设置定时器0为模式1
TH0 = 0xFC;
TL0 = 0x67; //定时值初值,定时1ms
TR0 = 1; //打开定时器0
while
(1)
{
if(1 == TF0) //判断定时器0是否溢出
{
TF0 = 0;
TH0 = 0xFC; //一旦溢出后,重新赋值
TL0 = 0x67;
counter++;
if(1000 == counter) //判断定时器0溢出是否达到50次
{
counter = 0;
stopwatch++; //秒表数值一秒加1
LedNumber[0] = stopwatch%10;
LedNumber[1] = stopwatch/10%10;
LedNumber[2] = stopwatch/100%10;
LedNumber[3] = stopwatch/1000%10; //数码管显示值计算
LedNumber[4] = stopwatch/10000%10;
LedNumber[5] = stopwatch/100000%10;
}
if (0==j)
{ ADDR0=0; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[0]]; }
else if (1==j)
{ ADDR0=1; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[1]]; }
else if (2==j)
{ ADDR0=0; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[2]]; }
else if (3==j)
{ ADDR0=1; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[3]]; }
else if (4==j)
{ ADDR0=0; ADDR1=0; ADDR2=1; j++; P0=LedChar[LedNumber[4]]; }
else if (5==j)
{ ADDR0=1; ADDR1=0; ADDR2=1; j=0; P0=LedChar[LedNumber[5]]; }
} //数码管动态刷新部分
}
}
这程序,大家自己抄到Keil中,然后边抄边理解,最终下载到实验板上实验一下效果。
其中下边的if...else语句就是每1ms快速的刷新一个数码管,这样6个数码管整体刷新一遍的时间就是6ms,视觉上就是6个数码管无闪烁的同时亮起来了。
另外一个简单知识点这个地方也提一下,其实属于小学三年级知识,但是很多同学刚接触C语言,可能遇到了也会发懵。
就是在数码管显示值计算这个地方,相信小学我们没学小数之前,除法运算里边有“被除数”、“除数”、“商”、“余数”这四个概念年。
而在我们C语言中,“/”等同于数学里的除法运算,而“%”等同于我们小学学的求余数运算。
如果是123456这个数字,我们要正常显示在数码管上,个位显示,就是直接对10取余数,这个“6”就出来了,十位数字就是先除以10,然后再对10取余数,以此类推,就把6个数字全部显示出来了。
对于多选一的动态刷新数码管的方式,我们如果用switch会有更好的效果,大家来看一下我们用switch语句完成的情况。
#include
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //用数组来表示数码管真值表
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e,
};
void main()
{
unsigned int counter = 0;
unsigned char j = 0;
unsigned long stopwatch =0;
unsigned char LedNumber[6]={0};
ENLED = 0; ADDR3 = 1;P0 = 0XFF; //74HC138和P0初始化部分
TMOD = 0x01; //设置定时器0为模式1
TH0 = 0xFC;
TL0 = 0x67; //定时值初值,定时1ms
TR0 = 1; //打开定时器0
while
(1)
{
if(1 == TF0) //判断定时器0是否溢出
{
TF0 = 0;
TH0 = 0xFC; //一旦溢出后,重新赋值
TL0 = 0x67;
counter++;
if(1000 == counter) //判断定时器0溢出是否达到1000次
{
counter = 0;
stopwatch++; //秒表数值一秒加1
LedNumber[0] = stopwatch%10;
LedNumber[1] = stopwatch/10%10;
LedNumber[2] = stopwatch/100%10;
LedNumber[3] = stopwatch/1000%10; //数码管显示值计算
LedNumber[4] = stopwatch/10000%10;
LedNumber[5] = stopwatch/100000%10;
}
switch(j)
{
case 0:
ADDR0=0; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[0]];break;
case 1:
ADDR0=1; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[1]];break;
case 2:
ADDR0=0; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[2]];break;
case 3:
ADDR0=1; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[3]];break;
case 4:
ADDR0=0; ADDR1=0; ADDR2=1; j++; P0=LedChar[LedNumber[4]];break;
case 5:
ADDR0=1; ADDR1=0; ADDR2=1; j=0; P0=LedChar[LedNumber[5]];break;
default:
break;
}
} //数码管动态刷新部分
}
}
大家是否能感觉到switch语句比if...else语句显得要整齐的多?
6.4.2 数码管消隐处理
不知道细心的同学能否发现,我们的两次数码管动态刷新显示的时候似乎并不是那么完美,第一个小问题,大家仔细看,数码管的不应该显示的段,似乎有微微的发亮,这种现象叫做“鬼影”,这个“鬼影”严重影响了我们的视觉效果,我们该如何解决呢?
同学们今后可能会遇到各种各样的问题,可能有很多我是没有讲过的问题,遇到问题怎么办呢?
大家要相信,你作为初学者,遇到的问题肯定不是第一个遇到的,肯定有前辈会遇到同类问题,他们一般会在网上发表各种帖子,各种讨论,所以大家遇到问题,首先解决方法就应该形成一个到网上搜索的条件反射,这个问题大家可以到网上搜:
“数码管消隐”或者“数码管鬼影解决”,多找相关关键词搜索,会搜索也是一种能力。
大家在网上搜了一下会发现,解决这类问题的普遍两个方法,其中之一是延时,延时之后我们肉眼就可能看不到这个“鬼影”了。
但是延时是一个非常拙劣的手段,且不说延时多久能让我们看不到“鬼影”,延时后,我们的数码管亮度会普遍降低。
我们解决问题呢,不能只知其然,不知其所以然,所以我们首先要弄懂为什么会出现“鬼影”。
“鬼影”的出现,主要是因为我们数码管位选和段选产生的瞬态所造成的。
举个简单例子,我们在数码管动态刷新的那部分程序中,实际上每一个数码管点亮的持续时间是1ms的时间,1ms后进行下个数码管的切换。
在进行数码管切换的时候,比如我们从case 5要切换到case 0的时候,case 5的位选用的是ADDR0=1; ADDR1=0; ADDR2=1;假如此刻case5也就是最高位数码管对应的值是0。
我们要切换成的case 0的数码管位选是ADDR0=0; ADDR1=0; ADDR2=0;而对应的数码管的值假如是1。
因为我们的C语言程序是一句一句顺序往下执行的,每一条语句都会占用一定的时间,即使这个时间非常非常短暂。
但是当我们把“ADDR0=1”改变成“ADDR0=0”的时候,这个瞬间存在了一个中间状态ADDR0=0; ADDR1=0; ADDR2=1;在这个瞬间上,我们就给case 4对应的数码管DS5瞬间赋值了0。
当我们全部写完了ADDR0=0; ADDR1=
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第六章 中断的学习 第六 中断 学习