ES32D26 sample code from Spain Aucavan

// == Aucavan Domo ==

#include <WiFi.h>
#include <PubSubClient.h>
#include <Preferences.h>

// ======================= WiFi =======================
const char* ssid = "*";
const char* password = "*";

// ======================= MQTT =======================
const char* mqtt_server = "192.168.1.100";
const char* mqtt_user = "*";
const char* mqtt_pass = "";
WiFiClient espClient;
PubSubClient client(espClient);

// ======================= 74HC165 (Entradas digitales) =======================
#define PIN_LOAD 0
#define PIN_CLK  2
#define PIN_DATA 15

// ======================= 74HC595 (Relés) — ACTIVO EN BAJO =======================
const int CLOCK_595 = 22;
const int LATCH_595 = 23;
const int DATA_595  = 12;
const int OE_595    = 13;  // <-- faltaba este punto y coma

// ======================= PWM =======================
#define VO1 25
#define VO2 26

// ======================= Entradas analógicas =======================
int analogPinsVI[] = {14, 33, 27, 32};       // 0–10 V
int analogPinsLI[] = {34, 39, 35, 36};       // 0–20 mA

// ======================= Estado =======================
// Entradas (NPN a GND -> activo=0)
uint8_t inputs = 0xFF;

// Relés activos en bajo: bit = 0 => ON, bit = 1 => OFF
// Valor inicial "seguro" con salidas deshabilitadas: todos OFF (1) hasta sincronizar
uint8_t outputs = 0xFF;

bool retained_seen = false;          // vimos algún retenido MQTT al inicio (relés)
bool initial_outputs_applied = false;

// Calibración
float voltReal = 0;
float voltLeido = 0;
float calibrationFactor = 1.0;

// ======================= PWM setpoints (0..10) =======================
int  vo1_sp = 0;
int  vo2_sp = 0;
bool vo1_retained_seen = false;
bool vo2_retained_seen = false;

// Ventana de arranque para detectar retenidos
volatile bool start_window_active = false;

// ======================= Preferencias =======================
Preferences prefsCalib;
Preferences prefsRelays;
Preferences prefsPWM;

// ======================= Utilidades =======================
void setup_wifi() {
  delay(10);
  Serial.println("Conectando a WiFi...");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi conectada: " + WiFi.localIP().toString());
}

// ----- 74HC595 -----
void Send_Bytes(uint8_t dat) { // MSB → LSB
  for (uint8_t i = 8; i >= 1; i--) {
    digitalWrite(DATA_595, (dat & 0x80) ? HIGH : LOW);
    dat <<= 1;
    digitalWrite(CLOCK_595, LOW);
    digitalWrite(CLOCK_595, HIGH);
  }
}

void Send_74HC595(uint8_t out) {
  digitalWrite(LATCH_595, LOW);
  Send_Bytes(out);
  digitalWrite(LATCH_595, HIGH);
}

void setupShiftRegisterPins() {
  pinMode(CLOCK_595, OUTPUT);
  pinMode(LATCH_595, OUTPUT);
  pinMode(DATA_595,  OUTPUT);
  pinMode(OE_595,    OUTPUT);

  // Mantener salidas deshabilitadas hasta tener estado inicial (anti-parpadeo)
  digitalWrite(OE_595, HIGH);
}

void applyOutputsAndEnable() {
  Send_74HC595(outputs);
  // Habilitar salidas
  digitalWrite(OE_595, LOW);
  initial_outputs_applied = true;
}

void updateRelays(uint8_t relaysState) {
  outputs = relaysState;
  Send_74HC595(outputs);
}

void publishRelayStates() {
  for (int i = 0; i < 8; i++) {
    // activo-bajo: bit 0 => ON, bit 1 => OFF
    int stateOn = (bitRead(outputs, i) == 0) ? 1 : 0;
    client.publish(String("RELE/STATE/CH" + String(i + 1)).c_str(), String(stateOn).c_str());
  }
}

// ----- 74HC165 -----
uint8_t readInputs165() {
  uint8_t result = 0;
  digitalWrite(PIN_LOAD, LOW);
  digitalWrite(PIN_LOAD, HIGH);
  for (uint8_t i = 0; i < 8; i++) {
    result <<= 1;
    digitalWrite(PIN_CLK, LOW);
    if (digitalRead(PIN_DATA) == 0) { result |= 0x01; } // NPN a GND => activo = 0
    digitalWrite(PIN_CLK, HIGH);
  }
  return result;
}

