微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 嵌入式设计讨论 > MCU和单片机设计讨论 > 让你的stm8轻松的处理各类任务

让你的stm8轻松的处理各类任务

时间:10-02 整理:3721RD 点击:

STM8应用,如何写出强健的程序
                     下文纯属个人见解,如有错误的地方请及时指正,也希望提出好的建议,在这里抛砖引玉了。
                实现 需求很简单。做好却很难,没有最好的程序。在满足需求的情况下,如何让程序健壮、良好可扩展性是一个程序员要考虑的事情。好的办法是反复实验、修改得来的。废话不多说,上硬菜。
            这里以我家台灯为例,功能需求很简单,可以通过旋钮,及红外遥控控制灯光强弱及开关状态,遥控优先级高于旋钮。我是利用  AIN0 采集旋钮的电压值,TIM2_CH1 捕获红外信号 ,TIM1_CH1 输出PWM 控制灯光  ,TLI 监控电压状态   ,内部FLASH存储数据。单片机 105K4   内部16M晶振。
            程序的主函数部分   :
#include<stm8s105k4.h>
#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<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结束
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)<adz)) adi++; //判断当前状态
//   else  adi=5;//状态不稳定时
//  adz1=adz;
//被注释掉的地方是为在稳定状态下降低旋钮电压检测次数,在不讲求功耗的时候没有太大必要。
  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)   //预设值
//#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<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;       //有效按键  进入按键待处理状态
     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<stm8s105k4.h>
#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<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);
}
}
//
在电压下降到认为发生掉电时,触发TLI中断,将需要保存的数据保存到FLASH内部,等待下次运行用。这里就一个数据需要存储。
按键小程序驾到
#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关闭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);   参数是承认该按键的时间,可更换有关变量类型加大选择时间的宽度,这里只写了按下的检测函数,可以很容易的更改 为松开后   、长按  、短按   、组合 、等检测方式,很有趣,这个需要读者理解,才能为己所用,我多说无益。重要是领悟编程方法    。

程序员万万不能懒。

我最近看了一个淘宝网的 叫试创电子设计的 买有STM8/STM32的软硬件资料 包含了操作系统的真实项目的完整资料 挺好的

平时的板上操作对你进步没多大帮助,只有实际的项目才能提高,基础打好,不要好高骛远

小编辛苦了。

小编是个高手 项目经验应该很丰富

谢谢,非常实用的编程方式。

谢谢分享

小编辛苦谢谢分享

准备学习的,小编真牛。顶一个

学习了~~~~~~~~~~~~~~~~~~~

谢谢分享。好好学习一下

强大,谢谢分享!

赞一个,非常给力,好好学习

谢谢分享,准备学习使用STM8

好东西,谢谢分享!

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top