Esta semana vamos a realizar un ejercicio práctico de cómo comparar dos cadenas de caracteres en Step 7 bien que sean alfanuméricos o dos cadenas cualesquiera de datos.
Si lo que queremos comparar son dos strings como tal, Step 7 proporciona de origen un FC con el que poder comparar cadenas de caracteres en forma de string. Se trata del bloque FC 10 EQ_STRNG que puedes encontrar desde el editor en Librerias-> Standard Library -> IEC Function Blocks.
Bien. Lo lógico es usar esta función o cualquier otra de las que te ofrece Step 7 si se ajusta a tus necesidades.
Pero lo que vamos a realizar hoy es poder comparar cualquier cadena de bytes de dos DB y ver si son iguales o no. Da igual el tipo de datos que tengamos en ese área de memoria.
Comparar dos cadenas de caracteres en Step 7
La idea es simple: poder comparar dos areas de memoria consecutiva de dos DB (o del mismo) y saber si ambas áreas son iguales.
¿Para qué sirve eso?
Pues por ejemplo si tienes una comunicación en la cual envias X bytes a otro PLC, Scada o lo que sea y quieres saber que realmente lo que envias y lo que el interlocutor está leyendo es lo mismo por ejemplo para activar una orden de actuación.
Entonces, la forma de operar es que un PLC escribe en un DB digamos DB1 y el interlocutor copia ese DB a un DB2 que te lo envía de vuelta.
La forma de saber que lo que uno escribe, el otro lo recibe es comparando ambos DB, cosa que puede ser muy simple si intercambias digamos un DW, pero bastante engorrosa si estás intercambiando 100 bytes.
¿Qué necesitaremos para comparar las cadenas de caracteres en Step 7?
Pues vamos a necesitar tener ciertos conocimientos de:
- Punteros y direccionamiento en Step 7, que ya vimos en esta parte del curso y en esta otra
- Hacer un FOR – NEXT en Step 7
- Saltos condicionales estudiados aquí.
- Un poco de XOR que nos servirá para la comparación
- Usar los acumuladores AR1 y AR2
Como ves, nada que no hayamos visto con anterioridad en el blog.
¿Y cómo vamos a hacer la comparación de las cadenas de caracteres en step7?
Básicamente vamos a realizar un FC donde pasaremos como variables de entrada:
- Los números de los DB
- Los números de byte de origen de los datos de ambos DB
- La longitud a comparar en bytes
Por otra parte, y como salida del FC, obtendremos una salida booleana indicativa de si ambas cadenas son iguales o no.
En cuanto a el programa en sí, y como referencia, la idea sería la siguiente:
- Poner una marca booeana a cero indicando inicialmente que ambas cadenas originalmente son iguales
- Hacer un FOR-NEXT con el número de bytes a comparar
- Crear los punteros correspondientes a cada posición
- Comparar cada byte entre ambos DB haciendo un XOR
- Si el resultado se mayor que cero, hacer un SET de la booleana indicando que no son iguales
- Si el resultado es cero, no hacemos nada.
- Continuamos con el FOR
- Finalmente igualamos la booleana a la salida del bloque.
Seguidamente te dejo con el código fuente del FC para que le vayas echando un vistazo:
FUNCTION «Comparador de cadenas» : VOID
TITLE =
VERSION : 0.1
VAR_INPUT
NumDB1 : INT ; //Número del primer DB a comparar
NumDB2 : INT ; //Número del segundo DB a comparar
Byte1 : INT ; //Número del primer byte de la cadena del DB1
Byte2 : INT ; //Número del primer byte de la cadena el DB2
Longitud : INT ; //Longitud de bytes a comparar
END_VAR
VAR_OUTPUT
Resultado : BOOL ; //Resultado final de la comparación.
END_VAR
VAR_TEMP
Comparacion : BOOL ; //Resultado temporal de la comparación
IndiceForNext : INT ;
PunteroOffSet : DWORD ;
DBNum1 : INT ;
DBNum2 : INT ;
ResultadoXOW : INT ;
END_VAR
BEGIN
NETWORK
TITLE =
//Inicializamos la variable Comparacion
SET ;
R #Comparacion;
// Transferimos los valores de los DB a las variables.
// Hay que hacerlo así porque no se puede leer directamente como IN
L #NumDB1;
T #DBNum1;
L #NumDB2;
T #DBNum2;
NETWORK
TITLE =
//Hacemos un for-next con el número de bytes de longitud ya que vamos a comparar
//byte a byte.
L #Longitud;
NEXT: T #IndiceForNext;
// Generamos un puntero OffSet
L #IndiceForNext; // Cargamos el indice
ITD ; // Lo pasamos a doble
L P#1.0; // Cargamos un puntero de un byte
*D ; // Multiplicamos en doble para general el puntero origen
T #PunteroOffSet; // Obtenemos el OffSet
// Generamos el puntero del DB1
L #Byte1; // Cargamos el byte de inicio
ITD ; // Pasamos a doble (como los punteros)
L P#1.0; // Cargamos un puntero de un byte
*D ; // Multiplicamos en doble para generar el puntero origen
LAR1 ; // Lo cargamos en el AR1
L #PunteroOffSet; //Cargamos el offset de este loop
+AR1 ; // Se lo sumamos al AR1
// Generamos el puntero del DB2
L #Byte2; // Cargamos el byte de inicio
ITD ; // Pasamos a doble (como los punteros)
L P#1.0; // Cargamos un puntero de un byte
*D ; // Multiplicamos en doble para generar el puntero origen
LAR2 ; // Lo cargamos en el AR2
L #PunteroOffSet; //Cargamos el offset de este loop
+AR2 ; // Se lo sumamos al AR2
AUF DB [#DBNum1]; // Abrimos el DB1
AUF DI [#DBNum2]; // Abrimos el DB2
L DBB [AR1,P#0.0]; //Cargamos el valor del primer puntero
L DIB [AR2,P#0.0]; //Cargaos el vaor del segundo puntero
XOW ; //Hacemos un XOR de palabra
T #ResultadoXOW;
// Toma de decisión
L #ResultadoXOW; // Si el resultado es cero, no hacemos nada porque de momento son iguales
L 0; // Comparamos con cero, si son iguales, saltamos sin hacer nada
==I ;
SPB EXIT;
SET ; // Si no son iguales, continuamos y ponemos a uno la variable
S #Comparacion;
EXIT: NOP 0;
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT;
UN #Comparacion; // tomamos el valor de la comparación y le cambiamos el signo
= #Resultado; // Igualamos a la salida
END_FUNCTION
Todo esto, será llamado desde el OB1:
CALL «Comparador de cadenas»
NumDB1 :=1
NumDB2 :=2
Byte1 :=0
Byte2 :=0
Longitud :=10
Resultado:=M100.0
Bien, no quiero que te agobies porque tengo preparado como siempre el…
Video de cómo comparar dos cadenas de caracteres en Step 7
Pues eso, que te dejo con la explicación de cómo funciona además de lo arriba expuesto.
¿Qué te ha parecido?
Espero que te haya sido de utilidad lo que te he contado hoy. En cualquier caso, aunque no lo uses para lo que está pensado, piensa que esta comparación de cadenas de caracteres en Step 7 está compuesta como hemos visto de operaciones interesantes.
Esta entrada entre otras están incluidas en el curso “Cómo programar Step 7 y no morir en el intento”
Enseño a programar PLC de Siemens a través de mis cursos.
Más información sobre mi aquí
Puedes seguirme en cualquiera de las siguientes redes sociales.
21 Comentarios
Hola Íñigo:
Siempre me había preguntado como resguardar la integridad de los datos en un sistema distribuido, sí, los mandamos, pero ¿qué es lo que se recibe por parte del interlocutor?, una vez más, mostrando como se hacen las cosas. Saludos.
hola,
gran aporte, pero me surge una duda.
por que uno lo tratas como B y el otro como I?
AUF DB [#DBNum1]; // Abrimos el DB1
AUF DI [#DBNum2]; // Abrimos el DB2
Hola Guillermo,
Para poder abrir dos DB hay que usar la apertura mediante AUF DB y la otra mediante AUF DI. Si haces dos AUF DB, lo que haces es abrir el primero y luego abrir el segundo pero cerrando el primero. Es decir, con AUF DB abres uno exclusivamente y haces las operaciones con el ultimo abierto. Si quieres combinar operaciones entre dos, no te que da más remedio que abrirlo con DI.
Salvo qua abras uno, vuelques la información a una variable temporal, abras otra vez y vuelques a una variable … etc.
Saludos
Hola que tal? Estoy encantado con tus videos ya que dan muchisimas soluciones aplicables.
Mi pregunta es:
Después de darle mil y una vuelta no consigo que me compare el byte 0 y el 6. Todos los demás si hace la comparación XOW correctamente pero cuando cambio el DB1.DBB0 o el DB2.DBB6( los 2 últimos del for-next) no me da el resultado de error como en todos los demás. Sabríais porque??? Gracias y saludos
Hola.
Como imaginarás sin ver el programa, es complicado. Te puedo decir, que revises el programa y te asegures de que lo has hecho igual. Si está igual, debe funcionar igual.
Saludos
Si lo cuentas con los dedos ,resulta que el program esta haciendo la carga y comparacion inicialmente de la siguiente manera:
#IndiceForNext =10; AR1= 10.0; AR2= 16.0)
compara byte 16.0 y 10.0; FUERA DE RANGO deberia ser115.0 y 9.0
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT
#IndiceForNext =9.0
compara byte 15 y 09;
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT
#IndiceForNext =8.0
compara byte 14 y 08;
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT
#IndiceForNext =7.0
compara byte 13 y 07;
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT
#IndiceForNext =6.0
compara byte 12 y 06
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT
#IndiceForNext =5.0
compara byte 11 y 05
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT
#IndiceForNext =4.0
compara byte 10 y 04
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT
#IndiceForNext =3.0
compara byte 09 y 03
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT
#IndiceForNext =2.0
compara byte 08 y 02
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT
#IndiceForNext =1.0
compara byte 07 y 01
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT
#IndiceForNext -1=0 y se sale del bucle y no hace:
compara byte 06 y 00; NO LO HACE
El descontaje lo hace a las salida, no a la entrada. Por lo que estás haciendo la cuenta mal. Si haces a la entrada del loop #IndiceForNext =10; AR1= 10.0; AR2= 16.0), lo que hará será desde 10.0-> 1.0 (10 bytes incluido el 10.0), y desde el 16.0 hasta el 7.0 (incluido el 16.0). Por tanto, o bien le quitas un byte a los AR, o le aumentas una unidad al contaje en función de lo que quieras hacer.
Si correcto el descontaje se hace al final del LOOP, para el ejemplo del video donde se ajusta:
NumDB1 :=1
NumDB2 :=2
Byte1 :=0
Byte2 :=6
Longitud :=10
Observo y escucho en el video, que dices que Para la primera entrada en el LOOP esta comparando el Byte 10.0 del DB1 con el Byte 16.0 del DB2;
¿lo deseado es que deberia comenzar haciendo la comparacion del byte 9.0 del DB1 con el Byte 15.0 del DB2. Por lo tanto asi como esta el programa AWL finalmente no se hara la comparacion de los byte 0.0 del DB1 con el Byte 6.0 del DB2. ya que esta comenzando con la comparacion de 10.0 con 16.0?
¿Asi como esta el programa esta haciendo es la comparacion de los bytes 10.0…1.0 del DB1con los Bytes 16.0…7.0 del DB2, y lo planteado es comparar los Bytes 9.0…0.0 del DB1 con los Bytes 15.0…6.0 del DB2. Que modificacion habria que hacerse?
Hola que tal??
He realizado el mismo programa en un FB y no funciona. Da error «Error de longitud de área al leer y escribir»
Al realizarlo con un FC funciona correctamente.
¿Por qué ocurre esto?
Gracias y saludos
Seguramente porque estarás invocando a la propia instancia del FB desde donde estas ejecutando el programa
Cómo comprenderás , sin ver el programa, es complicado saber dónde puede estar el problema.
Revisa el programa y mira cómo estás llamando a los DB y que no estés llamando al propio DB de instancia.
Saludos
Hola de nuevo Iñigo. He revisado el programa y no veo nada raro.
Este sería el programa: Los DB´s a comparar son el DB3 con el DB4. El DB de instancia del FB es el DB5
Las variables locales del FB:
VAR_INPUT
Num1DB : INT ; //Número del primer DB a comparar
Num2DB : INT ; //Número del segundo DB a comparar
Dir1IniByte : INT ; //Número del primer byte de la cadena del DB1
Dir2IniByte : INT ; //Número del primer byte de la cadena el DB2
NumBytes : INT ; //Longitud de bytes a comparar
END_VAR
VAR_OUTPUT
NoIguales : BOOL ; //Resultado final de la comparación.
END_VAR
VAR_TEMP
PunteroLoop : INT ;
DirByteFinal : DWORD ;
NumDB1Temp : INT ;
NumDB2Temp : INT ;
ResultadoXor : INT ;
END_VAR
PROGRAMA FB1
SET
R #NoIguales
L #Num1DB
T #NumDB1Temp
L #Num2DB
T #NumDB2Temp
L #NumBytes
NEXT: T #PunteroLoop
L #PunteroLoop
ITD
L P#1.0
*D
T #DirByteFinal
L #Dir1IniByte
ITD
L P#1.0
*D
LAR1
L #DirByteFinal //esta en formato puntero
+AR1
L #Dir2IniByte
ITD
L P#1.0
*D
LAR2
L #DirByteFinal
+AR2
AUF DB [#NumDB1Temp]
AUF DI [#NumDB2Temp]
L DBB [AR1,P#0.0]
L DIB [AR2,P#0.0]
XOW
T #ResultadoXor
L #ResultadoXor
L 0
==I //Si los datos son iguales
SPB met1
SET
S #NoIguales
met1: NOP 0
L #PunteroLoop
LOOP NEXT
LLAMADA DEL FB1,COMP DB, EN EL OB1
CALL «COMP DB» , DB5
Num1DB :=3
Num2DB :=4
NumBytes :=19
Dir1IniByte:=0
Dir2IniByte:=0
NoIguales :=M100.0
NOP 0
El problema es que al realizarlo con un FB, no debes usar la apertura DI ya que esa esá «reservada» para la instancia del FB.
Prueba a hacer lo mismo, pero abriendo solo con el AUF DB
AUF DB [#NumDB1Temp]
L DBB [AR1,P#0.0]
AUF DB [#NumDB2Temp]
L DBB [AR2,P#0.0]
Prueba así a ver qué pasa.
Saludos
Hola de nuevo Iñigo.
He probado tal y como me comentaste pero sigue sin funcionar.
Sigue el error de «Error de longitud de área al leer y escribir»
Es curioso esto que pasa…
Saludos
Lo que dice es que cuando lee y escribe se está saliendo de la longitud de alguno de los DB.
He dado por supuesto que las longitudes a chequear están bien no?
Que los DB no son más pequeños que lo que le estás diciendo que chequee.
Hola de nuevo Iñigo.
Todos los DB´s tienen la misma longitud e incluso los mismos tipos de datos.
Sigo investigando por qué sigue sin funcionar.
Gracias y saludos
Hola,
Querría saber como realizar esta misma comparación pero utilizando lenguaje SCL, tengo entendido que es mucho más sencillo para comparar cadenas de caracteres.
Un saludo, Alba.
Puedes hacerlo con un for-next.
Recuerda que tienes el módulo 4 del curso de TIA Portal dedicado a scl.
Saludos
Muy bueno el ejemplo para comprender el manejo de los registros de direcciones AR1 y AR2, de anticipo muchas gracias por sus buenos oficios. Por favor aclareme las siguientes dudas:
Durante el analisis del ejemplo aca planteado, obserbo que cuando se ejecuta el primer FOR-NEXT los AR1 y AR2 tienen valor 80 es decir 10.0, por lo tanto cuando se realice la primera carga :
L DBB [AR1,P#0.0]; //Cargamos el valor del primer puntero
L DIB [AR2,P#0.0]; //Cargaos el vaor del segundo puntero
se estaria adquiriendo son los byte 10.0 del DB1 y 10.0 del DB2 , ¿deberia ser 9.0 del DB1 y 9.0 del DB2?, ya que los Bytes 10.0 de cada DB no pertecen al area de memoria consecutiva (Bytes 0.0 a 9.0) de los dos DB que estamos comparando, ademas, ¿no se hace la lectura y comparacion de los bytes de la posicion 0?, ya que el ciclo no se repite cuando resulta cero el decremento de #IndiceForNext al final del ciclo con la instruccion LOOP :
L #IndiceForNext; // Hacemos el next cargando el valor del indice
LOOP NEXT
En el ejemplo del video los AR1 y AR2 para el primer ciclo del FOR-NEXT tienen los valores 10.0 y 16.0, , ¿estos deberian de tener los valores para AR1 y AR2 de 9.0 y 15.0 respectivamente?. de igual manera ¿esto hace que tampoco se compare el byte de posicion 0.0 del DB1 con el byte de la posicion 6.0 del DB2?.
No, va hasta el 10.0 ya que el valor0 es el 0.0, por lo tanto cargando 80 son 81 posiciones de memoria, desde la 0.0 hasta la 10.0. Haz la cuenta. En ningún caso sería 9.0, ya que obviando que cargar 80 son 81 posiciones, imaginemos que fueran realmente 80. Si fuera así, que no lo es, iría hasta la 9.7, no la 9.0. Como el 0 es la 0.0, entonces va hasta el 10.0
En este ejemplo, la primera vez que se ejecuta:
L DBB [AR1,P#0.0]; //Cargamos el valor del primer puntero
L DIB [AR2,P#0.0]; //Cargaos el vaor del segundo puntero
con que valores de AR1 y AR2 se ejecutan la primera vez?
En este ejemplo, la primera vez que se ejecuta:
L DBB [AR1,P#0.0]; //Cargamos el valor del primer puntero
L DIB [AR2,P#0.0]; //Cargaos el vaor del segundo puntero
con que valores de AR1 y AR2 se ejecutan la primera vez?