Baumkuchen’s Workshop

バイオリンと電子工作、DIY、ジョギングなど。

バイオリンタイマーの製作#15-低消費電力化(課題が後から次々)

低消費電力化の実装に手こずっています。

baum-kuchen.hatenablog.com

タスク構成の見直し

まず、システムモード、バックライトモードの状態遷移を割り込み処理で行うことにしました。通常処理で、表示と各モード処理などを行うと、表示に1秒とか掛かる場合は、反応が遅くて使い物になりません。ただ反応が遅いだけならまだしも、タイムアウト処理(スイッチ操作後30秒とか、音無し1分とか)が規定時間通りうまく働かず、状態遷移が機能しないことに(デバッグでも何を確認しているか分からない状態)。

なので、タスク構造を大きく見直して、基本のモード処理を全て割り込みベースにすることしました。

ただし、表示処理は時間が掛かるので、バックグラウンドタスク化して、表示自体は出来るまで待つという方法にしました。

ここで、懸案はフレームオーバーしないかどうかということ。現状の処理時間を測ったところ、システムモードとバックライトモード判定処理は、100μS以下だったのて、3KHzサンプリングでも行けそうです。一方、スタンバイモードの時1MHzクロックでどうかと言うと単純に8倍の時間が掛かるので3KHzはとても無理、よって、1KHzサンプリングとする事に。というのも、スタンバイモードでは、音色判定としてFFTは不要だからです。

タスク構成を図に表すと、こんな感じです。

f:id:Baum_kuchen:20220308185611p:plain

有音判定

次いで、音判定も割り込み化できないかトライ。まず、音判定自体がどの位時間が掛かっているか調べると、最初500msも、rms計算でfloat演算を全部intとlong int処理に変更しても(1024の二乗の256サンプルでLong intに収まるはず)、25msが限度。判定だけなら、閾値を二乗にしておけばいいので、平方根も取ってみたけど大差なしです。なので分りやすい様に、平方根計算は残すことに。25msも掛かるので、割り込み内ででの処理は無理なので、バックブランド処理のまま残すことにしました。割り込みでのモード判定処理との間で、フラグによるやり取りが必要になります。

バックグラウンド側で、割り込みによるAD変換が256データ完了するのを待って、音判定処理開始、終わったら音判定完了フラグを立て、今度は、割り込み側でモード判定処理に使いと言った構造です。

結構、ややこしいタスク構造になってしまいました。

SLEEP時の問題

10分間、音が無いと判断したらスリープモードに遷移します。これは、単にsleep命令(MLAでは、SLEEP()関数)でOKです。

で、問題はスリープからどうやって復活するかです。PICにはいくつかの方法がありますが、ウォッチドッグタイマの使用が一番シンプルでしょうね。タイマからの割り込みと言う手もありますが。以下が、スリープモードを選んだ時の処理です。

            case CPU_SLEEP:
//prepare for sleep
            TFTCS = TFTCD = 0;  //0 for LCD
            TFTRST = 0
            RC0 = RC1 = RC2 = 0;  //0 for CLOCK
//WDT
            di();
            CLRWDT();
            WDTCON = 0x01;           //SWDTEN = 1 for wake up          
            SLEEP();
//wake up sequence
            WDTCON = 0x00;           //SWDTEN = 0    
            ei();
            
            TFTRST = 1;
            break;    

最初の部分は、IO出力を0にして消費電力を抑えるための処理。

あとで、タイマ割り込みが入らないように割り込み禁止とし、ウッチドッグタイマをクリアするために、WDTCLRを行った後、SWDTEN=1でウォッチドッグタイマを有効にしてから、スリープに入ります。

そうすると、予め設定したウォッチドッグタイマの時間経過後に、スリープのコマンドの次からプログラムが再開するという段取りです。

ここまで、出来るようになるまで、結構、試行錯誤をしてます。今のタスク構成上は、この処理が走るのが1KHzのタイマ割り込み中なので、本当は割り込み禁止、許可は不要のハズです。

ウォッチドッグタイマの時間は、CONFIGレジスタのWDTPSで512(=約2秒)に設定する必要があります。

ここまでは、まあ、いろいろ試行錯誤はしたものの、何とかなりましたが、そこからが結構やらしいです。

