一般顯示器都需要接駁電源才能顯示,如果要外出仍能使用便需要帶著電池
在下發現電子紙這種電子裝置是一種只使用非常低電功耗便可以改變內容,在沒有供應電壓的情況下仍然可以維持最後變動的內容
電子紙的更新速度比傳統顯示器慢得多,平均只有 3 FPS 更新速度,比起傳統監視器 12 FPS 更新速度更慢
由於更新速度慢,比較適合不需要即時更新的內容,例如電子書及一些指示牌
電子紙應用比較出名就是 Amazon Kindle 電子書,是一種非常低電功耗的電子書,只需要在更新頁面資料才需要給予電源
在下的電子紙是使用 SPI技術 來控制顯示內容
SPI 全名是 Serial Peripheral Interface 中文是 序列周邊介面 來控制
SPI 與 I2C 相似,都是能夠減少引腳使用數量
SPI 訊號
| 引腳 | 初始化 | 訊號週期 | ||
|---|---|---|---|---|
| 位元位 | 結束 | |||
| 延遲 | 2微秒 | 2微秒 | ||
| SS | 高 | 低 | 高 | |
| SCK | 高 | 低 | 高 | 高 | 
| MOSI | 高/低 | |||
| MISO | 高/低 | |||
訊號波紋時序圖
Arduino 已經提供 SPI函式庫 ,在 Arduino 程式標頭加上 #include <SPI.h> 便可以使用 SPI函式庫
Arduino UNO Rev3 預設使用
| Arduino UNO Rev3 引腳 | SPI 引腳 | 
|---|---|
| 10 | SS | 
| 13 | SCK | 
| 11 | MOSI | 
| 12 | MISO | 
#include <SPI.h>
void setup() {
    SPI.begin();
    SPI.transfer(0x00);
}
void loop() {
}以上是最簡單透過 Arduino 的 SPI函式庫,傳輸 0x00 資料到目標裝置SPI 模式
SPI 有 4種模式| 模式 | 時脈閒置電壓 | 資料採取電壓 | 
|---|---|---|
| 0 | 低 | 低 | 
| 1 | 低 | 高 | 
| 2 | 高 | 高 | 
| 3 | 高 | 低 | 
SPI 次序
SPI 可以分- 由 最高有效位 至 最低有效位 ,即是資料由 第7位元 至 第0位元 傳送
- 由 最低有效位 至 最高有效位 ,即是資料由 第0位元 至 第7位元 傳送
在 Arduino 可以使用
#include <SPI.h>
void setup() {
    SPI.begin();
    SPI.setBitOrder(MSBFIRST);
//    SPI.setBitOrder(LSBFIRST);
    SPI.setDataMode(SPI_MODE0);
//    SPI.setDataMode(SPI_MODE1);
//    SPI.setDataMode(SPI_MODE2);
//    SPI.setDataMode(SPI_MODE3);
    SPI.transfer(0x00);
}
void loop() {
}同樣使用 SPI技術,但不同裝置的 模式 及 次序 會有分別,需要了解目標裝置的運作原理才能正確應用 SPI
由於使用 Arduino 的 SPI函式庫,限制必須使用指定的 SS, SCK, MOSI, MISO 4支引腳
而在下希望能隨意改動需要使用的引腳,不過發現網上的資源都是使用 Arduino 的 SPI函式庫
亦不見可以自訂引腳的 SPI函式庫,因此在下自行編寫一套函式庫
SPIModule.h
#ifndef SPIMODULE_H
#define SPIMODULE_H
#include <Arduino.h>
class SPIModule {
    private:
        byte ssPin;
        byte mosiPin;
        byte misoPin;
        byte sckPin;
        void declare(byte, byte, byte, byte);
    public:
        SPIModule();
        SPIModule(byte, byte, byte, byte);
        void initial();
        byte transfer(byte);
};
#endifSPIModule.cpp
#include "SPIModule.h"
void SPIModule::declare(byte ssPin, byte mosiPin, byte misoPin, byte sckPin) {
    this->ssPin = ssPin;
    this->mosiPin = mosiPin;
    this->misoPin = misoPin;
    this->sckPin = sckPin;
}
SPIModule::SPIModule() {
    this->declare(SS, MOSI, MISO, SCK);
}
SPIModule::SPIModule(byte ssPin, byte mosiPin, byte misoPin, byte sckPin) {
    this->declare(ssPin, mosiPin, misoPin, sckPin);
}
void SPIModule::initial() {
    pinMode(this->ssPin, OUTPUT);
    digitalWrite(this->ssPin, HIGH);
    pinMode(this->mosiPin, OUTPUT);
    pinMode(this->misoPin, INPUT);
    pinMode(this->sckPin, OUTPUT);
    digitalWrite(this->sckPin, HIGH);
}
byte SPIModule::transfer(byte data) {
    byte response = 0;
    digitalWrite(this->ssPin, LOW);
    for (byte i = 0x80; i > 0; i >>= 1) {
        digitalWrite(this->sckPin, LOW);
        digitalWrite(this->mosiPin, ((data & i) > 0) ? HIGH : LOW);
        digitalWrite(this->sckPin, HIGH);
        response |= ((digitalRead(this->misoPin) > LOW) ? i : 0);
    }
    digitalWrite(this->ssPin, HIGH);
    return response;
}不知道閣下有沒有留意 SPIModule::transfer 在哪裡似層相識,就是在下編寫 PlayStation::sendCommand 功能
當時在下還未學習 I2C 及 SPI ,只是參巧其他 PlayStation 訊號分析文獻
但在下學習使用 電子紙 還需要使用 SPI 時發現 SPI 的 transfer 與 sendCommand 基本上是相同
因此在下估計 PlayStation 手掣的晶片都是其中一種使用 SPI 的晶片
電子紙


