历史上的今天
返回首页

历史上的今天

今天是:2025年01月26日(星期日)

2019年01月26日 | STC15单片机6路专用PWM

2019-01-26 来源:eefocus

(1)STC例程分析
/* STC15Fxx 系列 输出任意周期和任意占空比的PWM实例*/

#define CYCLE   0x1000L    //定义PWM周期(最大值为32767)
#define DUTY    10L        //定义占空比为10%

void pwm()
{
    P0M0 = 0x00;                   //因PWM模块相关IO口初始状态为高阻,需要将IO口设置为准双向或推挽输出才能正常输出波形;
    P0M1 = 0x00;
    P1M0 = 0x00;
    P1M1 = 0x00;
    P2M0 = 0x00;
    P2M1 = 0x00;
    P3M0 = 0x00;
    P3M1 = 0x00;
    P4M0 = 0x00;
    P4M1 = 0x00;

    PIN_SW2 |= 0x80;                             //使能访问XSFR,否则无法访问以下特殊寄存器
    PWMCFG = 0x00;                              //配置PWM的输出初始电平为低电平,也就是第一次翻转前输出的电平
    PWMCKS = 0x00;                              //选择PWM的时钟为Fosc/(0+1),其中FOSC为外部或内部系统时钟经分频后给单片机的工作时钟
    PWMC = CYCLE;                               //设置PWM周期(最大值为32767),该寄存器为15位,实际使用时最好定义周期形参为unsigned int
    PWM2T1 = 0x0000;                         //设置PWM2第1次反转的PWM计数,也就是电平第一次发生翻转的计数值                                                             //此例中定义PWM2T1为0,初始电平也为0,所以在一开始,也就是计数为0时,直接翻转为高,这样方便计算占空比
    PWM2T2 = CYCLE * DUTY / 100;     //设置PWM2第2次反转的PWM计数,其实这两个寄存器不分先后,没有第1第2之分,只是设置两个点,到点电平翻转
                                                            //占空比为(PWM2T2-PWM2T1)/PWMC
    PWM2CR = 0x00;                           //选择PWM2输出到P3.7,不使能PWM2中断,也可以通过该寄存器切换要输出PWM的IO口
    PWMCR = 0x01;                            //使能PWM信号输出
    PWMCR |= 0x80;                          //使能PWM模块,此处有坑,容后详述!!
    PIN_SW2 &= ~0x80;
}

从例程中看需要特别注意以下事项:

(a)周期和占空比的定义:常量后面加L,代表数据类型为长整型,这是为了防止在给反转计数器设置值的时候产生错误。  在该例中,反转计数器的计算方式如下: PWM2T2 = CYCLE * DUTY / 100; 从该式中可以看出,假如周期未定义为4字节的长整型,而定义为2字节,那么在计算 CYCLE * DUTY ,比如CYCLE=30000,DUTY为50,大家可以计算一下,此时已经超过了16位最大值,而产生了中间变量的溢出,也就是说还没来得及除以100,已经溢出了,而导致不可预料的错误;

(b)IO口一定要初始化;特殊功能寄存器访问使能一定要开, PIN_SW2 |= 0x80;  其他寄存器的配置方法详见手册,都写的比较清楚; 

(c)PWM2T1与PWM2T2 不能设置为相等的值,否则产生竞争,而导致整个电平翻转全部乱套了,因为,因成本原因,STC单片机里没有相关的判断优先级的机制,在这里不是说STC单片机不好,一个两三块钱的单片机功能如此强大,已经很不错啦。另外,PWM2T1与PWM2T2两个值在应用情况允许的情况下尽量不要将其中一个设置为0!

(d)PWMCR |= 0x80;  使能PWM模块之前,一定要将所有的寄存器配置到位,因为一旦使能,内部计数器已经开始计数,并与翻转计数器比较,而默认的情况下两个翻转计数器都是0,从而产生竞争,而直接把PWM模块搞乱了。

(2)本人编程实例分析:
因为是第一次使用该单片机的PWM模块,本人老老实实的照搬例程,只是对占空比更改为可变,加了一个形参,,实际使用三个PWM模块,代码如下:


/*错误代码分析*/

