initial SSD1619 support

This commit is contained in:
Shuanglei Tao
2025-03-11 10:14:12 +08:00
parent d427580198
commit 19456a5886
11 changed files with 253 additions and 26 deletions

View File

@@ -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)

View File

@@ -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

199
EPD/SSD1619.c Normal file
View File

@@ -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,
};

View File

@@ -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,

View File

@@ -419,6 +419,11 @@
<FileType>1</FileType>
<FilePath>..\EPD\UC8176.c</FilePath>
</File>
<File>
<FileName>SSD1619.c</FileName>
<FileType>1</FileType>
<FilePath>..\EPD\SSD1619.c</FilePath>
</File>
</Files>
</Group>
<Group>
@@ -1021,6 +1026,11 @@
<FileType>1</FileType>
<FilePath>..\EPD\UC8176.c</FilePath>
</File>
<File>
<FileName>SSD1619.c</FileName>
<FileType>1</FileType>
<FilePath>..\EPD\SSD1619.c</FilePath>
</File>
</Files>
</Group>
<Group>

View File

@@ -419,6 +419,11 @@
<FileType>1</FileType>
<FilePath>..\EPD\UC8176.c</FilePath>
</File>
<File>
<FileName>SSD1619.c</FileName>
<FileType>1</FileType>
<FilePath>..\EPD\SSD1619.c</FilePath>
</File>
</Files>
</Group>
<Group>
@@ -1146,6 +1151,11 @@
<FileType>1</FileType>
<FilePath>..\EPD\UC8176.c</FilePath>
</File>
<File>
<FileName>SSD1619.c</FileName>
<FileType>1</FileType>
<FilePath>..\EPD\SSD1619.c</FilePath>
</File>
</Files>
</Group>
<Group>

View File

@@ -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 \

View File

@@ -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 \

View File

@@ -45,8 +45,9 @@
<div>
<label for="epddriver">驱动</label>
<select id="epddriver" onchange="filterDitheringOptions()">
<option value="01">EPD_4in2(黑白屏)</option>
<option value="03">EPD_4in2b_V2(三色屏)</option>
<option value="01">UC8176(黑白屏)</option>
<option value="03">UC8176(三色屏)</option>
<option value="02">SSD1619三色屏</option>
</select>
<label for="epdpins">引脚</label>
<input id="epdpins" type="text" value="">
@@ -87,7 +88,7 @@
<option value="floydsteinberg">floydsteinberg</option>
<option value="Atkinson">Atkinson</option>
</optgroup>
<optgroup id="dithering-bwr" data-driver="03" label="黑白红多色">
<optgroup id="dithering-bwr" data-driver="02|03" label="黑白红多色">
<option value="bwr_floydsteinberg">黑白红floydsteinberg</option>
<option value="bwr_Atkinson">黑白红Atkinson</option>
</optgroup>

View File

@@ -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));

View File

@@ -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);
}