PICマイコン(PIC12F675)を使ったPWMの考え方

組み込みエンジニア

こんにちは、ENGかぴです。

マイコンのソフト開発しているとPWM制御を使って電圧値を制御することがあります。PWMはPIC12F675ではタイマ機能とDOを使って生成することができます。PWMを使うと電流消費を押さえながらモーターを回転させることもできます。

PWMの考え方を応用するとブザーを鳴らすこともできます。ブザーを鳴らす方法については下記記事にまとめています。

PICマイコン(PIC12F675)を使ってブザーを鳴らす方法

PIC12F675を使ってマイコンの動きを勉強するためにPIC12F675の機能でできることについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

PIC12F675でPWM波形を生成する方法

PWM制御はマイコンからモーターの回転数などを変更する場合に使用されることが多い技術です。マイコン単体ではモーターを回転させるほどの出力電流が出せないので、通常はトランジスタなどを使用して電流を増幅して使用します。

PWM制御について

PWMの説明図
PWMの説明図

PWMとはpulse width modulationの略でありパルス幅変調です。一定のキャリア周波数に対する波形のデューティ比(HighとLowの比率)によって電圧を調整することができます。

PWM制御によってトランジスタなどの素子がON/OFFを繰り返すことからOFF時には電流が流れないことから消費電流を抑えることもできます。

タイマー0を使用する

PIC12F675のTMR0の考え方

PWM制御は可能な限りキャリア周波数が高い(タイマーの間隔が短い)方が電圧の操作がしやすいためプリスケーラなしの場合で256usでオーバーフローするタイマーであるタイマー0を使うことでキャリア周波数1Hz以上のPWM波形を生成します。

PIC12F675はTMR0とWDTでプリスケーラを兼用となっているためWDTでプリスケーラを使用している場合はTMR0のプリスケーラとしては使用できません。

WDTはプリスケーラなしに設定しても約18msで働くため余裕があると判断してTMR0でプリスケーラを使用することを考えていきます。

PICマイコン(PIC12F675)の初期化

PICマイコンを使用するためには、マイコンをどのような機能で使用するのかを選択する初期化を行う必要があります。デューティ比が変更できるようにするためAD変換器を使用します。AD変換の使い方については下記記事を参考にしてください。

PICマイコン(PIC12F675)のAD変換とタイマーの組み合わせ

ボタンを押したときにPWM波形が出るようにするためにDIフィルタについても実装します。下記記事を参考にしてください。

PICマイコン(PIC12F675)のDIピンのフィルタの考え方

タイマー0の初期化

タイマー0の使い方について説明します。タイマー0に関係するOPTIONレジスタとINTCONレジスタ及びTMR0レジスタです。

引用:PIC12F675のデータシート(OPTIONレジスタ)
引用:PIC12F675のデータシート(OPTIONレジスタ)

bit2-0についてTMR0を今回は1:4で使用するために0b001をセットします。
bit3についてTMR0のプリスケーラとして使用したいので0をセットします。
bit5について内部クロックを使ってカウントしたいので0をセットします。

bit5をセットするとTMR0が動き出しますので問題になる場合は初期化部分の最終箇所でセットすると良いでしょう。

引用:PIC12F675のデータシート(INTCONレジスタ)
引用:PIC12F675のデータシート(INTCONレジスタ)

bit5についてTMR0割り込みを使用するので1をセットします。セットするとタイマー0の割り込みが動き出すので問題となる場合は、初期化の最終箇所でT0IEのビットをセットすると良いでしょう。

bit2についてTMR0割り込みが発生するとセットされます。ユーザーでクリアする必要があります。

TMR0レジスタに値を入れると入力した値からカウントが開始するため初期値を入れることで管理したいタイマーの時間が生成できます。

定義部分を実装する

タイマー0の初期値を設定することでオーバーフローまでのカウント数を設定することができます。今回はカウント200でオーバーフローさせたいので初期値の定義として#define TIME0_START 0x38(56)を定義して割り込み内部でセットしています。

初期化部分を実装する

