Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
65 changes: 64 additions & 1 deletion wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ void BusNetwork::cleanup() {
// BusHub75Matrix "global" variables (static members)
MatrixPanel_I2S_DMA* BusHub75Matrix::activeDisplay = nullptr;
VirtualMatrixPanel* BusHub75Matrix::activeFourScanPanel = nullptr;

HUB75_I2S_CFG BusHub75Matrix::activeMXconfig = HUB75_I2S_CFG();
uint8_t BusHub75Matrix::activeType = 0;
uint8_t BusHub75Matrix::instanceCount = 0;
Expand Down Expand Up @@ -1096,7 +1097,69 @@ 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;

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);
}
}
}
}
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
18 changes: 15 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,6 +466,9 @@ 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).
Expand Down