秋月電子などで販売されているSHARP製HR-TFTモノクロメモリ液晶を、直接駆動するためのライブラリです。 図形、文字列の描画が可能です。 日本語のフォントも内包していますが、FlashサイズやRAMサイズが小さいものだと扱いきれないかもしれません。 既存のライブラリとは違い、貴重なSPIポートを占拠しません。DigitalOut端子3本でSPIモドキを実現しています。 結線方法については、デモプログラムのページをご参照ください。

Dependents:   SHARP_MEMORYLCD_WJ

内部画素記憶配列に関する注意

このライブラリでは、ライブラリを通して描かれた全画素の情報ををchar型の一次元配列として記憶します。
故に、小さなRAMしか搭載していないものではOutOfMemoryになり、うまく動作しません

init関数で固まる場合は、memset(R, -1, siz); で配列が初期化できずに止まっている可能性があります。

内部画素記憶配列のサイズは、((表示幅[px] / 8) * 表示高さ[px])[byte]です。
表示幅400px、表示高さ240pxの場合は 12Kbyte(12,000byte) の領域を要します。

シャープ製メモリ液晶直接駆動用ライブラリ

秋月電子などで販売されている、シャープ製のモノクロメモリ液晶を
直接駆動させるためのライブラリとデモプログラムです。

図形や文字列を比較的簡単に表示させることが可能です。
また、メモリに余裕があれば日本語(全角文字)も描画可能です。

文字描画のアルゴリズムは、こちらのページで公開されていた
日本語ビットマップフォント表示アルゴリズムを改変して使用しています。
松浦光洋様、素晴らしいアルゴリズムを公開していただきありがとうございます。
現在ソースコードの公開は停止されている模様です。

また、ビットマプフォントにはM+ BITMAP FONTSで公開されている
Mplus-gothic-medium-R-normal 12pxを使用しています。


使用できる液晶製品

LS027B7DH01Aと同一の駆動方式であれば駆動可能です。

対応済みと思われる製品(テストはLS027B7DH01Aで行っています)

LS012B7DD01とLS013B7DH06は駆動方式が異なるため使用できません。

直接結線して駆動することを前提としてコードを書いているので、
間にSRAMやEEPROMなどを挟んだり、4線式SPIの使用を前提としたものでは動作しません。


デモプログラムのページ(結線方法など)

mbed LPC1768用デモプログラム


描画方式と内部画素記憶配列について

メモリ液晶の描画方式は

  1. ゲートラインアドレス(1から始まる行番号)を選択
  2. 1行あたりの画素数を8画素ごとに区切って連続転送する(幅 / 8の数だけ) というものです。

つまり2値(白黒)の8画素なので、char型(1byte)の配列で画素をコントロールすることが可能です。

一見単純に見えますが、行ごとの更新では更新したくない画素も更新対象に入ってしまいます。
例えば、このような行がある場合、

11111011 11111111

13番目の画素に黒点を打つために「11111111 11110111」のデータを送ると・・・

11111111 11110111 ←6番目の画素も上書きされてしまう

更新したくない画素も更新されてしまいます。

これでは意図した描画が簡単に行えませんので、このライブラリでは現在の画素情報を
char型の1次元配列で保管し(サイズは 幅 / 8 * 高さ)、この記憶情報を更新してから、液晶に
送ることによって図形などの描画を実現しています。
例えば、

11111011 11111111

13番目の画素に黒点を打つために「11111111 11110111」のデータを表示したい・・・

.......11111011 11111111 ←もともと表示されていたデータ
AND)...11111111 11110111 ←表示したいデータと論理積をとる
++++++++++++++++++++++++
.......11111011 11110111 ←液晶に書き込むべき値がでてくる

これで更新すべき値を算出して、液晶に流し込んでいます。

また、この液晶は1が白0が黒です。
これもわかりにくいので、このライブラリ内では1を黒0を白として扱っています。


即時描画(immidiate)について

関数の最後の引数にbool型のimmidiateというものがありますが、これは即時描画を行うかどうかのフラグです。

例えばpixel関数で1個打点したあと、immidiatetrueであればすぐに液晶に反映されます。
immidiatefalseの場合、内部画素記憶配列のみを更新し、液晶には反映しません。

1行だけの反映であれば大した時間はかかりませんが、反映する行が増えれば増えるほど
処理の時間がかかってしまいますので、書きたい情報を全部内部画素記憶配列に書き込んでから
一気にドバっと反映するほうが効率的で処理時間の短縮が見込めます。

なので、文字や四角形や格子模様などを複数行広範囲にわたって書き込む場合はimmidiatefalseにして
すべての描画処理が完了したあとにwriteArrayN関数やwriteArrayA関数を使用して
ドバっと更新するほうがお得です。