// ======================= PWM helpers =======================
void applyVO() {
  int v1 = constrain(vo1_sp, 0, 10);
  int v2 = constrain(vo2_sp, 0, 10);
  ledcWrite(0, map(v1, 0, 10, 0, 255));
  ledcWrite(1, map(v2, 0, 10, 0, 255));
}

// ======================= Helpers RELE/CHx =======================
void setRelayFromMsg(uint8_t idx, const String& msg) {
  // Acepta "0"/"1" y también "ON"/"OFF"
  int val;
  if (msg.equalsIgnoreCase("ON")) val = 1;
  else if (msg.equalsIgnoreCase("OFF")) val = 0;
  else val = msg.toInt();

  // Activo-bajo: 1 => 0, 0 => 1
  bitWrite(outputs, idx, val ? 0 : 1);
}

// ======================= MQTT =======================
void callback(char* topic, byte* payload, unsigned int length) {
  String t = String(topic);
  String msg; msg.reserve(length);
  for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];

  // Detectar mensajes durante la ventana de arranque (para relés)
  static bool during_start_window = true;

  // Control individual de cada relé (activo-bajo)
  if (t == "RELE/CH1") { setRelayFromMsg(0, msg); retained_seen = retained_seen || during_start_window; }
  else if (t == "RELE/CH2") { setRelayFromMsg(1, msg); retained_seen = retained_seen || during_start_window; }
  else if (t == "RELE/CH3") { setRelayFromMsg(2, msg); retained_seen = retained_seen || during_start_window; }
  else if (t == "RELE/CH4") { setRelayFromMsg(3, msg); retained_seen = retained_seen || during_start_window; }
  else if (t == "RELE/CH5") { setRelayFromMsg(4, msg); retained_seen = retained_seen || during_start_window; }
  else if (t == "RELE/CH6") { setRelayFromMsg(5, msg); retained_seen = retained_seen || during_start_window; }
  else if (t == "RELE/CH7") { setRelayFromMsg(6, msg); retained_seen = retained_seen || during_start_window; }
  else if (t == "RELE/CH8") { setRelayFromMsg(7, msg); retained_seen = retained_seen || during_start_window; }

  // PWM (0..10) con persistencia y detección de retenidos
  if (t == "OUT/VO1") {
    int v = msg.toInt();
    vo1_sp = constrain(v, 0, 10);
    applyVO();
    if (start_window_active) vo1_retained_seen = true;
    prefsPWM.putInt("vo1_sp", vo1_sp);
  }
  if (t == "OUT/VO2") {
    int v = msg.toInt();
    vo2_sp = constrain(v, 0, 10);
    applyVO();
    if (start_window_active) vo2_retained_seen = true;
    prefsPWM.putInt("vo2_sp", vo2_sp);
  }

  // Calibración
  if (t == "CALIBRACION/VOLTR") {
    voltReal = msg.toFloat();
    prefsCalib.putFloat("voltReal", voltReal);
  }
  if (t == "CALIBRACION/VOLTL") {
    voltLeido = msg.toFloat();
    prefsCalib.putFloat("voltLeido", voltLeido);
  }
  if (voltLeido != 0) calibrationFactor = voltReal / voltLeido;

  // Aplicar cambios a relés si corresponde
  if (t.startsWith("RELE/CH")) {
    updateRelays(outputs);
    publishRelayStates();
    prefsRelays.putUChar("haveState", 1);
    prefsRelays.putUInt("relaysState", outputs);
  }
}

void subscribeTopics() {
  for (int i = 1; i <= 8; i++) client.subscribe(("RELE/CH" + String(i)).c_str()); // recibir retenidos
  client.subscribe("OUT/VO1");
  client.subscribe("OUT/VO2");
  client.subscribe("CALIBRACION/VOLTR");
  client.subscribe("CALIBRACION/VOLTL");
}

bool mqttConnect() {
  Serial.print("Conectando a MQTT...");
  bool ok = client.connect("ES32D26_Client", mqtt_user, mqtt_pass);
  Serial.println(ok ? "Conectado" : "Fallo");
  return ok;
}

void waitForRetainedWindow(unsigned long ms_window) {
  // Durante esta ventana procesamos loop() para que lleguen los retenidos
  start_window_active = true;
  unsigned long start = millis();
  while (millis() - start < ms_window) {
    client.loop();
    delay(5);
  }
  start_window_active = false;
}

void reconnect() {
  while (!client.connected()) {
    if (mqttConnect()) {
      subscribeTopics();

      // ventana para recibir retenidos al arranque
      waitForRetainedWindow(800); // ajusta si tu broker es lento

      // ---------- RELÉS: si no hubo retenidos, restaurar de NVS ----------
      if (!retained_seen) {
        uint8_t have = prefsRelays.getUChar("haveState", 0);
        if (have) {
          outputs = (uint8_t)prefsRelays.getUInt("relaysState", 0xFF);
          Serial.println("Relés: restaurado estado de NVS");
        } else {
          outputs = 0xFF;
          Serial.println("Relés: sin retenidos ni NVS, usando estado por defecto (OFF)");
        }
      } else {
        Serial.println("Relés: estado inicial tomado de mensajes retenidos MQTT");
      }

      // ---------- PWM: decidir valores iniciales de VO1/VO2 ----------
      if (!vo1_retained_seen) {
        vo1_sp = prefsPWM.getInt("vo1_sp", vo1_sp); // usa último guardado
        Serial.printf("VO1: sin retenido, recuperado de NVS: %d\n", vo1_sp);
      } else {
        Serial.printf("VO1: valor inicial desde MQTT retenido: %d\n", vo1_sp);
      }
      if (!vo2_retained_seen) {
        vo2_sp = prefsPWM.getInt("vo2_sp", vo2_sp);
        Serial.printf("VO2: sin retenido, recuperado de NVS: %d\n", vo2_sp);
      } else {
        Serial.printf("VO2: valor inicial desde MQTT retenido: %d\n", vo2_sp);
      }
      applyVO();  // asegura que quedan aplicados

      // Aplica estado inicial de relés y habilita salidas una única vez
      if (!initial_outputs_applied) {
        applyOutputsAndEnable();
        publishRelayStates();
      }

    } else {
      Serial.print("Fallo MQTT, rc=");
      Serial.println(client.state());
      delay(2000);
    }
  }
}

// ======================= SETUP =======================
void setup() {
  Serial.begin(115200);

  // Preferencias
  prefsCalib.begin("calib", false);
  voltReal  = prefsCalib.getFloat("voltReal", 0);
  voltLeido = prefsCalib.getFloat("voltLeido", 0);
  if (voltLeido != 0) calibrationFactor = voltReal / voltLeido;

  prefsRelays.begin("relays", false);

  prefsPWM.begin("pwm", false);
  vo1_sp = prefsPWM.getInt("vo1_sp", 0);  // lectura inicial (fallback)
  vo2_sp = prefsPWM.getInt("vo2_sp", 0);

  // Pines 74HC165
  pinMode(PIN_LOAD, OUTPUT);
  pinMode(PIN_CLK,  OUTPUT);
  pinMode(PIN_DATA, INPUT);

  // Pines 74HC595 — mantener salidas deshabilitadas hasta sincronizar estado
  setupShiftRegisterPins();

  // PWM setup
  ledcSetup(0, 5000, 8); ledcAttachPin(VO1, 0);
  ledcSetup(1, 5000, 8); ledcAttachPin(VO2, 1);
  applyVO(); // aplica el valor NVS inicial, se corregirá si llegan retenidos

  // Conectar WiFi y MQTT
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);

  // Conexión inicial y sincronización de estado
  reconnect();
}

// ======================= LOOP =======================
void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  // Leer entradas digitales y publicar
  inputs = readInputs165();
  for (int i = 0; i < 8; i++) {
    client.publish(("IN/IN" + String(i + 1)).c_str(), String(bitRead(inputs, i)).c_str());
  }

  // Entradas analógicas de voltaje (0–10 V)
  for (int i = 0; i < 4; i++) {
    int raw = analogRead(analogPinsVI[i]);
    float voltage = (raw / 4095.0f) * 10.0f * calibrationFactor;
    client.publish(("INV/VI" + String(i + 1)).c_str(), String(voltage, 2).c_str());
  }

  // Entradas de corriente (0–20 mA)
  for (int i = 0; i < 4; i++) {
    int raw = analogRead(analogPinsLI[i]);
    float current = (raw / 4095.0f) * 20.0f;
    client.publish(("INL/LI" + String(i + 1)).c_str(), String(current, 2).c_str());
  }

  delay(250);
}

