MCC Hardware-in-the-Loop Rig,Part-1

MCC-HIL: An 8-Motor Motor Control Center Hardware-in-the-Loop Rig — Part 1
MCC-HIL · Motor Control · Hardware-in-the-Loop

Building an 8-Motor Motor Control Center Hardware-in-the-Loop Rig

Part 1 — Why we built it, how it’s put together, and what it took to make the pieces actually work together.

Siemens S7-300 · BeagleBone Black · Nucleo-F446RE · STM32F429I-DISC1
Part 1 of 2 — Part 2 covers results, lessons, and what’s next

Why build this at all

A Motor Control Center is where an industrial facility’s motor protection actually lives — overcurrent, thermal overload, phase loss, short circuit, all coordinated so a fault on one motor doesn’t take down the ones next to it. The problem with learning, testing, or demonstrating that logic is that a real MCC means real motors, real switchgear, and real risk if the protection logic has a bug in it. You don’t want to find out your overcurrent threshold is wrong by actually letting a 50 HP motor cook itself.

MCC-HIL exists to solve that: a real Siemens S7-300 PLC running genuine protection and sequencing logic, closed-loop with a physically realistic simulation of eight induction motors, so every fault type, every edge case, and every timing assumption can be tested against realistic electrical behavior — inrush current, thermal time constants, slip dynamics — without a single real motor in the room. On top of that, a second, fully independent protection layer running on an STM32 watches the same system at hardware speed with deliberately different thresholds, mirroring how real industrial installations back up programmable logic with hardwired protection relays.

The goal was never just “make a simulation.” It was to build something that behaves enough like a real MCC that the protection logic tested on it would transfer directly to a real installation — and to document every real bug found along the way, because in a project like this the debugging history is as valuable as the working end state.


The architecture — controls and communication

Four real devices, two distinct layers of concern: a controls layer (what makes protection and sequencing decisions) and a communication layer (how those decisions get the data they need, and how they reach the outside world).

The controls layer

DeviceRole
Siemens S7-300
(CPU 315-2DP + CP343-1 + SM323 DI/DO)
Runs the primary protection chain — FB1 (overcurrent), FB2 (thermal), FB3 (phase loss), FB4 (short circuit), FB5 (anti-restart lockout) — plus Start/Stop/Trip sequencing, for all 8 motors, in STL.
BeagleBone BlackRuns the 8-motor physics simulation (slip-based torque/current/thermal model with realistic ~6.5x DOL starting inrush), the gateway process bridging simulation to PLC, a Flask-based operator dashboard, and a SQLite historian.
Nucleo-F446REThe independent hardware protection layer — watches fault data at hardware speed with its own thresholds, drives a physical trip relay (K1) wired into a PLC digital input, entirely separate from the PLC’s own logic.
STM32F429I-DISC1The fault injection engine — computes realistic overload, thermal, phase-loss, and short-circuit ramp dynamics on command, so the protection chain is exercised against real transient behavior rather than instantaneous step changes.

The communication layer

Two separate links carry two very different kinds of traffic:

  • BeagleBone ↔ S7-300, over S7comm / ISO-on-TCP (port 102) — DB10 carries telemetry out of the simulation into the PLC, DB11 carries commands (Start/Stop/Reset/LoadProfile) from the PLC/dashboard back into the simulation.
  • BeagleBone ↔ F446RE ↔ F429I, a chained UART link at 115200 baud, 8N1, using a shared 8-byte framed protocol ([0x55][device_id][msg_type][data][checksum][reserved]). F446RE relays frames between BeagleBone and F429I and separately watches for fault conditions to drive its own protection relay — one physical link, two independent jobs happening on top of it.

The dual-protection concept only works because these are genuinely separate paths: the PLC’s FB chain decides based on DB10 telemetry over S7comm, while F446RE decides independently based on data it sees over the UART link, with its own threshold table. Different thresholds, different code, different failure modes — closer to how a real installation backs up a PLC with a hardwired protection relay than to two copies of the same logic.

