ジャーナル

E Ink Spectra 6 パネルを動かす

C code that powers on a Spectra 6 panel and times its refresh, next to the BUSY signal

E Ink Spectra 6 は、電力なしで画像を保ち続ける6色の電子ペーパーパネルです。小さなマイコンからでも、約20個の SPI コマンドと、状態を知らせるピン1本で動かせます。ここでは LogCase で使っている手順と、その過程でつまずいたことを紹介します。

例として使うのは、横400ピクセル、縦600ピクセルの4インチパネルです。ほかのサイズの Spectra 6 も、解像度が違うだけで手順は同じです。コードは私たちのものを短くした、普通の C99 です。ピンと時間の関数(SCL_H/L, SDA_H/L, CS_H/L, DC_H/L, RST_H/L, BS_L, BUSY_READ, delay_ms)はボードによって異なるので、お使いのピンに合わせて定義してください。

コントローラの役割

どのパネルにも専用のコントローラチップが載っています。コントローラは画像を自分のメモリに保持し、高い駆動電圧をつくり、波形を実行します。波形とは、顔料をそれぞれの位置へ動かすための電圧の段階のことです。波形はパネルメーカーがコントローラに書き込んであるので、自分で触る必要はありません。

ピン

  • BS. ローに固定すると 4線式 SPI になります。ハイのままだと 3線式(9ビット)の SPI として扱われ、このコードは動きません。
  • SCL と SDA. SPI のクロックとデータです。MISO はありません。データ線は1本で双方向ですが、使うのは書き込みだけです。
  • CS. チップセレクトです。1バイトを送る間はローにします。
  • DC. コマンドのバイトではロー、そのあとに続くデータではハイにします。
  • RES#. ハードウェアリセットで、アクティブローです(コードでは RST_L と RST_H)。
  • BUSY. コントローラが処理中の間はロー、待機中はハイになります。

どのメッセージも、コマンド1バイトとそのパラメータでできています。次の関数ですべてのコマンドを送れます。BUSY を待つ関数にはタイムアウトを付けています。SEND() は、このあとのコマンド一覧を短く書くためのものです。

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

/* Bit-banged SPI, MSB first, write only. DC low = command, DC high = data. */
static void spi_byte(uint8_t b) {
    for (int i = 0; i < 8; i++) {
        if (b & 0x80) SDA_H(); else SDA_L();
        SCL_H(); SCL_L();
        b <<= 1;
    }
}

static void epd_cmd(uint8_t c)  { CS_L(); DC_L(); spi_byte(c); CS_H(); }
static void epd_data(uint8_t d) { CS_L(); DC_H(); spi_byte(d); CS_H(); }

static void epd_send(uint8_t cmd, const uint8_t *p, size_t n) {
    epd_cmd(cmd);
    while (n--) epd_data(*p++);
}

/* SEND(command, data...) */
#define SEND(cmd, ...) do { \
    static const uint8_t d_[] = {__VA_ARGS__}; \
    epd_send((cmd), d_, sizeof d_); \
} while (0)

/* BUSY: low = working, high = idle. Returns false on timeout. */
static bool epd_wait_idle(uint32_t timeout_ms) {
    while (timeout_ms--) {
        if (BUSY_READ()) return true;
        delay_ms(1);
    }
    return false;
}

速いマイコンでは、SCL_H() と SCL_L() の間に少し待ちが必要になることがあります。

パネルの電源が切れていると BUSY は浮いた状態になり、待機中と読めてしまうことがあります。これは書き換えが終わったときとまったく同じに見えます。BUSY を信じる前に、パネルに電源が入っていることを確かめてください。線にバイアスをかけるなら、弱いプルダウンにします。電源の入っていないパネルは処理中と読め、待ちはタイムアウトになります。

手順

1回の更新は、リセット、設定、画像の転送、電源オン、書き換えの順に進みます。電源が続くボードでは、そのあとパネルの電源を切ってスリープさせます。

1. リセット

static bool epd_reset(void) {
    BS_L();                    /* 4-wire SPI */
    RST_L(); delay_ms(20);
    RST_H(); delay_ms(120);    /* 20 ms minimum, 120 ms for margin */
    return epd_wait_idle(1000);
}

RES# を 20ms ローにしてから戻します。コントローラがコマンドを受け付けるまで、最低 20ms かかります。私たちは 120ms 待ってから、BUSY を待ちます。

リセットは毎回の更新の最初、画像を送る前に行い、送ったあとには行いません。画像を読み込んだあとにリセットすると、コントローラのメモリの中の画像が壊れます。パネル自体は壊れず、画像を送り直せば元に戻りますが、ハードウェアの故障と見間違えやすい症状です。私たちのパネルでは、画像の上のほうに規則的なパターンで誤ったピクセルが現れました。およそ20ピクセルに1つで、毎回同じでした。

白黒のブロックの Spectra 6 テストパターン。上部が規則的なパターンの誤ったピクセルで崩れている
画像を読み込んだパネルをリセットしたあとの白黒のテストパターン。両方のブロックの上部が、誤ったピクセルで崩れています。

2. コントローラを設定する

