// 中斷函數注意養成指定寄存器組的習慣 //不同優先級的中斷程序絕對不能使用同一組寄存器 /*****編程時防止中斷把寄存器中的數據改變的解決方法是給中斷指定寄存器, 同優先級的使用同一組沒事。 1、寫中斷程序一定要用using語句指定寄存器組。第1、2、3組都可以,不能是0. 2、51單片機的中斷有兩個優先級。一個中斷不會打斷另一個相同優先級的中斷。 這樣相同級別中斷可以使用同一個組。比如:低優先級的中斷函數都 用 using 1,高優先級的中斷都用 using 2 。這樣不會沖突。 下面是一個正常的例子: C程序: void int0() interrupt 0 using 1 默認5個中斷時同級的,不會沖突,但是最好養成好習慣 不指定中斷要使用的寄存器,每次都要入棧保護數據,中斷完還要出棧,代碼會增加32字節 完整代碼下載:http://m.zg4o1577.cn/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++) {;} } }