for(j=0;j<200;j++);
}
voidmain(void)//主函数
{
while
(1)//程序死循环
{
LED1=0;//P0.0输出低电平,LED1灭
delay(100);//调用延时函数,延时一段时间,约0.3秒,不精确
LED1=1;//P0.0输出高电平,LED1亮
delay(100);
}
}
/*****************************************************************************/
二、程序说明
(1)因为使用的单片机芯片为STC89C51,因此程序包含reg51.h文件,reg51.h文件定义了51单片机所有特殊功能寄存器的名称定义和相对应的地址值;
(2)单片机程序顺序执行程序,先执行主函数,在主函数内可以调用分函数,分函数可以调用分函数,但分函数不能调用主函数,程序执行一条命令再执行下一条,执行完毕后返回到主函数入口进行下次循环。
延时的过程是单片机执行了一个delay(100)函数而浪费一段时间。
在执行delay()的过程中,如果没有中断,单片机只能忙这一件事情,单片机在执行此函数相关指令时浪费和占用的时间就是执行延时函数获得的时间,但利用delay()不能得到精确的延时。
延时函数还可以利用带有形参的函数实现,例如:
/**************************/
voiddelay(unsignedintx)
{
while(x)
x--;
}
/**************************/
(3)利用位定义命令让LED等价于P0.0,程序执行LED1=1后,P0.0对以的单片机内部位寄存器就设置为高电平,P0.0端口输出高电平,单片机的所有I/O口都可位位定义,也可以字节定义。
(4)单片机C语言程序设计需要的C语言关键字不多,并且在keilC中用到的关键字是独有的,因此对于没有学习过C语言的人学习单片机C语言程序设计困难并不是很大,重点掌握单片机C语言书写格式和怎样用C语言控制单片机的硬件资源皆可;另外,在编程时,还要有清晰的逻辑思维头脑和认真实践,由浅逐步深入学习,当你坚持到最后时,单片机C语言程序设计会让你感觉很简单。
(5)每个人在利用C语言编写单片机程序时都有自己的风格。
一般情况下,函数的字符左行距为0,其下每条语句前留一个“tab”键空。
算数逻辑符号的左右各留一个空格,关键语句要有中文或英文说明,每一个函数有时也可以用“/**.....**/”上下隔开,这样有助于提高程序的层次感和可读性。
四、程序编译
程序设计采用第一章介绍的Kiel软件。
针对本节例子,在电脑上运行Kiel,首先新建一个项目,项目使用的单片机为AT89C51,这个项目暂且命名为LED;然后新建一个文件,并保存为”LED.c”文件,并添加到工程项目中。
由于本案例程序比较简单,可以直接在Keil软件界面中编写,也可以先把程序清单形成一个TXT文件,然后剪切到Keil的程序编辑界面中。
当程序设计完成后,通过Kiel编译并创建LED.HEX目标文件,见图1-2-2所示。
在Keil的应用过程中,由于编译过程成产生很多文件,因此新建一个项目需在一个目录中建立。
Keil软件创建的HEX文件可以下载到单片机的程序存储器中运行,也可以被仿真软件调用。
图1-2-3Keil软件目标文件创建
3.1.3汇编语言嵌入
在使用C语言所开发的项目中,有时需要写一些汇编语言的程序,以提高程序的精炼和精确度。
如本例中的delay()函数延时不够精确,可以使用汇编语言实现时间延迟,提高延时的精确程度。
在一个项目中要加入汇编语言程序时,有两种方法,第一种方法是使用Inlineassembly,也就是在程序中直接加入汇编语言码,如以下的程序所示
/**********延时函数************/
voiddelay100us()
{
#pragraendasm
more:
movR3,#48
djnzR3,$
djnzR7,more
#pragmaendasm
}
/*******************************/
在上面的程序中,直接加入汇编语言码时是使用编译指令#pragmaasm和#pragmaendasm。
编译指令#pragmaasm和#pragmaendasm之间则加入您需要的汇编语言代码。
这一段程序主要是实现100微秒时间延迟,使用汇编语言我们可以通过如表2-1-9所示的计算方法估计出延迟时间大约是0.1ms。
表2-1-9计算方法
执行的指令
执行次数
指令执行周期
计算结果
More:
movR3,#48
1
1
1
djnzR3,$
1×48
2
96
djnzR7,more
1
2
2
最后还有一条返回指令ret,所以总共需要100条指令,如果外接12MHz的石英晶体时,每执行一条指令需要1µs,所以执行这一个子程序总共需要100µs,也就是0.1µs.但是这只是一个估计值,因为执行子程序时如果有中断发生,就会造成执行时间增加。
第二种做法是将汇编语言的程序写在一个文件中,然后再从主程序中调用这些使用汇编语言写的子程序。
C语言主程序调用汇编语言子程序时,必须注意到,累加器(accumulator)和寄存器给的内容都必须自行维护,如果维护不当,可能会造成不可预期的结果。
一般而言,可以在进入汇编语言程序之后,马上将累加器A和寄存器R0~R7推入堆栈当中,等到要离开汇编语言程序之前再使用POP指令,将累加器A和寄存器R0~R7从堆栈中取出,但是要特别注意,堆栈推入与取出的顺序是相反的,也就是先进者后出,后进者先出。
3.1.4软件仿真
在安装过Proteus软件的PC上运行ISIS文件,即可进入Proteus电路原理仿真界面,利用该软件仿真时操作比较简单,其过程是首先构造电路,然后双击单片机加载HEX文件,最后执行仿真。
Proteus界面以及本案例的仿真电路见图所示。
电路中单片机采用AT89C51,单片机默认为最小系统,不需要再外接晶体振荡电路和复位电路。
仿真过程中,单片机加载程序模拟运行实际状态。
图1-2-4Proteus仿真
软件仿真是程序设计结果的验证,能够在没有硬件的条件下验证程序的完整性。
计算机是单片机程序设计的重要工具,但单片机的程序设计或相关产品开发必须有相关的软件和硬件,软件仿真虽然节约了一定硬件的投入,但软件仿真不能测试软件的安全性和可靠性,也不能测试电路的完整性。
程序的设计往往需要软件仿真和硬件仿真结合,并且在有限的时间里熟练完成项目的设计工作。
3.1.4程序下载与硬件仿真
把目标文件LED.HEX从PC机上加载、拷贝或传输到单片机的程序存储器的过程为单片机的程序下载。
要完成单片机程序的下载,首先有一个单片机程序烧写器,单片机的实验板大多支持在线下载,因此在有单片机实验板的情况下,还要有一个能在PC机运行的下载工具或软件,由于单片机无法直接与PC机联机,因此程序下载还需要一个接口电路。
常用的单片机下载接口有并口、串口和USB接口三种下载方式,随着计算机应用普及和技术发展,进来的PC机已经省去了并口和串口,因此单片机的下载接口使用的USB接口较多。
下面重点介绍USB下载接口和不同单片机下载所使用的软件。
一、STC系列单片机的USB接口程序下载
(1)下载电路
STC系列单片机支持串口下载,可以利用MAX232等电平转化芯片,直接把PC机的串口输出电平转换为TTL电平,然后把信号送往单片机串行引脚即可实现串口下载。
一些芯片如PL2303、CH341等可以把PC机的一个USB接口模拟一个串口,这样很方便的就可以实现单片机程序的USB接口下载。
图1-2-4为经常使用PL2303HX下载电路,图中的USB接口接PC机,R、T分别接单片机的RXD、TXD引脚。
图1-2-4PL2303HX连接的USB接口下载电路
PL2303HX支持USB1.1协议,但需要在PC机上先安装PL2303HX的驱动程序。
接口电路第一次接入PC机时,PC机会弹出一个对话窗口,发现新硬件并安装驱动,同时会在PC机的硬件设备管理器界面里增加一个串口,见图1-2-5所示。
这时就可以利用专用的软件利用此模拟串口下载单片机程序了。
图1-2-5USB下载电路模拟的串口设备
(2)程序下载与硬件仿真
STC单片机下载软件为STC_ISP,程序包安装后运行界面如图1-2-6所示。
程序下载之前先在程序界面左上角MCUType项中选择与实际实用的单片机型号,然后点击“OpenFile”按钮,选择要下载的HEX文件,接着点击“下载”按钮,这是需要对实验板或下载器的电源断电一次,STC系列单片机重启后,内部会先自动加载下载管理程序,只要与下载接口硬件连接无误,就可以顺利下载程序,从而实现硬件仿真。
二、AT89S51程序的USB接口下载
(1)下载电路
(2)下载软件
(3)下载过程
思考题
2-1电路见题图2-1所示,完成程序设计并在Proteus中设计电路,仿真实现LED闪烁。
题图2-1
2-2跑马灯又叫流水灯,能够达到明灭交替顺序显示的效果,利用单片机的P0口驱动8只LED可以实现跑马灯效果,程序中可以先让P0=0x01,再加入延时,然后让P0左移一位,依次循环,并判断如果P0为0时,从新赋值0x01。
请你完成这个程序的设计,并在Proteus中设计电路仿真实现。
3.2数码管显示
数码管是单片机常用的数字或字符显示部件,本节任务是让单片机P0口驱动一个共阳型的数码管显示依次0~9并循环,并编写程序仿真实现。
通过简单的程序设计让单片机驱动数码管显示,重点掌握数码管的字形编码和显示原理,并了解数组的应用和程序设计过程的程序优化技巧。
2.2.1数码管的显示原理
一、数码管的字形编码
单片机系统常用的数码管有共阳型和共阴型两种类型,它是单片机常用的外围显示器件。
两种类型的数码管外形和结构类似,只是数码管内部组成数码段和标点的LED接法有区别,共阳型数码管的内部所有LED的正极接在一起为公共极引脚,负极分别引出,依次命名为a、b、c、d、e、f、g、dop,见图2-2-1所示数码管电路符号。
使用时,共阳型数码管的公共极接正极,其他引脚分别接驱动电路,数码管显示时低电平有效。
数码管可以显示0到9共十个数字,如果加上小数点的显示,驱动一个数码管显示至少需要8位有效数据。
驱动数码管显示数字的8位数据编码见表2-2-1和2-2-2所示,其中表2-2-1为共阳型数码管编码,表2-2-2为共阴型编码。
由于共阴型数码管内部所有LED的负极接在一起,所以数码管显示时驱动数据高电平有效。
表2-2-1共阳数码管显示编码
显示数字
dot
g
f
e
d
c
b
a
16进制
0
1
1
0
0
0
0
0
0
0xc0
1
1
1
1
1
1
0
0
1
0xf9
2
1
0
1
0
0
1
0
0
0xa4
3
1
0
1
1
0
0
0
0
0xb0
4
1
0
0
1
1
0
0
1
0x99
5
1
0
0
1
0
0
1
0
0x92
6
1
0
0
0
0
0
1
0
0x82
7
1
1
1
1
1
0
0
0
0xf8
8
1
0
0
0
0
0
0
0
0x80
9
1
0
0
1
0
0
0
0
0x90
表2-2-2共阴数码管显示编码
显示数字
dot
g
f
e
d
c
b
a
16进制
0
0
0
1
1
1
1
1
1
0x3f
1
0
0
0
0
0
1
1
0
0x06
2
0
1
0
1
1
0
1
1
0x5b
3
0
1
0
0
1
1
1
1
0x4f
4
0
1
1
0
0
1
1
0
0x66
5
0
1
1
0
1
1
0
1
0x6d
6
0
1
1
1
1
1
0
1
0x7d
7
0
0
0
0
0
1
1
1
0x07
8
0
1
1
1
1
1
1
1
0x7f
9
0
1
1
0
1
1
1
1
0x6f
二、数码管的驱动电路
本节任务所需的电路只需在单片机的最小系统基础增加一个数码管即可。
在图2-2-1中,单片机的P0口接一只共阳数码管,其中P0.0~P0.7口分别接数码管的a~dot引脚,P0的每个端口只要有低电平输出,对应的数码管的那个段就显示。
如让数码管显示1,数码管b、c段亮,程序控制P0输出0xbe十六进制编码即可,因此共阳数码管显示0~9十进制数字,需要利用10个显示码组成的数组。
小数点在不用时一般不让显示,高位端口P0.7输出高电平即可。
图2-1-2单片机驱动共阳数码管电路
由于P0每个端口的灌电流达20mA,数码管每段LED正常显示5mA即可,因此电路中需要R2~9八个电阻用来限制数码管每一段电流,以防止驱动电流过大而烧毁器件。
在利用Proteus软件仿真时,数码管采用Optoelectronics元件库中7-SegmentDisplays下的7-SEG-COM-ANODE元件,可以不接限流电阻。
数码管是单片机常用的显示器件,在实际应用中,为了保证数码管使用安全,一般在P0口和数码管之间加有限流电阻。
另外,很多器件如三极管、继电器、蜂鸣器、步进电机等都要用到单片机的I/O口驱动。
单片机的P0口在不加上拉电阻的情况下只能驱动低电平有的负载,P1、P2和P3由于采用场效应管互补对称输出方式,高电平和低电平都有电流出,除P0口以外的其他I/O口作驱动输出应用时可以把上拉电阻省去。
三、程序设计
数码管显示0到9数字过程中,数字的变化需要有一定的时间间隔,因此程序还要用到delay()函数。
在程序设计过程中,可以把数码管的字形编码做在一个数组里面,为了让P0口依次输出0~9数字,让P0口的内容依次在数组中取值即可。
程序流程见程序清单如下:
/*****************************************************************************/
#include
codeunsignedcharseven_seg[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};//字形数组
voiddelay(unsignedintx)/*时间延迟函数*/
{
unsignedinti;
unsignedcharj;
for(i=0;i}
voidmain(void)
{
unsignedchari;//变量i作为数组的0~9编号
P2=0;//P2.0=0,通过反相器反相后,加在数码管公共端上的电压为正
while
(1)
{
P0=seven_seg[i];//输出0~9到共阳七段显示器
delay(1000);//调用时间延迟函数delay()
i++;
if(i==10)i=0;
}
}
/*****************************************************************************/
本案例程序中,当程序中使用常量数据时,如共阳数码管数字显示编码、液晶显示器的汉字编码等,一般希望这些数据当程序下载到单片机时存放在单片机的ROM区,对此类数据声明前面需要加上关键字code或const,如数码管的显示编码。
另外,在本案例中用到了数组和函数调用以及文件包含等操作,这些操作在单片机C语言程序中经常用到,下面将简单介绍数组和函数的基本概念。
2.2.2数组
在本案例中,数码管的显示码是一组有规律的同类型数据,如果定义大量的简单变量,程序将变得非常繁琐。
为了处理方便,C语言把具有相同类型的若干变量或常量,用一个带下标数组定义。
对各个变量的相同操作可以利用循环改变下标值来进行重复的处理,使程序变得简明清晰。
带下标的变量由数组名称和用方括号括起来的下标共同表示,称为数组元素。
通过数组名和下标可直接访问数组的每个元素。
数组有两个特点:
一是其长度是确定的,在定义的同时确定了其数组的大小,在程序中不允许随机变动;二是其元素必须是相同类型,不允许出现混合类型。
一、一维数组
在C语言中使用数组必须先进行定义或声明,一旦定义了一个数组,系统就将在内存中为其分配一个所申请大小的空间,该空间大小固定,以后不能改变。
一维数组的定义格式为
数据类型数组名[常量表达式];
在C语方中规定,一个数组的名字表示该数组在内存中所分配的一块存储区域的首地址,因此,数组名是一个地址常量,不允许对其进得修改。
“常量表达式”表示该数组拥有的元素个数,即定义了数组的大小,必须是正整数。
例如,以下语句定义了int型的长度为10的一维数组
unsignedcharseven_seg[10]
在定义了一个数组后,系统在内存在分配一块连续的存储空间用于存储数组。
一个数组中的元素下标必须从0开始。
所以,定义数组时,若“常量表达式”指出数组长度为N,数组元素下标只能从0到n-1。
“常量表达式”能包含常量,但不能包含变量。
二、一维数组元素的引用
在程序中,一维数组元素可以直接作为变量或常量直接饮用,其的引用格式为
数组名[下标]
其中,“下标”可以是整型常量或是整型表达式。
下标是数组元素到数组开始的偏移量,第一个元素的偏移量是0(亦称0号元素),第二个元素的偏移量是1(亦称1号元素),依此类推。
例如,seven_seg[5]表示引用数组seven_seg[]的下标为5的元素,即0x99。
三、一维数组的初始化
每个数组元素可以表示一个变量,对数组的赋值也就是对数组元素的赋值。
在定义数组的语句中,可以直接为数组赋值,这称为数组的初始化。
数组的初始化方法是将数组元素的初值信存放在由大括号括起来的初始值表中,每个初值之间由逗号隔开。
2.1.3函数调用与文件包含
按照一定顺序把单片机的程序在一个函数或一个中全部完成,是简单的单片机系统程序设计常用的一种结构,当程序只有几十行或几百行的时候,采用顺序结构编程的方法很容易让人看明白。
如果一个程序超过几千行的时候,分析就会变得很复杂。
单片机C语言程序也支持模块化设计,在模块化程序设计过程,经常会用到函数的调用、文件的包含问题。
C语言的模块化程序设计给单片机编程带来很大的方便,在这里以本节程序为例,简单介绍在Keil软件中实现模块化设计常用的技巧。
一、自定义函数
程序中经常反复执行的部分可以写成一个函数,然后就可以在程序中反复地调用。
以下是函数的一般格式
函数类型函数名称(参数序列);
{
函数的主体
}
其中函数类型用来设置一个函数被调用之后所返回数值的类型,如果用户希望写一个不返回任何数据的函数时,可以将函数类型设为void。
(1)无返回值函数
本节案例中delay()函数声明和调用情况为
/*********************************************/
voiddelay(unsignedintx)//没有返回值,有形参
{
unsignedinti;
unsignedcharj;
for(i=0;ifor(j=0;j=200;j++);
}
voidmain(void)
{
…….
while
(1)
{
……..
delay(1000);//调用时间延迟函数,有实参
……
}
}
/