2022年2月21日月曜日

PIC18F47J53でPWM出力


背景

PIC18F47J53とは、Microchip社が出しているPICマイコンと呼ばれるMPUの1つです。
PICマイコンでのPWM出力実現にまあまあ時間がかかったので、備忘録を兼ねて設定内容を記事として残します。

使ったもの


回路作成

PICKit4を繋ぎ、RC7にLEDを繋いだ回路を組みました。


組むとこうなりました。



信号確認用のAnalog discovery2はRC7とGNDに繋ぎます。


プログラム作成

全体像

// CONFIG1L
#pragma config WDTEN = OFF, PLLDIV = 2, CFGPLLEN = ON, STVREN = OFF, XINST = OFF
// CONFIG1H
#pragma config CPUDIV = OSC1, CP0 = OFF
// CONFIG2L
#pragma config OSC = INTOSCPLL, SOSCSEL = HIGH, CLKOEC = OFF, FCMEN = OFF, IESO = OFF
// CONFIG2H
#pragma config WDTPS = 1024
// CONFIG3L
#pragma config DSWDTOSC = T1OSCREF, RTCOSC = T1OSCREF, DSBOREN = OFF, DSWDTEN = OFF, DSWDTPS = G2
// CONFIG3H
#pragma config IOL1WAY = OFF, ADCSEL = BIT12, MSSP7B_EN = MSK7
// CONFIG4L
#pragma config WPFP = PAGE_127, WPCFG = OFF
// CONFIG4H
#pragma config WPDIS = OFF, WPEND = PAGE_WPFP, LS48MHZ = SYS48X8

#include <xc.h>

// To use __delay_ms()
#define _XTAL_FREQ 48000000

void delay_ms(unsigned long milliseconds) {
while(milliseconds > 0) {
__delay_ms(1);
milliseconds--;
}
}

void main(void) {
// PWM for RC7 CCP10 on PIC18F27J53
    // 10bit mode
PR2 = 0xfe;
T2CONbits.T2CKPS = 0b00; // Prescaler 0b00: 1, 0b01: 4, 0b10: 16

CCP10CONbits.DC10B = 0b00;
CCPTMRS2bits.C10TSEL0 = 0; // TMR2 for PWM
CCP10CONbits.CCP10M = 0b1111; // PWM mode
T2CONbits.TMR2ON = 1; // Turn on Timer2
// set duty
CCPR10L = 0x0;
TRISC7 = 0; // Output

unsigned long waitMs = 2000UL;

while(1) {
CCPR10L = 0x0;
CCP10CONbits.DC10B = 0b00;
delay_ms(waitMs);
CCPR10L = 0x20;
delay_ms(waitMs);
CCPR10L = 0x80;
delay_ms(waitMs);
CCPR10L = 0xfe;
delay_ms(waitMs);
CCPR10L = 0xff;
delay_ms(waitMs);
}
}

要所を解説します。

pragma設定

PIC18F47J53でRTCを利用して時計を作った方の設定をそのまま利用させてもらっています。
// CONFIG1L
#pragma config WDTEN = OFF, PLLDIV = 2, CFGPLLEN = ON, STVREN = OFF, XINST = OFF
// CONFIG1H
#pragma config CPUDIV = OSC1, CP0 = OFF
// CONFIG2L
#pragma config OSC = INTOSCPLL, SOSCSEL = HIGH, CLKOEC = OFF, FCMEN = OFF, IESO = OFF
// CONFIG2H
#pragma config WDTPS = 1024
// CONFIG3L
#pragma config DSWDTOSC = T1OSCREF, RTCOSC = T1OSCREF, DSBOREN = OFF, DSWDTEN = OFF, DSWDTPS = G2
// CONFIG3H
#pragma config IOL1WAY = OFF, ADCSEL = BIT12, MSSP7B_EN = MSK7
// CONFIG4L
#pragma config WPFP = PAGE_127, WPCFG = OFF
// CONFIG4H
#pragma config WPDIS = OFF, WPEND = PAGE_WPFP, LS48MHZ = SYS48X8

#include <xc.h>

delay_msの準備

48MHzで動作するため、その周波数で__delay_msが使えるようにマクロを定義し、__delay_meはconstしか受け付けないので、変数を受け付けられるdelay_msを定義しています。
// To use __delay_ms()
#define _XTAL_FREQ 48000000

void delay_ms(unsigned long milliseconds) {
while(milliseconds > 0) {
__delay_ms(1);
milliseconds--;
}
}

RC7(CCP10)でPWMを利用する設定

データシート(PDF)の「18.4.3 SETUP FOR PWM OPERATION」にも記される通りPWM出力に必要なのは下記の設定です。
  1. PR2レジスタにPWMの区切りを設定(標準は0xffで、設定値小さくするほどPWM周波数が高くなります。)
  2. CCPRxLとCCPxCON<5:4>(DCxB)にdutyを設定(XXPRxLが基本のduty、CCPxCON<5:4>が8bitを越える精度のPWMの下位bitです。)
  3. TRIS bitを操作して対象となるピンを出力に設定
  4. T2CONを操作しTimer2を起動
  5. CCPxを操作してPWM運用を設定

