Votre question

Lire port série C# et convertir en texte

Tags :
  • port série
  • Programmation
Dernière réponse : dans Programmation
24 Avril 2012 15:49:52

Bonjour à tous,

Je viens vous solliciter parce que je rencontre un petit soucis. Je suis en train de développer une petite application qui me permet de lire les données d'un chronomètre. J'ai réussi à faire la lecture des données, mais comme c'est la première fois que je fais ça, j'aurais voulu savoir comment traduire en format texte ce qui ressort du chrono, c'est-à-dire comment obtenir le temps indiquer sur le chronomètre. Pour l'instant, je récupère mes données en héxa. Mais je bloque sur la conversion en chaîne.

Je vous mets le code que j'utilise pour la lecture du port série.

  1. using System;
  2. using System.Linq;
  3. using System.Data;
  4. using System.Text;
  5. using System.Drawing;
  6. using System.IO.Ports;
  7. using System.Windows.Forms;
  8. using System.ComponentModel;
  9. using System.Collections.Generic;
  10. using System.Threading;
  11. using System.IO;
  12. namespace WindowsFormsApplication1
  13. {
  14. public partial class Form1 : Form
  15. {
  16. private SerialPort sp = new SerialPort();
  17. public enum LogMsgType { Incoming, Outgoing, Normal, Warning, Error };
  18. private Color[] LogMsgTypeColor = { Color.Blue, Color.Green, Color.Black, Color.Orange, Color.Red };
  19. public Form1()
  20. {
  21. InitializeComponent();
  22. sp.BaudRate = 9600;
  23. sp.Parity = Parity.None;
  24. sp.StopBits = StopBits.One;
  25. sp.DataBits = 8;
  26. sp.Handshake = Handshake.None;
  27. sp.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
  28. }
  29. private void button1_Click(object sender, EventArgs e)
  30. {
  31. bool erreur = false;
  32. // Si le port est ouvert, il faut le fermer
  33. if (sp.IsOpen) sp.Close();
  34. else
  35. {
  36. // Réglage paramètre du port
  37. sp.PortName = cmbPortName.Text;
  38. try
  39. {
  40. // Ouvrir le port
  41. sp.Open();
  42. }
  43. catch (UnauthorizedAccessException) { erreur = true; }
  44. catch (IOException) { erreur = true; }
  45. catch (ArgumentException) { erreur = true; }
  46. if (erreur)
  47. MessageBox.Show("Impossible d'ouvrir le port COM. Très probablement, il est déjà en cours d'utilisation, a été supprimé, ou n'est pas disponible.", "COM Port indisponible", MessageBoxButtons.OK, MessageBoxIcon.Stop);
  48. else
  49. MessageBox.Show("Connexion réussi","Port disponible" );
  50. }
  51. }
  52. // Connexion à la fenêtre du terminal
  53. private void Log(LogMsgType msgtype, string msg)
  54. {
  55. rtfTerminal.Invoke(new EventHandler(delegate
  56. {
  57. rtfTerminal.SelectedText = string.Empty;
  58. rtfTerminal.SelectionFont = new Font(rtfTerminal.SelectionFont, FontStyle.Bold);
  59. rtfTerminal.SelectionColor = LogMsgTypeColor[(int)msgtype];
  60. rtfTerminal.AppendText(msg);
  61. rtfTerminal.ScrollToCaret();
  62. }));
  63. }
  64. //Convertit un tableau d'octets en une chaîne formatée de chiffres hexadécimaux.
  65. private string ByteArrayToHexString(byte[] data)
  66. {
  67. StringBuilder sb = new StringBuilder(data.Length * 3);
  68. foreach (byte b in data)
  69. sb.Append(Convert.ToString(b, 16).PadLeft(2, '0').PadRight(3, ' '));
  70. return sb.ToString().ToUpper();
  71. }
  72. private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
  73. {
  74. // Si le port est ouvert, le fermer
  75. if (!sp.IsOpen) return;
  76. // Obtenir le nombre d'octets en attente dans le tampon du port
  77. int bytes = sp.BytesToRead;
  78. // Créez une zone tampon (tableau d'octets) pour stocker les données entrantes
  79. byte[] buffer = new byte[bytes];
  80. // Lire les données du port et de le stocker dans la mémoire tampon
  81. sp.Read(buffer, 0, bytes);
  82. // Montrer à l'utilisateur les données entrantes dans un format hexadécimal
  83. Log(LogMsgType.Incoming, ByteArrayToHexString(buffer));
  84. }
  85. }
  86. }