FaultPLC layer (per motor)STM32 layer (independent)
Overcurrent40 A sustained 2 s37.4 A sustained 10 s
Thermal95°C sustained 30 s70.0°C instant
Phase lossinstantinstant
Short circuit300 A instantinstant
Anti-restart lockout30 s from any triptrip latched until explicit stop

How we overcame the issues, one layer at a time

Nothing about this rig worked on the first try, at any layer. What follows are the bugs that mattered most — not a complete list (the full debug logs run past 30 entries), but the ones that changed how the whole system was understood.

Controls layer

The relay that never actually protected anything

F446RE’s early bridge firmware passed every fault byte-for-byte between BeagleBone and F429I — every protocol test looked perfect, checksum-clean, correctly addressed. What it never did was call its own protection logic against that data. The wire proving out end-to-end said nothing about whether the receiving code actually acted on what it received — two claims that look identical from a passing test and are not the same claim at all.

Controls layer

Realistic inrush vs. idealized thresholds

Once the motor physics model used a realistic ~6.5x DOL starting-current ratio, a plain Start command on a 50 HP motor — no fault injected at all — tripped the overcurrent and short-circuit protection immediately. The thresholds had been calibrated against F429I’s fixed injected fault values, never against each motor’s own realistic starting transient, so every normal start looked exactly like a fault. The fix was a per-motor 3-second start-inhibit timer riding through the inrush window, feeding into FB1/FB4’s reset input — a real design bug, and exactly the class of problem that reaches the field on actual MCC commissioning projects.

Controls layer

A timer collision from months earlier

Motor 4’s anti-restart lockout timer toggled erratically instead of counting down cleanly — while Motors 1-3, running identical logic, were fine. The first diagnosis blamed inverted timer logic and “fixed” it, which immediately produced a new symptom and was reverted the same day once video evidence proved the original logic was correct. The real cause, found via a STEP7 cross-reference on the timer address itself: a stale diagnostic network from months earlier, left over from early Motor-1 testing, was still writing to the same timer T20 that Motor 4’s protection chain had since been legitimately assigned. Harmless when the timer was unused; a direct collision the moment it wasn’t. The lesson that stuck: when identical code behaves differently per instance, suspect the shared resource, not the logic.

Data layer

Invalid SQL, silently hiding every reading

The dashboard’s latest-per-motor query had been broken since the second version of the dashboard — every single call — but a fallback path quietly substituted plausible-looking default values instead of surfacing the failure. The dashboard looked like it was working the entire time it wasn’t.

Data layer

A clock that time-traveled

The BeagleBone has no battery-backed RTC. Across several sessions, its clock reset and drifted, producing future-stamped rows in the historian that silently shadowed all genuinely fresh data in any query using MAX(timestamp) — masquerading as three unrelated “application bugs” before the actual cause (an unset clock) was identified. Setting the clock at the start of every session is now step zero of the run procedure, documented directly in the README for exactly this reason.

Communication layer

The UART chain — its own two-part story

Getting BeagleBone, F446RE, and F429I talking reliably over the shared UART chain was substantial enough to be its own separate write-up: an empty Error_Handler() hiding a failed clock configuration, a single unchecked NVIC interrupt-enable box costing an entire link, a terminal’s Local Echo setting fabricating a false “reply” that looked exactly like success, and a protocol handler that silently never sent its acknowledgment. If you want the full blow-by-blow, including exactly how each false lead was ruled out, that’s covered in the companion post “Three Boards, One Wire Protocol, and a Week of Learning to Doubt My Own Evidence.”


Part 2 covers where all of this landed — the final validated system, the cross-cutting lessons that applied across every layer, and the roadmap for what comes next.

MCC-HIL — Motor Control Center Hardware-in-the-Loop · Durgaram, Jdsan Controls · Part 1 of 2