当前位置:文档之家› C51入门

C51入门

C51入门
C51入门

C51培训教程

1. 前言

1.1为什么使用C语言而放弃汇编?

好些年之前,每个单片机开发人员都为自己写了几万行汇编代码而骄傲。

然而几年之后的今天,却要劝说后来学习者一定要舍弃汇编而学习C语言,是否很好笑。首先说说C的优点:

直观,可读性强:这点很重要。对于一个产品,周期是很长的,即使出第一台产品之后,还有很长的维护时间。这中间维护人员可能经常变动,如果可读性强,将给维护工作省下很大的成本。即使是在开发,可读性强的程序也便于查错。

模块化可以做的很好:这点也是很重要的。模块化做得好,当然程序得重用性就高。对于公司来说,这一点是关系到公司长远发展的。程序可以重用,说明下一次开发的投入就可以减少,时间也可以加快,多好的事呀。

还有很多有点,当然也就是高级语言相对于汇编语言的优点,这里就不一一列举了。

再来看看汇编的优点,

汇编语言操作硬件直观,对于硬件非常熟悉的人来说,直接操作很方便。

“汇编语言操作硬件直观”,这是在代码编写阶段,对于整个产品周期来说,应该是要避免使用汇编语言的,这个在C语言的优点中已经说明。

效率要高。

目前C语言的编译器优化也做的很好,对于一个汇编不是很熟练的来说,C编出来的程序应该不会效率比汇编低。当然这样就对开发人员的要求降低了很多,人员的限制也就没有那么严格。另外是否真的是效率问题呢。我觉得应该是一个整体效率和局部效率的均衡问题。需要提高的是整体的效率。一个好的软件架构,远远比一个好的函数效率要高的多。因此主要的精力应该放在软件的架构上。另外现在CPU的速度不停的往上提,CPU越来越快,这点应该也可以弥补程序的效率吧。

当然,不是不学习汇编。汇编对于熟悉硬件有很大的好处,应此汇编语言在学习初期一定是要学习的。在基本的硬件熟悉之后,就可以转向C了。

1.2C51的编程规范

现在单片机的程序设计,C51已经得到广泛的推广和应用,算是单片机的主流设计程序,甚至可以说作为单片机开发人员必须要掌握的一门语言了。作为一门工具,最终的目的就是实现功能。在满足这个前提条件下,我们希望我们的程序能很容易地被别人读懂,或者能够很容易地读懂别人的程序,在团体合作开发中就能起到事半功倍之效。在网上请求帮助时,如能以规范的写法贴出程序,网友会比较容易地明白你的问题,则会比较快的得到网友的帮助,否则让人看上半天也不明所以然,这样就达不到预期的效果了。因此,为了便于源程序的交流,减少合作开发中的障碍,需要形成良好的编程规范。

一、注释

1,采用中文;

2,开始的注释:

文件(模块)注释内容:

公司名称、版权、作者名称、修改时间、模块功能、背景介绍等,复杂的算法需要加上流程说明;

比如:

/*********************************************************************/

/*公司名称: */

/*模块名:LCD 模块LCD 型号:HD44780 */

/*创建人:zhaojunjie 日期:2001-06-08 */

/*修改人:日期:2001-06-08 */

/*功能描述:*/

/*其他说明:*/

/*版本:

/***************************************/

函数开头的注释内容:

函数名称、功能、说明输入、返回、函数描述、流程处理、全局变量、调用样例等,复杂的函数需要加上变量用途说明;

/*****************************************

*

* 函数名: v_LcdInit

* 功能描述: LCD初始化

* 函数说明: 初始化命令:0x3c, 0x08, 0x01, 0x06, 0x10, 0x0c

* 调用函数: v_Delaymsec(),v_LcdCmd()

* 全局变量:

* 输入: 无

* 返回: 无

* 设计者:zhao 日期:2001-12-09

* 修改者:zhao 日期:2001-12-09

* 版本:

*************************************/

3、程序中的注释内容:

修改时间和作者、方便理解的注释等。注释内容应简炼、清楚、明了,一目了然的语句不加注释。

二、命名:

命名必须具有一定的实际意义。

1、常量的命名:全部用大写。

2、变量的命名:

变量名加前缀,前缀反映变量的数据类型,用小写,反映变量意义的第一个字母大写,其他小写。

其中变量数据类型:

unsigned char 前缀uc signed char 前缀sc

unsigned int 前缀ui signed int 前缀si

unsigned long 前缀ul signed long 前缀sl

bit 前缀b 指针前缀p

例:ucReceivData 接收数据

3、结构体命名:

4、函数的命名:

函数名首字大写,若包含有两个单词的每个单词首字母大写。

函数原型说明包括:引用外来函数及内部函数,外部引用必须在右侧注明函数来源:模块名及文件名, 内部函数,只要注释其定义文件名;

三、编辑风格

1、缩进:缩进以Tab 为单位,一个Tab 为四个空格大小。预处理语句、全局数据、函数原型、标题、附加说明、函数说明、标号等均顶格书写。语句块的“{”“}”配对对齐,并与其前一行对齐;

2、空格:数据和函数在其类型,修饰名称之间适当空格并据情况对齐。关键字原则上空一格,如:

if ( ... ) 等,运算符的空格规定如下:“->”、“[”、“]”、“++”、“--”、“~”、“!”、“+”、“-”(指正负号),“&”(取址或引用)、“*”(指使用指针时)等几个运算符两边不空格(其中单目运算符系指与操作数相连的一边),其它运算符(包括大多数二目运算符和三目运算符“?:”两边均空一格,“(”、“)”运算符在其内侧空一格,在作函数定义时还可据情况多空或不空格来对齐,但在函数实现时可以不用。“,”运算符只在其后空一格,需对齐时也可不空或多空格,对语句行后加的注释应用适当空格与语句隔开并尽可能对齐。

3、对齐:原则上关系密切的行应对齐,对齐包括类型、修饰、名称、参数等各部分对齐。另每一行的长度不应超过屏幕太多,必要时适当换行,换行时尽可能在“,”处或运算符处,换行后最好以运算符打头,并且以下各行均以该语句首行缩进,但该语句仍以首行的缩进为准,即如其下一行为“{”应与首行对齐。

4、空行:程序文件结构各部分之间空两行,若不必要也可只空一行,各函数实现之间一般空两行

5、修改:版本封存以后的修改一定要将老语句用/* */ 封闭,不能自行删除或修改,并要在文件及函数的修改记录中加以记录。

6、形参:在定义函数时,在函数名后面括号中直接进行形式参数说明,不再另行说明

2. 流水灯实验

图1.1 流水灯实验电路图

1.八盏灯闪烁实验:

#include //预处理命令,定义SFR的头文件,一般的C51程序都有void main(void) //主函数名

{

//这是第一种注释方式

unsigned int a; //定义变量a为int类型

/*

这是第二种注释方式

*/

do{ //do while组成循环

for (a=0; a<50000; a++); //这是一个循环

P1 = 0x00; //设P1口全为低电平,点亮LED

for (a=0; a<50000; a++); //这是一个循环

P1 = 0xff; //设P1口全高电平,点亮熄灭LED

}

while(1);

}

知识点:

一、C51程序结构

#include //预处理命令,定义SFR的头文件,一般的C51程序都有void main(void) //主函数名

{

//这是第一种注释方式

unsigned int a; //定义变量a为int类型

/*

这是第二种注释方式

*/

do{//do while组成循环

for (a=0; a<50000; a++); //这是一个循环

P1 = 0x00; //设P1口全为低电平,点亮LED

for (a=0; a<50000; a++); //这是一个循环

P1 = 0xff; //设P1口全高电平,点亮熄灭LED

}

while(1);

}

二、C51数据类型

表1KEIL uVision2 C51编译器所支持的数据类型

char类型的长度是一个字节,通常用于定义处理字符数据的变量或常量。分无符号字符类型unsigned char和有符号字符类型signed char,默认值为signed char类型。unsigned char类型用字节中所有的位来表示数值,所可以表达的数值范围是0~255。signed char类型用字节中最高位字节表示数据的符号,"0"表示正数,"1"表示负数,负数用补码表示。所能表示的数值范围是-128~+127。unsigned char常用于处理ASCII字符或用于处理小于或等于255的整型数。

正数的补码与原码相同,负二进制数的补码等于它的绝对值按位取反后加1。

2,int整型

int整型长度为两个字节,用于存放一个双字节数据。分有符号int整型数signed int和无符号整型数unsigned int,默认值为signed int类型。signed int表示的数值范围是-32768~+32767,字节中最高位表示数据的符号,"0"表示正数,"1"表示负数。unsigned int表示的数值范围是0~65535。

好了,先停一下吧,我们来写个小程序看看unsigned char和unsigned int用于延时的不同效果,说明它们的长度是不同的,呵,尽管它并没有实际的应用意义,这里我们学习它们的用

法就行。依旧用我们上一课的最小化系统做实验,不过要加多一个电阻和LED,如图1.1。实验中用DS8的点亮表明正在用unsigned int数值延时,用DS7点亮表明正在用unsigned char 数值延时。

2.数据类型比较实验:

我们把这个项目称为TwoLED,实验程序如下:

#include //预处理命令

void main(void) //主函数名

{

unsigned int a; //定义变量a为unsigned int类型

unsigned char b; //定义变量b为unsigned char类型

do

{ //do while组成循环

for (a=0; a<65535; a++)

P1_0 = 0; //65535次设P1.0口为低电平,点亮LED

P1_0 = 1; //设P1.0口为高电平,熄灭LED

for (a=0; a<30000; a++); //空循环

for (b=0; b<255; b++)

P1_1 = 0; //255次设P1.1口为低电平,点亮LED

P1_1 = 1; //设P1.1口为高电平,熄灭LED

for (a=0; a<30000; a++); //空循环

}

while(1);

}

同样编译烧写,上电运行您就可以看到结果了。很明显D8点亮的时间长于D7点亮的时间。程序中的循环延时时间并不是很好确定,并不太适合要求精确延时的场合,关于这方面我们以后也会做讨论。这里必须要讲的是,当定义一个变量为特定的数据类型时,在程序使用该变量不应使它的值超过数据类型的值域。如本例中的变量b不能赋超出0~255的值,如for (b=0; b<255; b++)改为for (b=0; b<256; b++),编译是可以通过的,但运行时就会有问题出现,就是说b的值永远都是小于256的,所以无法跳出循环执行下一句P1_1 = 1,从而造成死循环。同理a的值不应超出0~65535。大家可以烧片看看实验的运行结果,同样软件仿真也是可以看到结果的。

3. long长整型

long长整型长度为四个字节,用于存放一个四字节数据。分有符号long长整型signed long 和无符号长整型unsigned long,默认值为signed long类型。signed int表示的数值范围是-2147483648~+2147483647,字节中最高位表示数据的符号,"0"表示正数,"1"表示负数。unsigned long表示的数值范围是0~4294967295。

4. float浮点型

float浮点型在十进制中具有7位有效数字,是符合IEEE-754标准的单精度浮点型数据,占用四个字节。因浮点数的结构较复杂在以后的章节中再做详细的讨论。

5.*指针型

指针型本身就是一个变量,在这个变量中存放的指向另一个数据的地址。这个指针变量要占

据一定的内存单元,对不同的处理器长度也不尽相同,在C51中它的长度一般为1~3个字节。指针变量也具有类型,在以后的课程中有专门一课做探讨,这里就不多说了。

6. bit位标量

bit位标量是C51编译器的一种扩充数据类型,利用它可定义一个位标量,但不能定义位指针,也不能定义位数组。它的值是一个二进制位,不是0就是1,类似一些高级语言中的Boolean类型中的True和False。

7. sfr特殊功能寄存器

sfr也是一种扩充数据类型,点用一个内存单元,值域为0~255。利用它可以访问51单片机内部的所有特殊功能寄存器。如用sfr P1 = 0x90这一句定P1为P1端口在片内的寄存器,在后面的语句中我们用以用P1 = 255(对P1端口的所有引脚置高电平)之类的语句来操作特殊功能寄存器。

8.sfr16 16位特殊功能寄存器

sfr16占用两个内存单元,值域为0~65535。sfr16和sfr一样用于操作特殊功能寄存器,所不同的是它用于操作占两个字节的寄存器,如定时器T0和T1。

9. sbit可寻址位

sbit同位是C51中的一种扩充数据类型,利用它可以访问芯片内部的RAM中的可寻址位或特殊功能寄存器中的可寻址位。如先前我们定义了

sfr P1 = 0x90; //因P1端口的寄存器是可位寻址的,所以我们可以定义

sbit P1_1 = P1^1;//P1_1为P1中的P1.1引脚

//同样我们可以用P1.1的地址去写,如sbit P1_1 = 0x91;

3.单灯闪烁实验:

#include //预处理命令,定义SFR的头文件,一般的C51程序都有

sbit P1_1 = P1^1;

void main(void) //主函数名

{

//这是第一种注释方式

unsigned int a; //定义变量a为int类型

do{ //do while组成循环

for (a=0; a<50000; a++); //这是一个循环

P1_1 = 0; //点亮第2盏灯

for (a=0; a<50000; a++); //这是一个循环

P1_1 = 1; //熄灭第2盏灯

}

while(1);

}

这样我们在以后的程序语句中就可以用P1_1来对P1.1引脚进行读写操作了。通常这些可以直接使用系统提供的预处理文件,里面已定义好各特殊功能寄存器的简单名字,直接引用可以省去一点时间,我自己是一直用的。当然您也可以自己写自己的定义文件,用您认为好记的名字。

关于数据类型转换等相关操作在后面的课程或程序实例中将有所提及。大家可以用所讲到的数据类型改写一下这课的实例程序,加深对各类型的认识。

4.跑马灯实验:

【例】跑马灯程序

#include //预处理文件里面定义了特殊寄存器的名称如P1口定义为P1 void main(void)

{

//定义花样数据,为常量,存储在程序存储空间

const unsigned char

design[32]={0xFF,0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F,

0x7F,0xBF,0xDF,0xEF,0xF7,0xFB,0xFD,0xFE,0xFF,

0xFF,0xFE,0xFC,0xF8,0xF0,0xE0,0xC0,0x80,0x0,

0xE7,0xDB,0xBD,0x7E,0xFF};

unsigned int a; //定义循环用的变量

unsigned char b; //在C51编程中因内存有限尽可能注意变量类型的使用

//尽可能使用少字节的类型,在大型的程序中很受用do{

for (b=0; b<32; b++)

{

for(a=0; a<30000; a++); //延时一段时间

P1 = design[b]; //读已定义的花样数据并写花样数据到P1口}

}while(1);

}

知识点:

常量

常量是在程序运行过程中不能改变值的量,而变量是可以在程序运行过程中不断变化的量。变量的定义可以使用所有C51编译器支持的数据类型,而常量的数据类型只有整型、浮点型、字符型、字符串型和位标量。

常量的数据类型说明是这样的

1.整型常量可以表示为十进制如123,0,-89等。十六进制则以0x开头如0x34,-0x3B等。长整型就在数字后面加字母L,如104L,034L,0xF340等。

2.浮点型常量可分为十进制和指数表示形式。十进制由数字和小数点组成,如0.888,3345.345,0.0等,整数或小数部分为0,可以省略但必须有小数点。指数表示形式为[±]数字[.数字]e[±]数字,[]中的内容为可选项,其中内容根据具体情况可有可无,但其余部分必须有,如125e3,7e9,-3.0e-3。

3.字符型常量是单引号内的字符,如'a','d'等,不可以显示的控制字符,可以在该字符前面加一个反斜杠"\"组成专用转义字符。常用转义字符表请看表5-1。

4.字符串型常量由双引号内的字符组成,如"test","OK"等。当引号内的没有字符时,为空字符串。在使用特殊字符时同样要使用转义字符如双引号。在C 中字符串常量是做为字符类型数组来处理的,在存储字符串时系统会在字符串尾

部加上\o转义字符以作为该字符串的结束符。字符串常量"A"和字符常量'A'是不同的,前者在存储时多占用一个字节的字间。

5.位标量,它的值是一个二进制。

常量可用在不必改变值的场合,如固定的数据表,字库等。

常量的定义方式有几种,下面来加以说明。

#define False 0x0; //用预定义语句可以定义常量

#define True 0x1; //这里定义False为0,True为1

//在程序中用到False编译时自动用0替换,同理True替换为1 unsigned int code a=100; //这一句用code把a定义在程序存储器中并赋值

const unsigned int c=100; //用const定义c为无符号int常量并赋值

以上两句它们的值都保存在程序存储器中,而程序存储器在运行中是不允许被修改的,所以如果在这两句后面用了类似a=110,a++这样的赋值语句,编译时将会出错。

5.八盏灯闪烁实验二:

#include //预处理命令,定义SFR的头文件,一般的C51程序都有

void delay(unsigned int time )

{

unsigned int a; //定义变量a为int类型

for (a=0; a< time; a++); //这是一个循环

}

void main(void) //主函数名

{

do{ //do while组成循环

delay(50000 ); //调用延时函数

P1 = 0x00; //设P1口全为低电平,点亮LED

delay(50000 ); //调用延时函数

P1 = 0xff; //设P1口全高电平,点亮熄灭LED

}

while(1);

}

知识点:带参数的函数

任务:

1)编写一个延时0.2ms的函数delay_200us( )

2)编写一个延时20ms的函数delay_20ms( )

