undoPLC v0.2.0

undoPLC Released ยท v0.2.0

Open source multi-threaded PLC infrastructure designed for the undoRT ecosystem. Provides deterministic Master/Worker synchronization with real-time guarantees.

๐Ÿ”„ v0.2.0 Update: This release integrates undoCore as the shared foundation library, providing ProcessImage for I/O management and IoBus as the fieldbus contract.

Overview

undoPLC is a C++20 framework that implements a deterministic fork-join execution model for real-time control applications. It provides:

๐Ÿ’ก Real-time ready: Designed for PREEMPT_RT kernels with isolated CPU cores. Requires at least 2 isolated cores (1 for Master, 1+ for Workers).

Key Features

1. Master/Worker Fork-Join Model

The UndoMasterTaskBase orchestrates the execution cycle:

2. Cycle Time Management

The system maintains an aligned absolute cycle time that is:

// Master provides aligned cycle time to workers
uint64_t cycleTimeMs = master.getCurrentCycleTimeMs();
uint64_t cycleTimeNs = master.getCurrentCycleTimeNs();

3. Startup Delay

The Master task starts with a 5-cycle delay to ensure:

Architecture

Core Components

Component Description
UndoMasterTaskBase Orchestrates the execution cycle, reads/writes fieldbus I/O via undoCore::IoBus
UndoWorkerTaskBase Executes user PRG logic in parallel on isolated cores
UndoLog Lock-free deferred logging with thread-local queues
UndoSys CPU isolation, frequency management, TSC utilities
UndoMutex Pthread mutex with PTHREAD_PRIO_INHERIT
undoCore::ProcessImage Double-buffered IEC 61131-3 memory layout (%I, %Q, %M)
undoCore::IoBus Abstract fieldbus master interface (implemented by undoBUS)
โœ… Shared Foundation: undoPLC now uses undoCore as a submodule, ensuring seamless interoperability with undoBUS and other undoRT components.

Execution Flow

// 1. Master thread runs with SCHED_FIFO
while (running) {
    // Wait for next aligned cycle
    clock_nanosleep(...);

    // Update absolute cycle time
    _currentCycleTimeNs += _cycleTimeNs;

    // Read fieldbus inputs (via undoCore::IoBus)
    readInputBus();  // calls bus->waitCycle() -> processImage.copyIn()

    // Fork: wake all workers
    workerCv.notify_all();

    // Join: wait for workers with timeout
    masterCv.wait_for(...);

    // Write fieldbus outputs (via undoCore::IoBus)
    writeOutputBus(); // calls bus->notifyDone() -> processImage.copyOut()
}

// 2. Worker threads run with SCHED_FIFO (lower priority)
while (running) {
    // Wait for master signal
    workerCv.wait(...);

    // Copy master's cycle time
    _currentCycleTimeNs = master.getCurrentCycleTimeNs();

    // Execute user logic
    runWork();

    // Notify master of completion
    activeWorkers.fetch_sub(1);
}

Cycle Time Management

The aligned cycle time is a key feature that provides deterministic timing independent of system jitter. Here's how it works:

Initialization

// In UndoMasterTaskBase::run()
struct timespec baseTime;
clock_gettime(CLOCK_MONOTONIC, &baseTime);

uint64_t baseNs = baseTime.tv_sec * 1000000000ULL + baseTime.tv_nsec;

// Align to next multiple of cycle time
uint64_t alignNs = _cycleTimeNs - (baseNs % _cycleTimeNs);
if (alignNs == _cycleTimeNs) alignNs = 0;
baseNs += alignNs;

// Add 5-cycle startup delay for safety
baseNs += (_cycleTimeNs * _STARTUP_DELAY_CYCLES);

_currentCycleTimeNs.store(baseNs, std::memory_order_release);

Cycle Update

// Each cycle increment is deterministic
uint64_t currentAbsoluteNs = _currentCycleTimeNs.load(std::memory_order_acquire) + _cycleTimeNs;
_currentCycleTimeNs.store(currentAbsoluteNs, std::memory_order_release);

Worker Synchronization

// Worker copies master's cycle time
_cycleTimeNs = _master->getCycleNs();
_currentCycleTimeNs = _master->getCurrentCycleTimeNs();
โœ“ Benefits: All workers see the exact same absolute timestamp for each cycle, enabling precise coordination and logging.

Test Application

The main.cpp demonstrates the complete infrastructure with a real-time test:

Demo Worker

Simulates a PRG block with deterministic busy-wait:

class DemoWorkerTask : public UndoWorkerTaskBase
{
   protected:
      bool runWork() override
      {
         // Simulate fixed computational workload
         UndoSys::getInstance().busyWait(_workNs);
         ++_cycleCount;
         return true;
      }
};

Demo Master

Drives the execution cycle and logs diagnostics every second:

class DemoMasterTask : public UndoMasterTaskBase
{
   protected:
      void writeOutputBus() override
      {
         if (++_cycleCount % 1000 == 0) {
            const DiagVars& d = getDiagVars();
            UndoLog::getInstance().logRT(
               LogDomain::PLC, LOG_INFO,
               "cycle %llu (%lu mS)| exec[min=%u max=%u]us jitter[min=%u max=%u]us",
               _cycleCount, getCurrentCycleTimeMs(),
               d.execMin, d.execMax, d.jitterMin, d.jitterMax
            );
         }
      }
};

Running the Test

# Build and run with PREEMPT_RT kernel
$ make
$ sudo ./undoPLC --log2console

# Expected output:
# [INFO] undoPLC: Starting with 5 cycles delay
# [INFO] undoPLC: Master on core 4 | 2 worker(s) spawned
# [INFO] undoPLC: cycle 1000 (1000 mS)| exec[min=98 max=102]us jitter[min=0 max=5]us
โš ๏ธ Requirements:
  • PREEMPT_RT kernel
  • At least 2 isolated CPU cores (isolcpus= in GRUB)
  • Root privileges for CPU frequency management

Configuration Example

# GRUB configuration (/etc/default/grub)
GRUB_CMDLINE_LINUX="... isolcpus=4,5,6,7 nohz_full=4,5,6,7 rcu_nocbs=4,5,6,7"

# Update GRUB
$ sudo update-grub

# Reboot and verify
$ cat /sys/devices/system/cpu/isolated
4-7

API Reference

Full API documentation is generated with Doxygen: Browse API Reference

Key Classes

Class Key Methods
UndoMasterTaskBase start(), stop(), getCurrentCycleTimeNs(), getCurrentCycleTimeMs(), registerWorker(), readInputBus(), writeOutputBus()
UndoWorkerTaskBase start(), stop(), runWork(), getCurrentCycleTimeNs()
UndoLog logRT(), registerThread(), init()
UndoSys getIsolatedCpu(), getSharedCpu(), setCpuNominalFrequency(), tsc2Ns()
๐Ÿ“– More documentation: Visit the GitHub repository for build instructions, examples, and contributing guidelines.