Files
arduino_gauge_controller/docs/superpowers/specs/2026-05-21-gaugecontroller-v2-design.md
Adrian A. Baumann 7c3068ff3a docs: add Gaugecontroller v2.0 design spec
Describes the two-part refactor: gauge_config.h for centralised pin and
motion defaults, and uniform sscanf parsing across all parse* functions.
2026-05-21 21:47:07 +02:00

5.1 KiB

Gaugecontroller v2.0 Design

Date: 2026-05-21
Branch: Stepper-Only
Scope: Code quality / architecture — same features, better structure. No behaviour change.

Goal

Eliminate scattered magic constants and inconsistent parsing patterns. A developer adding or tuning a gauge should only need to edit one file.

What is NOT changing

  • ISR logic, Q16 fixed-point stepping, trapezoidal velocity profile
  • Serial protocol commands and responses
  • Runtime Gauge struct fields (stay float for velocity, speed, accel)
  • LED code (absent on this branch; out of scope)

Section 1: gauge_config.h

Create Gaugecontroller/gauge_config.h alongside the sketch.

New struct

struct GaugeConfig {
  // Hardware
  uint8_t dirPin;
  uint8_t stepPin;
  int8_t  enablePin;       // -1 = no enable pin
  bool    dirInverted;
  bool    stepActiveHigh;
  bool    enableActiveLow;

  // Motion defaults (integers — cast to float in setup())
  long  minPos;
  long  maxPos;
  long  homingBackoffSteps;
  int   maxSpeed;          // steps/s
  int   accel;             // steps/s²
  int   homingSpeed;       // steps/s
};

Config table

constexpr GaugeConfig gaugeConfigs[] = {
  // dir  step  en   dirInv stepHi enLow  min   max   backoff  speed  accel  homeSpd
  {  48,  49,  -1,  false, true,  true,    0,  3780,   3800,   4000,  6000,    500 },
  {   8,   9,  -1,  true,  true,  true,    0,  3780,   3800,   4000,  6000,    500 },
  {  52,  53,  -1,  false, true,  true,    0,  3780,   3800,   4000,  6000,    500 },
  {  50,  51,  -1,  false, true,  true,    0,  3780,   3800,   4000,  6000,    500 },
};

static const uint8_t GAUGE_COUNT =
    sizeof(gaugeConfigs) / sizeof(gaugeConfigs[0]);

Adding gauge 5 is one new table row. GAUGE_COUNT updates automatically.

Changes to Gaugecontroller.ino

  • Remove constexpr GaugePins gaugePins[], struct GaugePins, and the hardcoded GAUGE_COUNT.
  • Add #include "gauge_config.h".
  • In setup(), initialise each Gauge's motion defaults from gaugeConfigs[i]:
gauges[i].minPos             = gaugeConfigs[i].minPos;
gauges[i].maxPos             = gaugeConfigs[i].maxPos;
gauges[i].homingBackoffSteps = gaugeConfigs[i].homingBackoffSteps;
gauges[i].maxSpeed           = (float)gaugeConfigs[i].maxSpeed;
gauges[i].accel              = (float)gaugeConfigs[i].accel;
gauges[i].homingSpeed        = (float)gaugeConfigs[i].homingSpeed;
  • All existing references to gaugePins[i].dirPin etc. become gaugeConfigs[i].dirPin etc. (field names are identical).
  • Remove the hardcoded default initialisers from the Gauge struct definition (maxPos = 3780, homingBackoffSteps = 3800, maxSpeed = 4000.0f, accel = 6000.0f, homingSpeed = 500.0f, minPos = 0). These fields become zero-initialised and are then set from gaugeConfigs[i] in setup(), eliminating the risk of the struct defaults and config table silently diverging.

Section 2: Uniform sscanf parsing

Three parse* functions currently use manual indexOf/substring. Convert them to sscanf to match the rest of the parser.

parseSpeed

bool parseSpeed(const String& line) {
  int id; float speed;
  if (sscanf(line.c_str(), "SPEED %d %f", &id, &speed) == 2) {
    if (id < 0 || id >= GAUGE_COUNT) { sendReply("ERR BAD_ID");    return true; }
    if (speed <= 0.0f)               { sendReply("ERR BAD_SPEED"); return true; }
    gauges[id].maxSpeed = speed;
    sendReply("OK");
    return true;
  }
  return false;
}

parseAccel

bool parseAccel(const String& line) {
  int id; float accel;
  if (sscanf(line.c_str(), "ACCEL %d %f", &id, &accel) == 2) {
    if (id < 0 || id >= GAUGE_COUNT) { sendReply("ERR BAD_ID");    return true; }
    if (accel <= 0.0f)               { sendReply("ERR BAD_ACCEL"); return true; }
    gauges[id].accel = accel;
    sendReply("OK");
    return true;
  }
  return false;
}

parseSweep

bool parseSweep(const String& line) {
  int id; float accel, speed;
  if (sscanf(line.c_str(), "SWEEP %d %f %f", &id, &accel, &speed) == 3) {
    if (id < 0 || id >= GAUGE_COUNT) { sendReply("ERR BAD_ID"); return true; }
    Gauge& g = gauges[id];
    if (accel <= 0.0f || speed <= 0.0f) {
      g.sweepEnabled = false;
      g.velocity = 0.0f;
      stopTimerStepping(id);
      sendReply("OK");
      return true;
    }
    g.accel        = accel;
    g.maxSpeed     = speed;
    g.sweepEnabled = true;
    g.sweepTowardMax = true;
    atomicWriteLong(g.targetPos, g.maxPos);
    sendReply("OK");
    return true;
  }
  return false;
}

No change to accepted syntax, error codes, or response format.


File inventory

File Change
Gaugecontroller/gauge_config.h New — all pin + motion defaults
Gaugecontroller/Gaugecontroller.ino Remove GaugePins, add include, update setup(), rewrite 3 parsers

Success criteria

  • Sketch compiles cleanly with arduino-cli compile --fqbn arduino:avr:mega Gaugecontroller
  • GAUGE_COUNT need not be edited when adding a gauge — only gaugeConfigs[] changes
  • No indexOf/substring remain in any parse* function
  • All existing protocol commands behave identically to v1