天天看点

STC12系列单片机PCA模块应用

    STC12C5系列单片机属于增强性单片机,有多强呢?带有SPI接口,PCA模块,定时器输出,16K+的片上ROM,越来越妖孽了!前面见识了定时器输出功能,现在来领教一下STC12C5A60S单片机的PCA捕捉比较模块,后面有心情再看看SPI模块。

    按我个人理解捕捉比较的意思应该是捕捉外部引脚上的跳变,与预设的值比较,然后做相应的动作。下文按这个理解展开。老规矩,寄存器功能介绍省略了,懒得抄手册了。

    虽然说寄存器设置的说明不介绍,不过表格还是得给一张,这样条理比较清晰,如下:

STC12系列单片机PCA模块应用

1)PCA模块PWM的应用,感觉设置寄存器最少的一个:

PWM(脉宽调制,改变输出方波的占空比)估计拥有最广泛的应用空间(恕我眼见浅),据说常有人用PWM做心跳灯向妹子表白,不知道有没有成功;还有改变直流电机的输出转速。。。 

怎么理解PWM这个功能呢?刚去了趟WC突然想到了:在一个有0xFF个刻度的跑表上,CH是秒表,CCAPnH是表面上的一个绊脚石,当CH遇到绊脚石,直接给跪了,一直跪到下一轮0x00,继续站起来行走。如果CCAPnH没被移动,CH还在同一个地方周而复始的摔倒(人说不要再同一个地方摔倒)。然后计算从0x00-0xFF这段时间里,CH站着走路占了这段时间的百分之几,给跪占了剩下的多少(路遇倒地不扶也就算了还做应用题)!

#include <STC\STC12C5A.H>

enum sState
{
    sInit=0,sPca,sClk,sPwm,sClkOut,
};

#define CCF1_INT 0x02
#define CCF0_INT 0x01

#define ResetPCA() \
do{ \
    CCON = 0; \
    CL = 0; \
    CH = 0; \
    CMOD = 0x00; \
    CCAPM0 = 0x00; \
    CCAPM1 = 0x00; \
}while(0); \

#define StartPCA() \
do{ \
    CR = 1; \
}while(0); \

#define StopPCA() \
do{ \
    CR = 0; \
}while(0); \

#define SetModnCmp(n) \
do{ \ 
    CCAPM##n |= PCA0_ECOM; \
    CCAPM##n |= PCA0_MAT; \    
}while(0); \

#define SetModnTog(n) \
do{ \
    CCAPM##n |= PCA0_TOG; \
}while(0); \

#define SetCapnMod(n,mod) \
do{ \
    CCAPM##n |= mod; \
}while(0); \

#define SetCCAPn(n,hi,lo) \
do{ \
    CCAP##n##H = hi; \
    CCAP##n##L = lo; \    
}while(0); \
//ECCFn 使能CCFn中断。使能寄存器CCON的比较/捕获标志CCFn,用来产生中断。
#define EnPcaModnInt(n) \
do{ \
    CCAPM##n |= PCA0_ECCF; \    
}while(0); \

//PCA0_ECOM 允许比较器功能控制位
//PCA0_PWM 脉宽调制模式。当PWMn=1时,CEXn脚用作脉宽调制输出
#define SetPwmMod(n) \
do{ \
    CCAPM##n |= PCA0_ECOM|PCA0_PWM; \    
}while(0); \

unsigned int CCAPnHVal = 0x80,CCAPnLVal = 0x80; 

void delay()
{
    int i=0,j=0;
    for(i=0;i<1000;i++)
    {
        for(j=0;j<110;j++);
    }
}

int main()
{
    unsigned char sState = sInit;
    ResetPCA();
    //pwm有比较功能 但没用到匹配...真特立独行
    //只要设置CCAPMn的PWM位和比较位
    SetPwmMod(0);
    
    //设置占空比,有点类似定时器Mod2,TH自动装载到TL
    //此处是CCAPnH自动装载到CCAPnL(8位);CH装载到CL(8位)
    //CH-CL低于    CCAPnH-CCAPnL输出0,其他输出1
    while(1)
    {
        StopPCA();
        SetCCAPn(0,CCAPnHVal,CCAPnLVal);
        if(CCAPnHVal == 0xF0)
            CCAPnLVal = CCAPnHVal = 0x00;
        else
            CCAPnLVal = CCAPnHVal = CCAPnLVal+0x10;
        //启动pca模块
        StartPCA();
        delay();
    }
}      

PWM0的输出引脚是P1.3在我的开发板上正好连了一个红色LED,忽明忽亮的,眼都刺瞎了!用示波器测试时的确输出可变的波形。(无图无真相随我BB)

本来想用中断控制pwm占空比,不过没成功,没法调试好麻烦,只能用这种粗线条的延时了。

2) 在看PCA捕捉功能之前,先看下51MCU的计数器功能,PCA的捕捉功能应该是加强版计数器:

