自己的学习总结文档,有些乱,勿怪
1、51单片机的延时计算
voidDelay10us()//@12.000MHz
{
unsignedchari;
_nop_();
_nop_();
i=27;
while(--i);
}
上面这段代码是用STC-ISP软件中的软件延时计算器给出的,选用的是8051指令集STC-Y5,延时10us。
以前都是直接这么拿来用的,今天却突然想搞个明白,为什么代码要这么写。
于是查了各方资料。
从单片机计时的源头找起,它由下面几部分依次组成。
首先是时钟周期的算法:时钟周期(T)=1(秒)/晶振频率。
(比如:上面代码的时钟周期为1/12M(秒))。
这是单片机的基本时间单位。是由晶振震荡出来的,也叫震荡周期。
其次是机器周期:机器周期是由时钟周期组成的,机器周期是单片机完成一个基本操作所需要的时间。
关于机器周期,每种单片机可能都不太一样,我也只用过传统51和STC这两款,就拿此来对比下
1传统的8051单片机:
它的1个机器周期是由12个时钟周期组成的。
以12M晶振举例,它的一个机器周期就是:12(个时钟周期)*1(秒)/12MHz=1(us)
2STC单片机:
拿我常用的STC12C5A60S2这款单片机来讲,它可以有两个模式选择,
一个是1T模式,在这个模式下STC单片机1个时钟周期就是1个机器周期;
另一个是12T模式,这个模式下STC单片就和传统的8051单片机一样,12个时钟周期组成1个机器周期。
由此可见1T模式的速度就是12T模式的12倍。
以12M晶振为例,1T模式下就可以算得机器周期是:
1(个时钟周期)*1(秒)/12Mhz=1/12(us)
最后是指令周期:这个是单片机执行一条指令所需要的时间,它是由机器周期组成的。
现在可以回到正文开头的代码中了。这个10us的函数是怎么得出来的呢?
这个我之前查过很多资料,比如执行while语句需要多少个机器周期。赋值需要多少个周期。也就是查这个占用了我很大一部分时间。直到最后将上面的延时函数直接调到main函数中debug调试,才明白,问题其实很简单啊。
无论是执行什么语句,最终都会回到汇编上来,debug里单步调试,所有的指令周期就会明明白白了。
我用main函数直接调用延时函数,如下:
voidDelay10us()//@12.000MHz
{
unsignedchari;
_nop_();
_nop_();
i=27;
while(--i);
}
main
{
Delay10us();
}
这里有个长转移指令LJMP,它要转移到C:0171行去执行Delay10us这个函数。
那执行LJMP这个指令需要多长时间呢,查找STC数据手册,在1T模式下,此条指令在单片机上运行需要4个时钟周期。
接下来,按单步调试F11键,如下图:
程序成功转移到C:0171行,跳转到Delay10us函数中,此行程序执行NOP指令,空操作。查STC数据手册,NOP指令占用1个时钟周期。
接下来C:0172行,依然是NOP指令,1个时钟周期。
接下来C:0173行,此行执行MOVR7,
typedefunsignedcharuchar;
typedefunsignedintuint;
sbitirs=P7^5;//红外发送
sbitK=P0^7;//按键总开关
sbitkey1=P0^0;//按键1
sbitkey2=P0^1;//按键2
uinthwcount,count;//要进中断的总次数、用于记录进入中断次数
ucharirsys[2]={0x00,0xff};//16位用户码
bithsflag=0;//发送38KHz载波标志位
ucharircode;//发送的红外数据
voidTimer1Init(void)//13微秒@12.000MHz
{
AUXR=0xBF;//定时器时钟12T模式
TMOD=0x0F;//设置定时器模式
TMOD|=0x20;//设置定时器模式
TL1=0xF3;//设置定时初值
TH1=0xF3;//设置定时重载值
TF1=0;//清除TF1标志
TR1=0;//定时器1关闭计时
ET1=1;//开定时器1中断
EA=1;//开总中断
}
voidTimer1_isr()interrupt3
{
count++;
if(hsflag)//有发射标志,则发射38khz
{
irs=~irs;
}
else//否则不发射,即相当于发射编码中的低电平
irs=1;
}
voidir_SByte()//红外发送一字节数据
{
uchari;
for(i=0;i8;i++)//一字节八位,循环八次
{
hwcount=43;//0.56ms高电平,需要进43次定时器1中断(560/13=43)
hsflag=1;//发射38KHz载波标志
count=0;//count置0,从这时起记录进入定时器1中断的次数
TR1=1;//定时器1开启计时
while(counthwcount);//在此等待,直到进入中断次数达到43次
TR1=0;//定时器1关闭计时
if(ircode0x01)//数据是从最低位开始发送的,最低位是1则要进130次中断
{
hwcount=130;//1.69ms低电平,进中断总次数130(1690/13=130)
}
else//最低位是0,则要进43次定时器1中断
{
hwcount=43;//0.565ms低电平,进中断总次数43(565/13=43)
}
hsflag=0;//低电平,不需要38kHz载波
count=0;
TR1=1;
while(counthwcount);
TR1=0;
ircode=ircode1;//将数据右移一位,即从低位到高位发送
}
}
voidir_S(uchardate)
{
hwcount=692;//(引导码中的)9ms高电平,9000/13=692
hsflag=1;//高电平需要38kHz载波
count=0;
TR1=1;
while(counthwcount);
TR1=0;
hwcount=346;//(引导码中)4.5ms低电平,4500/13=346
hsflag=0;//低电平不需要38kHz载波
count=0;
TR1=1;
while(counthwcount);
TR1=0;
ircode=irsys[0];//发送用户码的前8位
ir_SByte();
ircode=irsys[1];//发送用户码的后8位
ir_SByte();
ircode=date;//发送键值
ir_SByte();
ircode=~date;//发送键值反码
ir_SByte();
hwcount=43;//0.56ms高电平,560/13=43
hsflag=1;//高电平需要38kHz载波
count=0;
TR1=1;//定时器1开启计时
while(counthwcount);
TR1=0;//定时器1关闭计时
hwcount=43;//(NEC协议中的停止码)0.56ms低电平
hsflag=0;
count=0;
TR1=1;
while(counthwcount);
TR1=0;
irs=1;//关闭红外发射
}
voidmain()
{
K=0;//按键总开关拉低
Timer1Init();//定时器1初始化
while(1)
{
if(key1==0)//按键1
{
ir_S(0x8a);//发送键值8aH
}
if(key2==0)//按键2
{
ir_S(0xa6);//发送键值a6H
}
}
}
4.2按键代码
defineGPIO_KEYP0
bitflag=0;
/**********************************************
*函数名:Check_key
*描述:矩阵按键扫描(缺陷:不能通过按一次按键,给变量只加一)
*参数:无
*返回值:键值
*调用:外部调用
**********************************************/
//unsignedcharCheck_key(void)
//{
//unsignedcharrow,col,temp1,temp2,keyvalue;
//temp1=0x01;
//for(row=0;row4;row++)//行扫
//{
//P0=0xF0;//先将~置高
//P0=~temp1;//使~中有一位为0
//temp1*=2;//temp1左移一位
//if((P00xF0)0xF0)//当按键按下时,(P00xF0)高四位不在是F,可能为7或B或D或E。
//{//这时可以确定按下的是(row+1)行
//temp2=0x80;
//for(col=0;col4;col++)//列扫
//{
//if((P0temp2)==0x00)//当(P0temp2)等于0x00时,可以确定按下的位置是(col+1)列
//{
//keyvalue=row*4+col;//得到所按下按键的键值
//returnkeyvalue;//把得到的键值作为返回值
//}
//temp2/=2;//temp2右移一位
//}
//}
//}
//return16;//因为定义数码管段选表中,16对应的是全灭,故无按键按下时返回16
//}
/*************************************************
*函数名:delay_ms
*描述:延时函数
*参数:xms,xms是几延时几毫秒
*返回值:无
*调用:内部调用
*************************************************/
voiddelay_ms(unsignedintxms)
{
unsignedchari,j;
unsignedintx;
for(x=xms;x0;x--)
{
i=16;
j=147;
do
{
while(--j);
}while(--i);
}
}
/*************************************************
*函数名:key_scan
*描述:把按下的矩阵按键的键值返回
*参数:无
*返回值:按下的键值
*调用:外部调用
*************************************************/
unsignedcharkey_scan()
{
unsignedcharkeyvalue1,keyvalue2,a=0;
if(flag==0)
{
keyvalue2=16;
flag=1;
}
GPIO_KEY=0xf0;//高四位为1,低四位为0
if(GPIO_KEY!=0xf0)
{
delay_ms(10);//延时消抖
if(GPIO_KEY!=0xf0)
{
GPIO_KEY=0xf0;
switch(GPIO_KEY)
{
case0xe0:keyvalue1=3;break;//确定矩阵按键被按下的位置是第几列
case0xd0:keyvalue1=2;break;//0、1、2、3
case0xb0:keyvalue1=1;break;
case0x70:keyvalue1=0;break;
}
GPIO_KEY=0x0f;
//确定矩阵按键被按下位置的键值:列(或0或1或2或3)+行(或0或4或8或12)
if((GPIO_KEY!=0x0d)||(GPIO_KEY!=0x0b)||(GPIO_KEY!=0x07))
keyvalue2=keyvalue1;
if(GPIO_KEY==0x0d)
keyvalue2=keyvalue1+4;
if(GPIO_KEY==0x0b)
keyvalue2=keyvalue1+8;
if(GPIO_KEY==0x07)
keyvalue2=keyvalue1+12;
while((a50)(GPIO_KEY!=0x0f))
{
delay_ms(10);
a++;
}
}
}
if(GPIO_KEY==0xF0)
keyvalue2=16;
returnkeyvalue2;
}
4.3单片机红外解码源程序如下
include""
typedefunsignedcharuchar;
typedefunsignedintuint;
sbitir=P3^2;//红外接收
ucharirtime;//记录定时器0中断次数
ucharirdata[33];//存放接收到的33位红外数据的每位进入中断的次数
ucharbitnum;//数组下标,用于记录是第几位红外数据
ucharstartflag;//开始接收标志
ucharirok;//33位数据收集完成标志
ucharircode[4];//用于存放16位用户码+8位键值+8位键值反码
ucharirprosok;//四个码值转化完成标志
uchardisnum[8];//把四个码值分割成8位,用于数码管显示
voidInt0Init(void)//外部中断0初始化
{
IT0=1;//下降沿触发
EX0=1;//开启外部中断0
EA=1;//开总中断
ir=1;//红外接收置1
}
voidTimer0Init(void)//定时器0初始化,模式:12T,晶振:12MHz
{
TMOD=0x02;//定时器0模式2,8位自动重装载
TH0=0x00;//256*(1/12)*12=0.256ms
TL0=0x00;
ET0=1;//开定时器0中断
EA=1;//开总中断
TR0=1;//定时器0开始计时
}
voidirpros(void)//码值转换
{
ucharnum,k,i,j;
k=1;
for(j=0;j4;j++)//四个码值,循环四次
{
for(i=0;i8;i++)//每个码值八位,循环八次
{
num=num1;//从最低位开始接收
if(irdata[k]6)//判断这位数据是0还是1:(0:1.12/0.256=4.4)(1:2.25/0.256=8.8)
{
num=num|0x80;
}
k++;
}
ircode[j]=num;//存放码值
}
irprosok=1;//码值转换完成标志
}
voidirwork(void)//码值分割,用于数码管显示
{
disnum[0]=ircode[0]/16;
disnum[1]=ircode[0]%16;
disnum[2]=ircode[1]/16;
disnum[3]=ircode[1]%16;
disnum[4]=ircode[2]/16;
disnum[5]=ircode[2]%16;
disnum[6]=ircode[3]/16;
disnum[7]=ircode[3]%16;
}
voidInt0()interrupt0
{
if(startflag)
{
if(irtime32irtime63)//8~16ms
{
bitnum=0;
}
irdata[bitnum]=irtime;//存放每位进中断的次数
irtime=0;//清零,为下次计数做准备
bitnum++;//下标加一
if(bitnum==33)//判断是否33位数据接收完
{
bitnum=0;
irok=1;//接收完成标志
}
}
else
{
irtime=0;
startflag=1;
}
}
voidTimer0()interrupt1
{
irtime++;
}
voidmain()
{
Int0Init();
Timer0Init();
while(1)
{
if(irok==1)//接收完成
{
irpros();
irok=0;
}
if(irprosok==1)//码值转换完成
{
irwork();
irprosok=0;
}
display(0,disnum[4]);//显示键值
display(1,disnum[5]);
display(2,20);//显示"H"
}
}
总结下,下面这一个代码比较清晰,思路,利于理解,明确了中断和定时的时间,而不像前面的是利用单片机的机器周期,指令周期来确认。