单片机c51参考资料.docx
《单片机c51参考资料.docx》由会员分享,可在线阅读,更多相关《单片机c51参考资料.docx(63页珍藏版)》请在冰豆网上搜索。
![单片机c51参考资料.docx](https://file1.bdocx.com/fileroot1/2023-1/3/080ff514-6b28-4a29-8b05-61ab0095ebe5/080ff514-6b28-4a29-8b05-61ab0095ebe51.gif)
单片机c51参考资料
第4章学习C51例题,入门C简单程序设计
本章的例题都是简单C语言例题,例题中没有复杂的C语言语法与难以理解的数据结构,因此特别适合入门学习C语言。
本章所有例题都在实验板上实验过,实验用51单片机电路板的接线图如图4-1所示。
图4-1实验电路接线图
图中单片机为AT89S51或是STC89C51RC,其P0、P1口连接共阳极数码管,P2口连接8个低电平有效的LED灯(发光二极管),P3口连接8个低电平有效的按钮。
若是采用STC89C51RC单片机,由于ISP需要占用串行口,若是不切换引脚,则与P3.0和P3.1相连的按钮不能使用。
若是采用AT89S51,由于SPI编程需要P1.5、P1.6和P1.7引脚通信,所以编程后,需要切换引脚,才能使P1口连接的数码管正常显示。
4.1第一部分
[例题4-1]控制P2_0引脚相连的LED灯闪烁。
该程序只能用软件单步仿真,因为在实际的单片机上运行,使P2_0引脚变为低电平后,立刻又将其变为高电平,眼睛不能看到LED灯闪烁,但是使用软件单步仿真,可以看到P2_0引脚的电平变化。
#include"AT89X51.H"//包含头文件
voidmain(void)//主程序
{
while
(1)//无限循环
{
P2_0=0;//亮灯
P2_0=1;//灭灯
}}
软件单步仿真的窗口如图4-2所示。
图4-2软件单步仿真的窗口
[例题4-2]使P2_0引脚相连的LED灯闪烁。
C源程序如下:
#include"AT89X51.H"//头文件
voidmain(void)//主程序
{
unsignedintn;//声明变量
while
(1)//无限循环
{
P2_0=~p2_0;//引脚取反,与引脚相连的LED灯闪烁
for(n=0;n<2000;n++);//循环语句延时
}}
编译并链接后,在图4-3所示的仿真窗口仿真。
图4-3例题1-2所示的仿真窗口
可以在图4-4所示屏幕左下角的命令窗口输入变量名后回车的方法查看变量;也可以屏幕右下角的Local窗口或是Watch窗口观察变量。
图4-4查看变量
选择View/MomoryWindow菜单,屏幕弹出图4-5所示的存储器窗口。
图4-5存储器窗口
在Address对话框输入:
“d:
0x00”就可以看到data空间的从0x00开始的所有内存。
如图4-5。
输入“i:
0x00”,就可以看到idata空间的所有内存的值。
输入“x:
0x00”,就可以看到xdata空间的所有内存的值。
输入“c:
0x00”,就可以看到code空间的所有程序。
[例题4-3]如下程序实现流水灯的方法是,依次灭掉前一个灯,然后点亮后一个灯,再延时一会,不断循环,就可以看到流水灯的效果了。
源程序如下:
#include"AT89X51.H"//头文件
voidmain(void)//主程序
{
unsignedintn;
while
(1)
{
P2_3=1;P2_0=0;//灭掉P2_3,点亮P2_0=0
for(n=0;n<20000;n++);//循环延时
P2_0=1;P2_1=0;//灭掉P2_0,点亮P2_1=0
for(n=0;n<20000;n++);//循环延时
P2_1=1;P2_2=0;//灭掉P2_1,点亮P2_2=0
for(n=0;n<20000;n++);//循环延时
P2_2=1;P2_3=0;//灭掉P2_2,点亮P2_3=0
for(n=0;n<20000;n++);//循环延时
}}
[例题4-4]双按键控制的LED灯。
用单片机读取按键的值,并使用一个与P3-1引脚相连的按键点亮与P2_0引脚连接的LED,用另一个与P3_2引脚相连的按键关闭与P2_0引脚连接的LED。
用单片机可以读取某个IO的值,因为51单片机的I/O口,如果处于输出1的状态(51上电后I/O就默认为1),这时的I/O口内部简化成为一个几十K的电阻上拉到电源VCC(P0口除外),因此可以作为输入引脚。
P0口没有上拉电阻,相当于一个悬空的引脚,就是高阻状态,如果用P0口,必须在外部接上拉电阻。
这里用的是内部有上拉电阻的P3口连接按键。
如果直接读一个没有与地短路按键的I/O引脚,就会读到高电平,就是逻辑1。
如果这个I/O引脚通过按键与地短路。
这时会读到低电平,就是逻辑0。
该例的源程序如下:
#include"AT89X51.H"
voidmain(void)//主程序
{
while
(1)
{
if(P3_1==0)//判断按键,如果按键按下(逻辑0),则执行将LED点亮的动作
{P2_0=0;}
if(P3_2==0)//判断按键,如果按键按下(逻辑0),则执行将LED灭掉的动作
{P2_0=1;}
}}
该例的仿真窗口如图4-6所示。
图4-6例题1-4的仿真窗口
[例题4-5]单按键控制LED发光,就是用一个与P3_0引脚连接的按键控制P2_0相连LED的亮和灭两种状态。
按一次按键灯亮,再按一次按键灯灭。
再按一次又亮,再按一次灯又灭。
程序用一个位(bit)变量Mark来做一个标记,然后在按键的控制下,使标记变化,再根据这个标记的值,使LED亮或灭。
因为按键按下时可能会有抖动的情况,就是每次按下按键时,可能会发生多次接点的接触,相当于按键一下子按下了很多次,导致程序识别多次按键的输入。
按键的抖动一般都是发生在刚按下按键和松开按键的时候,只要避开这一段时间,等按键稳定按下或者松开时,就可以正确读取按键的状态。
所以,当读到第一次按键的值时,要等待一会,躲过按键抖动后,再处理其他操作;在松开后,也需要等一会,免得程序检测到松开的抖动以为又是按键按下。
(在更复杂的应用中,需要在按下延时之后重新验证按键是否按下)
因为程序是循环运行的,当一次按键处理后,又会再循环回来继续检测,如果您的按键这时还没有松开,就又会被读到一次,并做处理。
所以还要做一个特殊的处理,识别到一个按键并处理完成之后,还要等待这个按键松开后,再继续循环运行。
源程序如下:
#include"AT89X51.H"
voidmain(void)//主程序
{
bitmark;//定义位变量
unsignedintn;//定义循环变量
while
(1)//无限循环
{
if(P3_0==0)//如果按键按下
{
for(n=0;n<1000;n++);//延时一段时间,等待按键完全按下
mark=~mark;//翻转标记
while(!
P3_0)//等待按键弹起
for(n=0;n<1000;n++);//等待按键完全弹起
}
P2_0=mark;//点亮或是关灭LED灯
}}
[例题4-6]定时器中断控制LED闪烁。
由于51单片机从中断发生到进入中断的时间不定,是3至8个机器周期,在进入了中断后,软件才重新置定时器初始值,这样就会存在定时误差。
不是精确定时,如果要精确定时,需要使用定时器自动装载方式,也就是在定时器溢出的同时,硬件逻辑就自动把定时器初始值装载进去,而不是在中断服务程序里赋初始值,只有这样才可以实现精确定时,在精确定时的情况下,定时误差由晶振的频率不稳定引起。
源程序如下:
#include"AT89X51.H"
voidmain(void)//主程序
{
TMOD=0X01;//定时器0,工作模式1,16位定时模式,GATE=0,C/T=0
TR0=1;//启动定时器
ET0=1;//允许定时器中断
EA=1;//允许总中断
while
(1);//无限循环
}
timer0()interrupt1//定时器0中断服务程序
{
TH0=0X00;//写入定时器TH初值00H
TL0=0X06;//写入定时器TL初值06H,计数器溢出值为65530
P2_0=~P2_0;//闪烁LED
}
[例题4-7]精确定时0.5s。
在定时器中断服务函数里,设置了一个静态变量kk,静态变量kk的值在进入函数时是不会被初始化的,而是保持上次的值。
它用来计数定时器的溢出次数,也就是T0中断服务函数进入的次数,每溢出2000次,就是间隔0.5秒,使LED灯亮灭一次。
源程序如下:
#include"AT89X51.H"
voidmain(void)//主程序
{
TMOD=0x02;//定时器0,工作模式2(0000,0010),8位定时模式
//GATE=0,C/T=0,
TH0=0x06;//写入预置初值到定时器TH,预置6,使250微秒溢出一次(12MHz)
TL0=0x06;//写入预置值
TR0=1;//启动定时器
ET0=1;//允许定时器中断
EA=1;//允许总中断
while
(1);//无限循环
}
timer0()interrupt1//定时器0中断服务程序
{
staticunsignedintkk;//设置局部静态变量
kk++;//每中断一次加1
if(kk==2000)//当中断2000次后,相当于0.5秒0.25ms*2000=0.5s
{kk=0;
P2_0=~P2_0;//闪烁LED
}}
[例题4-8]精确定时的流水灯。
所有的中断都要尽快的运行和退出,中断服务程序越短越好,这样才不至于干扰主程序的工作和其他中断的运行。
所以应该尽量把程序代码从中断服务函数里搬到主程序中运行。
对于定时器的中断的工作方式,可以建立一个全局变量的标记,在中断服务程序中置位这个标记,然后就退出中断服务。
在主程序里检查到这个全局变量标记之后,就运行相关的程序。
对于CPU任务比较多的程序来说,这种工作方式可以获得较好的工作效率。
采用查表的方式,将要点亮的LED灯预先设置好,到了指定的时间,就一起将LED灯亮灭信息送到P2口。
源程序如下:
#include"AT89X51.H"
unsignedintldelay=0;//长定时溢出标记ldelayt,预置值是0
voidmain(void)//主程序
{
unsignedcharcodeledp[4]={0xfe,0xfd,0xfb,0xf7};//预定的灯亮灭顺序,写入P2
unsignedintledi;//用来确定表格位置的变量
TMOD=0x02;//定时器0,工作模式2(0000,0010),8位定时模式
//GATE=0,C/T=0,
TH0=0x06;//写入预置初值到定时器TH,预置6,250微秒溢出一次(12MHz)
TL0=0x06;//写入预置值
TR0=1;//启动定时器
ET0=1;//允许定时器中断
EA=1;//允许总中断
while
(1)//无限循环
{
if(ldelay==1)//时间溢出标记为1,处理如下事件
{
ldelay=0;//清除溢出标记
P2=ledp[ledi];//读出一个表格值送到P2口
ledi++;//指向下一个表格值
if(ledi==4)//如果表格查过一遍
ledi=0;//指向第一个表格值
}}}
timer0()interrupt1//定时器0中断服务程序
{
staticunsignedintkk;//定义静态局部变量
kk++;//每次中断服务程序执行,kk增加1
if(kk==2000)//T0的预置值0x06,溢出2000次就是0.5秒钟,晶振12MHz
{
kk=0;//如果中断服务程序执行2000次,则执行下一个语句
ldelay=1;//将该标记置1,以便主程序处理
}}
[例题4-9]变速流水LED灯。
控制流水LED灯的流动速度从慢到快自动变化。
程序中设置了一个变量sp,用来保存流水灯的移动速度,也是定时器的累计时间溢出次数。
在主程序中定时修改sp的数值,溢出的时间就会改变,流水LED灯的移动速度也就改变了。
在每跑完一圈,就改变一次速度。
源程序如下:
#include"AT89X51.H"
unsignedintldelay=0;//定义长定时溢出标记,预置是0
unsignedintsp=23;//定义变速标记,预置是20
voidmain(void)//主程序
{
unsignedcharcodeledp[4]={0xfe,0xfd,0xfb,0xf7};
//预定的灯亮灭顺序表格,用于写入P2口
unsignedintledi;//用来确定表格位置的变量
TMOD=0x02;//定时器0,工作模式2(0000,0010),8位定时模式
TH0=0x06;//写入预置初值到定时器TH,预置6,则250微秒溢出一次(12MHz)
TL0=0x06;//写入预置值
TR0=1;//启动定时器
ET0=1;//允许定时器中断
EA=1;//允许总中断
while
(1)//无限循环
{
if(ldelay==1)//若是时间溢出标记为1,处理如下语句
{
ldelay=0;//清除时间溢出标记
P2=ledp[ledi];//读出一个表格值送到P2口
ledi++;//指向下一个表格值
if(ledi==4)
{
ledi=0;//到了最后一个灯就换到第一个
sp--;//速度级别减1
if(sp==3)//如果速度级别减到3
sp=23;//恢复速度最慢的级别23
}}}}
timer0()interrupt1//定时器0中断服务程序
{
staticunsignedintkk;//定义静态局部变量
kk++;//每次中断服务,kk加1
if(kk==(100*sp))//最少次数为100*sp=300,时间为300次*0.25ms
//最多次数为2300次,时间为2300次*0.25ms
//如果kk=(100*sp),执行如下语句
{
kk=0;
ldelay=1;//当kk值与100*sp值相等,将ldelay标记置1,则处理灯亮与显示速度
}}
[例题4-10]4个按键控制4个流水速度的流水LED灯
用4个按键,控制流水LED灯的4种不同的跑动速度。
每按一个键,就赋给定时器0一个不同的溢出次数,使流水速度发生变化。
主程序执行了2个任务:
一个是流水LED灯,一个是检测按键。
源程序如下:
#include"AT89X51.H"
unsignedintldelay=0;//长定时溢出标记,预置是0
unsignedintsp=10;//变速标记,预置是10
voidmain(void)//主程序
{
unsignedcharcodeledp[4]={0xfe,0xfd,0xfb,0xf7};//预定的LED灯亮灭顺序数组
unsignedintledi;//用来确定表格位置的变量
TMOD=0x02;//定时器0,工作模式2(0000,0010),8位定时模式
TH0=0x06;//写入预置初值到定时器TH,预置6,则250微秒溢出一次(12MHz)
TL0=0x06;//写入预置值
TR0=1;//启动定时器
ET0=1;//允许定时器中断
EA=1;//允许总中断
while
(1)//无限循环
{
if(ldelay==1)//如果溢出标记为1,处理如下语句
{
ldelay=0;//清除溢出标记
P2=ledp[ledi];//读出一个表格值送到P2口
ledi++;//指向下一个表格值
if(ledi==4)
{
ledi=0;//返回第1个表格值
}}
if(P3_0==0)sp=3;//按键P3_0设置流水灯速度
if(P3_1==0)sp=8;
if(P3_2==0)sp=13;
if(P3_3==0)sp=18;
}}
timer0()interrupt1//定时器0中断服务程序
{
staticunsignedintkk;//定义静态局部变量
kk++;//每次中断服务,kk加1
if(kk==(100*sp)||(kk>2000))//kk=(100*sp)||(kk>2000)时,执行如下语句
//kk溢出次数为100*sp,最少为300次,最多为2000次
{
kk=0;
ldelay=1;//每次溢出后,将ldelay标记置1
}}
[例题4-11]单按键控制流水LED灯的流水速度。
用一个按键来实现流水LED灯的10级调速。
每按一次按键,流水速度就降低一级,共10级。
源程序如下:
#include"AT89X51.H"
unsignedintldelay=0;//长定时溢出标记,预置是0
unsignedintsp=10;//变速标记,预置是10
unsignedintspr=1;//按键按下次数标记
voidmain(void)//主程序
{
unsignedcharcodeledp[4]={0xfe,0xfd,0xfb,0xf7};//预定LED灯亮灭的表格
unsignedintledi,n;//ledi用来指示显示顺序,n是循环变量
TMOD=0x02;//定时器0,工作模式2(0000,0010),8位定时模式
TH0=0x06;//写入预置初值6到定时器TH,则250微秒溢出一次(12MHz)
TL0=0x06;//写入预置值
TR0=1;//启动定时器
ET0=1;//允许定时器中断
EA=1;//允许总中断
while
(1)//无限循环
{
if(ldelay==1)//溢出标记为1,处理如下语句
{
ldelay=0;//清除溢出标记
P2=ledp[ledi];//读出一个值送到P2口
ledi++;//指向下一个显示值
if(ledi==4)
{
ledi=0;//指向表格的第一个显示值
}}
if(P3_0==0)//如果读到按键K1为0
{
for(n=0;n<1000;n++);//等待按键稳定延时
while(!
P3_0);//等待按键松开循环
for(n=0;n<1000;n++);//等待按键稳定松开延时
spr++;//按键次数加1
if(spr==10)spr=1;//如果按键按下10次,则返回1
sp=spr*2;//流水LED灯速度等于按键次数乘以2
}}}
timer0()interrupt1//定时器0中断
{
staticunsignedintkk;//定义静态局部变量
kk++;//每次中断服务,kk加1
if(kk==(100*sp)||(kk>2200))//kk溢出次数为100*sp,最少为200次,最多为2000次
{
kk=0;
ldelay=1;//每次溢出,使ldelay=1
}}
[例题4-12]按照预定的流水速度控制流水LED灯。
首先预先定义了一个变化的顺序speedcode,每次流水灯循环一圈,就根据预定设置的表格数据来决定下一圈的流水速度。
这样就实现了流水LED灯速度按照预定的顺序自动变化。
#include"AT89X51.H"
unsignedintldelay=0;//溢出标记,预置是0
unsignedintsp=10;//变速标记,预置是20
unsignedintcodespr[10]={3,1,5,12,3,20,2,10,1,4};//10个预定义流水速度的表格
voidmain(void)//主程序
{
unsignedcharcodeledp[4]={0xfe,0xfd,0xfb,0xf7};//预定的流水LED灯亮灭表格
unsignedintledi,i;//定义指向灯亮灭顺序表格ledi与流水速度表格的元素的变量i
TMOD=0x02;//定时器0,工作模式2(0000,0010),8位定时模式
TH0=0x06;//写入预置初值6到定时器TH,则250微秒溢出一次(12MHz)
TL0=0x06;//写入预置值
TR0=1;//启动定时器启动
ET0=1;//允许定时器中断
EA=1;//允许总中断
while
(1)//无限循环
{
if(ldelay==1)//溢出标记为1,执行如下语句
{
ldelay=0;//清除溢出标记
P2=ledp[ledi];//读出灯亮灭顺序表格到P2口
ledi++;//指向表格的下1个元素
if(ledi==4)
{
ledi=0;//指向表格的第1个元素
sp=spr[i];//按照流水速度表格设定流水灯速度
i++;//指向速度表格中的下1个元素
if(i==10)i=0;//如果速度为10,则返回速度0
}}}}
timer0()interrupt1//定时器0中断服务程序
{
staticunsignedintkk;//设置静态局部变量
kk++;//每次中断,kk加1
if(kk==(100*sp)||(kk>2200))//溢出次数为100*sp
{
kk=0;//溢出次数清零
ldelay=1;//溢出标记为1
}}
[例题4-13]外引脚中断控制LED灯。
外引脚中断,就是外部I/O引脚INT0和INT1产生的中断。
对应的引脚是P3_2和P3_3。
当按下连接在P3_2引脚的按键,使其接地时,可以触发一个INT0中断,控制LED灯的亮或灭。
源程序如下:
#include"AT89X51.H"
voidmain(void)//主程序
{
IT0=1;//外中断负边沿产生中断
EX0=1;//外中断0使能
EA=1;//允许总中断
while
(1);//主程序无限循环,没有循环体
}
int0()interrupt0//外中断0服务程序
{P2_0=!
P2_0;}//在中断服务程序中闪烁LED灯
[例题4-14]脉冲宽度调制(PWM)方式控制LED灯亮度
在一定的频率的方波中,调整高电平和低电平的占空比,即可实现LED灯亮度控制。
如图4-7所示,程序中使用定时器0产生2.5ms周期脉冲,使用占空比控制变量scale控制占空比,在低电平期间使LED灯亮,在高电平期间使LED灯灭,改变scale就改变了高电平与低电平的时间,因此也就控制了LED灯的亮度。
图4-7PWM占空比控制示意图
源程序如下:
#include"AT89X51.H"//模拟PWM输出控制灯的10个亮度级
unsignedintscale;//占空比控制变量
voidmain(void)//主程序
{
unsignedintn;//延时循环