2a) 51的定时器对系统时钟计数,计满诺干个时钟脉冲后,产生溢出;计数器对外部输入信号计数,计满诺干负跳变后,产生溢出。传说,计算流水线上的工件数会用到计数器功能。想象一下点钞机里的票票经过光感时,使计数器不断的加一,想想是不是有点小激动?醒醒了,拿钱不是自己的。还是看下面的代码了

#include <reg51.h>

#define MakeByte(target, Hi,Lo) \  
do{ \  
    target |= (((Hi)<<4)|(Lo)); \   
}while(0); \  
  
#define SetTH(n,val) \  
do{ \  
    TH##n = val; \  
}while(0); \  
  
#define SetTL(n,val)  \  
do{ \  
    TL##n = val; \  
}while(0); \  
  
#define EnableET(n) \  
do{ \  
    ET##n = 0x01; \  
    IE |= 0x80; \  
}while(0); \

#define StartClk(val) \
do{ \
    TCON |= val; \
}while(0); \

sbit P10=P1^0;

int main()
{
    //T0工作在方式2 计数器模式
    MakeByte(TMOD,0x00,0x06);
    SetTH(0,0xFF);
    SetTL(0,0xFF);
        
    EnableET(0);
    //启动定时器
    StartClk(0x10);
    while(1);
}  

void IsrT0() interrupt 1 
{
    //TH自动装入0xFF,再计满一个负跳变又能进入isr
    P10 = ~P10; 
}      

程序对P3.4引脚采样,遇到负跳变计数器加1,因为设置计数溢出是0xFF,因此只要有负跳变就产生中断,对P1.0引脚取反。因为文章是关于PCA的,所以不能安排MCU计数器模块抢了镜头~

STC12系列单片机PCA模块应用

蓝色波形是信号发生器的输出,黄色波形是P1.0的输出。因为2个负跳变产生一个完整的波形,因此P1.0方波周期正好对应2个信号发生器的周期。

2b) 回到文章的主角PCA模块,当PCA模块工作在捕获模式时,对外部输入CEXn(P13/P14)的跳变进行采样。当采样到有效跳变时,PCA硬件将PCA计数器阵列寄存器(CH和CL)的值装载到捕获寄存器(CCAPnH和CCAPnL)中。如果CCON中的CCFn位和CCAPMn中的ECCFn位被置位,将产生中断。

#include <STC\STC12C5A.H>

enum sState
{
    sInit=0,sPca,sClk,sPwm,sClkOut,
};

#define CCF1_INT 0x02
#define CCF0_INT 0x01

#define ResetPCA() \
do{ \
    CCON = 0; \
    CL = 0; \
    CH = 0; \
    CMOD = 0x00; \
    CCAPM0 = 0x00; \
    CCAPM1 = 0x00; \
}while(0); \

#define StartPCA() \
do{ \
    CR = 1; \
}while(0); \

#define StopPCA() \
do{ \
    CR = 0; \
}while(0); \

#define SetModnCmp(n) \
do{ \ 
    CCAPM##n |= PCA0_ECOM; \
    CCAPM##n |= PCA0_MAT; \ 
}while(0); \

#define SetModnTog(n) \
do{ \
    CCAPM##n |= PCA0_TOG; \
}while(0); \

#define SetCapnMod(n,mod) \
do{ \
    CCAPM##n |= mod; \
}while(0); \

#define SetCCAPn(n,hi,lo) \
do{ \
    CCAP##n##H = hi; \
    CCAP##n##L = lo; \  
}while(0); \
//ECCFn 使能CCFn中断。使能寄存器CCON的比较/捕获标志CCFn,用来产生中断。
#define EnPcaModnInt(n) \
do{ \
    CCAPM##n |= PCA0_ECCF; \    
}while(0); \

//PCA0_ECOM 允许比较器功能控制位
//PCA0_PWM 脉宽调制模式。当PWMn=1时,CEXn脚用作脉宽调制输出
#define SetPwmMod(n) \
do{ \
    CCAPM##n |= PCA0_ECOM|PCA0_PWM; \   
}while(0); \

//当PCA模块工作在捕获模式时,对外部输入CEXn的跳变进行采样。
//当采样到有效跳变时,PCA硬件将PCA计数器阵列寄存器(CH和CL)的值装载到捕获寄存器(CCAPnH和CCAPnL)中。
//如果CCON中的CCFn位和CCAPMn中的ECCFn位被置位,将产生中断。
//当P1.3出现下降沿时产生中断 对P1.5取反

void delay()
{
    int i=0,j=0;
    for(i=0;i<200;i++)
    {
        for(j=0;j<110;j++);
    }
}

int main()
{
    unsigned char sState = sInit;
    ResetPCA();
    EA = 1;
    //捕捉模式,捕捉到跳变沿时触发中断
    //CH-CL不用与CCAPnH-CCAPnL比较,因此不用使能ECOMn
    //也不需要设置PCA计数器阵列寄存器(CH和CL)以及捕获寄存器(CCAPnH和CCAPnL)
    SetCapnMod(0,PCA0_CAPN);
    //开启PCA中断
    EnPcaModnInt(0);
    //启动pca模块
    StartPCA();
    while(1)
    {
        P12 = ~P12; 
        delay();
    }   
}