まず、簡単なところからいうと、スリープ中に一旦、IO=0としたことで、LCDが初期化されます。なので、スリープから復活してもLCDに何も表示されないという問題が。ま、これは、もう一度LCD初期化ルーチンを呼べば済む話なので、単純な話ですが、そうは問屋が卸さずです。割り込みの中で、LCD初期化ルーチンを呼ぶとその中でLCDへのコマンドライト関数を呼びと、どんどん関数のネストが深くなり、ついにはスタックオーバーフローが発生してしまいました。最初は何が起こったか全くわかりませんでしたが、STKPTRレジスタの値を表示させてみて分かりました。原因が分かったのでとりあえずLCDのリセットを発生させないようにして、LCD初期はしないでデバッグしてます。いずれは、改善が必須です。

次の問題は、スタンバイ時そのものの問題です。

スタンバイ時の問題

問題というか、必然というか。低消費電力化のため、8MHz作動を1MHz作動に切り替えると、いろいろ影響が出ます。まずは、処理スピードが1/8になるので、ソフト的に作っていた1msカウンタ、50msカウンタ、1秒カウンタなどが、ことごとくずれます。AD変換のパラメータが変ります。LCD描画速度が激遅になります。等々。

LCD描画は、SPIの速度が律速なのでどうしようもないです。ソフトタイマとAD変換パラメータは、2つの定数を切り替える様にソフト処理をそれぞれ追加しました。

最初にも書いた通り、ADサンプリングも3KHzを1KHzに変更します。なので、256サンプルに、256mSかかり、有音判定も25msだったものが、200ms必要になるので、トータル0.5秒近く必要になります。なので、当初、スリープ中は、実際には2秒スリープ、0.2秒スタンバイの繰り返しのハズが、2秒スリープ、0.5秒スタンバイとなって、低消費電力化の効果激減してしまいます。実際には、0.5秒では終わらないので、効果はもっと落ちます。単純計算で、稼働日数が約5日減少の影響になりそうです。(実測は、まだできてません)

フラッシュメモリ活用

低消費電力化と直接関係は無いですが、どの位バッテリが持つかを測るため、フラッシュメモリに作動時間を記録するように考えました。

使っているPICがプログラムメモリをプログラムから書き込みできるタイプです。

フラッシュメモリのイレーズ、ライト、リードの基本的関数を作ってみました。リードは直ぐできましたが、ライトが動くまで、結構悩みました。

最初は、フラッシュメモリのプロテクト設定です。これも、CONFIGレジスタの設定になりますが、0xF000からの4Kbyte分を書き込み可能とする設定は次の通りで、プログラムが間違っているのか設定が間違っているのか何回もトライしてます。

#pragma config WPFP = PAGE_59       //Write Protect Program Flash Page 59
#pragma config WPEND = PAGE_0       //Start protection at page 0
#pragma config WPCFG = OFF           //Configuration Words page erase/write-protected
#pragma config WPDIS = ON           //WPFP<5:0>/WPEND region erase/write protected

とりあえず、何か書けるようになってからも、何故か、0しか書けません。ちょうど、1Hz毎にタイマ値を書いて、直ぐに読み出して表示するテストプログラムで確認しましたが、全て0です。アドレス設定とか型の不整合なども、何度も見直しましたが、分かりません。で分ったきっかけが、テストプログラムを起動直後から、ずっと表示を見ていると、最初だけ何か数値があり、あとは必ず0になるという動きを発見。最初の数回だけ正しくてあとは動かなくなるモードも考えましたが、ちょっと思いつきません。数字が出るのは、テストプログラムを書き込んだ直後のみ。

で、分かりました。フラッシュメモリの”書く”=”0にする”だからです。イレーズをすると0xFFになり、値を1回書くと、0のbitが0になり値が書き込まれます。全てのbitに0を書いたら、それ以降は1にならない。です。

値を書く前に、イレーズ処理が必要なんでした。

確かに、データシートを良く読めべば書き込みの前にイレーズがされている場合・・とあります。ワード書き込みモードを使ったんですが、これは新しいモードなので、事前のイレーズはいらないのかなと勝手に勘違していました。

ということで、以下の関数が無事動くことが確認できました。ほっ。

/* flash memory */
union {
    unsigned int iw;
    struct {
        unsigned char il,ih;
    };
} fd;

void flash_erase1()
{
    TBLPTRU = 0x00;
    TBLPTRH = 0xF0;
    TBLPTRL = 0x00;
   
    EECON1 = 0b00010100;    //FREE = 1,WREN=1,WR=0  
    di();
    EECON2 = 0x55;
    EECON2 = 0xAA; 
    WR = 1;                 //WR=1
    ei();
    EECON1 = 0b00000000;    //WPROG =0,WREN=0,WR=0
}