ちなみに、各関数のimmidiateのデフォルト値はtrueです。
引数を省略した場合は即時描画されてしまいますのでご注意ください。


COM反転信号

COM反転は、液晶の寿命を延ばすために(焼き付き防止?)人間に見えない速度で液晶を明滅させる仕組みです。
COM反転信号はCOM反転を行うタイミングを液晶に伝達するもので、外部信号と内部信号の2種類があります。
このライブラリでは呼ばれたコンストラクタによって処理を変えており、
引数が3つのコンストラクタで呼ばれたらCOM反転は内部信号を使用します。
引数が5つのコンストラクタで呼ばれたらCOM反転は外部信号を使用します。
(ただし、引数が5つでもDigitalOutができないピンであれば内部信号に切り替わる)

引数が3つのコンストラクタを呼ぶ場合は、EXTCOMの端子VSS(GND)に接続し 引数が5つのコンストラクタを呼ぶ場合は、EXTCOMの端子VDD(3.3-5V)に接続してください。

COM反転信号はTickerを使って自動的に送出される仕組みになっていますが
init関数を呼ぶまではタイマーは作動しません。ユーザーがderegTimer関数でタイマーを解除した場合も、
regTimer関数で再度タイマーを開始するまでは止まったままになります。

また、int main()が終了した場合もSH_MLCD_Jクラスのインスタンスそのものが削除されて
COM反転信号が止まりますので、プログラムの終了を意図的に遅延させるか、プログラム終了後は
速やかに液晶を取り外してください。

SH_MLCD_J.cpp

Committer:
Yajirushi
Date:
2015-12-11
Revision:
0:8512c64be557

File content as of revision 0:8512c64be557:

#include "SH_MLCD_J.h"

//ショートカット
#define SCK_LOW pinSCK->write(0)
#define SCK_HIGH pinSCK->write(1)
#define CS_LOW pinSCS->write(0)
#define CS_HIGH pinSCS->write(1)
#define WAIT_SPI wait_us(SHMLCD_J_SPIWAIT)
#define WAIT_1US wait_us(1)
#define WAIT_3US wait_us(3)

//フォントファイル
#include "MPLUS12.txt"

//コンストラクタ(オーバーロード +3) =================================
SHMLCD_J::SHMLCD_J(PinName vSCK, PinName vSI, PinName vCS, PinName vCOM, PinName vDISP){
    //画面サイズゼロクリア
    _width = 0;
    _height = 0;
    _bytes = 0;

    //使用ピン保存
    pinSCK = new DigitalOut(vSCK);
    pinSI = new DigitalOut(vSI);
    pinSCS = new DigitalOut(vCS);
    pinCOM = new DigitalOut(vCOM);
    pinDISP = new DigitalOut(vDISP);

    //外部COM、外部DISPを使用するかどうか
    useEcom = (vCOM != NC);
    useEdisp = (vDISP != NC);

    //COMフラグ初期化
    comFlag = false;

    //命令送信フラグ初期化
    busySPI = false;
}

//コンストラクタ(オーバーロード +3) =================================
SHMLCD_J::SHMLCD_J(DigitalOut *vSCK, DigitalOut *vSI, DigitalOut *vCS, DigitalOut *vCOM, DigitalOut *vDISP){
    //画面サイズゼロクリア
    _width = 0;
    _height = 0;
    _bytes = 0;

    //使用ピン保存
    pinSCK = vSCK;
    pinSI = vSI;
    pinSCS = vCS;
    pinCOM = vCOM;
    pinDISP = vDISP;

    //外部COM、外部DISPを使用するかどうか
    useEcom = true;
    useEdisp = true;

    //COMフラグ初期化
    comFlag = false;

    //命令送信フラグ初期化
    busySPI = false;
}

//コンストラクタ(オーバーロード +3) =================================
SHMLCD_J::SHMLCD_J(PinName vSCK, PinName vSI, PinName vCS){
    //画面サイズゼロクリア
    _width = 0;
    _height = 0;
    _bytes = 0;

    //使用ピン保存
    pinSCK = new DigitalOut(vSCK);
    pinSI = new DigitalOut(vSI);
    pinSCS = new DigitalOut(vCS);
    pinCOM = new DigitalOut(NC);
    pinDISP = new DigitalOut(NC);

    //外部COM、外部DISPを使用するかどうか
    useEcom = false;
    useEdisp = false;

    //COMフラグ初期化
    comFlag = false;

    //命令送信フラグ初期化
    busySPI = false;
}

//コンストラクタ(オーバーロード +3) =================================
SHMLCD_J::SHMLCD_J(DigitalOut *vSCK, DigitalOut *vSI, DigitalOut *vCS){
    //画面サイズゼロクリア
    _width = 0;
    _height = 0;
    _bytes = 0;

    //使用ピン保存
    pinSCK = vSCK;
    pinSI = vSI;
    pinSCS = vCS;
    pinCOM = new DigitalOut(NC);
    pinDISP = new DigitalOut(NC);

    //外部COM、外部DISPを使用するかどうか
    useEcom = false;
    useEdisp = false;

    //COMフラグ初期化
    comFlag = false;

    //命令送信フラグ初期化
    busySPI = false;
}

