Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 83 additions & 1 deletion wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,10 @@ void BusNetwork::cleanup() {
// BusHub75Matrix "global" variables (static members)
MatrixPanel_I2S_DMA* BusHub75Matrix::activeDisplay = nullptr;
VirtualMatrixPanel* BusHub75Matrix::activeFourScanPanel = nullptr;
uint8_t BusHub75Matrix::activeVRows = 1;
uint8_t BusHub75Matrix::activeVCols = 1;
uint8_t BusHub75Matrix::activeVChainType = 0;

HUB75_I2S_CFG BusHub75Matrix::activeMXconfig = HUB75_I2S_CFG();
uint8_t BusHub75Matrix::activeType = 0;
uint8_t BusHub75Matrix::instanceCount = 0;
Expand Down Expand Up @@ -1096,7 +1100,85 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh
fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH);
fourScanPanel->setRotation(0);
break;
}

// WLEDMM: non-horizontal panel arrangement for NORMAL (non four-scan) panels.
// A HUB75 chain is electrically always horizontal: N panels form an area of
// (panel width * N) x panel height. Stacking the panels physically therefore needs a mapping
// from logical to physical coordinates. VirtualMatrixPanel does exactly that, but so far it was
// only created for four-scan panels, and there hard-coded as (1, chain_length) - always a single
// row. Without this branch show() iterates over display->width() and wraps the image onto the
// physical chain width. Measured on device: a logical 64x128 area came out as 128x64, with the
// upper half red on BOTH panels instead of left red / right blue.
//
// The arrangement is carried in the bus "pin" field: [0] = chain length, [1] = rows,
// [2] = columns, [3] = PANEL_CHAIN_TYPE (0 = CHAIN_NONE, 1 = TOP_LEFT_DOWN, 2 = TOP_RIGHT_DOWN,
// 3 = BOTTOM_LEFT_UP, 4 = BOTTOM_RIGHT_UP, 5..8 = the ZigZag variants). Which chain type is
// correct depends on how the panels are wired and mounted. Example for 4 panels of 64x32
// stacked vertically and chained bottom-left up in a zigzag: pin: [4, 4, 1, 8].
// Unset values are normalised to 1, so an existing pin: [2] becomes [2, 1, 1, 0] and the
// arrangement stays dormant - behaviour is unchanged for every existing configuration.
// AI: below section was generated by an AI
default:
{
unsigned vRows = (bc.pins[1] == 255 || bc.pins[1] == 0) ? 1 : bc.pins[1];
unsigned vCols = (bc.pins[2] == 255 || bc.pins[2] == 0) ? 1 : bc.pins[2];
unsigned vType = (bc.pins[3] == 255) ? 0 : bc.pins[3];
if (vType > CHAIN_BOTTOM_LEFT_UP_ZZ) vType = 0; // ignore out-of-range values

// Keep the arrangement metadata in sync with the configuration on EVERY pass, not only
// when the panel object is created. getPins() reports these members so the arrangement
// survives a config save - and when a bus is re-created while the display object is
// re-used (see "continue with existing matrix object" above), fourScanPanel is already
// set on this fresh instance, so the creation block below is skipped. Assigning only in
// there would leave the new instance at the (1,1,0) defaults, and the next config save
// would silently overwrite a working arrangement with "none" - unrecoverable without
// physical access.
// Deliberately also assigned when the arrangement is rejected below: what the user
// configured stays in the configuration, and the warning repeats on every boot.
_vRows = vRows; _vCols = vCols; _vChainType = vType;

// A re-used display brings its mapping along (see "continue with existing matrix object"
// above), and the re-use check only looks at the physical configuration - the arrangement
// is not part of it. Keep the mapping only while it still describes what is configured;
// otherwise drop it, so the block below builds the right one and switching the
// arrangement off falls back to the plain chain instead of rendering through the old map.
// Not deleted on purpose: see the note next to the disabled delete in cleanup().
if (fourScanPanel
&& !((activeVRows == vRows) && (activeVCols == vCols) && (activeVChainType == vType))) {
fourScanPanel = nullptr;
}

if (!fourScanPanel && ((vRows > 1) || (vCols > 1))) {
// The arrangement must describe exactly the panels that are chained. chain_length is
// already capped to a sane value above, so this also bounds vRows and vCols.
if (vRows * vCols != mxconfig.chain_length) {
USER_PRINTF("MatrixPanel_I2S_DMA WARNING: %ux%u panels != chain length %u - arrangement ignored.\n",
vRows, vCols, mxconfig.chain_length);
} else {
// The display is fully initialised at this point - VirtualMatrixPanel only remaps
// coordinates, so the allocation is the one thing left that can fail here. Without a
// panel object the plain horizontal chain is used, which is the previous behaviour.
fourScanPanel = new(std::nothrow) VirtualMatrixPanel((*display), vRows, vCols,
mxconfig.mx_width, mxconfig.mx_height,
(PANEL_CHAIN_TYPE)vType);
if (fourScanPanel == nullptr) {
USER_PRINTLN("MatrixPanel_I2S_DMA WARNING: not enough memory for the virtual arrangement - using the plain chain.");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else {
USER_PRINTF("MatrixPanel_I2S_DMA virtual arrangement: %u rows x %u cols, chain type %u.\n",
vRows, vCols, vType);
fourScanPanel->setRotation(0);
}
}
}

// Record what the mapping in use actually represents, so the check above can tell a
// re-used one apart from a reconfigured arrangement.
if (fourScanPanel) { activeVRows = vRows; activeVCols = vCols; activeVChainType = vType; }
else { activeVRows = 1; activeVCols = 1; activeVChainType = 0; }
}
break;
// AI: end
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if (_valid) {
_panelWidth = fourScanPanel ? fourScanPanel->width() : display->width(); // cache width - it will never change
Expand Down
21 changes: 18 additions & 3 deletions wled00/bus_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ struct BusConfig {
if ((type >= TYPE_NET_DDP_RGB) && (type < (TYPE_NET_DDP_RGB + 16))) nPins = 4; // virtual network bus. 4 "pins" store IP address
else if ((type > 47) && (type < 63)) nPins = 2; // (data + clock / SPI) busses - two pins
else if (IS_PWM(type)) nPins = NUM_PWM_PINS(type); // PWM needs 1..5 pins
else if (type >= TYPE_HUB75MATRIX && type <= (TYPE_HUB75MATRIX + 10)) nPins = 1; // HUB75 does not use LED pins, but we need to preserve the "chain length" parameter
// HUB75 does not use LED pins. The "pin" array carries panel arrangement instead:
// [0] chain length, [1] virtual rows, [2] virtual cols, [3] PANEL_CHAIN_TYPE
// Rows/cols > 1 describe a non-horizontal arrangement (e.g. panels stacked vertically),
// which is handled by VirtualMatrixPanel. Was 1 before - then only the chain length survived
// a config save, and any arrangement was silently lost.
else if (type >= TYPE_HUB75MATRIX && type <= (TYPE_HUB75MATRIX + 10)) nPins = 4;
for (uint8_t i = 0; i < min(unsigned(nPins), sizeof(pins)/sizeof(pins[0])); i++) pins[i] = ppins[i]; //softhack007 fix for potential array out-of-bounds access
}

Expand Down Expand Up @@ -440,9 +445,13 @@ class BusHub75Matrix : public Bus {
void setBrightness(uint8_t b, bool immediate) override;

uint8_t getPins(uint8_t* pinArray) const override {
// No real LED pins - we report back the panel arrangement so it survives a config save.
pinArray[0] = activeMXconfig.chain_length;
return 1;
} // Fake value due to keep finaliseInit happy
pinArray[1] = _vRows;
pinArray[2] = _vCols;
pinArray[3] = _vChainType;
return 4;
} // Fake values due to keep finaliseInit happy

void deallocatePins();

Expand All @@ -457,12 +466,18 @@ class BusHub75Matrix : public Bus {
private:
unsigned _panelWidth = 0;
uint8_t _colorOrder = COL_ORDER_RGB;
uint8_t _vRows = 1; // virtual panel rows (1 = classic horizontal chain)
uint8_t _vCols = 1; // virtual panel columns
uint8_t _vChainType = 0; // PANEL_CHAIN_TYPE, 0 = CHAIN_NONE
CRGB *_ledBuffer = nullptr;
byte *_ledsDirty = nullptr;
// C++ dirty trick: private static variables are actually _not_ part of the class (however only visibile to class instances).
// These variables persist when BusHub75Matrix gets deleted.
static MatrixPanel_I2S_DMA *activeDisplay; // active display object
static VirtualMatrixPanel *activeFourScanPanel; // active fourScan object
// WLEDMM: arrangement the active fourScan object was built for - the re-use check for the
// display only covers the physical configuration, so the arrangement is tracked separately.
static uint8_t activeVRows, activeVCols, activeVChainType;
static HUB75_I2S_CFG activeMXconfig; // last used mxconfig
static uint8_t activeType; // last used type
static uint8_t instanceCount; // active instances - 0 or 1
Expand Down