Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Varios (https://www.clubdelphi.com/foros/forumdisplay.php?f=11)
-   -   Disminuir el tiempo en recibir byte con ApsComPort... (https://www.clubdelphi.com/foros/showthread.php?t=34687)

Geta 17-08-2006 20:22:35

Disminuir el tiempo en recibir byte con ApsComPort...
 
Estoy leyendo los datos del com con este componente, pero mi problema es que necesito recibir una cantidad de bytes de cerca de 8K y necesito que el tiempo de lectura entre byte y byte sea de 10 milisegundos o menos porque si no los datos del buffer me salen erroneos.

¿ Hay alguna forma de modificar el tiempo de lectura de bytes desde el com que no sea modificando valores de sistema operativo, es decir desde delphi ?

Gracias.

egostar 17-08-2006 20:39:46

El componente ApdComPort tiene otros componentes que puedes usar para recibir los datos que estan llegando al puerto serial sin necesidad de calcular el tiempo entre byte y byte, te recomiendo que uses el otro componente que esta en ese tab de nombre ApdDataPacket, ese componente tiene la característica de leer todos los caracteres que llegan al puerto serial y te lo deja en una sola variable que puedes usar para lo que necesitas.

Aqui un ejemplo del uso de este componente:

Código Delphi [-]
 
procedure TLector.ApdDataPacket1StringPacket(Sender: TObject;
  Data: String);
begin
  Linea2 := Data;
  If (Linea2[5] = 'A') or
     (Linea2[5] = 'H') then begin
     Limpiavariables;
     CargaVariables(Linea2);
     ProcesaInformacion('A');
  end;
end;

Lo único que necesitas saber es que caracter está al inicio o al final de la cadena recibida, normalmente en comunicaciones seriales se usan protocolos los cuales te indican que caracteres identifican el inicio y el final de una cadena, por ejemplo Start of Text (#2), End of Text (#3) o no tiene caracter de inicio pero si tiene caracter de fin como un CR.

Lo que tú estas haciendo es leer el puerto todo el tiempo y eso en mi experiencia no es lo mas conveniente, es mejor leer cuando una cadena es recibida completamente y no estar perdiendo caractéres por cuestiones de sincronía.

Saludos.

Geta 17-08-2006 20:45:24

Pero este componente me pide string de inicio y fin y no los tiene son bytes de principio y fin pueden ser distintos cada vez ya que es un dispositivo que lee memorias.

seoane 17-08-2006 20:57:07

Ignoro como funciona el componente que mencionas, pero me surge una pregunta, ¿por que no utilizas la API para leer el puerto serie?. Windows dispone de las funciones necesarias para leer el puerto serie de manera sencilla, los componentes lo único que hacen es llamar a esas mismas funciones, pero además incluyen un montón de código a mayores, que a veces en vez de ayudar es una molestia.

¿Que es lo que necesitas exactamente?, es decir, si tienes que leer byte a byte e ir procesando cada uno por separado un simple bucle sera mucho mas rápido que cualquier componente que funcione con eventos. Si no quieres tener tu aplicación congelada mientras ejecutas el bucle, utiliza threads, de esta manera la lectura se realizara en paralelo con hilo principal de tu programa. Un simple loop dentro de un thread es el método mas rápido que existe para leer los bytes, eso te lo garantizo.

Por ejemplo:
Código Delphi [-]
var
  hPort: THandle;
  DCB: TDCB;
  Estado: TCOMSTAT;
  ErrorCode: cardinal;
  Leidos: Cardinal;
  B: Byte;
begin
  // Para el COM2 usa '\\.\COM2'
  hPort:= CreateFile(PChar('\\.\COM1'), GENERIC_READ or GENERIC_WRITE,0, nil,
    OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
  if hPort<>INVALID_HANDLE_VALUE then
  begin
    DCB.DCBlength:= sizeof(DCB);
    if GetCommState(hPort,DCB) then
    begin
      // Cambiar esto para una configuracion del puerto diferente
      with DCB do
      begin
        BaudRate := CBR_9600;
        ByteSize := 8;
        Parity   := NOPARITY;
        StopBits := ONESTOPBIT;
        // Aqui es donde indicamos que active RTS y DTR
        Flags    := $1013;
      end;
      if SetCommState(hPort, DCB) then
      begin
        // Vaciamos el Buffer
        PurgeComm(hPort, PURGE_TXABORT or PURGE_RXABORT or PURGE_TXCLEAR or
          PURGE_RXCLEAR);
        repeat
          B:= 1;
          ClearCommError(hPort, ErrorCode, @Estado);
          if Estado.cbInQue > 0 then
          begin
            ReadFile(hPort,B,1,Leidos,nil);
            // Aqui usa el valor de B para lo que quieras
          end else Sleep(100);
          // Si no hay bytes esperando dormimos un poco para no estresarnos
        until B = 0;
        // Terminamos el bucle cuando recibimos un 0, tu ajustalo a tus necesidades
      end;
    end;
    CloseHandle(hPort);
  end;
end;

Aunque si vas a leer una gran cantidad de bytes te recomiendo que metas el código dentro de un Thread, para no tener la aplicación parada.

Geta 17-08-2006 21:17:13

Seoane, al final voy a probar lo que dices de programarme a pelo las rutinas de manipulacion del COM. Pero estoy pez en la api. Sabes de informacion de la API referente a como manipular el puerto COM ?

Agredeceria links o documentos.

A por cierto esto de los threads, ni idea como funcionan?

gracias.

seoane 17-08-2006 21:26:59

Todas las funciones las tienes en la ayuda. En el menú de ayuda de Delphi abre "Windows SDK" hay encontraras la función CreateFile, SetCommState, etc. Incluso creo recordar que viene algún ejemplo de como leer el puerto serie. Básicamente windows trata el puerto serie como si fuera un archivo mas (con ciertas limitaciones), una vez abierto y configurado, puedes leer y escribir en el como si de un archivo se tratase. Tienes un par de funciones, además, para manejar si te hiciese falta, las lineas RTS,DTR, etc.

PD: En el ejemplo anterior, añadi una linea que faltaba.

seoane 17-08-2006 21:31:56

Cita:

Empezado por Geta
A por cierto esto de los threads, ni idea como funcionan?

Los threads sirven para ejecutar un código en un hilo diferente al principal. Si una rutina va a durar mucho corremos el riesgo de que nuestra aplicación parezca congelada. Así que podemos utilizar threads para que se ejecute paralelamente al resto de la aplicación. En este foro encontraras mucha información sobre threads, echale un vistazo

Geta 18-08-2006 16:41:18

Nada, no soy capaz de mandar un string por el com con las funciones de la API. Me es imposible.

Imaginad una cadena a mandar por ejemplo: ' hola'

Como la mando, he imtentado con writefile pero al leer la respuesta del dispositivo, nada, no hay respuesta.

estoy desesperado.

egostar 18-08-2006 19:57:46

He hecho algunas pruebas con el componente AdpComPort, prueba con este código, haber que tal.

Código Delphi [-]
 
 
var
  Form1: TForm1;
  Primer,Resto : String;
  
:::::::::
 
procedure TForm1.ApdComPort1TriggerAvail(CP: TObject; Count: Word);
var
  I : Word;
  C : Char;
  S : String;
begin
  S := '';
  for I := 1 to Count do begin
    C := ApdComPort1.GetChar;
    S := S + C;
  end;
  sleep(100);
  if Length(S) = 1 then begin
     Primer := S;
  end
  else begin
         Resto := S;
         Memo1.Lines.Add(Primer+Resto);
         Primer := '';
         Resto  := '';
       end;
end;

Puede ser mejorado, pero lo hice asi al vuelo.

Te explico un poco, en el evento OnTriggerAvail del AdpComPort llega los bytes uno por uno, entonces, al llegar cada paquete leo el primer caracter y espero 100 ms para que llegue toda la cadena restante. Yo uso un Memo para ver la cadena pero tu puedes usar variables.

Podrás mover este parámetro de espera en ms si la cadena es muy grande, pero no lo creo necesario.

Espero te sirva.

Saludos

seoane 18-08-2006 20:06:02

Suponiendo que ya tienes el puerto abierto y configurado, para escribir una cadena puedes usar algo como esto:

Código Delphi [-]
var
  s: string;
  Escritos: Cardinal;
begin
  s:= 'hola'; // O puede que s:= 'hola' + #13#10;
  WriteFile(hPort, PChar(s)^,Length(s), Escritos, nil);
end;

Geta 18-08-2006 21:01:52

Cita:

Empezado por seoane
Suponiendo que ya tienes el puerto abierto y configurado, para escribir una cadena puedes usar algo como esto:

Código Delphi [-]var s: string; Escritos: Cardinal; begin s:= 'hola'; // O puede que s:= 'hola' + #13#10; WriteFile(hPort, PChar(s)^,Length(s), Escritos, nil); end;


Magnifico, asi si que me lo entiende el dispositivo. Le he dicho a si a groso modo que encienda un led con un byte en hexadecimal y se ha encendido. pero se supone me tenia que responder un byte y no lo hace. Leo con ReadFile asi:

Código:

comando: String;
...
...
ReadFile(hPort,comando,length(comando),cuantos,nil);

pero nada.

seoane 18-08-2006 21:26:49

Bien, parece que estas controlando algún tipo de dispositivo electrónico, habría que ver como es el protocolo de comunicación de ese dispositivo. ¿Utiliza comandos de texto?, es decir, los que tu mandas y recibes son cadenas de texto. Si es así, para mandar el texto ya tienes la solución arriba, y para recibir texto hay que saber que carácter termina cada linea. Lo normal es leer byte a byte el puerto serie hasta encontrar el carácter final que suele ser #13.

De todas formas el código que tu pones no es correcto:
Cita:

Empezado por Geta
comando: String;
...
...
ReadFile(hPort,comando,length(comando),cuantos,nil);

No puedes leer el puerto serie con una variable tipo string, al menos no así. Ya que ReadFile intentara leer tantos caracteres como longitud tenga la cadena de texto almacenada en comando (si no hay nada sera 0), y lo peor es que lo almacenara a partir de la primera posición de memoria de la variable comando, que en un string no coincide con el primer carácter de la misma. Yo normalmente leo los bytes uno a uno, o utilizo un buffer (un array de Chars).

De todos modos, ¿cual es el protocolo de comunicación de ese dispositivo?, ¿que le tienes que mandar tu?, ¿que te responde el?, ¿es texto o bytes?, ¿sabes la longitud de la respuesta, o esta delimitada por un carácter final?. Y si no es mucho preguntar ¿que dispositivo es?, si me das más datos puede que entienda mejor lo que necesitas.

Geta 19-08-2006 11:42:58

al dispositivo se le mandan bytes en hexadecimal pero para mandarselos y que lo entienda ese byte lo hemos de pasar a char. Asi por ejemploo mandando la siguiente secuencia de bytes 20 30 40 50 primero tendria que pasar el byte 20 a char luego el 30 a char, etc y cuando tengamos todos los bytes en char mandarlo. la respuesta nos tendria que dar una secuencia de chars que nosotros pasariamos a hex para ver su valor.

Pero vamos que casi todos los circuitos electronicos que se manejan por el com creo que van de forma parecido o igual.

Otra cosa, se me olvidaba darte la gracias por tu interes.

Geta 19-08-2006 12:51:01

Al final lo he conseguido. Muchas gracias. Lo que he hecho a sido hacer lo que tu decias. Con un bucle ir leyendo byte a byte pasarlo a char y añadirlo a un string y ya tengo la respuestas que tenia que darme.

Me has sido de gran ayuda. Muchisimas gracias.

Solo una pregunta que se me ocurre. Al leer un byte del buffer del com con ReadFile, ese byte se borra del buffer al ser leido o se sigue quedando ahi hasta que limpiemos el buffer o se reciba otra cosa?

seoane 19-08-2006 13:36:08

No estoy seguro si te entendí bien, quieres decir que si tienes que mandar el byte 41h (65 en decimal) lo que tienes que mandar es Chr($41) (Una 'A'). No entiendo porque, un Char y un byte tienen ambos el mismo tamaño (8 bits), de hecho no hay diferencia entre enviar un byte o un Char. ¿O refieres acaso a convertir cada byte en dos caracteres que representen su valor hexadecimal?

Por ejemplo, si tengo que enviar los bytes 20h, 30h, 40h, 50h se podría hacer así (siempre suponiendo que tenemos el puerto abierto y configurado):
Código Delphi [-]
var
  Buffer: array[1..4] of byte; // El tamaño puede ser mayor
  Escritos: Cardinal;
begin
  Buffer[1]:= $20;
  Buffer[2]:= $30;
  Buffer[3]:= $40;
  Buffer[4]:= $50;
  WriteFile(hPort, Buffer,4, Escritos, nil);
end;


Para leer los bytes tenemos que saber que nos va a mandar, es decir, podemos estar leyendo continuamente, leer durante un tiempo, o leer hasta encontrar un carácter final. La primera opción no parece practica, solo seria útil para hacer un programa que monitorizara el puerto serie. Y como parece ser que tu dispositivo no utiliza un carácter especial para terminar la conexión, tendremos que quedarnos con la segunda opción.

Código Delphi [-]
var
  Buffer: array[1..4096] of byte; //4kb por ejemplo
  Leidos: Cardinal;
  Ticks: Cardinal;
  i,j: integer;
  B: byte;
  Str: string;
  Estado: TCOMSTAT;
  ErrorCode: cardinal;
begin
  i:= 0;
  Ticks:= GetTickCount;
  repeat
    ClearCommError(hPort, ErrorCode, @Estado);
    if Estado.cbInQue > 0 then
    begin
      ReadFile(hPort,B,1,Leidos,nil);
      inc(i);
      Buffer[i]:= B;
    end else Sleep(10);
  until (GetTickCount - Ticks) > 5000; // Leemos durante 5 segundos
  // Ahora puedes hacer lo que quieras con los bytes recibidos,
  // por ejemplo mostrarlos
  Str:= '';
  for j:= 1 to i do
    Str:= Str + IntToHex(Buffer[j],2) + 'h ';
  ShowMessage(Str);
end;

Una secuencia de como lo haria yo seria la siguiente:

Código:

Abrir el puerto
Configurar el puerto

  Purgar el puerto
  Mandar los bytes
  Recibir la respuesta durante unos segundos
  Mostrar la respuesta o usarla para lo que se quiera

Cerrar el puerto


seoane 19-08-2006 13:38:45

Parece que mientras respondía, tu te adelantaste :D

Cita:

Empezado por Geta
Al final lo he conseguido. Muchas gracias. Lo que he hecho a sido hacer lo que tu decias. Con un bucle ir leyendo byte a byte pasarlo a char y añadirlo a un string y ya tengo la respuestas que tenia que darme.

Me has sido de gran ayuda. Muchisimas gracias.

Solo una pregunta que se me ocurre. Al leer un byte del buffer del com con ReadFile, ese byte se borra del buffer al ser leido o se sigue quedando ahi hasta que limpiemos el buffer o se reciba otra cosa?

Al leerlo se borra del buffer, lo limpiar el buffer es una manía que tengo para asegurarme que no hay nada en el buffer antes de empezar.

Geta 19-08-2006 16:58:20

Muchas gracias por todo amigo mio. Ya lo tengo funcionando.


La franja horaria es GMT +2. Ahora son las 14:31:22.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi