PIC32でPWMを出力する

Generate PWM signal by PIC32
 
特定の比率のオン・オフを周期的かつ正確に繰り返すパルス幅変調「PWM」は、サーボモーターの制御や、LEDの輝度調整やブザーの音階調整などの擬似的なアナログ出力に用いられます。PIC32マイコンでは、このPWM出力を出力コンペア機能の一つとして提供しています。

PWMを使用するには出力コンペア設定レジスタ(OCxCON)のOCMを0110にします。ちなみに、0111でフォルトを有効にすると、指定したフォルトピン(OCFA/OCFB)に0信号が流れたとき、PICはプログラムを介さず直接PWMを停止させます。

PIC32においては、タイマーで周期の長さをカウントし、タイマーカウントが特定の値(OCxR)になるとスイッチを切り、タイマーの1サイクルが終わると再びスイッチを入れることを繰り返しています。
タイマーは2もしくは3を使います。Timer2および出力コンペアをともに32bitモードにするとカウンターの上限が増えるので、極端に低い周波数の生成もできるようになります。

タイマークロックがそのまま1秒あたりの実行回となるので、このクロックを適用したい周波数で割ると、指定すべきタイマー値(PRx)になります。例えば、タイマーが20MHzクロックで動作している環境で1kHzの周期(1000カウントで1単位)を実装したいのなら、20000000/1000=2000が計算値となります。

PIC32MX1xx/2xxシリーズでは、PWMを出力できる主なピンはA0,B3,B4,B7,B15です。 PWMではスイッチのオン・オフの長さの比率がデューティ比になるので、30%にしたいときはPRxの1/3の計算結果をOCxRSの値にすると良いことになります。

このサンプルでは100msごとにタイマー割り込みを発生させ、呼ばれた回数に応じて「2kHz→1kHz→休止」の処理を繰り返します。
pwm.c
#include <p32xxxx.h>
#include <sys/attribs.h>

// 外部の8MHz水晶発振器から40MHzのクロックを生成するための設定
#pragma config FNOSC = PRIPLL // 発信源 = 主外部発振器
#pragma config POSCMOD = XT // 主発振の方法 = 外部高精度発振器を使う
#pragma config FPLLIDIV = DIV_2 // PLL入力分数 = x1/2
#pragma config FPLLMUL = MUL_20 // PLL逓倍比 = x20
#pragma config FPLLODIV = DIV_2 // PLL出力分数 = x1/2
#pragma config FPBDIV = DIV_2 //周辺モジュールクロック倍数 : x1/2

#define BASE_CLK 20000000  // 周辺クロック(20MHz)

void updateDuty(int hz)
{
    // 1サイクルあたりの時間を計算
    int ticks = BASE_CLK / hz;
    PR2 = ticks;
    
    // PR2のタイマーカウントの半分で出力を切り替えるようにするとデューティ比が50%になる
    // 1/4ならデューティ比は25%になる。
    ticks /= 2;
    
    // OCxRSに値をセットするとバッファとして機能する。
    // これによりOCxRの値が急に切り替わることによって発生するノイズ(グリッチ)を抑止できる
    OC1RS = ticks;
    
    // 出力コンペアに割り当てているタイマーをリセットすることでPWMを再始動
    TMR2 = 0;
}

void InitTimer1()
{
    T1CONbits.ON = 0;    // 設定のため、タイマー1を無効にする
    TMR1 = 0;            // タイマー1をリセット
     
    // 100ms周期で割り込み <- 500ns(1 / 20MHz) * 256 * 7812(0x1E84) = 100ms
    T1CONbits.TCKPS = 3; // プリスケーラ : x1/256
    PR1 = 0x1E84;        // カウンター
        
    IPC1bits.T1IS = 3;   // 副優先度(0-3)
    IPC1bits.T1IP = 4;   // 割り込み優先度(0-7)
    IFS0bits.T1IF = 0;   // 割り込みフラグをリセット
    IEC0bits.T1IE = 1;   // タイマー1の割り込みを許可
    
    T1CONbits.ON = 1;    // タイマー1有効
}

int beep_mode = 1;

void __ISR(_TIMER_1_VECTOR, IPL4AUTO) OnTimer1()
{
    IFS0bits.T1IF = 0;

    if(beep_mode == 1){
        OC1CONbits.ON = 1;  // 出力コンペアを有効
        updateDuty(2000);
    }else if(beep_mode == 2){
        updateDuty(1000);
    }else if(beep_mode == 3){
        OC1CONbits.ON = 0;  // 出力コンペアを無効
    }else if(beep_mode == 16){
        beep_mode = 0;
    }
    beep_mode++;
}

int main(void)
{
    ANSELB = 0x00;          // ポートBをすべてデジタルモードにする  
    TRISB = 0;              // ポートBをすべて出力にする
    
    // タイマー2の設定(プリスケーラ1/1、内部クロックを使用)
    T2CONbits.ON = 0;
    T2CONbits.T32 = 1;       // タイマー3と併合して32bitモードで使用
    T2CONbits.TCKPS = 0b000; // 分周比 1:1
    T2CONbits.ON = 1;  
    TMR2 = 0;
        
    OC1CONbits.ON = 0;        // 設定のため出力コンパレータ(OC1)を無効に
    OC1CONbits.OCTSEL = 0;    // タイマー2を使用
    OC1CONbits.OCM = 0b0110;  // PWMで、失敗(fault)の検知を無効
    OC1CONbits.OC32 = 1;      // 32bitで計測
    RPB3Rbits.RPB3R = 0b0101; // B3にOC1を割り当てる
    OC1CONbits.ON = 1;
    
    InitTimer1();             // タイマー1の初期化
    
    INTCONbits.MVEC = 1;            // マルチベクタ割り込みを有効にする      
    __builtin_enable_interrupts();  // マイコンにおける割り込みの有効
    
    while(1) ;
}
PWMをデューティ比50%で出力すると一般的な矩形波になるので、圧電ブザーをB3とGND端子に接続すると、普遍的なブザー音が鳴り響きます。

2018/10/01