本帖最后由 正版张小强 于 2014-6-13 18:27 编辑
STM8应用,如何写出强健的程序
下文纯属个人见解,如有错误的地方请及时指正,也希望提出好的建议,在这里抛砖引玉了。
实现 需求很简单。做好却很难,没有最好的程序。在满足需求的情况下,如何让程序健壮、良好可扩展性是一个程序员要考虑的事情。好的办法是反复实验、修改得来的。废话不多说,上硬菜。
这里以我家台灯为例,功能需求很简单,可以通过旋钮,及红外遥控控制灯光强弱及开关状态,遥控优先级高于旋钮。我是利用 AIN0 采集旋钮的电压值,tiM2_CH1 捕获红外信号 ,TIM1_CH1 输出PWM 控制灯光 ,TLI 监控电压状态 ,内部FLASH存储数据。单片机 105K4 内部16M晶振。
程序的主函数部分 :
#include
#include"ad105.h" //旋钮电压采集头文件
#include"deng.h" //灯光控制头文件
#include"irdate.h" //红外采集头文件
#include"eeprom.h" //数据存储头文件,包含TLI电压检测
#include"keys.h" //红外解码值转换为按键头文件
////////////////////////////////////////////////////
void TIM3_INIT(void) //定时器初始化 程序基本运行定时器,所有工作指示都靠这个定时器指定
{
TIM3_EGR=0X01; //产生更新事件
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(); //喂看门狗
/////////////////////////
}
}
}
你可能说有啥问题,这个小项目,对如何提高单片机的执行效率方面的代码不是很多。首先我说一下我的代码风格。
我的程序都有一个程序运行基本定时器,保证程序的基本运行,它决定程序的运行方向。就是上面的定时器3。这个定时器的作用是,合理的管理要执行的任务,避免CPU 的浪费,提高吞吐率.我大致分为三个时间段,10ms 级别 ,200ms级别,1s级别。对应的下边是待处理函数的标志位,中断是把双刃剑,时效性高,过度利用中断会加重CPU的工作量,很大一部分时间浪费在进入中断及跳出中断的路上。这样一来,在频繁、多个中断同时到达时,中断的的时效性反而不如不用中断了。我习惯在中断里边只处理标志位的状态,在大循环里面运行内嵌标志位的实时函数。这样,在多中断,大工作量的情况下,程序也跑的很轻松。我会在讲解完本历程后贴出一个按键检测小程序,你就会感觉这样做的优点。
不难发现,在While 里面有一下几个函数
adzread(); //旋钮电压值读取
deng_pwm(); //灯光控制
getzudate(); //红外解码
keysirrun(); //按键处理
旋钮电压采集函数,电压采集函数内部有个允许电压采集标志位, adstartflag 只有这个标志位被至位后,才运行函数内部程序,且运行完后将标志为复位,等待下次标志位置位。在定时器200MS下将这个标志位每200MS 置位一次
if(t200ms>9)
{
t200ms=0;
/////////////////////////add 200ms flag
adstartflag=1; //电压采集标志位
}
你可能又会说,为啥只有这么一个标志位,这样有啥用,上边说了本历程相关代码较少,是因为其他函数不需要有时间的感念,它们都是基于,旋钮电压的变化、及红外捕获值存在,而变化,不需要时间决定它们的运行状态。当程序庞大,关系繁琐复杂时你会体会到这种写法的优点。
看门狗每1s喂一次,这里不多说。
下面分别说每个头文件下的函数
#include
#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结束
adz=ADC_DRH;adz<<=2;adz+=ADC_DRL;
return (adz);
}
void adzread(void)
{
if(adstartflag) //是否可以采集旋钮电压
{
// if(adi>4)
if(adendlag==0){ //没有处于红外控制状态下
adz=readadz();
// adi=0;
pwmstartflag=1;} //灯光控制允许变化。这里也可以对电压值比较有变化时允许灯光变化
// if(((adz1+10)>adz)&&((adz1-10)
// else adi=5;//状态不稳定时
// adz1=adz;
//被注释掉的地方是为在稳定状态下降低旋钮电压检测次数,在不讲求功耗的时候没有太大必要。
adstartflag=0;
}
}
每个头文件下总有那么1-2个标志位。号召它们旗下的小兵们。这个就这么简单,速度过。
#include
#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
#include"irdate.h"
#include"eeprom.h"
//////////14 03 27 张小强
#define tim2_ccr3 (TIM2_CCR1H*256+TIM2_CCR1L) //捕获值
#define tim2_ovfz (0xffff) //预设值
//#define tim2_ovfz (0x7a10) //预设值
//更换遥控器需要修改的的数据
#define learnadd 0 //是否学习地址
#define learndate 0 //是否学习数据
//////////////////////////////////////////////////
#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; //解出得遥控码包含地址 数据 及各自的反码
unsigned char irflag=0x01; //捕获值进入
unsigned char irz=0,tempfalg=0;//得到的键值与次数
_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
if((tim2_ch3date>irlL)&&(tim2_ch3date
{
zu[iri++]=tim2_ch3date;
if(iri>31)irflag=0x0a;
}
if((tim2_ch3date>irstopL)&&(tim2_ch3date
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]
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; //有效按键 进入按键待处理状态
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
}
上面是红外解码部分,原理是,捕获的一组时间段,放到数组中,接收完毕后判断数组的内容,解出红外值,对应有,地址学习 功能,数据分析功能。方便在更换遥控器调试使用。这些代码需要耐心看,首先要成功捕获数据,以后就顺理成章了。功能可以做的跟好。这里只是粗略的写写,有兴趣的可以慢慢研究。
#include
#include"keys.h"
#include"eeprom.h"
#include"irdate.h"
_Bool pwmen@TIM1_CCER1:0;
void keysirrun(void)
{
if(irokflag) //是否有有效遥控解码值
{
if(irz==11) //进入遥控控制模式,关闭旋钮电压采集功能
{
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; //开关灯光
}
irokflag=0;
}
}
//////////内容很少有效指令后处理对应的动作。
#include
#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);
}
}
//
在电压下降到认为发生掉电时,触发TLI中断,将需要保存的数据保存到FLASH内部,等待下次运行用。这里就一个数据需要存储。
按键小程序驾到
#include
#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关闭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下,调用函数 keysjian(1,2,10,50,100,200,250); 参数是承认该按键的时间,可更换有关变量类型加大选择时间的宽度,这里只写了按下的检测函数,可以很容易的更改 为松开后 、长按 、短按 、组合 、等检测方式,很有趣,这个需要读者理解,才能为己所用,我多说无益。重要是领悟编程方法 。
程序员万万不能懒。
|