電子紙 正面


電子紙 背面
在下的 電子紙 為 4.2寸 ,解像度 闊 為 400 , 高 為 300

電子紙 透過 柔性電路板 (Flexible Curcuit Board (FCB)) 連接到 電子紙模組

電子紙 使用 YF08E晶片 是其中一種 SPI晶片
YF08E引腳功能
| 後 | 引腳20 | 引腳19 | 引腳18 | 引腳17 | 引腳16 | 引腳15 | 引腳14 | 引腳13 | 引腳12 | 引腳11 | 
|---|---|---|---|---|---|---|---|---|---|---|
| 前 | 引腳1 | 引腳2 | 引腳3 | 引腳4 | 引腳5 | 引腳6 | 引腳7 | 引腳8 | 引腳9 | 引腳10 | 
| 描述 (缺口左邊) | B1 | VCC(B) | B2 | B3 | B4 | B5 | B6 | B7 | B8 | GND | 
| A1 | VCC(A) | A2 | A3 | A4 | A5 | A6 | A7 | A8 | OE | 
| 引腳編號 | 描述 | 方向 | 用途 | 
|---|---|---|---|
| 1 | A1 | 輸入輸出 | 資料,第7位元;連接到目標裝置(A)引腳 | 
| 2 | VCC(A) | 電源(A) ,接受 1.2V 至 3.6V , VCC(A) <= VCC(B) | |
| 3 | A2 | 輸入輸出 | 資料,第6位元;連接到目標裝置(A)引腳 | 
| 4 | A3 | 輸入輸出 | 資料,第5位元;連接到目標裝置(A)引腳 | 
| 5 | A4 | 輸入輸出 | 資料,第4位元;連接到目標裝置(A)引腳 | 
| 6 | A5 | 輸入輸出 | 資料,第3位元;連接到目標裝置(A)引腳 | 
| 7 | A6 | 輸入輸出 | 資料,第2位元;連接到目標裝置(A)引腳 | 
| 8 | A7 | 輸入輸出 | 資料,第1位元;連接到目標裝置(A)引腳 | 
| 9 | A8 | 輸入輸出 | 資料,第0位元;連接到目標裝置(A)引腳 | 
| 10 | OE | 輸入 | |
| 11 | GND | 接地 | |
| 12 | B8 | 輸入輸出 | 資料,第0位元;連接到目標裝置(B)引腳 | 
| 13 | B7 | 輸入輸出 | 資料,第1位元;連接到目標裝置(B)引腳 | 
| 14 | B6 | 輸入輸出 | 資料,第2位元;連接到目標裝置(B)引腳 | 
| 15 | B5 | 輸入輸出 | 資料,第3位元;連接到目標裝置(B)引腳 | 
| 16 | B4 | 輸入輸出 | 資料,第4位元;連接到目標裝置(B)引腳 | 
| 17 | B3 | 輸入輸出 | 資料,第5位元;連接到目標裝置(B)引腳 | 
| 18 | B2 | 輸入輸出 | 資料,第6位元;連接到目標裝置(B)引腳 | 
| 19 | VCC(B) | 電源(B),接受 1.65V 至 5.5V | |
| 20 | B1 | 輸入輸出 | 資料,第7位元;連接到目標裝置(B)引腳 | 


