忍者ブログ
 30年ぶりに復活した隊長の電子工作指令本部
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

 

・節電制御とクロッキング
節電の方法についていろいろ考えた結果、下記の制御を盛り込んでいます。

1) 就寝中は、SDC/LCDのの電源をOFFすると共に、PICをSLEEPモードにする。
2) 写真を提示しているだけの時は、PICの主クロックを低電力内臓発振器使用に切換え、
     なおかつIDLEモードにする。
3) 時々、明るさを調べたり、写真を切り替えたりするために、WDTを使ってウェイクアップ
     →用が無ければ、即、SLEEP or IDLE へ戻る。
4) タクトスイッチが押されたら、状態変化割込みでウェイクアップ。
5) PMDレジスタを使い、使っていない周辺モジュールは全て無効化する。

WDTの間隔は、4秒に設定しました。

_FWDT(FWDTEN_OFF & WINDIS_OFF & WDTPRE_PR32 & WDTPOST_PS4096)
尚、クロック切換えは、レジスタ一発書き込みでは行えません。
Cコードから実行するのは難しいので、アセンブラを使って実行します。
また、デバッグモードで起動すると、動作が不安定になるので、_DEBUG で切り分けています
static void SwitchClock(U8 flag)
{
#ifndef _DEBUG
IEC1bits.CNIE = 0;
if (flag) {
// 高速CR発振 PLL付き(FOC=79.2275MHz)
ASM("MOV #0b00000001, w4");
}
else {
// 低電力CR発振(FOC=32.768kHz)
ASM("MOV #0b00000101, w4");
}
// OSCCON への解除シーケンス書き込みと設定     ASM("MOV #OSCCONH, w1");
    ASM("MOV #0x78, w2");
 ASM("MOV #0x9A, w3");
ASM("MOV.b w2, [w1]");
ASM("MOV.b w3, [w1]");
ASM("MOV.b w4, [w1]");
ASM("MOV #OSCCONL, w1");
ASM("MOV #0x46, w2");
ASM("MOV #0x57, w3");
ASM("MOV.b w2, [w1]");
ASM("MOV.b w3, [w1]");
ASM("BSET OSCCON, #0"); while (OSCCONbits.OSWEN);
IEC1bits.CNIE = 1;
#endif 
}
余談ですが、手持ちの dsPIC33FJ256GP710 の高速内臓発振器は、結構な誤差が
あるようです。
フル稼働時は、高速内臓発振器をPLLへ接続し、7.37MHzを、約10倍の 79.2275MHz
にしています。
_FOSCSEL(IESO_ON & FNOSC_FRCPLL)
_FOSC(POSCMD_NONE & OSCIOFNC_ON & FCKSM_CSECMD)

CLKDIVbits.PLLPRE = 0;  // N1:1/2
CLKDIVbits.PLLPOST = 0; // N2:1/2
PLLFBD = 41;            // FOC:79.2275MHz Fcy:39.61375MHz
しかし、どうも、75MHz にも満たないようです。
10倍という事は、誤差も10倍される訳で、元のCR発振に 5%程度の誤差が生じていると
すれば、うなづけなくもないです。
温度でも変化すると思われますので、無理に倍率をあげる事は避けました。

外部クリスタルを使う事は、高くもないし別に大変でもないので、PLLを使う場合には、
精度不要であってもクリスタルにしておいた方が無難ですね。

・ファイルシステム(FatFs)
FatFs では環境に応じて、スペックと必要モリ量のトレードオフを調整できるようになっています。
本作では、TINYモデルを採用し、ライトオペレーションを有効にして、他のファンクションの
シュリンクは行っていません。結構、フルスペックに近いです。

ファイルシステムへのアクセスは、最終的にはSPIのリードライト等になります。
FatFs の diskio.c 内において、自作のSDカードAPIの呼び出しへ変換します。
SDカードAPIは、sdc1.c/sdc2.c に実装してあります。めんどかったので、単純に
ファイルをコピーし、最低限の修正を行って、2つのSPIをサポートしました。

・SPI(microSD)制御
SDの規定である、SPI Mode 0 で制御しています。
    // dsPIC33FJ256GP710
    // SPI Mode0 - 353.69kHz(Fcy:39.61375MHz)
    SPICON1.DISSCK = 0;
    SPICON1.DISSDO = 0;
    SPICON1.MODE16 = 0;
    SPICON1.SMP = 0;
    SPICON1.CKE = 1;
    SPICON1.SSEN = 0;
    SPICON1.CKP = 0;
    SPICON1.MSTEN = 1;
    SPICON1.SPRE = 0b001;
    SPICON1.PPRE = 0b01;
    SPICON2.FRMEN = 0;
    SPISTAT.SPISIDL = 1;
    SPISTAT.SPIROV = 0;
    SPISTAT.SPIEN = 1;
