第八章 单片机8051内部资源C语言编程.docx
《第八章 单片机8051内部资源C语言编程.docx》由会员分享,可在线阅读,更多相关《第八章 单片机8051内部资源C语言编程.docx(46页珍藏版)》请在冰豆网上搜索。
![第八章 单片机8051内部资源C语言编程.docx](https://file1.bdocx.com/fileroot1/2023-7/21/61a2dcc1-2d2c-4edb-b4bf-ffa7bef61af7/61a2dcc1-2d2c-4edb-b4bf-ffa7bef61af71.gif)
第八章单片机8051内部资源C语言编程
第八章8051内部资源C语言编程
一、I/O口编程
例1:
用按键控制发光二极管。
并口是用的最多的资源,下面以P1口为例,见电路图,P1口低4位接了4个按钮,高4位接了4个指示灯,要求按下相应的按钮,对应的指示灯亮。
P10对应P14,依次类推。
指示灯是端口输出高电平亮。
源程序如下:
#include
voidmain()
{
unsignedchardatax,i;
while
(1)
{
P1=P1|0x0f;//给低4位置1,高4位不变
x=P1&0x0f;//读低4位
x=~x;//低4位取反
P1=x<<4;//左移4位
for(i=0;i<255;i++);//延时
}
}
(端口编程)
注意上面几种常见的用法。
二、中断的C语言编程
编号
中断源
入口地址
0
外部中断0
0003H
1
定时器/计数器0
000BH
2
外部中断1
0013H
3
定时器/计数器1
001BH
4
串行口中断
0023H
C51编译器支持在C源程序中直接开发中断程序。
前面已经讲过,中断服务程序是通过按规定语法格式定义的一个函数。
(中断有关内容)
中断服务程序的函数定义的语法格式如下:
Void函数名(void)interruptm[usingn]{中断程序代码;}
m为MCS-51中断源编号,见表
usingn选项用于实现工作寄存器组的切换,n是中断服务子程序中选用的工作寄存器组号(0-3)。
例2:
设AT89C52的时钟频率为12MHz,利用定时中断在其P1.0引脚输出周期为4ms,占空比为1:
1的方波。
确定定时器工作方式和计算定时器初值。
选用定时器T0工作方式1,每个机器周期为1μs,翻转一次电平需要2ms,则
计数次数n=2000/1=2000,
初值x=65536-2000=63536=F830H
参考程序如下:
#include
sbitP10=P1^0;//定义位
voidclock_initial()reentrantusing0//在中断中调用,定义为重入函数
{TR0=0;
TH0=0XF8;//装载计数初值
TL0=0X30;
TR0=1;}
main()
{
TMOD=0x01;//定时器T0方式1工作
P10=0;//初始值为低电平
TF0=0;//清除中断标志位
clock_initial();
ET0=1;
EA=1;
do{}while
(1);//死循环,等价于汇编语言的SJMP$
}
voidclk_int(void)interrupt1using0
{
P10=!
P10;//逻辑变量
clock_initial();
}
(延时中断)
例3:
图示是利用优先权解码芯片74LS148,在单片机8031的一个外部中断INT1上扩展多个中断源的原理电路图。
图中是以开关闭合来模拟中断请求信号。
当有任一中断源产生中断请求,能给8031的INT1引脚送一个有效中断信号,由P1的低3位可得对应中断源的中断号。
74LS148是8线-3线优先编码器,
为输入端,
为编码输出端,
为扩展端。
真值表见右图。
只要有输入的中断请求,
为低电平,申请中断。
同时根据P10,P11,P12的值,可以判断出是那一个中断源提出的中断申请。
在中断服务程序中仅设置标志,并保存I/O口输入状态。
参考程序如下:
#include
unsignedcharstatus;
bitflag=0;
voidservice_int1()interrupt2using2//INT1中断服务程序,使用第3组工作寄存器
{flag=1;//设置标志
status=P1&0x07;//存输入口状态}
voidmain(void)
{IP=0x04;//置INT1为高优先级中断
IE=0x84;//INT1开中断,CPU开中断
for(;;)//无限循环
{if(flag)//有中断
{switch(status)//根据中断源分支
{case0:
{中断1程序;}break;//处理IN0
case1:
{中断2程序;}break;//处理IN1
case2:
{中断3程序;}break;//处理IN2
case3:
{中断4程序;}break;//处理IN3
case4:
{中断5程序;}break;//处理IN4
case5:
{中断6程序;}break;//处理IN5
case6:
{中断7程序;}break;//处理IN6
default:
{中断8程序;}//处理IN8
}
flag=0;//处理完成清标志
}}}
三、定时器/计数器的c语言编程
定时器/计数器的有关内容:
定时器的编程,要选择定时器/计数器的工作方式,如果要定时,需要计算初值,溢出时相应中断标志置1。
下面用例子就进行说明。
例4:
将中断中的例2采用查询的方法实现。
采用AT89C51单片机。
参考程序如下:
#include
main()
{
TMOD=0x01;//置工作方式,
for(;;)//无限循环
{
TR0=0;
TF0=0;
TH0=0xf8;
TL0=0x30;
TR0=1;//开定时器T0
do{}while(!
TF0);//循环等待,TF0为1时退出
P1_0=!
P1_0;//AT89C51的头文件中已经定义
}
}
计数器在生产线等场合应用的很多,下面看以下定时器的编程。
思路:
计数器初始化,如果计数总值小于65535,直接取出TL0、TH0中的数据即可,如果大于65535,则要设置一个存储单元进行软件计数,本程序用软件计数。
每中断一次,计数值增加65535。
例5、由P3.4输入脉冲信号,用定时器T0进行计数,并不断输出计数值。
#include
typedefunsignedcharuchar;
#defineuintunsignedint;
ucharclow,chigh;
unsignedlongcvalue=0;//用全局变量传递数据
voidc_initial()
{
TCON=0x00;//将中断标志和开关全置0
TH0=0x00;
TL0=0x00;
TR0=1;//开定时器T0
}
uintc_module()//从TL0、TH0中取数据并计数
{unsignedlongdatax;
do
{
chigh=TH0;//读取数据
clow=TL0;
}while(chigh!
=TH0);//(item1)
x=cvalue*65536+chigh*256+clow;//计数总值
returnx;
}
voidmain()
{
TMOD=0x05;//设T0为16位(方式1)计数状态,
c_initial();//初始化
ET0=1;//开中断
EA=1;
do{c_module();
(显示程序和其他数据处理程序)
}while
(1);
}
voidc_int(void)interrupt1using1//中断程序,计溢出中断的次数
{
cvalue=cvalue+1;
}
(定时中断1)
item1;在THO、TL0中取数时,有一个时间差,先取TH0,然后在取TL0,在取TL0前,TL0可能进位。
因此取完TL0后返回来再取TH0判断是否变化,取值过程中若有进位,则重新取值。
四、串行口的C语言编程
(串行通信有关内容)
串行口通信,采用T1定时器作为波特率发生器,对不同的工作方式,波特率的设置是不同的,数据的位数也不同。
下面举几个例子:
例6:
在单片机中,printf的默认输出是串行口,用串行口输出字符“helloworld”。
#include
#include
voidmain(void)
{
SCON=0x50;//串行通信方式1,10位
TMOD=0x20;//定时器T1方式2
TH1=243;//置初值
TL1=243;
TR1=1;//开定时器
TI=1;//用串行口软件仿真输出时,必须这样设置
while
(1){
printf("HelloWorld\n");
}}
以上程序很简单,但在keil软件上进行仿真时很有用。
注意,和原例有修改,原例是用Monitor-51可以在目标硬件上调试程序。
例7:
要求每按一下按键(P10口),从串行口发出一个字符,该程序主要用于串行通信是否正常工作的调试。
和计算机通信时,由于各种原因,有时经常调试不通,可以用最简单的程序进行调试,调好后再调试应用程序。
下面是一段调试单片机与微机用VB编程通信的一段调试程序。
波特率9600B/S,24MHz的晶振频率,定时器初值的确定,当SMOD=1时,舍入误差较小(作业已做过)。
取x=243=0xf3.
#include
#defineucharunsignedchar;
#defineuintunsignedint;
voidmain()
{
uchardatax=5;//待发送的数
TMOD=0x20;//定时器T1方式2
TL1=0xf3;//置初值
TH1=0xf3;
SCON=0xd8;//串行通信方式3
PCON=0x80;//SMOD=1
TR1=0;
while
(1)
{while(P1_0!
=0);//当按键未按下时等待
while(P1_0==0);//当按键未放开时等待
TR1=1;
SBUF=x;
while(TI==0);
TI=0;
TR1=0;}
}
(串行通信)
例8:
点对点的串行异步通信
1、通信双方的硬件连接
2、程序流程图及编程
点对点通信双方基本等同,只是人为规定一个为发送,一个为接收。
要求两机串行口的波特率相同,因而发送和接收方串行口的初始化相同。
可编制含有初
始化函数、发送函数接收函数的程序,在主函数中根据程序的发送、接收设置TR,采用条件判别决定使用发送函数还是接收函数。
这样点对点通信的双方都可运行此程序,只需在程序运行之前人为设置选择TR,一个令TR=0,一个令
TR=1,然后分别编译,在两机上分别装入,同时运行。
A机开始发送时,先送一个AA信号,B机接收到AA信号后,回答一个BB信号,表示准备好可以接受。
然后A机发送,B机接收,A机边发送边求校验和,B机边接收边求校验和,发送完后发校验和,B机接收校验和进行校对。
不正确要求重发。
本例晶振频率为11.0592MHz,波特率为1200。
初值为:
下面程序是将发送和接收都合在一个程序中,可以分别装入两个单片机系统中,由语句“#defineTR1”确定发送或接收,TR=0发送,TR=1为接收。
3、参考源程序
#include
#defineucharunsignedchar
#defineTR1//发送与接收差别值TR=0发送,TR=1为接收
ucharidatabuf[16];
ucharpf;//求和
voidinit(void)//串行口初始化
{TMOD=0x20;//设T/C1为定时方式2
TH1=0xe8;//设定波特率
TL1=0xe8;
PCON=0x00;
TR1=1;//启动T/C1
SCON=0x50;//串行口工作在方式1
}
voidsend(ucharidata*d)//发送字符,参数用指针说明
{uchari;//下面是两机联络
do{
SBUF=0xaa;//发送联络信号
while(TI==0);//等待发送结束
TI=0;
for(i=1;i<0xff;i++);//注意发送完以后要等待接收,否则出错
}while((SBUF^0xbb)!
=0);//B机未准备好,继续联络
RI=0;//接收后RI=1,软件清0
do{//以下为发送数据,每发送一个都要求和。
pf=0;//清校验和
for(i=0;i<16;i++)
{SBUF=d[i];//发送一个数据
pf+=d[i];//求校验和
while(TI==0);TI=0;//等待发送结束
}
SBUF=pf;//发送校验和
while(TI==0);TI=0;//等待发送结束
while(RI==0);
RI=0;//等待B机回答
}while(SBUF!
=0);//回答出错,则重发
}
voidreceive(ucharidata*d)//接收字符
{uchari;
do{while(RI==0);
RI=0;//等待接收
}while((SBUF^0xaa)!
=0)//判A机请求否
SBUF=0xbb;//发应答信号
while(TI==0);TI=0;//等待发送结束
while
(1)
{pf=0;//清校验和
for(i=0;i<16;i++)
{while(RI==0);RI=0;
d[i]=SBUF;//接收一个数据
pf+=d[i];}//求校验和
while(RI==0);RI=0;//接收A机校验和
if((SBUF^pf)==0)//接收的校验和与自加得比较
{SBUF=0x00;//校验和相同,回答0X00
while(TI==0);TI=0;
break;}//校验和相同发"00"
else
{SBUF=0xff;//出错发"FF",重新接收
while(TI==0);TI=0;}
}
}
voidmain(void)
{init();
if(TR==0)
{send(buf);//参数为数组名,指针
}
else
{receive(buf);
}
}
(串行通信1)
将上面的程序发送与接收分开,接收程序如下:
#include
#defineucharunsignedchar
ucharidatabuf[16];
ucharpf;//求和
voidinit(void)//串行口初始化
{TMOD=0x20;//设T/C1为定时方式2
TH1=0xe8;//设定波特率
TL1=0xe8;
PCON=0x00;
TR1=1;//启动T/C1
SCON=0x50;//串行口工作在方式1
}
voidreceive(ucharidata*d)//接收字符
{uchari;
do{while(RI==0);
RI=0;//等待接收
}while((SBUF^0xaa)!
=0)//判A机请求否
SBUF=0xbb;//发应答信号
while(TI==0);TI=0;//等待发送结束
while
(1)
{pf=0;//清校验和
for(i=0;i<16;i++)
{while(RI==0);RI=0;
d[i]=SBUF;//接收一个数据
pf+=d[i];}//求校验和
while(RI==0);RI=0;//接收A机校验和
if((SBUF^pf)==0)//接收的校验和与自加得比较
{SBUF=0x00;//校验和相同,回答0X00
while(TI==0);TI=0;
break;}//校验和相同发"00"
else
{SBUF=0xff;//出错发"FF",重新接收
while(TI==0);TI=0;}
}
}
voidmain(void)
{init();
receive(buf);
}
发送程序如下:
#include
#defineucharunsignedchar
ucharidatabuf[16];
ucharpf;//求和
voidinit(void)//串行口初始化
{TMOD=0x20;//设T/C1为定时方式2
TH1=0xe8;//设定波特率
TL1=0xe8;
PCON=0x00;
TR1=1;//启动T/C1
SCON=0x50;//串行口工作在方式1
}
voidsend(ucharidata*d)//发送字符,参数用指针说明
{uchari;//下面是两机联络
do{
SBUF=0xaa;//发送联络信号
while(TI==0);//等待发送结束
TI=0;
for(i=1;i<0xff;i++);//注意发送完以后要等待接收,否则出错
}while((SBUF^0xbb)!
=0);//B机未准备好,继续联络
RI=0;//接收后RI=1,软件清0
do{//以下为发送数据,每发送一个都要求和。
pf=0;//清校验和
for(i=0;i<16;i++)
{SBUF=d[i];//发送一个数据
pf+=d[i];//求校验和
while(TI==0);TI=0;//等待发送结束
}
SBUF=pf;//发送校验和
while(TI==0);TI=0;//等待发送结束
while(RI==0);
RI=0;//等待B机回答
}while(SBUF!
=0);//回答出错,则重发
voidmain(void)
{init();
send(buf);//参数为数组名,指针
}
五、综合编程举例
例9、定时器程序,用到定时器、定时器中断、端口按键控制并用串行口输出,是一个片内资源的综合应用实例,程序是由本人编写,并用keil软件仿真通过,但不是所有的功能全部调试过,难免还有错误。
从思路上,不是完美的,但通过亲自编程,体会了该程序编写中的难点,以便更好的给大家介绍。
晶振按24MHz,用定时器0方式1,计数次数为25000/0.5=50000,初值为65535-50000=15535=3CAFH。
该程序有分辨大、小月、2月,24-12显示功能,有时间调整功能。
参考程序:
#include
#include
#include
#defineucharunsignedchar
#defineuintunsignedint
sbitan1=P1^0;//调整时间功能转换
sbitan2=P1^1;//加1
sbitan3=P1^2;//减1
sbitan4=P1^3;//12-24显示转换
sbitan5=P1^4;//显示年月日与时分秒转换
sbitan6=P1^5;//待定
uchardataclow,chigh,year=4,month=12,day=31,hour=23,minute=59,second=0,m,x;
uintdatan=0;
voidc_initial()reentrantusing1
{
TCON=0x00;
TH0=0x3c;
TL0=0xaf;
TR0=1;
SCON=0x50;
TH1=221;
TR1=1;
TI=1;
}
add(ucharm)//加1
{uchari;
if(an2==0)
{for(i=0;i<0xff;i++);
while(an2==0);
m++;
returnm;
}
sub(m)//减1
{uchari;
if(an3==0)
{for(i=0;i<0xff;i++);
while(an3==0);
m--;
returnm;
}
}
xianshi(uints1,uints2,uints3)//显示
{
printf("%d",s1);
printf("%d",s2);
printf("%u\n",s3);
}
voidmain()
{
uchardatai,j=0;
bitn=0;
TMOD=0x21;
c_initial();
ET0=1;
EA=1;
while
(1)
{
lab0:
while(an1==0);
if(an4==0)
{for(i=1;i<0xff;i++);//防弹跳
while(an4==0);
n=!
n;}
if(an5==0)xianshi(year,month,day);//显示年、月、日
else
{if(n==0)//24制式显示hour
xianshi(hour,minute,second);//显示时、分、秒
else
{if(hour>12)
xianshi(hour-12,minute,second);//12小时制式
else
xianshi(hour,minute,second);}
}
if(an1==0)
{while(an1==0);//调整年
while
(1)
{if(an2==0)
{year=add(year);
if(year==99)year=0;}
if(an3==0)
{year=sub(year);
if(year==0)
year=99;}
if(an1==0)
gotolab2;
}
lab2:
while(an1==0);//调整月
while
(1)
{
if(an2==0)
{month=add(month);
if(month>=13)
month=1;}
if(an3==0)
{month=sub(month);
if(month==0)
month=12;}
if(an1==0)gotolab3;
}
lab3:
while(an1==0);//调整天
while
(1)
{
if(an2==0)
{day=add(day);
if(month==1||month==3||month==5||month==7||month==8||month==10||month==12)
{