今回はPICマイコンのPWM機能を使ってLEDの明るさを調整します。
PWM機能?
前回はLEDをPWM制御して明るさを調整しました。PWM制御ってプログラムで簡単に実現できることがわかりましたよね。
ところで、PIC12F1822には「PWM制御」機能が搭載されています。。。
って言われても、前回、プログラムを作ってPWM制御した身からすると、何をいってるんだろう、って感じですよね。
実は、PICマイコンの「PWM制御」は、ハードウエアで実現するPWM制御のことなんです。
プログラムで簡単にPWM制御できるのに、わざわざマイコン側でハードウエア機能として用意するのって、意味がよくわからないですよね。
そこで、PWM制御についてもう少し考えてみます。
前回作成したPWM制御プログラムの動作は、LEDを点灯して、ちょっと待って、LEDを消灯して、ちょっと待って、、、という処理を繰り返すことによりPWM制御を実現しました。このような方法でソフトウエアによりPWM制御する場合、状況によっては面倒なことが発生します。
例えば、前回の記事のプログラムで、PWM制御中に何か他の処理をしたい時はどうすればいいでしょうか。PWM制御中に何か他の処理をする、ということは、以下のプログラムのwhileループ内のどこかに処理を追加することになります。
// LED PWM制御
while(1){
// LEDを0.5ms点灯
LATA5 = 1;
__delay_us(500);
// LEDを4.5ms消灯
LATA5 = 0;
__delay_us(4500);
}
このwhileループ内に、例えば処理に数msかかるようなプログラムを追加したい場合、どこに入れても周期やデューティーサイクルが変わってしまいますよね。変えないようにするには、追加する処理の時間分、__delay_us()関数の待ち時間を変えれば良さそうですが、追加したプログラムが状況に応じて処理時間が変わる場合は解決が難しそうです。
この問題をソフトウエアで解決するのは無理がありますので、PWM制御をハードウエアで実現してしまう方がよさそうです。ハードウエアでPWM制御を行うと、ソフトウエアではPWMのパラメータ(周期やデューティーサイクルなど)を設定するだけで、あとはハードウエアが勝手にPWM信号を生成してくれます。このハードウエアで実現するPWM制御が、PIC12F1822によるPWM制御になります。
今回の記事では、PIC12F1822のPWM制御機能を使って、LEDの明るさを変えてみます。
なお、このようにPWM制御をハードウエアで実現する意味は大きいため、PICマイコンに限らず、他のマイコンやRaspberry PiなどのワンボードPCなどでもハードウエアPWM制御機能が実装されています。
PICマイコンのPWM機能の概要
ところで、PICマイコンのPWM制御機能を使うのは、かなり大変です。理解してしまえば大したことはないのですが、初めて使うときは、嫌がらせではないか、と思われるほどいろいろな用語や設定が出てきます。
そこで、詳しい説明をする前に、PICマイコンのPWM制御手順の概要を説明し、その手順と対応づける形で完成したプログラムを見てみます。制御手順の概要と完成したプログラムを確認した後、各項目を細かく説明し、補足が必要な場合は寄り道して補足する、という方法で説明を進めたいと思います。多分長い説明になると思いますので、自分がどこにいるのか見失わないように読み進めてください。
まずこのセクションでは、PICマイコンのPWM制御手順の概要を説明します。
ハードウエアによるPWM制御は、PICマイコンのどのピンでも使える、というわけではありません。PWMをハードウエアで実現するためにはPWM制御モジュールを用意する必要がありますが、全てのピンにPWM制御モジュールを実装するのはコストがかかります。
そのため、ハードウエアによるPWM制御は、PICマイコンごとにPWM制御モジュール数と割り当てピンに制限があります。
例えばPIC12F1822では、PWM制御モジュールの数は1個、また、割り当て可能ピンはRA2ピンかRA5ピンのどちらかになります。例えばRA0ピンとRA1ピンの両方を独立したPWM制御ピンとして使う、ということはできません。
PWM機能の割り当てピンは、特定のレジスタを設定することにより行います。似たような例では、基礎編でクロック周波数を1MHzに設定しましたが、その設定はOSCCONレジスタを使って「OSCCON = 0b01011010;」と書きました。このような感じで、特定のレジスタに設定値を代入することによりPWM機能をRA2ピンに割り当てるか、RA5ピンに割り当てるかの設定を行います。
PWM機能のピン割り当てを行なったら、次に、PWM信号の周期とデューティサイクルを指定します。この指定は、クロック周波数を元にして、「周期はクロック何個分」「デューティサイクルはクロック何個分」という形で行います。
これで、PWM機能のピン割り当てとPWM信号のパラメータを設定しましたので、あとはPWM制御をスタートさせると、PWM機能を割り当てたピンにPWM信号が出力されます。なお、PWM制御スタート後でも、周期とデューティサイクルは変更可能です。
いったんまとめておきましょう。
PICマイコンでPWM機能を使用するには、
- 最初にPWM機能を割り当てるピンを指定して
- 周期とデューティサイクルの長さを設定して
- PWM制御をスタートさせる
という手順で進めます。なんだこれだけの設定でPWM信号が生成できるのか、と思ってしまいそうですが、おそらく解説を読み進めるとげんなりしてきます。げんなりしたら休憩を取るなど対策をしてみてください。
それでは次に、この手順に対応づける形で、完成したプログラムを確認します。
PWM制御プログラム
PWM制御プログラムがどのようになるか確認しておきましょう。
以下は、PWM機能をRA5ピンに割り当て、クロック周波数が1MHz、周期が1ms、デューティーサイクルが0.5msの場合のPWM制御部分のプログラムです。先ほどの(1)〜(3)の手順に対応する形でコメントを入れてあります。
// (1) PWM機能のピン割り当て設定
APFCONbits.CCP1SEL = 1; // PWM機能をRA5ピンに設定
CCP1CONbits.CCP1M = 0b1100; // PWM機能を有効にして、PWM信号をactive-highに設定
CCP1CONbits.P1M = 0b00; // RA2ピンはGPIOに設定
// (2) 周期とデューティーサイクルの設定
T2CONbits.T2CKPS = 0b00; // プリスケーラを1:1に設定
PR2 = 249; // 周期を1msに設定 (249 + 1) x 4 x 1us = 1000us = 1ms
CCPR1L = 500/4; // デューティーサイクルを0.5msに設定
CCP1CONbits.DC1B = 500;
// (3) PWM制御スタート
T2CONbits.TMR2ON = 1;
このプログラムを実行すると、ハードウエアのPWM制御モジュールがPWM信号の発生を始めます。このあとは、プログラムで何をしても、その処理とは関係なくPWM信号を発生し続けます。
手順としては理解できるけど、実際のプログラムはさっぱり、、、って感じですよね。今回の記事は、このプログラムを理解して、他のPICマイコンでも応用できるようにするのがゴールです。
それではこれから、(1)〜(3)まで、各項目の設定方法を詳しく説明します。途中で補足知識も説明しますので、どの部分の説明を読んでいるか常に確認しながら読み進めてください。本当に複雑ですので! げんなりしたら休憩を取ることをお忘れなく!!
(1) PWM機能のピン割り当て指定
ハードウエアのPWM制御モジュールは数が限られていて、割り当てることのできるピンも限られていることは先ほど説明しました。
まず最初にデータシートから、PWM制御モジュール数と割り当て可能なピンを読み取って見ましょう。
ここではPIC12F1822のデータシートを確認しますが、他のPICマイコンのデータシートの読み方も同じです。今後のためにも実際にデータシートを確認しながら進めてみてください。
なお、データシートを確認する前に注意点があります。これからPWM機能を設定しますが、データシートには「PWM」という言葉はあまり登場しません。この点がPWM機能設定の理解を難しくしています。ただ、他のPICマイコンでPWM制御する場合でも避けられませんので、一通り説明します。
まず最初にPWM制御モジュール数の確認です。
(Microchip Technology社「PIC12F1822データシート」より引用)
この表は、PIC12F1822データシートのp.2にある機能リストです。この表には、GPIOピンは何ピンあるのか、メモリはどのぐらいあるのか、などの情報がまとめられています。PWM機能はピンク色の枠で囲んでいる「CCP」または「ECCP」の欄になります。
CCPは「Capture機能/Compare機能/PWM機能」、ECCPは「Enhanced CCP」を意味しています。このように、PWM機能は、Capture機能、Compare機能とセットになっています。つまりPWM機能は、「CCP」または「ECCP」の一つの機能として扱われています。Capture機能、Compare機能についてはこの応用編では説明しませんが、これら3つは同じハードウエアで実現する機能のためにセットになっている、という程度で理解いただければ問題ありません。
上の図のピンク色枠の記述を見ると、「ECCP(Full-bridge)」「ECCP(Half-bridge)」「CCP」がそれぞれいくつあるか、という表現になっています。例えば、このシリーズて使用しているPIC12F1822は一番上の行で、「0/1/0」と書かれています。これは、「ECCP(Full-bridge)」が0、「ECCP(Half-bridge)」が1個、「CCP」が0という意味です。
ということで、PWMのことを知りたいのに、抱き合わせ商法のような感じで、Capture機能、Compare機能が出てきて、さらにCCP、ECCPが出てきた上に、さらにさらにECCPは2種類、ECCP(Half-bridge)とECCP(Full-bridge)、というように色々な種類が出てきてしまいました。
さっそく道に迷いそうですが、PWM機能を使う場合、PWMを意味する「P」は「ECCP(Full-bridge)」「ECCP(Half-bridge)」「CCP」いずれにもありますので、どれかが1以上であればPWMは使えることになります。PIC12F1822は「ECCP(Half-bridge)」が1個、ということは、PWM制御モジュールが1個ある、と理解してOKです。
これで、PIC12F1822のPWM制御モジュール数が1個であることがわかりました。
次に、PWM機能がどのピンに割り当て可能か、です。この仕様はデータシートの次のペーシに書かれています。
以下の表はPIC12F1822データシートのp.3に書かれているものです。
(Microchip Technology社「PIC12F1822データシート」より引用)
一番左の列がピン名称の列です。ピンク色の枠で囲んだ部分がECCP機能の割り当て列になります。次にこの表を読み解く必要があります。
結論としては「CCP1」と書かれているピンがPWM機能割り当て可能ピンになります。「CCP1」と書かれているピンは、RA2ピンとRA5ピンであることがわかりますので、PWM機能割り当て可能ピンは、RA2ピンとRA5ピン、ということがわかります。
それでは、この表の読み方を説明します。
ECCP欄には、「CCP1」「P1A」「P1B」という文字列があります(「FLT0」はCCPの付随機能のため説明は省略します)。
まず「CCP1」ですが、これは「Capture機能/Compare機能/PWM機能」の意味で、数字は、PWM制御モジュールが複数ある場合には「CCP2」、「CCP3」と数字が増えていきます。PIC12F1822はPWM制御モジュールが1個ですので、CCP1しかありません。この表では「CCP1」はRA2とRA5のところにあるので、PWM機能はRA2かRA5に割り当て可能、ということがわかります。
次に「P1A」と「P1B」ですが、これは特定用途に使用されるPWM機能名です。例えばモーターを制御する場合、正転/逆転制御したいケースがあります。この場合、以下のような回路で、モーターを制御します。
(Microchip Technology社「PIC12F1822データシート」より引用)
この回路に深入りするとちょっと大変ですので、この回路のポイントのみを説明します。モーターを正転/反転制御する場合は、2つのPWM制御ピンが必要です(4つ必要な場合もあります)。この回路では、PICマイコン側のP1A、P1Bという2つのPWM機能ピンでモーターを制御します。
この回路において、P1AでPWM制御する場合とP1BでPWM制御する場合とでは、モーターの回転は逆になります。また、P1AとP1Bは同時に制御してはいけません(同時にONにすると回路がショートします)。
つまり、PWM制御モジュールは1個しか使わないけど、モーター制御のように、状況に応じて2つのピンのどちらかをPWM機能として使用したいケースがあります。
このようなケースのために、PWM制御をP1A、P1Bの2ピンで使用できるモードも用意されています。あくまでもハードウエアのPWM制御モジュールは1個で、それを2ピンのどちらかで排他的に使用する、ということです。P1A、P1Bの2ピンあるから、2つのPWM制御モジュールがある、というわけではありません。
なお、上の回路は「ハーフブリッジ(Half-bridge)回路」と呼ばれていて、PWM制御ピンは2ピン必要になります。他に「フルブリッジ(Full-bridge)回路」というものもあり、こちらはPWM制御ピンは4ピン必要になります(PIC12F1822のデータシートp.203に回路図がありますので、ご興味があれば確認してみてください。
なおこの後のPWM設定のレジスタで、これらの用語が出てきますが、「CCP1」と「P1A」は同じ意味で解釈して問題ありません。通常のPWM制御として使用する場合は「CCP1」ですが、上のようにHalf-bridge回路で使用する場合は2ピン必要で、そのときの名称は、「CCP1」を「P1A」と呼び、もう1つのピンを「P1B」と呼んでいます。このように「CCP1」と「P1A」は同じ意味になります。
それでは、これからPWM機能ピンのプログラムでの指定方法を説明します。
PWM機能ピンを指定するには、使用するピンを出力ピンにしておく必要があります。すでに作成している回路では、RA5ピンにLEDを接続して、このピンを出力に設定してありますので(TRISAレジスタ設定)、特にプログラムの変更は必要ありません。
さらに、PWM機能のピンとして使用するには、以下の設定が必要になります。
- APFCONレジスタのCCP1SELビットでPWM機能を割り当てるピンを指定
- CCP1CONレジスタで、CCP1のPWM機能を有効にしてPWM信号タイプを設定
それでは最初に「APFCON」レジスタです。以下はPIC12F1822データシートのAPFCONレジスタのページの抜粋です。
(Microchip Technology社「PIC12F1822データシート」p.114より引用・加工)
このページの説明にあるように、APFCONレジスタのCCP1SELビットを0にするとRA2ピンが、1にするとRA5ピンがCCP1/P1Aピンになる、と書かれています。
つまり先ほど説明したように、CCP1 = P1A = PWM制御ですので、このビットでRA2かRA5どちらをPWM制御ピンとして使用するのか指定することになります。
今回はRA5ピンをPWM制御ピンにしますので、このCCP1SELビットを1に設定します。APFCONレジスタは8ビットで、その一番下のCCP1SELビットのみを1に変更したい場合、
APFCON = 0bなんらかの値;
とか書いてしまうと、CCP1SELビット以外のビットを書き換えてしまう可能性があります。そこで、レジスタの特定ビットを設定する方法がありますので、その方法を説明します。
この方法はAPFCONレジスタだけではなく、他のすべてのレジスタにも有効な方法です。今後も頻出しますので、是非覚えておきましょう。
レジスタの特定ビットを指定したい場合、レジスタ名に「bits.」をつけます(bitsの最後のピリオドを忘れないようにしてください)。例えば「APFCON」レジスタであれば、「APFCONbits.」です。
実際にMPLABXのエディタ上で基礎編で作成したmain.cを開いて、適当な場所で「APFCONbits.」と入力してみてください。最後のピリオドを入力すると、以下のようにそのあとの指定可能なビットが表示されたと思います。
今回指定するのは、APFCONのCCP1SELビットですので、一番上の「CCP1SEL」を選択した状態でリターンキーを押すと「APFCONbits.CCP1SEL」となります。これは、APFCONレジスタのCCP1SELビット、という意味になります(APFCONレジスタはAPFCONbitsという構造体が定義されていて、この構造体の中に各ビットが定義されています)。
基礎編のmain.cは変更しませんので、保存せずに閉じてください。
ということで、RA5ピンをPWM制御ピンとして使用するために、まず、以下のように設定します。
APFCONbits.CCP1SEL = 1;
次に、PWM機能のタイプを「CCP1CON」レジスタ(CCP1 Control)で設定します。
以下は、データシートのCCP1CONレジスタのページ(p.213)です。
(Microchip Technology社「PIC12F1822データシート」p.213より引用・加工)
CCP1CONレジスタは、第7ビット〜第6ビットの「P1M」(図中青色枠)、第5ビット〜第4ビットの「DC1B」、第3ビット〜第0ビットの「CCP1M」(図中ピンク色枠)から構成されています。PWM設定に関わるのは「P1M」と「CCP1M」です。「DC1B」はデューティーサイクル設定で使用しますので、この後のセクションで説明します。
PWMタイプは、ピンク色枠の第3ビット〜第0ビット「CCP1M」で設定します。このビットのデフォルトは0b0000で、この値にすると、Capture/Compare/PWM機能はすべてOFFになります。
PWMとして使用するには、0b1100〜0b1111のいずれかを設定します。設定値の意味を見ると、例えば0b1100であれば、「PWM mode: P1A, P1C active-high; P1B, P1D active-high」と書かれています。これは、「PWMモード、P1A、P1B、P1C、P1DピンはすべてアクティブハイのPWM制御」という意味になります。PIC12F1822はP1AとP1Bしかありませんので、P1C、P1Dは無視してOKです。
設定内容を一通り確認すると、「active-high」「active-low」の設定があることがわかります。これは、PWM制御でONの期間、「active-high」であれば5Vに、「active-low」であれば0Vにする、という意味です。
なんだかややこしいですが、電子回路によってはactive-low制御が必要な場合もあります。今回のLEDの制御は、ONの時に5VにしてLEDを点灯させますので、「active-high」設定になります。
P1Aをactive-highにするには、0b1100か0b1101のどちらでもOKです。0b1100を設定することにします。
CCP1CONのCCP1Mビットを0b1100に設定しますので、先ほどと同じように、「CCP1CONbits.」と入力します。最後のピリオドを入力すると、設定可能なビットが表示されますので、「CCP1M」を選択してリターンを押します。「CCP1CONbits.CCP1M」と表示されますので、その後に以下のように設定値を書きます。
CCP1CONbits.CCP1M = 0b1100; // PWM機能を有効にして、PWM信号をactive-highに設定
これで、CCP1CONレジスタのCCP1M設定ができました。
次に、CCP1CONレジスタのP1Mビットを設定します。P1Mビットは以下のような設定内容になっています。
- CCP1Mの上位2ビットが00、01、10のとき(=Capture/Compareモードのとき)
使用しない - CCP1Mの上位2ビットが11のとき(=PWMモードのとき)
00: P1AがPWM信号出力。P1B、P1C、P1DはGPIO
01: Full-Bridge
10: Half-Bridge
11: Full-Bridge(反転)
ということで、CCP1CONレジスタのP1Mビットは、赤色文字の設定にすればいいことがわかります。赤色文字は、P1AがPWM信号出力、P1BがGPIO、という設定ですが、「APFCONbits.CCP1SEL」の設定でRA5ピンをPWM機能のピンに設定しましたので、RA5がPWM = CCP1 = P1A、RA2がP1Bになりますので、結局この設定の意味は、「RA5ピンがPWM機能、RA2ピンがGPIO機能」ということになります。
ということで、RA5ピンをPWM(half-bridge)、RA2ピンをGPIOにするために、
CCP1CONbits.P1M = 0b00; // RA2ピンはGPIOに設定
というプログラムになります。
これで、PWM制御モジュールの設定が終わりました。おそらく休憩が必要だと思いますので、コーヒーでも飲んで一息入れましょう。次は周期とデューティーサイクルの設定です。
(2) 周期とデューティーサイクルの設定
次に周期とデューティーサイクルの設定です。これらの設定はPWM信号発生中でも変更することができますので、LEDの明るさやモーターの回転速度を可変制御できます。
この記事の最初の方で、周期とデューティーサイクルの設定は「クロック何個分」という設定をする、と説明しましたが、実は素直に何個分、と設定できるわけではありません。今回のプログラムは1MHzで動作させますので、1クロックは1μsです。周期は1msにしたいので、クロックは1000個分になりますが、素直に「1000」という指定ではありません。
周期とデューティサイクルの設定も、コーヒーでも飲みながらゆっくり理解を進めることをおすすめします。
周期の設定は、「PR2」レジスタで行います。「PR2」レジスタは8ビット、つまり0〜255までの設定になります。このPR2の値を元に、周期は以下の式で決定されます。
周期 = ( PR2 + 1 ) x 4 x 1クロック分の時間 x プリスケーラ値
今回のプログラムは基礎編のmain.cを流用します。基礎編ではクロック周波数は1MHzに設定しました。つまり、1秒間に1,000,000個のクロックを発生しています。1秒間に1,000,000個、ということは1クロック分の時間は
1s ÷ 1000000 = 0.000001s = 1μs
です。つまり、クロック周波数を1MHzにした場合、上の計算式は
周期 = ( PR2 + 1 ) x 4 x 1μs x プリスケーラ値
となります。あとは「プリスケーラ値」が何か?ということがわかれば周期が決まります。
最初に、上の計算式で「プリスケーラ値」がない場合を考えてみます。
周期 = ( PR2 + 1 ) x 4 x 1μs
この計算式で、PR2は0〜255までの範囲になりますので、周期として設定できる値は、
PR2=0のとき:
周期 = ( 0 + 1 ) x 4 x 1μs = 4μs
PR2=255のとき:
周期 = ( 255 + 1 ) x 4 x 1μs = 1024μs
ということで、プリスケーラ値がない場合は、周期として設定できる値は、4μs〜1024μs(=約1ms)です。前回、ソフトウエアでPWM制御した場合は、周期を5msにしました。ということは、上の式では周期を5msにすることができません。
このように、1クロック分の時間に比べてある程度長い周期を設定したい場合のために「プリスケーラ値」があります。このプリスケーラ値はデフォルトでは1ですが、他に、4, 16, 64が選択できます。
例えば、周期を5msにしたい場合、プリスケーラ値を16、PR2を77にすれば、
周期 = ( 77 + 1 ) x 4 x 1μs x 16 = 4992μs = 約5ms
ということで、5msに近い値にすることができます。この場合は、PR2を77、プリスケーラ値は16に設定します。
ということで、周期を設定する場合、PR2の他にプリスケーラ値の設定も必要になります。
PR2は単に8ビットのレジスタですので、0〜255の値を代入すればOKです。
プリスケーラ値は「T2CON」レジスタのT2CKPSビットで設定します。以下はPIC12F1822データシートのT2CONレジスタのページです。
(Microchip Technology社「PIC12F1822データシート」p.178より引用・加工)
T2CONレジスタのT2CKPSビットの設定値は以下の意味になります。
T2CON/T2CKPS | プリスケーラ値 |
---|---|
00 | 1 |
01 | 4 |
10 | 16 |
11 | 64 |
今回は、周期は1msにします。最初に計算したように、プリスケーラが1の場合、設定できる範囲は4μs〜1024μsで1msの設定ができますので、プリスケーラ値は1に設定します。
T2CONbits.T2CKPS = 0b00; // プリスケーラを1:1に設定
ということになります。
続いて、PR2ですが、
周期 = 1ms = 1000μs = ( PR2 + 1 ) x 4 x 1μs
となるようなPR2を求めると(1次方程式を解けばOKですね)、PR2は249になります。
ということで、周期の設定は、
PR2 = 249; // 周期を1msに設定 (249 + 1) x 4 x 1us = 1000us = 1ms
ということになります。これで周期の設定が終わりました。
次はデューティーサイクルです。デューティーサイクルは10ビットで設定します。デューティーサイクルは以下の式になります。
デューティーサイクル = (10ビットの値) x 1クロック分の時間 x プリスケーラ値
この式で、1クロック分の時間は1μs、プリスケーラ値は1にしましたので、
デューティーサイクル = (10ビットの値) x 1μs
となります。あとは10ビットの値をどこに設定すればよいか、ですが、通常のレジスタは8ビットです。そこで、この10ビットの値を上位8ビットと下位2ビットに分けて、それぞれ別のレジスタに設定します。
代入するレジスタは以下になります。
値 | 設定レジスタ |
---|---|
上位8ビット | CCPR1L |
下位2ビット | CCP1CONレジスタのDC1Bビット |
デューティー比は50%にしますので、デューティーサイクルは0.5ms、つまり500μsです。
上の式でデューティーサイクルを500μsにするには、
デューティーサイクル = (10ビットの値) x 1μs = 500 x 1μs
ということで、10ビットの値は500になります。ということで、500の上位8ビットをCCPR1Lに、下位2ビットをCCP1CONレジスタのDC1Bビット設定することになります。500は2進数ビットでは0b0111110100ですので、以下のようにレジスタに設定することになります。
さて、500をわざわざ2進数に変換して、その上位8ビットや下位2ビットを求めるのは面倒ですので、楽な方法でプログラムを書くことにしましょう。
まず、上位8ビットの求め方です。2進数表現では、2で割ると1桁右に移動します。一番右の桁は無くなります。
さらに2で割ればもう1桁右に移動します。ということは、500を4で割れば上位8ビットを取り出すことができます。
ということで、CCPR1Lに上位8ビットを設定するには、
CCPR1L = 500/4;
と書けばよいことになります。
次に、CCP1CONレジスタのDC1Bビットに下位2ビットを設定します。つまり、CCP1CONbits.DC1Bに500の下位2ビットを代入すればOKです。
これはビルド時に警告が出ますが、単純に
CCP1CONbits.DC1B = 500;
と書いてしまいましょう。左辺は2ビットですので、右辺が3ビット以上の数字でも、下位2ビットだけが使用されますので、手抜きでこのように書きました。なお、ビルド時の警告が気持ち悪いようでしたら、以下のように下位2ビット以外をマスクすればOKです。
CCP1CONbits.DC1B = 500 & 0b11;
これで周期とデューティーサイクルの設定は終わりました。
(3) PWM制御スタート
これで、PWMの設定ができましたので、あとはPWMをスタートさせるだけです。今まで、周期を設定するだけでもかなり大変だったので、PWMをスタートさせるのも大変では、と余計な心配をしてしまいますが、今回は簡単です。
PWMをスタートさせるには、T2CONレジスタのTMR2ONを1にするだけです。つまり、
T2CONbits.TMR2ON = 1;
とすればOKです。これでPWM信号が生成され始めます。
実際のプログラム
それでは、今までの内容をまとめて、周期1ms、デューティー比50%でLEDをPWM制御してみましょう。
前回は、「PWMControl」というプロジェクト名で新規プロジェクトを作成しましたので、今回は「PWMFunction」というプロジェクトで新規プロジェクトを作成しましょう。
作成したら、以下のプログラムをコピペしてビルド、書き込み、実行しましょう。
/*
* File: main.c
* Author: Tool Labs
*/
#include <xc.h>
// PIC12F1822 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF // PLL Enable (4x PLL disabled)
#pragma config STVREN = OFF // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset)
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)
// クロック周波数指定
// __delay_ms(), __delay_us()関数が使用する
#define _XTAL_FREQ 1000000
void main(void) {
// PICマイコン設定
OSCCON = 0b01011010; // 内部クロック周波数を1MHzに設定
ANSELA = 0b00000000; // すべてのピンをデジタルモードに設定
TRISA = 0b00001000; // すべてのピンを出力モードに設定(ただしRA3ピンは常に入力モード)
// (1) PWM機能のピン割り当て設定
APFCONbits.CCP1SEL = 1; // PWM機能をRA5ピンに設定
CCP1CONbits.CCP1M = 0b1100; // PWM機能を有効、active-highに設定
CCP1CONbits.P1M = 0b00; // RA2ピンはGPIOに設定
// (2) 周期とデューティーサイクルの設定
T2CONbits.T2CKPS = 0b00; // プリスケーラを1:1に設定
PR2 = 249; // 周期を1msに設定 (249 + 1) x 4 x 1us = 1000us = 1ms
CCPR1L = 500/4; // デューティーサイクルを0.5msに設定
CCP1CONbits.DC1B = 500;
// (3) PWM制御スタート
T2CONbits.TMR2ON = 1;
// 何もしない
while(1);
// 以下の命令は実行されない
return;
}
PWM制御のまとめ
PWM制御の設定はかなり複雑ですので、何を設定すればよいかまとめておきます。なお、クロック周波数の設定など、基本的な設定は省略しています。
設定内容 | 設定レジスタ |
---|---|
使用するピンの出力設定 | TRISA |
PWM機能のピン割り当て | APFCONbits.CCP1SEL(ピンの割り当て) CCP1CONbits.CCP1M(PWM有効化とactive-high/lowの指定) CCP1CONbits.P1M(残りのピンの設定) |
周期とデューティーサイクル設定 | T2CONbits.T2CKPS(プリスケーラ) PR2(周期) CCPR1LとCCP1CONbits.DC1B(デューティーサイクル) |
PWM制御開始/停止 | T2CONbits.TMR2ON |
PWM信号の確認
前回は、PWM制御のプログラムを自分で作りましたが、今回はPWM制御モジュールのパラメータを設定して、あとはPWM信号生成をPICマイコンにお任せした、という状態ですので、本当に周期が1ms、デューティーサイクルが0.5msかよくわかりませんよね。LEDの明るさを見ても果たしてデューティー比が50%なのか、自信がないでよすね。
そこで、基礎編で作成したブレッドボードと上のプログラムを使用して、RA5ピンの波形を確認しました。以下の図はRA5ピンの電圧をオシロスコープ(電圧の波形の計測器)で観測した結果です。
横軸が時間で1目盛1ms、縦軸が電圧で1目盛2Vです。
1ms周期の約5Vの信号が生成されているのがわかると思います。デューティー比は50%っぽいです。このオシロスコープは自動的に時間幅を測ってくれる機能があるので測ってみました。
周期(Period)は「1ms / 1.000kHz」とありますので指定通りです。またデューティサイクル(Width)も「500us」とありますのでこちらも指定通りです。
PWM制御は、1) PWM機能のピンを割り当てて、2) 周期とデューティーサイクルを設定して、3) PWM制御スタート、という手順でOKですが、かなり大変ですよね。ただ、他のPICマイコンも同様の指定方法ですので、PIC12F1822がマスターできれば他のPICマイコンでも問題なく使えるようになると思います。
また、他のマイコンも同じような感じの設定になりますので、是非PWM制御に慣れてみてください。ロボットアームに使用されるサーボモーターはPWMで制御しますので、色々応用がきくと思いますよ。
更新履歴
日付 | 内容 |
---|---|
日付 | 内容 |
2017.1.4 | 新規投稿 |
2018.11.30 | ビット演算子の誤記訂正(&&→&) プログラムテンプレートをMPLABX IDE v5.10に更新 |
お世話になってます。
回答いただきましてありがとうございます。
再度ご質問ですが、 0b0111110100 & 0b11
01 1111 0100
00 0000 0011
——————
00 0000 0000
となるのは理解できましたが、0b0111110100 & 0b00
であっても成立するのでしょうか。
01 1111 0100
00 0000 0000
——————
00 0000 0000
以上、よろしくお願いいたします。
ご質問どうもありがとうございます。
ポイントは
「0b11で論理積を取るのは、下位2ビットを値を取り出す」
ということになります。
0x00で論理積を取ると、その意味は「どんな値でも結果が0になる」という演算になりますのので意味がない、ということになります。
もし、ビット演算について詳しくないようでしたら、以下のサイトによくまとまっていますのでご確認いただければと思います。
https://www.binzume.net/library/robo/ca3.html
よくわかりました。
ありがとうございます。
お世話になってます。
CCP1CONbits.DC18 = 500 & 0b11;について教えてください。
CCP1CONレジスタのDC1Bに500(0b0111110100)の
下位2ビットを代入するのですが、
①CCP1CONbits.DC18 = 0;で良いのでしょうか?
②CCP1CONbits.DC18 = 500 & 0b11; は、
0b0111110100 & 0b00 → All ゼロ という意味でしょうか。
500 & 0b11 この部分がどうなるのかについて、教えてください。
以上、よろしくお願いいたします。
ご質問どうもありがとうございます。
①CCP1CONbits.DC1B = 0;で良いのでしょうか?
はい、500の場合、下位2ビットは00ですので、500を代入しても0を代入しても同じ結果になります。
②CCP1CONbits.DC1B = 500 & 0b11;
この式の右辺を2進数で書くと 0b0111110100 & 0b11 になります。
論理積を取ることになりますので、0b0111110100 & 0b11 = 0 になります。
記事のプログラムでは500を代入しましたので分かりづらいですが、
例えば10進数で525を代入する場合を考えてみます。
10進数の525は2進数で表すと 0b1000001101になります。下位2ビットが0b01になることに注意してください。
この時、
CCP1CONbits.DC1B = 525;
と書くと、DC1Bの2ビットには0b01が代入されます。
ただ、この書き方ですと、
CCP1CONbits.DC1B = 0b1000001101;
という意味になり、2ビットのレジスタに10ビットを代入することになってしまいます。
そこで、下位2ビットのみを取り出すために、
CCP1CONbits.DC1B = 525 & 0b11;
と書けば、525の下位2ビットの0b01のみが代入されます。
0b1000001101 & 0b11 = 0b01
となるためです。
ちょっとややこしいですので、不明点ございましたらお手数ですがコメントいただければと思います。
ずいぶん昔に買って挫折したPIC16F88をリベンジしている者です。
いつも大変参考になっております。
一つ質問がありまして、
スイッチの状態によってLEDの点灯パターンを3つの中から選べるようにするプログラムを書いているのですが、うまく動作しません(C言語の知識の問題なのかも)。
今、RA1とRA2にディップスイッチを、RA0(AN0)に可変抵抗を接続してあります。
出力はRB3にLEDを接続しています。
点灯パターンは
if(RA1 == 1 ){
LEDをだんだん明るく→だんだん暗く を繰り返す
}
if(RA1 == 0 ){
LEDの明るさを可変抵抗の値によって変える
}
if(RA1 == 0 && RA2 == 1){
LEDを133us点灯後、267us消灯
}
の3つなのですが、
if(RA1 == 0 && RA2 == 1)の条件式内の処理がどうしても行われません。
if(RA1 == 1)と if(RA1 == 0)の処理は問題なく動くのですが…
何か原因は分かりますでしょうか?
お力添え頂ければ幸いです。
以下プログラム全文です。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
#include <xc.h>
// CONFIG1
#pragma config FOSC = INTOSCIO // Oscillator Selection bits (INTRC oscillator; port I/O function on both RA6/OSC2/CLKO pin and RA7/OSC1/CLKI pin)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
#pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF // Low-Voltage Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF // Data EE Memory Code Protection bit (Code protection off)
#pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off)
#pragma config CCPMX = RB3 // CCP1 Pin Selection bit (CCP1 function on RB3)
#pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off)
// CONFIG2
#pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF // Internal External Switchover bit (Internal External Switchover mode disabled)
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#define _XTAL_FREQ 8000000
void PICinit() {
OSCCON = 0b01110000;
VCFG0 = 0; //A/D変換を行うポートの基準電圧をvdd-vssに設定
VCFG1 = 0;
ADCS0 = 1; //a/d変換のクロック選択
ADCS1 = 0;
ADCS2 = 1;
ADFM = 0; //a/d変換結果の格納方法 今回は左づめ8桁 下2桁は捨てる
ANSEL = 0b00000001; //AN0だけアナログに設定 あとはデジタル
TRISA = 0b00000111; //RA0(AN0),RA1,RA2を入力に設定
TRISB = 0b00000000;
PORTA = 0b00000000; //出力をリセット
PORTB = 0b00000000;
}
unsigned int adconvAX() { //電圧を10bitで取得する関数
ADCON0bits.CHS = 0; //AN0のみ測定
ADON = 1; //AD変換ON
__delay_us(20);
GO = 1; //AD変換開始
while(GO); //変換完了待ち
return (ADRESH);
}
void pwmLED(int duty,int time){ //LEDの明るさと持続時間を決める関数
int i=0;
int j=0;
for(j=1;j<time;j++){
for(i=1;i<100;i++){
if(i<duty){
RB3 = 1; //LEDをオン
}
else{
RB3 = 0; //LEDをオフ
}
__delay_us(100);
}
}
}
int main(void){
double ADvalue; //AD変換された値の格納用
double ADC;
PICinit(); //PICを初期化
while(1){
if(RA1 == 0 ){ //もしRA1スイッチがOFFなら
CCP1CON = 0b00000000; //pwmモードoff
for(int i = 1;i<100;i++){ //段々明るく
pwmLED(i,4);
}
for(int i = 100;i>1;i–){ //段々暗く
pwmLED(i,2);
}
}
if(RA1 == 1 ){ //もしRA1スイッチがonなら
CCP1CON = 0b00001100; // PWMモードon
T2CON = 0b00000111; // プリスケーラ16
PR2 = 255; // PWM周期
for(int i=0;i<50;i++){ //ADvalueを51回積算
ADvalue += adconvAX();
}
ADvalue = ADvalue/51; //51で割って平均
ADC = (ADvalue/255)*100; //0〜100までの値に変換
CCPR1L = ADC;
__delay_ms(200);
}
if(RA1 == 0 && RA2 == 1){ //もしRA1スイッチがOFFかつRA2スイッチがonなら
CCP1CON = 0b00000000; //pwmモードoff
RB3 = 1;
__delay_us(133);
RB3 = 0;
__delay_us(267);
}
return 0;
}
}
ご質問どうもありがとうございます。
いろいろ工夫されたプログラムを作成してますね。ピンが少ないマイコンでもプログラム次第でいろいろなことができるのは楽しいですよね。
ところでコメント欄にいただいたプログラムの場合、確かに最後の処理はされていないように見えてしまいます。言葉でうまく伝わるか自信ないですが、以下に原因と解決策を説明いたします。
まず、以下の現在のプログラムの動作を分析してみます。
if( RA1 == 1 )
明るい暗いの繰り返し;
if( RA1 == 0 )
可変抵抗による点灯;
if( RA1 == 0 && RA2 == 1 )
133us点灯/267us消灯;
この場合、RA1とRA2を使用して場合分けされますが、スイッチの組み合わせとしては全部で4通りあります。
RA1 RA2
———————-
(A) 0 0
(B) 0 1
(C) 1 0
(D) 1 1
最初の if( RA1 == 1 )では、(C)と(D)のケースになります。つまり「明るい暗いの繰り返し」はRA2の値によらず、RA1が1の時に実行されます。
次に、(A)と(B)のケースを考えてみます。
まず(A)の場合、RA1が0、RA2が0ですので、if( RA1 == 0 )の条件に合致して、可変抵抗による点灯を行います。続く if( RA1 == 0 && RA2 == 1 )では条件に合致しませんので、133us点灯/267us消灯は実行されません。
次に(B)の場合、RA1が0、RA2が1ですので、この時も if( RA1 == 0 )の条件に合致します。そのため、可変抵抗による点灯が実行されます。さらに次の if( RA1 == 0 && RA2 == 1 )も合致しますので、可変抵抗による点灯の後、続いて133us点灯/267us消灯が実行されます。
つまり、(B)の条件のとき、可変抵抗による点灯が200ms実行された後、133us点灯/267us消灯(=0.4msの処理)が実行されています。人間の目には200msの処理の方だけ認識できるので、133us点灯/267us消灯が実行されていないように見えています。
解決策ですが、RA1が0の時、RA2により処理が2通りありますので、RA2は両方のif文で判断する必要があります。
if( RA1 == 1 )
明るい暗いの繰り返し;
if( RA1 == 0 && RA2 == 0)
可変抵抗による点灯;
if( RA1 == 0 && RA2 == 1 )
133us点灯/267us消灯;
このようにすると、(C)と(D)のときは最初の処理のみ、(A)のときは2番目の処理のみ、(B)のときは3場面の処理のみにできると思います。
実機確認していませんが、おそらくこれが原因と思われます。
うまく動作しないようでしたらまたご質問いただければと思います。
早速のご返信ありがとうございます!
いろいろ試したところ無事動きました。
問題は2つありまして、一つはおっしゃる通りif文の条件が被っていたことでした。先に条件を満たした方を優先して処理するため、その後に条件を満たした処理が複数あっても無視されるようです。
もう一つは何をとち狂ったのか、ディップスイッチからPICへの入力に際しプルダウン抵抗をRA1とRA2で共有していたことでした。どちらかのスイッチをonにするとRA1,RA2両方に5Vが印加される状態になっていました。
やっぱり動くと嬉しいですね。どうもありがとうございました。
ちなみになんですが、MPLAB Xってレジスタの値をプログラム中に書くと赤い波線で警告されるんですね。コンパイルは普通に出来ますが、以前(MPLAB IDE)はこんなこと無かったように思います。この警告ってどうにか消せませんか?
無事動いてよかったです。
ところでMPLAB Xで赤い波線が表示される件ですが、実は他の記事のコメントでも質問をいただいているのですが、原因はわかっていません。
赤い波線は、ヘッダファイルなどで認識できなかった定数などに表示されますが、ビルドはできているので解決はできるはずなんですよね。ヘッダファイル(xc.h)を指定していればビルドは問題ありませんので、気持ち悪いですが解決されるまではこのまま利用するしかないかな、と諦めています。
解決策提示できずにすみません…
なるほど…アップデート待ちって感じですかね
ありがとうございます。
すいません、赤の波線について自己解決できました。
プロジェクトプロパティから、xc8 Global Options → xc8 compiler 内の Include directories右の…をクリックして、ウィンドウが開いたらBrows…をクリック、
macであれば
/Applications/microchip/xc8/v2.41/pic/include/proc
/Applications/microchip/xc8/v2.41/pic/include/c90 ←c99で開発していたらc99
を入れることで赤の波線が消えました。
ついでに使用可能なレジスタの文字が青くなって、commandキーを押しながらクリックでヘッダファイルに飛べるようになり、また存在しないレジスタの名前を書いたときだけ赤の波線が出るようになりました。
そちらの環境では如何でしょうか?
MPLAB Xの赤波線の件、情報どうもありがとうございました!
同じ設定をしたところ、赤線が消えました!
microchipのコミュニティ投稿などを確認してもわからなかったのですが、コメントいただいた設定をしたところ赤線が消えました。本当に感謝です。
それにしてもビルドできるんだったらヘッダファイルの場所をしてるはずなので、MPLAB X内で解決して欲しいですよね。
他の記事のコメント欄で質問があり、未解決でしたのでいただいた情報をそのコメント欄にも記載しておこうと思います。
情報提供くださり本当にどうもありがとうございました!
よかったです!
これからも色々と参考にさせて頂きます!
はじめまして。
非常に初心者に分かりやすく大変参考になっております。
質問なのですが
DC1B = 500 & 0b11; というのがイマイチ理解できません。
DC1Bは2bitなので500の10進数の下位2bitの00が入る事は理解できましたが
0b11;でマスクするとはどういう事でしょうか?
是非、ご教授ください。
ご質問どうもありがとうございます。
結論としては、DC1Bは2ビットのためです。
デューティーサイクルは10ビットで指定しますが、8ビットを超えていますので、上位8ビットをCCPR1Lレジスタ(8ビット)に設定して、下位2ビットはCCP1CONレジスタ(8ビット)の中のDC1B(2ビット)に設定します。
つまり、CCP1CON.DC1Bは2ビットとなります。ここで、
CCP1CON.DC1B = 500;
とした場合、左辺は2ビットで、右辺は0b1 1111 0100の9ビットになります。
この状態でビルドすると、「2ビットの変数に9ビットの数値を代入しようとしている」という警告メッセージが出ることがあります。
警告が出ても意図があってこのようにしていますので気にしなければいいだけですが、警告メッセージが気になる場合は、
CCP1CON.DC1B = 500 & 0b11;
というように、500(10進数)の下位2ビットだけを取り出して代入する、というように書けば警告が出なくなります。
というわけで、0b11でマスク(ANDをとる)のはそれほど意味はありませんが、正確にはマスクした方がより正しい書き方、ということになります。
すみません、まずはざっとご説明しましたが、わからないところがありましたら遠慮なくご質問いただければと思います。
C言語はこういうところが面倒ですが、逆に細かい制御ができるのもいいところなので一長一短ですね…
返信いただいているのに気付かず大変失礼いたしました。
ちなみに
CCPR1L = 0b01111101;
DC1B = 0b00;
と書いても同じ事と捉えてよろしいでしょうか?
こちらこそ返信が遅くなり申し訳ございません。
はい、
CCPR1L = 0b01111101;
DC1B = 0b00;
このように記述しても問題なく、ビルド時に警告も出なくなります。
ただ、このように記述しようとすると10進数の500を自分で2進数に変換してコードを書く必要があるため、記事中にあるようなコーディングをしています。
他にも不明点ありましたらご質問いただければと思います。
本当に初心者に分かりやすいサイトで助かりました。また分からない事があったら是非ともご教授ください。ありがとうございました。
記事が古いので、最新のPIC環境と合わないところもあると思います。何かわからないことがありましたらご質問いただければと思います。
初めまして。
pic 及びc言語の初心者ですが、このサイトを拝見して割り込みやpwm制御の方法がやっと理解することができ、大変助かっております。
現在、pic16f1827にてフルカラーledをpwm制御にて青、紫、紺・・等で点灯させ、各色の切り替えをsw(led切替用)による割り込み処理で行うことと、led点灯中にsw(電源on/off用)割り込みでスリープモードに移行と復旧(電源のon/off)は成功しているのですが、例えば、青点灯中にその明るさを変更したく、pwmのdutyを増減するループで処理したところ、明るさの変化(数秒間の変化の繰り返し)は上手くできるのですが、色変更の切り替えが明るさ変更中にできません。(明るさ変化のループが終わった時点でないと割り込みができない)
pwmのdutyを変更するループ内では割り込みを受け付けないのでしょうか?
ご多忙のところを誠に恐縮ですが、ご指導賜りたくお願い申し上げます。
コメントどうもありがとうございます。
原因を考えてみたのですが、思い当たるものは思いつきませんでした…
PWM機能のパラメータ変更は、単にレジスタを変更するだけですので、割り込みに影響する/されるようなことはないと思いました。
割り込み処理は、内部のハードウエアで生成される割り込み信号がONになるとハードウエアが強制的に割り込み関数を実行します。また、PWM機能は内部では割り込みフラグ回路とは別のハードウエアで構成されていて、タイマーと各種レジスタのハードウエア処理で信号が生成されています。
ということで、PWMのDutyを変更する処理で割り込みを受け付けないというのは、意図的に割り込みを禁止(例えばGIEをDisableにするなど)する以外は特に原因が思いつきませんでした。
割り込みはデバッグが難しいので原因特定が難しいですよね。その場合は、別の簡単なプログラムを作成して動作を確認してみるのもよいかと思います。
回答になっておらず申し訳ございません…
早速のご連絡を有難うございました。
その後、プログラムを確認いたしまして、割り込みルーチンに判定用の定数を設定し、割り込み発生時ループ内にその定数を確認して一致する場合はループから抜ける判断を追加して修正したところ、DUTY可変中でも割り込みがかかるようになりました。
不勉強で申し訳ありませんでした。
ご確認、ご連絡いただきどうもありがとうございました。
解決できてよかったです。不勉強なんておっしゃらないでください。私もこのようなサイトを作っていますが、いまだに知らないことが多いんです。
何よりもご自身で解決でたきた、というのがよかったと思います!
初めまして。
ここのサイトでかなり勉強させていただいてます。
picは最近始めたばかりの初心者です。
(2)以降の『RA2レジスタ』というのは、どこからきたのでしょうか?
何度見返しても見つけられずにいたので教えてください。
あと、デューティーサイクルでいくつか質問させてください。
『デューティーサイクルは10ビットで設定』というのは決まったものなのかということと、『CCPR1Lレジスタ』というのもどこからきたのかわかりません…。1番悩ましいのは上位8ビット、500を4で割るということです(^_^;)
かなりの初心者的質問で大変申し訳ないんですが、よろしくお願いします。
ご質問どうもありがとうございます。
「RA2」はポート名またはピン名で、基礎編第22回でちょっと触れていますので、お手数ですがご一読いただければと思います。
デューティーサイクルは10ビットで設定、というのはPIC12F1822の仕様で、他のマイコンでは異なる可能性があります。とは言ってもこのクラスのマイコンではPWMのデューティーサイクルは10ビット指定のものが多いです。当然ながらビット数が多いほど細かい制御ができます。
ところで、このマイコンはデューティー比を10ビットで指定できますが、マイコン内部の設定レジスタは全て8ビットです。ということで、12F1822では、デューティー比10ビットのうち、上位8ビットをCCPR1Lリジスタに設定し、下位2ビットをDC1Bに指定します。
ところで、10進数で500は、2進数で表現すると111110100です。この2進数のうち、上位8ビット、つまり「1111101」をCCPR1Lにセットして、「00」をDC1Bにセットすることになります。
まず簡単な方からですと、DC1Bに「00」をセットするのは、正確には
DC1B = 500 & 0b11;
となりますが、DC1Bは2ビットですのでDC1B = 500と書いてしまって問題ありません。この場合、上位8ビットは捨てられてしまいます。
次に「111110100」から上位8ビットの「1111101」取り出す方法ですが、これは右に2桁ずらせばOKです。2進数を右に1桁ずらすには2で割ればいいので、2桁ずらすにはもう1回2で割る、つまり4で割れば上位8ビットが取り出せます。
もし10進数、2進数に慣れていないとこの取り扱いはとても難しく感じると思います。慣れていないようでしたら、入門書の2進数と10進数の説明をご確認いただければと思います。
丁寧な返信ありがとうございます!
一つ訂正がありました。
自分がお聞きたしたかったのは『PR2』レジスタについてでした(^_^;)質問内容が間違っており失礼しました。
周期の設定レジスタ=『PR2
レジスタ』という理解でよろしかったでしょうか?
質問ばかりで申し訳ありません。よろしくお願いします。
はい、PR2レジスタはこの記事の(2)で説明しているように使用します。PR2の値そのものが周期になるわけではなく、
「周期 = ( PR2 + 1 ) x 4 x 1クロック分の時間 x プリスケーラ値」
が周期になりますので、この式からPR2の値求めることになります。
初めまして。
現在私は仕事で32.768kHzのクロックで1秒ごとに電圧を上げ、60秒になってある電圧値に達したら再び0Vに戻してカウントを繰り返す動作をマイコンで行いたいと考えております。
電圧は1秒ごと階段状に上げていき、60秒(60階段)で特定の上限電圧(例えば5V)になったら再び0Vに戻す。
クロックは外部に32.768kHzの水晶発振器を接続しマイコンに入れようと検討しております。
今回この記事を読み、段階的に電圧を出力する方法としてPICのPWM機能が利用できるかなと思いましたが、実現可能そうでしょうか^^;
ご質問どうもありがとうございます。
一般的にはDAC(DAコンバータ)を使用するのがよいと思います。
PIC12F1822でPWM制御をしていますが、出力ピンには0Vか5Vのどちらかの電圧しか出ていません。PWMは0Vと5Vの出力時間の割合を変えて見かけ上、LEDの明るさやモーターの回転数を変えることができています。
なお、ADコンバータは10ビット(1024段階)や12ビット(4096段階)の分解能がありますが、DAコンバータは分解能が少なく、例えばPIC12F1822のDAコンバータは5ビット(32段階)しかありません。5Vを32分割して出力するということになります。60段階で出力する場合は、256段階サポートしているPICマイコンを使用することになります。
claynets様
この度はご回答、及び情報提供ありがとうございます!
PWMよりもDACの方が有効との事でかしこまりました。
claynets様
この度はご回答、並びに貴重な情報ありがとうございます!
方法としてはDACの方が良いという事で、その方法で検討しようと思います!^^
それで現在DACの分解能が256段階(8bit)仕様の物を探しておりまして。当初PIC16F18313で検討していたのですが、こちらもDAC分解能が5bitで私が望む機能を満たさないかなと思いまして_ _;
pwm の解説を色々聞いていましたが、このサイトが一番わかりやすいです
ありがとうございました
コメントどうもありがとうございました。
分からないところがありましたら遠慮なくご質問いただければと思います。
PIC18F2320を使用しているのですが、イマイチよくわかっていないので、足りない点を教えていただけたらと思います。
PWMに使用するピンCCP1を割り当てるには以下の2文だけでいいのでしょうか。
TRISCbits.TRISC2=0; //RC2をPWM割り当て
CCP1CONbits.CCP1M=0b1100; //PWM有効化、active-highに設定
周期とデューティーサイクルの設定についてはツールラボさんが記載しているコードとほぼ同じになると理解しています。
理解が甘く、あやふやな質問で申し訳ないです。
追記:
連投すみません。
PIC18F2320のデータシートに記載されているPWMの有効化手順(15.5.3 Setup for ~)を参考にしました。
ただ、
5.Configure the CCP1 module for PWM operation.
というところだけあいまいだったためこちらのサイトを参考にさせていただいた次第です。
ご質問どうもありがとうございます。
確かにテータシートのp.139を見ると「Configure the CCP1 module for PWM operation」とあって具体的な内容が曖昧ですよね。
p.139の手順の1〜4を考慮すると、
CCP1CONbits.CCP1M=0b1100;
の設定をしてください、いう意味になると思います。また、PIC18F2320はこのページ記事記載のコードとほぼ同じになると思います。
すみません、実物があれば確認できるのですが、手持ちがないのでデータシートの確認のみのご回答になってしまいます。(それにしてもデータシートにプログラムサンプル欲しいですよね)
dsPIC30F4013
を使用しています。
本サイトのソースコードをコピーしてみても赤線だらけで,そもそもPWM機能を使用できるピンすら把握できておらず,挫折しかけています…。
何か有用なヒントがありましたらご教示いただけたら幸いです。
[参考]
データシートはこちらです↓
http://ww1.microchip.com/downloads/en/devicedoc/70138c.pdf
ご質問どうもありがとうございます。
dsPICをお使いなんですね。何か信号処理やデータ処理などをされるのでしょうか。
このPICマイコンを使ったことがないのでデータシートの確認のみになりますが、PWM機能は以下の使い方のようです。(第13章の13.4節です)
[設定]
・出力ピンはOC1かOC2のどちらか
・PWM設定は、OCxCONのOCMビットに0b110か0b111を設定
(この設定だけでOCxピンのPWM機能が有効になるようです)
・タイマーはTMR2かTMR3
[PWM制御]
・周期はTMRxのPRxに設定(周期の計算は式13-1(p.83)に出ています)
・デューティーサイクルはOCxRSに設定
・TMR2か3のTONを1にするとPWM信号が生成されます
なお、TMR2かTMR3の選択は、OCxCONレジスタのOCTSELビットで設定します。
現物を持っていないので実機確認できませんが、データシートでは上記手順で説明されています。
詳細はデータシートの第13.4節に説明されています。
ご無沙汰しております。丁寧なご回答本当にありがとうございます。
教えていただいた内容を元にして試行錯誤した結果,同一のタイマーを用いたPWM信号を2つのピンから出すことに成功しました。本当にありがとうございます。
そこで,再び質問があるのですが,OC1のタイマをTMR2に,OC2のタイマをTMR3に指定し,それぞれの周期を同じ値にしているのにもかかわらずPWMの周期が大きく異なる,といった現象に悩まされています。
この質問がPICマイコン全般に言えることであれば,ご教示いただけましたら幸いです。
質問に質問を重ねてしまうようで恐縮なのですが,どうぞよろしくお願いいたします。
※こちら側の接続上の不具合かサイトに反映されなかったので再送しております。
ありがとうございました。
無事成功しました!
たびたびすみません
今回のプログラムをそのまま張り付けたのですが
make -f nbproject/Makefile-default.mk SUBPROJECTS= .build-conf
make[1]: Entering directory ‘C:/Users/81908/Desktop/MPLAB�v���O�����t�@�C��/LED_flash.X’
make[2]: *** No rule to make target ‘newmain.c’, needed by ‘build/default/production/newmain.p1’. Stop.
make[1]: *** [.build-conf] Error 2
make -f nbproject/Makefile-default.mk dist/default/production/LED_flash.X.production.hex
make[2]: Entering directory ‘C:/Users/81908/Desktop/MPLAB�v���O�����t�@�C��/LED_flash.X’
make[2]: Leaving directory ‘C:/Users/81908/Desktop/MPLAB�v���O�����t�@�C��/LED_flash.X’
nbproject/Makefile-default.mk:90: recipe for target ‘.build-conf’ failed
make[1]: Leaving directory ‘C:/Users/81908/Desktop/MPLAB�v���O�����t�@�C��/LED_flash.X’
make: *** [.build-impl] Error 2
nbproject/Makefile-impl.mk:39: recipe for target ‘.build-impl’ failed
BUILD FAILED (exit value 2, total time: 159ms)
と表示されてしまいます。。
何が原因なのでしょうか
教えていただけると助かります。
よろしくお願いいたします。
けんけんさま、
ご質問どうもありがとうございます。
エラー内容を確認すると、フログラムの中身の問題ではなく、ビルドルール(どのファイルをどのようにビルドするか、という指示)の問題のようです。
大変お手数ですが、新規プロジェクトを作成→新規main.cファイルを作成→プログラムをコピペ→ビルドをしてご確認いただければと思います。
ツールラボさんのサイトが一番わかりやすかったので参考にさせていただきながら、PIC18F67J94にてPWMを試しています。が、動きません。。この型番でもなにかご存知でしたら教えてください。
下記内容
void PWM2_init(void){
//TIMER2
T2CON = 0b00000001; // Post-Scaler 1/1, Pre-Scaler 1/4
TMR2 = 0; // スタート位置
PR2 = 63 – 1; // ストップ位置 5(μsec)*62.5 =125(μsec)
TMR2IF = 0; // Flag Clear
TMR2IE = 1; // Interrupt Enable
//ピン割り当て
EECON2 = 0x55;
EECON2 = 0xAA;
IOLOCK = 0;
RPOR16_17 = 0x09;//RPO17R<7:4>,RPO16R<3:0>出力CCP5に
EECON2 = 0x55;
EECON2 = 0xAA;
IOLOCK = 1;
//
MDSRC = 0b00001100;
CCPTMRS1 = 0b00000000;//CCP5 is based off of TMR1/TMR2
CCP2CON = 0b00001100;// CCP2 is PWM mode
//周期とデューティーサイクル設定
CCPR2L = 0b00000000;
CCPR2H = 0b00001000;//20=32
//PWMSTART
T2CONbits.TMR2ON = 1;
}
Ichiyamaさま、
ご質問どうもありがとうございます。
PIC18F67J94は持っていないのでデータシートによる確認のみになりますが、回答させていただきます。
PWMモジュールとして、以下の構成を考えられていると思います。
使用するPWMモジュール: CCP5
PWMモジュールのタイマー: TMR2
この場合、設定するレジスタは以下になります。
PWM関連
CCP5CON: CCP5モジュールの設定
CCPTMRS1: CCP5モジュールに使用するタイマー選択
CCPR5L: デューティーサイクル設定
PR2: 周期設定
タイマー関連
T2CON: タイマー2の設定
いただいたプログラムでは、CCPモジュール(PWMモジュール)の設定レジスタとしてCCP2CONを使用されていますので、ここはCCP5CONを設定する必要があります。CCP5CONのデフォルト設定は0x00ですので、デフォルトのままでPWMモジュールはOFFになってしまい、結果としてPWMが動作しない、という状況になっていると思われます。
またデューティーサイクルの設定ですが、CCPRxHではなく、CCPRxL (HではなくLのレジスタ)に設定します。CCPRxHレジスタは設定する必要はありません。
この理由について簡単にご説明します。PICマイコン内部の回路の話になってしまいますが、PWM制御を行う際、デューティサイクル値は、PWM制御中は1サイクル毎にCCPRxLに設定されている値をCCPRxHにコピーして、このCCPRxHレジスタの値をデューティサイクルとして使用しています。なぜこのようなことをするかというと、PWM制御中にデューティーサイクルを変更する場合、本来であればPWM制御の1サイクルのちょうど区切りで変更するのか理想です。適当なタイミングでデューティーサイクルを変更すると、その1サイクルのデューティー比がおかしくなってしまいます。そこで、PWMの1サイクル制御ごとに、デューティーサイクルの値は、内部でCCPRxLからCCPRxHにコピーして制御しています。(読み返してみましたが言葉での説明は難しいですね….わかりづらくてすみません…)
以上のことから、CCPxCONレジスタの設定部分を以下のようにすれば動作するのではないかと思っています。
CCP5CON = 0b00001100; // CCP5をPWMモードに設定
CCPR5L = 0b00001000; // デューティーサイクルの設定
// CCPR5Hは設定しない
ピンアサインなどの部分は確認しておりませんので、以上の対応で動作しないようであればまたご質問いただければと思います。
こんなにご丁寧に回答いただきありがとうございます!CCPRxHを使わない理由も合点がいきました。とてもわかりやすかったです。そして、下記コードで動きました。本当にありがとうございます!!
void PWM2_init(void){
//TIMER2
T2CON = 0b00000001; // Post-Scaler 1/1, Pre-Scaler 1/4
TMR2 = 0; // スタート位置 タイマー2カウントリセット
PR2 = 124; //周期 = ( PR2 + 1 ) x 4 x 1クロック分の時間(0.5μsec) x プリスケーラ値
TMR2IF = 0; // Flag Clear
TMR2IE = 1; // Interrupt Enable
//解除
EECON2 = 0x55;
EECON2 = 0xAA;
IOLOCK = 0;
//ピン割り当て
RPOR16_17 = 0b00001001;//RPO17R<7:4>,RPO16R<3:0>出力CCP5に
//ロック
EECON2 = 0x55;
EECON2 = 0xAA;
IOLOCK = 1;
//
CCP5CON = 0b00001100; // CCP5をPWMモードに設定
CCP5CONbits.DC5B = 250;// デューティーサイクルの設定上位8bit 0.5(μsec)*250 = 125(μsec)
CCPR5L = 250 >> 2; // デューティーサイクルの設定下位2bit
CCPTMRS1 = 0b00000000; //使用するタイマーの設定CCP5 is based off of TMR1/TMR2
//PWMSTART
T2CONbits.TMR2ON = 1;
}
ご連絡いただきどうもありがとうございました。
かなり多機能なPICマイコンなのと、PWM機能など使用する場合はPPSでピン設定が必須のようですので、使いこなすのは大変そうですね。
また何か不明点ありましたらご質問いただければと思います。
41行目にエラーが出てしまうのですが
原因がわかりません。
お忙しい中とは思いますが回答よろしくお願いします。
ゆうすけさま、
ご質問どうもありがとうございます。
このページのプログラムをコピーして、MPLABX IDEでプロジェクトを作成、ビルドしたところ、Mac版、Win版のMPLABXともにエラーは出ることなくビルドできました。
お手数ですが、Outputタブに出力されるエラーの内容をお教えいただけませんでしょうか。その内容から原因がわかるかもしれません。