リード処理では、DMAを使用しました。
ただ、手動でも試してみましたが、さほど変わりません。DMAの方が少し早いような気が
するだけです。それだけ、シリアルがネックになっているという事でしょう。
と言いますか、、、DMAは早くなるというより、CPUの手が空く=もっとマルチタスキングに
動作している状況において最大の効果を発揮するものなのですが。
十年以上、DMAをいじる事なんてなかったので、久々にワクワクしたかっただけです。

蛇足ですが、PIC(dsPIC33Fだけ?)のDMAは、DMAxCON.NULLW レジスタの設定により
受信時に 0x00 を自動送信して、SCLKを生成させる事が出来るようですが、誠にもって残念
ながらこれは、SDカードには通用しませんでした。
NULLではなく、0xFFを送信しないとダメなようです。他の方法があるのでしょうか。。
仕方が無いので、受信はDMAにまかせつつ、手動でせっせと0xFFを送信しSCLKを生成
しています。(DMAの使用意義半減)
まあ、どっちみち受信完了を待つので、これによる速度低下は全くありません。
if (nR1 == DATA_START_TOKEN)
{
    // DMA セットアップ
    DMACS1 = 0;
    DMACON = 0x4001; // SIZE:1 DIR:0 HALF:0 AMODE:00 MODE01
    DMAREQ = SPIIRQ;
    DMASTA = (U16)buf - (U16)&_DMA_BASE;
    DMAPAD = (volatile U16)&SPIBUF;
    DMACNT = SDCBLK - 1;
    DMAIE = 1;
    CHEN = 1;
    // SCLKクロック送出
    nCount = SDCBLK;
    do {
        SPIBUF = 0xFF;
        while(SPISTAT.SPITBF);
    }
    while (--nCount > 0);
    // DMA終了待ち
    while (DMAIE);
    // CRC 空読み
    RecvSPI();
    RecvSPI();
}
尚、DMAがアクセスできるデュアルポートメモリは、DMAメモリと呼ばれ、Cプログラムからも
普通にアクセス出来ます。
しかし、実際には普通の変数とは異なるアドレス領域に配置する必要があるため、それなり
の宣言をしてコンパイラに教えてあげる必要があります。
この程度のシステムで、普通のメモリとDMAメモリとの間でコピーを発生させるのも本末転倒
なので、リードバッファ、及びそれを持つ構造体は、全てDMAメモリに配置しました。
FatFs の、オブジェクト(構造体)もその一つです。全部で、2KB の制限内に収まりました。
 static FATFS s_FS1 __attribute__ ((space(dma)));
 U8 g_DmaBuf[SDCBLK] __attribute__ ((space(dma)));
他、ハマった所といえば、SPIxSTAT.SPIEN を、一旦 LOW にしないと周波数変更
されなかった点でしょうか。
暫くの間、SDカードの初期化時の周波数である、400kHz未満(本作では353.69kHz)
のままで駆動していて、libjpeg なんぞを使っているものですから、JPEG のデコードは
やっぱり遅いななどと勘違いし続けていたのでした。
    // 転送速度UP
    SPISTAT.SPIEN = 0;
    SPICON1.PPRE = 0b11;
    //SPICON1.SPRE = 0b100; // Fcy/4=9.9MHz
    SPICON1.SPRE = 0b101; // Fcy/3=13.2MHz
    SPISTAT.SPIEN = 1;

後、データシートでは、10MHzを超えるSPIクロックの設定は、無効と記されています。
しかし、実際にはそれ以上でも動作するようです。
本作では、13.2MHz に設定していますが、それ以上はダメでした。
個体差もあるのかもしれません。

・JPEG表示
特に難しい所はありません。
①幾つかのコールバック関数を登録し、②縮小設定をした後、③jpeg_start_decompress()
を呼び出します。
④続いて、jpeg_read_scanlines()を繰り返し呼び出し、1ライン(画像幅×高さ1px)毎に
LCDへ転送します。

ファイルからの読込みやスキップを行うコールバック関数では、ファイルポインタが常にセクタ
境界となるようにします。
そうしないと、何度も同じセクタをリードする事態が発生し、パフォーマンスが著しく低下する
可能性があります。