左端有一排 8支引腳 的 JST插口 (全名為 Japan Solderless Terminals)

電子紙公司會附送一條 JST線

JST線 另一端為 2.54mm引腳插口

通過 JST線 便可以接駁到 微控制器

右端有一排 8個通孔 ,需要自行焊接引腳
(不附送引腳)

焊接引腳則可以直接接駁到 麵包板
電子紙引腳功能
| 引腳編號 | 描述 | 方向 | 用途 | 
|---|---|---|---|
| 1 | VCC | 電源 | |
| 2 | GND | 接地 | |
| 3 | DIN | 輸入 | 資料輸入 | 
| 4 | CLK | 輸入 | 時脈訊號 | 
| 5 | CS | 輸入 | 晶片選擇,低電壓 為 選擇,高電壓 為 不選擇 | 
| 6 | DC | 輸入 | 模式切換,低電壓 為 指令模式,高電壓 為 資料模式 | 
| 7 | RST | 輸入 | 重置所有設定 | 
| 8 | BUSY | 輸出 | 回應狀態,低電壓 為 忙碌,高電壓 為 閒置 | 
電子紙引腳對應SPI用途
| 電子紙 SPI 引腳名稱 | 原 SPI 引腳名稱 | 用途 | 
|---|---|---|
| CS | SS | Slaver 選擇 | 
| CLK | SCK | 序列時脈 | 
| DIN | MOSI | Master 寫入資料到 Slaver | 
| - | MISO | Master 讀取 Slaver 資料 | 

