Compare commits
2 Commits
00b9287d23
...
review
| Author | SHA1 | Date | |
|---|---|---|---|
| d1e6d3dfe6 | |||
| 84449e5235 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -32,4 +32,3 @@
|
||||
*.out
|
||||
*.app
|
||||
|
||||
.codex
|
||||
|
||||
28
CLAUDE.md
28
CLAUDE.md
@@ -4,9 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Build & Upload
|
||||
|
||||
Main firmware lives in `Gaugecontroller/Gaugecontroller.ino`. No external libraries required on the `Stepper-Only` branch. Use the Arduino IDE or `arduino-cli`:
|
||||
|
||||
The ESP32 bridge runs ESPHome; the config is in `gaugecontroller.yaml`.
|
||||
Main firmware lives in `Gaugecontroller/Gaugecontroller.ino`. Requires the **FastLED** library (`arduino-cli lib install FastLED`). Use the Arduino IDE or `arduino-cli`:
|
||||
|
||||
```bash
|
||||
# Compile (replace board/port as needed)
|
||||
@@ -16,7 +14,7 @@ arduino-cli compile --fqbn arduino:avr:mega Gaugecontroller
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:mega Gaugecontroller
|
||||
```
|
||||
|
||||
Current default serial setup: `CMD_PORT` and `DEBUG_PORT` both point to `Serial` (USB) at 38400 baud.
|
||||
Current default serial setup: `CMD_PORT` and `DEBUG_PORT` both point to `Serial1` at 38400 baud.
|
||||
|
||||
## Switching serial ports (debug → production)
|
||||
|
||||
@@ -27,7 +25,7 @@ Two `#define`s at the top of `Gaugecontroller.ino` control where commands and de
|
||||
#define DEBUG_PORT Serial1 // diagnostic prints (homing, boot messages)
|
||||
```
|
||||
|
||||
**Current default:** both point to `Serial` (USB), so command and debug traffic go over the Arduino USB port at 38400 baud.
|
||||
**Current default:** both point to `Serial1`, so command and debug traffic share Mega pins TX1=18 / RX1=19 at 38400 baud.
|
||||
|
||||
**USB-only debug setup:** point both defines back at `Serial` if you want to talk to the sketch over the Arduino USB port instead:
|
||||
|
||||
@@ -60,8 +58,8 @@ The sketch controls `GAUGE_COUNT` stepper-motor gauges using a trapezoidal veloc
|
||||
|
||||
### Key data structures
|
||||
|
||||
- `GaugeConfig` — compile-time config per gauge: pin assignments (dir, step, enable, polarity flags) and motion defaults (minPos, maxPos, homingBackoffSteps, maxSpeed, accel, homingSpeed). All gauges are defined in `gauge_config.h` as `constexpr GaugeConfig gaugeConfigs[]`. `GAUGE_COUNT` is derived automatically from the array length.
|
||||
- `Gauge` — per-gauge runtime state: position, target, velocity, accel, homing state machine, sweep mode. Initialised from `gaugeConfigs[]` in `setup()`.
|
||||
- `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.
|
||||
- `Gauge` — per-gauge runtime state: position, target, velocity, accel, homing state machine, sweep mode.
|
||||
|
||||
### Motion control (`updateGauge`)
|
||||
|
||||
@@ -76,6 +74,10 @@ Backs up `homingBackoffSteps` at `homingSpeed`, waits 100 ms settle, then declar
|
||||
|
||||
When `sweepEnabled`, `updateSweepTarget` bounces `targetPos` between `minPos` and `maxPos` autonomously.
|
||||
|
||||
### 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.
|
||||
|
||||
### Serial command protocol
|
||||
|
||||
Commands arrive as newline-terminated ASCII lines. Each `parse*` function in `processLine` handles one command family:
|
||||
@@ -90,12 +92,18 @@ Commands arrive as newline-terminated ASCII lines. Each `parse*` function in `pr
|
||||
| `HOME` | `HOME <id>` / `HOMEALL` | Run homing sequence |
|
||||
| `SWEEP` | `SWEEP <id> <accel> <speed>` | Start sweep (0/0 stops) |
|
||||
| `POS?` | `POS?` | Query all gauges: `POS <id> <cur> <tgt> <homed> <homingState> <sweep>` |
|
||||
| `CFG?` | `CFG?` | Query all gauges: `CFG <id> <maxSpeed> <accel>` per gauge |
|
||||
| `LED` | `LED <id> <idx> <r> <g> <b>` | Set one LED (0-based index within gauge segment) to RGB colour (0–255 each); `<idx>` may be a range `N-M` to set LEDs N through M in one command; also stops any active effect on those LEDs |
|
||||
| `LED?` | `LED?` | Query all LEDs: one `LED <id> <idx> <r> <g> <b>` line per LED, then `OK` |
|
||||
| `BLINK` | `BLINK <id> <idx> <on_ms> <off_ms> <r> <g> <b>` | Blink LED(s) at given colour; `<idx>` may be a range `N-M`; `on_ms`/`off_ms` both 0 stops blinking. 4-arg form (no colour) uses current LED colour |
|
||||
| `BREATHE` | `BREATHE <id> <idx> <period_ms> <r> <g> <b>` | Smooth triangle-wave fade between black and the given colour; `<idx>` may be a range `N-M` |
|
||||
| `DFLASH` | `DFLASH <id> <idx> <r> <g> <b>` | Two quick flashes (100 ms on/off each) followed by a 700 ms pause, then repeats; `<idx>` may be a range `N-M` |
|
||||
| `PING` | `PING` | Responds `PONG` |
|
||||
|
||||
All commands reply `OK` or `ERR BAD_ID` / `ERR BAD_CMD` etc.
|
||||
|
||||
### Adding gauges
|
||||
|
||||
1. Open `Gaugecontroller/gauge_config.h` and append one row to `gaugeConfigs[]`.
|
||||
2. `GAUGE_COUNT` updates automatically — no other changes needed.
|
||||
1. Increment `GAUGE_COUNT`.
|
||||
2. Add a `constexpr GaugePins` entry to `gaugePins[]` (including `ledCount`).
|
||||
3. Tune `maxPos` and `homingBackoffSteps` in the corresponding `Gauge` default or at runtime.
|
||||
4. `TOTAL_LEDS` and `gaugeLedOffset[]` update automatically — no manual changes needed.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
constexpr GaugeConfig gaugeConfigs[] = {
|
||||
// dir step en dirInv stpHi enLow min max backoff speed accel hmSpd
|
||||
{ 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]);
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
52
README.md
52
README.md
@@ -7,7 +7,7 @@ A dedicated gauge controller for Arduinos.
|
||||
This repository contains:
|
||||
|
||||
- `Gaugecontroller/Gaugecontroller.ino`: the Arduino Mega firmware for the stepper gauges, LEDs, and integrated HV5812-based VFD
|
||||
- `gaugecontroller.yaml`: the ESPHome-based ESP32 firmware that exposes the gauges and VFD to Home Assistant via the native API
|
||||
- `gauge.py`: the ESP32 / MicroPython MQTT bridge that exposes the controller to Home Assistant
|
||||
|
||||
## VFD Support
|
||||
|
||||
@@ -48,19 +48,16 @@ Rules:
|
||||
- shorter values are right-aligned
|
||||
- leading zeroes are preserved if they are part of the input
|
||||
|
||||
## Home Assistant Integration
|
||||
## Home Assistant Entities
|
||||
|
||||
The ESPHome firmware in `gaugecontroller.yaml` exposes entities to Home Assistant via the native API:
|
||||
The MQTT bridge publishes Home Assistant discovery entities for the VFD:
|
||||
|
||||
### Gauge Controls
|
||||
- Number entities for each gauge's target value (with unit conversion)
|
||||
- Number entities for speed and acceleration (diagnostic)
|
||||
- Rezero buttons for each gauge and all gauges
|
||||
|
||||
### VFD Display
|
||||
- `VFD Display`: text entity for the displayed value
|
||||
- `VFD Decimal Point`: switch entity
|
||||
- `VFD Alarm`: switch entity
|
||||
- `VFD Display`
|
||||
text entity for the displayed value
|
||||
- `VFD Decimal Point`
|
||||
switch entity
|
||||
- `VFD Alarm`
|
||||
switch entity
|
||||
|
||||
The display is intentionally exposed as a text entity rather than a numeric entity so that:
|
||||
|
||||
@@ -68,12 +65,27 @@ The display is intentionally exposed as a text entity rather than a numeric enti
|
||||
- hexadecimal values like `DEAD` or `BEEF` work
|
||||
- clearing the display is possible with an empty value
|
||||
|
||||
### LED Controls
|
||||
- RGB light entity for each gauge's backlight with effects (Blink, Breathe, Double Flash)
|
||||
- Binary light entities for each gauge's red/green indicators and status lights
|
||||
## MQTT Topics
|
||||
|
||||
### Diagnostics
|
||||
- WiFi signal sensor
|
||||
- Uptime sensor
|
||||
- IP address and SSID text sensors
|
||||
- Arduino Last Message sensor
|
||||
Using the configured `mqtt_prefix` from `config.json`, the VFD topics are:
|
||||
|
||||
- `<prefix>/vfd/set`
|
||||
- `<prefix>/vfd/state`
|
||||
- `<prefix>/vfd/decimal_point/set`
|
||||
- `<prefix>/vfd/decimal_point/state`
|
||||
- `<prefix>/vfd/alarm/set`
|
||||
- `<prefix>/vfd/alarm/state`
|
||||
|
||||
Example with the default prefix `gauges`:
|
||||
|
||||
- `gauges/vfd/set`
|
||||
- `gauges/vfd/decimal_point/set`
|
||||
- `gauges/vfd/alarm/set`
|
||||
|
||||
Example payloads:
|
||||
|
||||
- publish `0123` to `gauges/vfd/set`
|
||||
- publish `ON` to `gauges/vfd/decimal_point/set`
|
||||
- publish `OFF` to `gauges/vfd/alarm/set`
|
||||
|
||||
The MQTT bridge then converts that into the correct Arduino serial command such as `VFD 0123.`.
|
||||
|
||||
@@ -183,14 +183,13 @@ Then connect the motor side of that driver to:
|
||||
|
||||
according to the driver board you are using.
|
||||
|
||||
## 14. Wire The WS2812 LEDs
|
||||
## 14. Wire The WS2812B LEDs
|
||||
|
||||
Connect:
|
||||
|
||||
- `Mega D22` -> main backlight/status strip `DIN`
|
||||
- `Mega D36` -> indicator strip `DIN`
|
||||
- `5V LED supply` -> both strip `5V` inputs
|
||||
- both strip `GND` inputs -> common ground rail
|
||||
- `Mega D22` -> `WS2812B DIN`
|
||||
- `5V LED supply` -> `WS2812B 5V`
|
||||
- `WS2812B GND` -> common ground rail
|
||||
|
||||
If the LED chain is long or bright:
|
||||
|
||||
|
||||
@@ -205,10 +205,9 @@ If `D8` and `D9` come from separate fly wires to the stripboard, keep them in th
|
||||
|
||||
Route:
|
||||
|
||||
- `D22` -> main backlight/status strip `DIN`
|
||||
- `D36` -> indicator strip `DIN`
|
||||
- `5V` -> both strip `5V` inputs
|
||||
- `GND` -> both strip `GND` inputs
|
||||
- `D22` -> `WS2812 DIN`
|
||||
- `5V` -> `WS2812 5V`
|
||||
- `GND` -> `WS2812 GND`
|
||||
|
||||
Keep the LED connector in the low-voltage area.
|
||||
|
||||
|
||||
64
boot.py
Normal file
64
boot.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
boot.py — runs before main.py on every ESP32 boot
|
||||
|
||||
Connects WiFi, runs OTA update, then hands off to main.py.
|
||||
Keep this file as simple as possible — it is never OTA-updated itself
|
||||
(it lives outside the repo folder) so bugs here require USB to fix.
|
||||
"""
|
||||
import gauge
|
||||
import network
|
||||
import gc
|
||||
import utime
|
||||
import sys
|
||||
|
||||
import ota
|
||||
|
||||
ota.load_config()
|
||||
WIFI_SSID, WIFI_PASSWORD = ota.WIFI_SSID, ota.WIFI_PASSWORD
|
||||
|
||||
def _connect_wifi(timeout_s=20):
|
||||
sta = network.WLAN(network.STA_IF)
|
||||
sta.active(True)
|
||||
sta.config(txpower=15)
|
||||
if sta.isconnected():
|
||||
return True
|
||||
sta.connect(WIFI_SSID, WIFI_PASSWORD)
|
||||
deadline = utime.time() + timeout_s
|
||||
while not sta.isconnected():
|
||||
if utime.time() > deadline:
|
||||
return False
|
||||
utime.sleep_ms(300)
|
||||
return True
|
||||
|
||||
if WIFI_SSID is None:
|
||||
print("[boot] No WiFi credentials — cannot connect, skipping OTA")
|
||||
elif _connect_wifi():
|
||||
ip = network.WLAN(network.STA_IF).ifconfig()[0]
|
||||
print(f"[boot] WiFi connected — {ip}")
|
||||
|
||||
try:
|
||||
ota.update()
|
||||
except Exception as e:
|
||||
print(f"[boot] OTA error: {e} — continuing with existing files")
|
||||
sys.print_exception(e)
|
||||
utime.sleep_ms(5000)
|
||||
ota._fetch_commit_sha = None
|
||||
ota._fetch_manifest = None
|
||||
ota._fetch_dir = None
|
||||
ota._api_get = None
|
||||
ota._download = None
|
||||
ota.urequests = None
|
||||
del ota.urequests
|
||||
del ota
|
||||
gc.collect()
|
||||
del sys.modules["ota"]
|
||||
gc.collect()
|
||||
|
||||
else:
|
||||
print("[boot] WiFi failed — skipping OTA, booting with existing files")
|
||||
|
||||
# main.py runs automatically after boot.py
|
||||
|
||||
|
||||
|
||||
|
||||
46
changes.md
46
changes.md
@@ -1,46 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## 2026-04-27 — Arduino firmware refactor (`Gaugecontroller/Gaugecontroller.ino`)
|
||||
|
||||
### Non-blocking VFD multiplexer
|
||||
`vfd::refresh()` previously held each digit for 2000 µs via `delayMicroseconds`,
|
||||
which capped the effective stepper pulse rate at roughly 500 Hz regardless of
|
||||
`maxSpeed`. It now tracks `phaseStartMicros`/`phaseActive` and returns
|
||||
immediately while the digit is still being held; the main loop runs at
|
||||
microsecond cadence again and the configured `maxSpeed = 4000.0f` steps/s is
|
||||
actually achievable.
|
||||
|
||||
### Fixed-buffer command parser (no more `String` heap churn)
|
||||
Replaced `String rxLine` with `char rxBuf[128]` and converted the entire
|
||||
command pipeline to take `const char*`:
|
||||
|
||||
- `processLine`, `sendReply`, `vfd::parseCommand`
|
||||
- All `parse*` functions: `parseSet`, `parseSpeed`, `parseAccel`, `parseEnable`,
|
||||
`parseZero`, `parseHome`, `parseSweep`, `parsePosQuery`, `parseCfgQuery`,
|
||||
`parseLedQuery`, `parseLed`, `parseBlink`, `parseBreathe`, `parseDflash`,
|
||||
`parseVfd`, `parsePing`.
|
||||
|
||||
`parseSpeed` / `parseAccel` / `parseSweep` use `strncmp` + `atof` because the
|
||||
default AVR-libc `sscanf` doesn't support `%f`. No allocations on the command
|
||||
path; the Mega's heap no longer fragments over time.
|
||||
|
||||
### Cached `ledNeedsSwap[TOTAL_LEDS]`
|
||||
Per-LED RGB-vs-GRB swap flag is now precomputed once in `setup()` from
|
||||
`gaugePins[].ledOrder`. `encodeForStrip` is a single array index instead of
|
||||
walking the gauge table on every LED read/write.
|
||||
|
||||
### Cached step direction per gauge
|
||||
Added `Gauge.lastDir`. `setDir()` skips the DIR-pin `digitalWrite` when the
|
||||
direction hasn't flipped (the common case during a step run) and adds a 1 µs
|
||||
DIR-to-STEP setup delay only when it actually flips.
|
||||
|
||||
### Cleanups
|
||||
- Removed the `absf` helper; use `fabsf` consistently.
|
||||
- Removed the `+ 0.0001f` epsilon in the trapezoidal braking-distance divisor.
|
||||
`parseAccel` already rejects `accel <= 0`, so the divisor is always positive.
|
||||
- Fixed the `<r> <ig> <b>` typo to `<r> <g> <b>` in the protocol comment for
|
||||
`DFLASH`.
|
||||
|
||||
### Build verification
|
||||
`arduino-cli compile --fqbn arduino:avr:mega Gaugecontroller`:
|
||||
17758 B flash (6%), 1845 B SRAM (22%).
|
||||
@@ -1,474 +0,0 @@
|
||||
# Gaugecontroller v2.0 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Centralise all gauge configuration in `gauge_config.h` and make the three inconsistently-written parsers use `sscanf` like the rest.
|
||||
|
||||
**Architecture:** A new header file holds one `constexpr GaugeConfig` table — pin assignments and motion defaults merged — and derives `GAUGE_COUNT` from its length. The sketch includes this header, removes the old `GaugePins` struct and all hardcoded defaults, and initialises `Gauge` runtime state from the table in `setup()`. Three parse functions are then rewritten from manual string splitting to `sscanf`.
|
||||
|
||||
**Tech Stack:** Arduino (AVR/Mega), `arduino-cli`, C++11 `constexpr`.
|
||||
|
||||
> **Note on testing:** This is a bare-metal Arduino sketch with no unit-test framework. Each task's verification step is a clean compile with `arduino-cli`. Functional testing requires the physical hardware; the plan notes what to check over serial when hardware is available.
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| File | Action | Responsibility |
|
||||
|---|---|---|
|
||||
| `Gaugecontroller/gauge_config.h` | **Create** | All pin assignments and motion defaults; `GAUGE_COUNT` |
|
||||
| `Gaugecontroller/Gaugecontroller.ino` | **Modify** | Remove `GaugePins`, add include, strip `Gauge` defaults, update all references, rewrite 3 parsers |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Create `gauge_config.h`
|
||||
|
||||
**Files:**
|
||||
- Create: `Gaugecontroller/gauge_config.h`
|
||||
|
||||
- [ ] **Step 1: Create the file**
|
||||
|
||||
Create `Gaugecontroller/gauge_config.h` with the following content:
|
||||
|
||||
```cpp
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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]);
|
||||
```
|
||||
|
||||
To add a fifth gauge later: append one row to `gaugeConfigs[]`. Nothing else changes.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Wire `gauge_config.h` into `Gaugecontroller.ino`
|
||||
|
||||
**Files:**
|
||||
- Modify: `Gaugecontroller/Gaugecontroller.ino`
|
||||
|
||||
This task makes six targeted edits in order. Each edit is shown as old → new. Do them top-to-bottom so line numbers don't shift unexpectedly.
|
||||
|
||||
- [ ] **Step 1: Add the include**
|
||||
|
||||
After the existing three `#include` lines at the top, add:
|
||||
|
||||
```cpp
|
||||
#include "gauge_config.h"
|
||||
```
|
||||
|
||||
The top of the file should now read:
|
||||
|
||||
```cpp
|
||||
#include <Arduino.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include <math.h>
|
||||
#include "gauge_config.h"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Remove `GAUGE_COUNT` and `GaugePins`**
|
||||
|
||||
Delete these two blocks entirely (they are now in `gauge_config.h`):
|
||||
|
||||
```cpp
|
||||
static const uint8_t GAUGE_COUNT = 4;
|
||||
```
|
||||
|
||||
```cpp
|
||||
struct GaugePins {
|
||||
uint8_t dirPin;
|
||||
uint8_t stepPin;
|
||||
int8_t enablePin; // -1 means there is no enable pin
|
||||
bool dirInverted;
|
||||
bool stepActiveHigh;
|
||||
bool enableActiveLow;
|
||||
};
|
||||
|
||||
constexpr GaugePins gaugePins[GAUGE_COUNT] = {
|
||||
// dir, step, en, dirInv, stepHigh, enActiveLow
|
||||
{48, 49, -1, false, true, true}, // Gauge 0
|
||||
{8, 9, -1, true, true, true}, // Gauge 1
|
||||
{52, 53, -1, false, true, true}, // Gauge 2
|
||||
{50, 51, -1, false, true, true}, // Gauge 3
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Strip hardcoded defaults from `struct Gauge`**
|
||||
|
||||
In the `Gauge` struct definition, remove the numeric defaults from the six motion fields. Change:
|
||||
|
||||
```cpp
|
||||
long minPos = 0;
|
||||
long maxPos = 3780;
|
||||
long homingBackoffSteps = 3800; // Deliberately a touch past full reverse travel.
|
||||
|
||||
float velocity = 0.0f;
|
||||
float maxSpeed = 4000.0f;
|
||||
float accel = 6000.0f;
|
||||
float homingSpeed = 500.0f;
|
||||
```
|
||||
|
||||
To:
|
||||
|
||||
```cpp
|
||||
long minPos = 0;
|
||||
long maxPos = 0;
|
||||
long homingBackoffSteps = 0;
|
||||
|
||||
float velocity = 0.0f;
|
||||
float maxSpeed = 0.0f;
|
||||
float accel = 0.0f;
|
||||
float homingSpeed = 0.0f;
|
||||
```
|
||||
|
||||
These will be populated from `gaugeConfigs[i]` in `setup()` (Step 5).
|
||||
|
||||
- [ ] **Step 4: Update `gaugePins` → `gaugeConfigs` references outside `setup()`**
|
||||
|
||||
Three functions reference `gaugePins`. Update each one:
|
||||
|
||||
**`writeDirectionPin` (~line 106):**
|
||||
```cpp
|
||||
// Before
|
||||
bool level = gaugePins[id].dirInverted ? !forward : forward;
|
||||
|
||||
// After
|
||||
bool level = gaugeConfigs[id].dirInverted ? !forward : forward;
|
||||
```
|
||||
|
||||
**`writeStepPin` (~line 111):**
|
||||
```cpp
|
||||
// Before
|
||||
bool level = gaugePins[id].stepActiveHigh ? active : !active;
|
||||
|
||||
// After
|
||||
bool level = gaugeConfigs[id].stepActiveHigh ? active : !active;
|
||||
```
|
||||
|
||||
**`configureStepperHardware` (~line 152):**
|
||||
```cpp
|
||||
// Before
|
||||
stepperHardware[id].stepPort = portOutputRegister(digitalPinToPort(gaugePins[id].stepPin));
|
||||
stepperHardware[id].stepMask = digitalPinToBitMask(gaugePins[id].stepPin);
|
||||
stepperHardware[id].dirPort = portOutputRegister(digitalPinToPort(gaugePins[id].dirPin));
|
||||
stepperHardware[id].dirMask = digitalPinToBitMask(gaugePins[id].dirPin);
|
||||
|
||||
// After
|
||||
stepperHardware[id].stepPort = portOutputRegister(digitalPinToPort(gaugeConfigs[id].stepPin));
|
||||
stepperHardware[id].stepMask = digitalPinToBitMask(gaugeConfigs[id].stepPin);
|
||||
stepperHardware[id].dirPort = portOutputRegister(digitalPinToPort(gaugeConfigs[id].dirPin));
|
||||
stepperHardware[id].dirMask = digitalPinToBitMask(gaugeConfigs[id].dirPin);
|
||||
```
|
||||
|
||||
**`setEnable` (~line 291):**
|
||||
```cpp
|
||||
// Before
|
||||
int8_t pin = gaugePins[id].enablePin;
|
||||
if (pin < 0) return;
|
||||
bool level = gaugePins[id].enableActiveLow ? !en : en;
|
||||
|
||||
// After
|
||||
int8_t pin = gaugeConfigs[id].enablePin;
|
||||
if (pin < 0) return;
|
||||
bool level = gaugeConfigs[id].enableActiveLow ? !en : en;
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Update `setup()` — pin references and add motion default init**
|
||||
|
||||
In `setup()`, the `for` loop currently reads:
|
||||
|
||||
```cpp
|
||||
for (uint8_t i = 0; i < GAUGE_COUNT; i++) {
|
||||
pinMode(gaugePins[i].dirPin, OUTPUT);
|
||||
pinMode(gaugePins[i].stepPin, OUTPUT);
|
||||
configureStepperHardware(i);
|
||||
|
||||
digitalWrite(gaugePins[i].dirPin, LOW);
|
||||
digitalWrite(gaugePins[i].stepPin, gaugePins[i].stepActiveHigh ? LOW : HIGH);
|
||||
|
||||
if (gaugePins[i].enablePin >= 0) {
|
||||
pinMode(gaugePins[i].enablePin, OUTPUT);
|
||||
setEnable(i, true);
|
||||
}
|
||||
|
||||
gauges[i].lastUpdateMicros = micros();
|
||||
}
|
||||
```
|
||||
|
||||
Replace it with:
|
||||
|
||||
```cpp
|
||||
for (uint8_t i = 0; i < GAUGE_COUNT; i++) {
|
||||
pinMode(gaugeConfigs[i].dirPin, OUTPUT);
|
||||
pinMode(gaugeConfigs[i].stepPin, OUTPUT);
|
||||
configureStepperHardware(i);
|
||||
|
||||
digitalWrite(gaugeConfigs[i].dirPin, LOW);
|
||||
digitalWrite(gaugeConfigs[i].stepPin, gaugeConfigs[i].stepActiveHigh ? LOW : HIGH);
|
||||
|
||||
if (gaugeConfigs[i].enablePin >= 0) {
|
||||
pinMode(gaugeConfigs[i].enablePin, OUTPUT);
|
||||
setEnable(i, true);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
gauges[i].lastUpdateMicros = micros();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Compile and verify clean**
|
||||
|
||||
```bash
|
||||
arduino-cli compile --fqbn arduino:avr:mega Gaugecontroller
|
||||
```
|
||||
|
||||
Expected: zero errors, zero warnings about `gaugePins` or `GAUGE_COUNT`. If the compiler reports "use of undeclared identifier 'gaugePins'", grep for any remaining reference:
|
||||
|
||||
```bash
|
||||
grep -n "gaugePins" Gaugecontroller/Gaugecontroller.ino
|
||||
```
|
||||
|
||||
Should return nothing.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add Gaugecontroller/gauge_config.h Gaugecontroller/Gaugecontroller.ino
|
||||
git commit -m "refactor: centralise gauge config in gauge_config.h"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Rewrite `parseSpeed`, `parseAccel`, `parseSweep` to use `sscanf`
|
||||
|
||||
**Files:**
|
||||
- Modify: `Gaugecontroller/Gaugecontroller.ino`
|
||||
|
||||
- [ ] **Step 1: Replace `parseSpeed`**
|
||||
|
||||
Find and replace the entire `parseSpeed` function:
|
||||
|
||||
```cpp
|
||||
// Before (~15 lines)
|
||||
bool parseSpeed(const String& line) {
|
||||
int firstSpace = line.indexOf(' ');
|
||||
int secondSpace = line.indexOf(' ', firstSpace + 1);
|
||||
if (firstSpace < 0 || secondSpace < 0) return false;
|
||||
if (line.substring(0, firstSpace) != "SPEED") return false;
|
||||
|
||||
int id = line.substring(firstSpace + 1, secondSpace).toInt();
|
||||
float speed = line.substring(secondSpace + 1).toFloat();
|
||||
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// After
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Replace `parseAccel`**
|
||||
|
||||
```cpp
|
||||
// Before (~15 lines)
|
||||
bool parseAccel(const String& line) {
|
||||
int firstSpace = line.indexOf(' ');
|
||||
int secondSpace = line.indexOf(' ', firstSpace + 1);
|
||||
if (firstSpace < 0 || secondSpace < 0) return false;
|
||||
if (line.substring(0, firstSpace) != "ACCEL") return false;
|
||||
|
||||
int id = line.substring(firstSpace + 1, secondSpace).toInt();
|
||||
float accel = line.substring(secondSpace + 1).toFloat();
|
||||
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// After
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Replace `parseSweep`**
|
||||
|
||||
```cpp
|
||||
// Before (~20 lines)
|
||||
bool parseSweep(const String& line) {
|
||||
int firstSpace = line.indexOf(' ');
|
||||
int secondSpace = line.indexOf(' ', firstSpace + 1);
|
||||
int thirdSpace = line.indexOf(' ', secondSpace + 1);
|
||||
|
||||
if (firstSpace < 0 || secondSpace < 0 || thirdSpace < 0) return false;
|
||||
if (line.substring(0, firstSpace) != "SWEEP") return false;
|
||||
|
||||
int id = line.substring(firstSpace + 1, secondSpace).toInt();
|
||||
float accel = line.substring(secondSpace + 1, thirdSpace).toFloat();
|
||||
float speed = line.substring(thirdSpace + 1).toFloat();
|
||||
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// After
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify no `indexOf`/`substring` remain in any `parse*` function**
|
||||
|
||||
```bash
|
||||
grep -n "indexOf\|substring" Gaugecontroller/Gaugecontroller.ino
|
||||
```
|
||||
|
||||
Expected: no output. If any lines appear, check which function still uses the old pattern and redo that step.
|
||||
|
||||
- [ ] **Step 5: Compile and verify clean**
|
||||
|
||||
```bash
|
||||
arduino-cli compile --fqbn arduino:avr:mega Gaugecontroller
|
||||
```
|
||||
|
||||
Expected: zero errors.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add Gaugecontroller/Gaugecontroller.ino
|
||||
git commit -m "refactor: uniform sscanf parsing in parseSpeed, parseAccel, parseSweep"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Optional hardware smoke-test (when board is available)
|
||||
|
||||
After uploading, send these commands over serial and confirm expected replies:
|
||||
|
||||
```
|
||||
PING → PONG
|
||||
HOMEALL → OK (then each gauge homes; HOMED 0..3 appear on debug port)
|
||||
POS? → POS 0 0 0 1 0 0 (×4, one per gauge)
|
||||
SET 0 1000 → OK
|
||||
SPEED 0 2000 → OK
|
||||
ACCEL 0 8000 → OK
|
||||
SWEEP 0 6000 4000 → OK
|
||||
SWEEP 0 0 0 → OK (stops sweep)
|
||||
```
|
||||
|
||||
No new error codes were introduced; all existing commands should behave identically to v1.
|
||||
@@ -1,162 +0,0 @@
|
||||
# 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
|
||||
|
||||
```cpp
|
||||
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
|
||||
|
||||
```cpp
|
||||
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]`:
|
||||
|
||||
```cpp
|
||||
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`
|
||||
|
||||
```cpp
|
||||
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`
|
||||
|
||||
```cpp
|
||||
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`
|
||||
|
||||
```cpp
|
||||
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
|
||||
@@ -930,19 +930,6 @@ _last_discovery_ms = 0
|
||||
_DISCOVERY_INTERVAL_MS = 350
|
||||
|
||||
|
||||
def _compact_discovery_payload(payload):
|
||||
"""Trim optional HA discovery fields when RAM is tight."""
|
||||
compact = dict(payload)
|
||||
|
||||
# Light entities are the largest payloads because they repeat effect metadata.
|
||||
# Keep core functionality, but omit optional effect declarations to reduce heap use.
|
||||
if compact.get("schema") == "json":
|
||||
compact.pop("effect", None)
|
||||
compact.pop("effect_list", None)
|
||||
|
||||
return compact
|
||||
|
||||
|
||||
def check_mqtt():
|
||||
global client_ref, _mqtt_connected, _last_mqtt_check
|
||||
now = utime.ticks_ms()
|
||||
@@ -997,7 +984,7 @@ def check_mqtt():
|
||||
|
||||
def _publish_discovery_entity(client, topic, payload, log_msg):
|
||||
gc.collect()
|
||||
client.publish(topic, ujson.dumps(_compact_discovery_payload(payload)), retain=True)
|
||||
client.publish(topic, ujson.dumps(payload), retain=True)
|
||||
info(log_msg)
|
||||
|
||||
|
||||
2394
gaugecontroller.yaml
2394
gaugecontroller.yaml
File diff suppressed because it is too large
Load Diff
17
wiring.md
17
wiring.md
@@ -163,22 +163,19 @@ Also connect:
|
||||
|
||||
If your driver boards need separate motor power, supply that from the proper motor supply. Do not power motors from the Mega `5V` pin.
|
||||
|
||||
## WS2812 LED Strips
|
||||
## WS2812B LED Strip
|
||||
|
||||
The current sketch expects two LED data chains. Backlight and status LEDs stay
|
||||
on the main strip; the red/green dial indicator LEDs are on their own strip.
|
||||
The current sketch expects one shared WS2812B chain.
|
||||
|
||||
| Mega Pin | LED Strip |
|
||||
| Mega Pin | WS2812B |
|
||||
|---|---|
|
||||
| `D22` | main backlight/status `DIN` |
|
||||
| `D36` | indicator `DIN` |
|
||||
| `5V` | both strips `5V` |
|
||||
| `GND` | both strips `GND` |
|
||||
| `D22` | `DIN` |
|
||||
| `5V` | `5V` |
|
||||
| `GND` | `GND` |
|
||||
|
||||
Notes:
|
||||
|
||||
- the command protocol still exposes `7 LEDs per gauge`
|
||||
- logical indices `0-2` are backlight, `3-4` are indicators, and `5-6` are status
|
||||
- the code expects `7 LEDs per gauge`, so `21 LEDs total`
|
||||
- use a proper 5V supply sized for the LED current
|
||||
- keep LED ground common with the Mega
|
||||
|
||||
|
||||
Reference in New Issue
Block a user