E Ink Spectra 6 パネルを動かす

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つで、毎回同じでした。

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ビットです。行は上から下の順に並びます。
| 色 | コード |
|---|---|
| Black | 0x0 |
| White | 0x1 |
| Yellow | 0x2 |
| Red | 0x3 |
| Blue | 0x5 |
| Green | 0x6 |
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 が待機中に戻ったら、失敗として扱います。書き換えが途中で止まったか、そもそもパネルに電源が入っていなかったということです。書き換え時間はパネルと温度で変わるので、お使いのパネルで測り、余裕を持たせて最小時間を決めてください。
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. の商標です。