Back to blog

1 comment

Hi. I bought the same board. I have already debugged the input/output. I made it in Arduino IDE in two files. Check your board.
***************************************************
//First file
//esp32_business.ino
#include “esp32_business.h”

extern IN_BYTE I;
extern QOUT_BYTE Q;
uint8_t SetOutRelay();
void ReadInputs ();

void setup ()
{
//set pins to output
pinMode(STcp,OUTPUT);
pinMode(SHcp,OUTPUT);
pinMode(DS,OUTPUT);
Serial.begin(115200);

pinMode(shld_pin, OUTPUT);

// pinMode(ce_pin, OUTPUT);
pinMode(clk_pin, OUTPUT);
pinMode(data_pin, INPUT);
// выключаем регистр
digitalWrite(clk_pin, HIGH);
digitalWrite(shld_pin, HIGH);

}
void loop()
{
//Всегда вызывать в кода
ReadInputs () ;
Q.CH1=I.IN1;
Q.CH2=I.IN2;
Q.CH3=I.IN3;
Q.CH4=I.IN4;
Q.CH5=I.IN5;
Q.CH6=I.IN6;
Q.CH7=I.IN7;
Q.CH8=I.IN8;

/// Serial.printf(“ADC analog value = %d\n”, SetOut()); //Всегда вызывать в конце кода SetOutRelay(); //delay(1000);

}

//Second Files
//esp32_business.ino

const int STcp = 23;//ST_CP
const int SHcp = 22;//SH_CP
const int DS = 12; //DS

const uint8_t data_pin = 15; //
const uint8_t shld_pin = 0; //
const uint8_t clk_pin = 2; // K

struct QOUT_BYTE
{
bool CH1;
bool CH2;
bool CH3;
bool CH4;
bool CH5;
bool CH6;
bool CH7;
bool CH8;
};

struct IN_BYTE
{
bool IN1;
bool IN2;
bool IN3;
bool IN4;
bool IN5;
bool IN6;
bool IN7;
bool IN8;
};
IN_BYTE I;
QOUT_BYTE Q;

void ReadInputs () { digitalWrite(shld_pin, LOW); delayMicroseconds(5); digitalWrite(shld_pin, HIGH); delayMicroseconds(5); pinMode(clk_pin, OUTPUT); pinMode(data_pin, INPUT); uint8_t the_shifted = shiftIn(data_pin, clk_pin, MSBFIRST); if ((the_shifted & 0×01) == 0×01) I.IN1 = true; else I.IN1 = false; if ((the_shifted & 0×02) == 0×02) I.IN2 = true; else I.IN2 = false; if ((the_shifted & 0×04) == 0×04) I.IN3 = true; else I.IN3 = false; if ((the_shifted & 0×08) == 0×08) I.IN4 = true; else I.IN4 = false; if ((the_shifted & 0×10) == 0×10) I.IN5 = true; else I.IN5 = false; if ((the_shifted & 0×20) == 0×20) I.IN6 = true; else I.IN6 = false; if ((the_shifted & 0×40) == 0×40) I.IN7 = true; else I.IN7 = false; if ((the_shifted & 0×80) == 0×80) I.IN8 = true; else I.IN8 = false; // Serial.print(the_shifted); // Serial.print(" – ");

// Serial.println(the_shifted, BIN);
}

uint8_t SetOutRelay()
{
uint8_t outs=0;
if (Q.CH1 == true ) outs |= 0×01;
if (Q.CH2 == true ) outs |= 0×02;
if (Q.CH3 == true ) outs |= 0×04;
if (Q.CH4 == true ) outs |= 0×08;
if (Q.CH5 == true ) outs |= 0×10;
if (Q.CH6 == true ) outs |= 0×20;
if (Q.CH7 == true ) outs |= 0×40;
if (Q.CH8 == true ) outs |= 0×80;

digitalWrite(STcp,LOW); //ground ST_CP and hold low for as long as you are transmitting shiftOut(DS,SHcp,MSBFIRST,outs); digitalWrite(STcp,HIGH); //pull the ST_CPST_CP to save the data

return outs;
}

Serg

Leave a comment