diff --git a/EPD/EPD_driver.c b/EPD/EPD_driver.c index 634ede7..72fd5f8 100644 --- a/EPD/EPD_driver.c +++ b/EPD/EPD_driver.c @@ -296,12 +296,14 @@ void EPD_LED_TOGGLE(void) } // EPD models -extern epd_model_t epd_4in2; -extern epd_model_t epd_4in2bv2; +extern epd_model_t epd_uc8176_420_bw; +extern epd_model_t epd_uc8176_420_bwr; +extern epd_model_t epd_ssd1619_420_bwr; static epd_model_t *epd_models[] = { - &epd_4in2, - &epd_4in2bv2, + &epd_uc8176_420_bw, + &epd_uc8176_420_bwr, + &epd_ssd1619_420_bwr, }; static epd_model_t *epd_model_get(epd_model_id_t id) diff --git a/EPD/EPD_driver.h b/EPD/EPD_driver.h index c4490d7..abf4fde 100644 --- a/EPD/EPD_driver.h +++ b/EPD/EPD_driver.h @@ -47,9 +47,9 @@ typedef struct typedef enum { - EPD_4IN2 = 1, - EPD_4IN2_V2 = 2, - EPD_4IN2B_V2 = 3, + EPD_UC8176_420_BW = 1, + EPD_UC8176_420_BWR = 3, + EPD_SSD1619_420_BWR = 2, } epd_model_id_t; typedef struct diff --git a/EPD/SSD1619.c b/EPD/SSD1619.c new file mode 100644 index 0000000..f12b4c0 --- /dev/null +++ b/EPD/SSD1619.c @@ -0,0 +1,199 @@ +/** + * Based on GDEH042Z96 driver from Good Display + * https://www.good-display.com/product/214.html + */ +#include "EPD_driver.h" +#include "nrf_log.h" + +// commands used by this driver +#define CMD_DRIVER_CTRL 0x01 // Driver Output control +#define CMD_BOOSTER_CTRL 0x0C // Booster Soft start Control +#define CMD_DEEP_SLEEP 0x10 // Deep Sleep mode +#define CMD_DATA_MODE 0x11 // Data Entry mode setting +#define CMD_SW_RESET 0x12 // SW RESET +#define CMD_TSENSOR_CTRL 0x18 // Temperature Sensor Control +#define CMD_TSENSOR_WRITE 0x1A // Temperature Sensor Control (Write to temperature register) +#define CMD_TSENSOR_READ 0x1B // Temperature Sensor Control (Read from temperature register) +#define CMD_MASTER_ACTIVATE 0x20 // Master Activation +#define CMD_DISP_CTRL1 0x21 // Display Update Control 1 +#define CMD_DISP_CTRL2 0x22 // Display Update Control 2 +#define CMD_WRITE_RAM1 0x24 // Write RAM (BW) +#define CMD_WRITE_RAM2 0x26 // Write RAM (RED) +#define CMD_VCOM_CTRL 0x2B // Write Register for VCOM Control +#define CMD_BORDER_CTRL 0x3C // Border Waveform Control +#define CMD_RAM_XPOS 0x44 // Set RAM X - address Start / End position +#define CMD_RAM_YPOS 0x45 // Set Ram Y- address Start / End position +#define CMD_RAM_XCOUNT 0x4E // Set RAM X address counter +#define CMD_RAM_YCOUNT 0x4F // Set RAM Y address counter +#define CMD_ANALOG_BLOCK_CTRL 0x74 // Set Analog Block Control +#define CMD_DIGITAL_BLOCK_CTRL 0x7E // Set Digital Block Control + +int8_t SSD1619_Read_Temp(void) +{ + EPD_WriteCommand_SW(CMD_TSENSOR_READ); + return (int8_t) EPD_ReadByte_SW(); +} + +void SSD1619_Force_Temp(int8_t value) +{ + EPD_WriteCommand(CMD_TSENSOR_WRITE); + EPD_WriteByte(value); +} + +static void _setPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h) +{ + EPD_WriteCommand(CMD_DATA_MODE); // set ram entry mode + EPD_WriteByte(0x03); // x increase, y increase + EPD_WriteCommand(CMD_RAM_XPOS); + EPD_WriteByte(x / 8); + EPD_WriteByte((x + w - 1) / 8); + EPD_WriteCommand(CMD_RAM_YPOS); + EPD_WriteByte(y % 256); + EPD_WriteByte(y / 256); + EPD_WriteByte((y + h - 1) % 256); + EPD_WriteByte((y + h - 1) / 256); + EPD_WriteCommand(CMD_RAM_XCOUNT); + EPD_WriteByte(x / 8); + EPD_WriteCommand(CMD_RAM_YCOUNT); + EPD_WriteByte(y % 256); + EPD_WriteByte(y / 256); +} + +void SSD1619_Init(epd_res_t res, bool bwr) +{ + EPD_BWR_MODE = bwr; + EPD_Reset(HIGH, 10); + + switch (res) { + case EPD_RES_320x300: + EPD_WIDTH = 320; + EPD_HEIGHT = 300; + break; + case EPD_RES_320x240: + EPD_WIDTH = 320; + EPD_HEIGHT = 240; + break; + case EPD_RES_200x300: + EPD_WIDTH = 200; + EPD_HEIGHT = 300; + break; + case EPD_RES_400x300: + default: + EPD_WIDTH = 400; + EPD_HEIGHT = 300; + break; + } + + EPD_WriteCommand(CMD_SW_RESET); + EPD_WaitBusy(HIGH, 200); + + EPD_WriteCommand(CMD_ANALOG_BLOCK_CTRL); + EPD_WriteByte(0x54); + EPD_WriteCommand(CMD_DIGITAL_BLOCK_CTRL); + EPD_WriteByte(0x3B); + EPD_WriteCommand(CMD_VCOM_CTRL); // Reduce glitch under ACVCOM + EPD_WriteByte(0x04); + EPD_WriteByte(0x63); + + EPD_WriteCommand(CMD_BOOSTER_CTRL); + EPD_WriteByte(0x8B); + EPD_WriteByte(0x9C); + EPD_WriteByte(0x96); + EPD_WriteByte(0x0F); + + EPD_WriteCommand(CMD_DRIVER_CTRL); + EPD_WriteByte((EPD_HEIGHT - 1) % 256); + EPD_WriteByte((EPD_HEIGHT - 1) / 256); + EPD_WriteByte(0x00); + + _setPartialRamArea(0, 0, EPD_WIDTH, EPD_HEIGHT); + + EPD_WriteCommand(CMD_BORDER_CTRL); + EPD_WriteByte(0x01); + + EPD_WriteCommand(CMD_TSENSOR_CTRL); + EPD_WriteByte(0x80); + EPD_WriteCommand(CMD_DISP_CTRL2); + EPD_WriteByte(0xB1); + EPD_WriteCommand(CMD_MASTER_ACTIVATE); + EPD_WaitBusy(HIGH, 200); +} + +static void SSD1619_Refresh(void) +{ + NRF_LOG_DEBUG("[EPD]: refresh begin\n"); + NRF_LOG_DEBUG("[EPD]: temperature: %d\n", SSD1619_Read_Temp()); + EPD_WriteCommand(CMD_DISP_CTRL2); + EPD_WriteByte(0xC7); + EPD_WriteCommand(CMD_MASTER_ACTIVATE); + EPD_WaitBusy(HIGH, 30000); + NRF_LOG_DEBUG("[EPD]: refresh end\n"); + + _setPartialRamArea(0, 0, EPD_WIDTH, EPD_HEIGHT); +} + +void SSD1619_Clear(void) +{ + uint16_t Width = (EPD_WIDTH % 8 == 0)? (EPD_WIDTH / 8 ): (EPD_WIDTH / 8 + 1); + uint16_t Height = EPD_HEIGHT; + + EPD_WriteCommand(CMD_WRITE_RAM1); + for (uint16_t j = 0; j < Height; j++) { + for (uint16_t i = 0; i < Width; i++) { + EPD_WriteByte(0xFF); + } + } + EPD_WriteCommand(CMD_WRITE_RAM2); + for (uint16_t j = 0; j < Height; j++) { + for (uint16_t i = 0; i < Width; i++) { + EPD_WriteByte(0x00); + } + } + + SSD1619_Refresh(); +} + +void SSD1619_Write_Image(uint8_t *black, uint8_t *color, uint16_t x, uint16_t y, uint16_t w, uint16_t h) +{ + uint16_t wb = (w + 7) / 8; // width bytes, bitmaps are padded + x -= x % 8; // byte boundary + w = wb * 8; // byte boundary + if (x + w > EPD_WIDTH || y + h > EPD_HEIGHT) return; + _setPartialRamArea(x, y, w, h); + EPD_WriteCommand(CMD_WRITE_RAM1); + for (uint16_t i = 0; i < h; i++) { + for (uint16_t j = 0; j < w / 8; j++) { + EPD_WriteByte(black ? black[j + i * wb] : 0xFF); + } + } + EPD_WriteCommand(CMD_WRITE_RAM2); + for (uint16_t i = 0; i < h; i++) { + for (uint16_t j = 0; j < w / 8; j++) { + EPD_WriteByte(color ? ~color[j + i * wb] : 0x00); + } + } +} + +void SSD1619_Sleep(void) +{ + EPD_WriteCommand(CMD_DEEP_SLEEP); + EPD_WriteByte(0x01); + delay(100); +} + +static epd_driver_t epd_drv_ssd1619 = { + .init = SSD1619_Init, + .clear = SSD1619_Clear, + .write_image = SSD1619_Write_Image, + .refresh = SSD1619_Refresh, + .sleep = SSD1619_Sleep, + .read_temp = SSD1619_Read_Temp, + .force_temp = SSD1619_Force_Temp, +}; + +const epd_model_t epd_ssd1619_420_bwr = { + .id = EPD_SSD1619_420_BWR, + .drv = &epd_drv_ssd1619, + .res = EPD_RES_400x300, + .bwr = true, +}; diff --git a/EPD/UC8176.c b/EPD/UC8176.c index 141b0cd..d4c87bd 100644 --- a/EPD/UC8176.c +++ b/EPD/UC8176.c @@ -78,10 +78,10 @@ int8_t UC8176_Read_Temp(void) // Force temperature (will trigger OTP LUT switch) void UC8176_Force_Temp(int8_t value) { - EPD_WriteCommand_SW(CMD_CCSET); - EPD_WriteByte_SW(0x02); - EPD_WriteCommand_SW(CMD_TSSET); - EPD_WriteByte_SW(value); + EPD_WriteCommand(CMD_CCSET); + EPD_WriteByte(0x02); + EPD_WriteCommand(CMD_TSSET); + EPD_WriteByte(value); } /****************************************************************************** @@ -238,15 +238,15 @@ static epd_driver_t epd_drv_uc8176 = { .force_temp = UC8176_Force_Temp, }; -const epd_model_t epd_4in2 = { - .id = EPD_4IN2, +const epd_model_t epd_uc8176_420_bw = { + .id = EPD_UC8176_420_BW, .drv = &epd_drv_uc8176, .res = EPD_RES_400x300, .bwr = false, }; -const epd_model_t epd_4in2bv2 = { - .id = EPD_4IN2B_V2, +const epd_model_t epd_uc8176_420_bwr = { + .id = EPD_UC8176_420_BWR, .drv = &epd_drv_uc8176, .res = EPD_RES_400x300, .bwr = true, diff --git a/Keil/EPD-nRF51.uvprojx b/Keil/EPD-nRF51.uvprojx index c5c4d4b..7c75362 100644 --- a/Keil/EPD-nRF51.uvprojx +++ b/Keil/EPD-nRF51.uvprojx @@ -419,6 +419,11 @@ 1 ..\EPD\UC8176.c + + SSD1619.c + 1 + ..\EPD\SSD1619.c + @@ -1021,6 +1026,11 @@ 1 ..\EPD\UC8176.c + + SSD1619.c + 1 + ..\EPD\SSD1619.c + diff --git a/Keil/EPD-nRF52.uvprojx b/Keil/EPD-nRF52.uvprojx index c4eec3e..fdc9353 100644 --- a/Keil/EPD-nRF52.uvprojx +++ b/Keil/EPD-nRF52.uvprojx @@ -419,6 +419,11 @@ 1 ..\EPD\UC8176.c + + SSD1619.c + 1 + ..\EPD\SSD1619.c + @@ -1146,6 +1151,11 @@ 1 ..\EPD\UC8176.c + + SSD1619.c + 1 + ..\EPD\SSD1619.c + diff --git a/Makefile.nRF51 b/Makefile.nRF51 index 0d6fc57..e970a36 100644 --- a/Makefile.nRF51 +++ b/Makefile.nRF51 @@ -40,6 +40,7 @@ SRC_FILES += \ $(PROJ_DIR)/EPD/EPD_driver.c \ $(PROJ_DIR)/EPD/EPD_service.c \ $(PROJ_DIR)/EPD/UC8176.c \ + $(PROJ_DIR)/EPD/SSD1619.c \ $(PROJ_DIR)/GUI/Calendar.c \ $(PROJ_DIR)/GUI/Lunar.c \ $(PROJ_DIR)/GUI/fonts.c \ diff --git a/Makefile.nRF52 b/Makefile.nRF52 index f040760..a3f55d1 100644 --- a/Makefile.nRF52 +++ b/Makefile.nRF52 @@ -65,6 +65,7 @@ SRC_FILES += \ $(PROJ_DIR)/EPD/EPD_driver.c \ $(PROJ_DIR)/EPD/EPD_service.c \ $(PROJ_DIR)/EPD/UC8176.c \ + $(PROJ_DIR)/EPD/SSD1619.c \ $(PROJ_DIR)/GUI/Calendar.c \ $(PROJ_DIR)/GUI/Lunar.c \ $(PROJ_DIR)/GUI/fonts.c \ diff --git a/html/index.html b/html/index.html index 8251148..a4e1db5 100644 --- a/html/index.html +++ b/html/index.html @@ -45,8 +45,9 @@
@@ -87,7 +88,7 @@ - + diff --git a/html/js/dithering.js b/html/js/dithering.js index 1b61a8f..4504828 100644 --- a/html/js/dithering.js +++ b/html/js/dithering.js @@ -122,7 +122,7 @@ function canvas2gray(canvas) { } // white: 1, black/red: 0 -function canvas2bytes(canvas, type='bw') { +function canvas2bytes(canvas, type='bw', invert = false) { const ctx = canvas.getContext("2d"); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); @@ -132,11 +132,14 @@ function canvas2bytes(canvas, type='bw') { for (let y = 0; y < canvas.height; y++) { for (let x = 0; x < canvas.width; x++) { const i = (canvas.width * y + x) * 4; + let data; if (type !== 'red') { - buffer.push(imageData.data[i] === 0 && imageData.data[i+1] === 0 && imageData.data[i+2] === 0 ? 0 : 1); + data = imageData.data[i] === 0 && imageData.data[i+1] === 0 && imageData.data[i+2] === 0 ? 0 : 1; } else { - buffer.push(imageData.data[i] > 0 && imageData.data[i+1] === 0 && imageData.data[i+2] === 0 ? 0 : 1); + data = imageData.data[i] > 0 && imageData.data[i+1] === 0 && imageData.data[i+2] === 0 ? 0 : 1; } + if (invert) data = ~data; + buffer.push(data); if (buffer.length === 8) { arr.push(parseInt(buffer.join(''), 2)); diff --git a/html/js/main.js b/html/js/main.js index 287f046..7ecc2a8 100644 --- a/html/js/main.js +++ b/html/js/main.js @@ -147,8 +147,8 @@ function getImageData(canvas, driver, mode) { return canvas2gray(canvas); } else { let data = canvas2bytes(canvas, 'bw'); - if (driver === '03' && mode.startsWith('bwr')) { - data.push(...canvas2bytes(canvas, 'red')); + if (mode.startsWith('bwr')) { + data.push(...canvas2bytes(canvas, 'red', driver === '02')); } return data; } @@ -167,9 +167,9 @@ async function sendimg() { return; } - if (imgArray.length == ramSize * 2) { - await epdWrite(0x10, imgArray.slice(0, ramSize)); - await epdWrite(0x13, imgArray.slice(ramSize)); + if (imgArray.length === ramSize * 2) { + await epdWrite(driver === "02" ? 0x24 : 0x10, imgArray.slice(0, ramSize)); + await epdWrite(driver === "02" ? 0x26 : 0x13, imgArray.slice(ramSize)); } else { await epdWrite(driver === "03" ? 0x10 : 0x13, imgArray); }