//デストラクタ ======================================================
SHMLCD_J::~SHMLCD_J(){
    //念のためタイマー停止
    deregCOMTimer();
    //画素状態記憶用配列を削除
    delete[] R;
    //使用したピン用変数削除
    delete pinSCK;
    delete pinSI;
    delete pinSCS;
    delete pinCOM;
    delete pinDISP;
}

//SPIモドキのコマンド送出 ===========================================
//@モード決定
void SHMLCD_J::send_mode(unsigned char val){
    //モード決定はMSBファーストでsend_dataと同一
    send_data(val);
}
//@ゲートアドレス選択
void SHMLCD_J::send_addr(unsigned char val){
    //アドレスはLSBファースト(ビットの最下位から送る)
    for(char i=0; i<8; i++){
        pinSI->write((val>>i) & 0x01);
        SCK_HIGH;
        WAIT_SPI;
        SCK_LOW;
    }
}
//@データ送出
void SHMLCD_J::send_data(unsigned char val){
    //データはMSBファースト(ビットの最上位から送る)
    for(char i=0; i<8; i++){
        pinSI->write(((val<<i) & 0x80)>>7);
        SCK_HIGH;
        WAIT_SPI;
        SCK_LOW;
    }
}

//初期化 ============================================================
bool SHMLCD_J::init(unsigned short dispWidth, unsigned short dispHeight){
    //既に初期化されているならtrueを返して終わる
    if((_width + _height) > 96) return true;

    //画面サイズが48pixel以下:そんなメモリ液晶は売ってない
    if(dispWidth < 48 || dispHeight < 48) return false;

    //画面の横幅は8で割り切れる数
    if((dispWidth % 8) != 0) return false;

    //縦横、1行あたりのバイト数を保存
    _width = dispWidth;
    _height = dispHeight;
    _bytes = _width >> 3; //_width / 8;

    //画素状態記憶用配列を生成((幅 / 8bit) * 高さ)
    //最大12KByte(400 / 8 * 240 = 12,000)
    //配列はゼロクリアする
    short siz = _bytes * _height;
    R = new char[siz];
    memset(R, -1, siz);

    //DISP以外のすべての信号をLOWに
    pinSCK->write(0);
    pinSI->write(0);
    pinSCS->write(0);
    if(useEcom) pinCOM->write(0);

    //表示オン
    dispON();

    wait_ms(2); //メモリ液晶初期化待ち時間

    //表示クリア
    clear();

    //COM反転信号タイマー起動
    regCOMTimer();

    return true;
}

//表示幅、表示高さを返す ============================================
//@表示幅を返す
unsigned short SHMLCD_J::getWidth(){return _width;}
//@表示高さを返す
unsigned short SHMLCD_J::getHeight(){return _height;}

//表示クリア ========================================================
void SHMLCD_J::clear(){
    //コマンド送出フラグ
    busySPI = true;

    CS_HIGH;
    wait_ms(25);
    send_mode(SHMLCD_J_CLEAR | ((comFlag)? SHMLCD_J_COM : SHMLCD_J_DUMMY));
    send_data(SHMLCD_J_DUMMY);
    wait_ms(25);
    CS_LOW;
    WAIT_1US;

    //コマンド送出フラグ
    busySPI = false;
}

void SHMLCD_J::cls(){ //void clear()と同一
    clear();
}

//内部配列クリア(0xFFで初期化) ======================================
void SHMLCD_J::clearArray(){
    short siz = _bytes * _height;
    memset(R, -1, siz);
}

void SHMLCD_J::cla(){ //void clearArray()と同一
    clearArray();
}

//表示オン、オフ ====================================================
//@表示オン
void SHMLCD_J::dispON(){
    if(useEdisp) pinDISP->write(1);
}
//@表示オフ
void SHMLCD_J::dispOFF(){
    if(useEdisp) pinDISP->write(0);
}

//COM反転 ===========================================================
void SHMLCD_J::invertCOM(){
    //コマンド送出中であればフラグを反転させるだけ
    //コマンド送出がない場合はCSを切り替えて強制的にCOM反転する
    if(busySPI){
        if(useEcom){
            pinCOM->write(1);
            WAIT_1US;
            pinCOM->write(0);
        }
    }else{
        CS_HIGH;
        WAIT_1US;
        if(useEcom){
            pinCOM->write(1);
            WAIT_1US;
            pinCOM->write(0);
        }else{
            send_mode((comFlag)? SHMLCD_J_DUMMY : SHMLCD_J_COM);
            send_data(SHMLCD_J_DUMMY);
        }
        WAIT_3US;
        CS_LOW;
        WAIT_1US;
    }
    comFlag = !comFlag;
}

