diff --git a/CLAUDE.md b/CLAUDE.md index 3e37166..2f2773f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,7 +58,7 @@ The sketch controls `GAUGE_COUNT` stepper-motor gauges using a trapezoidal veloc ### Key data structures -- `GaugePins` — hardware pin mapping per gauge (dir, step, enable, active-high/low polarity flags, `ledCount`). Declared `constexpr` so `TOTAL_LEDS` can be computed from it at compile time. Configured in the `gaugePins[]` array at the top. +- `GaugePins` — hardware pin mapping per gauge (dir, step, enable, active-high/low polarity flags, `ledOrder` string). Declared `constexpr` so `TOTAL_LEDS` can be computed from it at compile time. Configured in the `gaugePins[]` array at the top. - `Gauge` — per-gauge runtime state: position, target, velocity, accel, homing state machine, sweep mode. ### Motion control (`updateGauge`) @@ -76,7 +76,7 @@ When `sweepEnabled`, `updateSweepTarget` bounces `targetPos` between `minPos` an ### LED strip -One shared WS2812B strip is driven from `LED_DATA_PIN` (currently 22). Each gauge owns a contiguous segment of the strip; `gaugePins[i].ledCount` sets the segment length (0 = no LEDs). `TOTAL_LEDS` is computed at compile time via `constexpr sumLedCounts()` — no manual constant to keep in sync. Per-gauge offsets into the flat `leds[]` array are computed once in `setup()` into `gaugeLedOffset[]`. LED commands and effects mark the strip dirty, and `FastLED.show()` is called once per main-loop iteration if anything changed. +One shared WS2812B strip is driven from `LED_DATA_PIN` (currently 22). Each gauge owns a contiguous segment of the strip; `gaugePins[i].ledOrder` is a per-LED type string (one char per LED, `'G'` = GRB-ordered, `'R'` = RGB-ordered) and its length defines the segment length (empty string = no LEDs). `TOTAL_LEDS` is computed at compile time via `constexpr sumLedCounts()` — no manual constant to keep in sync. Per-gauge offsets and counts are cached in `setup()` into `gaugeLedOffset[]` and `gaugeLedCount[]`. The strip is initialised as `GRB`; writes to RGB-ordered LEDs are R/G-swapped via the `writeLed`/`readLed` helpers so callers always work in logical RGB. LED commands and effects mark the strip dirty, and `FastLED.show()` is called once per main-loop iteration if anything changed. ### Serial command protocol @@ -104,6 +104,6 @@ All commands reply `OK` or `ERR BAD_ID` / `ERR BAD_CMD` etc. ### Adding gauges 1. Increment `GAUGE_COUNT`. -2. Add a `constexpr GaugePins` entry to `gaugePins[]` (including `ledCount`). +2. Add a `constexpr GaugePins` entry to `gaugePins[]` (including the `ledOrder` string — one char per LED, `'G'` for GRB or `'R'` for RGB). 3. Tune `maxPos` and `homingBackoffSteps` in the corresponding `Gauge` default or at runtime. -4. `TOTAL_LEDS` and `gaugeLedOffset[]` update automatically — no manual changes needed. +4. `TOTAL_LEDS`, `gaugeLedOffset[]`, and `gaugeLedCount[]` update automatically — no manual changes needed. diff --git a/Gaugecontroller/Gaugecontroller.ino b/Gaugecontroller/Gaugecontroller.ino index 717bb89..e84be18 100644 --- a/Gaugecontroller/Gaugecontroller.ino +++ b/Gaugecontroller/Gaugecontroller.ino @@ -226,19 +226,23 @@ struct GaugePins { bool dirInverted; bool stepActiveHigh; bool enableActiveLow; - uint8_t ledCount; // LEDs assigned to this gauge + const char* ledOrder; // one char per LED: 'G' = GRB, 'R' = RGB; length defines ledCount }; constexpr GaugePins gaugePins[GAUGE_COUNT] = { - // dir, step, en, dirInv, stepHigh, enActiveLow, leds - {50, 51, -1, false, true, true, 7}, // Gauge 0 - {8, 9, -1, true, true, true, 7}, // Gauge 1 - {52, 53, -1, false, true, true, 7}, // Gauge 2 - {48, 49, -1, false, true, true, 7}, // Gauge 3 + // dir, step, en, dirInv, stepHigh, enActiveLow, ledOrder + {50, 51, -1, false, true, true, "RRRGGRR"}, // Gauge 0 + {8, 9, -1, true, true, true, "GGGRRRR"}, // Gauge 1 + {52, 53, -1, false, true, true, "GGGRRRR"}, // Gauge 2 + {48, 49, -1, false, true, true, "GGGRRRR"}, // Gauge 3 }; +constexpr uint8_t cstrLen(const char* s) { + return *s ? uint8_t(1 + cstrLen(s + 1)) : uint8_t(0); +} + constexpr uint8_t sumLedCounts(uint8_t i = 0) { - return i >= GAUGE_COUNT ? 0 : gaugePins[i].ledCount + sumLedCounts(i + 1); + return i >= GAUGE_COUNT ? 0 : cstrLen(gaugePins[i].ledOrder) + sumLedCounts(i + 1); } static const uint8_t TOTAL_LEDS = sumLedCounts(); @@ -298,9 +302,40 @@ String rxLine; CRGB leds[TOTAL_LEDS]; uint8_t gaugeLedOffset[GAUGE_COUNT]; +uint8_t gaugeLedCount[GAUGE_COUNT]; BlinkState blinkState[TOTAL_LEDS]; bool ledsDirty = false; +// FastLED drives the shared strip as RGB. Each gauge's ledOrder string marks per-LED +// type ('R' = RGB, 'G' = GRB); writes to GRB-ordered LEDs pre-swap R and G to compensate. +inline bool ledNeedsRgSwap(uint8_t globalIdx) { + for (uint8_t i = 0; i < GAUGE_COUNT; i++) { + uint8_t off = gaugeLedOffset[i]; + if (globalIdx >= off && globalIdx < off + gaugeLedCount[i]) { + char c = gaugePins[i].ledOrder[globalIdx - off]; + return c == 'G' || c == 'g'; + } + } + return false; +} + +inline CRGB encodeForStrip(uint8_t globalIdx, CRGB color) { + if (ledNeedsRgSwap(globalIdx)) { + uint8_t tmp = color.r; + color.r = color.g; + color.g = tmp; + } + return color; +} + +inline void writeLed(uint8_t globalIdx, CRGB color) { + leds[globalIdx] = encodeForStrip(globalIdx, color); +} + +inline CRGB readLed(uint8_t globalIdx) { + return encodeForStrip(globalIdx, leds[globalIdx]); +} + // Sends one-line command replies back over the control port. // // Serial protocol summary. @@ -854,8 +889,8 @@ bool parseVfd(const String& line) { bool parseLedQuery(const String& line) { if (line == "LED?") { for (uint8_t i = 0; i < GAUGE_COUNT; i++) { - for (uint8_t j = 0; j < gaugePins[i].ledCount; j++) { - const CRGB& c = leds[gaugeLedOffset[i] + j]; + for (uint8_t j = 0; j < gaugeLedCount[i]; j++) { + CRGB c = readLed(gaugeLedOffset[i] + j); CMD_PORT.print("LED "); CMD_PORT.print(i); CMD_PORT.print(' '); @@ -884,13 +919,13 @@ bool parseLed(const String& line) { char* dash = strchr(idxToken, '-'); int idxFirst = atoi(idxToken); int idxLast = dash ? atoi(dash + 1) : idxFirst; - if (idxFirst < 0 || idxLast >= gaugePins[id].ledCount || idxFirst > idxLast) { + if (idxFirst < 0 || idxLast >= gaugeLedCount[id] || idxFirst > idxLast) { sendReply("ERR BAD_IDX"); return true; } CRGB color(constrain(r, 0, 255), constrain(g, 0, 255), constrain(b, 0, 255)); for (int i = idxFirst; i <= idxLast; i++) { blinkState[gaugeLedOffset[id] + i].active = false; - leds[gaugeLedOffset[id] + i] = color; + writeLed(gaugeLedOffset[id] + i, color); } ledsDirty = true; sendReply("OK"); @@ -913,7 +948,7 @@ bool parseBlink(const String& line) { char* dash = strchr(idxToken, '-'); int idxFirst = atoi(idxToken); int idxLast = dash ? atoi(dash + 1) : idxFirst; - if (idxFirst < 0 || idxLast >= gaugePins[id].ledCount || idxFirst > idxLast) { + if (idxFirst < 0 || idxLast >= gaugeLedCount[id] || idxFirst > idxLast) { sendReply("ERR BAD_IDX"); return true; } @@ -934,13 +969,13 @@ bool parseBlink(const String& line) { uint8_t globalIdx = gaugeLedOffset[id] + i; BlinkState& bs = blinkState[globalIdx]; bs.fx = FX_BLINK; - bs.onColor = (count == 7) ? color : leds[globalIdx]; + bs.onColor = (count == 7) ? color : readLed(globalIdx); bs.onMs = (uint16_t)onMs; bs.offMs = (uint16_t)offMs; bs.currentlyOn = true; bs.lastMs = nowMs; bs.active = true; - leds[globalIdx] = bs.onColor; + writeLed(globalIdx, bs.onColor); } ledsDirty = true; sendReply("OK"); @@ -958,7 +993,7 @@ bool parseBreathe(const String& line) { char* dash = strchr(idxToken, '-'); int idxFirst = atoi(idxToken); int idxLast = dash ? atoi(dash + 1) : idxFirst; - if (idxFirst < 0 || idxLast >= gaugePins[id].ledCount || idxFirst > idxLast) { + if (idxFirst < 0 || idxLast >= gaugeLedCount[id] || idxFirst > idxLast) { sendReply("ERR BAD_IDX"); return true; } if (periodMs <= 0) { sendReply("ERR BAD_TIME"); return true; } @@ -973,7 +1008,7 @@ bool parseBreathe(const String& line) { bs.cyclePos = 0; bs.lastMs = nowMs; bs.active = true; - leds[gi] = CRGB::Black; + writeLed(gi, CRGB::Black); } ledsDirty = true; sendReply("OK"); @@ -991,7 +1026,7 @@ bool parseDflash(const String& line) { char* dash = strchr(idxToken, '-'); int idxFirst = atoi(idxToken); int idxLast = dash ? atoi(dash + 1) : idxFirst; - if (idxFirst < 0 || idxLast >= gaugePins[id].ledCount || idxFirst > idxLast) { + if (idxFirst < 0 || idxLast >= gaugeLedCount[id] || idxFirst > idxLast) { sendReply("ERR BAD_IDX"); return true; } CRGB color(constrain(r, 0, 255), constrain(g, 0, 255), constrain(b, 0, 255)); @@ -1004,7 +1039,7 @@ bool parseDflash(const String& line) { bs.dphase = 0; bs.lastMs = nowMs; bs.active = true; - leds[gi] = color; // phase 0 = on + writeLed(gi, color); // phase 0 = on } ledsDirty = true; sendReply("OK"); @@ -1017,7 +1052,7 @@ void updateBlink() { bool changed = false; for (uint8_t i = 0; i < GAUGE_COUNT; i++) { - for (uint8_t j = 0; j < gaugePins[i].ledCount; j++) { + for (uint8_t j = 0; j < gaugeLedCount[i]; j++) { uint8_t gi = gaugeLedOffset[i] + j; BlinkState& bs = blinkState[gi]; if (!bs.active) continue; @@ -1028,7 +1063,7 @@ void updateBlink() { if ((nowMs - bs.lastMs) >= period) { bs.currentlyOn = !bs.currentlyOn; bs.lastMs = nowMs; - leds[gi] = bs.currentlyOn ? bs.onColor : CRGB::Black; + writeLed(gi, bs.currentlyOn ? bs.onColor : CRGB::Black); changed = true; } break; @@ -1044,8 +1079,9 @@ void updateBlink() { uint8_t bri = (bs.cyclePos < half) ? (uint8_t)((uint32_t)bs.cyclePos * 255 / half) : (uint8_t)((uint32_t)(bs.periodMs - bs.cyclePos) * 255 / half); - leds[gi] = bs.onColor; - leds[gi].nscale8(bri ? bri : 1); + CRGB scaled = bs.onColor; + scaled.nscale8(bri ? bri : 1); + writeLed(gi, scaled); changed = true; break; } @@ -1054,7 +1090,7 @@ void updateBlink() { if ((nowMs - bs.lastMs) >= dur[bs.dphase]) { bs.lastMs = nowMs; bs.dphase = (bs.dphase + 1) & 3; - leds[gi] = (bs.dphase == 0 || bs.dphase == 2) ? bs.onColor : CRGB::Black; + writeLed(gi, (bs.dphase == 0 || bs.dphase == 2) ? bs.onColor : CRGB::Black); changed = true; } break; @@ -1135,10 +1171,11 @@ void setup() { // Flatten the per-gauge LED counts into offsets on the shared strip. uint8_t ledOff = 0; for (uint8_t i = 0; i < GAUGE_COUNT; i++) { + gaugeLedCount[i] = cstrLen(gaugePins[i].ledOrder); gaugeLedOffset[i] = ledOff; - ledOff += gaugePins[i].ledCount; + ledOff += gaugeLedCount[i]; } - FastLED.addLeds(leds, TOTAL_LEDS); + FastLED.addLeds(leds, TOTAL_LEDS); FastLED.setBrightness(255); FastLED.show();