PIC16F19155で電子ペーパーを動かす(その2)

このシリーズは今回で終わりです。なぜなら、失敗してしまったから。どう失敗なのかは読んでみてください。
前回は、Arduino電子ペーパーを動かすプログラムをPICに移植し、小さな数字を0と1交互に表示するところまでやりました。
alasixosaka.hatenablog.com

今回は、RTCCで割り込みをかけて、自分で作った大きな数字を表示させることをやってみます。

メモリが足りない

数字のデータは、Arduinoのときに作ったのをそのまま使います。
alasixosaka.hatenablog.com
これをArduinoの時と同じように、配列にしてコンパイルをかけようと思ったら、no space for WS_20_30[159] とエラーがでる(WS_20_30は電子ペーパー全体を書き換えるときのLUT)。配列に割り当てるメモリが足りないらしい。数字用の配列を全部コメントアウトするとコンパイルが通るので間違いなくメモリ不足のようだ。コメントアウトする変数を少しずつ減らしていったところ、数字の4までしか割り当てできない。

数字の4まで割り当てるとメモリがほぼ一杯。これ以上割り当てるとエラーになる。

上の図を見ると、データの領域は1kbyteしかないが、プログラム領域は8kwordもある。良い方法はないかと検索すると、配列宣言の前にromを書けば、プログラム領域に確保できるとのこと。こうすると、配列の中身を変更できなくなるが、LUTと数字用のデータなので、プログラムの途中で変える必要はない。
早速やってみたが、コンパイルエラーになる。romというのが文法チェックで引っかかる。また、調べてみるとromと書くのは古いコンパイラーの記述方法のようで、XC8ではconstと書けばよいらしい。これでメモリ不足は解消した。

冒頭の配列を全部プログラム領域に移してメモリ不足を解消

追加の部品はRTCC用のクリスタルとコンデンサ2個。RTCCの実験の時と同じ。
これでプログラムは動いた。
大きな変更点はないので、前回からの変更点のみ。
まず、配列の定義の所で、unsigned char の前にconstをつける。

