PIC16F628Aを使ってPWM(Pulse Width Modulation)のテストをやってみます。
Arduinoでは、簡単にPWMが使えますので、PWMを使った経験があるのですが、PICでは初めてなので、WebサイトやPIC16F628Aの説明書を見て、その構造と方法がある程度できました。(完全に理解したわけではありません。Hi)
まず、ハードウェアですが、PWMは、PIC内のCCPモジュールで行われます。説明書に簡略した図がありました。
その信号によって、TMR2がカウントアップされます。
PWM信号の周期を決定するのはPR2レジスタ(8bit)です。
TMR2とPR2は常時比較され、一致したらRB3(CCP1)ピンにHighが出力され、TMR2はリセットされます。
一方、デューティサイクルを決定するのは、CCPR1LレジスタにCCP1COレジスタの5bit目と4bit目の値を加えた10bitのデータです。
このデータは、CCPR1Hレジスタ(10bit)に送られ、その上位8bitが常時TMR2と比較され、一致したらRB3(CCP1)ピンにLowが出力されます。
この2つの動作が繰り返され、PWM信号を発生します。TNR2には、プリセレクタを設定することができ、その値を含めて、各レジスタとPWM周期、デューティサイクル、PR2の関係は下の図のようになります。
mainプログラムを次のようにして、1000Hz デューティ比50%の信号を発生させてみました。クロックは内部クロック4MHzです。CONFI等は前の記事と同じです。
void main() {
CMCON = 0x07; //comparator OFF
TRISA = 0b00100000; //RA5 input 他はoutput
TRISB = 0x00; //全てoutput
PORTA = 0x00; //PORTA初期化
PORTB = 0x00; //PORTB初期化
/* PWM設定 */
CCP1CON = 0b00001100;//PWMモード,CCP1X=0,CCP1Y=0
T2CON = 0b00000001; //プリスケーラ1:4
/*
* PWM 周期設定 1000Hzに設定
* PR2=(クロック周波数/(PWM周波数*4*プリスケーラ値)-1
* =(4000000/(1000*4*4))-1
* = 249
*/
PR2 = 249;
/*カウンタ・レジスタ初期化 */
CCPR1L = 0;
CCPR1H = 0;
TMR2 = 0;
/* ディーティセット */
CCPR1L = 199;//デューティ比50%にセット 249/2=124.5なので、124をCCPR1Lにセット
/* PWM START(TMR2 START) */
TMR2ON = 1;
while(1){
}
CMCON = 0x07; //comparator OFF
TRISA = 0b00100000; //RA5 input 他はoutput
TRISB = 0x00; //全てoutput
PORTA = 0x00; //PORTA初期化
PORTB = 0x00; //PORTB初期化
/* PWM設定 */
CCP1CON = 0b00001100;//PWMモード,CCP1X=0,CCP1Y=0
T2CON = 0b00000001; //プリスケーラ1:4
/*
* PWM 周期設定 1000Hzに設定
* PR2=(クロック周波数/(PWM周波数*4*プリスケーラ値)-1
* =(4000000/(1000*4*4))-1
* = 249
*/
PR2 = 249;
/*カウンタ・レジスタ初期化 */
CCPR1L = 0;
CCPR1H = 0;
TMR2 = 0;
/* ディーティセット */
CCPR1L = 199;//デューティ比50%にセット 249/2=124.5なので、124をCCPR1Lにセット
/* PWM START(TMR2 START) */
TMR2ON = 1;
while(1){
}
}
実際のPWM信号です。
周期は、1msよりやや短いですが、ほぼ希望通りデューティ比50%の信号が出ています。
周波数を測ると
1010Hz~1012Hzでした。約1%の誤差です。内部クロックの誤差と思われます。周波数の精度を求めなければ、これで問題ないようです。
デューティ比を変えて、20%と80%の時の波形です。
OKですね。
次は、周波数の精度を少し上げて、ドレミファソラシドの音階を発生させてみたいと思います。
今回は、以下のサイトが大変参考になりました。