Obsidean_VM/01-Documentation/Siemens/Scripts/Calculo de Offsets de DBs.md

12 KiB

Documentación: Cálculo de Direcciones en Siemens S7 Data Blocks

Índice

  1. Introducción
  2. Conceptos Básicos
  3. Tipos de Datos y Tamaños
  4. Reglas de Alineación
  5. Algoritmo de Cálculo de Direcciones
  6. Casos Especiales
  7. Ejemplos Prácticos
  8. Implementación de un Parser
  9. Referencias

Introducción

Este documento describe los mecanismos de cálculo de direcciones (offsets) para Data Blocks (DBs) en PLCs Siemens S7. El objetivo es proporcionar información detallada para implementar un parser que pueda analizar estructuras de DB y calcular correctamente las direcciones de cada elemento.

El algoritmo descrito se basa en el análisis del código fuente de la biblioteca DotNetSiemensPLCToolBoxLibrary, específicamente la clase S7DataRow y su método FillBlockAddresses.

Conceptos Básicos

Direcciones en S7

En los PLCs Siemens S7, una dirección dentro de un DB se representa con dos componentes:

  • ByteAddress: La dirección del byte (comenzando desde 0)
  • BitAddress: La posición del bit dentro del byte (0-7)

Esta notación se representa comúnmente como "ByteAddress.BitAddress", por ejemplo:

  • "0.0" representa el primer bit del primer byte
  • "10.4" representa el quinto bit del byte 11

Representación interna

La biblioteca utiliza la clase ByteBitAddress para representar una dirección:

public class ByteBitAddress
{
    public int ByteAddress { get; set; }
    public int BitAddress { get; set; }
}

Tipos de Datos y Tamaños

Tipo de Dato Tamaño (bytes) Alineación
BOOL 1 bit Bit
BYTE 1 Byte
CHAR 1 Byte
WORD 2 Word (dirección par)
INT 2 Word (dirección par)
DWORD 4 Word (dirección par)
DINT 4 Word (dirección par)
REAL 4 Word (dirección par)
S5TIME 2 Word (dirección par)
TIME 4 Word (dirección par)
DATE 2 Word (dirección par)
TIME_OF_DAY 4 Word (dirección par)
DATE_AND_TIME 8 Word (dirección par)
STRING Variable Word + 2 bytes de cabecera
STRUCT Variable Según contenido
UDT Variable Según definición
ARRAY Variable Según tipo base

Reglas de Alineación

Las siguientes reglas determinan cómo se posicionan los datos en memoria:

1. Alineación General

  • Inicialización: Al comenzar una estructura, la dirección se inicializa en 0.0
  • Bytes pares: Los tipos de datos que requieren alineación a word se colocan en direcciones de byte par

2. Reglas por Tipo de Dato

Elementos Alineados a Bit

  • BOOL:
    • Variables BOOL individuales se almacenan bit a bit secuencialmente
    • Se pueden colocar hasta 8 BOOLs en un solo byte

Elementos Alineados a Byte

  • BYTE y CHAR:
    • Se alinean a nivel de byte
    • Pueden comenzar en cualquier dirección de byte (par o impar)

Elementos Alineados a Word (dirección par)

  • Tipos de 2+ bytes (WORD, INT, DWORD, DINT, REAL, etc.):
    • Se alinean a direcciones de byte par
    • Si la dirección actual es impar, se avanza al siguiente byte par

Arrays

  • Los arrays siempre se alinean al menos a nivel de byte
  • El tipo base del array determina alineaciones adicionales
  • Arrays de BOOL tienen reglas especiales de empaquetado

Estructuras (STRUCT)

  • Las estructuras siguen sus propias reglas internas de alineación
  • La estructura como un todo se alinea según el elemento con requisito de alineación más estricto

Algoritmo de Cálculo de Direcciones

