Adds what the folder was missing for someone picking it up cold.
as-built/ the STruC++ output copied out of the live container - the C++
the PLC is actually executing, plus program.st and the Modbus
buffer config. The running image was made with docker commit
and exists in no registry, so this is the only other copy of
the compiled form.
Verified while copying: the deployed program.st matches
build/wrps.st generated from src/. Identical POU structure,
ZERO differences in non-declaration lines. src/ is genuinely
canonical and the running PLC agrees with it.
VERSIONS.md every version read from the running system, not from
documentation: runtime v4.1.10, STruC++ 0.6.2, Editor
4.2.11, Debian 12, g++ 12.2, pymodbus 3.11.2, CI
Server R1.03. Plus which plugins are enabled -
ethercat is on for no reason - and a v3-vs-v4 table,
since most OpenPLC guidance online is for v3 and the
%MW HR1024 change silently produces wrong data.
GETTING-STARTED.md three questions answered in a page: how to modify the
program, how to stand up a new PLC container from
scratch, how to move this one. Includes the compose
file, the Editor steps that are not generated and are
always missed, and a symptom-to-document table.
223 lines
7.6 KiB
C++
223 lines
7.6 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception
|
|
// Copyright (C) 2025 Autonomy / OpenPLC Project
|
|
// This file is part of the STruC++ Runtime Library and is covered by the
|
|
// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details.
|
|
/**
|
|
* STruC++ Runtime - Located Variables Support
|
|
*
|
|
* This header defines the types and structures needed for located variables
|
|
* (AT %IX0.0, %QX0.0, %MW100, etc.) that bind to runtime I/O image tables.
|
|
*
|
|
* Located variables provide the interface between IEC programs and physical
|
|
* I/O in PLC systems. The compiler generates a descriptor array that the
|
|
* runtime uses to bind variables to I/O addresses.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include "iec_fault.hpp"
|
|
#if STRUCPP_HAS_EXCEPTIONS
|
|
#include <stdexcept>
|
|
#endif
|
|
|
|
namespace strucpp {
|
|
|
|
// =============================================================================
|
|
// Located Variable Area Types
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Memory area for located variables.
|
|
* Corresponds to the first letter after % in IEC addresses:
|
|
* - I = Input (read from physical inputs)
|
|
* - Q = Output (write to physical outputs)
|
|
* - M = Memory (internal markers/flags)
|
|
*/
|
|
enum class LocatedArea : uint8_t {
|
|
Input = 0, // %I - Input area
|
|
Output = 1, // %Q - Output area
|
|
Memory = 2 // %M - Memory/Marker area
|
|
};
|
|
|
|
// =============================================================================
|
|
// Located Variable Size Types
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Data size for located variables.
|
|
* Corresponds to the size specifier in IEC addresses:
|
|
* - X = Bit (1 bit)
|
|
* - B = Byte (8 bits)
|
|
* - W = Word (16 bits)
|
|
* - D = Double Word (32 bits)
|
|
* - L = Long Word (64 bits)
|
|
*/
|
|
enum class LocatedSize : uint8_t {
|
|
Bit = 0, // %?X - Single bit
|
|
Byte = 1, // %?B - 8 bits
|
|
Word = 2, // %?W - 16 bits
|
|
DWord = 3, // %?D - 32 bits
|
|
LWord = 4 // %?L - 64 bits
|
|
};
|
|
|
|
// =============================================================================
|
|
// Located Variable Descriptor
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Descriptor for a located variable.
|
|
*
|
|
* This structure is generated by the compiler for each located variable
|
|
* in the program. The runtime uses this descriptor array to:
|
|
* 1. Bind variables to I/O image tables at startup
|
|
* 2. Copy values between I/O images and variables during scan cycle
|
|
* 3. Support variable forcing for debugging
|
|
*
|
|
* Memory Layout (16 bytes, aligned):
|
|
* - area: 1 byte
|
|
* - size: 1 byte
|
|
* - byte_index: 2 bytes (major address component)
|
|
* - bit_index: 1 byte (minor address component, 0-7 for bits)
|
|
* - reserved: 3 bytes (padding for alignment)
|
|
* - pointer: 8 bytes (pointer to variable storage)
|
|
*
|
|
* Address Format:
|
|
* %<area><size><byte_index>.<bit_index>
|
|
*
|
|
* Examples:
|
|
* %IX0.5 -> area=Input, size=Bit, byte_index=0, bit_index=5
|
|
* %QW10 -> area=Output, size=Word, byte_index=10, bit_index=0
|
|
* %MD100 -> area=Memory, size=DWord, byte_index=100, bit_index=0
|
|
*/
|
|
struct LocatedVar {
|
|
LocatedArea area; ///< Memory area (I, Q, or M)
|
|
LocatedSize size; ///< Data size (X, B, W, D, or L)
|
|
uint16_t byte_index; ///< Byte offset in the I/O image
|
|
uint8_t bit_index; ///< Bit offset within byte (0-7, only for X size)
|
|
uint8_t _reserved[3]; ///< Padding for alignment
|
|
void* pointer; ///< Pointer to the variable's raw storage
|
|
|
|
/**
|
|
* Check if this descriptor represents a bit-addressed variable.
|
|
*/
|
|
constexpr bool is_bit() const noexcept {
|
|
return size == LocatedSize::Bit;
|
|
}
|
|
|
|
/**
|
|
* Get the size in bytes for this variable.
|
|
* Returns 0 for bit-addressed variables (handled specially).
|
|
*/
|
|
constexpr size_t byte_size() const noexcept {
|
|
switch (size) {
|
|
case LocatedSize::Bit: return 0; // Special handling
|
|
case LocatedSize::Byte: return 1;
|
|
case LocatedSize::Word: return 2;
|
|
case LocatedSize::DWord: return 4;
|
|
case LocatedSize::LWord: return 8;
|
|
default: return 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Verify expected layout (size varies by platform: 16 bytes on 64-bit, 8 on 32-bit, 6 on AVR)
|
|
#if INTPTR_MAX == INT64_MAX
|
|
static_assert(sizeof(LocatedVar) == 16, "LocatedVar should be 16 bytes on 64-bit");
|
|
static_assert(alignof(LocatedVar) == 8, "LocatedVar should be 8-byte aligned on 64-bit");
|
|
#endif
|
|
|
|
// =============================================================================
|
|
// Helper Functions for Address Parsing
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Parse an area character to LocatedArea enum.
|
|
* @param c The area character ('I', 'Q', or 'M')
|
|
* @return The corresponding LocatedArea value
|
|
* @throws std::invalid_argument if character is invalid
|
|
*/
|
|
inline LocatedArea parse_area(char c) {
|
|
switch (c) {
|
|
case 'I': case 'i': return LocatedArea::Input;
|
|
case 'Q': case 'q': return LocatedArea::Output;
|
|
case 'M': case 'm': return LocatedArea::Memory;
|
|
#if STRUCPP_HAS_EXCEPTIONS
|
|
default: throw std::invalid_argument("Invalid area character");
|
|
#else
|
|
default: iec_runtime_fault(IecFault::BadLocation, "Invalid area character");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse a size character to LocatedSize enum.
|
|
* @param c The size character ('X', 'B', 'W', 'D', or 'L')
|
|
* @return The corresponding LocatedSize value
|
|
* @throws std::invalid_argument if character is invalid
|
|
*/
|
|
inline LocatedSize parse_size(char c) {
|
|
switch (c) {
|
|
case 'X': case 'x': return LocatedSize::Bit;
|
|
case 'B': case 'b': return LocatedSize::Byte;
|
|
case 'W': case 'w': return LocatedSize::Word;
|
|
case 'D': case 'd': return LocatedSize::DWord;
|
|
case 'L': case 'l': return LocatedSize::LWord;
|
|
#if STRUCPP_HAS_EXCEPTIONS
|
|
default: throw std::invalid_argument("Invalid size character");
|
|
#else
|
|
default: iec_runtime_fault(IecFault::BadLocation, "Invalid size character");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the area character for a LocatedArea enum.
|
|
* @param area The LocatedArea value
|
|
* @return The corresponding character ('I', 'Q', or 'M')
|
|
*/
|
|
constexpr char area_to_char(LocatedArea area) noexcept {
|
|
switch (area) {
|
|
case LocatedArea::Input: return 'I';
|
|
case LocatedArea::Output: return 'Q';
|
|
case LocatedArea::Memory: return 'M';
|
|
default: return '?';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the size character for a LocatedSize enum.
|
|
* @param size The LocatedSize value
|
|
* @return The corresponding character ('X', 'B', 'W', 'D', or 'L')
|
|
*/
|
|
constexpr char size_to_char(LocatedSize size) noexcept {
|
|
switch (size) {
|
|
case LocatedSize::Bit: return 'X';
|
|
case LocatedSize::Byte: return 'B';
|
|
case LocatedSize::Word: return 'W';
|
|
case LocatedSize::DWord: return 'D';
|
|
case LocatedSize::LWord: return 'L';
|
|
default: return '?';
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Located Variable Table Marker
|
|
// =============================================================================
|
|
|
|
/**
|
|
* End marker for the located variable descriptor array.
|
|
* The compiler generates this as the last entry in the array.
|
|
* Runtime scans the array until it finds this marker (pointer == nullptr).
|
|
*/
|
|
constexpr LocatedVar LOCATED_VAR_END = {
|
|
LocatedArea::Input, // Doesn't matter
|
|
LocatedSize::Bit, // Doesn't matter
|
|
0, // byte_index
|
|
0, // bit_index
|
|
{0, 0, 0}, // reserved
|
|
nullptr // End marker
|
|
};
|
|
|
|
} // namespace strucpp
|