METHODDEF(boolean) FillInputBuffer(j_decompress_ptr pObj)
{
    if (s_Src.fok)
    {
        // 効率低下を防ぐ為にセクタ単位で読み込む
        UINT cb;
        if (f_read(&s_Src.file, g_DmaBuf, _MAX_SS, &cb) != FR_OK)
            return FALSE;
        s_Src.jsm.next_input_byte = g_DmaBuf;
        s_Src.jsm.bytes_in_buffer = cb;
        return TRUE;
    }
    return FALSE;
}
jpeg_read_scanlines()は、何ライン取得するかを引数で渡すのですが、MAX(画像高さ)
を渡しても、実際には一度に数ラインづつしか取得できないようです。大抵1とか2、4ライン
ですが、ファイルの内容によると思われます。
本作の環境では、そもそもデコード自体が遅く、メモリ量に余裕がない事から、1ラインづつ
にしています。結果この方が、写真の切り替わりがスムースに見えます。

実際のプログラムでは、LCDのサイズ(320x240)に収まる最大のサイズに、アスペクト比
固定で縮小しますので、上下左右に余りの領域が発生した場合、そこを黒く塗りつぶす
処理と同時進行でラインを描画しています。
    while (s_Obj.output_scanline < s_Obj.output_height)
    {
        int i = 0;
        JDIMENSION nLine = jpeg_read_scanlines(&s_Obj, s_Rows, 1);
        if (nLine == 0)
            return RET_NG;
        do {
            // 左部
            if (rcl.w > 0)
            {
                FillLCD(&rcl, COL_BLACK);
                rcl.y++;
            }
            // 画像
            BlitLCD(&rci, s_Rows[i++]);
            rci.y++;
            // 右部
            if (rcr.w > 0)
            {
                FillLCD(&rcr, COL_BLACK);
                rcr.y++;
            }
        }
        while (--nLine > 0);
    }
ちなみに、libjpegでは、シームレスな縮小が行えるわけではなく、ブロックサイズの整数倍の、
1/2, 1/4, … でしか行えません。その代わり、画質はそれなりに保持されます。
※表示性能を参照して下さい。

後、デコードの前に画質設定を行い、綺麗で遅いか、汚くて早いかを指定する事も可能です。
この表示環境ですから、最初は、汚くて早いを行っていたのですが、試しに、綺麗で遅い
(デフォルト)にしてみても、速さ画質共に違いは感じられませんでした。
なので、デフォルトのままのデコード方法としています。
#ifdef DECOMP_FAST  ←未定義とした
    // 画質設定(品質を落とし速度を上げる)
    s_Obj.two_pass_quantize = FALSE;
    s_Obj.dither_mode = JDITHER_ORDERED;
    if (!s_Obj.quantize_colors)
        s_Obj.desired_number_of_colors = 216;
    s_Obj.dct_method = JDCT_FASTEST;
    s_Obj.do_fancy_upsampling = FALSE;
#endif
・LCD制御 
 電子工作室にもありますが、このLCDの制御は簡単です。
初期化は、殆どサンプルソースのままですが、18ビットモードにするのと、横表示にするための
設定を行いました。
    // Entry Mode  AM=1, I/D[1:0]=01
    WriteCmd(CMD_MODE);
    WriteDat(0x29);
    // Color mode  666 262K color
    WriteCmd(CMD_COLMOD);
    WriteDat(0x06);
    // Inteface mode  18-bit
    WriteCmd(CMD_IFMOD);
    WriteDat(0x04);
ノーウェイトでライト出来ています。
リードですが、PICのポートをRDピンへ接続はしたものの、結局使っていません。

18ビットのうち、2ビットの出力の仕方として、1ピンづつI/Oしていますので、ちょっと食えま
せんが。。これだけポートが余っているので、本来はどれか丸ごと16ビット空けて、
サクっと mov させた方がよいでしょう。
その代わり、小面積の片面基板ではジャンパーが増える事になります。多分。

後、サンプルプログラムでは、表示をONする直前に、80ms の delay が挿入されていますが、
これは、位置の誤りのような気がします。
このLCDは、表示をONしても直ぐに表示が行われるわけではなく、ちょっとだけ遅れて表示
されます。なので、本作では、表示をONにした後で delay するようにしています。

・ファイルのコピー

ファイルシステム越しに、1セクタ分ずつ読んでは書いてを繰り返すだけです。
一回のリードライト毎に、メインループに制御を戻すため、コピーの間でも、SW入力等の他の
処理へ移れるようになっています。

・BEEPやLEDのブリンク
当然の如く dsPIC33F には PWM が載っていますが、面倒くさいので、2つの16ビットタイマと、
1つの32ビットタイマ割込みを使って簡単に済ませています。  

 

拍手[0回]

PR
この記事へのトラックバック
この記事にトラックバックする:
隊長

ブログ内検索

Copyright © [ The 電子工作 ] All rights reserved.
Special Template : 忍者ブログ de テンプレート and ブログアクセスアップ
Special Thanks : 忍者ブログ
Commercial message : [PR]