El algoritmo para calcular direcciones es el siguiente:

  1. Se inicializa la dirección de inicio (normalmente 0.0)
  2. Para cada elemento en la estructura:
    • Se aplican reglas de alineación según el tipo
    • Se asigna la dirección actual al elemento
    • Se calcula la siguiente dirección basada en el tamaño del elemento
    • Se actualiza la dirección actual para el siguiente elemento

Pseudocódigo Fundamental

función CalcularDirecciones(estructura, direcciónInicial)
    dirActual = direcciónInicial
    
    // Ajuste inicial
    si dirActual.bitAddress != 0 entonces
        dirActual.byteAddress++
        dirActual.bitAddress = 0
    fin si
    
    si dirActual.byteAddress % 2 != 0 entonces
        dirActual.byteAddress++  // Alinear a word si es necesario
    fin si
    
    para cada elemento en estructura hacer
        // Aplicar reglas de alineación según tipo
        si elemento.tipo necesita alineación a word Y dirActual.byteAddress % 2 != 0 entonces
            dirActual.byteAddress++
        fin si
        
        // BOOLs - manejar bits
        si elemento.tipo == BOOL Y !elemento.esArray entonces
            elemento.dirección = dirActual
            dirActual = ObtenerSiguienteDirecciónBit(dirActual)
        sino
            // Otros tipos - reset bit address y avanzar bytes
            elemento.dirección = dirActual
            dirActual.bitAddress = 0
            dirActual.byteAddress += elemento.tamaño
        fin si
        
        // Si es estructura con hijos, procesar recursivamente
        si elemento tiene hijos entonces
            CalcularDirecciones(elemento.hijos, elemento.dirección)
        fin si
    fin para
    
    retornar dirActual
fin función

función ObtenerSiguienteDirecciónBit(dirección)
    dirección.bitAddress++
    si dirección.bitAddress > 7 entonces
        dirección.bitAddress = 0
        dirección.byteAddress++
    fin si
    retornar dirección
fin función

Casos Especiales

Arrays

Los arrays requieren atención especial:

  1. Alineación: Los arrays siempre se alinean al menos a nivel de byte, y si el tipo base requiere alineación a word, todo el array se alinea a word.

  2. Arrays de BOOL:

    • Pueden ser empaquetados bit a bit dentro de bytes consecutivos
    • Al cambiar de índice dentro del array, pueden aplicarse reglas especiales
  3. Cálculo de tamaño: El tamaño total es el producto del tamaño del tipo base y el número de elementos.

  4. Arrays multidimensionales: Se procesan como arrays anidados, aplicando las mismas reglas recursivamente.

Estructuras Anidadas

Las estructuras anidadas siguen estas reglas:

  1. La estructura como un todo se alinea según las reglas del tipo más exigente que contiene
  2. Cada campo dentro de la estructura sigue sus propias reglas de alineación
  3. El proceso es recursivo: se calculan las direcciones de los campos internos en relación a la dirección inicial de la estructura

User-Defined Types (UDT)

Los UDTs se tratan de manera similar a las estructuras:

  1. Se alinean como una entidad completa según sus requisitos internos
  2. Las direcciones internas se calculan según las reglas estándar
  3. Al utilizarse dentro de un DB, se expanden completamente (excepto con opciones específicas)

Parámetros IN_OUT

Los parámetros IN_OUT tienen un tratamiento especial:

if (plcDataRow.Parent != null && ((S7DataRow)plcDataRow.Parent).isInOut)
    useAddr = new ByteBitAddress(0, 0);

Esto indica que los parámetros IN_OUT se manejan como punteros, por lo que su contenido interno se dirige a dirección 0.0 en su propio espacio.

Ejemplos Prácticos

Ejemplo 1: Estructura Simple

STRUCT
  Flag1 : BOOL;      // Dirección: 0.0
  Flag2 : BOOL;      // Dirección: 0.1
  Count : INT;       // Requiere alineación a WORD -> Dirección: 2.0 (no 1.0)
  Value : REAL;      // Dirección: 4.0
  Code : BYTE;       // Dirección: 8.0
END_STRUCT

Proceso de cálculo:

  1. Comenzamos en dirección 0.0
  2. Flag1 (BOOL) se asigna a 0.0, avanzamos a 0.1
  3. Flag2 (BOOL) se asigna a 0.1, avanzamos a 0.2
  4. Count (INT) requiere alineación a word. La dirección actual tiene bit ≠ 0, así que avanzamos al siguiente byte (1.0). Como 1 es impar, avanzamos a 2.0.
  5. Count ocupa 2 bytes, avanzamos a 4.0
  6. Value (REAL) ya está alineado a word en 4.0, ocupa 4 bytes, avanzamos a 8.0
  7. Code (BYTE) se asigna a 8.0, avanza a 9.0

Ejemplo 2: Arrays y Estructuras

STRUCT
  Flags : ARRAY[0..15] OF BOOL;   // Dirección: 0.0 a 1.7
  Data : ARRAY[1..5] OF STRUCT    // Comienza en dirección 2.0 (alineada a word)
    ID : INT;                    // Offset relativo 0.0 en cada elemento
    Value : REAL;                // Offset relativo 2.0 en cada elemento
  END_STRUCT;                    // Cada elemento ocupa 6 bytes, total: 30 bytes
END_STRUCT

Implementación de un Parser

Para implementar un parser que calcule direcciones correctamente:

1. Componentes necesarios

  • Analizador lexicográfico/sintáctico: Para parsear la definición de la estructura
  • Modelo de datos: Clases para representar tipos, estructuras, arrays, etc.
  • Motor de cálculo de direcciones: Implementación del algoritmo descrito
  • Manejo de UDTs: Sistema para resolver y expandir UDTs

2. Pasos del algoritmo

  1. Parsear la definición: Convertir el texto de definición a un árbol de estructura
  2. Resolver UDTs: Expandir cualquier UDT referenciado
  3. Calcular tamaños: Determinar el tamaño de cada elemento
  4. Calcular direcciones: Aplicar el algoritmo de cálculo de direcciones
  5. Generar salida: Producir una lista plana de elementos con sus offsets

3. Consideraciones

  • Expansión de arrays: Decidir si expandir arrays o representarlos de forma condensada
  • Opciones de expansión: Implementar opciones como en S7DataBlockExpandOptions
  • Resolución de conflictos: Manejar casos donde la definición del DB no coincide con la estructura

4. Clase de Ejemplo para el Parser

public class S7AddressCalculator
{
    public List<S7DataRowWithAddress> CalculateAddresses(S7DataRow rootStructure)
    {
        var result = new List<S7DataRowWithAddress>();
        var currentAddress = new ByteBitAddress(0, 0);
        
        CalculateAddressesInternal(rootStructure, currentAddress, "", result);
        
        return result;
    }
    
    private void CalculateAddressesInternal(
        S7DataRow structure, 
        ByteBitAddress baseAddress,
        string parentPath,
        List<S7DataRowWithAddress> result)
    {
        // Implementar el algoritmo de cálculo de direcciones aquí
        // Aplicar las reglas de alineación descritas anteriormente
        
        // Para cada elemento:
        // 1. Calcular la dirección según las reglas
        // 2. Agregar el elemento a la lista de resultados
        // 3. Si es estructura, procesar recursivamente
    }
}

public class S7DataRowWithAddress
{
    public string Name { get; set; }
    public string FullPath { get; set; }
    public S7DataRowType DataType { get; set; }
    public ByteBitAddress Address { get; set; }
    public int Size { get; set; }
    public bool IsArray { get; set; }
    public int[] ArrayDimensions { get; set; }
    public string Comment { get; set; }
}

Referencias

  1. Biblioteca DotNetSiemensPLCToolBoxLibrary

    • Clase S7DataRow - Método FillBlockAddresses
    • Enumeración S7DataRowType
    • Clase S7DataBlockExpandOptions
  2. Documentación Siemens S7

    • Tipos de datos y requisitos de alineación
    • Estructura de DBs y UDTs
  3. Norma IEC 61131-3

    • Definición de tipos de datos para PLCs