9.7 KiB
CrossPoint Reader - Developer Guide
This guide provides essential information for developers working on the CrossPoint Reader firmware for the Xteink X4 e-paper device.
Project Overview
CrossPoint Reader is an open-source firmware replacement for the Xteink X4 e-paper reader, built using PlatformIO and targeting the ESP32-C3 microcontroller. The firmware provides EPUB reading capabilities with a focus on memory efficiency due to the ESP32-C3's limited RAM (~380KB).
Architecture
Main Components
- Activity System: The firmware uses an activity-based architecture where each screen/state is an
Activitysubclass - Display System: E-ink display handling via
EInkDisplayandGfxRendererfrom the OpenX4 SDK - Input System: Button handling via
InputManagerandMappedInputManagerwith configurable button layouts - File System: SD card management via
SDCardManager - State Management:
CrossPointStateandCrossPointSettingsfor persistent configuration
Key Directories
src/activities/- All screen activities (Home, Reader, Settings, etc.)src/network/- Web server and OTA update functionalitysrc/util/- Utility functionssrc/images/- Embedded image assetsopen-x4-sdk/- Community SDK with hardware libraries (submodule)
Display Programming
E-Ink Display Basics
The display is a 480x800 e-paper panel controlled via SPI. Key display-related files:
src/main.cpp- Display initialization and SPI pin definitions- Uses
EInkDisplayfrom OpenX4 SDK - Uses
GfxRendererfor drawing operations
Display Initialization
// Display SPI pins (custom pins for XteinkX4)
#define EPD_SCLK 8 // SPI Clock
#define EPD_MOSI 10 // SPI MOSI
#define EPD_CS 21 // Chip Select
#define EPD_DC 4 // Data/Command
#define EPD_RST 5 // Reset
#define EPD_BUSY 6 // Busy
EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
GfxRenderer renderer(einkDisplay);
Drawing Operations
The GfxRenderer class provides drawing primitives:
// Basic drawing functions
renderer.clearScreen(); // Clear display buffer
renderer.drawText(fontId, x, y, text); // Draw text
renderer.drawRect(x, y, width, height); // Draw rectangle outline
renderer.fillRect(x, y, width, height); // Fill rectangle
renderer.drawCenteredText(fontId, y, text); // Center text horizontally
renderer.displayBuffer(); // Update physical display
// Screen dimensions
int width = renderer.getScreenWidth(); // 480 in portrait
int height = renderer.getScreenHeight(); // 800 in portrait
Font System
Multiple font families are available (Bookerly, NotoSans, OpenDyslexic) in various sizes. Fonts are registered in main.cpp:
// Font registration example
renderer.insertFont(BOOKERLY_14_FONT_ID, bookerly14FontFamily);
Font IDs are defined in src/fontIds.h.
Display Refresh Optimization
Due to e-paper characteristics:
- Use
renderer.displayBuffer()sparingly (causes screen refresh) - Partial refreshes are handled automatically by the SDK
- Full refresh frequency is configurable in settings
Button Input Handling
Hardware Button Mapping
The Xteink X4 has:
- Front buttons: 4 buttons (Back, Confirm, Left, Right)
- Side buttons: 2 buttons (Up/Down for page turning)
- Power button: Separate power/sleep control
Input Manager System
Two-layer input system:
InputManager(OpenX4 SDK): Raw button stateMappedInputManager: Configurable button mapping
Button Configuration
Button layouts are configurable in settings:
- Front button layout: Back/Confirm/Left/Right can be rearranged
- Side button layout: Page up/down can be swapped
Using MappedInputManager
// In Activity constructor
MappedInputManager& mappedInput;
// Check button states
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
// Handle confirm press
}
if (mappedInput.isPressed(MappedInputManager::Button::Power)) {
// Power button currently held
}
// Get button labels for UI
auto labels = mappedInput.mapLabels("Back", "Select", "Prev", "Next");
Available Button Constants
MappedInputManager::Button::Back
MappedInputManager::Button::Confirm
MappedInputManager::Button::Left
MappedInputManager::Button::Right
MappedInputManager::Button::Up
MappedInputManager::Button::Down
MappedInputManager::Button::Power
MappedInputManager::Button::PageBack
MappedInputManager::Button::PageForward
Power Button Behavior
- Short press: Configurable (ignore, sleep, or page turn)
- Long press (>400ms): Always enters deep sleep
- Wake from sleep: Requires long press verification
Activity System
Creating a New Activity
- Create header file in appropriate
src/activities/subdirectory - Inherit from
Activitybase class - Implement required methods
Activity Template
// MyActivity.h
#pragma once
#include "activities/Activity.h"
class MyActivity : public Activity {
public:
MyActivity(GfxRenderer& renderer, MappedInputManager& mappedInput);
void onEnter() override;
void onExit() override;
void loop() override;
private:
void render();
};
// MyActivity.cpp
MyActivity::MyActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
: Activity("MyActivity", renderer, mappedInput) {}
void MyActivity::onEnter() {
Activity::onEnter();
// Initialization code
}
void MyActivity::onExit() {
// Cleanup code
Activity::onExit();
}
void MyActivity::loop() {
// Handle input
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
// Handle action
}
// Update and render if needed
render();
}
void MyActivity::render() {
renderer.clearScreen();
// Drawing code
renderer.displayBuffer();
}
Activity Navigation
Activities are managed in main.cpp:
// Switch to new activity
exitActivity(); // Clean up current
enterNewActivity(new MyActivity(renderer, mappedInputManager));
Memory Management
Constraints
- ESP32-C3 has ~380KB usable RAM
- Display buffer: ~45KB (480x800 / 8 bits per pixel)
- Must cache EPUB data to SD card
Caching Strategy
EPUB content is cached to .crosspoint/ directory on SD card:
- Chapter data cached after first load
- Book metadata stored in
book.bin - Reading progress in
progress.bin - Cover images in
cover.bmp
Best Practices
- Avoid dynamic allocations in loop()
- Use stack allocation when possible
- Free resources in
onExit() - Monitor memory usage (serial output shows free heap)
Configuration System
CrossPointSettings
Singleton class for user settings:
- Display settings (orientation, refresh rate)
- Reader settings (font, spacing, alignment)
- Button layouts
- Sleep timeouts
// Access settings
SETTINGS.fontFamily; // Current font family
SETTINGS.getSleepTimeoutMs(); // Auto-sleep timeout
SETTINGS.getPowerButtonDuration(); // Long-press duration
CrossPointState
Singleton for runtime state:
- Currently open EPUB path
- Last sleep screen image index
Development Workflow
Building and Flashing
# Build project
pio run
# Flash to device
pio run --target upload
# Monitor serial output
pio device monitor
Debugging
- Serial Monitor: View logs at 115200 baud
- Memory Monitoring: Heap usage printed every 10 seconds
- Error Handling: Full-screen error activities for SD card failures
Common Pitfalls
- Memory Exhaustion: Watch for heap fragmentation
- Display Artifacts: Ensure proper buffer management
- Button Debouncing: Handled by InputManager
- SD Card Access: Use
SdManhelper for file operations
Testing
Manual Testing Areas
- Button mappings in different layout configurations
- Memory usage with large EPUB files
- Sleep/wake cycles with power button
- SD card removal/insertion during operation
- Battery indicator accuracy
Automated Testing
- PlatformIO test framework available
- Run tests:
pio test - Test directory:
test/
Contributing Guidelines
Code Style
- Follow existing code patterns
- Use
clang-formatfor formatting - Add serial logging for major operations
- Include error handling for SD card operations
Adding Features
- Check ideas discussion board
- Create feature branch
- Implement with memory constraints in mind
- Test on actual hardware
- Submit PR with clear description
Performance Considerations
- Minimize display refreshes
- Cache computed values (text measurements, etc.)
- Use
skipLoopDelay()for activities needing fast response (webserver) - Implement
preventAutoSleep()for activities with background work
Hardware Reference
Pin Definitions (from main.cpp)
// Display SPI
#define EPD_SCLK 8
#define EPD_MOSI 10
#define EPD_CS 21
#define EPD_DC 4
#define EPD_RST 5
#define EPD_BUSY 6
// SD Card SPI
#define SD_SPI_MISO 7
// Battery monitoring
#define BAT_GPIO0 0
// USB detection
#define UART0_RXD 20
Power Management
- Deep sleep mode for power saving
- Wake on power button press (GPIO low)
- Auto-sleep after configurable inactivity period
- Battery monitoring via ADC on GPIO0
Useful Resources
Getting Help
- Check existing issues and discussions
- Review
docs/directory for internal documentation - Test with actual Xteink X4 hardware when possible
- Enable serial logging for debugging