|
下文純屬個人見解,如有錯誤的地方請及時指正,也希望提出好的建議,在這里拋磚引玉了。
實現(xiàn) 需求很簡單。做好卻很難,沒有最好的程序。在滿足需求的情況下,如何讓程序健壯、良好可擴展性是一個程序員要考慮的事情。好的辦法是反復實驗、修改得來的。廢話不多說,上硬菜。
這里以我家臺燈為例,功能需求很簡單,可以通過旋鈕,及紅外遙控控制燈光強弱及開關(guān)狀態(tài),遙控優(yōu)先級高于旋鈕。我是利用 AIN0 采集旋鈕的電壓值,TIM2_CH1 捕獲紅外信號 ,TIM1_CH1 輸出PWM 控制燈光 ,TLI 監(jiān)控電壓狀態(tài) ,內(nèi)部FLASH存儲數(shù)據(jù)。單片機 105K4 內(nèi)部16M晶振。
程序的主函數(shù)部分 :
#include<stm8s105k4.h>#include"ad105.h" //旋鈕電壓采集頭文件
#include"deng.h" //燈光控制頭文件
#include"irdate.h" //紅外采集頭文件
#include"eeprom.h" //數(shù)據(jù)存儲頭文件,包含TLI電壓檢測
#include"keys.h" //紅外解碼值轉(zhuǎn)換為按鍵頭文件
////////////////////////////////////////////////////
void TIM3_INIT(void) //定時器初始化 程序基本運行定時器,所有工作指示都靠這個定時器指定
{
TIM3_EGR=0X01; //產(chǎn)生更新事件
TIM3_PSCR=0X07; //128分頻
TIM3_ARRH=0X04; //1250 重裝載值
TIM3_ARRL=0Xe2; //1250 重裝載值
TIM3_CR1=0X01; //開定時器
TIM3_IER=0X01; //使能更新中斷
}
void DOG_Init(void)
{
IWDG_KR=0XCC; //使能看門狗
IWDG_KR=0X55; //解鎖,PR RLR 賦值
IWDG_PR=0X06; //分頻 賦值
IWDG_RLR=0XFF; //重裝值
IWDG_KR=0XAA; //重新加載
}
void Feed_DOG(void)
{
IWDG_KR=0XAA; //重新加載 ,喂狗
}
void clock_config(void)
{
CLK_CKDIVR=0X00;//CPU0分頻
}
main()
{
clock_config(); //時鐘初始化
TIM1_CH1PWM(); //燈管控制初始化
TIM3_INIT(); //基本運行定時器初始化
TIM2_CH3_Init(); //紅外捕獲初始化
adinit(0); //旋鈕電壓采集初始化
DOG_Init(); //看門狗初始化
_asm("rim"); //開總中斷
while (1)
{
adzread(); //旋鈕電壓值讀取
deng_pwm(); //燈光控制
getzudate(); //紅外解碼
keysirrun(); //按鍵處理
}
}
@far @interrupt void TIM3_UP (void) //基本運行定時器
{
static unsigned char t200ms=0,t1s=0;
TIM3_SR1=0X00; //清標志位
/////////////////////////add 10ms flag
/////////////////////////
t200ms++;
if(t200ms>9)
{
t200ms=0;
/////////////////////////add 200ms flag
adstartflag=1; //電壓采集標志位
/////////////////////////
t1s++;
if(t1s>4)
{
t1s=0;
/////////////////////////add 1s flag
Feed_DOG(); //喂看門狗
/////////////////////////
}
}
}
你可能說有啥問題,這個小項目,對如何提高單片機的執(zhí)行效率方面的代碼不是很多。首先我說一下我的代碼風格。
我的程序都有一個程序運行基本定時器,保證程序的基本運行,它決定程序的運行方向。就是上面的定時器3。這個定時器的作用是,合理的管理要執(zhí)行的任務,避免CPU 的浪費,提高吞吐率.我大致分為三個時間段,10ms 級別 ,200ms級別,1s級別。對應的下邊是待處理函數(shù)的標志位,中斷是把雙刃劍,時效性高,過度利用中斷會加重CPU的工作量,很大一部分時間浪費在進入中斷及跳出中斷的路上。這樣一來,在頻繁、多個中斷同時到達時,中斷的的時效性反而不如不用中斷了。我習慣在中斷里邊只處理標志位的狀態(tài),在大循環(huán)里面運行內(nèi)嵌標志位的實時函數(shù)。這樣,在多中斷,大工作量的情況下,程序也跑的很輕松。我會在講解完本歷程后貼出一個按鍵檢測小程序,你就會感覺這樣做的優(yōu)點。
不難發(fā)現(xiàn),在While 里面有一下幾個函數(shù)
adzread(); //旋鈕電壓值讀取 deng_pwm(); //燈光控制
getzudate(); //紅外解碼
keysirrun(); //按鍵處理
旋鈕電壓采集函數(shù),電壓采集函數(shù)內(nèi)部有個允許電壓采集標志位, adstartflag 只有這個標志位被至位后,才運行函數(shù)內(nèi)部程序,且運行完后將標志為復位,等待下次標志位置位。在定時器200MS下將這個標志位每200MS 置位一次
if(t200ms>9)
{
t200ms=0;
/////////////////////////add 200ms flag
adstartflag=1; //電壓采集標志位
}
你可能又會說,為啥只有這么一個標志位,這樣有啥用,上邊說了本歷程相關(guān)代碼較少,是因為其他函數(shù)不需要有時間的感念,它們都是基于,旋鈕電壓的變化、及紅外捕獲值存在,而變化,不需要時間決定它們的運行狀態(tài)。當程序龐大,關(guān)系繁瑣復雜時你會體會到這種寫法的優(yōu)點。
看門狗每1s喂一次,這里不多說。
下面分別說每個頭文件下的函數(shù)
#include<stm8s105k4.h>
#include"eeprom.h"
#include"ad105.h"
_Bool adstartflag=0; //標志位
unsigned short adz1, adz2;
unsigned char adi=0;
void adgpioinit(void)
{
PB_ODR&=0XFE;
PB_DDR&=0XFE;
PB_CR1&=0XFE;
PB_CR2&0XFE;
}
void adinit(unsigned char ch)
{
adgpioinit();
ADC_CSR &=0xf0;
ADC_CSR|= ch; //通道
ADC_CR1=0X01;
ADC_CR2=0X30;
}
unsigned short readadz(void)
{
unsigned short adz=0;
ADC_CR1|=0x01;
while(!(ADC_CSR & 0x80)); // 等待ADC結(jié)束
adz=ADC_DRH;adz<<=2;adz+=ADC_DRL;
return (adz);
}
void adzread(void)
{
if(adstartflag) //是否可以采集旋鈕電壓
{
// if(adi>4)
if(adendlag==0){ //沒有處于紅外控制狀態(tài)下
adz=readadz();
// adi=0;
pwmstartflag=1;} //燈光控制允許變化。這里也可以對電壓值比較有變化時允許燈光變化
// if(((adz1+10)>adz)&&((adz1-10)<adz)) adi++; //判斷當前狀態(tài)
// else adi=5;//狀態(tài)不穩(wěn)定時
// adz1=adz;
//被注釋掉的地方是為在穩(wěn)定狀態(tài)下降低旋鈕電壓檢測次數(shù),在不講求功耗的時候沒有太大必要。
adstartflag=0;
}
}
每個頭文件下總有那么1-2個標志位。號召它們旗下的小兵們。這個就這么簡單,速度過。
#include<stm8s105k4.h>
#include"deng.h"
#include"eeprom.h"
void TIM1_CH1PWM(void)
{
TIM1_CR1=0X00;
TIM1_CCMR1=0X68;
TIM1_CCER1=0X01;
TIM1_ARRH=0X04; //旋鈕電壓采集滿量程值,
TIM1_ARRL=0X00;
TIM1_PSCRH=0X00;
TIM1_PSCRL=0X00; /0分頻,讓PWM頻率最大
TIM1_BKR=0X80; //注意這個別忘了
TIM1_CR1=0X81;
}
void T1_CH1PWMT(unsigned short date)
{
TIM1_CCR1H=date/256;
TIM1_CCR1L=date%256;
}
void deng_pwm(void)
{
if(pwmstartflag) //是否可以控制更改燈光輸出
{
T1_CH1PWMT(adz); //賦值
pwmstartflag=0; // 清除標志位
}
}
也很簡單 過 。。。。。。。。。。。。。。
#include<stm8s105k4.h>
#include"irdate.h"
#include"eeprom.h"
//////////14 03 27 張小強
#define tim2_ccr3 (TIM2_CCR1H*256+TIM2_CCR1L) //捕獲值
#define tim2_ovfz (0xffff) //預設(shè)值
//#define tim2_ovfz (0x7a10) //預設(shè)值
//更換遙控器需要修改的的數(shù)據(jù)
#define learnadd 0 //是否學習地址
#define learndate 0 //是否學習數(shù)據(jù)
//////////////////////////////////////////////////
#define irstartH 255 //引導嗎最大值
#define irstartL 95 //引導嗎最小值
#define irstopH 95 //停止碼最大值
#define irstopL 75 //停止碼最小值
#define irhH 21 //高電平最大值
#define irhL 15 //高電平最小值
#define irlH 12 //低電平最大值
#define irlL 6 //低電平最小值
///////////////////////////////////////////////
//所用變量
unsigned char ovfflag=0;//溢出標志 實際捕獲值 輔助計算值
unsigned char zu[33]={0}; //捕獲臨時存放的捕獲時間
unsigned long irzu=0; //解出得遙控碼包含地址 數(shù)據(jù) 及各自的反碼
unsigned char irflag=0x01; //捕獲值進入
unsigned char irz=0,tempfalg=0;//得到的鍵值與次數(shù)
_Bool IRled @PD_ODR:1;
/////////////////////////////////////////////
#if learndate ==1
unsigned char irzu1[50]={0},irzu1i=0;
#endif
#if learnadd==1
unsigned char iradd=0 , iraddf=0;
unsigned char *iraddr; //= (unsigned char *)0x4000; //
void miyao(void)
{
do
{
FLASH_DUKR = 0xae; // 寫入第一個密鑰
FLASH_DUKR = 0x56; // 寫入第二個密鑰
}
while((FLASH_IAPSR & 0x08) == 0);
}
void irwrite(unsigned short add,unsigned char date )
{
miyao();
iraddr=(unsigned char *)add;
*iraddr = date;
while((FLASH_IAPSR & 0x04) == 0);
}
// 等待寫操作成功
unsigned char irreadadd(unsigned short add)
{
iraddr=(unsigned char *)add;
return (*iraddr);
}
#else
#define iradd 0x38 //遙控器地址
#define iraddf 0xc7 //遙控器地址反碼
#endif
////////////////////////////////////////////
void TIM2_CH3_Init(void)
{
TIM2_CR1=0X00;
TIM2_PSCR=0X0b;
TIM2_EGR=0X03;
TIM2_ARRH=0Xff;
TIM2_ARRL=0Xff;
TIM2_CCMR1=0Xa1;
TIM2_CCER1=0X03;
TIM2_IER|=0X03;
TIM2_CR1|=0X01;
PD_CR1|=0X10;
PD_DDR&=0XEF;
PD_CR2&=0XEF;
PD_ODR&=0XEF;
irled_config();
#if learnadd==1
iradd=irreadadd(0x40a0);
iraddf=irreadadd(0x40a1);
#endif
}
void getirdate(void)
{
static unsigned char tim2_ch3date=0,tim2_ccr3c=0,iri=0;
if(ovfflag==0)
{
tim2_ch3date=tim2_ccr3-tim2_ccr3c;
tim2_ccr3c=tim2_ccr3;
}
else
{
tim2_ch3date=((tim2_ovfz -tim2_ccr3c)+tim2_ccr3);
ovfflag=0;
}
if((tim2_ch3date>irstartL)&&(tim2_ch3date<irstartH)){iri=0;irflag=0x05;}
if((tim2_ch3date>irlL)&&(tim2_ch3date<irhH)&&(irflag==0x05))
{
zu[iri++]=tim2_ch3date;
if(iri>31)irflag=0x0a;
}
if((tim2_ch3date>irstopL)&&(tim2_ch3date<irstopH)){iri=0;irflag=0x0a;}
if(iri>32)iri=0;
#if learndate ==1
irzu1[irzu1i++]=tim2_ch3date;
if(irzu1i>49)irzu1i=0;
#endif
}
void restzu(void)
{
static unsigned char rzi=0;
for(rzi=0;rzi<32;rzi++)
{
zu[rzi]=0;
}
}
void irtimeupflag(void)
{
ovfflag=1;
}
void getzudate(void)
{
static unsigned char iri1=0,z1=0,z2=0,z3=0,z4=0,z5=0;
#if learnadd==1
static unsigned char add1=0,add2=0,add3=0,add4=0,add5=0,add6=0;
#endif
if(irflag==0x0a)
{
for(iri1=32;iri1>0;iri1--)
{
if((zu[iri1]>irhL)&&(zu[iri1]<irhH)){irzu|=0x01;}
irzu<<=1;
}
z1=irzu;z2=(irzu>>8);z3=(~(irzu>>24));z4=(irzu>>16);
if(((z1==iradd )&&(z2==iraddf)))
{
if(z3==z4)
{
switch(z4)
{
case 3: irz=10; irokflag=1; break; //有效按鍵 進入按鍵待處理狀態(tài)
case 9: irz=11; irokflag=1; break;
case 14:irz=12; irokflag=1; break;
case 26:irz=13; irokflag=1; break;
}
if((z5==z4)&&(irokflag)){tempfalg++;} //連按情況下
else tempfalg=0;
z5=z4;
restzu();
// z1=0;z2=0;z3=0;z4=0;
}
}
#if learnadd==1
else
{
add5=add3;add6=add4;add3=add1;add4=add2;add1=z1;add2=z2;
if((add5==add1)&&(add6==add2))
{iradd=add1;iraddf=add2;irwrite(0x40a0,add1);irwrite(0x40a1,add2);}
}//地址學習功能
#endif
irflag=0;
}
}
@far @interrupt void TIM2_CC (void)
{
TIM2_SR1&=0XFd;
TIM2_SR2&=0xfd;
getirdate();
}
@far @interrupt void TIM2_UP (void)
{
TIM2_SR1&=0XFE;
irtimeupflag();
#if learndate ==1
irzu1i=0;
#endif
}
上面是紅外解碼部分,原理是,捕獲的一組時間段,放到數(shù)組中,接收完畢后判斷數(shù)組的內(nèi)容,解出紅外值,對應有,地址學習 功能,數(shù)據(jù)分析功能。方便在更換遙控器調(diào)試使用。這些代碼需要耐心看,首先要成功捕獲數(shù)據(jù),以后就順理成章了。功能可以做的跟好。這里只是粗略的寫寫,有興趣的可以慢慢研究。
#include<stm8s105k4.h>
#include"keys.h"
#include"eeprom.h"
#include"irdate.h"
_Bool pwmen@TIM1_CCER1:0;
void keysirrun(void)
{
if(irokflag) //是否有有效遙控解碼值
{
if(irz==11) //進入遙控控制模式,關(guān)閉旋鈕電壓采集功能
{
adendlag=!adendlag;
adz=(adz-(adz%10));
}
if(adendlag) //在遙控模式下
{
if(irz==12)
{
if(adz<700)adz+=10;pwmstartflag=1; //更改燈光強度 ,使能燈光變化
}
if(irz==13)
{
if(adz>0)adz-=10;pwmstartflag=1;
}
}
if(irz==10)
{
pwmen=!pwmen; //開關(guān)燈光
}
irokflag=0;
}
}
//////////內(nèi)容很少有效指令后處理對應的動作。
#include<stm8s105k4.h>
#include"eeprom.h"
_Bool ledevent @PD_ODR:1;//事件指示燈
_Bool eventledflag=0;
unsigned char ledi=0;
/////////////////////////////自身變量
unsigned char eeflag=0;
////////////////////////////////////外部變量
unsigned short shiji=0;
unsigned char shanflag=0,shflag=0,disflag=0;
//////////////////////////////////
void eepromEN(void)
{
eeflag=1;
}
unsigned char *addr; //= (unsigned char *)0x4000; //
void miyao(void)
{
do
{
FLASH_DUKR = 0xae; // 寫入第一個密鑰
FLASH_DUKR = 0x56; // 寫入第二個密鑰
}
while((FLASH_IAPSR & 0x08) == 0);
}
void write(unsigned short add,unsigned char date )
{
miyao();
addr=(unsigned char *)add;
*addr = date;
while((FLASH_IAPSR & 0x04) == 0);
}
// 等待寫操作成功
unsigned char readadd(unsigned short add)
{
addr=(unsigned char *)add;
return (*addr);
}
void eventledgpio(void)
{
ledevent=1;
PD_DDR|=0X02;
PD_CR1|=0Xfd;
}
void eventleden(void)
{
eventledflag=1;// set ventlede flag
ledevent=0; // open led
}
void eventledable(void)
{
if(eventledflag)
{
ledi++;
if(ledi>1) // 30ms
{
ledi=0;
eventledflag=0; //reset ventlede flag
ledevent=1; //close led
}
}
}
void TLI_init(void)
{
PD_DDR&=0X7F;
PD_CR2|=0X80;
EXTI_CR2|=0X20;
eventledgpio();
eepromEN();
}
void readeeprom(void)
{
adendlag=readadd(0x4000);
}
@far @interrupt void TLI_PD7(void)
{
if(eeflag==1)
{
write(0x4000, adendlag);
}
}
//
在電壓下降到認為發(fā)生掉電時,觸發(fā)TLI中斷,將需要保存的數(shù)據(jù)保存到FLASH內(nèi)部,等待下次運行用。這里就一個數(shù)據(jù)需要存儲。
按鍵小程序駕到
#include<stm8s105k4.h>
#include"eeprom.h"
#include"key.h"
_Bool keystartflag=0;
#define keyc02 0x02
#define keyc04 0x04
#define keyc08 0x08
#define keyc10 0x10
#define keygc PC_IDR&0x1e //宏定義控制
#define keyd01 0x01
#define keyd04 0x04
#define keyd08 0x08
#define keygd PD_IDR&0x0d //宏定義控制
//端口c
unsigned char c10g=0,c20g=0,c40g=0,c80g=0;
_Bool c10kflag=0,c20kflag=0,c40kflag=0,c80kflag=0;
unsigned char c20cg=0; //長按
_Bool c20cflag=0;
//端口d
unsigned char d20g=0,d40g=0,d80g=0;
_Bool d40kflag=0,d20kflag=0,d80kflag=0;
void keyinit(void)
{
PC_DDR&=0Xe1;
PC_CR1|=0X1e;
PD_DDR&=0Xf2;
PD_CR1|=0X0d;
}
void keysjian(unsigned char k1t,unsigned char k2t,unsigned char k3t,unsigned char k4t,unsigned char k5t,unsigned char k6t,unsigned char k7t)
{
if(keystartflag)
{
eventledable(); //進過30ms關(guān)閉led 事件指示燈
//端口c
if((keygc&keyc10)==0x00){if(c10kflag==0){c10g++;if(c10g>k1t){c10kflag=1;eventleden(); 動作 }}}
else {c10kflag=0;c10g=0;}
if((keygc&keyc08)==0x00){if(c20kflag==0){c20g++;if(c20g>k2t){c20kflag=1;eventleden();動作 }}}
else {c20kflag=0;c20g=0;}
if((keygc&keyc04)==0x00){if(c40kflag==0){c40g++;if(c40g>k3t){c40kflag=1;eventleden();動作 }}}
else {c40kflag=0;c40g=0;}
if((keygc&keyc02)==0x00){if(c80kflag==0){c80g++;if(c80g>k4t){c80kflag=1;eventleden();動作 }}}
else {c80kflag=0;c80g=0;}
if((keygd&keyd01)==0) {if(d20kflag==0){d20g++;if(d20g>k5t){d20kflag=1;eventleden();動作 }}}
else {d20kflag=0;d20g=0;}
if((keygd&keyd04)==0) {if(d40kflag==0){d40g++;if(d40g>k6t){d40kflag=1;eventleden();動作 }}}
else {d40kflag=0;d40g=0;}
if((keygd&keyd08)==0x00){if(d80kflag==0){d80g++;if(d80g>k7t){d80kflag=1;eventleden();動作 }}}
else {d80kflag=0;d80g=0;}
keystartflag=0;
}
}
將標志位放到10MS下,調(diào)用函數(shù) keysjian(1,2,10,50,100,200,250); 參數(shù)是承認該按鍵的時間,可更換有關(guān)變量類型加大選擇時間的寬度,這里只寫了按下的檢測函數(shù),可以很容易的更改 為松開后 、長按 、短按 、組合 、等檢測方式,很有趣,這個需要讀者理解,才能為己所用,我多說無益。重要是領(lǐng)悟編程方法 。
程序員萬萬不能懶。
下次再會 張小強于2014 06 10 23 02
|
|