SPI 可以選擇 3線模式 或 4線模式
電子紙預設將一塊 SMD 0Ω 的電阻焊接在 0 的位置,即是使用 4線模式
SPI 可以只使用 3支引腳 即是 VCC, GND, MOSI 便能夠讓 SPI Master 控制 SPI Slaver
而完整功能亦只需要 4支引腳 除了基本 3支引腳 ,還有 MISO ,讓 SPI Master 接收 SPI Slaver 的訊號
電子紙暫存器
與 HD44780 相似,電子紙都有 暫存器 儲存著不同指示資料,而且資料數量比 HD44780 更多| DC | RW | 第n位元 (* 不限制) | 用途 | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | |||
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 板面設定 | 
| 1 | 0 | # | # | # | # | # | # | # | # | RES[1:0],REG,KW/R,UD,SHL,SHD_N,RST_N | 
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 電源設定 | 
| 1 | 0 | - | - | - | - | - | - | # | # | VDS_EN,VDG_EN | 
| 1 | 0 | - | - | - | - | - | # | # | # | VCOM_HV,VGHL_LV[1:0] | 
| 1 | 0 | - | - | # | # | # | # | # | # | VDH[5:0] | 
| 1 | 0 | - | - | # | # | # | # | # | # | VDL[5:0] | 
| 1 | 0 | - | - | # | # | # | # | # | # | VDHR[5:0] | 
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 關閉電源 | 
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 關閉電源順序設定 | 
| 1 | 0 | - | - | # | # | - | - | - | - | T_VDS_OF | 
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 啟動電源 | 
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 啟動電源測量 | 
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 助推器軟啟動 | 
| 1 | 0 | # | # | # | # | # | # | # | # | BT_PHA[7:0] | 
| 1 | 0 | # | # | # | # | # | # | # | # | BT_PHB[7:0] | 
| 1 | 0 | - | - | # | # | # | # | # | # | BT_PHC[5:0] | 
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 深度睡眠 | 
| 1 | 0 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | Check code | 
| 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 顯示開始傳送(黑) | 
| 1 | 0 | # | # | # | # | # | # | # | # | KPXL[1:8] | 
| 1 | 0 | - | - | - | - | - | - | - | - | - | 
| 1 | 0 | # | # | # | # | # | # | # | # | KPXL[n-1:n] | 
| 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 資料停止 | 
| 1 | 1 | # | - | - | - | - | - | - | - | KPXL[1:8] | 
| 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 顯示器重新整理 | 
| 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 顯示開始傳送(紅) | 
| 1 | 0 | # | # | # | # | # | # | # | # | KPXL[1:8] | 
| 1 | 0 | - | - | - | - | - | - | - | - | - | 
| 1 | 0 | # | # | # | # | # | # | # | # | KPXL[n-1:n] | 
| 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | VCOM LUT | 
| 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | W2W LUT | 
| 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | B2W LUT | 
| 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | W2B LUT | 
| 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | B2B LUT | 
| 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 鎖相迴路控制 | 
| 1 | 0 | - | - | # | # | # | # | # | # | M[2:0],N[2:0] | 
| 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 溫度感應器校準 | 
| 1 | 1 | # | # | # | # | # | # | # | # | LM[10:3]/TSR[7:0] | 
| 1 | 1 | # | # | # | - | - | - | - | - | LM[2:0]/- | 
| 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 溫度感應器選取 | 
| 1 | 0 | # | - | - | - | # | # | # | # | TSE,TO[3:0] | 
| 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 溫度感應器寫入 | 
| 1 | 0 | # | # | # | # | # | # | # | # | WATTR[7:0] | 
| 1 | 0 | # | # | # | # | # | # | # | # | WMSB[7:0] | 
| 1 | 0 | # | # | # | # | # | # | # | # | WLSB[7:0] | 
| 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 溫度感應器讀取 | 
| 1 | 1 | # | # | # | # | # | # | # | # | RMSB[7:0] | 
| 1 | 1 | # | # | # | # | # | # | # | # | RLSB[7:0] | 
| 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | CDI | 
| 1 | 0 | # | # | # | # | # | # | # | # | VBD[1:0],DDX[1:0],CDI[3:0] | 
| 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 低功耗檢測 | 
| 1 | 1 | # | - | - | - | - | - | - | - | LPD | 
| 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | TCON | 
| 1 | 0 | # | # | # | # | # | # | # | # | S2G[3:0],G2S[3:0] | 
| 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 解像度設定 | 
| 1 | 0 | - | - | - | - | - | - | - | # | HRES[8:3] | 
| 1 | 0 | # | # | # | # | # | 0 | 0 | 0 | HRES[8:3] | 
| 1 | 0 | - | - | - | - | - | - | - | # | VRES[8:0] | 
| 1 | 0 | # | # | # | # | # | 0 | 0 | 0 | VRES[8:0] | 
| 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | GSST | 
| 1 | 0 | - | - | - | - | - | - | - | # | HRES[8:3] | 
| 1 | 0 | # | # | # | # | # | 0 | 0 | 0 | HRES[8:3] | 
| 1 | 0 | - | - | - | - | - | - | - | # | VRES[8:0] | 
| 1 | 0 | # | # | # | # | # | 0 | 0 | 0 | VRES[8:0] | 
| 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 顯示狀態 | 
| 1 | 1 | - | # | # | # | # | # | # | # | PTL_FLAG,I C_BUSY,DATA_FLAG,PON,POF,BUSY | 
| 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 自動測量VCom | 
| 1 | 0 | - | - | # | # | # | # | # | # | AMVT[1:0],XON,AMVS,AMV,AMVE | 
| 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 讀取VCom | 
| 1 | 1 | - | - | # | # | # | # | # | # | VV[5:0] | 
| 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | VCM-DC | 
| 1 | 0 | - | - | # | # | # | # | # | # | VV[5:0] | 
| 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 部分視窗 | 
| 1 | 0 | - | - | - | - | - | - | - | # | HRST[8:3] | 
| 1 | 0 | # | # | # | # | # | 0 | 0 | 0 | HRST[8:3] | 
| 1 | 0 | - | - | - | - | - | - | - | # | HRED[8:3] | 
| 1 | 0 | # | # | # | # | # | 1 | 1 | 1 | HRED[8:3] | 
| 1 | 0 | - | - | - | - | - | - | - | # | VRST[8:0] | 
| 1 | 0 | # | # | # | # | # | # | # | # | VRST[8:0] | 
| 1 | 0 | - | - | - | - | - | - | - | # | VRED[8:0] | 
| 1 | 0 | # | # | # | # | # | # | # | # | VRED[8:0] | 
| 1 | 0 | - | - | - | - | - | - | - | # | PT_SCAN | 
| 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 部分輸入 | 
| 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 部分輸出 | 
| 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 編程模式 | 
| 1 | 0 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | Check code | 
| 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 主動編程 | 
| 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 讀取臨時密碼 | 
| 1 | 1 | - | - | - | - | - | - | - | - | Read Dummy | 
| 1 | 1 | # | # | # | # | # | # | # | # | Data of Address | 
| 1 | 1 | - | - | - | - | - | - | - | - | Read Dummy | 
| 1 | 1 | # | # | # | # | # | # | # | # | Data of address | 
| 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 慳電模式 | 
| 1 | 0 | # | # | # | # | # | # | # | # | VCOM_W[3:0],SD_W[3:0] | 
電子紙啟動次序
同樣與 HD44780 相似,列表中 DC 為 0 是 指示暫存器 , DC 為 1 是 資料暫存器指示暫存器 跟後有時會有一組 資料暫存器,這組資料就是用作設定 指示 的 資料
雖然 指示暫存器 有眾多不同功能的 指示
但實際要啟動 電子紙 只需要執行 助推器軟啟動 及 啟動電源
執行 助推器軟啟動 指示 後,還需要提交 0x17, 0x17, 0x17 3組 資料
及 啟動電源 ,不過 啟動電源 則不需要提交資料
#include "SPIModule.h"
SPIModule spiModule = SPIModule();
const byte DC_PIN = 9;
const byte RST_PIN = 8;
const byte BUSY_PIN = 7;
void setup() {
    spiModule.initial();
    pinMode(DC_PIN, OUTPUT);
    pinMode(RST_PIN, OUTPUT);
    digitalWrite(RST_PIN, HIGH);
    pinMode(BUSY_PIN, INPUT);
    reset();
    // 助推器軟啟動
    sendCommand(0x06);
    sendData(0x17);
    sendData(0x17);
    sendData(0x17);
    // 啟動電源
    sendCommand(0x04);
}
void loop() {
}
void reset() {
    digitalWrite(RST_PIN, LOW);
    digitalWrite(RST_PIN, HIGH);
}
void sendCommand(byte data) {
    digitalWrite(DC_PIN, LOW);
    spiModule.transfer(data);
}
void sendData(byte data) {
    digitalWrite(DC_PIN, HIGH);
    spiModule.transfer(data);
}啟動電子紙後便可以將資料寫入到電子紙來改變顯示的內容
電子紙每個像素都以 1位元 表達,該像素需要 顯示顏色時為 0 ,不顯示顏色為 1
但電子紙不是以 布林值 (boolean 或 bool) 來控制像素,而是以 位元組 (byte 或 unsigned char) 來控制
由於 1位元組 = 8位元 ,因此每次寫入資料都會以 1位元組 來控制 8格像素 ,亦所以電子紙 闊度 都是 8倍數 ,高度 則不限

像素是由電子紙左上角位置開始排列,並以 MSB 次序顯示
即是左上角的像素顯示顏色便要寫入 0x00 (主要是不顯示 0x80 的資料)
寫入資料後,便需要執行 顯示器重新整理 的 指示 ,即是 0x12
但 執行 顯示器重新整理 後,電子紙 會處於 忙碌狀態 ,需要等待回復到 閒置狀態 才能再 執行指示 或 寫入資料
當 BUSY引腳 是 低電壓 為 忙碌狀態,高電壓 為 閒置狀態
// 顯示開始傳送(黑)
sendCommand(0x10);
// 寫入資料
sendData(0x00);
// 顯示器重新整理
sendCommand(0x12);
while (digitalRead(BUSY_PIN) == LOW) {
    // 有需要可以顯示狀態變化
}
資料會由 左上角 至 右下角 順序寫入
當連續寫入資料,都會在第一列顯示
// 顯示開始傳送(黑)
sendCommand(0x10);
// 寫入資料
for (byte i = 0; i < 8; i++) {
    sendData(0x00);
}
// 顯示器重新整理
sendCommand(0x12);
while (digitalRead(BUSY_PIN) == LOW) {
    // 有需要可以顯示狀態變化
}但如果只是變動電子紙一小部分位內容卻要將整個畫面更新是非常浪費資源及時間
因此 電子紙 的 指示暫存器 提供指定範圍
- 執行指示 部分輸入 0x91
- 執行指示 部分視窗 0x90
- 設定部分視窗的資料,共8位元組
 - 設定資料 水平開始位置 (闊度 超過 248像素使用,只接受 第0位元,其餘位元忽視)
