Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions firmware/partitions/jcalendar_4mb_ota.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1e0000,
app1, app, ota_1, 0x1f0000,0x1e0000,
spiffs, data, spiffs, 0x3d0000,0x20000,
coredump, data, coredump,0x3f0000,0x10000,
14 changes: 14 additions & 0 deletions firmware/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ build_flags =
-DEPD_PANEL_42_SSD1683_BW
-DALLOW_INSECURE_FALLBACK=0

# ── J-Calendar ESP32 + GDEQ042Z21 4.2" BWR 三色屏 ──────────────────────
[env:epd_42_jcalendar_z21_esp32dev]
extends = common
board = esp32dev
upload_speed = 460800
board_build.partitions = partitions/jcalendar_4mb_ota.csv
build_flags =
-DBOARD_PROFILE_JCALENDAR_ESP32
-DEPD_WIDTH=400
-DEPD_HEIGHT=300
-DEPD_PANEL_42_Z21_BWR
-DEPD_BPP=2
-DALLOW_INSECURE_FALLBACK=0

# ── ESP32-WROOM32E + AI Chat (语音对话) ──────────────────────
[env:epd_42_wroom32e_ai_chat]
extends = common
Expand Down
11 changes: 11 additions & 0 deletions firmware/src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@
#define PIN_CFG_BTN 9
#define PIN_LED 5
#define PIN_AI_CHAT_SW -1
#elif defined(BOARD_PROFILE_JCALENDAR_ESP32)
#define PIN_EPD_MOSI 23
#define PIN_EPD_SCK 18
#define PIN_EPD_CS 5
#define PIN_EPD_DC 17
#define PIN_EPD_RST 16
#define PIN_EPD_BUSY 4
#define PIN_BAT_ADC 32
#define PIN_CFG_BTN 14
#define PIN_LED 22
#define PIN_AI_CHAT_SW -1
#elif defined(BOARD_PROFILE_ESP32_WROOM32E)
#define PIN_EPD_MOSI 14
#define PIN_EPD_SCK 13
Expand Down
111 changes: 106 additions & 5 deletions firmware/src/epd_driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,7 @@ void epdSleep() {

#include <SPI.h>
#include <GxEPD2_BW.h>
#include <GxEPD2_3C.h>

#ifndef EPD_GXEPD2_SPI_HZ
#define EPD_GXEPD2_SPI_HZ 4000000
Expand All @@ -794,6 +795,10 @@ void epdSleep() {
#include <gdey/GxEPD2_420_GDEY042T81.h>
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT / 4> display(
GxEPD2_420_GDEY042T81(PIN_EPD_CS, PIN_EPD_DC, PIN_EPD_RST, PIN_EPD_BUSY));
#elif defined(EPD_PANEL_42_Z21_BWR)
#include <epd3c/GxEPD2_420c_Z21.h>
GxEPD2_3C<GxEPD2_420c_Z21, GxEPD2_420c_Z21::HEIGHT> display(
GxEPD2_420c_Z21(PIN_EPD_CS, PIN_EPD_DC, PIN_EPD_RST, PIN_EPD_BUSY));
#elif defined(EPD_PANEL_42_GXEPD2_GYE042A87)
#include <other/GxEPD2_420_GYE042A87.h>
GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> display(
Expand Down Expand Up @@ -846,15 +851,15 @@ void epdSleep() {
GxEPD2_BW<GxEPD2_750_T7, GxEPD2_750_T7::HEIGHT / 4> display(
GxEPD2_750_T7(PIN_EPD_CS, PIN_EPD_DC, PIN_EPD_RST, PIN_EPD_BUSY));
#else
#error "No EPD panel type defined. Use -DEPD_PANEL_42_SSD1683_BW, -DEPD_PANEL_42_DKE_RY683, -DEPD_PANEL_42_GDEM042F52, -DEPD_PANEL_42_GXEPD2_T81, -DEPD_PANEL_42_GXEPD2_GYE042A87, -DEPD_PANEL_42_GXEPD2_420, -DEPD_PANEL_42_GXEPD2_M01, -DEPD_PANEL_29, -DEPD_PANEL_583_UC8179, -DEPD_PANEL_583, or -DEPD_PANEL_75"
#error "No EPD panel type defined. Use -DEPD_PANEL_42_SSD1683_BW, -DEPD_PANEL_42_DKE_RY683, -DEPD_PANEL_42_GDEM042F52, -DEPD_PANEL_42_Z21_BWR, -DEPD_PANEL_42_GXEPD2_T81, -DEPD_PANEL_42_GXEPD2_GYE042A87, -DEPD_PANEL_42_GXEPD2_420, -DEPD_PANEL_42_GXEPD2_M01, -DEPD_PANEL_29, -DEPD_PANEL_583_UC8179, -DEPD_PANEL_583, or -DEPD_PANEL_75"
#endif

static bool _initialized = false;
#if defined(EPD_PANEL_42_GXEPD2_GYE042A87)
static bool _needs_full_refresh_write = true;
#endif
static const uint8_t DISPLAY_ROTATION =
#if defined(EPD_PANEL_42_GXEPD2_T81) || defined(EPD_PANEL_42_GXEPD2_GYE042A87) || defined(EPD_PANEL_42_GXEPD2_420) || defined(EPD_PANEL_42_GXEPD2_M01)
#if defined(EPD_PANEL_42_Z21_BWR) || defined(EPD_PANEL_42_GXEPD2_T81) || defined(EPD_PANEL_42_GXEPD2_GYE042A87) || defined(EPD_PANEL_42_GXEPD2_420) || defined(EPD_PANEL_42_GXEPD2_M01)
0;
#else
1;
Expand All @@ -878,9 +883,79 @@ void epdInitFast() {
epdInit();
}

#if defined(EPD_PANEL_42_Z21_BWR)
static void z21SetPixel(uint8_t *buffer, int x, int y, bool active) {
int index = y * ROW_BYTES + x / 8;
uint8_t mask = 0x80 >> (x % 8);
if (active) {
buffer[index] &= ~mask;
} else {
buffer[index] |= mask;
}
}

static bool z21IsRed2bppColor(uint8_t color) {
return color == 0x03 || color == 0x02;
}

static void writeZ21FullImage(const uint8_t *blackBuf, const uint8_t *redBuf) {
epdInit();
display.writeImage(blackBuf, redBuf, 0, 0, W, H, false, false, false);
display.refresh(false);
display.powerOff();
}

static void writeZ21TricolorImage(const uint8_t *image2bpp) {
uint8_t *blackBuf = (uint8_t *)malloc(IMG_BUF_LEN);
uint8_t *redBuf = (uint8_t *)malloc(IMG_BUF_LEN);
if (!blackBuf || !redBuf) {
Serial.println("[EPD] Z21 2bpp buffer alloc failed");
free(blackBuf);
free(redBuf);
return;
}

memset(blackBuf, 0xFF, IMG_BUF_LEN);
memset(redBuf, 0xFF, IMG_BUF_LEN);

for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
int index = (y * W + x) / 4;
int shift = 6 - (((y * W + x) % 4) * 2);
uint8_t color = (image2bpp[index] >> shift) & 0x03;

if (color == 0x00) {
z21SetPixel(blackBuf, x, y, true);
} else if (z21IsRed2bppColor(color)) {
z21SetPixel(redBuf, x, y, true);
}
}
}

writeZ21FullImage(blackBuf, redBuf);
free(blackBuf);
free(redBuf);
}

static void writeZ21MonoImage(const uint8_t *image) {
uint8_t *redBuf = (uint8_t *)malloc(IMG_BUF_LEN);
if (!redBuf) {
Serial.println("[EPD] Z21 red buffer alloc failed");
return;
}

memset(redBuf, 0xFF, IMG_BUF_LEN);
writeZ21FullImage(image, redBuf);
free(redBuf);
}
#endif

void epdDisplay(const uint8_t *image) {
epdInit();
#if defined(EPD_PANEL_29)
#if defined(EPD_PANEL_42_Z21_BWR)
writeZ21MonoImage(image);
return;
#elif defined(EPD_PANEL_29)
rotate_landscape_to_panel(image);
display.writeImage(
rotated_buffer,
Expand All @@ -906,12 +981,25 @@ void epdDisplay(const uint8_t *image) {
display.powerOff();
}

void epdDisplay2bpp(const uint8_t *image2bpp) {
#if defined(EPD_PANEL_42_Z21_BWR)
writeZ21TricolorImage(image2bpp);
#else
(void)image2bpp;
epdDisplay(imgBuf);
#endif
}

void epdDisplayFast(const uint8_t *image) {
#if defined(EPD_PANEL_583_UC8179)
// 583 UC8179: always full refresh (GxEPD2 refresh(false)); avoids partial LUT ghosting.
epdDisplay(image);
return;
#endif
#if defined(EPD_PANEL_42_Z21_BWR)
epdDisplay(image);
return;
#endif
#if defined(EPD_PANEL_42_GXEPD2_GYE042A87)
epdDisplay(image);
return;
Expand All @@ -937,6 +1025,10 @@ void epdDisplayFast(const uint8_t *image) {
}

void epdDisplayDeepClear(const uint8_t *image) {
#if defined(EPD_PANEL_42_Z21_BWR)
epdDisplay(image);
return;
#endif
epdInit();

uint8_t *clearBuf = (uint8_t *)malloc(IMG_BUF_LEN);
Expand All @@ -958,7 +1050,7 @@ void epdPartialDisplay(uint8_t *data, int xStart, int yStart, int xEnd, int yEnd
}

bool epdSupportsPartialRefresh() {
#if defined(EPD_PANEL_29)
#if defined(EPD_PANEL_42_Z21_BWR) || defined(EPD_PANEL_29)
return false;
#else
return true;
Expand All @@ -967,7 +1059,16 @@ bool epdSupportsPartialRefresh() {

void epdPartialDisplayWithOld(uint8_t *data, const uint8_t *oldData, int xStart, int yStart, int xEnd, int yEnd) {
epdInit();
#if defined(EPD_PANEL_29)
#if defined(EPD_PANEL_42_Z21_BWR)
(void)data;
(void)oldData;
(void)xStart;
(void)yStart;
(void)xEnd;
(void)yEnd;
epdDisplay(imgBuf);
return;
#elif defined(EPD_PANEL_29)
(void)data;
(void)oldData;
(void)xStart;
Expand Down
11 changes: 8 additions & 3 deletions firmware/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ struct DeviceContext {

// Pending actions (set by button handler, consumed by loop)
bool wantRefresh = false;
bool wantRefreshNext = false;
bool wantEnterLiveMode = false;
bool wantEnterAiChatMode = false;
bool wantSingleVoiceTurn = false;
Expand Down Expand Up @@ -1111,7 +1112,11 @@ void loop() {
checkConfigButton();
checkAiChatButton();

if (ctx.wantEnterLiveMode) {
if (ctx.wantRefreshNext) {
ctx.wantRefreshNext = false;
triggerImmediateRefresh(true, true);
ctx.setupDoneAt = millis();
} else if (ctx.wantEnterLiveMode) {
ctx.wantEnterLiveMode = false;
if (ctx.liveMode) {
ctx.liveMode = false;
Expand Down Expand Up @@ -2209,8 +2214,8 @@ static void checkConfigButton() {

if (pressDuration >= (unsigned long)SHORT_PRESS_MIN_MS &&
pressDuration < (unsigned long)CFG_BTN_HOLD_MS) {
Serial.println("[BTN] Single click -> toggle live mode");
ctx.wantEnterLiveMode = true;
Serial.println("[BTN] Single click -> next content");
ctx.wantRefreshNext = true;
}
}
}
Expand Down
19 changes: 18 additions & 1 deletion firmware/src/network.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,24 @@ float readBatteryVoltage() {
sum += readings[i];

float avgRaw = (float)sum / (SAMPLES - 2 * DISCARD);
#if defined(BOARD_PROFILE_ESP32_C3_WROOM02)
#if defined(BOARD_PROFILE_JCALENDAR_ESP32)
uint32_t pinMv = analogReadMilliVolts(PIN_BAT_ADC);
float realBatteryVoltage = (pinMv / 1000.0f) * 2.0f;

const float measuredLow = 2.95f;
const float measuredHigh = 4.17f;
const float targetLow = 0.0f;
const float targetHigh = 3.3f;

if (realBatteryVoltage <= measuredLow) return targetLow;
if (realBatteryVoltage >= measuredHigh) return targetHigh;

float mappedVoltage = targetLow + (realBatteryVoltage - measuredLow) *
(targetHigh - targetLow) / (measuredHigh - measuredLow);
if (mappedVoltage > targetHigh) mappedVoltage = targetHigh;
if (mappedVoltage < targetLow) mappedVoltage = targetLow;
return mappedVoltage;
#elif defined(BOARD_PROFILE_ESP32_C3_WROOM02)
static esp_adc_cal_characteristics_t adcChars;
static bool calibrated = false;
if (!calibrated) {
Expand Down
Loading
Loading