Files
AstroRotaxMonitor/RotaxMonitor/src/extio.cpp
2026-04-14 11:02:33 +02:00

130 lines
3.9 KiB
C++

#include <extio.h>
// Static interrupt callback
static void onExpanderInterrupt(void *arg)
{
auto cls = (ExternalIO *)(arg);
if (!cls) // invalid args
return;
cls->extReadInterrupt();
}
ExternalIO::ExternalIO(TwoWire &i2c, std::mutex &i2c_mutex, const uint8_t int_pin) : m_i2cMutex(i2c_mutex), m_i2c(i2c), m_intPin(int_pin)
{
std::lock_guard<std::mutex> lock(m_i2cMutex);
// Attach OUT expanders on BUS
m_outMap[EXPANDER_A_OUT_ADDR] = std::make_unique<PCA9555>();
m_outMap[EXPANDER_A_OUT_ADDR]->attach(m_i2c, EXPANDER_A_OUT_ADDR);
m_outMap[EXPANDER_B_OUT_ADDR] = std::make_unique<PCA9555>();
m_outMap[EXPANDER_B_OUT_ADDR]->attach(m_i2c, EXPANDER_B_OUT_ADDR);
for (auto &[a, e] : m_outMap)
{
e->direction(PCA95x5::Direction::OUT_ALL);
e->polarity(PCA95x5::Polarity::ORIGINAL_ALL);
};
// Attach IN Expanders on Bus
m_inMap[EXPANDER_A_IN_ADDR] = std::make_unique<PCA9555>();
m_inMap[EXPANDER_A_IN_ADDR]->attach(m_i2c, EXPANDER_A_IN_ADDR);
m_inMap[EXPANDER_B_IN_ADDR] = std::make_unique<PCA9555>();
m_inMap[EXPANDER_B_IN_ADDR]->attach(m_i2c, EXPANDER_B_IN_ADDR);
for (auto &[a, e] : m_inMap)
{
e->direction(PCA95x5::Direction::IN_ALL);
e->polarity(PCA95x5::Polarity::ORIGINAL_ALL);
m_lastInputState[a] = e->read(); /// initialize input state to collect interrupts
};
}
ExternalIO::~ExternalIO() {
}
void ExternalIO::extDigitalWrite(const uint32_t mappedPin, const bool val)
{
std::lock_guard<std::mutex> lock(m_i2cMutex);
const io_t pa = map2pin(mappedPin);
if (!m_outMap.contains(pa.addr))
{
LOG_ERROR("Undefined IO Expander addr: [", pa.addr, "]");
return;
}
auto &io = m_outMap.at(pa.addr);
if (!io->write(static_cast<PCA95x5::Port::Port>(pa.pin), val ? PCA95x5::Level::H : PCA95x5::Level::L))
{
LOG_ERROR("IO Expander [", pa.addr, "] Unable to WRITE Port [", pa.pin, "] to [", val ? "HIGH" : "LOW");
LOG_ERROR("IO Expander Error [", io->i2c_error(), "]");
}
}
const bool ExternalIO::extDigitalRead(const uint32_t mappedPin)
{
std::lock_guard<std::mutex> lock(m_i2cMutex);
const io_t pa = map2pin(mappedPin);
if (!m_inMap.contains(pa.addr))
{
LOG_ERROR("Undefined IO Expander addr: [", pa.addr, "]");
return false;
}
auto &io = m_inMap.at(pa.addr);
const bool rv = io->read(static_cast<PCA95x5::Port::Port>(pa.pin)) == PCA95x5::Level::H ? true : false; // read value
const uint8_t err = io->i2c_error();
if (err)
{
LOG_ERROR("IO Expander [", pa.addr, "] Unable to READ Port [", pa.pin, "]");
LOG_ERROR("IO Expander Error [", err, "]");
}
return rv;
}
void ExternalIO::extAttachInterrupt(ExtInterruptCb cb)
{
attachInterruptArg(EXPANDER_ALL_INTERRUPT, onExpanderInterrupt, (void *)(this), FALLING);
m_extInterruptCb = cb;
}
void ExternalIO::extDetachInterrupt()
{
detachInterrupt(EXPANDER_ALL_INTERRUPT);
}
void ExternalIO::extReadInterrupt()
{
std::lock_guard<std::mutex> lock(m_i2cMutex);
disableInterrupt(EXPANDER_ALL_INTERRUPT);
// read all registers and collect
IOstate interruptState;
for (auto &[a, e] : m_inMap)
{
interruptState[a] = e->read();
}
m_lastInputState = interruptState; // restore to current values
// compare to last state to see the difference
if (m_extInterruptCb)
{
for (auto &[a, v] : interruptState)
{
if (v)
m_extInterruptCb(stat2map(a, v));
}
}
enableInterrupt(EXPANDER_ALL_INTERRUPT);
}
const ExternalIO::io_t ExternalIO::map2pin(const uint32_t mappedIO)
{
return io_t{
.addr = (uint8_t)((mappedIO >> 16) & (uint8_t)0xFF),
.pin = (uint8_t)(mappedIO && (uint32_t)0xFF),
};
}
const uint32_t ExternalIO::stat2map(const uint8_t addr, const uint16_t stat)
{
if (!stat)
return 0;
return (uint32_t)(addr << 16) | (1UL << __builtin_ctz(stat));
}