- 設定資料 水平開始位置 (第2, 1, 0位元 必定為 0 ,限制必定為 8n)
- 設定資料 水平結束位置 (闊度 超過 248像素使用,只接受 第0位元,其餘位元忽視)
- 設定資料 水平結束位置 (第2, 1, 0位元 必定為 1 ,限制必定為 8n + 7)
- 設定資料 垂直開始位置 (高度 超過 248像素使用,只接受 第0位元,其餘位元忽視)
- 設定資料 垂直開始位置
- 設定資料 垂直結束位置 (高度 超過 248像素使用,只接受 第0位元,其餘位元忽視)
- 設定資料 垂直結束位置
 
- 設定資料 結束部分視窗 0x01
- 執行指示 顯示開始傳送 (0x10 或 0x13)
- 寫入資料
- 執行指示 部分輸出 0x92
// 部分輸入
sendCommand(0x91);
// 部分視窗
sendCommand(0x90);
sendData(0x00);
sendData(0x00);
sendData(0x00);
sendData(0x3F);
sendData(0x00);
sendData(0x00);
sendData(0x00);
sendData(0x00);
sendData(0x01);
// 顯示開始傳送(黑)
sendCommand(0x10);
// 寫入資料
for (byte i = 0; i < 8; i++) {
    sendData(0x00);
}
// 部分輸出
sendCommand(0x92);
// 顯示器重新整理
sendCommand(0x12);
while (digitalRead(BUSY_PIN) == LOW) {
    // 有需要可以顯示狀態變化
}x1 = 0x00 * 256 + 0x00 = 0x2 = 0x00 * 256 + 0x3F = 63
y1 = 0x00 * 256 + 0x00 = 0
y2 = 0x00 * 256 + 0x00 = 0
表示資料會寫入至 (0,0) 至 (63,0) 之間 (與不使用 部分視窗 指示 相同)

將 部分視窗 指示 的 設定資料修改為
sendCommand(0x90); sendData(0x00); sendData(0x00); sendData(0x00); sendData(0x1F); sendData(0x00); sendData(0x00); sendData(0x00); sendData(0x01); sendData(0x01);x1 = 0x00 * 256 + 0x00 = 0
x2 = 0x00 * 256 + 0x1F = 31
y1 = 0x00 * 256 + 0x00 = 0
y2 = 0x00 * 256 + 0x01 = 1
表示資料會寫入至 (0,0) 至 (31,1) 之間

將 部分視窗 指示 的 設定資料修改為
sendCommand(0x90); sendData(0x00); sendData(0x00); sendData(0x00); sendData(0x0F); sendData(0x00); sendData(0x00); sendData(0x00); sendData(0x03); sendData(0x01);x1 = 0x00 * 256 + 0x00 = 0
x2 = 0x00 * 256 + 0x0F = 15
y1 = 0x00 * 256 + 0x00 = 0
y2 = 0x00 * 256 + 0x03 = 3
表示資料會寫入至 (0,0) 至 (15,3) 之間

