Mensajes recientes

Páginas: 1 [2] 3 4 ... 10
11
Buenos días Marcos,

En el caso de usar StrFormat con las cadenas "packed" es necesario poner la sentencia "true" en el tercer parámetro, correcto?

Si queres que la cadena resultante sea "packed", utilizar "true". Caso contrario, la salida será "unpacked" independiente del formato de la cadena de entrada.

¿Cuál es el resultado de usar la función de la siguiente manera?

StrFormat(errorMsg[1], DISPLAY_LENGTH, false, !"Falla al inicializar ModBus.")

En este caso, con parámetro "false", en la cadena resultante de salida en errorMsg[1][] vas tener la cadena "Falla al inicializar ModBus.", pero en formato "unpacked".

Es decir, seria lo mismo que hacer:

Código: (Pawn) [Seleccionar]
new errorMsg_Unpacked[] = "Falla al inicializar ModBus."  // Unpacked
Con parámetro "true" la salida seria equivalente a:

Código: (Pawn) [Seleccionar]
new errorMsg_Packed[] = !"Falla al inicializar ModBus."  // Packed
La diferencia esta que el array "errorMsg_Unpacked[]" ocupa 29 celdas *4 = 116 bytes, mientras que errorMsg_Packed[] ocupa 8 celdas * 4 = 32 bytes.



Solo para entender:

Para acceder a cada caracter de errorMsg_Unpacked[] (en el caso que quieras hacerlo), indexas normalmente, ejemplo:

