← Production Pulse Counter on an ESP32 PLC with Interrupts
Textile monitoring (weaving)ESP32 PLCGPIOAcquisition
Production Pulse Counter on an ESP32 PLC with Interrupts — full example
Count production pulses on an ESP32 PLC using hardware interrupts: debouncing, atomic reads, RPM estimation and MQTT reporting for machine monitoring.
Complete, runnable program for the ESP32 PLC (production-pulse-counter.ino): wiring header, requirements and integration notes included.
Download the full project pack — freeThis example + the related ones + bill of materials
Read-only preview.
/*
* COMPLETE EXAMPLE — Interrupt-driven production pulse counter
*
* Hardware: ESP32 PLC (Industrial Shields, any model with WiFi)
* Based on: textile monitoring project, modul_funciones.h
*
* Wiring:
* I0_0 Production pulse sensor (proximity switch/encoder on the
* cylinder of the Terrot knitting machine: 1 pulse = 1 revolution)
*
* Architecture:
* 1. Hardware interrupt on I0_0 -> volatile counter, no pulses lost
* even when the loop is busy (SD, MQTT, web...)
* 2. Debounce via minimum time between pulses (electrical noise filter)
* 3. Every send cycle (20 s) the counter is read, approximate RPM is
* computed and published over MQTT; then the counter is reset
* 4. Shift total kept in a second variable that is only reset
* by command
*
* Works together with other catalog examples:
* - mqtt-events-sd-buffering.ino (snapshot every 20 s)
* - sd-daily-file-datalogging.ino (backup)
* - mqtt-remote-commands.ino (reset of the shift total)
*/
#include
#include
const char *WIFI_SSID = "WIFI_SSID";
const char *WIFI_PASS = "WIFI_PASS";
const char *MQTT_HOST = "BROKER_IP";
const int MQTT_PORT = 1883;
const int PLC_ID = 1;
const char *TOPIC_DATA = "planta/plc1/data";
const uint32_t SEND_PERIOD_MS = 20000; // one snapshot every 20 s
const uint32_t MIN_PULSE_GAP_MS = 30; // debounce: max ~33 pulses/s
WiFiClient net;
PubSubClient mqtt(net);
// --- Counter state (volatile: touched from the ISR) ---------------------
volatile uint32_t cyclePulses = 0; // reset on every send
volatile uint32_t shiftPulses = 0; // running total, reset by command
volatile uint32_t lastPulseTime = 0;
uint32_t tSend = 0, tWifi = 0;
// ISR: as small as possible, no Serial and no String
void IRAM_ATTR onPulse() {
uint32_t now = millis();
if (now - lastPulseTime < MIN_PULSE_GAP_MS) return; // bounce/noise
lastPulseTime = now;
cyclePulses++;
shiftPulses++;
}
// --- Non-blocking connectivity (see the WiFi reconnection sheet) --------
void checkWiFi() {
if (WiFi.status() == WL_CONNECTED) return;
if (millis() - tWifi < 10000) return;
tWifi = millis();
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
}
void checkMQTT() {
if (WiFi.status() != WL_CONNECTED || mqtt.connected()) return;
mqtt.connect(("plc" + String(PLC_ID)).c_str());
}
void setup() {
Serial.begin(115200);
pinMode(I0_0, INPUT);
attachInterrupt(I0_0, onPulse, FALLING); // falling edge of the sensor
checkWiFi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
}
void loop() {
checkWiFi();
checkMQTT();
mqtt.loop();
if (millis() - tSend >= SEND_PERIOD_MS) {
uint32_t period = millis() - tSend;
tSend = millis();
// Atomic read and reset of the cycle counter
noInterrupts();
uint32_t n = cyclePulses;
uint32_t total = shiftPulses;
cyclePulses = 0;
interrupts();
// Approximate RPM: cycle pulses extrapolated to 1 minute
float rpm = (n * 60000.0f) / period;
String json = "{\"id\":" + String(PLC_ID) +
",\"pulses\":" + String(n) +
",\"shift_total\":" + String(total) +
",\"rpm\":" + String(rpm, 1) + "}";
if (mqtt.connected()) mqtt.publish(TOPIC_DATA, json.c_str());
Serial.println(json); // in production, additionally: saveToSD(json)
}
delay(10); // the counter keeps running in the ISR even while the loop sleeps
}
Download the full project pack — freeThis example + the related ones + bill of materials