2019年8月10日土曜日

PIC18F27J53でsleepを利用してLチカしてみた


背景

低消費電力で動く何かを作ってみたかったので、低消費電力の時計を作られていた方が使われていたマイコンを利用して、uAレベルでsleepしつつLチカ(LEDの点滅)装置を作ってみました。
Lチカといえども書き込み環境や設定値に悩まされたので、備忘録を兼ねて方法を共有します。

使ったもの

PICマイコンの動作にこれらを利用しました。
  • PIC18F27J53
    RTC(低消費電力で動く時計)を内蔵していて、Deep sleep可能なPICマイコンです。
    データシートなどの詳しい情報はメーカーの製品ページにあります。
  • PICKIt4
    PICマイコンにプログラムを書き込む装置です。
    amazonで買ったコピー品のPICKit3ではエラーが出て書き込めなかったので、秋月電子で純正品を買いました。
  • MPLABXよXC8コンパイラが使えるPC
    PCにMPLABXとXC8コンパイラをインストールしてください。
    Ubuntuの方は自分が書いた記事が参考になると思います。
    Ubuntuに開発環境を作り、PIC16F1829でLチカする方法
  • ブレッドボード
  • 32.768kHz 水晶発振子
  • コンデンサ 10uf, 0.1uf, 12pf
  • ジャンパワイヤ 硬いもの、オスオス
  • LED
  • タクトスイッチ
  • 抵抗

電流量の測定にこれらを利用しました。
(参考: Analog Discovery2を利用して電子部品に流れる電流量を計測してみた
PICマイコンを動かすだけで良ければ、これらは不要です。
  • Analog Discovery2
  • オスメスジャンパワイヤ
  • 1Ω抵抗

回路を作成

これらをするための回路です
  • PICKit4でPICマイコンにプログラムを書き込み
  • PICKit4から3.3V(3.25V)を給電
  • 水晶発震子の振動をPICマイコンで利用
  • PICマイコンでLEDを制御
  • PICマイコンをボタンで制御

回路図はこちらです。


組み立てるとこうなりました。


電流量を測定する場合は、PICKit4からの給電線に1Ωの抵抗を挟み、1Ω抵抗に発生する電圧をAnalog Discovery2で計測します。



プロジェクトを作成

プログラムを書くために、MPLABにプロジェクトをつくり、main.cを作成します。
PIC18F27J53向けにプロジェクトを作りました。


MPLABXでのプロジェクトとmain.cの作り方は過去の記事を参考にしてください。

Ubuntuに開発環境を作り、PIC16F1829でLチカする方法

今回はPICKit4から給電してPICマイコンを動作させます。
そのために、プロジェクトのPICKit4の設定を変更します。

プロジェクトタブのプロジェクトを右クリックしてPropertiesを選択するか、File -> Project Properties を選択して、プロジェクト設定画面を開きます。


設定画面の右側でPICKit4を選び、その後上部のCategoriesタブでPowerを選び、「Power target circuit」をチェックして、OKボタンを押します。
PIC18F27J53は2.0~3.6Vで動作するので、標準で設定されている3.25Vで動作します。


これでプログラムを書き込むと、PICKit4から3.25Vが給電されるようになります。
(プログラムを書き込むまでは、PICKit4から給電されません。)

sleep無しでLチカ

まずはLチカさせて、ボタンとLEDの回路が正しく組めているか確認します。
// 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 int milliseconds) {
    while(milliseconds > 0) {
       __delay_ms(1);
       milliseconds--;
    }
}

void main(void) {
    // RA5 setting
    TRISA5 = 0;
    LATA5 = 0;

    // RB0 setting
    TRISB0 = 1;
    PCFG12 = 1; // Disable analog for AN12(RB0)
    INTCON2bits.RBPU = 0; // Pull up PORTB

    unsigned int waitMs = 1000;
    while(1) {
        if (RB0 == 0) { // Pushed to pull down
            waitMs = 100;
        } else {
            waitMs = 1000;
        }
        LATA5 = 1;
        delay_ms(waitMs);
        LATA5 = 0;
        delay_ms(1000);
    }
}