#define CYCLE   6000   //定义PWM周期(最大值为32767)
void pwm2(unsigned int DUTY )                  //PWM2
{
    P_SW2 |= 0x80;                  //使能访问PWM在扩展RAM区的特殊功能寄存器XSFR
    PWMCFG = 0x00;                  //配置PWM的输出初始电平为低电平
    PWMCKS = 0x00;                  //选择PWM的时钟为Fosc/(0+1)
    PWMC = CYCLE;                   //设置PWM周期,定义PWM周期(最大值为32767)
    PWM2T1 = 0x0000;                //设置PWM2第1次反转的PWM计数
    PWM2T2 = CYCLE * DUTY / 100;    //设置PWM2第2次反转的PWM计数
                                    //占空比为(PWM2T2-PWM2T1)/PWMC
    PWM2CR = 0x00;                  //选择PWM2输出到P3.7,不使能PWM2中断
    PWMCR = 0x07;                   //使能PWM信号输出
    PWMCR |= 0x80;                  //使能PWM模块
    P_SW2 &= ~0x80;
}
void pwm3(unsigned int DUTY)                  //PWM3
{
    P_SW2 |= 0x80;               
    PWMCFG = 0x00;                  
    PWMCKS = 0x00;                  
    PWMC = CYCLE;                   
    PWM3T1 = 0x0000;                
    PWM3T2 = CYCLE * DUTY / 100;    

    PWM3CR = 0x00;                  
    PWMCR = 0x07;                  
    PWMCR |= 0x80;             
    P_SW2 &= ~0x80;
}
void pwm4(unsigned int DUTY)                  //PWM4
{
    P_SW2 |= 0x80;                
    PWMCFG = 0x00;                 
    PWMCKS = 0x00;              
    PWMC = CYCLE;                  
    PWM4T1 = 0x0000;               
    PWM4T2 = CYCLE * DUTY / 100;    

    PWM4CR = 0x00;                  
    PWMCR = 0x07;                   
    PWMCR |= 0x80;              
    P_SW2 &= ~0x80;
}
       

以上代码猛一看,并没有明显的问题,可是折腾了将近一天才发现原来是定义周期的时候没有在常量后面加L,是不是溢出了呢,终于找到错误原因了,果断在后面加个L,居然还是不行!又试了无数次,后来才发现一旦输出过占空比为0的PWM波形,那就全乱套了,否则是正常的。仔细分析得知:占空比为0时, PWM2T1, PWM2T2都设置为零,从而产生了竞争,详见上面例程分析C条注意事项。而不是想当然的根据公式,占空比为(PWM2T2-PWM2T1)/PWMC,算出占空比为0,这就错了!!那问题来了,怎么输出全低的电平呢?没有办法啊!只能变通解决!进函数后,先判断占空比是否为0,假如为0,关闭对应PWM的输出,将其变为普通IO口,然后以普通IO的形式输出0,猥琐但简单粗暴,占空比0的问题得以解决。为了保险起见,占空比为100%的情况也可以用类似的办法处理,以免发生不测。
      

说到这里,各位可能觉得我大功告成了,其实没有,你想输出占空比90%时,结果有时候是90,有时候是10,为什么列,翻转前的起始电平是随机的,时0时1,此时修改后的代码如下:
void pwm2( unsigned int DUTY)                  //PWM2
{   
        if(DUTY==0)
        {
        PWMCR &=~0x01;
        PWM2=0; 
        }
        else
        {
        P_SW2 |= 0x80;                  //使能访问PWM在扩展RAM区的特殊功能寄存器XSFR
    PWMCFG = 0x00;                  //配置PWM的输出初始电平为低电平
    PWMCKS = 0x0f;                  //选择PWM的时钟为Fosc/(0+1)
    PWMC = CYCLE;                   //设置PWM周期,定义PWM周期(最大值为32767)
    PWM2T1 = 0x0000;                //设置PWM2第1次反转的PWM计数
    PWM2T2 = CYCLE * DUTY / 100;    //设置PWM2第2次反转的PWM计数
                                    //占空比为(PWM2T2-PWM2T1)/PWMC
    PWM2CR = 0x00;                  //选择PWM2输出到P3.7,不使能PWM2中断
    PWMCR |= 0x01;                  //使能PWM信号输出
    PWMCR |= 0x80;                  //使能PWM模块
        }
}
void pwm3(unsigned int DUTY)                  //PWM3
{
        if(DUTY==0)
        {
        PWMCR &=~0x02;
        PWM3=0; 
        }
        else
        {
    P_SW2 |= 0x80;               
    PWMCFG = 0x00;                  
    PWMCKS = 0x0f;                  
    PWMC =  CYCLE;                  
    PWM3T1 = 0x0000;                
    PWM3T2 = CYCLE * DUTY / 100;                                     
    PWM3CR = 0x00;                  
    PWMCR |= 0x02;                   
    PWMCR |= 0x80;             
        }
}
void pwm4(unsigned int  DUTY)                  //PWM4
{
         if(DUTY==0)
        {
        PWMCR &=~0x04;
        PWM4=0; 
        }
        else
        {   
        P_SW2 |= 0x80;                
    PWMCFG = 0x00;                 
    PWMCKS = 0x0f;              
    PWMC = CYCLE;                   
    PWM4T1 = 0x0000;               
    PWM4T2 = CYCLE * DUTY / 100;                                
    PWM4CR = 0x00;                  
    PWMCR |= 0x04;                   
    PWMCR |= 0x80;              
        }
}
       