//COM反転用タイマー登録、解除 =======================================
//@タイマー登録、開始
void SHMLCD_J::regCOMTimer(float tickTime){
    comT.attach(this, &SHMLCD_J::invertCOM, tickTime);
}
//@タイマー停止、解除
void SHMLCD_J::deregCOMTimer(){
    comT.detach();
}

//配列データ更新 ====================================================
//@1行更新
void SHMLCD_J::updateArray1(unsigned char ln, const char *data){
    //ln = 行番号 0から_height-1まで
    //画素状態記憶用配列に*dataの内容を上書き
    ln = (ln >= _height)? _height-1 : ln;

    //データのサイズが_bytesに満たない場合は何もしない
    if((sizeof(data) / sizeof(data[0])) < _bytes) return;

    memcpy(R + (ln * _bytes), data, _bytes);
}
//@複数行更新
void SHMLCD_J::updateArrayN(unsigned char sn, unsigned char en, const char *data){
    //sn = 開始行, en = 終了行
    //画素状態記憶用配列に*dataの内容を上書き
    sn = (sn >= _height)? _height-1 : sn;
    en = (en >= _height)? _height-1 : en;

    //snとenが一緒:1行だけ更新
    if(sn == en){
        memcpy(R + (sn * _bytes), data, _bytes);
        return;
    }

    //snとenが逆:ひっくり返す
    if(sn > en){
        sn ^= en;
        en ^= sn;
        sn ^= en;
    }

    //snが1でenが_height:全部更新
    if((en - sn) == (_height-1)){
        memcpy(R, data, _height * _bytes);
        return;
    }

    //dataのサイズ確認
    unsigned short stidx = sn * _bytes;
    unsigned short len = (en - sn) * _bytes;
    if((sizeof(data) / sizeof(data[0])) < len) return;

    //画素状態記憶用配列に*dataの内容を上書き
    memcpy(R + stidx, data, len);
}
//@全部更新
void SHMLCD_J::updateArrayA(const char *data){
    //画素状態記憶用配列に*dataの内容を上書き
    memcpy(R, data, _height * _bytes);
}

//表示データ書き込み(内部配列を使用する) ============================
//@1行書き込み(内部配列を使用する)
void SHMLCD_J::writeArray1(unsigned char ln){
    //ln = 行番号 0から_height-1まで
    ln = (ln >= _height)? _height-1 : ln;

    //内部配列開始位置
    short idx = ln * _bytes;

    //コマンド送出フラグ
    busySPI = true;

    CS_HIGH;
    WAIT_1US;

    send_mode(SHMLCD_J_WRITE | ((comFlag)? SHMLCD_J_COM : SHMLCD_J_DUMMY)); //モード決定
    send_addr(ln+1); //ゲートライン(行番号 1から_heightまで)選択

    //1行分のデータ連続書き込み
    for(short i=0; i<_bytes; i++){
        send_data(R[idx+i]);
    }

    //転送待ち(16clockダミーを送る)
    send_data(SHMLCD_J_DUMMY);
    send_data(SHMLCD_J_DUMMY);

    WAIT_3US;
    CS_LOW;
    WAIT_1US;

    //コマンド送出フラグ
    busySPI = false;
}
//@複数行書き込み(内部配列を使用する)
void SHMLCD_J::writeArrayN(unsigned char sn, unsigned char en){
    //sn = 開始行, en = 終了行
    sn = (sn >= _height)? _height-1 : sn;
    en = (en >= _height)? _height-1 : en;

    //snとenが一緒:1行だけ書き込み
    if(sn == en){
        writeArray1(sn);
        return;
    }

    //snとenが逆:ひっくり返す
    if(sn > en){
        sn ^= en;
        en ^= sn;
        sn ^= en;
    }

    //内部配列開始位置
    short idx = sn * _bytes;
    int rows = en - sn + 1;

    //コマンド送出フラグ
    busySPI = true;

    CS_HIGH;
    WAIT_1US;

    send_mode(SHMLCD_J_WRITE | ((comFlag)? SHMLCD_J_COM : SHMLCD_J_DUMMY)); //モード決定

    //データ連続書き込み
    sn++; //0スタートを1スタートに
    en++; //0スタートを1スタートに
    for(short i=0, j, k; i<rows; i++){
        send_addr(sn + i); //ゲートライン(行番号 1から_heightまで)選択
        //1行分のデータ連続書き込み
        for(j=0, k = i * _bytes + idx; j<_bytes; j++){
            send_data(R[j+k]);
        }
        //転送待ち(8clockダミーを送る)
        send_data(SHMLCD_J_DUMMY);
    }
    //転送待ち(8clockダミーを送る)
    send_data(SHMLCD_J_DUMMY);

    WAIT_3US;
    CS_LOW;
    WAIT_1US;

    //コマンド送出フラグ
    busySPI = false;
}
//@全部書き込み(内部配列を使用する)
void SHMLCD_J::writeArrayA(){
    writeArrayN(0, _height-1);
}