3)编写一个延时1s的函数delay_s( )

4)编写延时函数delay_ms(unsigned char n) delay_ms(n)延时时间为n毫秒

5)编写延时函数delay_s(unsigned char n) delay_s(n)延时时间为n秒

6)编写程序,先让灯越闪越快,然后越闪越慢,又越闪越快,如此反复

提示:

1、一条空指令时长为1us

2、尽量使用unsigned型的数据结构。

3、尽量使用char型,实在不够用再用int,然后才是long。

4、如果有可能,不要用浮点型。

5、使用循环嵌套来实现长时间的延时

测试方法:

for (a=0; a<50000; a++)

{

delay_200us( );

}

P1 = 0x00;

for (a=0; a<50000; a++)

{

delay_200us( );

}

P1 = 0xFF;

3. 汽车转向灯实验

实验电路图如图所示:

键盘布局

6.按键控制灯程序:

编写程序,当按下1号键时,亮1盏灯,按下n号键时亮n盏灯,两种方法/* 使用if语句*/

#include //预处理命令

sbit P3_0 = P3^0;

sbit P3_1 = P3^1;

sbit P3_2 = P3^2;

sbit P3_3 = P3^3;

sbit P3_4 = P3^4;

sbit P3_5 = P3^5;

sbit P3_6 = P3^6;