void PCA_ISR(void) interrupt 7
{
    switch(CCON&0x03)
    {
    //模块0触发中断
    case CCF0_INT:
        //清除模块中断标志位
        CCON &= (0x00<<CCF0_INT);
        P15 = ~P15;
        break;
    //模块1触发中断
    case CCF1_INT:
        CCON &= (0x00<<CCF1_INT);
        break;
    default:
        break;
    }
}      

程序烧写到MCU后,用跳线帽连接P12和P13,使得P13对P12的输出经行采样。下面是示波器的输出:

STC12系列单片机PCA模块应用

蓝色波形是P12引脚的输出,黄色波形是P15引脚的输出。图中(我的低端示波器)可以很明显的看出P13上采样到一个负跳表,P15发生一次翻转。

这个功能跟计数器每次计满一个脉冲就触发中断类似,不过感觉没有计数器那么灵活,它可以计数256个跳变,不过PCA捕捉可以同时捕捉双边沿,可用于脉宽测量;

#include <STC\STC12C5A.H>

enum sState
{
  sInit=0,sPca,sClk,sPwm,sClkOut,
};

#define CCF1_INT 0x02
#define CCF0_INT 0x01

#define ResetPCA() \
do{ \
  CCON = 0; \
  CL = 0; \
  CH = 0; \
  CMOD = 0x00; \
  CCAPM0 = 0x00; \
  CCAPM1 = 0x00; \
}while(0); \

#define StartPCA() \
do{ \
  CR = 1; \
}while(0); \

#define SetModnCmp(n) \
do{ \ 
  CCAPM##n |= PCA0_ECOM; \
  CCAPM##n |= PCA0_MAT; \ 
}while(0); \

#define SetModnTog(n) \
do{ \
  CCAPM##n |= PCA0_TOG; \
}while(0); \

#define SetCapnMod(n,mod) \
do{ \
  CCAPM##n |= mod; \
}while(0); \

#define SetCCAPn(n,hi,lo) \
do{ \
  CCAP##n##H = hi; \
  CCAP##n##L = lo; \  
}while(0); \
//
#define EnPcaModnInt(n) \
do{ \
  CCAPM##n |= PCA0_ECCF; \  
}while(0); \

#define SetPwmMod(n) \
do{ \
  CCAPM##n |= PCA0_ECOM|PCA0_PWM; \ 
}while(0); \

int main()
{
  unsigned char sState = sInit;

  ResetPCA();
  EA = 1;
  while(1)
  {
    switch(sState)
    {
    case sInit:
      //输入提示
      break;
    default: 
      
      sState = sInit;
      break;
    case sPca:
      ResetPCA();
      //捕捉模式,捕捉到跳变沿时触发中断
      //CH-CL不用与CCAPnH-CCAPnL比较,因此不用使能ECOMn
      //也不需要设置PCA计数器阵列寄存器(CH和CL)以及捕获寄存器(CCAPnH和CCAPnL)
      SetCapnMod(0,PCA0_CAPN|PCA0_CAPP);
      //开启PCA中断
      EnPcaModnInt(0);
      sState = sInit;
      break;
    case sClk:
                        //软件定时器功能
                        ResetPCA();
      //定时器功能
      //CH-CL增加计数值,然后与CCAPnH和CCAPnL比较,相等触发中断
      //有比较功能 设置比较和匹配控制位
      SetModnCmp(0);
      //设置 CCAPnH和CCAPnL,当CH-CL计数值==CCAPnH-CCAPnL中的预制值
      //发生中断
      SetCCAPn(0,0x80,0x00);
      //开启PCA中断
      EnPcaModnInt(0);
      sState = sInit;
      break;
    case sPwm:
      ResetPCA();
      //pwm有比较功能 但没用到匹配...真特立独行
      //只要设置CCAPMn的PWM位和比较位
      SetPwmMod(0);
      //设置占空比,有点类似定时器Mod2,TH自动装载到TL
      //此处是CCAPnH自动装载到此处是CCAPnL(8位);CH装载到CL(8位)
      //CH-CL低于 CCAPnH-CCAPnL输出0,其他输出1
      SetCCAPn(0,0x80,0x80);
      sState = sInit;
      break;
    case sClkOut:
                        //高速输出功能
                        ResetPCA();
      //当PCA计数器的CH-CL与CCAPnH-CCAPnL中的预制值相等时
      //CCPn发生翻转,这个功能也用到比较-匹配
      SetModnCmp(0);
      SetModnTog(0);
      EnPcaModnInt(0);
      sState = sInit;
      break;           
    }
    //启动pca模块
    StartPCA(); 
  }
}

void PCA_ISR(void) interrupt 7
{
  switch(CCON&0x03)
  {
  //模块0触发中断
  case CCF0_INT:
    //清除模块中断标志位
    CCON &= (0x00<<CCF0_INT);
    break;
  //模块1触发中断
  case CCF1_INT:
    CCON &= (0x00<<CCF1_INT);
    break;
  default:
    break;
  }
}