A high-performance Mode-7 racing game for ESP32-P4 with BLE 5.0 multiplayer support, featuring 720x720 display, IMU steering, and real-time P2P synchronization.
- Mode-7 Graphics Engine: Pseudo-3D racing with perspective-correct rendering
- Real-time Physics: Car dynamics with realistic handling and collision detection
- Multiplayer: 1v1 BLE racing with 25-30 FPS and ≤80ms latency
- Track System: Custom track creation and loading with checkpoint system
- Input Methods: QWERTY keyboard + MPU6050 IMU tilt steering
- ESP32-P4: Main processor with 2D-DMA optimized rendering
- ESP32-C6: BLE 5.0 coordinator with 2M PHY support
- Display: 720x720 LCD with RGB interface
- Memory: PSRAM support for large assets and caching
- Storage: SPIFFS filesystem for tracks and assets
- Fixed-point Math: 16.16 precision for performance-critical calculations
- Asset Pipeline: PNG to RGB565 conversion with caching
- Track Format: Binary format with heightmaps and collision data
- BLE Protocol: Optimized game state synchronization protocol
- Performance Monitoring: Real-time FPS and latency tracking
- ESP-IDF 6.0 or later
- ESP32-P4 development board
- ESP32-C6 module (for BLE)
- 720x720 LCD display
- MPU6050 IMU sensor
# Set up ESP-IDF environment
. /opt/esp/idf/export.sh
# Configure project
idf.py set-target esp32p4
idf.py menuconfig
# Build and flash
idf.py build
idf.py flash
idf.py monitor
# Enable PSRAM
Component config → ESP32P4-specific → Support for external, SPI-connected RAM
# Enable BLE
Component config → Bluetooth → Bluetooth
Component config → Bluetooth → NimBLE Options
# Enable SPIFFS
Component config → SPI Flash driver → SPIFFS Filesystem
- WASD: Throttle, brake, steer
- Enter: Menu select / Game start
- ESC: Menu / Pause
- Space: Handbrake
- Tilt Left/Right: Steering
- Tilt Forward: Throttle
- Tilt Backward: Brake
mode7-ble-racer-why2025/
├── main/
│ ├── game_loop.c # Main game loop and state management
│ ├── game_loop.h
│ └── test_assets.c # Asset system test suite
├── components/
│ ├── ble/
│ │ ├── ble.c # BLE stack and communication
│ │ ├── lobby.c # P2P lobby system
│ │ ├── gatt.c # GATT services
│ │ └── protocol.c # Game state protocol
│ ├── game/
│ │ ├── physics.c # Car physics and collision
│ │ └── math.c # Fixed-point math utilities
│ ├── display/
│ │ └── display.c # LCD display driver
│ ├── input/
│ │ └── input.c # Keyboard and IMU handling
│ ├── track/
│ │ ├── track_loader.c # Track loading and caching
│ │ └── track_format.h # Track file format
│ └── assets/
│ ├── asset_loader.c # Asset loading and caching
│ └── tile_converter.c # Track conversion tools
├── sdkconfig.defaults # Default configuration
├── partitions.csv # Partition layout
└── README.md
LCD Data Lines → GPIO 0-15 (RGB565)
LCD VSYNC → GPIO 16
LCD HSYNC → GPIO 17
LCD DE → GPIO 18
LCD PCLK → GPIO 19
MPU6050 SDA → GPIO 21
MPU6050 SCL → GPIO 22
MPU6050 INT → GPIO 23
Keyboard Matrix → GPIO 24-31
UART TX → GPIO 4
UART RX → GPIO 5
RST → GPIO 6
- Practice against ghost car
- Time trial mode
- Track exploration
- Host/Client P2P connection
- Real-time synchronization
- Cross-device racing
- Create ASCII track file:
### Track Layout ###
#GGGGGGGGGGGGGGGGGG#
#G RRRRRRRRRRRR G#
#G R R G#
#G R XXXXXX R G#
#G R CCCCCC R G#
#G R R G#
#G RRRRRRRRRRRR G#
#GGGGGGGGGGGGGGGGGG#
- G = Grass
- R = Road
- C = Checkpoint
- X = Start/Finish
- S = Sand
- W = Water
- Convert to binary format:
// The system will auto-convert ASCII to .trk format
- Place PNG files in
/spiffs/assets/
- System auto-converts to RGB565 tilesheets
- Access via asset loader:
texture_t* tiles = asset_load_texture("/spiffs/assets/tilesheet.png");
- Frame Rate: 25-30 FPS
- Latency: ≤80ms (BLE)
- Memory Usage: ≤2MB PSRAM
- CPU Load: ≤80% on 240MHz core
- Real-time FPS counter
- BLE latency measurement
- Memory usage tracking
- Cache hit/miss statistics
idf.py menuconfig
# Component config → Log output → Default log verbosity → Debug
- Display not working: Check RGB pin connections
- BLE connection fails: Ensure 2M PHY is enabled
- Low FPS: Reduce track complexity or resolution
- Memory issues: Check PSRAM configuration
# Check memory
idf.py size
# Monitor logs
idf.py monitor --baud 115200
# Erase flash
idf.py erase-flash
- Discovery: Device scanning and advertising
- Pairing: BLE connection establishment
- Lobby: Game configuration exchange
- Sync: Real-time game state synchronization
- Game State: 40-byte position/velocity data
- Input: 16-byte control input
- Config: 16-byte game settings
- Use ASCII format for easy editing
- System auto-converts to optimized binary
- Support for checkpoints and collision data
- PNG to RGB565 conversion
- Automatic tilesheet generation
- Palette-based color reduction
esp_err_t game_loop_init(void);
void game_loop_run(void);
void game_loop_deinit(void);
void game_set_state(game_state_t state);
esp_err_t lobby_init(const lobby_config_t *config);
esp_err_t lobby_start_hosting(void);
esp_err_t lobby_connect_to_device(const uint8_t *addr);
texture_t* asset_load_texture(const char *filename);
palette_t* asset_load_palette(const char *filename);
void asset_free_texture(texture_t *texture);
- Fork the repository
- Create feature branch:
git checkout -b feature/new-track
- Commit changes:
git commit -am 'Add new track'
- Push to branch:
git push origin feature/new-track
- Submit pull request
This project is licensed under the MIT License - see the LICENSE file for details.
- ESP-IDF team for the excellent framework
- Espressif for ESP32-P4/C6 hardware
- Mode-7 rendering techniques from classic games
- Open source community for inspiration