//表示データ書き込み(内部配列を使用しない) ==========================
//@1行書き込み(内部配列を使用しない)
void SHMLCD_J::write1(unsigned char ln, const char *data){
    //データのサイズが_bytesに満たない場合は何もしない
    if((sizeof(data) / sizeof(data[0])) < _bytes) return;

    //ln = 行番号 0から_height-1まで
    ln = (ln >= _height)? _height-1 : ln;

    //コマンド送出フラグ
    busySPI = true;

    CS_HIGH;
    WAIT_1US;

    send_mode(SHMLCD_J_WRITE | ((comFlag)? SHMLCD_J_COM : SHMLCD_J_DUMMY)); //モード決定
    send_addr(ln+1); //ゲートライン(行番号 1から_heightまで)選択

    //1行分のデータ連続書き込み
    for(short i=0; i<_bytes; i++){
        send_data(data[i]);
    }

    //転送待ち(16clockダミーを送る)
    send_data(SHMLCD_J_DUMMY);
    send_data(SHMLCD_J_DUMMY);

    WAIT_3US;
    CS_LOW;
    WAIT_1US;

    //コマンド送出フラグ
    busySPI = false;

}
//@複数行書き込み(内部配列を使用しない)
void SHMLCD_J::writeN(unsigned char sn, unsigned char en, const char *data){
    //sn = 開始行, en = 終了行
    sn = (sn >= _height)? _height-1 : sn;
    en = (en >= _height)? _height-1 : en;

    //snとenが一緒:1行だけ書き込み
    if(sn == en){
        write1(sn, data);
        return;
    }

    //snとenが逆:ひっくり返す
    if(sn > en){
        sn ^= en;
        en ^= sn;
        sn ^= en;
    }

    //dataのサイズ確認
    unsigned short rows = en - sn + 1;
    unsigned short len = rows * _bytes;
    if((sizeof(data) / sizeof(data[0])) < len) return;

    //コマンド送出フラグ
    busySPI = true;

    CS_HIGH;
    WAIT_1US;

    send_mode(SHMLCD_J_WRITE | ((comFlag)? SHMLCD_J_COM : SHMLCD_J_DUMMY)); //モード決定

    //データ連続書き込み
    sn++; //0スタートを1スタートに
    en++; //0スタートを1スタートに
    for(short i=0, j, k; i<rows; i++){
        send_addr(sn + i); //ゲートライン(行番号 1から_heightまで)選択
        //1行分のデータ連続書き込み
        for(j=0, k=i*_bytes; j<_bytes; j++){
            send_data(data[j+k]);
        }
        //転送待ち(8clockダミーを送る)
        send_data(SHMLCD_J_DUMMY);
    }
    //転送待ち(8clockダミーを送る)
    send_data(SHMLCD_J_DUMMY);

    WAIT_3US;
    CS_LOW;
    WAIT_1US;

    //コマンド送出フラグ
    busySPI = false;
}
//@全部書き込み(内部配列を使用しない)
void SHMLCD_J::writeA(const char *data){
    writeN(0, _height-1, data);
}