最后经过惨痛的实验分析,仔细推敲,这三个子函数,分开调用没错,但只有最开始调的那个是正常的,为什么呢?原因是假如调用了PWM2,那么PWM3,PWM4中的 PWM3T1 ,PWM3T2 , PWM4T1 ,PWM4T2是未被初始化的,而此时因为PWM中已经执行了PWMCR |= 0x80,从而开启了PWM模块的总计数器,此总计数器一开,会自动的开始跟各路PWM模块的翻转计数器去比较,从而产生波形,但是PWM3,PWM4中的翻转寄存器还全是默认的0,从而产生竞争全乱套 了,自然就输出不了正常的波形,起始电平时高时低。这个问题主要会在使用多路PWM时不注意的情况下会产生。 解决办法是:在PWMCR |= 0x80执行前,一定要将所有需要用到的PWM的翻转计数器初始化,否则完蛋了!修改后的代码如下:
/*任意周期和任意占空比DUTY%的PWM*/
#define CYCLE   6000L           //定义PWM周期(最大值为32767)
sbit PWM2=P3^7;                                         
sbit PWM3=P2^1;                                           
sbit PWM4=P2^2;                                        

void pwminit()
{ P_SW2 |= 0x80; 
   PWMCFG = 0x00;                  //配置PWM的输出初始电平为低电平
   PWMCKS = 0x0f;                  //选择PWM的时钟为Fosc/(0+1)
   PWMC = CYCLE;                   //设置PWM周期,定义PWM周期(最大值为32767)
   PWM2CR = 0x00;                  //选择PWM2输出到P3.7,不使能PWM2中断   
   PWM3CR = 0x00; 
   PWM4CR = 0x00; 
   PWM2T1 = 0x0001;               
   PWM2T2 = 0;         
   PWM3T1 = 0x0001;               
   PWM3T2 = 0;         
   PWM4T1 = 0x0001;               
   PWM4T2 = 0;         
   PWMCR |= 0x80;                  //使能PWM模块
   P_SW2 &=~0x80; 
}
void pwm2( unsigned int DUTY)           //PWM2
{   
        if(DUTY==0)
        {
        PWMCR &=~0x01;
        PWM2=0; 
        }
        else if        (DUTY==100)
        {
        PWMCR &=~0x01;
        PWM2=1; 
        }
        else
        {
        P_SW2 |= 0x80;                  //使能访问PWM在扩展RAM区的特殊功能寄存器XSFR
       PWM2T1 = 0x0001;                //设置PWM2第1次反转的PWM计数
       PWM2T2 = CYCLE * DUTY / 100;    //设置PWM2第2次反转的PWM计数
       P_SW2 &=~0x80;                  //占空比为(PWM2T2-PWM2T1)/PWMC
       PWMCR |= 0x01;                  //使能PWM信号输出

        }

}
void pwm3(unsigned int DUTY)                  //PWM3
{         
        if(DUTY==0)
        {
        PWMCR &=~0x02;
        PWM3=0; 
        }
        else if(DUTY==100)
        {
        PWMCR &=~0x02;
        PWM3=1; 
        }        
        else
        { 
        P_SW2 |= 0x80;          
       PWM3T1 = 0x0001;                
       PWM3T2 = CYCLE * DUTY / 100;                                     
       P_SW2 &=~0x80;                 
       PWMCR |= 0x02;                   

        }        
}
void pwm4(unsigned int  DUTY)                  //PWM4
{         
         if(DUTY==0)
        {
        PWMCR &=~0x04;
        PWM4=0; 
        }
        else if (DUTY==100)
        {
        PWMCR &=~0x04;
        PWM4=1; 
        }
        else
        {         
        P_SW2 |= 0x80;                 
       PWM4T1 = 0x0001;               
       PWM4T2 = CYCLE * DUTY / 100;                                
      P_SW2 &=~0x80;                  
      PWMCR |= 0x04;                   

        }
}


推荐阅读

史海拾趣

CW Industries公司的发展小趣事

面对电子信息行业的快速发展和变化,CW Industries始终保持着敏锐的市场洞察力。公司紧跟技术潮流,不断调整和优化产品结构,以满足客户不断变化的需求。例如,随着智能家居和物联网的兴起,CW Industries积极开发适用于这些新兴领域的产品和解决方案。这种灵活应变的能力使得CW Industries在电子行业中始终保持着领先地位,并为公司带来了持续的增长和发展。

B&K Precision公司的发展小趣事