將 部分視窗 指示 的 設定資料修改為
sendCommand(0x90); sendData(0x00); sendData(0x00); sendData(0x00); sendData(0x0F); sendData(0x00); sendData(0x00); sendData(0x00); sendData(0x02); sendData(0x01);x1 = 0x00 * 256 + 0x00 = 0
x2 = 0x00 * 256 + 0x07 = 7
y1 = 0x00 * 256 + 0x00 = 0
y2 = 0x00 * 256 + 0x07 = 7
表示資料會寫入至 (0,0) 至 (7,7) 之間

透過 Arduino 使用 控制 電子紙 顯示圖案

在下使用的 電子紙 還能夠 接收 顯示開始傳送(紅) 指示 以 紅色電子墨水 顯示圖案

顯示圖案後可以不接駁電源仍能保留內容

電子紙 更新畫面的情況
以黑色電子墨水顯示,全畫面更新時間大約需要7秒
以紅色電子墨水顯示,全畫面更新時間大約需要13秒 (只要有1個像素使用紅字電子墨水便需要較長時間)

還可以同時顯示多種顏色
在下在電子紙上顯示 Linux Tux ,不可能手工逐點點印,在下是借用 ImageMagick 將圖案轉換成 可移植點陣圖 (Portable BitMap (PBM))
在 Terminal 輸入
sudo apt-get install imagemagick或 按此安裝 ImageMagick
並將 PBM 內容,以每8格資料分割,最後將 8格資料(位元) 轉換成 十六進制資料 便完成
但留意 電子紙技術 0為黑色, 1為白色, PBM 卻是 0為白色(無色), 1為黑色(有色) ,轉換後需要將 0 與 1 互換才能正確顯示圖像的顏色
#!/bin/bash
file="image"
width=`convert "${file}" -print "%w" "/dev/null"`
if [ $(($width%8)) -eq "0" ]; then
    data=`convert "${file}" -flatten -monochrome -compress none pbm:- | tail +3 | tr -d " " | sed -r "s/(.{8})/\1, /g"`
    v="0"
    while [ "${v}" -le "255" ]; do
        b=`printf "%08d" $(echo "obase=2;${v}" | bc)`
        h=`printf "0x%02X" $((255-$v))`
        data=`echo "${data}" | sed -r "s/${b}/${h}/g"`
        v=$(($v+1))
    done
    echo "${data}"
else
    echo "Image width is not multiple of 8"
fi
不同型號的 Arduino 的記憶空間不同,如果資料太多便不能將資料寫入至 Arduino 記憶空間
例如在下在 電子紙 顯示 Linux Tux 的圖案的資料量超過在下使用的 Arduino 的記憶空間
因此在下將資料分開寫入,電子紙會將寫入的資料保存,並在接收 顯示器重新整理 指示後,才一次過將資料顯示
由於原始碼資料篇幅比較長,因此在下不在此長篇大論
閣下有興趣可以到 https://create.arduino.cc/editor/hkgoldenmra/fe3904fb-41bf-4f29-a999-e78e5657821d/preview
或 https://bitbucket.org/hkgoldenmra/epaperspimodule
或在 Terminal 輸入
git clone "https://bitbucket.org/hkgoldenmra/epaperspimodule.git" --depth=1
總結
電子紙雖然與 LCD熒幕操作相似,但操作上仍然有不同,而且操作上比 LCD熒幕更複雜另外由於在下購買電子紙時沒有留意是連接 SPI模組,未能以原始方式操作電子紙
由於電子紙可以在沒有電源供應情況下保持圖案,其實很適合在一些只是展示、陳列、不常更新資訊的地方使用
而且 Society for Information Display 2019 來自世界各地的顯示器參展商,都展示不同類型的顯示器
展覽中有電子書參展商展示出使用全色電子墨水在電子紙上顯示彩色圖像 及 與 Android 整合的觸控平板電腦
參考資料
E-Ink CorporationGood e-Reader
Arduino has made huge leaps in the 21st century technology.
回覆刪除https://aab-edu.net/
yes, absolutely
刪除