Snake! (#10936)
* Snake! * Add spiLock to snake score saving * Check fixes * More careful locking * WIP: Big Display Node * Update src/graphics/HUB75Display.cpp Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Add HUB75 Native * Add Tetris game module with core logic and UI integration - Implement TetrisGame class for game logic, including piece movement, rotation, and line clearing. - Create TetrisModule class to manage game state, input handling, and rendering on OLED display. - Introduce high score tracking with persistence and optional mesh broadcasting. - Define UI states for title, playing, paused, game over, and high scores. - Implement input handling for game controls and state transitions. - Add rendering functions for the game board, high scores, and title screen. * feat(snake): add snake graphics and update display logic in SnakeModule * Prompt for Initials for Tetris, too * refactor games module * Games refactor * hub75 native double buffer * Games tuning * Make joystick repeat events on held button * Add clouds and colors * Fix breakout and more color * difficulty tuning * trunk * refactor game announcements, etc * Scale chirpy gravity as game speed increases * Portduino, check for hub75 display before reading in hub75 options * Final(?) games tuning * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Properly ignore input when games screen not shown * Fail gracefully when HUB75 is selected but not supported. --------- Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Ixitxachitl <kramerfm@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
Thomas Göttgens
coderabbitai[bot]
Ixitxachitl
parent
d3d02af817
commit
8d1fbbf55f
41 files changed
+4106
-34
No files matched your search
@@ -0,0 +1,163 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if defined(HAS_HUB75_NATIVE)
|
||||
|
||||
#include "HUB75Native.h"
|
||||
#include "PortduinoGlue.h"
|
||||
#include "graphics/TFTColorRegions.h"
|
||||
#include "graphics/TFTPalette.h"
|
||||
#include <led-matrix.h>
|
||||
|
||||
HUB75Native::HUB75Native(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C)
|
||||
{
|
||||
// The BaseUI treats the panel as a generic raw framebuffer (not an SSD1306 page layout). The
|
||||
// panel size comes from config.yaml at runtime (cols*chain x rows*parallel, computed in
|
||||
// loadConfig()). Config is fully loaded by portduinoSetup() before Screen constructs.
|
||||
setGeometry(GEOMETRY_RAWMODE, portduino_config.displayWidth, portduino_config.displayHeight);
|
||||
LOG_DEBUG("HUB75Native %dx%d", portduino_config.displayWidth, portduino_config.displayHeight);
|
||||
}
|
||||
|
||||
HUB75Native::~HUB75Native()
|
||||
{
|
||||
if (matrix) {
|
||||
delete matrix;
|
||||
matrix = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Bring up the rpi-rgb-led-matrix driver from config.yaml. The library owns the GPIO pins
|
||||
// (selected by HardwareMapping) and runs its own refresh thread, so there is no DMA/I2S setup.
|
||||
bool HUB75Native::connect()
|
||||
{
|
||||
LOG_INFO("Do HUB75 init");
|
||||
|
||||
rgb_matrix::RGBMatrix::Options options;
|
||||
options.hardware_mapping = portduino_config.hub75_hardware_mapping.c_str();
|
||||
options.rows = portduino_config.hub75_rows;
|
||||
options.cols = portduino_config.hub75_cols;
|
||||
options.chain_length = portduino_config.hub75_chain_length;
|
||||
options.parallel = portduino_config.hub75_parallel;
|
||||
options.pwm_bits = portduino_config.hub75_pwm_bits;
|
||||
options.pwm_lsb_nanoseconds = portduino_config.hub75_pwm_lsb_nanoseconds;
|
||||
options.brightness = portduino_config.hub75_brightness;
|
||||
options.scan_mode = portduino_config.hub75_scan_mode;
|
||||
options.row_address_type = portduino_config.hub75_row_address_type;
|
||||
options.multiplexing = portduino_config.hub75_multiplexing;
|
||||
options.disable_hardware_pulsing = portduino_config.hub75_disable_hardware_pulsing;
|
||||
options.show_refresh_rate = portduino_config.hub75_show_refresh_rate;
|
||||
options.inverse_colors = portduino_config.hub75_inverse_colors;
|
||||
// RGB order is handled by the library instead of a software swap (see display()).
|
||||
options.led_rgb_sequence = portduino_config.hub75_led_rgb_sequence.c_str();
|
||||
options.limit_refresh_rate_hz = portduino_config.hub75_limit_refresh_rate_hz;
|
||||
if (!portduino_config.hub75_pixel_mapper_config.empty())
|
||||
options.pixel_mapper_config = portduino_config.hub75_pixel_mapper_config.c_str();
|
||||
if (!portduino_config.hub75_panel_type.empty())
|
||||
options.panel_type = portduino_config.hub75_panel_type.c_str();
|
||||
|
||||
rgb_matrix::RuntimeOptions runtime;
|
||||
runtime.gpio_slowdown = portduino_config.hub75_gpio_slowdown;
|
||||
runtime.daemon = 0; // run in-process; the library starts its own refresh thread
|
||||
runtime.drop_privileges = 0; // meshtasticd manages its own privileges (keeps GPIO/LoRa access)
|
||||
|
||||
matrix = rgb_matrix::CreateMatrixFromOptions(options, runtime);
|
||||
if (matrix == nullptr) {
|
||||
LOG_ERROR("HUB75 CreateMatrixFromOptions() failed (need root / /dev/gpiomem on a Pi?)");
|
||||
return false;
|
||||
}
|
||||
brightness = portduino_config.hub75_brightness; // library scale is 1..100
|
||||
matrix->SetBrightness(brightness); // applies to the matrix and all its FrameCanvases
|
||||
matrix->Clear();
|
||||
// Offscreen buffer for double-buffered, tear-free presentation (see display()).
|
||||
offscreen = matrix->CreateFrameCanvas();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HUB75Native::display()
|
||||
{
|
||||
if (!matrix || !offscreen)
|
||||
return;
|
||||
|
||||
const uint16_t onNative = graphics::TFTPalette::White;
|
||||
const uint16_t offNative = graphics::getThemeBodyBg();
|
||||
|
||||
const uint16_t onBe = (uint16_t)((onNative >> 8) | (onNative << 8));
|
||||
const uint16_t offBe = (uint16_t)((offNative >> 8) | (offNative << 8));
|
||||
|
||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||
const bool hasColorRegions = graphics::getTFTColorRegionCount() > 0;
|
||||
#endif
|
||||
|
||||
// Full-frame redraw into the offscreen canvas every call (no dirty-diff needed at these
|
||||
// resolutions), then SwapOnVSync() below presents it atomically for tear-free output.
|
||||
for (uint16_t y = 0; y < displayHeight; y++) {
|
||||
const uint32_t yByteIndex = (y / 8) * displayWidth;
|
||||
const uint8_t yByteMask = (uint8_t)(1 << (y & 7));
|
||||
|
||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||
if (hasColorRegions)
|
||||
graphics::beginTFTColorRow((int16_t)y);
|
||||
#endif
|
||||
|
||||
for (uint16_t x = 0; x < displayWidth; x++) {
|
||||
const bool isset = (buffer[x + yByteIndex] & yByteMask) != 0;
|
||||
|
||||
uint16_t be;
|
||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||
if (hasColorRegions)
|
||||
be = graphics::resolveTFTColorPixelRow((int16_t)x, isset, onBe, offBe);
|
||||
else
|
||||
be = isset ? onBe : offBe;
|
||||
#else
|
||||
be = isset ? onBe : offBe;
|
||||
#endif
|
||||
|
||||
const uint16_t c = (uint16_t)((be >> 8) | (be << 8)); // back to native RGB565
|
||||
const uint8_t r = (uint8_t)(((c >> 11) & 0x1F) << 3);
|
||||
const uint8_t g = (uint8_t)(((c >> 5) & 0x3F) << 2);
|
||||
const uint8_t b = (uint8_t)((c & 0x1F) << 3);
|
||||
offscreen->SetPixel(x, y, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||
// Regions are re-registered every frame by the renderers; clear so they don't accumulate.
|
||||
graphics::clearTFTColorRegions();
|
||||
#endif
|
||||
|
||||
// Present the finished frame at the next vsync; the returned canvas (the previously shown one)
|
||||
// becomes our next offscreen buffer.
|
||||
offscreen = matrix->SwapOnVSync(offscreen);
|
||||
}
|
||||
|
||||
void HUB75Native::sendCommand(uint8_t com)
|
||||
{
|
||||
if (!matrix || !offscreen)
|
||||
return;
|
||||
|
||||
switch (com) {
|
||||
case DISPLAYON:
|
||||
matrix->SetBrightness(brightness);
|
||||
break;
|
||||
case DISPLAYOFF:
|
||||
// rpi-rgb-led-matrix clamps brightness 0 up to 1 and its refresh thread keeps scanning the
|
||||
// presented canvas, so SetBrightness(0) alone leaves the last frame faintly lit. Present a
|
||||
// cleared frame so the panel actually goes black while asleep. (matrix->Clear() would only
|
||||
// clear the RGBMatrix's own buffer, not the FrameCanvas we swap in.) Wake repaints via the
|
||||
// next display().
|
||||
offscreen->Clear();
|
||||
offscreen = matrix->SwapOnVSync(offscreen);
|
||||
break;
|
||||
default:
|
||||
// Drop all other SSD1306 init/config commands - not meaningful for the matrix.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HUB75Native::setDisplayBrightness(uint8_t _brightness)
|
||||
{
|
||||
brightness = _brightness;
|
||||
if (matrix)
|
||||
matrix->SetBrightness(brightness);
|
||||
}
|
||||
|
||||
#endif // HAS_HUB75_NATIVE
|
||||
@@ -1017,6 +1017,39 @@ bool loadConfig(const char *configPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
#if !defined(HAS_HUB75_NATIVE)
|
||||
if (portduino_config.displayPanel == hub75) {
|
||||
std::cerr << "HUB75 display panel selected, but this build does not support HUB75" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
#endif
|
||||
// HUB75 RGB matrix (Raspberry Pi). Options map onto rgb_matrix::RGBMatrix::Options +
|
||||
// RuntimeOptions; the library owns its GPIO pins so nothing is read via readGPIOFromYaml.
|
||||
if (portduino_config.displayPanel == hub75 && yamlConfig["Display"]["HUB75"]) {
|
||||
YAML::Node hub75 = yamlConfig["Display"]["HUB75"];
|
||||
portduino_config.hub75_hardware_mapping = hub75["HardwareMapping"].as<std::string>("regular");
|
||||
portduino_config.hub75_rows = hub75["Rows"].as<int>(64);
|
||||
portduino_config.hub75_cols = hub75["Cols"].as<int>(64);
|
||||
portduino_config.hub75_chain_length = hub75["ChainLength"].as<int>(1);
|
||||
portduino_config.hub75_parallel = hub75["Parallel"].as<int>(1);
|
||||
portduino_config.hub75_pwm_bits = hub75["PWMBits"].as<int>(11);
|
||||
portduino_config.hub75_pwm_lsb_nanoseconds = hub75["PWMLSBNanoseconds"].as<int>(130);
|
||||
portduino_config.hub75_brightness = hub75["Brightness"].as<int>(100);
|
||||
portduino_config.hub75_scan_mode = hub75["ScanMode"].as<int>(0);
|
||||
portduino_config.hub75_row_address_type = hub75["RowAddressType"].as<int>(0);
|
||||
portduino_config.hub75_multiplexing = hub75["Multiplexing"].as<int>(0);
|
||||
portduino_config.hub75_disable_hardware_pulsing = hub75["DisableHardwarePulsing"].as<bool>(false);
|
||||
portduino_config.hub75_show_refresh_rate = hub75["ShowRefreshRate"].as<bool>(false);
|
||||
portduino_config.hub75_inverse_colors = hub75["InverseColors"].as<bool>(false);
|
||||
portduino_config.hub75_led_rgb_sequence = hub75["RGBSequence"].as<std::string>("RGB");
|
||||
portduino_config.hub75_pixel_mapper_config = hub75["PixelMapper"].as<std::string>("");
|
||||
portduino_config.hub75_panel_type = hub75["PanelType"].as<std::string>("");
|
||||
portduino_config.hub75_limit_refresh_rate_hz = hub75["LimitRefreshRateHz"].as<int>(0);
|
||||
portduino_config.hub75_gpio_slowdown = hub75["GPIOSlowdown"].as<int>(1);
|
||||
// The BaseUI framebuffer geometry is the full panel size in pixels.
|
||||
portduino_config.displayWidth = portduino_config.hub75_cols * portduino_config.hub75_chain_length;
|
||||
portduino_config.displayHeight = portduino_config.hub75_rows * portduino_config.hub75_parallel;
|
||||
}
|
||||
}
|
||||
if (yamlConfig["Touchscreen"]) {
|
||||
if (yamlConfig["Touchscreen"]["Module"].as<std::string>("") == "XPT2046")
|
||||
|
||||
@@ -34,7 +34,7 @@ inline const std::unordered_map<std::string, std::string> configProducts = {
|
||||
{"RAK6421-13300-S1", "lora-RAK6421-13300-slot1.yaml"},
|
||||
{"RAK6421-13300-S2", "lora-RAK6421-13300-slot2.yaml"}};
|
||||
|
||||
enum screen_modules { no_screen, x11, fb, st7789, st7735, st7735s, st7796, ili9341, ili9342, ili9486, ili9488, hx8357d };
|
||||
enum screen_modules { no_screen, x11, fb, st7789, st7735, st7735s, st7796, ili9341, ili9342, ili9486, ili9488, hx8357d, hub75 };
|
||||
enum touchscreen_modules { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 };
|
||||
enum portduino_log_level { level_error, level_warn, level_info, level_debug, level_trace };
|
||||
enum lora_module_enum {
|
||||
@@ -83,7 +83,7 @@ extern struct portduino_config_struct {
|
||||
std::map<screen_modules, std::string> screen_names = {{x11, "X11"}, {fb, "FB"}, {st7789, "ST7789"},
|
||||
{st7735, "ST7735"}, {st7735s, "ST7735S"}, {st7796, "ST7796"},
|
||||
{ili9341, "ILI9341"}, {ili9342, "ILI9342"}, {ili9486, "ILI9486"},
|
||||
{ili9488, "ILI9488"}, {hx8357d, "HX8357D"}};
|
||||
{ili9488, "ILI9488"}, {hx8357d, "HX8357D"}, {hub75, "HUB75"}};
|
||||
|
||||
lora_module_enum lora_module;
|
||||
bool has_rfswitch_table = false;
|
||||
@@ -147,6 +147,29 @@ extern struct portduino_config_struct {
|
||||
pinMapping displayBacklightPWMChannel = {"Display", "BacklightPWMChannel"};
|
||||
pinMapping displayReset = {"Display", "Reset"};
|
||||
|
||||
// Display -> HUB75 (Raspberry Pi RGB matrix via hzeller/rpi-rgb-led-matrix).
|
||||
// These mirror rgb_matrix::RGBMatrix::Options + RuntimeOptions; the library owns the GPIO
|
||||
// pins (chosen by hub75_hardware_mapping), so there are no per-pin mappings here.
|
||||
std::string hub75_hardware_mapping = "regular";
|
||||
int hub75_rows = 64;
|
||||
int hub75_cols = 64;
|
||||
int hub75_chain_length = 1;
|
||||
int hub75_parallel = 1;
|
||||
int hub75_pwm_bits = 11;
|
||||
int hub75_pwm_lsb_nanoseconds = 130;
|
||||
int hub75_brightness = 100; // percent, 1..100
|
||||
int hub75_scan_mode = 0;
|
||||
int hub75_row_address_type = 0;
|
||||
int hub75_multiplexing = 0;
|
||||
bool hub75_disable_hardware_pulsing = false;
|
||||
bool hub75_show_refresh_rate = false;
|
||||
bool hub75_inverse_colors = false;
|
||||
std::string hub75_led_rgb_sequence = "RGB";
|
||||
std::string hub75_pixel_mapper_config = "";
|
||||
std::string hub75_panel_type = "";
|
||||
int hub75_limit_refresh_rate_hz = 0;
|
||||
int hub75_gpio_slowdown = 1; // RuntimeOptions; higher for faster Pis / long cables
|
||||
|
||||
// Touchscreen
|
||||
std::string touchscreen_spi_dev = "";
|
||||
int touchscreen_spi_dev_int = 0;
|
||||
@@ -423,6 +446,30 @@ extern struct portduino_config_struct {
|
||||
|
||||
out << YAML::Key << "OffsetRotate" << YAML::Value << displayOffsetRotate;
|
||||
|
||||
if (displayPanel == hub75) {
|
||||
out << YAML::Key << "HUB75" << YAML::Value << YAML::BeginMap;
|
||||
out << YAML::Key << "HardwareMapping" << YAML::Value << hub75_hardware_mapping;
|
||||
out << YAML::Key << "Rows" << YAML::Value << hub75_rows;
|
||||
out << YAML::Key << "Cols" << YAML::Value << hub75_cols;
|
||||
out << YAML::Key << "ChainLength" << YAML::Value << hub75_chain_length;
|
||||
out << YAML::Key << "Parallel" << YAML::Value << hub75_parallel;
|
||||
out << YAML::Key << "PWMBits" << YAML::Value << hub75_pwm_bits;
|
||||
out << YAML::Key << "PWMLSBNanoseconds" << YAML::Value << hub75_pwm_lsb_nanoseconds;
|
||||
out << YAML::Key << "Brightness" << YAML::Value << hub75_brightness;
|
||||
out << YAML::Key << "ScanMode" << YAML::Value << hub75_scan_mode;
|
||||
out << YAML::Key << "RowAddressType" << YAML::Value << hub75_row_address_type;
|
||||
out << YAML::Key << "Multiplexing" << YAML::Value << hub75_multiplexing;
|
||||
out << YAML::Key << "DisableHardwarePulsing" << YAML::Value << hub75_disable_hardware_pulsing;
|
||||
out << YAML::Key << "ShowRefreshRate" << YAML::Value << hub75_show_refresh_rate;
|
||||
out << YAML::Key << "InverseColors" << YAML::Value << hub75_inverse_colors;
|
||||
out << YAML::Key << "RGBSequence" << YAML::Value << hub75_led_rgb_sequence;
|
||||
out << YAML::Key << "PixelMapper" << YAML::Value << hub75_pixel_mapper_config;
|
||||
out << YAML::Key << "PanelType" << YAML::Value << hub75_panel_type;
|
||||
out << YAML::Key << "LimitRefreshRateHz" << YAML::Value << hub75_limit_refresh_rate_hz;
|
||||
out << YAML::Key << "GPIOSlowdown" << YAML::Value << hub75_gpio_slowdown;
|
||||
out << YAML::EndMap; // HUB75
|
||||
}
|
||||
|
||||
out << YAML::EndMap; // Display
|
||||
}
|
||||
|
||||
|
||||
@@ -30,4 +30,12 @@
|
||||
#define TB_LEFT (uint8_t) portduino_config.tbLeftPin.pin
|
||||
#define TB_RIGHT (uint8_t) portduino_config.tbRightPin.pin
|
||||
#define TB_PRESS (uint8_t) portduino_config.tbPressPin.pin
|
||||
#endif
|
||||
|
||||
// HUB75 RGB-matrix panel support on Raspberry Pi (Portduino) turns on automatically when the
|
||||
// hzeller/rpi-rgb-led-matrix library is installed - its headers land on the include path via
|
||||
// `pkg-config rgbmatrix` (wired up in variants/native/portduino/platformio.ini). When absent,
|
||||
// HAS_HUB75_NATIVE stays undefined and the backend compiles out. See src/graphics/HUB75Display.cpp.
|
||||
#if defined(ARCH_PORTDUINO) && __has_include(<led-matrix.h>)
|
||||
#define HAS_HUB75_NATIVE 1
|
||||
#endif
|
||||
Reference in New Issue
Block a user