自己的学习总结文档,有些乱,勿怪

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"

}

}

总结下,下面这一个代码比较清晰,思路,利于理解,明确了中断和定时的时间,而不像前面的是利用单片机的机器周期,指令周期来确认。