339 lines
12 KiB
Markdown
339 lines
12 KiB
Markdown
|
|
# Documentación: Cálculo de Direcciones en Siemens S7 Data Blocks
|
|
|
|
## Índice
|
|
|
|
1. [Introducción](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#introducci%C3%B3n)
|
|
2. [Conceptos Básicos](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#conceptos-b%C3%A1sicos)
|
|
3. [Tipos de Datos y Tamaños](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#tipos-de-datos-y-tama%C3%B1os)
|
|
4. [Reglas de Alineación](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#reglas-de-alineaci%C3%B3n)
|
|
5. [Algoritmo de Cálculo de Direcciones](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#algoritmo-de-c%C3%A1lculo-de-direcciones)
|
|
6. [Casos Especiales](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#casos-especiales)
|
|
- [Arrays](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#arrays)
|
|
- [Estructuras Anidadas](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#estructuras-anidadas)
|
|
- [User-Defined Types (UDT)](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#user-defined-types-udt)
|
|
- [Parámetros IN_OUT](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#par%C3%A1metros-in_out)
|
|
7. [Ejemplos Prácticos](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#ejemplos-pr%C3%A1cticos)
|
|
8. [Implementación de un Parser](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#implementaci%C3%B3n-de-un-parser)
|
|
9. [Referencias](https://claude.ai/chat/06d8e88f-8d18-41c2-8601-4b63debb19f1#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:
|
|
|
|
```csharp
|
|
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:
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
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 |