この記事ではRC7で利用できるCCP10でPWMを出力します。
    // PWM for RC7 CCP10 on PIC18F27J53

「PR2レジスタにPWMの区切りを設定」のため、PR2を0xfeにします。
    PR2 = 0xfe;

「CCPRxLとCCPxCON<5:4>にdutyを設定」のため、初期値として0を設定します。
    // set duty
CCPR10L = 0x0;
CCP10CONbits.DC10B = 0b00;

「TRIS bitを操作して対象となるピンを出力に設定」のために、TRISC7を0にしてRC7出力とします。
    TRISC7 = 0; // Output

「T2CONを操作しTimer2を起動」します。
また、T2CKPSを0(Prescalerを1)とします。
    T2CONbits.T2CKPS = 0b00; // Prescaler 0b00: 1, 0b01: 4, 0b10: 16
T2CONbits.TMR2ON = 1; // Turn on Timer2

「CCPxを操作してPWM運用を設定」します。
RC7ではCCP10を使うので、それをPWMとして設定します。
    CCP10CONbits.CCP10M = 0b1111; // PWM mode

PWM有効化の順序に書かれてはいませんが、CCPxで割り当てられるタイマーを選択できるので、CCP10で唯一割り当てられるタイマーの設定値0を設定します。
    CCPTMRS2bits.C10TSEL0 = 0; // TMR2 for PWM

PWMのdutyを変えながら動作

待ち時間として2000msを定義します。
    unsigned long waitMs = 2000UL;

2秒おきに下記の動作を順に切り替えつつ繰り返します。
  1. duty 0%
  2. duty 12.5% (0x20/0xff)
  3. duty 50% (0xf0/0xff)
  4. duty 99.6% (0xfe/0xff)
  5. duty 100%
    while(1) {
CCPR10L = 0x0;
CCP10CONbits.DC10B = 0b00;
delay_ms(waitMs);
CCPR10L = 0x20;
delay_ms(waitMs);
CCPR10L = 0x80;
delay_ms(waitMs);
CCPR10L = 0xfe;
delay_ms(waitMs);
CCPR10L = 0xff;
delay_ms(waitMs);
}

動作確認

LEDはこの順序で2秒ごとに光り方を変えます。
  1. 光らず
  2. とても弱く光る duty12.5%
  3. ほんのり光る duty50%
  4. しっかり光る duty99.6%
  5. しっかり光る duty100% (肉眼では99.6%との違いが分からない)




出力の変化を見てみます。

duty0%

duty 12.5%

duty 50%

duty 99.6%

duty 100%

期待通りにdutyが変化していました。


PIC18F47J53で扱えるPWM dutyのbit数は10が上限

PWMで扱えるビットの関係はこのようになっています。
「PR2が0xff」「Timer Prescalerが16」の組み合わせだと16bit扱えるとなっていますが、PIC18F47J53で扱えるPWMのdutyは「CCPRxL:CCPxCON<5:4>」の最大10bitです。
そのため10bitより多いbitが扱えるモード(14bitや12bit)にすると、10bit全てを1にしても電位が下がる期間が発生する(dutyが100%にならない)ので、マイコンが扱える10bitか8bitでの利用をお勧めします。

不可解なdutyのbit数: 48MHz動作、Timer Prescaler: 1、PR2: 0xffの組み合わせで12bit?

この記事では「MPUの動作速度48MHz」「PR2が0xfe」「Prescalerが1」としました。
PR2とPrescalerの該当する記述は下記の行です。
    PR2 = 0xfe;
T2CONbits.T2CKPS = 0b00; // Prescaler 0b00: 1, 0b01: 4, 0b10: 16

なぜPR2=0xfeという中途半端な値にしたかというと、PR2=0xffとすると10bit全てを1にしてもdutyが100%にならない挙動になったためです。
    while(1) {
PR2 = 0xff;
T2CONbits.T2CKPS = 0b00; // prescaler 0b00: 1, 0b01: 4, 0b10: 16
CCPR10L = 0xff;
CCP10CONbits.DC10B = 0b11;
delay_ms(waitMs); // なぜか電位が低くなる区間が発生し、dutyが100%にならない
}

データシートで紹介されるPWMで扱えるビットの関係はこのようになっているので、動作周波数が表の40MHzより高い48MHzなのが原因なのかもしれませんが、深くは追えていません。

48MHz駆動で10bitのdutyで100%の出力を作れる記述をご存知でしたら、共有していただけると嬉しいです。

終わり

8bitでPWMのdutyを操作できました。
dutyとして扱えるbitの数設定方法が不可解ですが、PR2 = 0xfe; として、とりあえず実現しました。

参考

PICで省エネ時計を作る
Getting Started with PWM Using CCP on PIC18 (PDF)
PIC16F877A PWM
PIC18F47J53 FAMILY datasheet (PDF)

変更履歴

2022.03.08
要所解説でのPR2への代入値が0xffになっていたので、0xfeに修正しました。

0 件のコメント :