sbit P3_7 = P3^7;

void main(void) //主函数名

{

//P3_0 = 1; //IO口做输入时要先置1

//P3_1 = 1;

P3 = 0xff;

do //do while组成循环

{

if(P3_0 == 0)

{

P0=0xfe;

}

if(!P3_1)

{

P0=0xfc;

}

if(!P3_2)

{

P0=0xf8;

}

if(!P3_3)

{

P0=0xf0;

}

if(!P3_4)

{

P0=0xe0;

}

if(!P3_5)

{

P0=0xc0;

}

if(!P3_6)

{

P0=0x80;

}

if(!P3_7)

{

P0=0x00;

}

}

while(1);

}

/* 使用case语句*/

#include //预处理命令

void main(void) //主函数名

{

unsigned char key_value; //定义变量b为unsigned char类型P3 = 0xff; //IO口做输入时要先置1

do //do while组成循环

{

key_value = P3;

switch( key_value )

{

case 0xfe:

P0 = 0xfe;

break;

case 0xfd:

P0 = 0xfc;

break;

case 0xfb:

P0 = 0xf8;

break;

case 0xf7:

P0 = 0xf0;

break;

case 0xef:

P0 = 0xe0;

break;

case 0xdf:

P0 = 0xc0;

break;

case 0xbf:

P0 = 0x80;

break;

case 0x7f:

P0 = 0x00;

break;

default:

break;

}

}

while(1);

任务1:模拟汽车方向灯。

设汽车尾部左右两侧各有3个指示灯(用6个发光管模拟),用独立按键来模拟汽车动作,要求是:

◆?? ??

1)、汽车正常行驶时(无按键),??灯亮。

