The Short Answer: Is an Arduino a PLC?
No, a standard Arduino (like the Uno or Mega) is not a Programmable Logic Controller (PLC). An Arduino is a microcontroller development board designed for 5V/3.3V logic, rapid prototyping, and community-driven software ecosystems. A PLC is an industrialized, ruggedized computer built to the IEC 61131-3 standard, featuring 24V DC logic, galvanic isolation, deterministic scan times, and hardware watchdogs designed to survive high-EMI factory floors.
However, you can make an Arduino behave like a PLC by adding optocoupler isolation, writing deterministic scan-cycle code, and enforcing strict memory management. Alternatively, the market now offers hybrid boards like the Arduino Opta, which bridges the gap by packaging an STM32-based Arduino core inside a DIN-rail PLC enclosure with native 24V I/O.
Decision Matrix: When to Use Which Controller
Choosing between a raw microcontroller, an Arduino-based PLC hybrid, and a traditional industrial PLC depends entirely on your environment and failure-tolerance. Use this decision path to select your hardware:
| Criteria | Standard Arduino (Mega 2560) | Arduino Opta WiFi | Traditional PLC (e.g., AutomationDirect CLICK) |
|---|---|---|---|
| Logic Voltage | 5V DC (Unprotected) | 24V DC (Isolated) | 24V DC (Isolated) |
| EMI Immunity | Low (Fails near VFDs/Motors) | High (Industrial shielding) | Extreme (Opto-isolated backplane) |
| Scan Determinism | Variable (Requires custom C++) | High (PLC IDE or custom C++) | Guaranteed (Firmware managed) |
| Typical Cost | $45 - $60 (with shields) | $180 - $220 | $150 - $300+ |
- If your project lives on a clean workbench, handles basic 120V AC switching via external relays, and budget is under $50 → Use the Arduino Mega 2560 Rev3 (ABX00053) with an optocoupler shield.
- If you are wiring directly into a factory panel with 24V sensors, VFDs, and require MQTT telemetry → Buy the Arduino Opta WiFi (AEM04W0000).
- If human safety or catastrophic financial loss relies on the system (e.g., emergency stop circuits, boiler controls) → Buy an AutomationDirect CLICK PLC (C0-00DD1-D) and abandon Arduino entirely.
Building a Deterministic PLC-Style Scan Cycle
To emulate a PLC on a standard Arduino, you must abandon the standard delay()-driven loop and adopt a deterministic scan cycle. A PLC reads all inputs into memory, executes the logic, and writes all outputs in a predictable timeframe (usually under 20ms).
Parts List & Pin Mapping
This build targets the Arduino Mega 2560 Rev3. We use a 4-20mA pressure transducer and an optocoupler relay module to simulate industrial I/O.
| Component | Exact Variant / Part Number | Pin Mapping | Notes |
|---|---|---|---|
| Microcontroller | Arduino Mega 2560 Rev3 (ABX00053) | N/A | Target board for this codebase |
| Output Isolation | Sainsmart 8-Ch 5V Relay w/ Optocouplers | Digital Pin 22 (Pump), 23 (Alarm) | Active LOW logic |
| Input: E-Stop | Industrial NC Mushroom Button | Digital Pin 2 (Interrupt 0) | Wired with 10k pull-up to 5V |
| Input: Pressure | 4-20mA Transducer (0-100 PSI) | Analog Pin A0 | Requires 250Ω shunt resistor to read 1-5V |
Complete Compilable Code
This C++ code implements a PLC-style scan cycle with a software watchdog timer. If the loop takes longer than 20ms, it triggers a fault state and shuts down outputs.
#include <Arduino.h>
// --- PIN DEFINITIONS ---
const int PIN_ESTOP = 2; // Hardware Interrupt 0
const int PIN_PUMP_RELAY = 22; // Active LOW
const int PIN_ALARM_RELAY = 23;// Active LOW
const int PIN_PRESSURE = A0; // Analog Input (1-5V via 250 ohm shunt)
// --- SYSTEM CONSTANTS ---
const unsigned long MAX_SCAN_TIME_MS = 20;
const float ADC_VREF = 5.0;
const int SHUNT_RESISTOR = 250;
// --- STATE VARIABLES ---
volatile bool eStopTriggered = false;
bool pumpState = false;
bool alarmState = false;
float currentPressurePSI = 0.0;
void eStopISR() {
eStopTriggered = true;
}
void setup() {
Serial.begin(115200);
pinMode(PIN_PUMP_RELAY, OUTPUT);
pinMode(PIN_ALARM_RELAY, OUTPUT);
pinMode(PIN_ESTOP, INPUT_PULLUP);
// De-energize relays (Active LOW means HIGH is off)
digitalWrite(PIN_PUMP_RELAY, HIGH);
digitalWrite(PIN_ALARM_RELAY, HIGH);
attachInterrupt(digitalPinToInterrupt(PIN_ESTOP), eStopISR, FALLING);
Serial.println("[SYSTEM] PLC-Style Scan Cycle Initialized.");
}
void readInputs() {
// Read 4-20mA sensor (1V = 4mA, 5V = 20mA)
int rawAdc = analogRead(PIN_PRESSURE);
float voltage = (rawAdc * ADC_VREF) / 1023.0;
float milliamps = (voltage / SHUNT_RESISTOR) * 1000.0;
// Map 4-20mA to 0-100 PSI
if (milliamps < 4.0) milliamps = 4.0; // Handle under-range fault
currentPressurePSI = map(milliamps * 100, 400, 2000, 0, 10000) / 100.0;
}
void executeLogic() {
if (eStopTriggered) {
pumpState = false;
alarmState = true;
return;
}
// Hysteresis logic for pump control
if (currentPressurePSI < 40.0) {
pumpState = true;
} else if (currentPressurePSI > 60.0) {
pumpState = false;
}
// High pressure alarm
alarmState = (currentPressurePSI > 85.0);
}
void writeOutputs() {
digitalWrite(PIN_PUMP_RELAY, pumpState ? LOW : HIGH);
digitalWrite(PIN_ALARM_RELAY, alarmState ? LOW : HIGH);
}
void loop() {
unsigned long scanStart = millis();
readInputs();
executeLogic();
writeOutputs();
// --- WATCHDOG / SCAN CYCLE CHECK ---
unsigned long scanDuration = millis() - scanStart;
if (scanDuration > MAX_SCAN_TIME_MS) {
Serial.print("[FAULT] Scan cycle timeout: Loop exceeded ");
Serial.print(MAX_SCAN_TIME_MS);
Serial.println("ms WDT limit");
// Force safe state
digitalWrite(PIN_PUMP_RELAY, HIGH);
digitalWrite(PIN_ALARM_RELAY, LOW); // Fail-safe alarm ON
while(1); // Halt execution until manual reset
}
// Telemetry (Throttled to avoid blocking the scan cycle)
static unsigned long lastTelemetry = 0;
if (millis() - lastTelemetry > 500) {
Serial.print("Pressure: "); Serial.print(currentPressurePSI);
Serial.print(" PSI | Scan: "); Serial.print(scanDuration); Serial.println("ms");
lastTelemetry = millis();
}
}
Debugging the Scan Cycle Timeout Fault
If your serial monitor outputs the exact error string [FAULT] Scan cycle timeout: Loop exceeded 20ms WDT limit, your microcontroller failed to complete its logic scan within the deterministic window. In a real PLC, this triggers a hardware watchdog reset. Here, we halt execution and sound the alarm.
Ranked Causes for Scan Timeouts
- Blocking I/O Functions: You added a library that uses
delay()or blocking I2C reads (likeWire.requestFrom()without a timeout) inside the main loop. - Interrupt Storms: A noisy 24V field wire is inducing voltage spikes on your 5V input lines, causing the hardware interrupt to fire thousands of times per second, starving the main loop.
- Serial Buffer Overflows: Printing too much telemetry data via
Serial.print()without throttling, causing the TX buffer to fill up and block the CPU.
The First Three Things to Check When It Fails
- Audit for blocking functions: Search your entire codebase for
delay(),while(Serial.available() == 0), and un-throttled I2C/SPI reads. Replace them withmillis()-based state machines. - Check for ground loops: Use an oscilloscope to probe the 5V logic ground relative to the 24V field common. If you see high-frequency noise, your optocoupler isolation has been defeated by a shared ground wire. Separate the logic ground from the field ground entirely.
- Verify analog input impedance: If reading 4-20mA sensors, ensure your shunt resistor is precisely 250Ω (1% tolerance). A floating or high-impedance analog pin will cause the ADC to sample noise, occasionally resulting in math errors that stall the processor.
Extending and Simplifying Your Build
Once your basic scan cycle is stable, you will inevitably need to scale the system. Here is how to manage complexity without breaking determinism.
How to Extend (Adding I/O and Telemetry)
- Add I2C Expansion: If you need more digital inputs, use an I2C expander like the MCP23017. Crucial: Do not read the MCP23017 on every scan cycle. Read it every 5th cycle using a modulo operator to keep the scan time under 20ms.
- Implement MQTT: To send data to a cloud dashboard, use an ESP32 as a dedicated telemetry coprocessor. Connect the Arduino Mega to the ESP32 via hardware UART (Pins 18/19). The Mega handles the deterministic 20ms control loop; the ESP32 handles the non-deterministic, blocking WiFi/MQTT handshakes.
How to Simplify (Reducing Failure Points)
- Ditch the Custom Shields: If you find yourself wiring messy Dupont cables between an Arduino and multiple relay boards, simplify by migrating to the Arduino Opta. It eliminates the need for custom optocoupler wiring and provides native screw-terminal 24V I/O, reducing mechanical failure points by 90%.
- Use PLC IDE: If your logic grows beyond 200 lines of C++, abandon C++ and use the official Arduino PLC IDE. It allows you to write standard IEC 61131-3 Ladder Logic or Function Block Diagrams, which compile down to deterministic machine code and are vastly easier for maintenance technicians to troubleshoot.
An Arduino is not a PLC out of the box, but with strict code discipline, galvanic isolation, and a deterministic scan cycle, it can reliably handle light industrial automation. Know your environment, respect the 20ms scan window, and never route 24V field wiring next to your 5V logic lines.






