/* 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;
}
}
轉自:http://bbs.21ic.com/icview-1057074-1-4.html