2)、当汽车右转弯时(按下1号键),最右侧指示灯( )慢闪烁。

3)、当汽车左转弯时(按下2号键),最左侧指示灯(◆)慢闪烁。

4)、刹车时(按下3号键), ?灯同时亮。

5)、临时停车时警示(按下4号键),所有灯快闪烁。

任务2:模拟声控楼道灯。

提示:

1)把E 点接到单片机的IO 口。有声音时,E 点为高电平,相应IO 口为高电平,单片机检测到该IO 口为高电平后,点亮某灯10秒。 2)连续发声的处理。

D2LED

声音采集电路

4. 数字秒表实验

7段数码管简介

静态七段数码管(共阳)电路:

要想数码管DS9上显示1,则需要b段和c段亮,其他段灭,共阳,所以低电平亮,高电平灭,故而P0口需要输出11111001B,即0xf9,称为“1”的共阳段码。同理,可以推出其他数字的共阳段码。共阴段码呢?

实验电路图见PDF文档(单片机静态数码管电路图.pdf)所示:

7.按键控制静态数码管程序:

编写程序,当按下n号键时,数码管显示n。

/* 使用if语句*/

#include //预处理命令

sbit P3_0 = P3^0;

sbit P3_1 = P3^1;

sbit P3_2 = P3^2;

