Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 597a06a4da | |||
| 54593d0c47 |
373
README_dev.md
Normal file
373
README_dev.md
Normal file
@@ -0,0 +1,373 @@
|
||||
# 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
|
||||
|
||||
1. **Activity System**: The firmware uses an activity-based architecture where each screen/state is an `Activity` subclass
|
||||
2. **Display System**: E-ink display handling via `EInkDisplay` and `GfxRenderer` from the OpenX4 SDK
|
||||
3. **Input System**: Button handling via `InputManager` and `MappedInputManager` with configurable button layouts
|
||||
4. **File System**: SD card management via `SDCardManager`
|
||||
5. **State Management**: `CrossPointState` and `CrossPointSettings` for persistent configuration
|
||||
|
||||
### Key Directories
|
||||
|
||||
- `src/activities/` - All screen activities (Home, Reader, Settings, etc.)
|
||||
- `src/network/` - Web server and OTA update functionality
|
||||
- `src/util/` - Utility functions
|
||||
- `src/images/` - Embedded image assets
|
||||
- `open-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 `EInkDisplay` from OpenX4 SDK
|
||||
- Uses `GfxRenderer` for drawing operations
|
||||
|
||||
### Display Initialization
|
||||
|
||||
```cpp
|
||||
// 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:
|
||||
|
||||
```cpp
|
||||
// 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`:
|
||||
|
||||
```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:
|
||||
|
||||
1. **`InputManager`** (OpenX4 SDK): Raw button state
|
||||
2. **`MappedInputManager`**: 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
|
||||
|
||||
```cpp
|
||||
// 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
|
||||
|
||||
```cpp
|
||||
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
|
||||
|
||||
1. Create header file in appropriate `src/activities/` subdirectory
|
||||
2. Inherit from `Activity` base class
|
||||
3. Implement required methods
|
||||
|
||||
### Activity Template
|
||||
|
||||
```cpp
|
||||
// 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`:
|
||||
|
||||
```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
|
||||
|
||||
1. **Avoid dynamic allocations in loop()**
|
||||
2. **Use stack allocation when possible**
|
||||
3. **Free resources in `onExit()`**
|
||||
4. **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
|
||||
|
||||
```cpp
|
||||
// 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
|
||||
|
||||
```bash
|
||||
# Build project
|
||||
pio run
|
||||
|
||||
# Flash to device
|
||||
pio run --target upload
|
||||
|
||||
# Monitor serial output
|
||||
pio device monitor
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
1. **Serial Monitor**: View logs at 115200 baud
|
||||
2. **Memory Monitoring**: Heap usage printed every 10 seconds
|
||||
3. **Error Handling**: Full-screen error activities for SD card failures
|
||||
|
||||
### Common Pitfalls
|
||||
|
||||
1. **Memory Exhaustion**: Watch for heap fragmentation
|
||||
2. **Display Artifacts**: Ensure proper buffer management
|
||||
3. **Button Debouncing**: Handled by InputManager
|
||||
4. **SD Card Access**: Use `SdMan` helper for file operations
|
||||
|
||||
## Testing
|
||||
|
||||
### Manual Testing Areas
|
||||
|
||||
1. **Button mappings** in different layout configurations
|
||||
2. **Memory usage** with large EPUB files
|
||||
3. **Sleep/wake cycles** with power button
|
||||
4. **SD card** removal/insertion during operation
|
||||
5. **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-format` for formatting
|
||||
- Add serial logging for major operations
|
||||
- Include error handling for SD card operations
|
||||
|
||||
### Adding Features
|
||||
|
||||
1. Check [ideas discussion board](https://github.com/daveallie/crosspoint-reader/discussions/categories/ideas)
|
||||
2. Create feature branch
|
||||
3. Implement with memory constraints in mind
|
||||
4. Test on actual hardware
|
||||
5. 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)
|
||||
|
||||
```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
|
||||
|
||||
- [OpenX4 SDK Documentation](https://github.com/open-x4-epaper/community-sdk)
|
||||
- [PlatformIO Documentation](https://docs.platformio.org/)
|
||||
- [ESP32-C3 Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/)
|
||||
- [EPUB Format Specification](https://www.w3.org/publishing/epub3/epub-overview.html)
|
||||
|
||||
## 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
|
||||
|
||||
@@ -127,6 +127,7 @@ void SleepActivity::renderDefaultSleepScreen() const {
|
||||
renderer.drawImage(CrossLarge, (pageWidth + 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 120, "Custom Build");
|
||||
|
||||
// Make sleep screen dark unless light is selected in settings
|
||||
if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) {
|
||||
|
||||
Reference in New Issue
Block a user