在1948年,当美国民众开始大量购买电视机时,电视机的维修需求也随之增加。芝加哥的企业家卡尔·科恩(Carl Korn)和菲利普·班(Philip Ban)注意到了这一市场机会。他们发现,当时缺乏能够轻松测试电视组件的设备,于是决定自己动手制作。两位创业者从车库起步,开始制造自己的测试设备,并成立了中央电视服务公司。他们的业务迅速扩展,开始向其他电视维修店销售CRT再生器和真空管测试仪,为公司的后续发展奠定了坚实的基础。

Ferranti Electric Inc公司的发展小趣事

Ferranti Electric Inc公司非常重视知识产权的保护和积累。通过不断的技术研发和创新,公司积累了大量的知识产权,包括商标、专利等。这些知识产权的拥有不仅彰显了公司的技术实力和创新能力,也为公司的市场竞争提供了有力的保障。公司不断加大在研发和创新方面的投入,以保持其在电气行业的领先地位。

CommScope Inc公司的发展小趣事

在通信行业,技术的不断创新和产品的持续升级是企业保持竞争力的关键。康普公司一直注重研发和创新,不断推出具有竞争力的新产品和解决方案。例如,在2004年,康普成立了CCS(连接解决方案)工厂,主要生产宽带同轴电缆、网络对绞线缆以及铜和光纤连接器系列产品。这些产品不仅满足了市场对于高速、高质量网络连接的需求,也进一步巩固了康普在通信行业的领先地位。

ABL Aluminum Components公司的发展小趣事

随着技术的成熟,ABL公司开始积极拓展市场。公司通过与大型电子产品制造商建立合作关系,将其铝合金组件应用于更广泛的领域。同时,ABL公司也注重品牌建设,通过参加行业展会、发布技术白皮书等方式,提升公司在业内的知名度和影响力。随着市场份额的逐步扩大,ABL公司逐渐成为了电子行业铝合金组件领域的领军企业。

Alpha & Omega Semiconductor(万国半导体)公司的发展小趣事

ABL Aluminum Components公司自成立以来,一直注重技术创新。早期,公司研发出了一种新型铝合金材料,这种材料具有优异的导电性和轻量化特点,迅速在电子行业引起关注。随着技术的不断迭代,ABL公司逐步将这种材料应用于手机、电脑等消费电子产品的内部组件,大大提高了产品的性能和用户体验。凭借这一技术优势,ABL公司逐渐在电子行业中占据了一席之地。

问答坊 | AI 解惑

ARE C

ARM  的C语言编程!…

查看全部问答>

谈谈单片机和你的梦想

我来这里学习是为了实现我参军的梦想,我要去的地方和单片机息息相关。我要穿上那白色的制服,实现自己的价值,为理想去战斗!所以单片机就是我现在的武器,为了我梦中的战斗生活开路!…

查看全部问答>

国产示波器的架构

输入的电压信号经耦合电路后送至前端放大器,前端放大器将信号放大,以提高示波器的灵敏度和动态范围。放大器输出的信号由取样/保持电路进行取样,并由A/D转换器数字化,经过A/D转换后,信号变成了数字形式存入内存中,微处理器对内存中的数字化信 ...…

查看全部问答>

红外遥控系统原理及单片机解码实例

本帖最后由 paulhyde 于 2014-9-15 03:23 编辑 红外解码,希望对大家有用。  …

查看全部问答>

三垦模块

系列功率模块资料…

查看全部问答>

我有个板子加了复位程序,搞不清楚在哪复位,帮忙分析下

EEWORLD合作qq群:49900581群主:wangkj 情况是这样的,我接上负载,他就会复位,不接负载,他就不复位,你们看,这是哪里复位呢? 我开始是怀疑电源引起的复位…

查看全部问答>

ARM片内RAM问题

我有块芯片是ARM9EJS的SOC,现在想问下,ARM9EJS是否有片内RAM,我怎么确认其是否可用?从NOR FLASH启动后在片内RAM中建立C环境,然后运行C代码方案是否可行,如果可行是否有什么需要注意的,比如同片外RAM运行C代码有什么不同?…

查看全部问答>

菜鸟问题:模组和解决方案有什么区别啊?

有朋友用EDGE的模组就可以做数据卡,我搞不清楚这和解决方案有什么区别。 请尽量说的详细一点。 多谢了。…

查看全部问答>

wince4.2與wince5.0與wince6.0的差異?

使用wince4.2的bsp如果要移植到wince5.0或6.0 是不是要大幅改寫driver或什麼的,之間的使用方式 是否通用 thx…

查看全部问答>

计算机串口通信

我想通过计算机的COM串口输出高脚电平和低电平,应当怎样做啊? 我是在VB语言中控制的 用MSComm  输出的高电平和低电平以后怎样读取啊? 请各位大侠帮忙。。 最好带一部分解释代码 先谢谢啦!!…

查看全部问答>