
// 中断函数注意养成指定寄存器组的习惯
//不同优先级的中断程序绝对不能使用同一组寄存器
/*****编程时防止中断把寄存器中的数据改变的解决方法是给中断指定寄存器,
同优先级的使用同一组没事。
1、写中断程序一定要用using语句指定寄存器组。第1、2、3组都可以,不能是0.
2、51单片机的中断有两个优先级。一个中断不会打断另一个相同优先级的中断。
这样相同级别中断可以使用同一个组。比如:低优先级的中断函数都
用 using 1,高优先级的中断都用 using 2 。这样不会冲突。
下面是一个正常的例子:
C程序: void int0() interrupt 0 using 1
默认5个中断时同级的,不会冲突,但是最好养成好习惯
不指定中断要使用的寄存器,每次都要入栈保护数据,中断完还要出栈,代码会增加32字节
完整代码下载:http://www.51hei.com/f/hwxx52.rar
********************************************************************/
#include <stc12c2052ad.h>
#include <intrins.h>
#define uchar unsigned char
#define uint unsigned int
//少占鱼制作 河北正定欢迎您 长沙航空职业技术学院 2010 年QQ:41165643
//
//定义Flash 操作等待时间及允许IAP/ISP/EEPROM 操作的常数
//#define ENABLE_ISP 0x80 //系统工作时钟<30MHz 时,对ISP_CONTR 寄存器设置此值
//#define ENABLE_ISP 0x81 //系统工作时钟<24MHz 时,对ISP_CONTR 寄存器设置此值
#define ENABLE_ISP 0x82 //系统工作时钟<20MHz 时,对ISP_CONTR 寄存器设置此值
//#define ENABLE_ISP 0x83 //系统工作时钟<12MHz 时,对ISP_CONTR 寄存器设置此值
//#define ENABLE_ISP 0x84 //系统工作时钟<6MHz 时,对ISP_CONTR 寄存器设置此值
//#define ENABLE_ISP 0x85 //系统工作时钟<3MHz 时,对ISP_CONTR 寄存器设置此值
//#define ENABLE_ISP 0x86 //系统工作时钟<2MHz 时,对ISP_CONTR 寄存器设置此值
//#define ENABLE_ISP 0x87 //系统工作时钟<1MHz 时,对ISP_CONTR 寄存器设置此值
union union_temp16
{
uint un_temp16;
uchar un_temp8[2];
}my_unTemp16;
uchar Byte_Read(uint add); //读一字节,调用前需打开IAP 功能
void Byte_Program(uint add, uchar ch); //字节编程,调用前需打开IAP 功能
void Sector_Erase(uint add); //擦除扇区
void IAP_Disable(); //关闭IAP 功能
sbit JIESHOU=P1^0; //接收指示灯
sbit FASHE=P1^1; //发射指示灯
sbit KEY=P3^5;
sbit cin=P3^2; //接收端
sbit contrl=P3^0;//发射控制端
sbit khz=P3^1;//38KHZ产生 ,由T1设置
/****************************************************************/
void delayms(uint);
void ADC();
void InitADC();
void init1();
void init2();
void fashe();
void jieshou();
void delayus(uchar i);
void led(uchar x);
/******************************************************/
uint voltage;
bit receive=0;//接收标志
bit flag=0;//低电平记录完成标志
bit end=0;
bit finish=1;
uchar a[43]={121,1,3,4,44,55,24,156,35};//间接寻址的高128RAM,内部256RAM高128只能间接寻址
uchar j=0;
uint zu=0,addr=0; //扇区地址
uchar k;//按键代号
uchar m=0;//写EEPROM时用来移动数组的
uchar b[6];//用来存储每一组数据的总字节数
/**********************************************************************/
/****************************************************************/
void main()
{
delayus(5);
delayms(1000);
InitADC();//这里对其他用到P1口的地方有影响,尽量放前面
contrl=0;//关闭38K输出
KEY=1;
Sector_Erase(0x0000);//擦除扇区 1
Sector_Erase(0x200);
Sector_Erase(0x400);
Sector_Erase(0x600);
Sector_Erase(0x800);
Sector_Erase(0xa00);
Sector_Erase(0xc00);//擦除扇区 7
addr=0xc00;
FASHE=0;
JIESHOU=0;
FASHE=1;
delayms(4900);
for(j=6;j>0;j--)//j是数据总长度,如此判断,不会存储多余的空位
{ Byte_Program(addr,a
??);//从本组数据对应扇区首地址开始写EEPROM
m++; //数组下移
addr++; //地址下移
delayms(1);} //for end
m=Byte_Read(0xc00);
if(m==121)
{
JIESHOU=1;
delayms(6000);
}
m=0;//下面还要用,所以清0
addr=0;
zu=0;
FASHE=0;
KEY=1;
JIESHOU=0;
delayms(1000);
Sector_Erase(0x0000);//擦除扇区 1
Sector_Erase(0x200);
Sector_Erase(0x400);
Sector_Erase(0x600);
Sector_Erase(0x800);
Sector_Erase(0xa00);
Sector_Erase(0xc00);//擦除扇区 7
EX1=1; //开外部中断1
IT1=1;//外部中断1边沿触发,不然按住的时候一直中断
EA=1;
delayms(1);
//等待按键时两个灯灭
while(1)
{
if(receive)//外部按键中断1,正式进入接收函数
{
FASHE=0;//发射指示灯
JIESHOU=1; //接收指示灯
delayms(3000);
JIESHOU=0;
delayms(2);
FASHE=1;
delayms(3000);
FASHE=0;
jieshou(); //接收函数是T0定时开始后计数满溢出跳出的
}
if(!KEY) //KEY为0时进入发射模式
{
JIESHOU=0;
delayms(200);
FASHE=1;
delayms(200);
KEY=1;
fashe();
}
}
}
/********************************************************************/
//接收函数初始化
void init1()//接收初始化
{
finish=1;
EA=0; //因为下面要写EEPROM,必须关闭EA
TMOD=0x01;//T0方式1
TH0=0x00;
TL0=0x00;
TR0=0;
EX0=1;
EX1=0;//关闭外部中断1按键 ,一旦进入接收函数,就关闭按键防止干扰
ET0=1; //开T0中断
IT0=1; //外部中断边沿触发
EA=0;
}
//
/********************************************************************/
//发射函数初始化
void init2()//发射初始化
{
contrl=0;//关闭发射端,由于它与38K输出端并联,所以拉低不输出
TMOD=0x21;//T0方式1,外部INTO唤醒 ,T1方式2
TH1=-(13/256);//定时13us翻转一次,即38KHZ (26us)
TL1=-(13%256);
ET1=1; //T1中断
TR1=1;
EA=1;
}
/************************************************************************/
// 红外接收子程序
void jieshou()
{
init1(); //接收初始化
delayms(3000);
flag=0;
finish=1;
JIESHOU=1;//接收灯亮才可以开始按遥控
EA=1;//开中断
EX0=1;
//接收灯亮等待接收
while(finish) //退出接收循环检测
{
while(flag)//T0已启动标志,用完记得清0,由外部中断0启动,初次启动检测
{//第一次低电平测宽已经开始
while(!cin);//等待高电平到来,T0中断不会在这里发生,因为低电平宽度不会有65MS这么长
{
TR0=0;
a[j]=TH0; //低电平宽度 先存高8位数据
j++;
a[j]=TL0;//存储的是低电平宽度
j++;//数组下移
TH0=0;//重装T0
TL0=9;//补偿前面消耗的时间
TR0=1;//重新启动T0,计时高电平
}
//高电平测宽开始
while(cin&&flag);//等待cin低电平到来。T0中断就是在这里等待的时候发生的,因为最后一个电平必然是高电平(无信号就是高)
//flag=1表示T0还没中断,还是接收有效
if(flag)//flag为1才表示计时有效,flag=0表示最后高电平很长结束了
{ //加个flag才能退出这个等待
TR0=0;
a[j]=TH0; //先存高8位数据
j++;
a[j]=TL0;//存储的是低电平段
j++;//数组下移
TH0=0; //重装T0
TL0=0;
TR0=1;//重新启动T0,计时低电平
}
}
//判断是否退出接收
if(end)
{
receive=0;//用完接收启动标志要清0
flag=0;
FASHE=1;
delayms(122);
JIESHOU=1;//亮两个灯表示接收成功
j=0;
finish=0;
end=0;
}
}
//接收完亮两个灯
finish=1;
EX1=1;//开外部按键中断1
EA=1;
}
/***********************************************/
// 红外发射子程序
void fashe() //发射程序里没有安排推出操作,所以只有重启才能重新进入选择模式
{
while(1)
{
ADC();
switch(k)
{
case 1:for(j=0<j<b[0];j++){a[j]=Byte_Read(j);}led(b[0]); break;
case 2:for(j=0<j<b[1];j++){a[j]=Byte_Read(j+0x200);}led(b[1]);break;
case 3:for(j=0<j<b[2];j++){a[j]=Byte_Read(j+0x400);}led(b[2]);break;
case 4:for(j=0<j<b[3];j++){a[j]=Byte_Read(j+0x600);}led(b[3]);break;
case 5:for(j=0<j<b[4];j++){a[j]=Byte_Read(j+0x800);}led(b[4]);break;
case 6:for(j=0<j<b[5];j++){a[j]=Byte_Read(j+0xa00);}led(b[5]);break;
default:k=0;break;
}
init2();//必须先读EEPROM再开定时器中断,不然会无法读EEPROM
}
}
//
/**********************************************************************/
void led(uchar x)
{
j=0;
x=x/2;//2个数组是一段电平,而且肯定是偶数个数组 2*N 是偶数嘛
while(x)
{
TH0=a[j];
j++;
TL0=a[j];
j++;
TR0=1;//
while(!TF0);//等待T0溢出,因为没有采用T0中断
contrl=!contrl;
x--;
}
}
//
/***************************************************************/
/*****编程时防止中断把寄存器中的数据改变的解决方法是给中断指定寄存器,同优先级的使用同一组没事。
1、写中断程序一定要用using语句指定寄存器组。第1、2、3组都可以,不能是0.
2、51单片机的中断有两个优先级。一个中断不会打断另一个相同优先级的中断。
这样相同级别中断可以使用同一个组。比如:低优先级的中断函数都用 using 1,高优先级的中断都用 using 2 。这样不会冲突。
下面是一个正常的例子:
C程序: void int0() interrupt 0 using 1
默认5个中断时同级的,不会冲突,但是最好养成好习惯
不指定中断要使用的寄存器,每次都要入栈保护数据,中断完还要出栈,代码会增加32字节
********************************************************************/
//中断函数要指定使用那组寄存器,使用同一组时可能会破坏了上次寄存器中的数据
//同一优先级的中断可以使用同一组寄存器
void time0() interrupt 1 using 1//定时器0中断
{
EA=0;
EX0=0;
EX1=0;
ET0=0;
FASHE=0;
delayms(200);
JIESHOU=0; //接收指示灯
delayms(1000);
//有65MS以上了,表示接收完毕
b[zu/0x200]=j; //j是从0开始的,最后一次电平存完j自加1了,总长度正好是当前值
addr=zu;//因为下面zu值还要用,所以这里转移其数据
//zu是每个存储空间的起始地址
m=0;
for(;j>0;j--)//j是数据总长度,如此判断,不会存储多余的空位
{ Byte_Program(addr,a
??);//从本组数据对应扇区首地址开始写EEPROM
m++; //数组下移
addr++; //地址下移
delayms(1);} //for end
i<(zu<0xa00)//第一组代码完毕后,转到第二组,每组都是200个空间
zu+=0x200; //测完一组 ,扇区地址指向下一个扇区
else
{ zu=0x000;} //超过6组代码,内存重新指向第1组
flag=0;
receive=0;//用完接收启动标志要清0
end=1;//退出接收函数最外层循环
}
/******************************************************/
// 发射频率38khz由T1产生
void time1() interrupt 3 using 1 //定时器1中断 ,因为默认是同优先级,所以可以使用同一组寄存器
{
khz=!khz;
}
/******************************************************/
// 外部中断 存储高电平长度
void interint0() interrupt 0 using 1 //外部中断0
{
if (flag==0)//flag=0表示是首次接收到脉冲
{
TH0=0;
TL0=10;//前面延时函数消耗的时间补上
TR0=1;
EX0=0;//关闭外部中断0,以后的计数都在接收函数里
flag=1;//表示启动T0
}
}
//
/************************************************************/
/******************************************************/
// 外部按键中断 1
void interint1() interrupt 2 using 1 //外部中断1
{
receive=1;
delayms(122);//等过抖动时间
EA=0;
}
/******************************************************/
//AD转换初始化 ----打开ADC电源
void InitADC()
{
P1=0xff;//这里对其他用到P1口的地方有影响
ADC_CONTR|=0x80;
delayms(30);
//这两个寄存器用来设置 P1口四种状态,每一位对应一个P1引脚 ,按状态组合操作
P1M0=0x08;//这两个寄存器用来设置 P1口四种状态,每一位对应一个P1引脚 ,按状态组合操作
P1M1=0x08;//设置 P1.3做AD
}
/******************************************************************/
// AD转换程序
void ADC()
{
ADC_DATA = 0; //清除结果
ADC_CONTR = 0x60; //转换速度设置 0x60 最快速度
ADC_CONTR = 0xE0; //1110,0000 清 ADC_FLAG, ADC_START 位和低 3 位
ADC_CONTR |= 0x03; //选择 A/D 当前通道 P1.3
delayus(100); //使输入电压达到稳定
ADC_CONTR |= 0x08; //0000,1000 令 ADCS = 1, 启动A/D转换,
while(!(ADC_CONTR & 0x10)); //!的优先级比&高太多了
/***************
这里while 不能改成while(ADC_CONTR & 0x10==0) ;就错误了,因为优先级 ==比&高 ,所以要加括号
while( (ADC_CONTR & 0x10) ==0) 或者非一下 while(!(ADC_CONTR & 0x10)); //!的优先级比&高太多了
******************************/
ADC_CONTR &= 0xE7; //1111,0111 清 ADC_FLAG 位, 关闭A/D转换,
voltage=ADC_DATA;
if( vol<age<40)
{
k=1; //对应0X000扇区内容
}
if(voltage>=40&&vol<age<80)
{
k=2; //对应0X200扇区内容
}
if(voltage>=80&&vol<age<110)
{
k=3;
}
if(voltage>=110&&vol<age<130)
{
k=4;
}
if(voltage>=130&&vol<age<148)
{
k=5;
}
if(voltage>=148&&vol<age<160)//注意:默认是165 电压AD值
{
k=6;
}
}
/******************************************/
/*
--- STC International Limited ----------------
一个完整的EEPROM 测试程序,用宏晶的下载板可以直接测试
STC12C52xxAD 系列单片机 EEPROM/IAP 功能测试程序演示
STC11xx 系列单片机 EEPROM/IAP 功能测试程序演示
STC10xx 系列单片机 EEPROM/IAP 功能测试程序演示
--- STC International Limited ------------------
--- 宏晶科技 设计 2009/1/12 V1.0 --------------
***********************************************/
//读一字节,调用前需打开IAP 功能,入口:DPTR = 字节地址,返回:A = 读出字节
uchar Byte_Read(uint add)
{
ISP_DATA = 0x00;
ISP_CONTR = ENABLE_ISP; //打开IAP 功能, 设置Flash 操作等待时间
ISP_CMD = 0x01; //IAP/ISP/EEPROM 字节读命令
my_unTemp16.un_temp16 = add; //联合体变量赋值 ,这里是俩字节,因为公用内存,所以下面数组也是此内容
ISP_ADDRH = my_unTemp16.un_temp8[0]; //设置目标单元地址的高8 位地址
ISP_ADDRL = my_unTemp16.un_temp8[1]; //设置目标单元地址的低8 位地址
EA = 0;
ISP_TRIG = 0x46; //先送 5Ah,再送A5h 到ISP/IAP 触发寄存器,每次都需如此
ISP_TRIG = 0xB9; //送完A5h 后,ISP/IAP 命令立即被触发起动
_nop_();
//EA = 1;
IAP_Disable(); //关闭IAP 功能, 清相关的特殊功能寄存器,使CPU 处于安全状态,
//一次连续的IAP 操作完成之后建议关闭IAP 功能,不需要每次都关
return (ISP_DATA); //数据在ISP_DATA寄存器中
}
//字节编程,调用前需打开IAP 功能,入口:DPTR = 字节地址, A= 须编程字节的数据
void Byte_Program(uint add, uchar ch)
{
ISP_CONTR = ENABLE_ISP; //打开 IAP 功能, 设置Flash 操作等待时间
ISP_CMD = 0x02; //IAP/ISP/EEPROM 字节编程命令
my_unTemp16.un_temp16 = add; //联合体变量赋值 ,这里是俩字节,因为公用内存,所以下面数组也是此内容
ISP_ADDRH = my_unTemp16.un_temp8[0]; //设置目标单元地址的高8 位地址
ISP_ADDRL = my_unTemp16.un_temp8[1]; //设置目标单元地址的低8 位地址
ISP_DATA = ch; //要编程的数据先送进ISP_DATA 寄存器
EA = 0;//必须关中断,不然没法写
ISP_TRIG = 0x46; //先送 46h,再送B9h 到ISP/IAP 触发寄存器,每次都需如此
ISP_TRIG = 0xb9; //送完B9h 后,ISP/IAP 命令立即被触发起动
_nop_();
//EA = 1;
IAP_Disable(); //关闭IAP 功能, 清相关的特殊功能寄存器,使CPU 处于安全状态,
//一次连续的IAP 操作完成之后建议关闭IAP 功能,不需要每次都关
}
//擦除扇区, 入口:DPTR = 扇区地址
void Sector_Erase(uint add)
{
ISP_CONTR = ENABLE_ISP; //打开IAP 功能, 设置Flash 操作等待时间
ISP_CMD = 0x03; //IAP/ISP/EEPROM 扇区擦除命令
my_unTemp16.un_temp16 = add;
ISP_ADDRH = my_unTemp16.un_temp8[0]; //设置目标单元地址的高8 位地址
ISP_ADDRL = my_unTemp16.un_temp8[1]; //设置目标单元地址的低8 位地址
EA = 0;//必须关中断
ISP_TRIG = 0x46; //先送 46h,再送B9h 到ISP/IAP 触发寄存器,每次都需如此
ISP_TRIG = 0xB9; //送完B9h 后,ISP/IAP 命令立即被触发起动
_nop_();
//EA = 1;
IAP_Disable(); //关闭IAP 功能, 清相关的特殊功能寄存器,使CPU 处于安全状态,
//一次连续的IAP 操作完成之后建议关闭IAP 功能,不需要每次都关
}
void IAP_Disable()
{
//关闭IAP 功能, 清相关的特殊功能寄存器,使CPU 处于安全状态,
//一次连续的IAP 操作完成之后建议关闭IAP 功能,不需要每次都关
ISP_CONTR = 0; //关闭IAP 功能
ISP_CMD = 0; //清命令寄存器,使命令寄存器无命令,此句可不用
ISP_TRIG = 0; //清命令触发寄存器,使命令触发寄存器无触发,此句可不用
ISP_ADDRH = 0;
ISP_ADDRL = 0;
}
//
void delayus(uchar i )
{
while(i--);
}
//延时函数
void delayms(uint k)
{
uint data i,j;
for(i<0;i<k;i++)
{
for(j<0;j<200;j++)
{;}
}
}