DigitalIO driver with dynamic channel count

This commit is contained in:
Emanuele Trabattoni
2025-07-12 13:45:00 +02:00
parent 1955b8cb39
commit ef7b9506b6
8 changed files with 335 additions and 38 deletions

View File

@@ -39,7 +39,7 @@ namespace drivers
uint8_t currState(0); uint8_t currState(0);
uint8_t newState(0); uint8_t newState(0);
if (ch < OUT_PIN1 || ch > OUT_PIN8) if (ch < DO1 || ch > DO8)
{ {
LOG_ERROR("Invalid write to output channel: [%d]", ch); LOG_ERROR("Invalid write to output channel: [%d]", ch);
return false; return false;
@@ -64,7 +64,7 @@ namespace drivers
const bool TCA9554PWR::readOut(const uint8_t ch) const bool TCA9554PWR::readOut(const uint8_t ch)
{ {
uint8_t currState(0); uint8_t currState(0);
if (ch < OUT_PIN1 || ch > OUT_PIN8) if (ch < DO1 || ch > DO8)
{ {
LOG_ERROR("Invalid read to output channel: [%d]", ch); LOG_ERROR("Invalid read to output channel: [%d]", ch);
return false; return false;

View File

@@ -21,18 +21,18 @@ namespace drivers
{ {
public: public:
enum typedef enum
{ {
OUT_PIN1, DO1,
OUT_PIN2, DO2,
OUT_PIN3, DO3,
OUT_PIN4, DO4,
OUT_PIN5, DO5,
OUT_PIN6, DO6,
OUT_PIN7, DO7,
OUT_PIN8, DO8,
OUT_PIN_MAX DO_MAX
}; } channel_t;
TCA9554PWR(I2C &i2c, const uint8_t address); TCA9554PWR(I2C &i2c, const uint8_t address);
~TCA9554PWR(); ~TCA9554PWR();

View File

@@ -6,17 +6,6 @@
#include <CRC16.h> #include <CRC16.h>
#include <memory> #include <memory>
#define Extension_CH1 1 // Expansion Channel 1
#define Extension_CH2 2 // Expansion Channel 2
#define Extension_CH3 3 // Expansion Channel 3
#define Extension_CH4 4 // Expansion Channel 4
#define Extension_CH5 5 // Expansion Channel 5
#define Extension_CH6 6 // Expansion Channel 6
#define Extension_CH7 7 // Expansion Channel 7
#define Extension_CH8 8 // Expansion Channel 8
#define Extension_ALL_ON 9 // Expansion ALL ON
#define Extension_ALL_OFF 10 // Expansion ALL OFF
namespace drivers namespace drivers
{ {
class RS485 class RS485
@@ -34,7 +23,7 @@ namespace drivers
HardwareSerial &m_serial; HardwareSerial &m_serial;
}; };
class MODBUS : public RS485 class MODBUS : private RS485
{ {
static const uint8_t RESP_HEADER_SIZE = 3; static const uint8_t RESP_HEADER_SIZE = 3;

124
src/digitalIO.cpp Normal file
View File

@@ -0,0 +1,124 @@
#include <digitalIO.h>
digitalIO::digitalIO(drivers::I2C &i2c, drivers::MODBUS &bus, std::vector<uint8_t> remotes) : m_localOuts(drivers::TCA9554PWR(i2c, TCA9554_ADDRESS)), m_remoteAddrs(remotes)
{
for (uint8_t i(DI1); i < DI_MAX; i++)
{
pinMode(i, INPUT_PULLUP); // set all local pins as digitalInput
}
for (auto a : remotes)
{
m_remotes.emplace_back(remoteIO(a, bus));
}
}
digitalIO::~digitalIO()
{
}
void digitalIO::digitalIOWrite(const uint8_t ch, const bool value)
{
if (ch < 0 || ch > getOutNum())
{
LOG_ERROR("Invalid digitalIOWrite channel number", ch);
}
if (ch < drivers::TCA9554PWR::DO_MAX) // write to i2c device for local outputs
{
digitalWriteLocal(ch, value);
}
else
{
digitalWriteRemote(ch - drivers::TCA9554PWR::DO_MAX, value);
}
}
const bool digitalIO::digitalIORead(const uint8_t ch)
{
if (ch < 0 || ch > getInNum())
{
LOG_ERROR("Invalid digitalIORead channel number", ch);
}
if (ch < (DI_MAX - DI1)) // read from local inputs not as gpio numbers
{
return digitalReadLocal(ch);
}
else
{
return digitalReadRemote(ch - (DI_MAX - DI1));
}
}
void digitalIO::reset()
{
// set all local and remote outputs to 0
m_localOuts.setPort(0x00);
for (auto r: m_remotes)
r.resetAll(false);
}
const uint8_t digitalIO::getOutNum()
{
return drivers::TCA9554PWR::DO_MAX + m_remotes.size() * remoteIO::CH_MAX;
}
const uint8_t digitalIO::getInNum()
{
return DI_MAX + m_remotes.size() * remoteIO::CH_MAX;
}
void digitalIO::digitalWriteLocal(const uint8_t ch, const bool value)
{
uint8_t retries(0);
while (retries++ < maxRetries)
{
if (m_localOuts.setOut(ch, value))
{
LOG_DEBUG("digitalWriteLocal channel", ch, " status", value ? "True" : "False");
break;
}
LOG_ERROR("Failed digitalWriteLocal channel ", ch, " status", value ? "True" : "False");
}
}
void digitalIO::digitalWriteRemote(const uint8_t ch, const bool value)
{
uint8_t retries(0);
const uint8_t selectedRemote(floor(ch / 8.0f));
const uint8_t selectedChannel(ch % remoteIO::CH_MAX);
while (retries++ < maxRetries)
{
if (m_remotes[selectedRemote].setOut((remoteIO::channel_t)selectedChannel, value))
{
LOG_DEBUG("digitalWriteRemote remote", selectedRemote, " channel ", selectedChannel, " status", value ? "True" : "False");
break;
}
LOG_ERROR("Failed digitalWriteRemote remote", selectedRemote, " channel ", selectedChannel, " status", value ? "True" : "False");
}
}
const bool digitalIO::digitalReadLocal(const uint8_t ch)
{
bool value = !digitalRead(ch + DI1); // base pin number in enum, inverted input
LOG_DEBUG("digitalReadLocal pin", (ch + DI1), " status", value ? "True" : "False");
return value;
}
const bool digitalIO::digitalReadRemote(const uint8_t ch)
{
uint8_t retries(0);
const uint8_t selectedRemote(floor(ch / 8.0f));
const uint8_t selectedChannel(ch % remoteIO::CH_MAX);
bool value;
while (retries++ < maxRetries)
{
if (m_remotes[selectedRemote].getIn((remoteIO::channel_t)selectedChannel, value))
{
LOG_DEBUG("digitalReadRemote remote", selectedRemote, " channel ", selectedChannel, " status", value ? "True" : "False");
return value;
}
LOG_ERROR("Failed digitalReadRemote remote", selectedRemote, " channel ", selectedChannel, " status", value ? "True" : "False");
}
return false;
}

52
src/digitalIO.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <DebugLog.h>
#include <Arduino.h>
#include <remoteIO.h>
#include <TCA9554PWR_Driver.h>
#define ON true
#define OFF false
class digitalIO
{
private:
enum localInputs
{
DI1 = 4, // gpio for local inputs starts at 4 as per manufacturer documentation
DI2,
DI3,
DI4,
DI5,
DI6,
DI7,
DI8,
DI_MAX
};
const uint8_t maxRetries = 5;
public:
digitalIO(drivers::I2C &i2c, drivers::MODBUS &bus, std::vector<uint8_t> remotes);
~digitalIO();
void digitalIOWrite(const uint8_t ch, const bool value);
const bool digitalIORead(const uint8_t ch);
void reset();
const uint8_t getOutNum();
const uint8_t getInNum();
private:
void digitalWriteLocal(const uint8_t ch, const bool value);
void digitalWriteRemote(const uint8_t ch, const bool value);
const bool digitalReadLocal(const uint8_t ch);
const bool digitalReadRemote(const uint8_t ch);
private:
std::vector<uint8_t> m_remoteAddrs;
drivers::TCA9554PWR m_localOuts;
std::vector<remoteIO> m_remotes;
};

View File

@@ -1,15 +1,15 @@
#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO #define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
#include <DebugLog.h> #include <DebugLog.h>
#include <DebugLogEnable.h> #include <DebugLogEnable.h>
#include <Arduino.h> #include <Arduino.h>
#include <PubSubClient.h> #include <PubSubClient.h>
#include <RS485_Driver.h>
#include <TCA9554PWR_Driver.h>
#include <PCF85063_Driver.h> #include <PCF85063_Driver.h>
#include <ETH_Driver.h> #include <ETH_Driver.h>
#include <digitalIO.h>
#include "utils.h" #include "utils.h"
void callback(char *topic, uint8_t *payload, unsigned int length) void callback(char *topic, uint8_t *payload, unsigned int length)
@@ -42,29 +42,28 @@ void loop()
const uint8_t relayBoardAddr(0x01); const uint8_t relayBoardAddr(0x01);
const uint8_t baseRegister(0x00); const uint8_t baseRegister(0x00);
uint16_t k(0); uint16_t k(0);
uint8_t ethRetries(0);
//////////////// DEVICES //////////////// //////////////// DEVICES ////////////////
// Declared here to keep devices local to the main loop otherwise the kernel crashes // // Declared here to keep devices local to the main loop otherwise the kernel crashes //
auto i2c = drivers::I2C(); auto i2c = drivers::I2C();
auto relays = drivers::TCA9554PWR(i2c, TCA9554_ADDRESS);
auto bus = drivers::MODBUS(9600, SERIAL_8N1); auto bus = drivers::MODBUS(9600, SERIAL_8N1);
auto rtc = drivers::PCF85063(i2c, PCF85063_ADDRESS); auto rtc = drivers::PCF85063(i2c, PCF85063_ADDRESS);
auto eth = drivers::Ethernet("waveshare-test"); auto eth = drivers::Ethernet("waveshare-test");
auto io = digitalIO(i2c, bus, {relayBoardAddr});
Network.onEvent([&eth](arduino_event_id_t event, arduino_event_info_t info) Network.onEvent([&eth](arduino_event_id_t event, arduino_event_info_t info)
{ eth.onEvent(event, info); }); { eth.onEvent(event, info); });
while (!eth.isConnected()) while (!eth.isConnected() && ethRetries++ < 5)
{ {
LOG_WARN("Waiting for Ethernet"); LOG_WARN("Waiting for Ethernet retry", ethRetries);
delay(1000); delay(1000);
} }
// Set RTC time at startup // Get RTC time at startup
time_t ntpTime; time_t ntpTime;
drivers::PCF85063::datetime_t dt; drivers::PCF85063::datetime_t dt;
eth.getNtpTime(ntpTime);
rtc.setDatetime(drivers::PCF85063::fromEpoch(ntpTime));
// MQTT Test // MQTT Test
NetworkClient tcp; NetworkClient tcp;
@@ -101,10 +100,13 @@ void loop()
results.clear(); results.clear();
} }
delay(500); for (auto j(0); j < io.getOutNum(); j++)
LOG_INFO("====>Read Inputs"); {
bus.readInputs(relayBoardAddr, 0x00, 8, values); //io.digitalIOWrite(j, true);
printBool("====>Inputs", values); LOG_INFO("Input", j, io.digitalIORead(j) ? "True" : "False");
delay(500);
}
delay(5000); delay(5000);
} }

93
src/remoteIO.cpp Normal file
View File

@@ -0,0 +1,93 @@
#include <remoteIO.h>
remoteIO::remoteIO(const uint8_t address, drivers::MODBUS &bus) : m_address(address), m_initialized(false), m_bus(bus)
{
LOG_INFO("Initializing relay module");
std::vector<uint16_t> response;
if (!m_bus.readHoldingRegisters(m_address, REG_VERSION, 1, response))
{
LOG_ERROR("Unable to inizialize relay module");
};
LOG_INFO("Software version", std::to_string(response.at(0) / 100.0f).c_str());
m_initialized = true;
resetAll(false);
}
remoteIO::~remoteIO()
{
m_initialized = false;
resetAll(false);
}
const bool remoteIO::setOut(const channel_t ch, const bool value)
{
if (!m_initialized)
return false;
LOG_DEBUG("Write Channel", ch, "->", value ? "True" : "False");
return m_bus.writeCoil(m_address, REG_COILS + ch, value);
}
const bool remoteIO::toggleOut(const channel_t ch)
{
if (!m_initialized)
return false;
std::vector<bool> value;
if (!m_bus.readCoils(m_address, REG_COILS + ch, 1, value))
return false;
LOG_DEBUG("Toggle Channel", ch, "->", !value.front() ? "True" : "False");
return m_bus.writeCoil(m_address, REG_COILS + ch, !value.front());
}
const bool remoteIO::setOutPort(const std::vector<bool> values)
{
if (!m_initialized)
return false;
LOG_DEBUG("Write Port", CH_MAX);
return m_bus.writeCoils(m_address, CH_MAX, values);
}
const bool remoteIO::getOut(const channel_t ch, bool &value)
{
if (!m_initialized)
return false;
std::vector<bool> values;
if (!m_bus.readCoils(m_address, REG_COILS + ch, 1, values))
return false;
value = values.front();
LOG_DEBUG("Read Channel", ch, "->", value ? "True" : "False");
return true;
}
const bool remoteIO::getOutPort(std::vector<bool> &values)
{
if (!m_initialized)
return false;
LOG_DEBUG("Read Port", CH_MAX);
return m_bus.readCoils(m_address, REG_COILS, 8, values);
}
const bool remoteIO::getIn(const channel_t input, bool &value)
{
if (!m_initialized)
return false;
std::vector<bool> values;
if (!m_bus.readInputs(m_address, REG_INPUT + input, 1, values))
return false;
value = values.front();
LOG_DEBUG("Read Input", input, "->", values.front() ? "True" : "False");
return true;
}
const bool remoteIO::getInPort(std::vector<bool> &values)
{
if (!m_initialized)
return false;
LOG_DEBUG("Read Inputs", CH_MAX);
return m_bus.readInputs(m_address, REG_INPUT, CH_MAX, values);
}
void remoteIO::resetAll(const bool value)
{
LOG_DEBUG("Reset All ->", value ? "True" : "False");
m_bus.writeCoil(m_address, REG_ALLCOILS, value);
}

37
src/remoteIO.h Normal file
View File

@@ -0,0 +1,37 @@
#pragma once
#include <DebugLog.h>
#include <RS485_Driver.h>
class remoteIO
{
public:
typedef enum {CH1, CH2, CH3, CH4, CH5, CH6, CH7, CH8, CH_MAX} channel_t;
private:
const uint16_t REG_VERSION = 0x8000;
const uint16_t REG_COILS = 0x0000;
const uint16_t REG_INPUT = 0x0000;
const uint16_t REG_ALLCOILS = 0x00FF;
public:
remoteIO(const uint8_t address, drivers::MODBUS &bus);
~remoteIO();
const bool setOut(const channel_t ch, const bool value);
const bool toggleOut(const channel_t ch);
const bool setOutPort(const std::vector<bool> values);
const bool getOut(const channel_t ch, bool &value);
const bool getOutPort(std::vector<bool> &values);
const bool getIn(const channel_t input, bool &value);
const bool getInPort(std::vector<bool> &values);
void resetAll(const bool value);
private:
bool m_initialized;
drivers::MODBUS &m_bus;
const uint8_t m_address;
};