//図形描画 ==========================================================
//@打点(1ピクセルのドットを打つ)
void SHMLCD_J::pixel(unsigned short x, unsigned short y, signed char mode, bool immidiate){
    if(x >= _width || y >= _height) return;

    short idx = (_bytes * y) + (x >> 3); //内部配列の場所を特定
    char bitidx = 7 - x % 8; //ビットの位置を特定

    //内部配列更新
    switch(mode){
        case SHMLCD_J_BLACK: //打点(0)
            R[idx] &= ~(1<<bitidx);
            break;
        case SHMLCD_J_ERASE: //消去(1)
            R[idx] |= 1<<bitidx;
            break;
        case SHMLCD_J_INVERT: //反転(-1)
            R[idx] ^= 1<<bitidx;
            break;
    }

    //即時表示の場合は書き込み
    if(immidiate) writeArray1(y);
}
//@線を描画
void SHMLCD_J::drawLine(unsigned short fromX, unsigned short fromY, unsigned short toX, unsigned short toY, signed char mode, bool immidiate){
    if(fromX>=_width || toX>=_width || fromY>=_height || toY>=_height) return;

    short dx = toX - fromX;
    short dy = toY - fromY;
    dx = (dx < 0)? dx * -1 : dx;
    dy = (dy < 0)? dy * -1 : dy;

    //垂直線
    if(dx == 0){
        if(fromY > toY){fromY ^= toY; toY ^= fromY; fromY ^= toY;}//swap

        short idx = (_bytes * fromY) + (fromX >> 3); //内部配列の場所を特定
        char bitidx = 7 - fromX % 8; //ビットの位置を特定

        //内部配列更新
        char bits = 0;
        switch(mode){
            case SHMLCD_J_BLACK: //打点(0)
                bits = ~(1<<bitidx);
                for(; fromY<=toY; fromY++, idx+=_bytes) R[idx] &= bits;
                break;
            case SHMLCD_J_ERASE: //消去(1)
                bits = 1<<bitidx;
                for(; fromY<=toY; fromY++, idx+=_bytes) R[idx] |= bits;
                break;
            case SHMLCD_J_INVERT: //反転(-1)
                bits = 1<<bitidx;
                for(; fromY<=toY; fromY++, idx+=_bytes) R[idx] ^= bits;
                break;
        }

        //即時更新の場合はすぐに表示
        if(immidiate) writeArrayN(toY - dy, toY); //fromY + 1, toY + 1

        return;
    }

    //水平線
    if(dy == 0){
        if(fromX > toX){fromX ^= toX; toX ^= fromX; fromX ^= toX;}//swap

        short idx = (_bytes * fromY) + (fromX >> 3); //内部配列の場所を特定

        //ビットマスク作成のためのシフト量とビット位置を算出
        char stbitRef = fromX % 8;
        char stbitIdx = 7 - stbitRef;
        char enbitIdx = toX % 8;
        char enbitRef = 7 - enbitIdx;

        //ビットマスク作成
        unsigned char stbits = ((0xFF << stbitRef) & 0xFF) >> stbitRef; //演算はintで実施される
        unsigned char enbits = (0xFF >> enbitRef) << enbitRef;

        //塗りつぶす領域が同じ領域内の場合
        if((fromX >> 3) == (toX >> 3)){
            //内部配列更新
            switch(mode){
                case SHMLCD_J_BLACK: //打点(0)
                    R[idx] &= ~(stbits & enbits);
                    break;
                case SHMLCD_J_ERASE: //消去(1)
                    R[idx] |= (stbits & enbits);
                    break;
                case SHMLCD_J_INVERT: //反転(-1)
                    R[idx] ^= (stbits & enbits);
                    break;
            }
        }else{
            //8bit全埋めマスの個数
            char count8 = (dx - (stbitIdx + enbitIdx)) >> 3;

            //内部配列更新
            switch(mode){
                case SHMLCD_J_BLACK: //打点(0)
                    R[idx] &= ~stbits;
                    for(idx++; count8>0; count8--, idx++) R[idx] &= 0x00;
                    R[idx] &= ~enbits;
                    break;
                case SHMLCD_J_ERASE: //消去(1)
                    R[idx] |= stbits;
                    for(idx++; count8>0; count8--, idx++) R[idx] |= 0xFF;
                    R[idx] |= enbits;
                    break;
                case SHMLCD_J_INVERT: //反転(-1)
                    R[idx] ^= stbits;
                    for(idx++; count8>0; count8--, idx++) R[idx] ^= 0xFF;
                    R[idx] ^= enbits;
                    break;
            }
        }

        //即時更新の場合はすぐに表示
        if(immidiate) writeArray1(fromY);

        return;
    }

    //傾きのある直線描画:ブレゼンハムのアルゴリズム(Bresenham's line algorithm)
    short sx = (fromX < toX)? 1:-1, sy = (fromY < toY)? 1:-1;
    short err = dx - dy;
    short e2;

    for(;;){
        pixel(fromX, fromY, mode, immidiate);
        if((fromX == toX) && (fromY == toY)) break;
        e2 = err * 2;
        if(e2 > -dy){
            err -= dy;
            fromX += sx;
        }
        if(e2 < dx){
            err += dx;
            fromY += sy;
        }
    }

}
//@四角形を描画
void SHMLCD_J::drawRect(unsigned short left, unsigned short top, unsigned short width, unsigned short height, signed char mode, bool immidiate){
    if(left>_width || top>_height || width<2 || height<2) return;

    int toX = left + width;
    int toY = top + height;

    if(toX>=_width) toX = _width-1;
    if(toY>=_height) toY = _height-1;

    drawLine(left, top, left, toY, mode, immidiate);
    drawLine(left, top, toX, top, mode, immidiate);
    drawLine(left, toY, toX, toY, mode, immidiate);
    drawLine(toX, top, toX, toY, mode, immidiate);
}
//@対角点指定(左上、右下)での四角形の描画
void SHMLCD_J::drawRect2(unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2, signed char mode, bool immidiate){
    if(x1>=_width || y1>=_height || x2>=_width || y2>=_height) return;

    if(x1>x2){x1^=x2;x2^=x1;x1^=x2;} //swap
    if(y1>y2){y1^=y2;y2^=y1;y1^=y2;} //swap

    if((x2-x1) < 1 || (y2-y1) < 1) return;

    drawLine(x1, y1, x1, y2, mode, immidiate);
    drawLine(x1, y1, x2, y1, mode, immidiate);
    drawLine(x1, y2, x2, y2, mode, immidiate);
    drawLine(x2, y1, x2, y2, mode, immidiate);
}
//@塗りつぶされた四角形を描画
void SHMLCD_J::fillRect(unsigned short left, unsigned short top, unsigned short width, unsigned short height, signed char mode, bool immidiate){
    if(left>_width || top>_height || width<2 || height<2) return;

    int toX = left + width;
    int toY = top + height;

    if(toX>=_width) toX = _width-1;
    if(toY>=_height) toY = _height-1;

    //水平線を高さ分書き込む
    for(; top<=toY; top++) drawLine(left, top, toX, top, mode, immidiate);
}
//@対角点指定(左上、右下)での塗りつぶされた四角形を描画
void SHMLCD_J::fill2Point(unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2, signed char mode, bool immidiate){
    if(x1>=_width || y1>=_height || x2>=_width || y2>=_height) return;

    if(x1>x2){x1^=x2;x2^=x1;x1^=x2;} //swap
    if(y1>y2){y1^=y2;y2^=y1;y1^=y2;} //swap

    if((x2-x1) < 1 || (y2-y1) < 1) return;

    //水平線を高さ分書き込む
    for(; y1<=y2; y1++) drawLine(x1, y1, x2, y1, mode, immidiate);
}
//@円の描画
void SHMLCD_J::drawCircle(unsigned short centerX, unsigned short centerY, unsigned short r, signed char mode, bool immidiate){
    if(centerX>=_width || centerY>=_height || r<1) return;

    //中間点円弧描画アルゴリズム(Bresenham's Midpoint circle algorithm)
    int cx=r, cy=0, err=1-cx;
    while(cx>=cy){
        pixel(centerX+cy, centerY-cx, mode, immidiate); //0:12時から45° 時計回り
        pixel(centerX+cx, centerY-cy, mode, immidiate); //1:3時から45° 反時計回り
        pixel(centerX+cx, centerY+cy, mode, immidiate); //2:3時から45° 時計回り
        pixel(centerX+cy, centerY+cx, mode, immidiate); //3:6時から45° 反時計回り
        pixel(centerX-cy, centerY+cx, mode, immidiate); //4:6時から45° 時計回り
        pixel(centerX-cx, centerY+cy, mode, immidiate); //5:9時から45° 反時計回り
        pixel(centerX-cx, centerY-cy, mode, immidiate); //6:9時から45° 時計回り
        pixel(centerX-cy, centerY-cx, mode, immidiate); //7:12時から45° 反時計回り
        cy++;
        if(err<0) err += cy*2 + 1;
        else err += 2*(cy - --cx + 1);
    }
}
//@塗りつぶされた円の描画
void SHMLCD_J::fillCircle(unsigned short centerX, unsigned short centerY, unsigned short r, signed char mode, bool immidiate){
    if(centerX>=_width || centerY>=_height || r<1) return;

    //中間点円弧描画アルゴリズム(Bresenham's Midpoint circle algorithm)
    int cx=r, cy=0, err=1-cx;
    while(cx>=cy){
        fill2Point(centerX-cy, centerY-cx, centerX+cy, centerY+cx, mode, immidiate);
        fill2Point(centerX-cx, centerY-cy, centerX+cx, centerY+cy, mode, immidiate);
        cy++;
        if(err<0) err += cy*2 + 1;
        else err += 2*(cy - --cx + 1);
    }
}

