Problema leer/escribir puerto serie (C++ WINAPI)

Iniciado por SARGE553413, 18 Agosto 2014, 12:22 PM

0 Miembros y 5 Visitantes están viendo este tema.

SARGE553413

Hola a todos.

Estoy intentando hacer una clase con la funcionalidad básica (abrir,cerrar,leer,escribir) para manejar el puerto serie.
Esto es debido a que el puerto serie que tengo que usar lo genera "virtualmente" un programa y la clase SerialPort del .NET Framework no consigue ni abrirlo. (Pregunté en foros de microsof (msdn) y me dijeron que tenía que hacerlo con la WINAPI).

Tras hacer la clase y probarla, he conseguido abrir el puerto y cerrarlo bien. El problema es que no consigo leer ni escribir. En ambas operaciones, si hago "GetLastError()" el error resultante es 997, que según microsoft es: ERROR_IO_PENDING - Overlapped I/O operation is in progress.
(http://msdn.microsoft.com/en-us/library/windows/desktop/ms681388(v=vs.85).aspx)

NOTA: si llamo a las funciones de lectura / escritura con el parámetro LPOVERLAPPED como NULL, el código de error (GetLastError) es 87 (el parámetro es incorrecto), el mismo error que tuve cuando traté de usar la clase Net Framework SerialPort.

Adjunto el código de la clase (solo el cpp, el .h no lo veo necesario):

#include "StdAfx.h"
#include "SerialPort_WINAPI.h"

/*** Atributos constantes definidos en el .h ***/
const wchar_t *const SerialPort_WINAPI::DEFAULT_PORT_NAME = L"COM6";
static const wchar_t *const DEFAULT_PORT_NAME; //Inicializado en el cpp.
static const DWORD BAUD_RATE = 9600;
static const BYTE STOP_BITS = 1;
static const BYTE PARITY = 0; //0==None
static const BYTE DATA_BITS = 8;
static const DWORD HANDSHAKE = 0x00;

SerialPort_WINAPI::SerialPort_WINAPI(void){

}

SerialPort_WINAPI::~SerialPort_WINAPI(void){

}

SerialPortErr SerialPort_WINAPI::openConnection(){
port = CreateFile(SerialPort_WINAPI::DEFAULT_PORT_NAME, //\\\\.\\COM4"
GENERIC_READ | GENERIC_WRITE,
0,    // exclusive access
NULL, // default security attributes
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);
if (port == INVALID_HANDLE_VALUE)
return Create_File_Err;

if (GetCommState(port, &portConfig) == 0){
closeConnection();
return Get_Comm_State_Err;
}

initData();
if (SetCommState(port, &portConfig) == 0){
closeConnection();
return Set_Comm_State_Err;
}

if (SetCommMask(port, EV_DSR) == 0){ //EV_CTS | EV_DSR
closeConnection();
return Set_Comm_Mask_Err;
}

initOverlapped();
portEvents.hEvent = CreateEvent(
NULL,   // default security attributes
TRUE,   // manual-reset event
FALSE,  // not signaled
NULL    // no name
);

return Exit_Success;
};

void SerialPort_WINAPI::initData(){
portConfig.BaudRate = BAUD_RATE;
portConfig.fRtsControl = HANDSHAKE;
portConfig.Parity = PARITY;
portConfig.ByteSize = DATA_BITS;
portConfig.StopBits = STOP_BITS;
};

void SerialPort_WINAPI::initOverlapped(){
portEvents.Internal = 0;
portEvents.InternalHigh = 0;
portEvents.Offset = 0;
portEvents.OffsetHigh = 0;
};

SerialPortErr SerialPort_WINAPI::closeConnection(){
SetCommMask(port,0);
if (CloseHandle(port) == 0)
return Close_Err;
return Exit_Success;
};

SerialPortErr SerialPort_WINAPI::read(char *buffer,int length,LPDWORD read){
if(!ReadFile(this->port,buffer,length,read,&this->portEvents)){
return Read_Err;
}
}

SerialPortErr SerialPort_WINAPI::write(char *buffer,int nBytesToWrite,LPDWORD write){
if(!WriteFile(this->port,buffer,nBytesToWrite,write,&this->portEvents)){
return Write_Err;
}
}


Y aquí el main:

int main(){
SerialPort_WINAPI sp;
char readBuffer[128];
DWORD read;
char writeBuffer[10];
DWORD write;
for(int i=0;i<10;i++) writeBuffer[i]='a';

SerialPortErr err=sp.openConnection();
if(err!=Exit_Success){
cout<<"open: "<<(int)err<<endl;
return -1;
}else{
cout<<"Openned..."<<endl;
}

err=sp.write(writeBuffer,10,&write);
if(err!=Exit_Success){
cout<<"write: "<<GetLastError()<<endl;
}

err=sp.read(readBuffer,128,&read);
if(err!=Exit_Success){
cout<<"read: "<<GetLastError()<<endl;
}


err=sp.closeConnection();
cout<<"close: "<<(int)err<<endl;
return 0;
}


He estado intentando aprender a usar esta API para el puerto serie, pero aunque he leído algunos ejemplos etc. hay muchas cosas que no entiendo.
Por ej. : ¿OVERLAPPED sirve para añadir funciones de callback a distintos eventos del puerto serie, como leer o escribir?

Gracias y saludos.

Eternal Idol

Overlapped es para trabajar con I/O asincrona, en este caso el evento sera señalado cuando se complete el I/O.

Synchronous and Asynchronous I/O.

En realidad ese no es un error, como dice la MSDN:
Note  The GetLastError code ERROR_IO_PENDING is not a failure; it designates the read operation is pending completion asynchronously. For more information, see Remarks.

En fin, te aconsejo no usar overlapped, al menos no al principio, primero hacelo funcionar de manera sincrona.

Synchronization and Overlapped Input and Output.
La economía nunca ha sido libre: o la controla el Estado en beneficio del Pueblo o lo hacen los grandes consorcios en perjuicio de éste.
Juan Domingo Perón

SARGE553413

#2
Si, justo lo acabo de leer lo del error, gracias.

[INNECESARIO]
El caso es que ahora si he podido leer y escribir, pero la función ReadFile() siempre me devuelve 0 (error) y no entiendo por qué. EDITO: Se me había olvidad añadir el "return Exit_Success" en caso de que todo fuese bien .... "
[/INNECESARIO]

Realmente creo que quizá no necesite usarlo de manera asíncrona, explico lo que tengo que hacer:

1 - Por un lado, enviar a la máquina conectada al serie señales para setear algunos parámetros (como por ej. temperaturas de peligro y no-peligro).

2 - Además, quiero tener un hilo en background escuchando "constantemente" (osea cada x milis) al puerto serie, porque esta máquina envía una señal si se alcanza una temperatura designada como "peligrosa" y vuelve a mandar una señal si se vuelve a la temperatura "no-peligrosa".

Entonces para el segundo punto si que necesito lo asíncrono, ¿cierto?

Gracias, saludos.


Eternal Idol

Cita de: SARGE553413 en 18 Agosto 2014, 13:09 PMRealmente creo que quizá no necesite usarlo de manera asíncrona, explico lo que tengo que hacer:

1 - Por un lado, enviar a la máquina conectada al serie señales para setear algunos parámetros (como por ej. temperaturas de peligro y no-peligro).

2 - Además, quiero tener un hilo en background escuchando "constantemente" (osea cada x milis) al puerto serie, porque esta máquina envía una señal si se alcanza una temperatura designada como "peligrosa" y vuelve a mandar una señal si se vuelve a la temperatura "no-peligrosa".

Entonces para el segundo punto si que necesito lo asíncrono, ¿cierto?

No hace falta, si la operacion de I/O no retorna cuando es sincrona es que no hay datos que procesar. En el segundo punto si el hilo solo se dedica a I/O no hay problema.
La economía nunca ha sido libre: o la controla el Estado en beneficio del Pueblo o lo hacen los grandes consorcios en perjuicio de éste.
Juan Domingo Perón

SARGE553413

Hola de nuevo, gracias por la respuesta.

Entonces de momento no haré nada asíncrono. Pero me queda una pregunta, se supone que ReadFile() no es bloqueante, es decir, si hago ReadFile() y el serie no escribe nada, continúa la ejecución del programa, no se queda ahí esperando para siempre.

Entonces ¿qué pasa si le mando algo al serie y antes de que le de tiempo a escribir yo hago ReadFile() ? ¿Hay alguna manera de esperar con ReadFile() un tiempo determinado, y después seguir?
Se qué se puede configurar el serie con timeouts por lo que he leído, pero no entiendo muy a qué se refiere, si a cerrar el serie tras un tiempo o a lo que digo yo.

Gracias, saludos.

Eternal Idol

Si no usas Overlapped ReadFile es bloqueante.
La economía nunca ha sido libre: o la controla el Estado en beneficio del Pueblo o lo hacen los grandes consorcios en perjuicio de éste.
Juan Domingo Perón

SARGE553413

#6
Ok, gracias de nuevo.

Entonces tengo otra duda:
Tengo que tener un hilo escuchando constantemente al serie. Esto en principio parece fácil porque ReadFile() es bloqueante, de manera que dejo al hilo en un bucle "infinito"
haciendo esta operación.

El problema es que, en algunos momentos, el programa tendrá que escribir y leer cosas en el serie, entonces la manera de hacer esto sería:
1 - Decirle al "hilo escuchador" que pare de escuchar y espere hasta nueva orden (con un evento por ej.)
2 - Realizar las operaciones de lectura/escritura.
3 - Decirle al hilo que vuelva a empezar a escuchar.

Bien, lo que no tengo claro como hacer es, estando el hilo bloqueado por el ReadFile(), conseguir que salga de ese bloqueo.

NOTA: EL hilo del que hablo, no es de la API de windows, es el del .Net Framework, quizá una posibilidad sería esta:


...
bool shouldStop=false;
ManualResetEvent ^mre=gcnew ManualResetEvent(false);
...

void warningListener(){
while(!stop){
try{
ReadFile(...);
//Hacer algo...
}catch(ThreadInterruptException ^ie){
mre->WaitOne();
}
}
}


Pero no se si al hacer Thread.Interrupt() estando el hilo bloqueado, saltará alguna excepción...

Saludos, gracias.

Eternal Idol

No conozco tu problema en profundidad pero tal vez en lugar de intentar desbloquear el hilo a mano una mejor alternativa sea realizar todas las lecturas en el hilo, dependiendo de que lea puede procesarlo, ignorarlo, crear otro hilo para que actue, etc..; cuando escribas desde el hilo principal u otro hilo esto va a terminar desbloqueando el hilo de lectura si el dispositivo en el puerto responde.

Por favor, no preguntes sobre .NET en este subforo, no es el lugar adecuado.
La economía nunca ha sido libre: o la controla el Estado en beneficio del Pueblo o lo hacen los grandes consorcios en perjuicio de éste.
Juan Domingo Perón


Eternal Idol

La economía nunca ha sido libre: o la controla el Estado en beneficio del Pueblo o lo hacen los grandes consorcios en perjuicio de éste.
Juan Domingo Perón