本帖最后由 lywust 于 2017-5-31 10:56 编辑
最近重新把单片机捡起来做了个小项目,其中调试STC专用PWM模块遇到一些阻碍,浪费了两天时间,为了后面的朋友少走弯路,特开贴抛砖引玉,也算是论坛新人报到帖。本帖先从分析STC例程开始,然后结合本人编程实际,来讨论一下专用PWM模块的使用注意事项:
(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;
}
}
|