PIC18F27J53はConfigでタイマーなどの設定をしないと動作してくれないため、Lチカさせるにしてもmain以外にConfigの記述が必要です。
まずはLチカさせて、ボタンとLEDの回路が正しく組めているか確認します。
// 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

LEDを点滅させるだけなのでこれほど速くなくて良いのですが、 参考にした記事の設定値を真似て48MHzで動作させました。
また、delay関数を引数で操作したかったので、引数を渡せない__delay_msを利用してdelay_msを定義しました。
// To use __delay_ms()
#define _XTAL_FREQ 48000000

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

A5の出力とB8の入力をmainのwhileの前に設定しています。
B0はAnalogの12としても利用できるピンなので、PCFG12を1にして、AnalogではなくDigitalとして扱うと宣言しています。
void main(void) {
    // RA5 setting
    TRISA5 = 0;
    LATA5 = 0;

    // RB0 setting
    TRISB0 = 1;
    PCFG12 = 1; // Disable analog for AN12(RB0)
    INTCON2bits.RBPU = 0; // Pull up PORTB

    while(1) {
    }
}

mainのwhile(true)のループで、B0のボタンを押して無ければ1秒間、押していたら0.2秒間、1秒の消灯を挟みつつLEDが光ります。(ボタンを押してなければピッカピッカ、押してたらピカッピカッ。)
void main(void) {
    unsigned int waitMs = 1000;
    while(1) {
        if (RB0 == 0) { // Pushed to pull down
            waitMs = 100;
        } else {
            waitMs = 1000;
        }
        LATA5 = 1;
        delay_ms(waitMs);
        LATA5 = 0;
        delay_ms(1000);
    }
}

PICKit4でプログラムを書き込み、LEDが点滅すれば書き込みとLチカ回路の作成が成功です。
ボタンを押すと、LEDの点滅時間が短くなります。

sleepを使わない場合の電流量は、LED点灯時約14.9mALED消灯時約13.7mAでした。
LEDの点灯に約1.2mAが使われるようでした。


ボタンを押した状態だと、点灯時約15.1mA、消灯時約14mAでした。
BポートのPULLUP機能を利用したボタンを押すと、0.2〜0.3mAの電流が流れるようでした。


sleepを利用してLチカ

水晶発振子をTimer1として利用して、LEDの制御時以外はsleepして消費電力を低くするプログラムを作りました。

// CONFIG1L
#pragma config WDTEN = OFF, PLLDIV = 2, CFGPLLEN = OFF, STVREN = OFF, XINST = OFF
// CONFIG1H
#pragma config CPUDIV = OSC1, CP0 = OFF
// CONFIG2L
#pragma config OSC = INTOSC, 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>

#define LED_BIT LATA5

void toggleLed() {
    LED_BIT = (LED_BIT == 1) ? 0 : 1;
}

void __interrupt() ISR(void) {
    if (PIE1bits.TMR1IE && PIR1bits.TMR1IF) {
        PIR1bits.TMR1IF = 0;
        TMR1L = TMR1L + 0x88;
        TMR1H = TMR1H + 0x88;
        toggleLed();
        if (LED_BIT && (RB0 == 0)) {
            TMR1L = 0xEE;
            TMR1H = 0xEE;
        }
        SLEEP();
    }
}

void main(void) {
    // Pin for crystal oscillator
    OSCCONbits.SCS = 0;
    DSCONLbits.RELEASE = 0;

    // TRISC for crystal oscillator
    TRISC1 = 1;
    TRISC0 = 0;

    // RA5 setting
    TRISA5 = 0;
    LATA5 = 0;

    // RB0 setting
    TRISB0 = 1;
    PCFG12 = 1; // Disable analog for AN12(RB0)
    INTCON2bits.RBPU = 0; // Pull up PORTB

    // Timer1
    T1CON = 0b10001101; // Set bits to use crystal oscillator connected to T1OSC
    PIE1bits.TMR1IE = 1;
    TMR1H = 0xFF;

    // Enable Interrupts with using TIMER1
    INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;

    while(1) {
        SLEEP();
    }
}