Je vous remercie d'avance

Autres pages sur : lire port serie convertir texte

a c 232 L Programmation
24 Avril 2012 16:34:43

Salut,

Tu dois pouvoir transformer ton tableau de byte[] en string, avec quelque chose dans ce genre :
  1. string data = Encoding.UTF8.GetString(buffer);
m
0
l
24 Avril 2012 16:44:23

En faisant ça, j'obtiens un codage bizarre : ffff�~~�
Alors que je voudrais obtenir un temps au format 00:00'00"00
m
0
l
Contenus similaires
a c 232 L Programmation
24 Avril 2012 17:02:22

C'est peut-être pas de l'UTF8 alors :D  Essaie avec un autre encodage (ASCII) alors
  1. string data = Encoding.ASCII.GetString(buffer);
m
0
l
24 Avril 2012 17:05:55

Oh j'ai aussi, mais pareil un truc bizarre apparaît : f`?f????~
Remarque c'est joli mais pas super utile ^^
m
0
l
a b L Programmation
24 Avril 2012 23:09:56

Je ne pense pas que ce soit une chaine de caractère.

Qu'affiche le log?
m
0
l
25 Avril 2012 08:34:31

Voici ce que j'obtiens en faisant un readExisting() : ?`fx????

Voici le protocole de sortie des données du chrono. Si quelqu'un y comprends quelques choses, je suis preneur, car là, je suis largé
http://imagesia.com/scan_2bu
m
0
l
a b L Programmation
25 Avril 2012 20:49:02

Ton code fait un log sur le port_DataReceived. C'est cela que je te demande, pas un readExisting() direct car il faut voir le code en hexa.

En regardant ta doc, on voit bien que c'est du binaire.
Si tu n'as pas l'habitude de ce genre de doc (spec de communication), voici comment il faut traduire:

1. Commencer par le premier tableau. La première case indique qu'en colonne tu trouve le type de message et en ligne les nibbles (un nibble étant un caractère hexa qui se code sur 4 bits, 2 nibbles forment un octet).
Ce tableau indique qu'il existe 7 messages possibles (Header, Wave No, etc).
La première colonne (le premier nibble) indique le type du message.
Donc, tu doit décoder le premier nibble pour savoir le type de message que tu es train de recevoir.
Les nibbles suivants indiquent pour chaque message les données qu'il contient.
Et pour savoir ce que signifie chaque nibble de chaque message, il faut se reporter au second tableau.

2. Dans le second tableau, tu retrouve pour chaque message une explication détaillée des champs. Par exemple pour race type, si tu as un nibble qui est égal à 0000b (je mets b pour indiquer que j'écris en binaire), ça signifie Track. Ce nibble peut avoir aussi les valeurs 0001b et 0010b.

Par exemple, si tu reçois le code hexa 01h en premier octet, alors 01h = 0000 0001b. Donc tu as le type de message 0000b qui signifie que c'est un message de type "Header" et donc, sachant que c'est ce message, tu sais que le 0001b correspond au race type qui signifie donc Single (road).

3. Le troisième tableau est un exemple. Il donne les valeurs des champs séparés par des espaces, mais attention,
il n'y a pas d'espace, et 1 caractère n'est qu'un nibble donc la moitié d'un octet.
par exemple le message 0 0 0001 020726 DDDD est en réalité en hexa (ce que tu reçois): 00 00 01 02 07 26 DD DD

4. Tu as un exemple du codage du temps.
ça t'indique que pour une durée 00:00:00:00, chaque élément est encodé en binaire dans un octet. C'est pourquoi tu a besoin de 8 nibbles pour coder un temps.

5. Sur le même exemple, on t'indique qu'en pratique les nibbles sont inversé dans un même octet

Bref, pour être bien sûr de l'encodage, tu regardes le message que tu reçois en hexadecimal (conversion binaire en hexa faite dans ton log).
m
0
l
26 Avril 2012 09:12:23

Merci pour tes explications qui m'aident beaucoup. En effet, je ne suis pas du tout expert dans ce genre de documentation, faut dire que c'est la première fois que j'ai affaire à ceci.

Voici un exemple de ce que je reçois : 06 00 05 00 35 15 08 00 15
Sachant que j'ai sélectionné un Single en 1 Wave, le temps = 00:05:35:18, le Bib= 5, Lane = 1

Si j'ai bien compris, si je le traduis ça donne ceci :
Place 0005, Time : 00 35 15 08 (donc si on inverse ça me donne bien 00:05:35:18), Lane 1, Bib 5

Par contre, si je comprends bien le 2ème tableau le Bib et Lane sont inversés dans ce que j'ai reçu
m
0
l
a b L Programmation
27 Avril 2012 21:38:03

En regardant ta trace, il faut en fait inverser tous les nibbles d'un même octet:
06 00 05 00 35 15 08 00 15
devient
60 00 50 00 53 51 80 00 51
soit en mettant les espace suivant les champs:
6 0005 00053518 0005 1

Pour récupérer un nibble dans l'octet, tu fais:
(octet & 0x0F) pour avoir le 2ème nibble de l'octet (qui est en fait le premier après inversion)
((octet & 0xF0) >> 4) pour avoir le 1er nibble de l'octet (qui est en fait le premier après inversion)

Dans le premier cas, je fais un masque binaire (octet abcdefgh, chaque lettre étant un bit à 0 ou 1) : abcdefgh ET 00001111, donc ça force à 0 les 4 premiers bits, et les 4 suivant sont à la valeur de l'octet, donc ça donne: 0000efgh = efgh (le nibble de droite allant de 0h à Fh)
Dans le deuxième cas, c'est pareil avec 11110000b, pour avoir abcd0000, et je décale tout de 4 bits à droite (ce qui, mathématiquement revient à diviser par 2^4=16). Bref, ça donne 0000abcd = abcd (le nibble de gauche allant de 0h à Fh).

La bonne méthode à appliquer si tu connais bien le C#, c'est de faire une classe par type de message. Lorsque tu reçois un message, tu analyses le premier nibble du premier octet, et selon le type tu instancies un objet de la classe qui correspond.
Chaque classe décode son message en remplissant des variables membres (c'est ce qui s'appelle la désérialisation). Ceci t'évite de travailler directement sur le message binaire : tu le décodes une bonne fois pour tout dans la classe pour ne plus à avoir à le manipuler dans tes algorithmes.
m
0
l
30 Avril 2012 11:30:49

J'ai capté qu'après avoir mis mon post qu'en fait tout était inversé, et j'ai pas eu le temps de rééditer le post.

Citation :

Pour récupérer un nibble dans l'octet, tu fais:
(octet & 0x0F) pour avoir le 2ème nibble de l'octet (qui est en fait le premier après inversion)
((octet & 0xF0) >> 4) pour avoir le 1er nibble de l'octet (qui est en fait le premier après inversion)

Dans le premier cas, je fais un masque binaire (octet abcdefgh, chaque lettre étant un bit à 0 ou 1) : abcdefgh ET 00001111, donc ça force à 0 les 4 premiers bits, et les 4 suivant sont à la valeur de l'octet, donc ça donne: 0000efgh = efgh (le nibble de droite allant de 0h à Fh)
Dans le deuxième cas, c'est pareil avec 11110000b, pour avoir abcd0000, et je décale tout de 4 bits à droite (ce qui, mathématiquement revient à diviser par 2^4=16). Bref, ça donne 0000abcd = abcd (le nibble de gauche allant de 0h à Fh).

Là je t'avoue que tu m'as perdu dans cette partie ... Les nibbles pour moi c'est nouveau et j'ai un peu de mal à tout saisir. Je comprends ce que c'est, mais j'ai du mal à comprendre ton explication.

Bien connaître le C# est un grand mot, je reste tout de même débutant. Après réflexion, j'avais aussi pensé à faire des classes par type de message, mais j'avais pas forcément pensé à la désérialisation, mais c'est vrai que comme tu dis ça m'évitera bien de décoder le message à chaque fois.

PS : je tiens tout de même à te remercier pour ton aide
m
0
l
a b L Programmation
1 Mai 2012 13:20:17

C'est du traitement binaire, alors j'indique les bases.

1 octet est codé sur 8 bits, et un nibble sur 4 bits
les valeurs possibles d'un nibble:
binaire hexadecimal decimal
0000 00 0
0001 01 1
0010 02 2
0011 03 3
0100 04 4
0101 05 5
0110 06 6
0111 07 7
1000 08 8
1001 09 9
1010 0A 10
1011 0B 11
1100 0C 12
1101 0D 13
1110 0E 14
1111 0F 15


et pour les opérations binaires:
0 ET 0 = 0
1 ET 0 = 0
0 ET 1 = 0
1 ET 1 = 1
Si je dis que le premier bit est la valeur lue, et le second le masque : si le masque = 0, alors le résultat sera toujours à 0, et si le masque est à 1, alors le résultat sera toujours la valeur.
donc, si tu as le nombre 10110101, et que tu veux forcer les premiers bits à 0, tu fais 10110101 ET 00001111 = 00000101.
Ceci permet d'extraire la valeur d'un nibble dans un octet.
m
0
l
1 Mai 2012 14:26:33

Ok. En effet, là je comprends mieux ce que tu essayais de m'expliquer dans ton précédent post.
Je te remercie beaucoup pour ton aide.
Si je comprends bien, corrige moi si je me trompe, si je veux récupérer la valeur du 1er nibble (si on reprends l'exemple 60 00 50 00 53 51 80 00 51), je dois faire :
valeur = octet & 0xF0 >> 4 c'est-à-dire ici : valeur = 0100 1000 & 1111 0000 = 0100 0000

Mais par contre, pourquoi passer par la récupération de la valeur du nibble ?
Pour moi, je pensais : je récupère la première valeur, une fois que je sais à quelle type de message ça correspond, je découpe ma chaîne et remplie les champs avec.
Comme je reste débutant, je pense que ma façon de penser n'est peut-être pas la plus optimale.
m
0
l
a b L Programmation
2 Mai 2012 20:19:35

Dans ton exemple, le premier octet est 60 en hexa, donc en binaire, c'est 0110 0000, sinon c'est bien ça.

En fait, sur ton ordinateur, tu ne gères que des octets (et pas plus précis), donc pas de nibbles.
Dans le log, la même chose est faite implicitement dans le Convert.ToString.

Après, tu peux te débrouiller avec la chaine de caractère convertie puisque le découpage en nibbles est fait, mais attention, c'est une chaine de caractère et '6' ne vaut pas 6, car c'est codé en ASCII.
m
0
l
3 Mai 2012 08:10:24

Ok, merci. Du coup, toi tu me conseillerais de faire comment ? Récupérer la valeur du 1er nibble ou me débrouiller avec ma chaîne ? Quelle est la méthode la plus optimale ?

Citation :
Après, tu peux te débrouiller avec la chaine de caractère convertie puisque le découpage en nibbles est fait, mais attention, c'est une chaine de caractère et '6' ne vaut pas 6, car c'est codé en ASCII.

Si je récupère le '6', je sais quel type de message il s'agit, après j'ai juste à découper ma chaîne correctement.
Par contre, justement quand je veux découper ma chaîne en faisant :
  1. MessageBox.Show(ByteArrayToHexString(buffer).Substring(0, 1));

ou
  1. string s = Reverse(ByteArrayToHexString(buffer));
  2. char[] tab = s.ToCharArray();
  3. MessageBox.Show(tab[0].ToString());


Je reçois deux MessagesBox avec (si on reprends l'exemple), le 1er MessageBox qui affiche '6' et le second qui affiche '0'. Donc en gros ça me fait deux chaînes :
chaîne1 = 60 et chaîne2 = 00 50 00 53 51 80 00 51

D'après ce que j'ai pu comprendre et de ce que l'on m'a expliqué sur d'autres forums, ma trame serait découpée. Mais maintenant comment faire pour qu'elle ne soit pas découpée ?
m
0
l
a b L Programmation
3 Mai 2012 23:43:18

Travailler sur les chaines n'est pas forcément bien car tu risques de devoir refaire la conversion inverse pour certains champs.

Je te conseille de travailler directement sur le buffer.
Pour identifier ton message, tu prends le premier octet (buffer[0]), tu le convertis pour avoir le nibble du type:
  1. byte msgType = (buffer[0] & 0x0F)

Dans ton exemple, msgType == 6.

Et selon le type, tu instancie un objet de classe correspondant au message.

Pour créer la classe, tu en fais une par type de message, par exemple class MsgRoadBibTime { ... }
tu fais passer le buffer en paramètre du constructeur, et tu renseignes des variables membres
  1. MsgRoadBibTime(byte[] data) {
  2.  
  3. byte nibble0 = (buffer[0] & 0x0F);
  4. mType = nibble0; // Bon tu sais déjà que c'est 6, tu peux le revérifier
  5.  
  6. // Récupération des 4 prochain nibbles pour le mettre dans un int
  7. byte nibble1 = ((buffer[0] & 0xF0) >> 4);
  8. byte nibble2 = ((buffer[1] & 0x0F));
  9. byte nibble3 = ((buffer[1] & 0xF0) >> 4);
  10. byte nibble4 = ((buffer[2] & 0x0F));
  11. // On replace les nibbles pour former la valeur
  12. mPlace = (((int)nibble1) << 12) + (((int)nibble2) << 8) + (((int)nibble3) << 4) + (int)nibble4
  13.  
  14. byte nibble5 = ((buffer[2] & 0xF0) >> 4);
  15. byte nibble6 = ((buffer[3] & 0x0F));
  16. mPlaceTimeHR = (((int)nibble5) << 4) + (int)nibble6
  17. byte nibble7 = ((buffer[3] & 0xF0) >> 4);
  18. byte nibble8 = ((buffer[4] & 0x0F));
  19. mPlaceTimeMN = (((int)nibble5) << 4) + (int)nibble6
  20. byte nibble9 = ((buffer[4] & 0xF0) >> 4);
  21. byte nibble10 = ((buffer[5] & 0x0F));
  22. mPlaceTimeSD = (((int)nibble5) << 4) + (int)nibble6
  23. byte nibble11 = ((buffer[5] & 0xF0) >> 4);
  24. byte nibble12 = ((buffer[6] & 0x0F));
  25. mPlaceTimeCT = (((int)nibble5) << 4) + (int)nibble6
  26.  
  27. // etc... en suivant les spécifications
  28. }


Donc, tu crées un objet en passant en paramètre le buffer, et tu accèdes directement aux valeurs avec des getters.
Bon, moi j'aurais factorisé le décodage, mais j'ai préféré simplifier
m
0
l
4 Mai 2012 10:43:22

En gros ma classe ressemblerait à ça ;

  1. class RoadBibTime
  2. {
  3. private byte[] buffer();
  4. private string mPlace;
  5. private string temps;
  6. private string bib;
  7. private string lane;
  8.  
  9. public RoadBibTime(byte[] buffer)
  10. {
  11. byte nibble0 = (byte)(buffer[0] & 0x0F);
  12. byte mType = nibble0; // Bon tu sais déjà que c'est 6, tu peux le revérifier
  13.  
  14. // Récupération des 4 prochain nibbles pour le mettre dans un int
  15. byte nibble1 = (byte)((buffer[0] & 0xF0) >> 4);
  16. byte nibble2 = (byte)((buffer[1] & 0x0F));
  17. byte nibble3 = (byte)((buffer[1] & 0xF0) >> 4);
  18. byte nibble4 = (byte)((buffer[2] & 0x0F));
  19. // On replace les nibbles pour former la valeur
  20. mPlace = ((((int)nibble1) << 12) + (((int)nibble2) << 8) + (((int)nibble3) << 4) + (int)nibble4).ToString();
  21.  
  22. byte nibble5 = (byte)((buffer[2] & 0xF0) >> 4);
  23. byte nibble6 = (byte)((buffer[3] & 0x0F));
  24. byte mPlaceTimeHR = (byte)((((int)nibble5) << 4) + (int)nibble6);
  25. byte nibble7 = (byte)((buffer[3] & 0xF0) >> 4);
  26. byte nibble8 = (byte)((buffer[4] & 0x0F));
  27. byte mPlaceTimeMN = (byte)((((int)nibble5) << 4) + (int)nibble6);
  28. byte nibble9 = (byte)((buffer[4] & 0xF0) >> 4);
  29. byte nibble10 = (byte)((buffer[5] & 0x0F));
  30. byte mPlaceTimeSD = (byte)((((int)nibble5) << 4) + (int)nibble6);
  31. byte nibble11 = (byte)((buffer[5] & 0xF0) >> 4);
  32. byte nibble12 = (byte)((buffer[6] & 0x0F));
  33. byte mPlaceTimeCT = (byte)((((int)nibble5) << 4) + (int)nibble6);
  34. temps = (mPlaceTimeHR + mPlaceTimeMN + mPlaceTimeSD + mPlaceTimeCT).ToString();
  35.  
  36. byte nibble13 = (byte)((buffer[6] & 0x0F) >> 4);
  37. byte nibble14 = (byte)((buffer[7] & 0x0F));
  38. byte nibble15 = (byte)((buffer[7] & 0x0F) >> 4);
  39. byte nibble16 = (byte)((buffer[8] & 0x0F));
  40. bib = ((((int)nibble13) << 12) + (((int)nibble14) << 8) + (((int)nibble15) << 4) + (int)nibble16).ToString();
  41.  
  42. byte nibble17 = (byte)((buffer[8] & 0x0F) >> 4);
  43. lane = nibble17.ToString();
  44. }
  45.  
  46. public string getmPlace() { return mPlace; }
  47. public string getTemps() { return temps; }
  48. public string getLane() { return lane; }
  49. public string getBib() { return bib; }
  50. }


Citation :
Bon, moi j'aurais factorisé le décodage, mais j'ai préféré simplifier

C'est-à-dire ?
m
0
l
a b L Programmation
5 Mai 2012 21:27:26

mPlacen'est pas unstring, mais un int. Utilise une variable locale int, si tu veux faire la conversion en texte.

Le calcul de ta variable "temps" n'est pas correct car tu ajoutes des minutes avec des secondes.
Tu dois les convertir individuellement en chaine, et concaténer les chaines.

Pour la factorisation, j'aurais fait des méthodes parseData qui décode les nibbles par type, mais reste comme ça, c'est plus facile pour corriger les problèmes.
m
0
l
Tom's guide dans le monde
  • Allemagne
  • Italie
  • Irlande
  • Royaume Uni
  • Etats Unis
Suivre Tom's Guide
Inscrivez-vous à la Newsletter
  • ajouter à twitter
  • ajouter à facebook
  • ajouter un flux RSS