PICのCCPによる PWM

PICマイコンのPWMの設定は結構面倒です。
PWMの設定方法を根本から学ぶにはマニュアルを
読むのが一番ですが、あいにく英語のマニュアルしか
有りません。
ここで仕方なしに英語マニュアルを翻訳して、自分なりの
解説を加えました。
これは PIC12F683のマニュアル のPWMの部分の日本語訳です。
11.3 PWMモード
PWMモードはCCP1ピンにPWM信号を発生させる。デューティ・サイクル(Duty cycle)、周期(period)と分解能
(resolution)は次のレジスターで決められる。
・PR2
・T2CON
・CCPR1L
・CCP1CON
PWMモードにおいては、CCPモジュールはCCP1ピンに
10ビットの分解能のPWMを出力する。
CCP1ピンはポート・データーラッチと多重化されている為に
このピンのTRISはCCP1ピンが出力となるようにクリアー
されなければならない。
ノート: CCP1CONレジスターをクリアーするとCCP1ピンの
CCP1コントロールは解除される。
[解説]
TRISはTRISIOレジスタのことで、このレジスターはGPIOを
デジタル入力か出力に設定するためのものである。
PIC12F683の場合CCPのポートはGP2であるため、
TRISIO2 = 0 ;
と設定してポートをデジタル出力にする。
CCP1CONレジスターの下位4ビットはCCP1のモード
を決めます。
CCP1CON = 0x0c;
とするとCCP1ピンはPWMモードにセットされ
CCP1CON = 0;
とするとPWMモードが解除されます。
TRISはTRISIOレジスタのことで、このレジスターはGPIOを
デジタル入力か出力に設定するためのものである。
PIC12F683の場合CCPのポートはGP2であるため、
TRISIO2 = 0 ;
と設定してポートをデジタル出力にする。
CCP1CONレジスターの下位4ビットはCCP1のモード
を決めます。
CCP1CON = 0x0c;
とするとCCP1ピンはPWMモードにセットされ
CCP1CON = 0;
とするとPWMモードが解除されます。
図 11-1に単純化されたPWMのブロック図を示します。
図 11-4に典型的なPWMの波形を示します。
PWMの為のCCPモジュールの一歩一歩のセットアップ
方法は11.3.7のセクション「PWMオペレーションの
セットアップ」を見られたし。
図 11-3 簡略化された PWMのブロック図

Note
1:8ビットのタイマーであるTMR2レジスターは、
10ビットのタイムベースを作る為に、2ビットの内部クロック(Fosc)
又は2ビットのプリスケーラーに結合される。
2:PWMモードに於いてはCCPR1Hは読み込み専用レジスタである。
PWMのアウトプット(図 11-4)はタイムベース(周期)と
出力がハイ(Dutyサイクル)になる時間を有する。

