工学第八章 8051内部资源C语言编程.docx
《工学第八章 8051内部资源C语言编程.docx》由会员分享,可在线阅读,更多相关《工学第八章 8051内部资源C语言编程.docx(47页珍藏版)》请在冰豆网上搜索。
工学第八章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语言编程
C51编译器支持在C源程序中直接开发中断程序。
前面已经讲过,中断服务程序是通过按规定语法格式定义的一个函数。
(中断有关内容)
编号
中断源
入口地址
0
外部中断0
0003H
1
定时器/计数器0
000BH
2
外部中断1
0013H
3
定时器/计数器1
001BH
4
串行口中断
0023H
中断服务程序的函数定义的语法格式如下:
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;//置工作方式,
TR0=0;
TF0=0;
TH0=0xf8;
TL0=0x30;
TR0=1;//开定时器T0
for(;;)//无限循环
{
while(!
TF0);//循环等待,TF0为1时退出
P1_0=!
P1_0;//AT89C51的头文件中已经定义
TL0=0x30;//献给TL0赋值,精度要高一点!
TH0=0xf8;
TF0=0;
}
}
计数器在生产线等场合应用的很多,下面看定时器用做计数的编程。
思路:
计数器初始化,如果计数总值小于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;//打开定时器T1
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机接收校验和进行校对。
B机接收正确结束,在此B发送一个正确的标志符“00”;否则发送错标志“FF”不正确要求重发,A机在重复发送。
本例晶振频率为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);//用异或判断收到的是否为bb,相同为“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);//等待B机回复发送是否正确
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步进电机的控制
步进电机的工作原理
步进电动机是用脉冲信号控制的,下图中定子有3对磁极,转子有4个齿,
工作过程:
A相通电:
A相磁极与0、2号齿对齐;
(A、B相通电,则0、1对A、B’,2、3对A’、B)
B相通电:
由于磁力线作用,B相磁极与1、3号齿对齐;
C相通电:
由于磁力线作用,C相磁极与0、2号齿对齐;
A相通电:
由于磁力线作用,A相磁极与1、3号齿对齐;
结论:
定子按A->B->C->A相轮流通电,则磁场沿A、B、C方向转动180度角,磁场每改变一次,转子转过30度。
在步进电机中,控制绕组每改变一次通电方式,称为一拍,每一拍转子就转过一个步距角。
上述的运行方式每次只有一个绕组单独通电,控制绕组每换接三次构成一个循环,故这种方式称为三相单三拍。
若按A-AB-B-BC-C-CA顺序通电,每次循环需换接6次,故称为三相六拍,因单相通电和两相通电轮流进行,故又称为三相单、双六拍。
步进电机的步距角按下式计算:
N是电动机工作拍数;Z是转子的齿数
三相三拍4齿的步距角=
三相六拍4齿的步距角=
实际采用的步进电机的步距角多为3度和1.5度,也有0.9度和1.8度,还可以细分。
步距角越小,机加工的精度越高。
为产生小步距角,定、转子都做成多齿的,图中转子40个齿,定子仍是6个磁极,但每个磁极上也有五个齿。
转速大小仅与脉冲频率成正比,通过改变脉冲频率的高低可以大范围地调节电机的转速,能实现快速起动、制动、反转,而且有自锁的能力,不需要机械制动装置,不经减速器也可获得低速运行。
转过一周的步数是固定的,只要不丢步,角位移误差不存在长期积累的情况,主要用于数字控制系统中,精度高,运行可靠。
步进电动机工业过程控制和仪表中的主要控制元件之一。
例:
三相六拍控制方式
步进电机与单片机的连接如图所示。
采用三相六拍控制方式正转绕组通电顺序为:
A→AB→B→BC→C→CA→A,P1口发出的控制字为01H→03H→02H→06H→04H→05H→01H;
反转绕组通电顺序为:
A→CA→C→CB→B→BA→A,P1口发出的控制字为01H→05H→04H→06H→02H→03H→01H;
由P2_0控制转向P2_0=0为正向,否则为反向。
由P2_1控制转速调节,P2_2控制退出转速调节。
源程序如下:
#include
#defineucharunsignedchar
#defineuintunsignedint
uchardataplus[7]={0x01,0x03,0x02,0x06,0x04,0x05,0x00};
//正向,0x00用于判断一个循环是否结束
uchardataminu[7]={0x01,0x05,0x04,0x06,0x02,0x03,0x00};
//反向
uchark=0;//中断标志,置初值
ucharidata*x;//存放通电顺序的数组地址
uchardl=1;//由dl控制转速,dl越小,速度越大
voidmain(void)
{bitcf;//控制转向的位变量
uintn,i;//n为步数
cf=P2_0;//控制转向
if(cf==0)x=plus;//送正向控制数组首地址
elsex=minu;//送反向控制数组首地址
TMOD=0x01;//定时器T0工作方式1
if(P2_1==0)//由P2_1控制进入转速控制调节
{while
(1)//进入转速调节
{for(i=0;i<0xff;i++);//软件延时,以便能看到调节变化
while(P2_1==0);//P2_1按下,转速减小
dl++;
if(dl==100)dl=1;//dl最大为100
if(P2_2==0)gotolab;//用P2_2退出速度调节
}}
lab:
n=0x01ff;//设置步数
TH0=(65536-dl*500)/256;
TL0=(65536-dl*500)%256;//设置定时器初值,dl越小