void flash_write_word(unsigned char adrs,unsigned int data)
{
    TBLPTRU = 0x00;
    TBLPTRH = 0xF0;
    TBLPTRL = 0x00 + adrs+ adrs;
    fd.iw = data;
    
    TABLAT = fd.il;         //LSB
    __asm("TBLWT*+");
    TABLAT = fd.ih;         //MSB
    __asm("TBLWT*");
    
    EECON1 = 0b00100100;    //WPROG = 1,WREN=1,WR=0  
    di();
    EECON2 = 0x55;
    EECON2 = 0xAA; 
    WR = 1;                 //WR=1
    ei();
    EECON1 = 0b00000000;    //WPROG =0,WREN=0,WR=0
}

unsigned int flash_read_word(unsigned char adrs)
{
    TBLPTRU = 0x00;
    TBLPTRH = 0xF0;
    TBLPTRL = 0x00 + adrs + adrs;   
    
    __asm("TBLRD*+");
    fd.il = TABLAT;
    __asm("TBLRD*+");
    fd.ih = TABLAT;
      
    return fd.iw;
}


ただ、フラッシュメモリの本質的問題は避けて通れません。書き込み回数制限があります。データシートを見ると、10,000回以上とあるではないですか。
ダメです。バッテリ電圧低下時に作動時間が記録されてないとけないので、1秒毎に作動時間をフラッシュメモリに書くつもりでいましたが、そんなことしたら、3時間も経つと書けなくなってしまうということ。書き込み間隔を広げても、実際の運用までに消耗してしまいそう。10,000時間以上の練習時間を記録(そんなに練習できるか!!)しようとすると、1時間に1回しか記録できない勘定です。
ということは、解決策は一つ。バッテリOFFのタイミグで1回のみ、それまでの作動時間を記録するし無い、と考えた次第。
で、Brouwn Out Resetの機能をトライすることになりました。

BOR(Brown Out Reset)活用

バッテリ電圧が下がりBORになったとき、結局はリセットなので、プログラムは最初から動くことになります。そこで、通常のPOR(Power On Reset)と区別をして、フラッシュメモリにデータを書き込むことを行えばいいはずです。

PORかBORかの区別は、RCONレジスタのPOR bit、BOR bitを使えば判別でき、コードとしては次の様になります。これで、PORの時は、フラッシュメモリーから値を復活し作動し、BORの時は、フラッシュメモリーに値を書いたあと、バッテリが死ぬまで無限ループで終了となるはずです。

    if((POR == 0) && (BOR == 0)) {//PORの時
        POR = 1;            //for detetc BOR
        isBOR = 0;
    }
    else {//BORの時
        if(BOR == 0)
            isBOR = 1;  
    }
  
//
    if( isBOR == 1)  {
        stat_to_flash();
        while(1);
    }
    flash_to_stat();

ところが、これもうまくいきません。実際、プログラムがこれでいいのかを確認するのが、なかなか難しいですが、強制的にreset命令をデバッグ用に入れるなどしてみると、ロジックとしては動いているようですが、フラッシュメモリーの値が正しく書けてません。MPLAB Xでメモリを読み出しても、やはり0になっています。今回は、ちゃんとイレーズを入れているのですが。

困ったときは、まず、データシートを良く読めということで、いろいろ読みました。原因はまだ特定できていない状態ですが、ちょっと致命的なことに気づいてしまいました。

データシート上、フラッシュメモリのイレーズ(1Block分)に掛かる時間TIE=33msとあります。つまり、BOR後、最低33ms立たないと、データを書ける状態にならないということです。バッテリが死にかけている状態で、そんなに作動できるのか微妙です。

(2022.3.9追記)実際、DC電源をつないで、だんだん電圧を落としていって、VDDCOREの電圧がどうなるか確認してみました。2.2Vを切ってからVDDCOREが2.0Vとなるまでの時間は、約1.1msでした。やっぱり時間的にも無理ですね。実際には電池の電圧はもっと滑らかに落ちるでしょうが。

もう一つは、フラッシュメモリの書き込み電圧です。VPEW=2.25V(min)とあります。一方、BOR判定の電圧は、VBOR=1.9~2.2Vです。つまり、BORが発生した時の電圧では、フラッシュメモリには書き込みできないということです。

なんと。

構想が見事に破綻しました。

う~ん。

今後の計画

ということで、少し疲れ感、満載です。(いろいろ、勉強にはなりましたが)

さて、どうしようかな。

消費電流低減がどこまでできるかは結果次第ということで、Violin Timer機能本体の仕上げをしますか。バッテリ交換の頻度が上がっても、使えるもの作って、なんぼですからね。

まだ、完成まで、とっ~ても長そうですが、よろしかった、お付き合い願います。