今回はPWM制御に関するチャレンジ課題です。
PWM制御プログラム2通り
PWMの最後はチャレンジ課題で締めくくりたいと思います。
前回までに、LEDを2通りの方法でPWM制御しました。PWMの原理自体はとても簡単だったと思いますが、周期やデューティーサイクルの算出が難しかったと思います。
そこで、ちょっと地味ですが、PWMのチャレンジ課題は周期とデューティーサイクルの計算に慣れていただく内容にしました。
LEDの点滅周期(PWMの周期)を長くすると (つまりゆっくり点滅すると)「光過敏性発作」を誘発する可能性があります。点滅は1秒間に30回以下にしないよう注意してください。
プログラム制御のPWM
PWMをプログラムで制御する場合、LEDを点灯、デューティーサイクルの時間分__delay_us()関数などで時間待ち、LEDを消灯、時間待ち、という処理の繰り返しでした。
説明の時に作成したプログラムは、この点滅が認識できないように1秒間に200回点灯させました。目視した場合、どの程度の周期で点滅しているように見えてしまうのか、確認するプログラムを作成してみましょう。
動作内容としては、PWMのデューティー比は50%として、以下の動作をするようにします。
- 最初に1秒間に200回の点滅を5秒間続ける
- LEDを1秒間消灯する
- 次に1秒間に180回の点滅を5秒間続ける
- (2)と(3)の動作を繰り返す。点滅回数は、20回ずつ減らし、1秒間に40回の点滅まで続ける
PWMモジュール制御のPWM
上の動作をPWM機能を利用してプログラムを作成してみてください。
周期の設定はPR2レジスタ、デューティーサイクルの設定はCCPR1LレジスタとCCP1CON/DC1Bレジスタです。
PWMの次のステップ
今回はLEDをPWM制御を行いました。今回は明るさの調整のみでしたが、フルカラーLEDという部品を使用すると様々な色の表示が可能になります。
フルカラーLEDは以下のようなものです。
フルカラーLEDは、単に赤色、緑色、青色の3つのLEDが1つのパッケーシに収められている部品です。赤、緑、青は光の三原色ですから、赤、緑、青のそれぞれをPWM制御して明るさを変えれば様々な色の表示が可能になります。
また、サーボモーターというモーターがあります。
通常のモーターは電圧を加えるとモーターの軸がぐるぐる回転しますが、このサーボモーターは0度〜180度の角度の範囲内で回転します。用途としてはロボットアームなど、稼働角度が限られている場合などです。
サーボモーターを制御するにはPWM信号を与えます。上のサーボモーターの仕様を見ると、
- PWMサイクル: 20ms
- 制御パルス: 0.5ms 〜 2.4ms
と書かれています。つまり、PWMの周期は20ms、デューティーサイクルを0.5msから2.4msまで変化させることにより、0度〜180度まで回転制御ができる、というわけです。
サーボモーターの制御信号は、PICマイコンの出力ピンを直接接続できますので、比較的簡単な回路で制御回路が実現できます。
いずれも、結局はPWMの周期とデューティーサイクルの制御を行うものです。今までの知識とアイデアがあれば、色々なものが作れるようになりますので、ぜひPWMをマスターしてみてください。
次回からA/Dコンバータの説明になります。
日付 | 内容 |
---|
更新履歴
日付 | 内容 |
---|---|
2017.1.22 | 新規投稿 |
2018.12.1 | 点滅周波数の注意点追加 |
お世話になります。
WAKAZOU様への返信で
1) 細かく設定する方法で、この要領でCCPR1L、DC1Bも周波数freqで計算できると思います。
と説明がありますが、CCPR1Lは理解できましたが、DC1Bの計算方法が分からないので
ご説明お願い致します。
ご質問どうもありがとうございます。
デューティーサイクルは10ビットで設定する仕様ですが、PICマイコンのレジスタは8ビットです。レジスタ1つでは設定できませんので、PIC12F1822ではCCPR1Lの8ビット + DC1Bの2ビットを10ビットとして扱います。デューティーサイクルの値からCCPR1LとDC1Bの値を求める方法はこのシリーズの第3回の記事に詳しく説明しておりますので、ご確認いただければと思います。
https://tool-lab.com/pic-app-3/
早速のご返信ありがとうございます。
PR2、CCPR1L、DC1B、周波数の相関関係をようやく理解することができました。デューティーサイクルと1クロック分の時間及び周波数の関係を混同していたのと、2進数と10進数を混同していました。コピペしていてもなかなか理解が進まづ、チャレンジ課題の応答等で大分参考になりました。今後ともよろしくお願いいたします。
こんばんは、自信がないので教えてください。
チャレンジ課題を考えはじめましたが、ひとまず周期だけ20Hzにして点灯・消灯してみようと思ったのですが私の実験環境,試作プリント基板搭載のPIC12F683では80Hzが下限(PR2=194,プリスケーラ16まで)になるとの計算になりました。点滅は見えてないのですが、プリスケーラ64が設定できる他のPICを使用しないと上記チャレンジ課題はできないのでしょうか?
それとも理解不足でしょうか?
ーーー 初期化、Config除く
//チャレンジ課題
//PWM HW 設定
CCP1CONbits.CCP1M = 0b1100; // PWM有効 Acitve High
T2CONbits.T2CKPS = 0b10; // プリスケーラ 1:16
PR2 = 194; // 周期 12.5ms
CCPR1L = 394/4; // dutycycle 50%(6.3ms/16) 上位8ビット
CCP1CONbits.DC1B = 394; // 下位2ビット
//LEDの点滅周期を変更
//デューティー比は50%固定
while(1){
//80Hz 5秒間点灯 1秒間消灯
T2CONbits.TMR2ON = 1; //PWM制御 LED GP2 ON
__delay_ms(5000);
T2CONbits.TMR2ON = 0; //PWM制御 LED GP2 OFF
__delay_ms(1000);
}
wakazoさま、
コメントどうもありがとうございます。
結論としては、wakazoさまのご理解で正しいです。
PWMの周期は
( (PR2) + 1 ) x 4 x 1クロック周期 x (TMR2プリスケーラ値)
ですので、20Hz周期にしたい場合、1周期は50msになりますが、この式によると最大の周期は、
( 255 + 1 ) x 4 x 1μs x 16 = 約16.4ms
となり、20msは実現できません。
このような場合ですが、解決策としては邪道と言われそうですが、クロック周期を長くすると対応できます。クロック周波数は1MHzにしていると思いますが、この場合の最大周期は16.4msですので、例えば、クロック周波数を250kHzにすると、クロック周期は4μsになります。例えば20ms周期にしたい場合、
( 77 + 1 ) x 4 x 4μs x 16 = 約20ms
ですので、PR2を77、プリスケーラを16にすれば約20msの周期を得ることができます。また、クロック周波数250kHzの状態で周期を2msにしたい場合は、PR2を30、プリスケーラを4にすれば近い値が得られると思います( (30 + 1) x 4 x 4μs x 4 = 1.984ms)。
ということで、チャレンジ課題としてはちょっと良問とは言い難いものになってしまいました。すみません。
PICマイコンにPWM制御機能が入っているのはいいですが、なんかこう、100%自分の思い通りになる、という感じではないですよね。人生も同じように思い通りになりませんが、PWM機能の場合はハードウエアで実現しているので仕方ないところもあります。PWM制御をする場合、プログラムでPWM信号を生成するか、PWMモジュールを使うか、目的に応じて使い分けられるといいと思います。
ありがとうございます。
やってみます。 簡単ですが
こんにちは、クロック周波数を250KHzに変更してチャレンジ課題をすすめてみました。60Hzから10Hzづつ下げて20Hzまで変更するようにしてみました。PWMの設定値を変更する作戦がよくわからずひとまず配列を使っていれてみました。
—
/*チャレンジ課題 PWM HW 設定 LEDの点滅周期を変更 60Hz –> 20Hz
デューティー比は50%固定*/
CCP1CONbits.CCP1M = 0b1100; // PWM有効 Acitve High
T2CONbits.T2CKPS = 0b10; // プリスケーラ 1:16
int i = 0;
int pr2[5] = {64,77,97,129,194}; //計算結果を配列に
int ccpr1L[5] = {33,39,49,65,99}; //計算結果を配列に
int ccp1con[5] = {13,156,192,260,391}; //計算結果を配列に
while(1){
for(i = 0; i < 5; i++){
PR2 = pr2[i];
CCPR1L = ccpr1L[i];
CCP1CONbits.DC1B = ccp1con[i];
//5秒間点灯 1秒間消灯
T2CONbits.TMR2ON = 1; //PWM制御 LED GP2 ON
__delay_ms(5000);
T2CONbits.TMR2ON = 0; //PWM制御 LED GP2 OFF
__delay_ms(1000);
}
}
2点質問させてください。
・もっと細かく変更したい場合、配列だと面倒なのですが何かスマートな方法はありますか?
・私の実験環境(PICが12F683で基本的な回路は同等)では、以上で動かすと5秒後にLEDが消えず、逆に1秒間明るく点灯してしまうことがあります。これはどのような状況なのか?
毎日こつこつ充実させ頂いてます。
ありがとうございます。
wakazoさま、
ご質問どうもありがとうございます。
以前のご質問で初心者と書かれていましたが、ご質問内容を見ると中級者という感じで、かなり色々とチャレンジされてるんですね。色々と工夫されていらっしゃるで、ご自分で作りたいものが作れるようになってくると思いますよ。
ところで、ご質問の件の2番目については私の説明が悪かったようです。その内容も含めてご回答いたします。
1) 細かく設定する方法
細かく設定したい場合ですが、配列で設定値を用意しておき、for文で順番に設定していくのはわかりやすい方法です。細かく設定したい場合は配列が大きくなりますので、PR2/CCPRL/DC1Bそれぞれをプログラムで計算して設定していくのがよいと思います。
その考え方をご説明します。配列を使用したプログラムで、例えばpr2配列の1番目の要素(60Hzの設定値)は以下のように計算されたと思います。
(PR2 + 1 ) x 4 x 4μ x 16 = 1 / 60
を満たすPR2を計算する。
まず、上の式をμsに統一すると以下の式になります。
(PR2 + 1 ) x 4 x 4 x 16 = 1000000 / 60
この式から、PR2は
PR2 = 1000000 / (60 x 4 x 4 x 16 ) – 1 = 64 (少数切り捨て)
ということは、周波数を変数としてfreqとすると、PR2は
PR2 = 1000000 / ( freq * 4 * 4 * 16 ) – 1
となります。この要領でCCPR1L、DC1Bも周波数freqで計算できると思います。
次に、周波数を5Hz刻みで減らしていく場合、for文で変えていくのがよいと思います。具体的には以下のようになります。
for( freq = 60; freq >= 20; freq = freq – 5 ) {
// freqには周波数が入っているので、freqからPR2、CCPR1L、DC1Bを計算してそれぞれのレジスタに設定する
}
for文は、for(A, B, C)という書き方ですが、動作としては、最初の状態はAとして、Bが成立している間、Cを行いながら{}で囲った動作を繰り返します。
2) T2CONbits.TMR2ON = 0 にしてもLEDが消えない
すみません、これは私の説明が悪かったです。PWM制御はタイマー2という内部モジュールが発生する信号を元にPWM信号を生成しています(タイマー2モジュールが生成する信号をカウントしながらピンのON/OFF を制御している)。この
T2CONbits.TMR2ONは、この元となる信号を生成するかしないかの設定になります。
TMR2ON=0にした場合、PWM信号はその時の状態で止まります。例えばPWM信号がONの時(= LEDが点灯している時)にタイマー2信号をOFFにすると、そこでPWM制御動作は停止して、LEDはONのままになってしまいます。また、TMR2ON=0にしたタイミングがたまたまLEDがOFFの時はLEDは消灯した状態で停止します。
ではどのようにすればいいかというと、PWM制御をしている場合、ON(LEDの100%点灯)、OFF(LED消灯)も含めてデューティー比の設定で制御します。ONにしたい場合はデューティー比100%、OFFにしたい場合はデューティー比0%にします。つまり、LEDを消灯したい場合、CCPR1L=0、CCP1CONbits.DC1B=
0にします。
TMR2ONの使い方がわかりづらいので、これを踏まえたプログラムは以下に示します。(インデントは無視されてしまいますので読みづらくなってしまいすみません…)
CCP1CONbits.CCP1M = 0b1100; // PWM有効 Acitve High
T2CONbits.T2CKPS = 0b10; // プリスケーラ 1:16
int i = 0;
int pr2[5] = {64,77,97,129,194}; //計算結果を配列に
int ccpr1L[5] = {33,39,49,65,99}; //計算結果を配列に
int ccp1con[5] = {13,156,192,260,391}; //計算結果を配列に
T2CONbits.TMR2ON = 1; //PWM制御生成開始
while(1){
for(i = 0; i < 5; i++){
PR2 = pr2[i];
//5秒間点灯
CCPR1L = ccpr1L[i];
CCP1CONbits.DC1B = ccp1con[i];
__delay_ms(5000);
//1秒間消灯
CCPR1L = 0;
CCP1CONbits.DC1B = 0;
__delay_ms(1000);
}
}
ということで、説明がかなり複雑になってしまいました。言葉だけでお伝えするのはなかなか難しいので、理解しづらいところがありましたらお手数ですがご質問いただければと思います。
アドバイスありがとうございます。うまく動きました。
1つ教えてください。どうも、教えてもらった式ではうまくいったのですが、実は自分で作った計算式ではうまく動きませんでした。
もしかして、なにか基本的なことでしょうか?
PR2 = 1000000 / ( freq * 4 * 4 * 16 ) – 1;
を
PR2 = ((1/freq)*1000000)/256-1;
早速のご確認どうもありがとうございました。
ここまでくるとC言語の細かい話になってきましたね…
すみません、XC8コンパイラが生成した機械語の確認まではしていないのですが、おそらくこうだろう、ということでご回答いたします。
PR2 = ((1/freq)*1000000)/256 – 1;
という式ですが、おそらく最初に (1/freq)が計算されるのではないかと思います。この場合、特に指定はしていませんので、整数として扱われ、例えばfreqが60の時は、
1/freq = 1 ÷ 60 = 0
(※整数扱いのため少数は切り落とされる)
で計算されると思います。結果として、PR2 = -1 = 0xffになっているのではないかと思います。
少数が出る場合は(1/freq)は少数として扱ってもらうために、 (float)のキャストをつけて
PR2 = ( (float)(1/freq) * 1000000 ) / 256 – 1;
とすればよさそうですが、、、こちらでPIC12F1822で試そうとしたところビルドエラーになってしまいました。小数点計算はメモリを多く使用するので、無償モードでは対応していないというようなメッセージが表示されました。
このような計算は、途中に小数点が出ないような式にするとよいと思います。とはいってもこの辺りの話はC言語コンパイラの仕組みの領域に入ってくるので、式の途中の結果がなるべく小さい少数にならないように計算式を作るとよいと思います。
y = ( 1/x + 10 ) * 30;
とするのではなく、
y = 30/x + 300;
とするなど、途中に 1/x などの小さい少数の結果が出ないようにすればよいのではないかと思います。(すみません、この辺りまでくると私も自信がないです…)
なるほど、わかりました。私もすぐに、はまる性質なのですが、キャスト?やfloatの使い方大変気になるところですが、ここは深追いしないで、次のADコンバータの実験をやってみます。
丁寧にありがとうございます。
気になるところがあると深追いしたくなりますよね。この先も深追いしたくなる場面がいろいろ出てくると思いますが、まずは全体を把握されるとよいと思います。また何か不明点ありましたらご質問いただければと思います。