sbit P3_3 = P3^3;

sbit P3_4 = P3^4;

sbit P3_5 = P3^5;

sbit P3_6 = P3^6;

sbit P3_7 = P3^7;

const unsigned char LED_TAB[16]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xf8,0x80,0x90, 0x88,0x83,0xC6,0xA1,0x86,0x8e};

void main(void) //主函数名

{

unsigned char key_value;

P3 = 0xff;

P2 = 0xff;

do //do while组成循环{

if(P3_0 == 0)

{

key_value = 1;

}

if(!P3_1)

{

key_value = 2;

}

if(!P3_2)

{

key_value = 3;

}

if(!P3_3)

{

key_value = 4;

}

if(!P3_4)

{

key_value = 5;

}

if(!P3_5)

{

key_value = 6;

}

if(!P3_6)

{

key_value = 7;

}

if(!P3_7)

{

key_value = 8;

}

P0 = LED_TAB[key_value];

}

while(1);

}

任务1:数字秒表实验1。

设计一个数字秒表,要求如下:

1.秒表计时范围:0~99秒

2.使用两个静态共阳数码管分别显示时间的个位和十位

3.实现倒计时功能

8.动态数码管显示0 1 2 3 4 5 6 7 程序

/*

在动态数码管上显示0 1 2 3 4 5 6 7

*/

#include //预处理命令

//const unsigned char LED_TAB[16]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xf8, 0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e};

const unsigned char LED_TAB[16]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

/*延时1ms函数*/

void delay_1ms(void)

{

unsigned char temp = 249;

while(--temp);

temp = 249;

while(--temp);

}

void main(void) //主函数名

{

unsigned char i;

unsigned char dsSel = 0xfe;

do //do while组成循环

{

dsSel = 0xfe;

for(i = 0;i < 8;i++)

{

P2 = dsSel;

P0 = LED_TAB[i];

delay_1ms( );

dsSel <<=1;

dsSel |=0x01;

}

}

while(1);

}

思考:把延时时间改短,直至为0,观察数码管显示的变化,有什么现象,试分析原因,并解决之。

8.动态数码管显示数组ds_data[4]的值程序

/*

在动态数码管上显示数组ds_data[4] 的值

*/

#include //预处理命令

//const unsigned char LED_TAB[16]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xf8, 0x80,0x90, 0x88,0x83,0xC6,0xA1,0x86,0x8e};

const unsigned char LED_TAB[16]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 0x7f,0x6f, 0x77,0x7c,0x39,0x5e,0x79,0x71};

unsigned char ds_data[4] = {0x01,0x04,0x10,0x20};

/*延时1ms函数*/

void delay_1ms(void)

{

unsigned char temp = 249;

while(--temp);

temp = 249;

while(--temp);

}

