mirror of
https://github.com/open-x4-epaper/sample-firmware.git
synced 2026-04-29 10:24:26 -07:00
init
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
@@ -0,0 +1,94 @@
|
||||
# Xteink X4 Sample
|
||||
|
||||
A simple sample project for the Xteink X4 e-ink device using the GxEPD2 library.
|
||||
|
||||
## Hardware
|
||||
|
||||
- **Device**: Xteink X4
|
||||
- **Board**: ESP32-C3 (QFN32)
|
||||
- **Display**: [4.26" E-Ink (800×480px, GDEQ0426T82, SSD1677 controller)](https://www.good-display.com/product/457.html)
|
||||
- **Custom SPI pins**: SCLK=8, MOSI=10, CS=21, DC=4, RST=5, BUSY=6
|
||||
|
||||
### Resources
|
||||
|
||||
- [Hardware Schematics](https://github.com/sunwoods/Xteink-X4) - XteinkX4 circuit diagrams
|
||||
- [Display Datasheet](https://www.good-display.com/product/457.html) - 4.26" GDEQ0426T82 specifications
|
||||
|
||||
## Features
|
||||
|
||||
- Basic GxEPD2 display initialization
|
||||
- Custom SPI pin configuration
|
||||
- Button input detection via ADC
|
||||
- Simple text display examples
|
||||
|
||||
## Building
|
||||
|
||||
```powershell
|
||||
# Build
|
||||
platformio run
|
||||
|
||||
# Upload
|
||||
platformio run -t upload
|
||||
|
||||
# Monitor
|
||||
platformio device monitor
|
||||
```
|
||||
|
||||
## Firmware Backup & Restore
|
||||
|
||||
### Backup Original Firmware
|
||||
|
||||
Before flashing custom firmware, back up the factory firmware:
|
||||
|
||||
```powershell
|
||||
# Read entire 16MB flash
|
||||
esptool.py --chip esp32c3 --port COM5 read_flash 0x0 0x1000000 firmware_backup.bin
|
||||
```
|
||||
|
||||
### Restore Original Firmware
|
||||
|
||||
To restore the backed-up firmware:
|
||||
|
||||
```powershell
|
||||
# Write back the entire flash
|
||||
esptool.py --chip esp32c3 --port COM5 write_flash 0x0 firmware_backup.bin
|
||||
```
|
||||
|
||||
**Important**: Make sure to use the correct COM port for your device.
|
||||
|
||||
## Notes
|
||||
|
||||
- This uses `GxEPD2_426_GDEQ0426T82` as the display class for the 4.26" 800x480 display
|
||||
- Display rotation is set to 3 (270 degrees)
|
||||
- Partial refresh is used for button presses to improve responsiveness
|
||||
|
||||
## Open Tasks
|
||||
|
||||
- Better font rendering with grayscale
|
||||
- Power on/off
|
||||
- SD card reader
|
||||
- WiFi
|
||||
- Bluetooth
|
||||
|
||||
## Button System
|
||||
|
||||
The XteinkX4 uses **resistor ladder networks** connected to two ADC pins for button detection. Each button press produces a unique analog voltage that's read via `analogRead()`.
|
||||
|
||||
### Button ADC Values
|
||||
|
||||
**GPIO1 (4 buttons)**:
|
||||
- Back: ~3470
|
||||
- Confirm: ~2655
|
||||
- Left: ~1470
|
||||
- Right: ~3
|
||||
|
||||
**GPIO2 (2 buttons)**:
|
||||
- Volume Up: ~2205
|
||||
- Volume Down: ~3
|
||||
|
||||
### Implementation Notes
|
||||
|
||||
- Use threshold ranges (e.g., `value > 3200 && value < 3700`) to detect button presses
|
||||
- Add debouncing with edge detection (track last button state) to prevent multiple triggers
|
||||
- Polling every 50ms works well for responsive input
|
||||
- The resistor ladder allows multiple buttons on a single ADC pin, saving GPIO pins
|
||||
@@ -0,0 +1,20 @@
|
||||
[env:esp32-c3-devkitm-1]
|
||||
platform = espressif32
|
||||
board = esp32-c3-devkitm-1
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
upload_speed = 921600
|
||||
|
||||
; Board configuration
|
||||
board_build.flash_mode = dio
|
||||
board_build.flash_size = 4MB
|
||||
|
||||
; Libraries
|
||||
lib_deps =
|
||||
zinggjm/GxEPD2@^1.5.9
|
||||
|
||||
; Build flags
|
||||
build_flags =
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DCONFIG_ESP_TASK_WDT_INIT=0
|
||||
+587
File diff suppressed because it is too large
Load Diff
+261
@@ -0,0 +1,261 @@
|
||||
#include <Arduino.h>
|
||||
#include <Fonts/FreeMonoBold18pt7b.h>
|
||||
#include <Fonts/FreeMonoBold12pt7b.h>
|
||||
#include <GxEPD2_BW.h>
|
||||
#include <SPI.h>
|
||||
#include "image.h"
|
||||
|
||||
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
|
||||
#define EPD_SCLK 8 // SPI Clock
|
||||
#define EPD_MOSI 10 // SPI MOSI (Master Out Slave In)
|
||||
#define EPD_CS 21 // Chip Select
|
||||
#define EPD_DC 4 // Data/Command
|
||||
#define EPD_RST 5 // Reset
|
||||
#define EPD_BUSY 6 // Busy
|
||||
|
||||
// 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
|
||||
|
||||
// Button enum
|
||||
enum Button
|
||||
{
|
||||
NONE = 0,
|
||||
RIGHT,
|
||||
LEFT,
|
||||
CONFIRM,
|
||||
BACK,
|
||||
VOLUME_UP,
|
||||
VOLUME_DOWN
|
||||
};
|
||||
|
||||
// Display command enum
|
||||
enum DisplayCommand
|
||||
{
|
||||
DISPLAY_NONE = 0,
|
||||
DISPLAY_HEADER,
|
||||
DISPLAY_TEXT,
|
||||
TOGGLE_IMAGE
|
||||
};
|
||||
|
||||
// Button ADC thresholds
|
||||
const int BTN_THRESHOLD = 100; // Threshold tolerance
|
||||
const int BTN_RIGHT_VAL = 3;
|
||||
const int BTN_LEFT_VAL = 1470;
|
||||
const int BTN_CONFIRM_VAL = 2655;
|
||||
const int BTN_BACK_VAL = 3470;
|
||||
const int BTN_VOLUME_DOWN_VAL = 3;
|
||||
const int BTN_VOLUME_UP_VAL = 2205;
|
||||
|
||||
// Get button name as string
|
||||
const char *getButtonName(Button btn)
|
||||
{
|
||||
switch (btn)
|
||||
{
|
||||
case NONE:
|
||||
return "Press any button";
|
||||
case RIGHT:
|
||||
return "RIGHT pressed!";
|
||||
case LEFT:
|
||||
return "LEFT pressed!";
|
||||
case CONFIRM:
|
||||
return "CONFIRM pressed!";
|
||||
case BACK:
|
||||
return "BACK pressed!";
|
||||
case VOLUME_UP:
|
||||
return "VOLUME UP pressed!";
|
||||
case VOLUME_DOWN:
|
||||
return "VOLUME DOWN pressed!";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Get currently pressed button by reading ADC values
|
||||
Button GetPressedButton()
|
||||
{
|
||||
int btn1 = analogRead(BTN_GPIO1);
|
||||
int btn2 = analogRead(BTN_GPIO2);
|
||||
|
||||
// Check BTN_GPIO1 (4 buttons on resistor ladder)
|
||||
if (btn1 < BTN_RIGHT_VAL + BTN_THRESHOLD)
|
||||
{
|
||||
return RIGHT;
|
||||
}
|
||||
else if (btn1 < BTN_LEFT_VAL + BTN_THRESHOLD)
|
||||
{
|
||||
return LEFT;
|
||||
}
|
||||
else if (btn1 < BTN_CONFIRM_VAL + BTN_THRESHOLD)
|
||||
{
|
||||
return CONFIRM;
|
||||
}
|
||||
else if (btn1 < BTN_BACK_VAL + BTN_THRESHOLD)
|
||||
{
|
||||
return BACK;
|
||||
}
|
||||
|
||||
// Check BTN_GPIO2 (2 buttons on resistor ladder)
|
||||
if (btn2 < BTN_VOLUME_DOWN_VAL + BTN_THRESHOLD)
|
||||
{
|
||||
return VOLUME_DOWN;
|
||||
}
|
||||
else if (btn2 < BTN_VOLUME_UP_VAL + BTN_THRESHOLD)
|
||||
{
|
||||
return VOLUME_UP;
|
||||
}
|
||||
|
||||
return NONE;
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
// FreeRTOS task for non-blocking display updates
|
||||
TaskHandle_t displayTaskHandle = NULL;
|
||||
volatile DisplayCommand displayCommand = DISPLAY_NONE;
|
||||
volatile Button currentPressedButton = NONE;
|
||||
volatile bool imageVisible = false;
|
||||
|
||||
// Display update task running on separate core
|
||||
void displayUpdateTask(void *parameter)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
if (displayCommand != DISPLAY_NONE)
|
||||
{
|
||||
DisplayCommand cmd = displayCommand;
|
||||
displayCommand = DISPLAY_NONE;
|
||||
|
||||
if (cmd == DISPLAY_HEADER)
|
||||
{
|
||||
// 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(20, 50);
|
||||
display.print("XteinkX4 Sample");
|
||||
|
||||
// Button text with smaller font
|
||||
display.setFont(&FreeMonoBold12pt7b);
|
||||
display.setCursor(20, 100);
|
||||
display.print(getButtonName(currentPressedButton));
|
||||
} while (display.nextPage());
|
||||
}
|
||||
else if (cmd == DISPLAY_TEXT)
|
||||
{
|
||||
// Use partial refresh for text updates
|
||||
display.setPartialWindow(0, 75, display.width(), 150);
|
||||
display.firstPage();
|
||||
do
|
||||
{
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
display.setFont(&FreeMonoBold12pt7b);
|
||||
display.setCursor(20, 100);
|
||||
display.print(getButtonName(currentPressedButton));
|
||||
} while (display.nextPage());
|
||||
}
|
||||
else if (cmd == TOGGLE_IMAGE)
|
||||
{
|
||||
// Toggle image visibility and use partial refresh
|
||||
imageVisible = !imageVisible;
|
||||
display.setPartialWindow(20, 150, 263, 280);
|
||||
display.firstPage();
|
||||
do
|
||||
{
|
||||
if (imageVisible)
|
||||
{
|
||||
display.drawBitmap(20, 150, dr_mario, 263, 280, GxEPD_BLACK);
|
||||
}
|
||||
else
|
||||
{
|
||||
display.fillRect(20, 150, 263, 280, GxEPD_WHITE);
|
||||
}
|
||||
} while (display.nextPage());
|
||||
}
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
// Wait for serial monitor
|
||||
unsigned long start = millis();
|
||||
while (!Serial && (millis() - start) < 3000)
|
||||
{
|
||||
delay(10);
|
||||
}
|
||||
|
||||
Serial.println("\n=================================");
|
||||
Serial.println(" xteink x4 sample");
|
||||
Serial.println("=================================");
|
||||
Serial.println();
|
||||
|
||||
// Initialize button pins
|
||||
pinMode(BTN_GPIO1, INPUT);
|
||||
pinMode(BTN_GPIO2, INPUT);
|
||||
|
||||
// Initialize SPI with custom pins
|
||||
SPI.begin(EPD_SCLK, -1, EPD_MOSI, EPD_CS);
|
||||
|
||||
// Initialize display
|
||||
display.init(115200);
|
||||
|
||||
// Setup display properties
|
||||
display.setRotation(3); // 270 degrees
|
||||
display.setTextColor(GxEPD_BLACK);
|
||||
|
||||
Serial.println("Display initialized");
|
||||
|
||||
// Draw initial welcome screen
|
||||
currentPressedButton = NONE;
|
||||
displayCommand = DISPLAY_HEADER;
|
||||
|
||||
// Create display update task on core 0 (main loop runs on core 1)
|
||||
xTaskCreatePinnedToCore(displayUpdateTask, // Task function
|
||||
"DisplayUpdate", // Task name
|
||||
4096, // Stack size
|
||||
NULL, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle, // Task handle
|
||||
0 // Core 0
|
||||
);
|
||||
|
||||
Serial.println("Display task created");
|
||||
Serial.println("Setup complete!\n");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
static Button lastButton = NONE;
|
||||
Button currentButton = GetPressedButton();
|
||||
|
||||
// Detect button press (transition from NONE to a button)
|
||||
if (currentButton != NONE && lastButton == NONE)
|
||||
{
|
||||
Serial.print("Button: ");
|
||||
Serial.println(getButtonName(currentButton));
|
||||
currentPressedButton = currentButton;
|
||||
|
||||
if (currentButton == CONFIRM)
|
||||
{
|
||||
displayCommand = TOGGLE_IMAGE;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayCommand = DISPLAY_TEXT;
|
||||
}
|
||||
}
|
||||
|
||||
lastButton = currentButton;
|
||||
delay(50);
|
||||
}
|
||||
Reference in New Issue
Block a user