const unsigned char WS_20_30[159] =
{                      
0x80, 0x66, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x40, 0x0,  0x0,  0x0,
0x10, 0x66, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20, 0x0,  0x0,  0x0,
0x80, 0x66, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x40, 0x0,  0x0,  0x0,
0x10, 0x66, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20, 0x0,  0x0,  0x0,
0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,
以下省略

数字用のデータも同じ。Arduinoの記事でデータは書いたので今回は省略。
割り込み処理ルーチンは実質何もしていない。割り込みフラグをクリアしてリターンするだけ。

void __interrupt() isr(void)
{
    PIR8bits.RTCCIF = 0;
    return;
}

数字を描く処理。

void DispN(int n, int width, int height){
    switch(n){
        case 0:
            SetFrameMemory_Partial(zero, 16, 200, width, height);
            break;
        case 1:
            SetFrameMemory_Partial(one, 16, 200, width, height);
            break;
        case 2:
            SetFrameMemory_Partial(two, 16, 200, width, height);
            break;
        case 3:
            SetFrameMemory_Partial(tre, 16, 200, width, height);
            break;
        case 4:
            SetFrameMemory_Partial(fou, 16, 200, width, height);
            break;
        case 5:
            SetFrameMemory_Partial(fiv, 16, 200, width, height);
            break;
        case 6:
            SetFrameMemory_Partial(six, 16, 200, width, height);
            break;
        case 7:
            SetFrameMemory_Partial(sev, 16, 200, width, height);
            break;
        case 8:
            SetFrameMemory_Partial(eig, 16, 200, width, height);
            break;
        case 9:
            SetFrameMemory_Partial(nin, 16, 200, width, height);
            break;
    }
}

Arduinoの時とほとんど同じ。引き数nで数字をもらって、その数字に応じたデータを表示する。

省電力設定。RTCCとMSPP以外は無効にする。

// SYSCMD SYSCLK disabled; ACTMD ACT disabled; FVRMD FVR disabled; IOCMD IOC disabled; NVMMD NVM disabled; 
    PMD0 = 0xFF;
    // TMR0MD TMR0 disabled; TMR1MD TMR1 disabled; TMR4MD TMR4 disabled; TMR2MD TMR2 disabled; 
    PMD1 = 0xFF;
    // ZCDMD ZCD disabled; DACMD DAC disabled; CMP1MD CMP1 disabled; ADCMD ADC disabled; CMP2MD CMP2 disabled; RTCCMD RTCC enabled; 
    PMD2 = 0x7F;
    // CCP2MD CCP2 disabled; CCP1MD CCP1 disabled; CCP4MD PWM4 disabled; CCP3MD PWM3 disabled; 
    PMD3 = 0xFF;
    // CWG1MD CWG1 disabled; UART2MD EUSART2 disabled; MSSP1MD MSSP1 enabled; UART1MD EUSART disabled; 
    PMD4 = 0xEF;
    // CLC3MD CLC3 disabled; CLC4MD CLC4 disabled; SMT1MD SMT1 disabled; LCDMD LCD disabled; CLC1MD CLC1 disabled; CLC2MD CLC2 disabled; 
    PMD5 = 0xFF;

RTCC関連の設定。これも以前のプログラムと同じ。

    VREGCONbits.VREGPM = 1;
    //__builtin_write_RTCWREN();
    
    RTCCONbits.RTCWREN = 1;

    // Disable RTCC
    RTCCONbits.RTCEN = 0;
    
    RTCCONbits.RTCCLKSEL = 0;
    
    
       // set RTCC time 2022-09-11 21-44-10
    YEAR     = 0x22;   // year
    MONTH    = 0x9;    // month 
    WEEKDAY  = 0x0;    // weekday 
    DAY      = 0x11;    // day
    HOURS    = 0x21;    // hours 
    MINUTES  = 0x44;    // minutes 
    SECONDS  = 0x10;    // seconds 
        
    // set Alarm time 2022-09-11 21-44-10
    ALRMCONbits.ALRMEN = 0;
    // ARPT 0; 
    ALRMRPT = 0x00;
    
    ALRMMTH  = 0x9;  // month 
    ALRMWD   = 0x0; // weekday 
    ALRMDAY  = 0x11;  // day
    ALRMHR   = 0x21; // hours 
    ALRMMIN  = 0x44;  // minutes 
    ALRMSEC  = 0x10;  // seconds 

    // Re-enable the alarm
    ALRMCONbits.ALRMEN = 1;
    
    // AMASK Every 10 Second; CHIME enabled; ALRMEN enabled; 
    ALRMCON = 0xC8;

    // CAL 0; 
    RTCCAL = 0x00;
    RTCCONbits.RTCEN = 1;
    while(!RTCCONbits.RTCEN);

    // Disable write operations on RTCC timer registers
    RTCCONbits.RTCWREN = 0;

    // Clear the RTCC interrupt flag
    PIR8bits.RTCCIF = 0;

    // Enable RTCC interrupt
    PIE8bits.RTCCIE = 1;
    
    INTCONbits.PEIE   = 1 ;             // 周辺装置割り込み有効
    INTCONbits.GIE    = 1 ;             // 全割込み処理を許可する

無限ループ。countを順に足していって、10になったら戻る。countを引数にして、DispNを呼び出して数字を書き換えている。

    while(1){
        DispN(count, width, height);
        DisplayFrame_Partial();
        count++;
        if (count == 10){
            count = 0;    
        }
        //SendCommand(0x10);
        //SendData(0x01);
        SLEEP();
        NOP();
    }

相変わらず、電子ペーパーの起動までは30秒ほどかかるが、とりあえず、文字を大きくして、方向を合わせることができた。
動くと、RTCC割込みで10秒間隔で文字を0から9まで書き換える。
問題はここからで、PICをスリープさせると、消費電流は数十μAレベルまで下げられることがわかったが、問題は電子ペーパーの方。こっちの消費電流が大きいと何をやっているのかわからない。
そこで、電子ペーパーのマニュアルを読んで、電子ペーパー側もスリープに入れることにした。
上の無限ループの中で、コメントアウトしている2行が電子ペーパーをスリープするコマンド(結局働かなかったか?)。
また、PIC側も余分な周辺装置はPMDを使ってRTCCとMSPP以外は止める設定にして、できるだけ消費電流を下げるようにした。

でどのくらいの消費電流か測定しようと思ってやったら、またトラブル。

電流計を繋ぐと動かない

電源をPicKitからからACアダプタ(3.3V)に変更して、電流計(といっても安物の中華製テスター)を繋いで、動かしてみると、ものの見事に動かない。おかしいなと思って、電流計を介さずに直接電源とブレッドボードを繋ぐとちゃんと動く。 どうも安物の中華製テスターが悪さしているらしい。内部抵抗が大きくて電圧降下が大きいせいかもしれない。
そこで、電源を5Vに変えてみると、テスターを繋いだ状態でもちゃんと動いた。
しかし、

消費電流が大きすぎて使い物にならない。

ここまでやって結論はそれかい、というところだが。
実測値がこちら。

スリープ時の消費電流は4.46mA。電池駆動は苦しい。

なんと4.46mAも消費している。ちなみに、電子ペーパーを書き換えるときは、6mAくらいまで上昇。というか、これは単にPICが動いているだけのような気がする。電源が5Vなので、消費電流も大きくなっているが。電源を3.3Vにすると、電子ペーパーが動かない状態で2mAくらいを指す。もっとも電子ペーパーの方が動いていないので、本当に電子ペーパーを動かして3.3Vのときどのくらいの消費電流になっているかわからない。正しい答えを得るためにはもっと精度のいい電流計が必要。いずれにしてもmAオーダーの電流が流れていそう。

スリープから出ると約6mAくらい。

電子ペーパーのデータシートによると、Deep Sleep Modeの電流は2μAとなっているので、どうも電子ペーパーがスリープに入ってないか、スリープ状態でも駆動回路自体が電気を食っているかのどちらかのようだ。
後者の方は、液晶ディスプレイの時も経験した。液晶自体は非常に低消費電力なのだが、例えばI2Cなんかで動かそうとするとI2Cのドライバーチップが電気を食うので、時計のように乾電池で長期間動かすことができない。ならば、電子ペーパーの電源自体を切ってやったらどうか?

電子ペーパーの電源を切ると部分書き換えはできない

必要な電流値は数mAなので、これくらいならPICの端子で直接駆動できる。そこで、電子ペーパーのVCCをPICのデジタル端子につないでスリープに入る前に端子をLowにして電子ペーパー側の電源を切ってみた。
すると、予想通りというか、電源を一度切るともう一度イニシャライズする必要があるようで、こんな感じで汚い画面になってしまった。

節電のため電子ペーパーの主電源を切ってしまうと書き換えができない。

また、WaveShareの公式サイトのFAQに以下のような文が載っていた。
Question:When the ink screen is in deep sleep mode, the first time the screen refreshes will be unclean. How can I solve it?
Answer:The process of re-awakening the e-ink screen is actually the process of re-powering on the power. Therefore, when the EPD wakes up, it must first clear the screen, so as to avoid the afterimage phenomenon to the greatest extent.
これを見ると、電子ペーパーをスリープに入れてしまうと、同じことが起こるらしい。ということは、スリープ命令を出しているのに、実はスリープしてなかったということ???
そして、仮にスリープできたとしても、結局イニシャライズから始める必要があり、イニシャライズのための時間、その時に電子ペーパーが一度全体が黒くなりそして白くなるというチカチカ動作などを考えると、いずれにしても、電子ペーパーで電池駆動の時計を作ろうという今回の試みは失敗という結論。
電池駆動の時計を作ろうと思ったら、結局、ドライバーのないベアの液晶をドライバー回路搭載のPICで駆動するしかなさそう。
ということで、電子ペーパーの記事はとりあえず、今回でおしまいです。まあ、こういう失敗もあるさ。液晶で動く時計を作ってみましょう。
それにしても、この電子ペーパーどう使ったらいいのか? 平均で5mA消費するとして、単三電池がだいたい2000mAhとすると、400時間しか動かない。連続駆動だと16日(約2週間)で電池が切れる。もうちょっと好意的に考えて電圧を3.3Vに下げると電流値が半分に減ると仮定しても、だいたい1か月で電池がなくなる。困りましたね。
時計がダメなら温湿度計あたりかと思っていましたが、表示が変わるたびにディスプレーがチカチカしてもみづらいし。寝ている間に表示を書き換えてくれるカレンダーくらいしか今のところ思いつかない。こんなちっこいカレンダー面白くないし。まあ、なにかアイデアを思い付いたらまた挑戦してみたいと思います。