//文字の描画 ========================================================
//@一文字を描画
signed char SHMLCD_J::drawChar(const char *s, unsigned short x, unsigned short y, unsigned char zoom, signed char mode, bool immidiate){
    if(x>=_width || y>=_height) return -1;

    //戻り値と検出インデックス
    char ret = 0;
    unsigned short cidx = 0;

    //UTF-8 謎の符号反転への対策
    char one = *s & 0xFF;

#ifndef MP12_USE_ZENKAKU //全角文字を使用しない場合
    //フォントのコードの数だけ繰り返す
    for(; cidx<MPF12_TOTAL_CHARS; cidx++){
        //文字コードが合致するものを探す
        if(one == fMPF12[cidx].idx){
            ret = 1;
            break;
        }
    }
#else //全角文字を使用する場合
    if(one<0x20 || one>0x7F){
        //環境依存文字を含まない0x20-0x7Fの領域の外側 = マルチバイト文字の可能性
        for(; cidx<MPJ12R_TOTAL_CHARS; cidx++){
    #if defined MP12_USE_TABLE_UTF8
            if(memcmp(s, &fMPJ12R[cidx].idx[2], 3) == 0){
                ret = 3;
                break;
            }
    #elif defined MP12_USE_TABLE_SJIS
            if(memcmp(s, &fMPJ12R[cidx].idx[0], 2) == 0){
                ret = 2;
                break;
            }
    #endif
        }
    }

    //全角文字を探しても見つからなかった場合、半角文字から検索
    if(ret == 0){
        for(cidx = 0; cidx<MPF12_TOTAL_CHARS; cidx++){
            //文字コードが合致するものを探す
            if(one == fMPF12[cidx].idx){
                ret = 1;
                break;
            }
        }
    }
#endif

    //文字が見つからなかった ret = 0
    if(ret == 0) return ret;

    //描画処理
    if(zoom>1){
        //倍角処理
        short z1 = zoom - 1;
        switch(ret){
            case 1: //半角文字の描画
                for(unsigned char hi=0; hi<MP12_SIZE_H; hi++){
                    char g_cpy = fMPF12[cidx].g[hi];
                    if(g_cpy == 0) continue;
                    for(char wi=0; wi<MP12_SIZE_W_HALF; wi++){
                        if(g_cpy<<wi & 0x80){
                            short gx = x+wi*zoom, gy = y+hi*zoom;
                            fill2Point(gx, gy, gx+z1, gy+z1, mode, immidiate);
                        }
                    }
                }
                break;
            case 2: //全角文字の描画
            case 3:
                unsigned short g_cpy = 0;
                for(unsigned char hi=0; hi<MP12_SIZE_H; hi++){
                    g_cpy = fMPJ12R[cidx].g[hi<<1] << 8 | fMPJ12R[cidx].g[(hi<<1)+1];
                    if(g_cpy == 0) continue;
                    for(char wi=0; wi<MP12_SIZE_W; wi++){
                        if(g_cpy<<wi & 0x8000){
                            short gx = x+wi*zoom, gy = y+hi*zoom;
                            fill2Point(gx, gy, gx+z1, gy+z1, mode, immidiate);
                        }
                    }
                }
                break;
        }
    }else{
        switch(ret){
            case 1: //半角文字の描画
                for(unsigned char hi=0; hi<MP12_SIZE_H; hi++){
                    char g_cpy = fMPF12[cidx].g[hi];
                    if(g_cpy == 0) continue;
                    for(char wi=0; wi<MP12_SIZE_W_HALF; wi++){
                        if(g_cpy<<wi & 0x80){
                            pixel(x+wi, y+hi, mode, immidiate);
                        }
                    }
                }
                break;
            case 2: //全角文字の描画
            case 3:
                unsigned short g_cpy = 0;
                for(unsigned char hi=0; hi<MP12_SIZE_H; hi++){
                    g_cpy = fMPJ12R[cidx].g[hi<<1] << 8 | fMPJ12R[cidx].g[(hi<<1)+1];
                    if(g_cpy == 0) continue;
                    for(char wi=0; wi<MP12_SIZE_W; wi++){
                        if(g_cpy<<wi & 0x8000){
                            pixel(x+wi, y+hi, mode, immidiate);
                        }
                    }
                }
                break;
        }
    }

    return ret;
}
//@倍角指定ありで文字列を描画
signed char SHMLCD_J::writeString(const char *str, unsigned short x, unsigned short y, unsigned char zoom, signed char mode, bool immidiate){
    if(x>=_width || y>=_height || zoom<1) return -1;

    unsigned short org_x = x;

    while(*str){
        //改行処理(\n)
        if(*str == 0x0A){
            x = org_x;
            y += MP12_SIZE_H * zoom;
            str++;
            continue;
        }
        //文字を描画、バイト数を返す
        char charWide = drawChar(str, x, y, zoom, mode, immidiate);
        //バイト数の分だけポインタを進める(半角=1, UTF-8=3, それ以外=2)
        str += (charWide<2)? 1 : charWide;
        x += ((charWide<2)? MP12_SIZE_W_HALF : MP12_SIZE_W) * zoom;
    }

    return 1;
}
//@倍角指定なしで文字列を描画
signed char SHMLCD_J::writeString(const char *str, unsigned short x, unsigned short y, signed char mode, bool immidiate){
    return writeString(str, x, y, 1, mode, immidiate);
}
//@文字列を描画(上記4つの短縮別名)
signed char SHMLCD_J::ws(const char *str, unsigned short x, unsigned short y, unsigned char zoom, signed char mode, bool immidiate){
    return writeString(str, x, y, zoom, mode, immidiate);
}
signed char SHMLCD_J::ws(const char *str, unsigned short x, unsigned short y, signed char mode, bool immidiate){
    return writeString(str, x, y, mode, immidiate);
}