void main(void) {
    //各種初期化  
    ADCON0 = 0x81;  //ADコンバータの電源をON。結果は右詰。
    ANSEL = 0x11;   //GP0をアナログ入力に使用。変換クロックは8TOSC
    CMCON = 0x07;   //コンパレータ使用しない
    TRISIO = 0x09;  //GP0,GP3入力、その他出力
    GPIO = 0x00;    //ポートの設定 1:High 0:Low
    OPTION_REG = 0x01;  //プリスケーラ4 TMR0で使用
    //割り込み設定
    INTCON = 0xC0;  //PIE1許可
    PIE1 = 0x41;    //AD変換終了・TMR1割り込み許可  
    //TMR1起動
    T1CON = BIT_CLR;
    TMR1H = TIME_START_H;   //タイマ初期化
    TMR1L = TIME_START_L;   //タイマ初期化
    cnt10ms = 0;
    TMR1ON = BIT_SET;   //タイマ1ON
    TMR1IF = BIT_CLR;   //割り込みクリア
    //DIフィルタ初期起動
    timWait = TIME_WAIT_MAX;

    while( timWait > TIME_UP){
	CLRWDT();
        mainTimer();
	DiFilter();
    }

    timWait = TIME_OFF;
    //TMR0を起動
    TMR0 = TIME0_START;
    T0IE =BIT_SET;  //TMR0割り込み許可
    T0IF = BIT_CLR; //割り込みクリア
    //AD変換起動
    GO_DONE = BIT_SET;

TMR0はOPTIONレジスタのbit5(T0CS)を内部クロックを使用する設定(0)としたとき動き出すので割り込みを使用していなくてもT0IFビットがセットされてしまいます。

割り込みを使用する場合は初期値と割り込みの許可をする際にT0IFビットのクリアをしておく必要があります。

PWMを実装する

PWM確認として2つの回路を使用します。

回路図(PWMでLED1を点灯)
回路図(PWMでLED1を点灯)

マイコンの出力電流でも動作可能なLEDを使ってPWMについて動作確認を行います。可変抵抗によってPWMのデューティ比が変更できるようにしてLEDの点灯が変化することが確認できる構成を考えていきます。

回路図2(PWMでモーターを回す)
回路図2(PWMでモーターを回す)

PWMでトランジスタTR1をON/OFFしてモーターを回す回路を考えます。モーターの電源はトランジスタのコレクタ側なのでGNDが共通であれば別の電源でも問題ありません。

TR1のベースにマイコンのPWM波形を入力しますが電流制限のため抵抗R3を入れています。この抵抗が大きすぎると増幅してもモーターが回せなくなるので調整が必要です。R4はトランジスタへの入力を安定化させることやトランジスタの漏れ電流を防止するために入れています。

モーターの容量が大きい場合は残留磁束によって逆起電力が発生してダメージになることがあることからダイオードを実装することがあります。電池で動かすようなDCモーターであれば不要です。

PWMの回路図と出力電流に関する注意事項

SW1を押すとLED1が点灯しPWM制御を開始します。PWMはGP4から出力され可変抵抗VR1を変更すると明るさが変化します。

DCモーターなどを駆動したい場合は、トランジスタなどのスイッチング素子を介して接続する必要があります。直接接続しても電流不足で駆動できないことやマイコンが出力電流を引き出そうとして熱を持ち故障する可能性があります。

下記記事にシンク電流と出力電流についてまとめています。

PICマイコン(PIC12F675)のI/Oポートのシンク電流/出力電流の考え方

PWMを実装する

    while(1){
	CLRWDT();
        mainTimer();
        DiFilter();
        ADFilter();
        
        CntData = TMR0;

        if( gpio3_buf){
            if( CntData >= CntMax ){
                GPIO4 = BIT_CLR;
            }
            else{
                GPIO4 = BIT_SET;			
            } 
        }else{
            GPIO4 = BIT_CLR;
        }
    } 

TMR0のカウント値をメイン関数で確認しAD変換からで算出したCntMaxと比較することでPWM波形を生成しています。可変抵抗を調整しAD変換の値が大きくなるほど比較値のCntMaxが大きくなるようにすることでデューティ比が高くなります。

//--------ADフィルタ----------------------------
void ADFilter(void){
    unsigned short set;
    char i;
    
    if( adfiltflg ){
        adfiltflg = BIT_CLR;
        di();   //割り込みを一旦禁止
        AdData.buf[ AdData.wp ] = ADRESH;
        AdData.buf[ AdData.wp ] = ( AdData.buf[ AdData.wp ] << 8) + ADRESL;
        ei();   //割り込み禁止を解除
        set = 0;
        for( i = 0; i < AD_NUM; i++ ){
            set += AdData.buf[ i ];
        }
        set = set >> 2;
        if(++AdData.wp >= AD_NUM){
            AdData.wp=0;
        }

        CntMax = (set >> 2); 
       //TMR0は8ビットなのでAD変換値を8ビットのデータに合わせるため2ビットシフト
        if(CntMax <= TIME0_START){
            CntMax = TIME0_START;
        }
        CntMax += HOSEI; //デューティー比の初期値を上げる補正を入れてもよい
    }
}

AD変換値の移動平均をとって値を確定した後に2ビットシフトしているのはTMR0が8ビットデータであるためです。

CntMaxが初期値以下のときは初期値を採用するように条件を加えています。CntMaxの初期値を任意にすることで最低のデューティ比を決めることができます。

TMR0のプリスケーラを使用しないことや1:2にして間隔を細かくしてもよいのですが、割り込みが増えてしまうことや精度が取れなくなるためPIC12F675において内部クロックを使用する場合は誤差に注意が必要です。

GEEKJOB-未経験からITエンジニアに【オンライン無料体験】

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

// CONFIG
#pragma config FOSC = INTRCIO
#pragma config WDTE = ON
#pragma config PWRTE = OFF
#pragma config MCLRE = OFF
#pragma config BOREN = OFF
#pragma config CP = OFF
#pragma config CPD = OFF

#include <xc.h>
//--------------定数定義----------------------------
#define _XTAL_FREQ  4000000     //発振周波数 4MHz
#define TIME_START_H    0xD8    //タイマスタート位置 65536- 55536 = 10000
#define TIME_START_L    0xF0    //タイマスタート位置 65536- 55536 = 10000
#define TIME0_START 0x38        //タイマスタート位置 256 - 56 = 200
#define CNT_MAX	2
#define BIT_SET	1
#define BIT_CLR 0
#define CNT_OFF -1
#define AD_NUM  4           //移動平均フィルタのサンプリング数
#define DI_NUM  4
#define TIME_OFF    -1      //タイマーを使用しない場合
#define TIME_UP 0           //タイムアップ
#define TIME_ADCNT_MAX 10   //100ms経過
#define TIME_BASE_MAX 1     //ベースタイマカウント値
#define TIME_WAIT_MAX 10
#define HOSEI 75            //PWMの初期のデューティー比をあげるための補正

typedef struct{
    char	wp;
    unsigned short	buf[ AD_NUM ];
}FILT_DATA;
//-----------------変数定義--------------------------
unsigned short  CntMax;	//Duty最大値一時保管用
unsigned short  CntData;
FILT_DATA   AdData;
FILT_DATA   DiData;
char    adfiltflg = BIT_CLR;		//ADフィルタ起動
char    difiltflg = BIT_CLR;		//DIフィルタ起動
unsigned char   cnt10ms;
char    TimerAdStart = TIME_OFF;
char    timWait = TIME_OFF;
char    gpio3_buf;
//-----------------関数定義---------------------------
void ADFilter(void);
void mainTimer(void);
void DiFilter(void);

void main(void) {
    //各種初期化  
    ADCON0 = 0x81;  //ADコンバータの電源をON。結果は右詰。
    ANSEL = 0x11;   //GP0をアナログ入力に使用。変換クロックは8TOSC
    CMCON = 0x07;   //コンパレータ使用しない
    TRISIO = 0x09;  //GP0,GP3入力、その他出力
    GPIO = 0x00;    //ポートの設定 1:High 0:Low
    OPTION_REG = 0x01;  //プリスケーラ4 TMR0で使用
    //割り込み設定
    INTCON = 0xC0;  //PIE1許可
    PIE1 = 0x41;    //AD変換終了・TMR1割り込み許可  
    //TMR1起動
    T1CON = BIT_CLR;
    TMR1H = TIME_START_H;   //タイマ初期化
    TMR1L = TIME_START_L;   //タイマ初期化
    cnt10ms = 0;
    TMR1ON = BIT_SET;   //タイマ1ON
    TMR1IF = BIT_CLR;   //割り込みクリア
    //DIフィルタ初期起動
    timWait = TIME_WAIT_MAX;
    while( timWait > TIME_UP){
        CLRWDT();
        mainTimer();
	DiFilter();
	}
    timWait = TIME_OFF;
    //TMR0を起動
    TMR0 = TIME0_START;
    T0IE =BIT_SET;  //TMR0割り込み許可
    T0IF = BIT_CLR; //割り込みクリア
    //AD変換起動
    GO_DONE = BIT_SET;
    
    while(1){
	CLRWDT();
        mainTimer();
        DiFilter();
        ADFilter();
        
        CntData = TMR0;

        if( gpio3_buf){
            if( CntData >= CntMax ){
                GPIO4 = BIT_CLR;
            }
            else{
                GPIO4 = BIT_SET;			
            } 
        }else{
            GPIO4 = BIT_CLR;
        }
    }    
}
//-----------------割り込み関数-------------------------
void __interrupt() intr( void){
    
    if( TMR1IE == 1 && TMR1IF == 1){ //タイマ1の割り込みであるか
        TMR1IF = 0;//割り込みフラグをクリア
        ++cnt10ms;
        difiltflg = BIT_SET;
        TMR1ON = 0;
        TMR1H = TIME_START_H;//タイマ初期化
        TMR1L = TIME_START_L;//タイマ初期化
        TMR1ON = 1;//タイマ1ON
    }
    if( T0IE == 1 && T0IF == 1){
        T0IF = 0;
        TMR0 = TIME0_START;
    }    
    if( ADIE == 1 && ADIF == 1){
        ADIF = 0;
        adfiltflg = BIT_SET;
    }
}
//----------タイマー管理----------------------------------
void mainTimer(void){
    
    if( cnt10ms >=  TIME_BASE_MAX){
        cnt10ms -=TIME_BASE_MAX;

        if( TimerAdStart > TIME_UP ){
            --TimerAdStart;
        }
        if( timWait > TIME_UP ){
            --timWait;
        }
    }
    
    if( TimerAdStart == TIME_UP){
        if( GO_DONE == BIT_CLR){
            GO_DONE = 1;
            TimerAdStart = TIME_ADCNT_MAX;
        }
    }
}
//--------モード切替部のDIフィルタ----------------------------
void DiFilter(void){

    if( difiltflg ){
        difiltflg = BIT_CLR;
        DiData.buf[ DiData.wp ] = GPIO3;
			
        if( DiData.buf[ 0 ] == DiData.buf[ 1 ] &&
            DiData.buf[ 1 ] == DiData.buf[ 2 ] &&
            DiData.buf[ 2 ] == DiData.buf[ 3 ] ){
            //比較して一致したら値を採用
            gpio3_buf = DiData.buf[ 0 ];
        }	
        if( ++DiData.wp >= DI_NUM) DiData.wp = 0;
     }
}
//--------ADフィルタ----------------------------
void ADFilter(void){
    unsigned short set;
    char i;
    
    if( adfiltflg ){
        adfiltflg = BIT_CLR;
        di();   //割り込みを一旦禁止
        AdData.buf[ AdData.wp ] = ADRESH;
        AdData.buf[ AdData.wp ] = ( AdData.buf[ AdData.wp ] << 8) + ADRESL;
        ei();   //割り込み禁止を解除
        set = 0;
        for( i = 0; i < AD_NUM; i++ ){
            set += AdData.buf[ i ];
        }
        set = set >> 2;
        if(++AdData.wp >= AD_NUM){
            AdData.wp=0;
        }

        CntMax = (set >> 2); //TMR0は8ビットなのでAD変換値を8ビットのデータに合わせるため2ビットシフト
        if(CntMax <= TIME0_START){
            CntMax = TIME0_START;
        }
        CntMax += HOSEI; //デューティー比の初期値を上げる補正を入れてもよい
    }
}
//---------------------end file----------------------------

関連リンク

PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

TECH::CAMPプログラミング教養【無料体験会】

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました