Forum |  HardWare.fr | News | Articles | PC | S'identifier | S'inscrire | Shop Recherche
1162 connectés 

  FORUM HardWare.fr
  Programmation
  C

  Lecture port com vc++, sans blocage ?

 


 Mot :   Pseudo :  
 
Bas de page
Auteur Sujet :

Lecture port com vc++, sans blocage ?

n°2150424
evilbrood
Posté le 23-07-2012 à 15:44:10  profilanswer
 

Bonjour,  
dans le cadre d'un stage, je dois réaliser une application qui récupère une trame via un port COM, ensuite convertir ces données, puis les envoyer sur un port Ethernet.
 
J'ai tout d'abord réalisé cette appli avec visual studio 2010, mais avec la console, le code fonctionne.
 
Maintenant je dois réaliser une interface graphique, j'ai donc opté pour des windowsforms sur vc++.
 
Le code fonctionne toujours, je peux recevoir des données via le port COM, puis les convertir et les envoyer sur le port Ethernet.  
Mon seul problème est que j'ai utilisé un bouton pour lancer la lecture dur port COM, et que une fois le bouton appuyé, l'appli est bloquée tant qu'une trame n'est pas reçue sur le port COM.  
 
Pour l'instant, le code de mon bouton est le suivant :
 

Code :
  1. int nBytesRead=0, nBytesMessageRecu = 0, tailleMessageCOM=0;
  2. char charBuffer[1024], IP[16] = "127.0.0.1";
  3. int j, portSophie;
  4. BOOL COM_SOPHIE2 = FALSE;
  5. unsigned char message_conv_KLV[MAX_TEXT_KLV], recept_COM[TAILLE_MAX_COM];
  6. klv_icd ^ klv_icd2 = gcnew klv_icd();
  7. portCOM ^ portCOM2 = gcnew portCOM();
  8. portSophie = (int)NUM_PORT_COM->Value;
  9. TIM_COM->Enabled = 1;
  10. // Lecture des deux premiers octets de la trame afin de récupérer la taille de la trame
  11. while(nBytesMessageRecu < 2) {
  12. if(portCOM2->COM_Sophie == 0){
  13.  if(portCOM2->OpenSophie(portSophie)){
  14.   portCOM2->COM_Sophie = TRUE;
  15.   this->TXT_ETAT_COM->Text = "Connexion OK !";
  16.  }
  17.  else this->TXT_ETAT_COM->Text = "No Connection !";
  18. }
  19. if(portCOM2->COM_Sophie) if(portCOM2->ReadSophie(charBuffer, 1, &nBytesRead) == 0) portCOM2->COM_Sophie = FALSE;
  20.  nBytesMessageRecu = 0;
  21.  if(portCOM2->COM_Sophie){
  22.  if(nBytesRead){
  23.   while(nBytesMessageRecu < 2) {
  24.    if(nBytesMessageRecu < 2) recept_COM[nBytesMessageRecu] = (unsigned char) charBuffer[0];
  25.    nBytesMessageRecu++;
  26.    portCOM2->ReadSophie(charBuffer, 1, &nBytesRead);
  27.   }
  28.  }
  29. }
  30. }
  31. recept_COM[nBytesMessageRecu] = (unsigned char) charBuffer[0];
  32. nBytesMessageRecu = 0;
  33. //Lecture des octets restants
  34. while((nBytesMessageRecu + 3) < (recept_COM[1] + 4)) {
  35. if(!portCOM2->COM_Sophie) if(portCOM2->OpenSophie(portSophie)) portCOM2->COM_Sophie = TRUE;
  36. if(portCOM2->COM_Sophie) if(!portCOM2->ReadSophie(charBuffer, 1, &nBytesRead)) portCOM2->COM_Sophie = FALSE;
  37.  nBytesMessageRecu = 0;
  38. if(portCOM2->COM_Sophie){
  39.  if(nBytesRead){
  40.   while(nBytesRead) {
  41.    if((nBytesMessageRecu + 3) < (recept_COM[1] + 4)) recept_COM[nBytesMessageRecu+3] = (unsigned char) charBuffer[0];
  42.    nBytesMessageRecu++;
  43.    portCOM2->ReadSophie(charBuffer, 1, &nBytesRead);
  44.   }
  45.  }
  46. }
  47. }
  48. portCOM2->CloseSophie();
  49. //affichage de la trame reçue
  50. ...
  51. ...
  52. ...


 
 
Afin de résoudre ce problème, j'ai pensé à utiliser un timer, cette fonction se terminerait au bout de x secondes si elle n'a pas reçu de trame, mais je ne vois pas vraiment comment m'y prendre.
Ou alors utiliser des threads, mais là je vois encore moins, surtout qu'une fonction appellée dans un thread ne peut avoir qu'un argument ?
 
Si vous avez des idées, des pistes, je vous en remercie d'avance !


---------------
Mon topic de ventes matos informatique : http://forum.hardware.fr/hfr/Achat [...] 1897_1.htm
mood
Publicité
Posté le 23-07-2012 à 15:44:10  profilanswer
 

n°2150447
xilebo
noone
Posté le 23-07-2012 à 16:37:44  profilanswer
 

il faut créer le port COM en mode OVERLAPPED , et ensuite faire un WaitForSingleObject sur le handle du port COM pour savoir s'il y a des données à lire ou non, et les lire le cas échéant. C'est un peu le même principe que le select en réseau.
 
 

Code :
  1. // creation port
  2. HANDLE serial_handle = CreateFile(buf, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING , FILE_FLAG_OVERLAPPED  , NULL);
  3. // creation event  
  4. OVERLAPPED overlap_read_evt;
  5. memset(&overlap_read_evt, 0, sizeof(overlap_read_evt));
  6. overlap_read_evt.hEvent =  CreateEvent(NULL,TRUE,FALSE,NULL);
  7. // ecoute sur le port  
  8. DWORD event_mask;
  9. event_mask = EV_RXCHAR;
  10. WaitCommEvent( serial_handle , &event_mask, &overlap_read_evt); // attache l event au handle , a réarmer à chaque appel
  11. DWORD retwait = WaitForSingleObject(overlap_read_evt.hEvent, 1000); // attend 1000 ms  s'il s'est passé quelque chose  
  12. if (retwait == WAIT_OBJECT_0)
  13. { // recu des données
  14. ResetEvent(overlap_read_evt.hEvent);
  15. ReadFile( serial_handle, mybuffer , mybuffersize, (DWORD*)&mybytesread, &overlap_read_evt) == 0);
  16. }
  17. else if (retwait == WAIT_TIMEOUT)
  18. { // rien recu
  19. }
  20. else if (retwait == WAIT_FAILED)
  21. { // erreur
  22. }
  23. // fermeture
  24. CloseHandle( overlap_read_evt.hEvent );
  25. CloseHandle( serial_handle );


n°2150479
evilbrood
Posté le 23-07-2012 à 17:44:22  profilanswer
 

Merci pour ta réponse rapide !
 
J'ai implémenté ta solution, effectivement, le programme attend bien le temps indiqué, puis laisse la main à l'interface.
 
Par contre, maintenant plus moyen de recevoir des données sur ce port, j'ai beau lui envoyer une trame, retwait vaut toujours "WAIT_TIMEOUT", à quoi est-ce du ?
 
J'ai créé une fonction renvoyant un DWORD (retwait), prenant en argument  
 - "unsigned char* buffer"
 - "int portCOM" j'ai remplit 'buf' avec "\\\\.\\COM" + portCOM
 - "int mybuffersize" qui correspond à la taille de la trame à recevoir
 
Le corps de la fonction correspond à ton code + remplissage de 'buf'


---------------
Mon topic de ventes matos informatique : http://forum.hardware.fr/hfr/Achat [...] 1897_1.htm
n°2150680
evilbrood
Posté le 24-07-2012 à 15:54:41  profilanswer
 

Petit up,  
en fait j'ai implémenté la solution avec le port com overlapped, mon code est le suivant :

Code :
  1. int portCOM::LectureCOM(unsigned char* buffer, int portCOM, int mybuffersize){
  2. /* Délais d'attente sur le port COM Sophie */
  3. COMMTIMEOUTS g_ctoSophie = {
  4.  MAX_WAIT_READ_SOPHIE, /* ReadIntervalTimeOut */
  5.  0, /* ReadTotalTimeOutMultiplier */
  6.  MAX_WAIT_READ_SOPHIE, /* ReadTotalTimeOutConstant */
  7.  0, /* WriteTotalTimeOutMultiplier */
  8.  0 /* WriteTotalTimeOutConstant */
  9. };
  10. /* Configuration du port COM Sophie */
  11. DCB g_dcbSophie = {
  12.   sizeof(DCB), /* DCBlength */
  13.   115200, /* BaudRate */ //115200
  14.   TRUE, /* fBinary */
  15.   TRUE, /* fParity */
  16.   FALSE, /* fOutxCtsFlow */
  17.   FALSE, /* fOutxDsrFlow */
  18.   DTR_CONTROL_ENABLE, /* fDtrControl */
  19.   FALSE, /* fDsrSensitivity */
  20.   FALSE, /* fTXContinueOnXoff */
  21.   FALSE, /* fOutX */
  22.   FALSE, /* fInX */
  23.   FALSE, /* fErrorChar */
  24.   FALSE, /* fNull */
  25.   RTS_CONTROL_ENABLE, /* fRtsControl */
  26.   FALSE, /* fAbortOnError */
  27.   0, /* fDummy2 */
  28.   0, /* wReserved */
  29.   0x100, /* XonLim */
  30.   0x100, /* XoffLim */
  31.   8, /* ByteSize */
  32.   ODDPARITY, /* Parity */
  33.   ONESTOPBIT, /* StopBits */
  34.   //ONESTOPBIT, //DEBUG
  35.   0x11, /* XonChar */
  36.   0x13, /* XoffChar */
  37.   '?', /* ErrorChar */
  38.   0x1A, /* EofChar */
  39.   0x10 /* EvtChar */
  40. };
  41. char szCOM[16], charBuffer[1024];
  42. int result, nBytesMessageRecu = 0;
  43. int* nBytesRead = 0;
  44. OVERLAPPED overlap_read_evt;
  45. sprintf(szCOM, "\\\\.\\COM%d", portCOM);
  46. HANDLE h_sophie2 = CreateFile(szCOM, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
  47. if (h_sophie2 == INVALID_HANDLE_VALUE) result = 1;
  48.  /* affectation taille des tampons d'émission et de réception */
  49.     SetupComm(h_sophie2, RX_SIZE, TX_SIZE);
  50.     /* configuration du port COM */
  51.     if(!SetCommTimeouts(h_sophie2, &g_ctoSophie) || !SetCommState(h_sophie2, &g_dcbSophie)){
  52.         result = 2;
  53. }
  54.     /* on vide les tampons d'émission et de réception, mise à 1 DTR */
  55.     PurgeComm(h_sophie2, PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT|PURGE_RXABORT);
  56.     EscapeCommFunction(h_sophie2, SETDTR);
  57. memset(&overlap_read_evt, 0, sizeof(overlap_read_evt));
  58. overlap_read_evt.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  59. DWORD event_mask;
  60. event_mask = EV_RXCHAR;
  61. WaitCommEvent(h_sophie2, &event_mask, &overlap_read_evt);
  62. DWORD retwait = WaitForSingleObject(overlap_read_evt.hEvent, 6000);
  63. if (retwait == WAIT_OBJECT_0){
  64.  ResetEvent(overlap_read_evt.hEvent);
  65.  // Lecture des deux premiers octets de la trame afin de récupérer la taille de la trame
  66.  while(nBytesMessageRecu < 2) {
  67.   nBytesMessageRecu = 0;
  68.   if(nBytesRead){
  69.    while(nBytesMessageRecu < 2) {
  70.     if(nBytesMessageRecu < 2) buffer[nBytesMessageRecu] = (unsigned char) charBuffer[0];
  71.     nBytesMessageRecu++;
  72.     ReadFile(h_sophie2, charBuffer, 1, (DWORD*)&nBytesRead, &overlap_read_evt);
  73.    }
  74.   }
  75.  }
  76.  buffer[nBytesMessageRecu] = (unsigned char) charBuffer[0];
  77.  nBytesMessageRecu = 0;
  78.  //Lecture des octets restants
  79.  while((nBytesMessageRecu + 3) < (buffer[1] + 4)) {
  80.   nBytesMessageRecu = 0;
  81.   if(nBytesRead){
  82.    while(nBytesRead) {
  83.     if((nBytesMessageRecu + 3) < (buffer[1] + 4)) buffer[nBytesMessageRecu+3] = (unsigned char) charBuffer[0];
  84.     nBytesMessageRecu++;
  85.     ReadFile(h_sophie2, charBuffer, 1, (DWORD*)&nBytesRead, &overlap_read_evt);
  86.    }
  87.   }
  88.  }
  89. }
  90. else if (retwait == WAIT_TIMEOUT){
  91.  result = 5;
  92. }
  93. CloseHandle(overlap_read_evt.hEvent);
  94. CloseHandle(h_sophie2);
  95. return result;
  96. }


 
Le problème est que WaitForSingleObject(.....) renvoie toujours WAIT_TIMEOUT, même si j'envoie une trame sur le port désigné.
Je ne comprends pas vraiment pourquoi...


---------------
Mon topic de ventes matos informatique : http://forum.hardware.fr/hfr/Achat [...] 1897_1.htm
n°2150700
xilebo
noone
Posté le 24-07-2012 à 16:19:15  profilanswer
 

je regarderai en détail plus tard , juste pour info, j'ouvre le port com avec la chaine de caractère suivante : "COM1:"  ou "COM2:" ou ...
 

n°2150873
evilbrood
Posté le 25-07-2012 à 11:29:17  profilanswer
 

Bon finalement j'ai contourné le problème.
 
Au lieu de configurer le port COM en OVRELAPPED, j'ai utilisé un timer.
 
A chaque évènement TICK du Timer, je lance une lecture sur le port COM, sauf que j'ai enlevé les boucles  
 - "while(nBytesMessageRecu < 2)"  
 - "while(nBytesMessageRecu + 3 < recept_COM[1] + 4)"
 
Ca fonctionne, avec une cadence de 10ms, le soft n'est pas bloqué, et les trames sont reçues.
Par contre, une fois sur deux environ, je ne reçoit pas la bonne trame, à quoi cela peut-il être du ? Peut être que à cause du timer, le prog commence à lire la trame "au milieu" de sa réception ?  
Comment résoudre ce problème ? J'ai beau diminuer la cadence du timer, cela ne résout rien...
 
 
J'ai aussi une autre question qui me taraude, pour l'instant je déclare mes variables dans mes contrôles.
C'est à dire que à chaque TICK du timer (ou avant à chaque clic sur un bouton), je déclare mes variables (nBytesMessageRecu, recept_COM[]...).
Du coup ces dernières sont inaccessibles à l'extérieur de ce contrôle. Comment faire pour que mes variables soient accessibles depuis tous les contrôles ? J'ai bien essayé de les déclarer dans form_load, mais le problème est le même. Ou alors au début du fichier Form1.h, à cet endroit :

Code :
  1. public ref class Form1 : public System::Windows::Forms::Form
  2. {
  3. public:
  4.  Form1(void)
  5.  {
  6.   InitializeComponent();
  7.   //
  8.   //TODO: ajoutez ici le code du constructeur
  9.   //
  10.  }
  11.  //Déclaration des variables à cet endroit ??
  12. protected:
  13.  /// <summary>
  14.  /// Nettoyage des ressources utilisées.
  15.  /// </summary>
  16.  ~Form1()
  17.  {
  18.   if (components)
  19.   {
  20.    delete components;
  21.   }
  22.  }


 
Mais j'ai des erreurs me disant que le type est managé et que cela n'est donc pas possible.
 
Ou les déclarer dans ce cas ? Ou alors comment y accéder depuis les autres contrôles ?
 
 
Merci d'avance


---------------
Mon topic de ventes matos informatique : http://forum.hardware.fr/hfr/Achat [...] 1897_1.htm
n°2150952
SquiZZ
Posté le 25-07-2012 à 14:48:49  profilanswer
 

Pour tes problèmes de lecture, ca me semble normal si tu utilises la fonction LectureCom de ton message précédent. A chaque appel tu ouvres le port, vides les buffers puis le referme.
 
Si tu es parti pour faire du C#, il faut (faudrait) que tu te fasse une classe pour gérer le port série avec :  
- une méthode pour ouvrir et configurer les port (à appeler dans le constructeur de ta form)
- une méthode pour fermer le port (à appeler dans le destructeur de ta form)
- une methode de lecture, soit non bloquante à appeler dans un timer, soit bloquante à appeler dans un thread (suivant les paramètres de SetCommTimeout)
 
et déclarer une instance de cette classe dans la partie 'protected' du code que tu a posté ci-dessus.


Message édité par SquiZZ le 25-07-2012 à 14:49:04
n°2150973
evilbrood
Posté le 25-07-2012 à 15:38:35  profilanswer
 

Pour le coup des variables accessibles uniquement dans le contrôle où elles sont déclarées, le problème est réglé.  
J'ai crée un fichier .h dans lequel j'ai fait mes déclarations, et j'ai inclus ce fichier au début de mon form1.h
 
 
 
Après pour la lecture du port COM, je n'ai pas précisé, je n'ai en fait pas utilisé la fonction LectureCOM.
Enfin au final tu as raison, j'ouvre et ferme le port série à chaque fois.
 
Effectivement, à chaque évènement Tick, je fais ceci :

Code :
  1. int nBytesRead=0, nBytesMessageRecu = 0, tailleMessageCOM=0;
  2. char charBuffer[1024];
  3. int j, portSophie;
  4. BOOL COM_SOPHIE2 = FALSE, COM_RECEIVED = FALSE;
  5. unsigned char recept_COM[TAILLE_MAX_COM];
  6. klv_icd ^ klv_icd2 = gcnew klv_icd();
  7. portCOM ^ portCOM2 = gcnew portCOM();
  8. portSophie = (int)NUM_PORT_COM->Value;
  9. if(!COM_SOPHIE2) if(portCOM2->OpenSophie(portSophie)){
  10.  COM_SOPHIE2 = TRUE;
  11.  TXT_ETAT_COM->Text = "Connexion OK";
  12. }
  13. else TXT_ETAT_COM->Text = "No Connection";
  14. if(COM_SOPHIE2) if(!portCOM2->ReadSophie(charBuffer, 1, &nBytesRead)){
  15.  COM_SOPHIE2 = FALSE;
  16.  portCOM2->CloseSophie();
  17. }
  18. nBytesMessageRecu = 0;
  19. if(COM_SOPHIE2){
  20.  if(nBytesRead){
  21.   while(nBytesMessageRecu < 2) {
  22.    if(nBytesMessageRecu < 2) recept_COM[nBytesMessageRecu] = (unsigned char) charBuffer[0];
  23.    nBytesMessageRecu++;
  24.    portCOM2->ReadSophie(charBuffer, 1, &nBytesRead);
  25.   }
  26.  }
  27. }
  28. recept_COM[nBytesMessageRecu] = (unsigned char) charBuffer[0];
  29. nBytesMessageRecu = 0;
  30. //Lecture des octets restants
  31. if(!COM_SOPHIE2) if(portCOM2->OpenSophie(portSophie)) COM_SOPHIE2 = TRUE;
  32. if(COM_SOPHIE2) if(!portCOM2->ReadSophie(charBuffer, 1, &nBytesRead)) COM_SOPHIE2 = FALSE;
  33. nBytesMessageRecu = 0;
  34. if(COM_SOPHIE2){
  35.  if(nBytesRead){
  36.   while(nBytesRead) {
  37.    if((nBytesMessageRecu + 3) < (recept_COM[1] + 4)) recept_COM[nBytesMessageRecu+3] = (unsigned char) charBuffer[0];
  38.    nBytesMessageRecu++;
  39.    portCOM2->ReadSophie(charBuffer, 1, &nBytesRead);
  40.    COM_RECEIVED = TRUE;
  41.   }
  42.  }
  43. }
  44. portCOM2->CloseSophie();
  45. if(COM_RECEIVED){
  46.  //affichage de la trame reçue
  47.  .....
  48.  //Lecture de la trame - extraction des coordonnées
  49.  if(klv_icd2->extractFields(recept_COM, recept_COM[1] + 4, &reponseSophie) == 1){
  50.   //Affichage des coordonnées
  51.   ....
  52.   //Conversion en KLV
  53.   klv_icd2->reponseToKLV(reponseSophie, message_conv_KLV);
  54.   //Affichage des cooronnées en KLV
  55.   ....
  56.  }
  57. }


 
Si j'ai bien compris, ce que tu me conseille de faire, c'est :
 - Ouvrir le port série au lancement de l'application
 - Lire le port série dans un timer, ou avec un thread
 - Fermer le port quand on quitte l'application
C'est bien ça ?
 
Le problème, c'est que je dois pouvoir modifier le port COM à lire quand l'application est lancée, du coup il faudrait ouvrir le port COM quand on modifie le n° du port ?
Et le fermer (si il a été ouvert) à ce moment aussi ?


Message édité par evilbrood le 25-07-2012 à 15:42:07

---------------
Mon topic de ventes matos informatique : http://forum.hardware.fr/hfr/Achat [...] 1897_1.htm
n°2151125
evilbrood
Posté le 26-07-2012 à 12:56:59  profilanswer
 

petit up !
 
J'ai résolu le problème de réception COM, j'ai rajouté une condition sur le premier octet recu (si il vaut bien 0x01 on continue à lire, sinon on réessaie), et ça fonctionne !!
 
Du coup il ne me reste plus qu'un problème à régler dans mon projet, c'est à dire pouvoir envoyer la trame reçue (une fois convertie) via le port Ethernet.
Pour l'instant ça fonctionne (du moins en local, je n'ai pas testé entre deux PCs), mais le problème est le même que pour le port série.
L'envoi se fait sur le click d'un bouton, et tant que le client ne s'est pas connecté, le programme est bloqué.
 
Par contre cette fois je ne pense pas pouvoir contourner le problème avec un timer, vu que pour envoyer la trame j'utilise la fonction suivante :
csock = accept(..., ..., .......);
qui elle est bloquante.
 
Donc à moins de pouvoir utiliser une fonction non bloquante qui ferait à peu près la même chose, je vais devoir utiliser les threads ?
Mais je suis un peu perdu, je ne vois pas vraiment quelles fonctions utiliser, pthreat_create() et pthread_join() ???


---------------
Mon topic de ventes matos informatique : http://forum.hardware.fr/hfr/Achat [...] 1897_1.htm
n°2151245
Farian
Posté le 27-07-2012 à 08:21:11  profilanswer
 

Bonjour !
 
Effectivement, la fonction "accept" est bloquante, mais la fonction "select" permet de résoudre le problème, car elle permet de savoir si il y a un client en attente sur la "listening socket" (celle sur laquelle vous faites le accept).
 
Lors de l'appel,  vous rajoutez le "file descriptor" de la socket dans le paramètre "readfs" et si un client est en attente, la socket est marquée comme ayant des données à lire, et vous pouvez lancer l'appel "accept" qui rendra la main tout de suite.

mood
Publicité
Posté le 27-07-2012 à 08:21:11  profilanswer
 

n°2151931
evilbrood
Posté le 01-08-2012 à 14:16:52  profilanswer
 

Merci pour ta réponse, ça m'a pas mal aidé quand j'en avais besoin.
 
Finalement, j'ai du utiliser les threads, et au final c'est pas si compliqué que ça, il faut faire gaffe et bien savoir ce que l'on fait mais ça va tout seul !


---------------
Mon topic de ventes matos informatique : http://forum.hardware.fr/hfr/Achat [...] 1897_1.htm

Aller à :
Ajouter une réponse
  FORUM HardWare.fr
  Programmation
  C

  Lecture port com vc++, sans blocage ?

 

Sujets relatifs
Lecture, insertion/création fichierLecture des articles de mon blog horizontalement.
blocage sur mon programmeLecture de fichier
pb subi de lecture de mp3, barre de boutons out, compatibilité embed?Lire port série C# et convertir en texte
Question sur lecture fichierexplication et lecture d'un fichier binaire(C3D)
Lecture de fichiers binaires [résolu]problème réception port série.
Plus de sujets relatifs à : Lecture port com vc++, sans blocage ?


Copyright © 1997-2022 Hardware.fr SARL (Signaler un contenu illicite / Données personnelles) / Groupe LDLC / Shop HFR