static void epd_init_registers(void) {
    SEND(0xAA, 0x49, 0x55, 0x20, 0x08, 0x09, 0x18);   /* unlock        */
    SEND(0x00, 0x5F, 0x69);                           /* panel setting */
    SEND(0x03, 0x00, 0x54, 0x00, 0x44);               /* power-off seq */
    SEND(0x60, 0x02, 0x00);                           /* timing        */
    SEND(0x30, 0x08);                                 /* clock (PLL)   */
    SEND(0x50, 0x3F);                                 /* VCOM, data    */
    SEND(0x61, 0x01, 0x90, 0x02, 0x58);               /* 400 x 600     */
    SEND(0xE3, 0x2F);                                 /* power saving  */
    SEND(0x84, 0x01);                                 /* as given      */
}

これらの値は、最後に挙げたオープンソースのドライバと同じです。0xAA でコマンドセットのロックを解除し、0x61 で解像度を設定します。400 は 0x0190、600 は 0x0258 です。0x84 のように、ほとんど資料のないコマンドもあります。書かれているとおりに送ってください。

確認のためにレジスタを読み返すのはやめましょう。このコントローラでは、レジスタを読むつもりでそのコマンドのバイトを送ると、そのコマンドが実行されます。読むつもりが設定を変えてしまうことがあり、悪いタイミングで送るとパネルが真っ白になることもあります。バスは書き込み専用として扱い、状態は BUSY で確かめてください。

3. 画像を送る

コントローラは1ピクセルを4ビットで受け取ります。1バイトに2ピクセルが入り、左のピクセルが上位4ビットです。行は上から下の順に並びます。

色コード
Black0x0
White0x1
Yellow0x2
Red0x3
Blue0x5
Green0x6
enum { BLACK = 0x0, WHITE = 0x1, YELLOW = 0x2,
       RED   = 0x3, BLUE  = 0x5, GREEN  = 0x6 };   /* 0x4 is not used */

/* Two pixels per byte: the left (even x) pixel in the high nibble. */
static inline uint8_t epd_pack(uint8_t left, uint8_t right) {
    return (uint8_t)(((left & 0x0F) << 4) | (right & 0x0F));
}

/* len = 400 x 600 / 2 = 120,000 bytes */
static void epd_write_image(const uint8_t *buf, size_t len) {
    epd_cmd(0x10);   /* DTM */
    while (len--) epd_data(*buf++);
}

画像全体は 120,000 バイトで、DTM コマンド(0x10)を1回送ったあとに続けて送ります。データはそのままコントローラのメモリに入るので、マイコン側にフレームバッファは要りません。私たちのマイコンは 1KB だけを持ち、残りは順に流し込んでいます。epd_data() は1バイトごとに CS を切り替えますが、コントローラはそれで問題なく受け付けます。写真をこの6色に変換するのは別の作業で、ふつうはパネルにデータを送る前にディザリングで行います。

4. 電源を入れて書き換える

#define MIN_REFRESH_MS 15000   /* ours takes about 19 s at room temperature */

static bool epd_power_on_and_refresh(void) {
    SEND(0x01, 0x3F);                     /* PWR   */
    SEND(0x05, 0x40, 0x1F, 0x1F, 0x2C);   /* BTST1 */
    SEND(0x08, 0x6F, 0x1F, 0x1F, 0x22);   /* BTST3 */
    SEND(0x06, 0x6F, 0x1F, 0x17, 0x17);   /* BTST2 */
    epd_cmd(0x04);                        /* PON   */
    if (!epd_wait_idle(1500)) return false;
    SEND(0x06, 0x6F, 0x1F, 0x17, 0x27);   /* BTST2 */
    SEND(0x12, 0x00);                     /* DRF   */

    uint32_t t = 0;
    while (!BUSY_READ()) {
        if (t++ >= 30000) return false;   /* never finished */
        delay_ms(1);
    }
    return t >= MIN_REFRESH_MS;           /* idle too soon: failed */
}

PWR と3つのブースター設定で電源回路を準備します。BTST3 を BTST2 より先に送るのは、オープンソースのドライバと同じです。PON で電源回路が動き出し、駆動電圧が整うと BUSY がハイになります。私たちのボードでは、1.5 秒あれば必ず足りています。BTST2 を最後のバイトだけ変えてもう一度送り、DRF で書き換えを始めます。

書き換えの間、パネルは何色かに点滅します。4インチのパネルでは約19秒かかり、これは想定どおりの動きです。時間を測ってください。パネルの通常の書き換え時間よりかなり早く BUSY が待機中に戻ったら、失敗として扱います。書き換えが途中で止まったか、そもそもパネルに電源が入っていなかったということです。書き換え時間はパネルと温度で変わるので、お使いのパネルで測り、余裕を持たせて最小時間を決めてください。

4インチのパネルの書き換え。最後の画像が定着するまで、色が点滅しながら変わっていきます。

5. 電源を切ってスリープさせる

static void epd_sleep(void) {
    SEND(0x02, 0x00);   /* POF */
    epd_wait_idle(1000);
    SEND(0x07, 0xA5);   /* DSLP; 0xA5 is a check code */
}

POF で駆動電圧を切り、DSLP でコントローラをディープスリープにします。ディープスリープから戻すには、ふつうはハードウェアリセットを使います。そのため、次の更新はまたステップ1から始まります。どちらの場合も、画像はパネルに残ります。

LogCase はこのステップを省いています。ケースには電池がなく、iPhone の NFC の電磁界から受け取る電力だけで動きます。そのため、書き換えが終わると電力は自然になくなります。

参考資料

オープンソースのドライバ bb_epaper と zy_e_album は同じパネルを駆動していて、ほかのサイズにも対応しています。顔料の動き方については、テクノロジーのページをご覧ください。

E Ink および Spectra は E Ink Holdings Inc. の商標です。