要所を解説します。

Timer1として水晶発振子を利用し、Timer1のカウンタが0になったら割り込みを発生させる設定をmainで行っています。
void main(void) {
    // Pin for crystal oscillator
    OSCCONbits.SCS = 0;
    DSCONLbits.RELEASE = 0;

    // TRISC for crystal oscillator
    TRISC1 = 1;
    TRISC0 = 0;

    // Timer1
    T1CON = 0b10001101; // Set bits to use crystal oscillator connected to T1OSC
    PIE1bits.TMR1IE = 1;
    TMR1H = 0xFF;

    // Enable Interrupts with using TIMER1
    INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;
}

割り込み時に呼び出される関数を定義し、タイマーの割り込みだったら、割り込みのビットの再設定、カウンタの初期化、LEDの制御、スリープの呼び出しを行っています。
void __interrupt() ISR(void) {
    if (PIE1bits.TMR1IE && PIR1bits.TMR1IF) {
        PIR1bits.TMR1IF = 0;
        TMR1L = TMR1L + 0x88;
        TMR1H = TMR1H + 0x88;
        toggleLed();
        if (LED_BIT && (RB0 == 0)) {
            TMR1L = 0xEE;
            TMR1H = 0xEE;
        }
        SLEEP();
    }
}

上記の割り込み関数は、Timer1のカウンターが0x0000になったらsleepから復帰して実行されます。
32.768HzでTimer1のカウンタをカウントアップすると0x0000が2秒で0xFFFF(int表記で65535)になります。
カウンタに0xFFFFの半分の値である0x8888を加算しておくと、1秒で起動してくれます。
カウンタは起動する度に0になるので、加算は毎回行う必要があります。
void __interrupt() ISR(void) {
    if (PIE1bits.TMR1IE && PIR1bits.TMR1IF) {
        TMR1L = TMR1L + 0x88;
        TMR1H = TMR1H + 0x88;
    }
}

RB0のボタンを押している場合は、カウンタを0xEEEE(0x8888 + 0x7777)にして、1/8秒だけ点灯させています。
(カウンタに0xEEEEを足すのでは既にカウントアップされているためか、点灯時間が目に見えて短くなってしまいます。)
void __interrupt() ISR(void) {
    if (PIE1bits.TMR1IE && PIR1bits.TMR1IF) {
        if (LED_BIT && (RB0 == 0)) {
            TMR1L = 0xEE;
            TMR1H = 0xEE;
        }
    }
}

状態切替時は約5mA、LED点灯時約約2mA消灯時約0.85mAで動作しました。
LEDの消灯時の電流量だけ見ると、sleepを利用すると1/10以下の電流量で動かせました。
sleepを利用しない場合と同じく、LEDの点灯に約1.2mAが使われるようでした。


ボタンを押していると、点灯時約2.4mA、消灯時約1.2mAで動作しました。
ボタンを押すと約0.4mAの電流が消費されるようでした。
(sleepを使わない場合よりボタンで消費される電流量が0.1mA多くなりましたが、測定器の誤差だと思います。)


まとめ

PICマイコンでsleepを利用して、低消費電力で動作させることができました。

参考にしたサイトでは約1.5uAになるとありましたが、自分の測定環境では0.85mAとなってしまいました。
何か回路かプログラムか測定回路に足りていない部分があるのかもしれませんが、sleepを使うことで1mA以下で動いてくれるので一旦良しとします。

「ここを変えたらもっと低消費電力になる」などの情報がありましたら、コメントなどで共有していただけるととても嬉しいです。

参考

PICで省エネ時計を作る
PIC18F27J53 | Microchip
Why 32.768KHz?

2 件のコメント :

Unknown さんのコメント...

スリープ時にクロックを落として各ポートをハイインピーダンスに設定するとuAオーダーにならないでしょうか?

Asuki Kono さんのコメント...

コメントありがとうございます。
具体的なコードやレジスタの設定値を共有してもらえると嬉しいです。