Skip to content

File PinRef.cpp

File List > Firmware > Libraries > PanelGroup > PinRef.cpp

Go to the documentation of this file

#ifdef ARDUINO_ARCH_STM32

#include "PinRef.h"
#include "ADS1115.h"     // full ADS1115 type for readSingleEnded()
#include <MCP23017.h>    // full MCP23017 type for reference binding

#ifdef PINREF_DEBUG
#include <STM32Board.h>
#endif

// ── PanelGroup package-internal functions ─────────────────────────────────────
//
// Declared here so PinRef.cpp can call them without including PanelGroup.h.
// Defined in PanelGroup.cpp when PanelGroup is fully implemented.
// PinRef tests (GPIO and NC paths) do not exercise the MCP path, so the linker
// does not require these definitions during test builds.

namespace PanelGroup {
    bool readCachedPin(const MCP23017& chip, uint8_t port, uint8_t bit);
    void writeCachedPin(MCP23017& chip, uint8_t port, uint8_t bit, bool value);
    void writeCachedPinDeferred(MCP23017& chip, uint8_t port, uint8_t bit, bool value);
    bool readLivePin(MCP23017& chip, uint8_t port, uint8_t bit);
}

// ── PIN_NC definition ─────────────────────────────────────────────────────────

const PinRef PIN_NC;

// ── Constructors ──────────────────────────────────────────────────────────────

PinRef::PinRef(uint8_t pin) : _type(Type::GPIO) {
    _src.pin = pin;
}

PinRef::PinRef(MCP23017& chip, uint8_t port, uint8_t bit) : _type(Type::MCP) {
    _src.mcp = { &chip, port, bit };
}

PinRef::PinRef(ADS1115& adc, uint8_t channel) : _type(Type::ADS) {
    _src.ads = { &adc, channel };
    adc.setGain(GAIN_ONE); // ±4.096V FSR — best resolution for 0–3.3V inputs
}

// Default no-connect ctor is constexpr, defined inline in PinRef.h (constant-initialized
// so PIN_NC is safe in global array initialisers — no static-init-order hazard).

// ── read ──────────────────────────────────────────────────────────────────────

bool PinRef::read() const {
    switch (_type) {
    case Type::GPIO:
        return digitalRead(_src.pin) == HIGH;
    case Type::MCP:
        return PanelGroup::readCachedPin(*_src.mcp.chip, _src.mcp.port, _src.mcp.bit);
    case Type::ADS:
        return readAnalog() > 32767u;
    case Type::NC:
    default:
        return false;
    }
}

// ── readLive ──────────────────────────────────────────────────────────────────

bool PinRef::readLive() const {
    switch (_type) {
    case Type::GPIO:
        return digitalRead(_src.pin) == HIGH;          // already live
    case Type::MCP:
        return PanelGroup::readLivePin(*_src.mcp.chip, _src.mcp.port, _src.mcp.bit);
    case Type::ADS:
        return readAnalog() > 32767u;                  // already live
    case Type::NC:
    default:
        return false;
    }
}

// ── readAnalog ────────────────────────────────────────────────────────────────

uint16_t PinRef::readAnalog() const {
    switch (_type) {
    case Type::GPIO:
        // analogReadResolution(16) set in STM32Board::begin(); framework scales 12-bit → 16-bit
        return static_cast<uint16_t>(analogRead(_src.pin));
    case Type::ADS: {
        // 15-bit single-ended × 2 → 0–65534; clamp negatives (should not occur)
        int16_t raw = _src.ads.adc->readADC_SingleEnded(_src.ads.channel);
        if (raw < 0) raw = 0;
        return static_cast<uint16_t>(raw) << 1;
    }
    case Type::MCP:
#ifdef PINREF_DEBUG
        STM32Board::log("[PinRef] readAnalog on MCP23017 pin — not supported, returns 0");
#endif
        return 0;
    case Type::NC:
    default:
        return 0;
    }
}

// ── write ─────────────────────────────────────────────────────────────────────

void PinRef::write(bool value) {
    switch (_type) {
    case Type::GPIO:
        digitalWrite(_src.pin, value ? HIGH : LOW);
        break;
    case Type::MCP:
        PanelGroup::writeCachedPin(*_src.mcp.chip, _src.mcp.port, _src.mcp.bit, value);
        break;
    case Type::ADS:
#ifdef PINREF_DEBUG
        STM32Board::log("[PinRef] write on ADS1115 pin — input-only, no-op");
#endif
        break;
    case Type::NC:
    default:
        break;
    }
}

void PinRef::writeDeferred(bool value) {
    switch (_type) {
    case Type::GPIO:
        digitalWrite(_src.pin, value ? HIGH : LOW);   // native GPIO is already immediate
        break;
    case Type::MCP:
        PanelGroup::writeCachedPinDeferred(*_src.mcp.chip, _src.mcp.port, _src.mcp.bit, value);
        break;
    case Type::ADS:
    case Type::NC:
    default:
        break;
    }
}

// ── writeAnalog ───────────────────────────────────────────────────────────────

void PinRef::writeAnalog(uint16_t val) {
    switch (_type) {
    case Type::GPIO:
        // 16-bit → 8-bit duty cycle (upper byte)
        analogWrite(_src.pin, val >> 8);
        break;
    case Type::MCP:
#ifdef PINREF_DEBUG
        STM32Board::log("[PinRef] writeAnalog on MCP23017 pin — no PWM, no-op");
#endif
        break;
    case Type::ADS:
#ifdef PINREF_DEBUG
        STM32Board::log("[PinRef] writeAnalog on ADS1115 pin — input-only, no-op");
#endif
        break;
    case Type::NC:
    default:
        break;
    }
}

// ── configureAsInput ──────────────────────────────────────────────────────────

void PinRef::configureAsInput() {
    switch (_type) {
    case Type::GPIO:
        pinMode(_src.pin, INPUT);
        break;
    case Type::MCP:
        // Flat pin: 0-7 = PORT A, 8-15 = PORT B. INPUT sets IODIR=1, GPPU=0.
        _src.mcp.chip->pinMode(_src.mcp.port * 8 + _src.mcp.bit, INPUT);
        break;
    case Type::ADS:
    case Type::NC:
    default:
        break;
    }
}

// ── configureAsOutput ─────────────────────────────────────────────────────────

void PinRef::configureAsOutput() {
    switch (_type) {
    case Type::GPIO:
        pinMode(_src.pin, OUTPUT);
        break;
    case Type::MCP:
        // Flat pin: 0-7 = PORT A, 8-15 = PORT B. OUTPUT sets IODIR=0, GPPU=0.
        _src.mcp.chip->pinMode(_src.mcp.port * 8 + _src.mcp.bit, OUTPUT);
        break;
    case Type::ADS:
#ifdef PINREF_DEBUG
        STM32Board::log("[PinRef] configureAsOutput on ADS1115 pin — input-only, no-op");
#endif
        break;
    case Type::NC:
    default:
        break;
    }
}

// ── isNC / isGpio / gpioPin ───────────────────────────────────────────────────

bool PinRef::isNC()   const { return _type == Type::NC; }
bool PinRef::isGpio() const { return _type == Type::GPIO; }

uint8_t PinRef::gpioPin() const {
    if (_type == Type::GPIO) return _src.pin;
#ifdef PINREF_DEBUG
    STM32Board::log("[PinRef] gpioPin called on non-GPIO pin — returns 0");
#endif
    return 0;
}

#endif // ARDUINO_ARCH_STM32