[解説]
TMR2は8ビットのインクリメントカウンターで、通常は
システムクロック(Fosc/4)をプリスケーラ値で割った
クロックをカウントしますが、PWMモードの時は、
下位に2ビット足して、Foscを4で割り更にプリスケーラ
値で割ったクロックを10ビットのカウンタでカウントします。
例えば、PR2に255をセットすると、Timer2は
通常の場合255をカウントするとコンパレータ―がオンしますが
PWMモードの時は、
1024でコンパレータ―がONします。
なぜならコンパレーターはPR2のセット値とT10ビットに
拡張されたTMR2の上位8ビットを比較する為です。
この辺は深く考える必要は有りませんが、PR2にセット出来る最大の
値は8ビットなので255ですが、この時PWMの分解能は10ビットの
で1024になります。
TMR2は8ビットのインクリメントカウンターで、通常は
システムクロック(Fosc/4)をプリスケーラ値で割った
クロックをカウントしますが、PWMモードの時は、
下位に2ビット足して、Foscを4で割り更にプリスケーラ
値で割ったクロックを10ビットのカウンタでカウントします。
例えば、PR2に255をセットすると、Timer2は
通常の場合255をカウントするとコンパレータ―がオンしますが
PWMモードの時は、
1024でコンパレータ―がONします。
なぜならコンパレーターはPR2のセット値とT10ビットに
拡張されたTMR2の上位8ビットを比較する為です。
この辺は深く考える必要は有りませんが、PR2にセット出来る最大の
値は8ビットなので255ですが、この時PWMの分解能は10ビットの
で1024になります。
Dutyセット サンプルコード
void set_duty(unsigned int duty){ //上位8ビットを CCPR1Lにセット CCPR1L = duty >> 2; //下位2ビットをCCP1CONの4、5ビットセットする CCP1CON |= (duty & 0x0003) << 4; }
11.3.1 PWMの周期
PWMの周期はTimer2のPR2レジスターに設定される。PWMの周期は次の式で計算される。

TMR2がPR2と同じになった時、次のアップカウント・サイクルで
3つのイベントが発生する。
・TMR2がクリアーされる
・CCP1ピンがセットされる(例外:PWMのDutyが0の
場合はこのピンはセットされない)
・PWMのDutyサイクルがCCPR1LからCCP1Hに
ラッチされる。
Note:
Timer2のポスト・スケーラー(セクション7.0の「Timer2モジュール」
を参照のこと)はPWMの周波数の設定には使われない。
[解説]
ここで注意すべきはToscはPICのクロックの周期だと言うこと。
たとえば8MHzのクロックの場合
Tosc = 0.125 x 10^(-6) となる。
ここで注意すべきはToscはPICのクロックの周期だと言うこと。
たとえば8MHzのクロックの場合
Tosc = 0.125 x 10^(-6) となる。
11.3.2 PWM DUTY サイクル
PWMのDutyサイクルはCCPR1LレジスタとCCP1CONレジスタのDC1B<1:0>の複合レジスタに10ビットのデーターを書き込むことにより
指定される。
CCPL1Lは8ビットのMSbsとCCP1CONレジスタのDC1B<1:0>の
LSbs2ビットを有する。
CCP1LとCCP1CONレジスタのDC1B<1:0>は好きな時に書き込む
事が可能である。
PWMのDutyはPWMのサイクルが終了した時に初めて、CCPR1Hに
書き込まれる(たとえばPR2とTMR2のレジスターが同じになった時)。
PWMモードの時はCCPR1Hレジスターは読み込み専用となる。
PWMのパルス幅は11-2の式で計算され、

PWMのDutyは11-3の式で計算される。

CCPR1Hレジスターと2ビットの内部ラッチはPWMのDutyの
ダブルバッファとして使われる。
ダブルバッファリングは基本的にはPWM操作時のグリッジを
防ぐためのものである。
8ビットタイマーであるTMR2レジスターは、2ビットのシステムクロック
Fosc又は、2ビットのプリスケーラーと複合され、10ビットのタイマーとなる。
10ビットのタイムベースがCCPR1Hと2ビットラッチと一致した時、
CCP1ピンはクリアーされる(図 11-1を参照).
[解説]
dutyサイクルをCCPR1Lに設定してしかもこれをCCPR1Hに
コピーするのは、PWM周期の途中でDutyの設定が書き換えられても、
Dutyの設定が直ぐに変わらず、PWMの周期の始まりで、CCPR1Lの
値がCCPR1Hにコピーされて初めてDutyが変えられる為に、
PWMが乱れないようにしているのである。
これによりユーザーはPWMの繋ぎ目を気にすることなく、Dutyを
変えられるのである。
dutyサイクルをCCPR1Lに設定してしかもこれをCCPR1Hに
コピーするのは、PWM周期の途中でDutyの設定が書き換えられても、
Dutyの設定が直ぐに変わらず、PWMの周期の始まりで、CCPR1Lの
値がCCPR1Hにコピーされて初めてDutyが変えられる為に、
PWMが乱れないようにしているのである。
これによりユーザーはPWMの繋ぎ目を気にすることなく、Dutyを
変えられるのである。
11.3.3 PWM 分解能
分解能は与えられた周期に幾つのDutyサイクルが可能かで決定される。
たとえば、10ビットの分解能は1024の別々のDutyサイクルを
得ることが出来るが、8ビットの分解能は256のDuytsサイクル
を得る。
分解能の最大はPR2が255のとき、10ビットである。
分解能はPR2レジスター値の関数で11-4式であらわされる。



Note:
仮にパルス幅が周期より広い場合は、PWMのピンは
変化しない。
[解説]
PWMの分解能はPWMの周期の中をいくつに分解できるかと
言うことであり、それはPR2の設定値で決まると言うことです。
PR2は下位に2ビット追加されていますので、4倍になります。
すなわちPR2を10に設定すると分解能は40になります。
PWMの分解能はPWMの周期の中をいくつに分解できるかと
言うことであり、それはPR2の設定値で決まると言うことです。
PR2は下位に2ビット追加されていますので、4倍になります。
すなわちPR2を10に設定すると分解能は40になります。
11.3.4 スリープモード中の操作
スリープモード中はTMR2レジスタはインクリメントしません。そしてモジュールの状態も変化しません。
もしCCP1のピンに出力がある場合、そのまま出力し続けます。
そのデバイスが起き上がった(ウエイク・アップ)した時は、
TMR2は前の状態を保持します。
11.3.5 システムクロックが変化した時
PWMの周波数はシステムクロックにより供給されます。
システムクロックの辺かはPWMの周波数に変化を与えます。
セクション3.0
"Oscillator Module (With Fail-SafeClock Monitor)"
の追加情報を参照のこと。
11.2.6 リセットの効果
リセットは全てのポートを強制的に入力モードにし、CCPレジスタをリセットの状態にします。
11.3.7 PWMオペレーションのセットアップ
PWMのオぺーレーションには次の手順を実行します。1、関連したTRISビットをセットすることで、PWM pin(CCP1)
の出力ドライバーを無効にします。
2、PR2レジスターをセットしてPWMの周期をセットします。
3、CCP1CONレジスタに適当な値を入れて、CCPモジュールを
PWMモードにします。
4、CCPR1LレジスターとCCP1CONレジスタのDC1Bビットを
設定してDutyサイクルを設定します。
5、Timer2のスタート
・PIR1レジスタのTMR2IF割り込みフラグをクリアーします。
・T2CONレジスタのT2CKPSビットにTimer2のプリスケール
値を設定します。
・T2CONレジスタのTM2ONビットをセットしてTimer2を起動します。
6、PWMの新しいサイクルが始まったら、PWMの出力を許可します。
・Timer2がオーバーフローするのを待ちます。
(PIR1レジスターのTMR2IFビットをセットします)
・関連したTRISビットをクリヤーして、CCP1ピンの出力を
有効にします。
[解説]
この手順によると、最初はTRISレジスタで、PWMの出力端子を
入力にしておいて、全ての設定が終わった状態で、しかもTimer2が
オーバーフローした段階で初めてPWMの端子を出力にしている。
これはLEDの照度調整などの場合は厳格なプロシジャーは必要ないが、
モーターの制御などでは、ほんの少しのタイミングのずれが大きな
事故になるからであろう。
いずれにしても、基本的にはこのプロシジャーに従うべきである。
この手順によると、最初はTRISレジスタで、PWMの出力端子を
入力にしておいて、全ての設定が終わった状態で、しかもTimer2が
オーバーフローした段階で初めてPWMの端子を出力にしている。
これはLEDの照度調整などの場合は厳格なプロシジャーは必要ないが、
モーターの制御などでは、ほんの少しのタイミングのずれが大きな
事故になるからであろう。
いずれにしても、基本的にはこのプロシジャーに従うべきである。
簡単な実験回路とソースコード
さて上のマニュアルに従って最も簡単な回路、ボリュームの値を読み取ってその値でLEDの輝度を
PWMで変える回路です。
AN0(GP0)に0~5Vのボリュームの電圧がかかります。
それをA/DのAN0で読み込み、その読み込み値を
DutyにしてPWMを行い、LEDを駆動します。
クロックは8M、TMR2のプリスケーラーは1を設定、
PR2 = 255 として PWMの分解能は10ビット1024となります。
PWMの周期は、
(255 + 1) x 0.125μSec x 4 = 128 μSec =7.81Khz
となります。
もちろんLED駆動でこんな高い分解能と周波数は必要
有りませんが、あくまでも実験です。
(周波数と分解能が高くていけないと言うことは無く、
これでもきちんとLEDの調光は出来ました。)
回路図です。
バラックで組み立てた実験サーキットです。
PWMのオシロスコープ波形です。
以下この実験に使用したコードを載せます。
#define _LEGACY_HEADERS #include#define _XTAL_FREQ 8000000 //8MHz __CONFIG(WDTDIS & PWRTEN & BOREN & INTIO & MCLRDIS & IESODIS & FCMDIS & UNPROTECT); void ini_set(); unsigned int adc_read(unsigned char ch); void pwm_start(); void set_duty(unsigned int duty); /*********************************** 初期設定 ***********************************/ void ini_set(){ GIE = 1; //全体割り込みの許可 PEIE = 1; //周辺割り込みの許可 OSCCON = 0b01110000; //内蔵発振器 8MHz使用に設定 //以下2行はA/Dの読み込みでPWMの設定とは無関係です ANSEL = 0b01011000; //AN3をアナログに FOSC /16 ADCON0 = 0b10000000; //AD分周比 1/16 //PWMの出力はGP2ですがこの段階では1の入力にしてあります TRISIO = 0b00010101; //GP0,4,2 を 入力 = 1 } void main() { unsigned int value; //A/D 読値 10ビット ini_set(); pwm_start(); while(1){ value = adc_read(3); // A/Dの読み込み set_duty(value); //PWMセット } } /************************************** 内臓PWMのDutyの設定 DutyはA/Dの読み値そのままで 10ビットで設定されます ***************************************/ void set_duty(unsigned int duty){ //上位8ビットを CCPR1Lにセット CCPR1L = duty >> 2; //下位2ビットをCCP1CONの4、5ビットセットする CCP1CON = (duty << 4) | 0x0C; } /*************************************** PWMのスタート ***************************************/ void pwm_start(){ //Timer2を使った、組み込みのPWMのスタート CCP1CON |= 0b00001100; //PWMモードにする PR2 = 255 ; T2CON =0b00000100; //プリスケールを1に設定 //Timer2がカウントアップするのを待っています while(!TMR2IF) ; //ここでようやくPWMの端子を出力に設定します TRISIO2 = 0; //TRISIO = 0b00010001; } /***************************************** A/Dコンバーターの読み込み 10ビットで返す *****************************************/ unsigned int adc_read(unsigned char ch){ //右詰め、電源基準、A/D有効化 + チャンネルセレクト ADCON0 = 0b10000001 + (ch << 2); __delay_us(20); GODONE = 1; while(GODONE); return (ADRESH * 256 + ADRESL) ; }