Código: (Pawn) [Seleccionar]
nLcdPrintf(0, 1, LCD_CLRLINE, ""%c %c %c %c %c", errorMsg_Unpacked[0], errorMsg_Unpacked[1],errorMsg_Unpacked[2],errorMsg_Unpacked[3],errorMsg_Unpacked[4])
Imprime "F a l l a"

En el caso de un "packed" string, se agrupan 4 carácter por celda, estando el primer carácter de 8-bit, en la posición más alta. Por lo tanto hay que "desempaquetarlo" para obtener caracter por caracter.

Para ello usamos GetCellChar(cell, char_num), donde "cell" es el entero donde esta el carácter y char_num el número de caracter a obtener (del 0 al 3).

Ejemplo:

Código: (Pawn) [Seleccionar]
nLcdPrintf(0, 1, LCD_CLRLINE, ""%c %c %c %c %c", GetCellChar(errorMsg_Packed[0], 0), GetCellChar(errorMsg_Packed[0], 1),GetCellChar(errorMsg_Packed[0], 2),GetCellChar(errorMsg_Packed[0], 3),GetCellChar(errorMsg_Packed[1], 0))
Imprime "F a l l a"

Saludos!

12
Kinco / Trend curve
« Último mensaje por camarasajm junio 09, 2018, 20:30:04 pm »
Buenos Tardas, mi consulta es sobre la manera correcta de realizar un gráfico de curva "TREND CURVE".. no puedo hacer que se desplace la barra para datos anteriores y hacer el zoom sobre ellos.

estoy tratando de realizar el modelo de ejemplo.



Saludos, Juan
13
Hola Boris,
Excelente, ahora tiene sentido lo extraño que era el error.
Voy a implementar los consejos que escribiste para ahorrar memoria RAM. Me quedo una sola duda respecto a el empaquetamiento de strings
En el caso de usar StrFormat con las cadenas "packed" es necesario poner la sentencia "true" en el tercer parámetro, correcto?
¿Cuál es el resultado de usar la función de la siguiente manera?
StrFormat(errorMsg[1], DISPLAY_LENGTH, false, !"Falla al inicializar ModBus.")

Muchas gracias.
14
Buenas tardes Marcos,

Te paso a detallar las causas del problema y como solucionarlo.

CAUSAS

Luego de analizar el proyecto completo que enviaste por email, encontré que el mismo "colgaba" el PLC en el mismo instante que se transfería el código desde StxLadder a la memoria del PLC, sin ejecutar instrucción alguna de tu código.

Por lo tanto, el problema no es de error de código o de alguna función como StrFormat() que producía misteriosamente cierta interacción de acuerdo a si se comentaba o no.

El problema es de memoria RAM, en este caso, el proyecto utilizaba más memoria RAM que la que tiene asignada el dispositivo para código de usuario, por lo tanto, cuando se cargaba al PLC, interfería con el sistema operativo interno, escribiendo su área de memoria, lo cual generaba fallas aleatorias, de acuerdo a si el proyecto utilizaba más o menos RAM (por ello cuando comentabas, a veces funcionaba y otras no).

El "culpable" fue StxLadder que comprobaba erróneamente la cantidad de RAM utilizada por el proyecto y no te daba advertencia alguna de que estabas superando los limites del modelo de dispositivo que tenés.

SOLUCIÓN

Esto se solucionó en la última versión de StxLadder, la versión 1.9.0 que liberé hoy.

Te recomiendo instalarla.

Esta versión te muestra al compilar en detalle cuanta memoria ROM y RAM utilizas en el proyecto, y cuanto te queda disponible. También, si te excedes, te da un error de compilación para que bajes el consumo.

Por otro lado, también se mejoró el firmware del PLC, que también comprueba previamente si el programa que va a ejecutar no excede sus limites de RAM / ROM. Es decir, un doble-check, entre StxLadder y el PLC. Aumentando la seguridad, y evitando "colgarse" si el programa es inválido, solo prenderia el led RUN ERROR, pero no perderías conectividad (como te pasaba).

La versión de firmware que debés bajar es la 211 o superior para tu modelo de PLC:

http://slicetex.com/hw/stx8091/soft.php#Firmware

APLICACIÓN A TU PROYECTO

Una vez que instales StxLadder versión 1.9.0 y actualices el firmware del PLC a la versión 211, abrí tu proyecto.

Si es el mismo que me pasaste a mí, al compilar deberías ver el siguiente mensaje de error:

Cita de: StxLadder 1.9.0
Error de Memoria: Insuficiente memoria RAM: El proyecto requiere 23268 bytes y el dispositivo tiene disponible 16384 bytes. Disminuya variables globales, variables locales inicializadas, comparta/reutilice variables globales, utilice packed strings, agrupe datos, etc

El mensaje dice que utilizas 23268 bytes de memoria RAM, pero el dispositivo tiene 16384 (16KB).

En el PLC tenés dos memorias, RAM (donde van las variables lectura/escritura) y la ROM (donde van las instrucciones de código para ejecutar la lógica o datos de solo lectura).

La RAM es más pequeña que la ROM, por lo tanto hay que tener más cuidado en definir grandes cantidades de variables, que en escribir código.

El compilador Pawn, considera a los string literales o constantes string como variables que van en RAM, por lo tanto cuando escribís "Falla al inicializar ModBus." en StrFormat(), se coloca en memoria RAM dicha cadena, ocupándote lugar:

Código: (Pawn) [Seleccionar]
StrFormat(errorMsg[1], DISPLAY_LENGTH, false, "Falla al inicializar ModBus.")
La cadena "Falla al inicializar ModBus." tiene 26 caracteres más un terminador "0", en total ocupa 27 posiciones. En Pawn, las cadenas son arrays de enteros, por lo tanto cada posición tiene 4 bytes.

La memoria RAM consumida por esa cadena es de: 27*4=108 bytes.

Es bastante, y más si utilizas muchísimas cadenas en tu código, a la larga ocupas toda la memoria RAM.

Un truco para que una cadena literal (constante) no utilice tanta memoria RAM es haciéndola "packed", en ese caso, el compilador agrupa en cada posición 4 caracteres. Esto se especifica con el operador " ! " antes de una cadena, por ejemplo:

Código: (Pawn) [Seleccionar]
StrFormat(errorMsg[1], DISPLAY_LENGTH, true, !"Falla al inicializar ModBus.")
Ahora la cadena de 27 caracteres ocupa 27 bytes, una reducción importante.

Notar como el tercer parámetro de StrFormat() es "true", indicando que la cadena resultante en errorMsg[] va a ser packed también.

Como la cadena resultante es "packed", podes definir a errorMsg[]  con un tamaño en "celdas" 4 veces menor.

Otro ejemplo:

Código: (Pawn) [Seleccionar]
new String1[] = !"Falla al inicializar ModBus."
Esto solo funciona con cadenas, pero para variables enteras o flotantes, arrays, etc, cada vez que definís uno de forma global o del tipo estático (inicializado dentro de una función), te ocupa 4 bytes de memoria RAM. Por lo tanto, también, usar a discreción e intentar ahorrar lo más que pueda.

LUEGO VOY A PUBLICAR UN POST CON TRUCOS Y CONSEJOS PARA DISMINUIR MEMORIA RAM

Volviendo a tu proyecto, una forma de considerar a todas las cadenas como "packed" por defecto, es utilizando la opción "Packed literal strings" antes de compilar como muestra la siguiente imagen:



Se encuentra en menú "Proyecto > Propiedades > Compilador (pestaña)".

Ahora si volvés a compilar te va a mostrar "Compilación correcta":



Notar como en los mensajes ahora el total de RAM requerida es "10348" bytes, respecto a los "23268" bytes de antes.

Una reducción importante logra la opción Packed literal strings, que considera a todas tus cadenas literales como packed, tal como si le hubieras aplicado el símbolo " ! " a cada una.

Cuando esta opción esta activada, si antepones el símbolo " ! " a la cadena, genera el comportamiento inverso, se transforma en unpacked y ocupa 1 carácter por celda.



Si bien utilizar la opción Packed literal strings ya te permite liberar RAM necesaria para que el proyecto se pueda cargar, te recomiendo no utilizar tantas cadenas, así tenes RAM disponible para futuro uso cuando el proyecto crezca con nuevas funciones o código que le agregues.

En todo caso, hacé las cadenas menos descriptivas, utilizá códigos de error para hacerlas más cortas en extensión, etc.

Por otro lado, al usar el argumento "true" en StrFormat(), te habilita a que puedas reducir el tamaño del array:

Código: (Pawn) [Seleccionar]
new errorMsg[8][DISPLAY_LENGTH+5]
Que si lo analizás, tiene 8*(DISPLAY_LENGTH+5) celdas. Si DISPLAY_LENGTH=34, la cantidad de celdas utilizadas es:

312 celdas = 1248 bytes (bastante)

Si el string que vas a almacenar es "packed" con StrFormat() podrías reducir DISPLAY_LENGTH a 10, para almacenar 39+1 caracteres por linea:

Código: (Pawn) [Seleccionar]
#define DISPLAY_LENGTH 10
new errorMsg[8][DISPLAY_LENGTH]

Ahora, el total seria: 80*4 = 320 (bytes)

Podes ir probando reduciendo código o diferentes alternativas con variables, compilar y ver que opción te genera menos consumo de RAM, hasta que logres un equilibrio.

En el detalle de compilación, donde dice "Data block 6252 bytes", esa es la RAM usada por variables globales, strings, etc.

Luego haré un post resumiendo y explicando mejor como ahorrar RAM, pero esto es lo fundamental.

Saludos!
15
STX8081 / Ejemplo display LCD para I2C y memoria EEPROM
« Último mensaje por Soporte junio 07, 2018, 12:13:50 pm »
Para aquellos que utilizan el display LCD a través de I2C (puerto HP2/HP3), ver siguiente post del foro:

foro.slicetex.com/index.php?topic=269.msg1432#msg1432

Adjuntamos en esta oportunidad el mismo ejemplo, pero que también escribe y lee datos de memoria EEPROM.

Cabe mencionar que las funciones para la memoria EEPROM están descriptas en el Manual de Programación Pawn del PLC.

Ver proyecto adjunto.

Saludos!
16
A continuación adjuntamos el ejemplo "MbTcpServerCountOut.zip" para el PLC que a diferencia del ejemplo "MbTcpServerCounter.zip" conmuta una salida a transistor cada vez que el registro ModBus 40001 es puesto a 0.

Requiere un PLC con salida PWM o transistor rápida.

Esto permite medir tiempos mediante un osciloscopio o analizador lógico en la salida.

Capturas tomadas en salida digital (la duración de cada pulso  alto o bajo es el tiempo entre escritura lectura):





Log tomado desde aplicación Visual C# (sin relación a capturas previas):

Código: (log.txt) [Seleccionar]
Response: 915 ms
Response: 934 ms
Response: 20 ms
Response: 30 ms
Response: 76 ms
Response: 97 ms
Response: 139 ms
Response: 161 ms
Response: 21 ms
Response: 31 ms
Response: 84 ms
Response: 95 ms
Response: 140 ms
Response: 161 ms
Response: 205 ms
Response: 218 ms
Response: 271 ms
Response: 282 ms
Response: 327 ms
Response: 347 ms
Response: 389 ms
Response: 411 ms
Response: 458 ms
Response: 469 ms
Response: 522 ms
Response: 532 ms
Response: 10 ms
Response: 31 ms
Response: 9 ms
Response: 24 ms
Response: 76 ms
Response: 86 ms
Response: 133 ms
Response: 154 ms
Response: 10 ms
Response: 30 ms
Response: 20 ms
Response: 31 ms
Response: 79 ms
Response: 89 ms
Response: 141 ms
Response: 152 ms
Response: 205 ms
Response: 217 ms
Response: 21 ms
Response: 32 ms
Response: 13 ms
Response: 33 ms
Response: 76 ms
Response: 97 ms
Response: 142 ms

17
Kinco / Re:Tiempo de actualización de elementos
« Último mensaje por Soporte junio 04, 2018, 13:21:01 pm »
Buenos días Francisco,

Estuve probando el HMI con tu diseño, y presentaba el problema que describís.

Como no encontré error alguno en el HMI, luego de algunas horas de pruebas, decidí recrear el proyecto desde cero y funcionó perfecto.

Ahora, yo utilicé el el HMIware 2.4 para el diseño, cuando lo abrí con la nueva versión que utilizás en tu proyecto, HMIware 2.5 presentó el mismo problema. Cosa de no creer.

Aparentemente cuando abrís un proyecto creado en versión 2.4 desde el software versión 2.5, pasa esto.

No tuve tiempo para hacer un proyecto desde cero utilizando HMIware 2.5 para ver si así funciona bien.



Pasos a seguir:

Para no dilatar más, te recomiendo desinstalar la versión 2.5 e instalar la versión 2.4 del soft para el HMI en tu PC. En el CD de instalación del paquete de venta esta dicha versión, sino avísame y te comparto algún link de descarga.

Luego abrí el proyecto "HMI_SlicetexTankTest_V1.zip" que te adjunto y probalo, es un "clon" de tu diseño, pero más rustico. Lo único que le falta es actualizar los gráficos y animaciones de los tanques.

Si te funciona bien, seguí utilizando la versión 2.4 del software, ya que no hay forma de abrir un proyecto creado con 2.5 en la versión 2.4 (pero sí al revés, que es lo que no funciona, quizás algo hace mal el soft al actualizar diferentes versiones en proyectos creados con versiones previas).

Seguramente tu diseño, lo basaste en algún diseño de ejemplo de la página, y como fueron creados con versión 2.4, se trasladó el problema. No sé si creando desde cero en 2.5, esto se soluciona, cosa de locos.



Por otro lado, revisando tu código en el PLC, noto que en la Network N026, N027 y N028 escribís en la EEPROM en cada Scan Cycle. Esto no es recomendado, porque agotás la vida útil de la EEPROM con miles de escrituras por segundos, y además crea un retardo de decenas de milisegundos en el Scan Cycle, ya que la EEPROM tiene un tiempo de escritura.

Para evitar esto, te adjunto el proyecto "PLC_TanquesSlicetex_Version_1.zip" para el PLC, que desactiva estos componentes (borralos luego) y en su lugar llama al diagrama "WriteEeprom" cada diez segundos, para guardar los últimos valores, de tal forma de evitar escribir permanentemente en EEPROM.



Me habías comentado de un defasaje de tiempo, el tema de la EEPROM puede ser una causa, pero también, como los segundos/contadores los incrementas desde Principal.sld, y a su vez antes/luego de cada incremento tenes bastante código, esto genera que el procesador pierda tiempo ejecutando componentes antes de incrementar contadores. Este pequeño delay (producido pro ejecutar muchas cosas antes en el Scan Cycle) a la larga puede introducirte el error de tiempo que observás.

Lo ideal seria que trates de agrupar código en fuciones Ladder, y solo llamarlas si es necesario, o con Temporizadores Ladder, llamar a fragmentos de código cada 50 o 100 mS (por ejemplo aquellos que no requieran una repuesta en tiempo real ya sea en el PLC o en HMI, como carga de registros, etc), de tal forma de no ocupar el procesador en la lógica principal para ejecutar todos los componentes al mismo tiempo.

Saludos!
18
Buenas tardes.

Te adjunto un ejemplo StringArrayCheck.zip que replica las cadenas que me pasaste con StrFormat().

Cargalo al PLC, y accedé vía el navegador web a la IP del dispositivo, debería mostrarte las cadenas, o decime si te da error.

Deberías ver algo así:





Por otro lado, como no veo un error que salte a la vista, ¿tenés alguna versión del código del proyecto que te de el error descripto, para que yo pueda reproducirlo?.

Es decir, podrías eliminar/comentar todas las partes que no tengan que ver con el error, dejando solo el código problematico (que al cargar te produce el RunError). Luego desde menú "Archivo > Crear backup del proyecto (zip)", generás el archivo ZIP para adjuntar con el proyecto funcional en cuestión así lo puedo abrir y cargar al PLC.

Las partes que requieran lectura de alguna entrada, comunicacíon de módulo externo, etc, eliminalas, para no tener que cablear hardware externo a la hora de probar el programa, ya que aparenta ser un tema de software puntual.

Saludos!





19
Librería ModBus TCP Master para Visual C#

A continuación subimos un ejemplo simple en Visual C# para comunicarse al PLC utilizando protocolo ModBus TCP.

En este caso, el PLC actúa como Esclavo y desde Visual C# (una computadora) nos comunicamos como Maestro para escribir/leer registros o bits.

Aplicación de ejemplo

Cargue en el PLC el proyecto MbTcpServerCounter.zip para StxLadder que adjuntamos.

Luego descargue el proyecto ModBusTcpResponseTime.zip para Visual C# y ejecute la aplicación "ModBusTcpResponseTime.exe" de la carpeta "bin\Release".

Especifique dirección del PLC (ejemplo 192.168.1.81) y haga click en botón "Conectar". El puerto por defecto utilizado es 502. A continuación, si no hay errores de comunicación verá la siguiente pantalla (similar):



El funcionamiento es muy simple.

En el PLC, como esclavo ModBus, constantemente se incrementa una variable contador cada 1 mS y se la carga al registro ModBus número 40001.

En el caso que el registro 40001 se haga 0 (por ejemplo un Maestro escribió valor 0), la variable contador se hace cero nuevamente.
Por lo tanto, el valor en el registro 40001 representa el tiempo que pasó desde que un Maestro escribió el valor 0 y volvió a leer el valor del registro.

La aplicación en Visual C#, simplemente envía cada 10 mS una transacción ModBus de escritura de registro (escribe valor 0 en 40001) y luego intenta leer el mismo registro a través de ModBus.

Luego imprime dicho valor en pantalla, que representa el tiempo entre escritura y lectura ModBus.

También calcula el valor promedio de los últimos X valores (definido en caja Cantidad de muestras) y el valor máximo alcanzado.

Adicionalmente en el directorio del ejecutable, se guarda un archivo de texto llamado "log.txt" que registra todas las lecturas de tiempo realizadas de la última conexión para realizar un mejor analisis.

Código en C#

Abra el proyecto en Visual C# (recomendado versión 2015 o superior).

La librería ModBus TCP Master, se encuentra en el archivo stx8xxx.dll incluido en el directorio "bin\release" del proyecto.
Es esta librería que incluimos como referencia en el proyecto (References) y debe ser empleada en su aplicación Visual C# para tener acceso a las funciones ModBus TCP.

Luego agregamos el espacio de nombre en el código fuente con:

Código: (C#) [Seleccionar]
using stx8xxx;

Creamos el objeto que accede a las funciones ModBus como maestro:

Código: (C#) [Seleccionar]
private ModBusTcpMaster MbMaster;
Para conectarnos, simplemente especificamos dirección IP y puerto (que se obtienen de los controles de la ventana):

Código: (C#) [Seleccionar]
MbMaster = new ModBusTcpMaster(textDeviceIP.Text, (ushort)numDevicePort.Value);
Luego especificamos dos eventos, que serán llamados cuando tengamos repuesta de datos y excepciones (un error):

Código: (C#) [Seleccionar]
MbMaster.OnResponseData += new ModBusTcpMaster.ResponseData(MbMaster_OnResponseData);
MbMaster.OnException += new ModBusTcpMaster.ExceptionData(MbMaster_OnException);

Posteriormente, en el código creamos una funcion para leer el registro ModBus 40001, asociado al contador del PLC:

Código: (C#) [Seleccionar]
        private bool ReadPlcCounterRegister()
        {
            bool ErrorsFound = false;

            // Check connection.
            if (!IsConnectedMsg())
            {
                ErrorsFound = true;
                return ErrorsFound;
            }

            // Unique transaction ID (only for identify this transaction on response event).
            ushort ID = 2;

            // Send "Read Holding Register" request.
            MbMaster.ReadHoldingRegister(ID, 40001, 1);

            // Return to caller.
            return ErrorsFound;
        }

Notar como usamos la función "MbMaster.ReadHoldingRegister()" para leer un Holding Register del PLC en dirección "40001". El ID, es un valor arbitrario que utilizamos para luego poder identificar en la repuesta que los datos recibidos corresponden a esta transacción y no a otra que podamos haber realizado.

También creamos una función para escribir el registro del PLC numero 40001:

Código: (C#) [Seleccionar]
        private bool WritePlcCounterRegister(ushort Value)
        {
            bool ErrorsFound = false;

            // Check connection.
            if (!IsConnectedMsg())
            {
                ErrorsFound = true;
                return ErrorsFound;
            }

            // Unique transaction ID (only for identify this transaction on response event).
            ushort ID = 1;

            // Get byte array from "Value".
            byte[] Data = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((ushort)Value));
           
            // Send "Write Single Holding Register" request.
            MbMaster.WriteSingleRegister(ID, 40001, Data);           

            // Return to caller.
            return ErrorsFound;
        }

Nuevamente, notar como usamos la función "MbMaster.WriteSingleRegister()" para escribir un Holding Register del PLC en dirección "40001". El ID, es un valor arbitrario que utilizamos para luego poder identificar en la repuesta que los datos recibidos corresponden a esta transacción y no a otra que podamos haber realizado.

También, el objeto "MbMaster" de la librería contiene más funciones para leer bits, o escribir/leer varios registros al mismo tiempo. Ver documentación en código ofrecida por Visual C#

Luego, desde un timer, llamamos a estas funciones cada 10 mS, donde comprobamos variable ReadyForTransaction, que indica que estamos listos para comenzar una nueva transacción y luego comprobamos variable StartWriteTransaction, si es "1", enviamos una transacción de escritura, sino, enviamos una transacción de lectura:

Código: (C#) [Seleccionar]
        private void timerPolling_Tick(object sender, EventArgs e)
        {
            //
            // Check if previous response was received or is ready for new transaction.
            //

            if (ReadyForTransaction)
            {
                //
                // Check transaction to do: write or read counter register.
                //

                if (StartWriteTransaction)
                {
                    // Write counter register with 0 value.
                    WritePlcCounterRegister(0);
                }
                else
                {
                    // Read PLC counter register.
                    ReadPlcCounterRegister();
                }
            }
        }

La respuesta a estas transacciones, las obtenemos del evento:

Código: (C#) [Seleccionar]
        private void MbMaster_OnResponseData(ushort ID, byte function, byte[] values)
        {
            // ------------------------------------------------------------------------
            // Seperate calling threads
            // ------------------------------------------------------------------------

            if (this.InvokeRequired)
            {
                this.BeginInvoke(new ModBusTcpMaster.ResponseData(MbMaster_OnResponseData), new object[] { ID, function, values });
                return;
            }

            // ------------------------------------------------------------------------
            // Identify requested data
            // ------------------------------------------------------------------------

            StringBuilder ResponseStr = new StringBuilder("Respuesta recibida: ");

            //
            // Check ModBus function code received.
            //

            switch (function)
            {
                case 3:
                    ResponseStr.Append("Read holding register ");

                    //
                    // Check transaction ID (counter read).
                    //

                    if (ID == 2)
                    {
                        //
                        // Get register value.
                        //
                        // UInt16 RegisterValue = (UInt16)(((UInt16)(values[0] << 8)) | ((UInt16)(values[1])));
                        //

                        // Check architecture endianess.
                        if (BitConverter.IsLittleEndian)
                        {
                            // Reverse array values.
                            Array.Reverse(values);
                        }

                        // Get register value from received bytes.
                        UInt16 RegisterValue = BitConverter.ToUInt16(values, 0);

                        // Update text box values on windows form.
                        UpdateTextBox(RegisterValue);

                        // Read register is done, now, write register on next timer tick.
                        StartWriteTransaction = true;
                    }

                    break;
                case 6:
                    ResponseStr.Append("Write single register ");

                    //
                    // Check transaction ID (counter write).
                    //

                    if (ID == 1)
                    {
                        // Write register is done, now, read register on next timer tick.
                        StartWriteTransaction = false;
                    }

                    break;
                default:
                    ResponseStr.Append("Unknown ");
                    break;
            }

            // Show message in status bar.
            ResponseStr.AppendFormat("({0}) - data bytes/words ({1}/{2}).", function, values.Length, values.Length / 2);

            PrintStatusBarMessage(ResponseStr.ToString());

            // Response received, ready for next transaction.
            ReadyForTransaction = true;
        }

Cuando este evento es llamado, nos pasa 3 argumentos:

  • ID: Identificación de transacción que colocamos al realizar una operación.
  • function: Código de Función ModBus al que pertenece la repuesta.
  • values[]: Bytes recibidos de la repuesta ModBus.

Notar como el siguiente fragmento es llamado, cuando hay una repuesta de escritura:

Código: (C#) [Seleccionar]
                case 6:
                    ResponseStr.Append("Write single register ");

                    //
                    // Check transaction ID (counter write).
                    //

                    if (ID == 1)
                    {
                        // Write register is done, now, read register on next timer tick.
                        StartWriteTransaction = false;
                    }

Procesamos, si es "function=6" (repuesta de una transacción Write single register) y "ID=1" (lo establecimos en WritePlcCounterRegister(), hacemos la variable StartWriteTransaction=false para que en el próximo evento timer en timerPolling_Tick() realicemos una operación de lectura.

En la repuesta de lectura, luego de llamar a función "ReadPlcCounterRegister()" se entra al siguiente fragmento de código:

Código: (C#) [Seleccionar]
                case 3:
                    ResponseStr.Append("Read holding register ");

                    //
                    // Check transaction ID (counter read).
                    //

                    if (ID == 2)
                    {
                        //
                        // Get register value.
                        //
                        // UInt16 RegisterValue = (UInt16)(((UInt16)(values[0] << 8)) | ((UInt16)(values[1])));
                        //

                        // Check architecture endianess.
                        if (BitConverter.IsLittleEndian)
                        {
                            // Reverse array values.
                            Array.Reverse(values);
                        }

                        // Get register value from received bytes.
                        UInt16 RegisterValue = BitConverter.ToUInt16(values, 0);

                        // Update text box values on windows form.
                        UpdateTextBox(RegisterValue);

                        // Read register is done, now, write register on next timer tick.
                        StartWriteTransaction = true;
                    }

Aquí obtenemos el registro leído en RegisterValue (convertimos de valor de red a valor de computadora de 16-bits) y luego actualizamos las cajas de texto de la ventana con la función UpdateTextBox().

Al final del evento se hace la variable ReadyForTransaction igual a true indicando que la aplicación esta lista para una nueva transacción ya que recibimos el dato que esperábamos (esto es simplemente como lógica en nuestro ejemplo, para no enviar una transacción nueva sí no recibimos la enviada previamente).

Código: (C#) [Seleccionar]
            // Response received, ready for next transaction.
            ReadyForTransaction = true;
        }



Aún la librería no esta publicada en la página web oficialmente por estar en desarrollo, por ello cualquier duda, pueden utilizar nuestro foro.

Abrir los ejemplos para el PLC con StxLadder 1.8.9 o superior.

Saludos!
20
StxLadder - Slicetex Ladder Designer Studio / Led RUN ERROR se activa al llamar StrFormat()
« Último mensaje por EBD mayo 30, 2018, 11:56:58 am »
Hola,
Recientemente adquirí uno de los módulos PD3060 para medir temperaturas y lo estoy incluyendo en un proyecto que ya venía desarrollando desde antes. El problema surgió modularizando cada una de las funciones de la comunicación ModBus siguiendo el ejemplo sugerido en la página de Slicetex.
En un primer intento incluí unicamente las funciones necesarias para tomar la temperatura y convertirlas a punto flotante y todo anduvo bien. El problema ocurrió al intentar, finalmente, hacer un manejo de los errores que puede devolver el módulo. Al tratar de escribir un mensaje de error en el display según el código devuelto en MbStat o FuncStat con nLcdPrintf() o con StrFormat() (en un arreglo para mensajes), al cargar el programa en el PLC se enciende la led de RunError y titila a una frecuencia menor a 1 segundo. En este punto debo resetear el PLC a los valores de fábrica para poder cargar un nuevo programa ya que el mismo no acepta ningun tipo de comunicación. Tener que resetear el PLC a fábrica además hace que se vuelva tedioso el debugging del programa.
Adjunto dos de los archivos del proyecto en donde se programaron las funciones que manejan el módulo.

Paso a explicar para que se entienda un poco el código:
-La función initPD3060() se llama una vez al principio de PlcMain(). Luego se entra en un loop secundario donde se llama cada 0.5s a requestTempData()
-En la función @OnMbRtuClient() se guarda el paquete recibido de ModBus en el arreglo de floats PD3060_RxData[] que luego será lo que uso para guardar los valores de temperaturas en las variables correspondientes.
-Dentro de PlcMain() hay un loop primario (donde ocurre la operación normal del PLC) distinto del anterior donde, si la variable comsError es alguno de los errores establecidos, llama a la función handleComsError(). Aqui es donde ocurre lo extraño. Esa función nunca es llamada porque nunca dí la orden de salir del loop secundario mencionado antes.

Espero que se haya entendido algo y puedas orientarme a porque surge este problema.

Te cuento a continuación algunas conclusiones a las que llegue después de un largo rato de debuggeo:
- Si no escribo la función handleComsError() en el loop principal, el PLC no tira el mensaje de RunError. Esto es incluso aunque esta función nunca la llego a llamar en la operación. Es decir, no es un problema de ejecución de handleComsError() sino cuando se compila y se carga la función.
- En handleComsError(), probé reemplazando los mensajes de error de StrFormat() por nLcdPrintf() y haciendo más cortos los mismos. Nada funciona. Lo único que evitó que salte el error es agregando dentro del primer if() un máximo de 2 nLcdPrintf() y comentando el resto de los if(). Si intento escribir 3 lineas en el display el error vuelve a surgir.
- Considero que tiene algo que ver con la escritura de los strings ya que, si dentro de los 4 if() de handleComsError() ejecuto cuentas sin sentido (asigno numeros a variables), el error no ocurre. Solo ocurre cuando intento escribir un mensaje de error.

Disculpas por el post tan largo pero no encuentro la forma de solucionar este problema, por un lado ya que no me queda bien claro las razones por la cual se enciende el led de RunError.
Gracias de antemano,
Saludos.
Páginas: 1 [2] 3 4 ... 10