The short answer is no: a standard Arduino is a microcontroller development board, not a Programmable Logic Controller (PLC). A PLC is a ruggedized, deterministic industrial computer built to survive high electromagnetic interference (EMI), wide temperature swings, and 24V DC factory environments. A standard Arduino Uno operates at 5V (or 3.3V), lacks galvanic isolation on its I/O pins, and runs a non-deterministic loop() that can stall if a single line of code blocks.
However, the gap is closing. By pairing a modern microcontroller with industrial I/O shields (optoisolators, 24V tolerant inputs) and writing cyclic-scan firmware with watchdog timers, you can build a 'PLC-equivalent' system for light automation, prototyping, or custom machinery. Below, we break down the exact hardware differences and build a functional, isolated motor starter circuit to prove it.
The Hardware Gap: Microcontroller vs. Industrial PLC
To understand why you cannot wire a 24V industrial proximity sensor directly to an Arduino Uno, you have to look at the physical layer. Industrial PLCs comply with standards like IEC 61131-2, which mandates strict voltage thresholds and galvanic isolation to prevent high-voltage transients from frying the CPU. Here is how a standard hobby board, a dedicated industrial Arduino, and a legacy PLC stack up.
| Specification | Arduino Uno R4 Minima | Arduino Opta (WiFi) | Siemens S7-1200 (CPU 1212C) |
|---|---|---|---|
| Native I/O Voltage | 5V DC (Digital), 0-5V (Analog) | 24V DC (Digital Inputs) | 24V DC (Sink/Source) |
| Galvanic Isolation | None (Direct CPU connection) | 1500 Vrms (I/O to Logic) | 500 Vrms (Channel to Channel) |
| Scan Time (Deterministic) | Variable (depends on loop() bloat) |
< 1ms (Optimized PLC runtime) | 0.1ms to 10ms (Hardware enforced) |
| Operating Temperature | 0°C to 50°C | -20°C to 60°C | -20°C to 60°C |
| Approx. Price (2026) | $20.00 (Board only) | $185.00 (Board only) | $450.00+ (CPU only) |
| Programming Standard | C/C++ (Arduino IDE) | IEC 61131-3 (Ladder/ST) & C++ | IEC 61131-3 (TIA Portal) |
If your budget allows, the Arduino Opta is a true micro-PLC. But for custom machine builds where you need the flexibility of C++ and a lower price point, an Arduino Uno R4 Minima paired with an industrial 24V optoisolated shield is the standard maker workaround.
Building a PLC-Equivalent Motor Starter
We are building a 3-phase motor starter control circuit. The Arduino will read a 24V proximity sensor and a hardwired E-Stop, then trigger a 24V DC contactor coil. We use an optoisolated shield to ensure that when the contactor coil collapses and generates a massive inductive voltage spike, it does not travel back into the microcontroller's GPIO pins.
Parts List
- Microcontroller: Arduino Uno R4 Minima (ABX00080)
- I/O Shield: 4-Channel 24V Industrial Optoisolated Relay/Input Module (e.g., NCD or WaveShare 24V industrial shield)
- Contactor: Schneider Electric TeSys D LC1D09 (24V DC coil)
- Power Supply: Mean Well DR-30-24 (24V DC, 1.5A DIN-rail PSU)
- Sensors: 24V NPN Proximity Sensor (M12), 24V E-Stop pushbutton (Normally Closed)
- Wiring: 18 AWG stranded for 24V control, 12 AWG for 3-phase power
Pin Mapping Table
| Arduino R4 Pin | Shield / Component | Function |
|---|---|---|
| D2 (INT) | Opto-Input 1 | 24V Proximity Sensor (NO) |
| D3 (INT) | Opto-Input 2 | E-Stop Button (NC) |
| D8 | Opto-Relay 1 | Contactor Coil Drive |
| A0 | Voltage Divider | 24V Bus Analog Sense |
| GND | Shield GND | Logic Ground Reference |
Wiring Steps
- Mount the Mean Well DR-30-24 PSU and the Schneider contactor on a grounded DIN rail.
- Wire the 24V DC output from the PSU to the VCC and GND terminals of the optoisolated shield.
- Connect the E-Stop NC contact between the 24V positive bus and Opto-Input 2. When the E-Stop is pressed, the circuit opens, pulling the opto-input LOW.
- Connect the proximity sensor's brown wire to 24V, blue to GND, and black (signal) to Opto-Input 1.
- Wire Opto-Relay 1's Common (COM) to 24V positive, and Normally Open (NO) to the A1 terminal of the contactor coil. Wire A2 to 24V GND.
- Install a flyback diode (1N4007) across the contactor coil (A1 to A2, stripe facing A1) to suppress inductive kickback.
The Code: Cyclic Scan and Watchdog Implementation
PLCs do not use delay(). They use a continuous, high-speed cyclic scan: Read Inputs → Execute Logic → Write Outputs → Diagnostics. The code below targets the Arduino Uno R4 Minima. It implements a strict scan-time monitor and a software watchdog to ensure the contactor drops out if the firmware hangs.
// Target: Arduino Uno R4 Minima
// Application: PLC-Style Motor Starter with Cyclic Scan & Diagnostics
#define PIN_SENSOR_PROX 2
#define PIN_ESTOP_NC 3
#define PIN_CONTACTOR 8
#define PIN_24V_SENSE A0
// Diagnostics thresholds
#define MAX_SCAN_TIME_US 5000 // 5ms max scan time
#define MIN_BUS_VOLTAGE 20.0 // Minimum 24V bus threshold
// State variables
bool system_fault = false;
bool motor_running = false;
unsigned long previous_micros = 0;
unsigned long scan_time_us = 0;
void setup() {
Serial.begin(115200);
pinMode(PIN_SENSOR_PROX, INPUT); // Opto-isolator outputs active LOW or HIGH depending on shield
pinMode(PIN_ESTOP_NC, INPUT_PULLUP);
pinMode(PIN_CONTACTOR, OUTPUT);
// Ensure safe state on boot
digitalWrite(PIN_CONTACTOR, LOW);
Serial.println("SYSTEM: Boot sequence complete. Entering cyclic scan.");
}
void loop() {
unsigned long current_micros = micros();
// 1. DIAGNOSTICS: Check Scan Time (Simulated Watchdog)
scan_time_us = current_micros - previous_micros;
previous_micros = current_micros;
if (scan_time_us > MAX_SCAN_TIME_US) {
triggerFault("ERR: SCAN_TIME > 5ms");
}
// 2. DIAGNOSTICS: Check 24V Bus Health
float bus_voltage = readBusVoltage();
if (bus_voltage < MIN_BUS_VOLTAGE && !system_fault) {
char msg[50];
snprintf(msg, sizeof(msg), "FAULT: 24V_BUS_UNDERVOLTAGE (Read: %.1fV)", bus_voltage);
triggerFault(msg);
}
// 3. READ INPUTS
bool prox_active = digitalRead(PIN_SENSOR_PROX) == HIGH; // Adjust for shield logic
bool estop_ok = digitalRead(PIN_ESTOP_NC) == LOW; // NC contact pulls LOW when safe/closed
// 4. EXECUTE LOGIC
if (system_fault) {
motor_running = false; // Fault latches motor OFF
} else if (!estop_ok) {
motor_running = false; // E-Stop overrides everything
Serial.println("STATE: E-STOP LATCHED");
} else if (prox_active) {
motor_running = true;
} else {
motor_running = false;
}
// 5. WRITE OUTPUTS
digitalWrite(PIN_CONTACTOR, motor_running ? HIGH : LOW);
// Small yield to prevent WDT reset on some RTOS-backed cores
delay(1);
}
float readBusVoltage() {
// Assuming a 10:1 voltage divider (e.g., 100k and 10k) feeding A0 (0-5V range)
int raw_adc = analogRead(PIN_24V_SENSE);
float voltage_5v = (raw_adc / 1023.0) * 5.0;
return voltage_5v * 11.0; // Multiply by divider ratio
}
void triggerFault(const char* error_msg) {
system_fault = true;
digitalWrite(PIN_CONTACTOR, LOW); // Fail-safe: drop contactor
Serial.print("CRITICAL: ");
Serial.println(error_msg);
// In a real PLC, this would trigger a hardware watchdog reset or blink a red fault LED
while(1) {
// Halt execution until manual hardware reset
}
}
Debugging Industrial I/O Failures
When transitioning from breadboards to industrial 24V I/O, you will encounter specific failure modes. If your Serial Monitor outputs the exact error string FAULT: 24V_BUS_UNDERVOLTAGE (Read: 18.2V) and the contactor refuses to engage, do not immediately blame the code. Follow this ranked diagnostic tree.
Ranked Causes for 24V Undervoltage Faults
- Inductive Inrush Sag: Contactor coils draw a massive inrush current (often 10x the holding current) for the first 20-50 milliseconds when energized. If your Mean Well PSU is undersized (e.g., a 15W supply trying to drive a 30VA coil), the voltage will temporarily collapse below 20V, triggering the software fault before the contactor fully pulls in. Fix: Upgrade to a PSU with at least 20% overhead on the inrush rating, or add a 4700µF 35V capacitor across the 24V bus.
- Voltage Drop Across Control Wiring: If you ran 50 feet of 22 AWG wire to a remote sensor box, the resistance will drop the voltage significantly under load. Fix: Measure the voltage at the contactor A1/A2 terminals under load, not at the PSU. Upgrade control wiring to 18 AWG or 16 AWG.
- Blown Shield Fuse: Most industrial opto-shields have a 5x20mm glass fuse on the 24V input. A short circuit in the field wiring may have partially blown the fuse, increasing its resistance and dropping voltage. Fix: Check fuse continuity with a multimeter; replace with the exact amperage specified on the shield silkscreen.
The First Three Things to Check When It Fails
If the system is completely unresponsive and the contactor never clicks:
- Verify E-Stop Continuity: E-Stops are wired Normally Closed (NC). If you accidentally wired a Normally Open (NO) contact, the Arduino will read the E-Stop as permanently 'pressed' and inhibit the motor. Check continuity across the E-Stop terminals with the button released.
- Check Optocoupler CTR Degradation: If you are using a cheap, unbranded opto-shield, the Current Transfer Ratio (CTR) of the internal PC817 optocouplers may be too low to trigger reliably at 24V with the provided current-limiting resistors. Verify the shield's status LEDs light up when the sensor triggers.
- Confirm Flyback Diode Polarity: If the 1N4007 flyback diode across the contactor coil is installed backward, it acts as a dead short across the 24V supply the moment the relay turns on, instantly tripping the PSU's overcurrent protection and dropping the bus voltage to zero.
Extending and Simplifying the Build
Once the basic motor starter is reliable, you will likely need to integrate it into a larger factory network or scale it down for a desktop prototype.
How to Extend: Adding Modbus RTU
To make this Arduino talk to a commercial HMI (Human Machine Interface) or a master PLC, add an RS-485 transceiver module (like the MAX485 or an isolated ADM2587). Wire the RS-485 A/B lines to a twisted-pair cable. Use the ArduinoModbus library to expose the motor_running state as a Coils register and the bus_voltage as an Input Register. This allows a central SCADA system to monitor your custom machine without rewiring the entire facility.
How to Simplify: Benchtop Prototyping
If you are just testing logic on a workbench and do not have a 24V PSU or a heavy contactor, drop the industrial shield entirely. Replace the contactor with a 5V or 12V solenoid valve or a small DC motor. Use a logic-level N-channel MOSFET (like the IRLZ44N) to switch the load. Connect the MOSFET gate to Arduino Pin 8 via a 220Ω resistor, add a 10kΩ pull-down resistor from gate to ground to prevent floating-gate turn-on during boot, and wire the load between the drain and your positive supply. This strips away the isolation but preserves the exact same cyclic-scan code logic for software testing.