/*

在动态数码管上显示数组data1[4] 的值,非常有用的一个函数,后面会用到

*/

void led_dsplay(unsigned char data1[4])

{

unsigned char i;

unsigned char dsSel = 0xfe;

dsSel = 0xfe;

for(i = 0;i < 4;i++)

{

P2 = dsSel;

P0 = LED_TAB[data1[i]&0x0f];

delay_1ms( );

dsSel <<=1;

dsSel |=0x01;

P0 = 0x00;

P2 = dsSel;

P0 = LED_TAB[(data1[i]&0xf0)>>4];

delay_1ms( );

dsSel <<=1;

dsSel |=0x01;

P0 = 0x00;

}

}

void main(void) //主函数名

{

do

{

led_dsplay(ds_data);

}

while(1);

}

任务2:数字秒表实验2。

设计一个数字秒表,要求如下:

1.秒表计时范围:0~99秒

2.使用8位动态共阴数码管分别显示时间的个位和十位

3.实现倒计时功能

5. 外部中断实验

(以下内容摘自平凡的单片机教程)

一、有关中断的概念

什么是中断,我们从一个生活中的例子引入。你正在家中看书,突然电话铃响了,你放下书本,去接电话,和来电话的人交谈,然后放下电话,回来继续看你的书。这就是生活中的“中断”的现象,就是正常的工作过程被外部的事件打断了。

仔细研究一下生活中的中断,对于我们学习单片机的中断也很有好处。第一、什么可经引起中断,生活中很多事件可以引起中断:有人按了门铃了,电话铃响了,你的闹钟闹响了,你烧的水开了….等等诸如此类的事件,我们把可以引起中断的称之为中断源,

单片机中也有一些可以引起中断的事件,8031中一共有5个:两个外部中断,两个计数/定时器中断,一个串行口中断。

第二、中断的嵌套与优先级处理:设想一下,我们正在看书,电话铃响了,同时又有人按了门铃,你该先做那样呢?如果你正是在等一个很重要的电话,你一般不会去理会门铃的,而反之,你正在等一个重要的客人,则可能就不会去理会电话了。如果不是这两者(即不等电话,也不是等人上门),你可能会按你通常的习惯去处理。总之这里存在一个优先级的问题,单片机中也是如此,也有优先级的问题。优先级的问题不仅仅发生在两个中断同时产生的情况,也发生在一个中断已产生,又有一个中断产生的情况,比如你正接电话,有人按门铃的情况,或你正开门与人交谈,又有电话响了情况。考虑一下我们会怎么办吧。

第三、中断的响应过程:当有事件产生,进入中断之前我们必须先记住现在看书的第几页了,或拿一个书签放在当前页的位置,然后去处理不同的事情(因为处理完了,我们还要回来继续看书):电话铃响我们要到放电话的地方去,门铃响我们要到门那边去,也说是不同的中断,我们要在不同的地点处理,而这个地点通常还是固定的。计算机中也是采用的这种方法,五个中断源,每个中断产生后都到一个固定的地方去找处理这个中断的程序,当然在去之前首先要保存下面将执行的指令的地址,以便处理完中断后回到原来的地方继续往下执行程序。具体地说,中断响应可以分为以下几个步骤:

1、保护断点,即保存下一将要执行的指令的地址,就是把这个地址送入堆栈。

2、寻找中断入口,根据5个不同的中断源所产生的中断,查找5个不同的入口地址。以上工作是由计算机自动完成的,与编程者无关。在这5个入口地址处存放有中断处理程序(这是程序编写时放在那儿的,如果没把中断程序放在那儿,就错了,中断程序就不能被执行到)。

3、执行中断处理程序。

4、中断返回:执行完中断指令后,就从中断处返回到主程序,继续执行。

究竟单片机是怎么样找到中断程序所在位置,又怎么返回的呢?我们稍后再谈。二、MCS-51中断系统的结构:

如图所示,由与中断有关的特殊功能寄存器、中断入口、顺序查询逻辑电路等组成,包括5个中断请求源,4个用于中断控制的寄存器IE、IP、TCON和SCON来控制中断类型、中断的开、关和各种中断源的优先级确定。

相关主题
文本预览
相关文档 最新文档