การขับจอ E Ink Spectra 6

E Ink Spectra 6 คือจอ E-Paper 6 สีที่แสดงภาพค้างไว้ได้โดยไม่ใช้พลังงาน คุณขับจอนี้จากไมโครคอนโทรลเลอร์ขนาดเล็กได้ ด้วยคำสั่ง SPI ราว 20 คำสั่งและขาสถานะเพียงขาเดียว นี่คือลำดับขั้นที่เราใช้ใน LogCase พร้อมข้อผิดพลาดที่เราเจอระหว่างทาง
ตัวอย่างนี้ใช้จอขนาด 4 นิ้ว กว้าง 400 พิกเซล สูง 600 พิกเซล จอ 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. ต่อเป็น low เพื่อเลือก SPI แบบ 4 สาย ถ้าปล่อยเป็น high จอจะรอรับ SPI แบบ 3 สาย 9 บิต และโค้ดนี้จะใช้ไม่ได้เลย
- SCL และ SDA. สัญญาณนาฬิกาและข้อมูลของ SPI ไม่มี MISO สายข้อมูลมีเส้นเดียวและส่งได้สองทิศทาง แต่เราใช้แค่การเขียน
- CS. Chip select ดึงเป็น low ขณะส่งแต่ละไบต์
- DC. เป็น low สำหรับไบต์คำสั่ง และเป็น high สำหรับข้อมูลที่ตามมา
- RES#. รีเซ็ตฮาร์ดแวร์ ทำงานเมื่อเป็น low (ในโค้ดคือ
RST_LและRST_H) - BUSY. เป็น low ขณะคอนโทรลเลอร์กำลังทำงาน และเป็น high เมื่อว่าง
ทุกข้อความประกอบด้วยไบต์คำสั่งหนึ่งไบต์ ตามด้วยพารามิเตอร์ ฟังก์ชันเหล่านี้ใช้ส่งได้ทุกคำสั่ง รวมถึงฟังก์ชันรอ 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. รีเซ็ต
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# เป็น low 20 ms แล้วปล่อย คอนโทรลเลอร์ต้องใช้เวลาอย่างน้อย 20 ms ก่อนจะรับคำสั่ง เรารอ 120 ms แล้วจึงรอ BUSY
ให้รีเซ็ตตอนเริ่มการอัปเดตแต่ละครั้ง ก่อนส่งภาพ และอย่ารีเซ็ตหลังจากนั้น การรีเซ็ตหลังโหลดภาพแล้วจะทำให้ภาพในหน่วยความจำของคอนโทรลเลอร์เสียหาย ตัวจอไม่ได้เสีย และส่งภาพใหม่ก็กลับมาปกติ แต่อาการนี้ดูคล้ายฮาร์ดแวร์เสียได้ง่าย บนจอของเรา มีพิกเซลผิดเรียงเป็นรูปแบบสม่ำเสมอบริเวณด้านบนของภาพ ราวหนึ่งในยี่สิบพิกเซล และเหมือนเดิมทุกครั้ง

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. ส่งภาพ
คอนโทรลเลอร์รับข้อมูล 4 บิตต่อพิกเซล หนึ่งไบต์มีสองพิกเซล โดยพิกเซลซ้ายอยู่ใน 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 KB แล้วทยอยส่งส่วนที่เหลือ epd_data() สลับ 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 และค่าบูสเตอร์ทั้งสามเตรียมภาคจ่ายไฟ BTST3 ส่งก่อน BTST2 ตามแบบไดรเวอร์โอเพนซอร์ส PON เริ่มการทำงานของภาคจ่ายไฟ และ BUSY จะเป็น high เมื่อแรงดันขับพร้อม บนบอร์ดของเรา 1.5 วินาทีเพียงพอเสมอ จากนั้นส่ง BTST2 อีกครั้งโดยเปลี่ยนไบต์สุดท้าย แล้ว DRF จะเริ่มการรีเฟรช
ระหว่างรีเฟรช จอจะกะพริบเป็นหลายสี และใช้เวลาราว 19 วินาทีบนจอ 4 นิ้ว นี่เป็นเรื่องปกติ ให้จับเวลาไว้ ถ้า 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 ข้ามขั้นตอนนี้ เคสไม่มีแบตเตอรี่และทำงานด้วยพลังงานจากสนาม NFC ของ iPhone เท่านั้น เมื่อรีเฟรชเสร็จ พลังงานก็ค่อยๆ หมดไปเอง
อ่านเพิ่มเติม
ไดรเวอร์โอเพนซอร์ส bb_epaper และ zy_e_album ขับจอรุ่นเดียวกันนี้ และรองรับขนาดอื่นด้วย ส่วนเรื่องการเคลื่อนที่ของเม็ดสี ดูได้ที่หน้าเทคโนโลยีของเรา
E Ink และ Spectra เป็นเครื่องหมายการค้าของ E Ink Holdings Inc.


