Merge pull request #2 from CrazyCoder/main

Battery monitoring, on/off button, original partitions
This commit is contained in:
CidVonHighwind
2025-11-21 09:07:56 +01:00
committed by GitHub
4 changed files with 197 additions and 7 deletions
+22 -2
View File
@@ -46,6 +46,11 @@ Before flashing custom firmware, back up the factory firmware:
# Read entire 16MB flash
esptool.py --chip esp32c3 --port COM5 read_flash 0x0 0x1000000 firmware_backup.bin
```
```powershell
# Read only app0 (faster)
esptool.py --chip esp32c3 --port COM5 read_flash 0x10000 0x640000 app0_backup.bin
```
### Restore Original Firmware
@@ -56,6 +61,11 @@ To restore the backed-up firmware:
esptool.py --chip esp32c3 --port COM5 write_flash 0x0 firmware_backup.bin
```
```powershell
# Write back only app0 (faster)
esptool.py --chip esp32c3 --port COM5 write_flash 0x10000 app0_backup.bin
```
**Important**: Make sure to use the correct COM port for your device.
## Notes
@@ -67,9 +77,9 @@ esptool.py --chip esp32c3 --port COM5 write_flash 0x0 firmware_backup.bin
## Open Tasks
- Better font rendering with grayscale support
- Power on/off
- ~~Power on/off~~
- SD card reader
- Read battery percentage
- ~~Read battery percentage~~
- WiFi
- Bluetooth
@@ -89,6 +99,16 @@ The XteinkX4 uses **resistor ladder networks** connected to two ADC pins for but
- Volume Up: ~2205
- Volume Down: ~3
**GPIO3 (Power button)**:
- Pressed: ~3
- This example uses a 2-second-long press for sleep and a 1.5-second-long press to wake from sleep
**Battery ADC**:
- GPIO0, raw value ranges up to ~2800 when charging. ~2760 when not charging and full.
- Voltage divider is ~2 (2 x 10K resistors), `CONV_FACTOR=1.6113` for [this library](https://github.com/pangodream/18650CL)
- [See here](https://www.pangodream.es/esp32-getting-battery-charging-level) for details how voltage and charge level are calculated
- `CONV_FACTOR` may need calibration depending on the device, `1.5176` working well on mine
### Implementation Notes
- Use threshold ranges (e.g., `value > 3200 && value < 3700`) to detect button presses
+7
View File
@@ -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, 0x640000,
app1, app, ota_1, 0x650000,0x640000,
spiffs, data, spiffs, 0xc90000,0x360000,
coredump, data, coredump,0xFF0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x640000
5 app1 app ota_1 0x650000 0x640000
6 spiffs data spiffs 0xc90000 0x360000
7 coredump data coredump 0xFF0000 0x10000
+8 -2
View File
@@ -4,17 +4,23 @@ board = esp32-c3-devkitm-1
framework = arduino
monitor_speed = 115200
upload_speed = 921600
; upload to app0
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
board_upload.offset_address = 0x10000
; Board configuration
board_build.flash_mode = dio
board_build.flash_size = 4MB
board_build.flash_size = 16MB
board_build.partitions = default_16MB.csv
; Libraries
lib_deps =
zinggjm/GxEPD2@^1.5.9
https://github.com/pangodream/18650CL
; Build flags
build_flags =
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
-DCONFIG_ESP_TASK_WDT_INIT=0
-DDEBUG_IO=1
+160 -3
View File
@@ -4,6 +4,7 @@
#include <GxEPD2_BW.h>
#include <SPI.h>
#include "image.h"
#include "Pangodream_18650_CL.h"
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
#define EPD_SCLK 8 // SPI Clock
@@ -16,6 +17,17 @@
// Button pins (ADC resistor ladders)
#define BTN_GPIO1 1 // 4 buttons: Back, Confirm, Left, Right
#define BTN_GPIO2 2 // 2 buttons: Volume Up, Volume Down
#define BTN_GPIO3 3 // Power button
#define BAT_GPIO 0 // Battery voltage
#define ADC_PIN 0
#define CONV_FACTOR 1.5176
#define READS 10
#define CHARGER_THRESHOLD 2770
Pangodream_18650_CL BL(ADC_PIN, CONV_FACTOR, READS);
static int rawBat = 0;
// Button enum
enum Button
@@ -26,7 +38,8 @@ enum Button
CONFIRM,
BACK,
VOLUME_UP,
VOLUME_DOWN
VOLUME_DOWN,
POWER
};
// Display command enum
@@ -35,11 +48,13 @@ enum DisplayCommand
DISPLAY_NONE = 0,
DISPLAY_HEADER,
DISPLAY_TEXT,
DISPLAY_SLEEP,
TOGGLE_IMAGE
};
// Button ADC thresholds
const int BTN_THRESHOLD = 100; // Threshold tolerance
const int BTN_POWER_VAL = 3;
const int BTN_RIGHT_VAL = 3;
const int BTN_LEFT_VAL = 1470;
const int BTN_CONFIRM_VAL = 2655;
@@ -66,6 +81,8 @@ const char *getButtonName(Button btn)
return "VOLUME UP pressed!";
case VOLUME_DOWN:
return "VOLUME DOWN pressed!";
case POWER:
return "POWER pressed!";
default:
return "";
}
@@ -76,7 +93,13 @@ Button GetPressedButton()
{
int btn1 = analogRead(BTN_GPIO1);
int btn2 = analogRead(BTN_GPIO2);
int btn3 = analogRead(BTN_GPIO3);
// Check BTN_GPIO3 (Power button)
if (btn3 < BTN_POWER_VAL + BTN_THRESHOLD)
{
return POWER;
}
// Check BTN_GPIO1 (4 buttons on resistor ladder)
if (btn1 < BTN_RIGHT_VAL + BTN_THRESHOLD)
{
@@ -110,7 +133,8 @@ Button GetPressedButton()
// GxEPD2 display - Using GxEPD2_426_GDEQ0426T82
// Note: XteinkX4 has 4.26" 800x480 display
GxEPD2_BW<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT> display(GxEPD2_426_GDEQ0426T82(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY));
GxEPD2_BW<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT> display(
GxEPD2_426_GDEQ0426T82(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY));
// FreeRTOS task for non-blocking display updates
TaskHandle_t displayTaskHandle = NULL;
@@ -146,12 +170,14 @@ void displayUpdateTask(void *parameter)
display.setFont(&FreeMonoBold12pt7b);
display.setCursor(20, 100);
display.print(getButtonName(currentPressedButton));
display.setCursor(20, 200);
display.print(esp_reset_reason());
} while (display.nextPage());
}
else if (cmd == DISPLAY_TEXT)
{
// Use partial refresh for text updates
display.setPartialWindow(0, 75, display.width(), 150);
display.setPartialWindow(0, 75, display.width(), 300);
display.firstPage();
do
{
@@ -159,6 +185,14 @@ void displayUpdateTask(void *parameter)
display.setFont(&FreeMonoBold12pt7b);
display.setCursor(20, 100);
display.print(getButtonName(currentPressedButton));
display.setCursor(20, 160);
display.printf("Battery: %s", rawBat > CHARGER_THRESHOLD ? "Charging" : "Discharging");
display.setCursor(40, 200);
display.printf("Raw: %i", rawBat);
display.setCursor(40, 240);
display.printf("Volts: %.2f", BL.getBatteryVolts());
display.setCursor(40, 280);
display.printf("Charge level: %i%%", BL.getBatteryChargeLevel());
} while (display.nextPage());
}
else if (cmd == TOGGLE_IMAGE)
@@ -179,6 +213,20 @@ void displayUpdateTask(void *parameter)
}
} while (display.nextPage());
}
else if (cmd == DISPLAY_SLEEP)
{
// Use full window for initial welcome screen with header
display.setFullWindow();
display.firstPage();
do
{
display.fillScreen(GxEPD_WHITE);
// Header font
display.setFont(&FreeMonoBold18pt7b);
display.setCursor(120, 380);
display.print("Sleeping...");
} while (display.nextPage());
}
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
@@ -186,6 +234,40 @@ void displayUpdateTask(void *parameter)
void setup()
{
// Check if boot was triggered by the Power Button (Deep Sleep Wakeup)
// If triggered by RST pin or Battery insertion, this will be false, allowing normal boot.
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_GPIO)
{
// Woke up from sleep via Power Button. Verifying long press...
// Temporarily configure pin as digital input to check state
pinMode(BTN_GPIO3, INPUT);
const unsigned long LONG_PRESS_MS = 1500; // 1.5 seconds required
unsigned long pressStart = millis();
bool bootConfirmed = true;
// Monitor button state for the duration
while (millis() - pressStart < LONG_PRESS_MS)
{
// If button reads HIGH (released) before time is up
if (digitalRead(BTN_GPIO3) == HIGH)
{
bootConfirmed = false;
break;
}
delay(10);
}
if (!bootConfirmed)
{
// Button released too early. Returning to sleep.
pinMode(BTN_GPIO3, INPUT_PULLUP);
// IMPORTANT: Re-arm the wakeup trigger before sleeping again
esp_deep_sleep_enable_gpio_wakeup(1ULL << BTN_GPIO3, ESP_GPIO_WAKEUP_GPIO_LOW);
esp_deep_sleep_start();
}
}
Serial.begin(115200);
// Wait for serial monitor
@@ -195,14 +277,17 @@ void setup()
delay(10);
}
Serial.println("\n=================================");
Serial.println(" xteink x4 sample");
Serial.println("=================================");
Serial.println();
// Initialize button pins
pinMode(BAT_GPIO, INPUT);
pinMode(BTN_GPIO1, INPUT);
pinMode(BTN_GPIO2, INPUT);
pinMode(BTN_GPIO3, INPUT);
// Initialize SPI with custom pins
SPI.begin(EPD_SCLK, -1, EPD_MOSI, EPD_CS);
@@ -246,6 +331,46 @@ void loop()
Serial.println(getButtonName(currentButton));
currentPressedButton = currentButton;
if (currentButton == POWER)
{
Serial.println("Power button pressed");
displayCommand = DISPLAY_TEXT;
// 1. Switch pin to Digital Input with Pull-up
// This is crucial! 'analogRead' configures the pin as ADC, often disabling the digital input buffer.
// If the digital buffer is disabled, the sleep controller reads '0' (LOW) and wakes immediately.
// The Pull-up also prevents the pin from floating if the external circuit is open.
pinMode(BTN_GPIO3, INPUT_PULLUP);
// 2. Wait for button release
// We wait until the digital state is HIGH (released)
unsigned long pressedTime = millis();
bool longPress = false;
while (digitalRead(BTN_GPIO3) == LOW)
{
delay(50);
if (millis() - pressedTime > 2000)
{
longPress = true;
break;
}
}
// Power button long pressed, go to sleep
if (longPress)
{
displayCommand = DISPLAY_SLEEP;
Serial.println("Power button released after a long press. Entering deep sleep.");
delay(2000); // Allow Serial buffer to empty and display to update
// 3. Enable Wakeup on LOW
esp_deep_sleep_enable_gpio_wakeup(1ULL << BTN_GPIO3, ESP_GPIO_WAKEUP_GPIO_LOW);
// 4. Enter Deep Sleep
esp_deep_sleep_start();
}
}
if (currentButton == CONFIRM)
{
displayCommand = TOGGLE_IMAGE;
@@ -257,5 +382,37 @@ void loop()
}
lastButton = currentButton;
#ifdef DEBUG_IO
// Log raw analog levels of BTN1 and BTN2 not more often than once per second
static unsigned long lastLogMs = 0;
unsigned long now = millis();
if (now - lastLogMs >= 1000)
{
rawBat = analogRead(BAT_GPIO);
int rawBtn1 = analogRead(BTN_GPIO1);
int rawBtn2 = analogRead(BTN_GPIO2);
int rawBtn3 = analogRead(BTN_GPIO3);
Serial.print("ADC BTN1=");
Serial.print(rawBtn1);
Serial.print(" BTN2=");
Serial.print(rawBtn2);
Serial.print(" BTN3=");
Serial.print(rawBtn3);
Serial.println("");
Serial.print("Value from pin: ");
Serial.println(rawBat);
Serial.print("Average value from pin: ");
Serial.println(BL.pinRead());
Serial.print("Volts: ");
Serial.println(BL.getBatteryVolts());
Serial.print("Charge level: ");
Serial.println(BL.getBatteryChargeLevel());
Serial.println("");
lastLogMs = now;
}
delay(50);
#endif
}