Utilisation de Tcomport [Résolu]

Messages postés
8
Date d'inscription
dimanche 9 avril 2006
Dernière intervention
14 avril 2006
- 11 avril 2006 à 09:13 - Dernière réponse :
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
- 14 avril 2006 à 15:06
Bonjour à tous,

Je suis nouveau sur le forum, et novice en POO... J'utilise Delphi 4.
Je souhaite utiliser Tcomport pour recevoir des trames de données binaires à 9600 bauds. Elles sont émises par un microcontroleur. La trame fait toujours 10 octets, mais la cadence à laquelle elles sont émises par le UC change (de 2 trames secondes à 10 trames secondes). Il n'y a pas de contrôle de flux, donc le UC envoie autoritairement ces trames de données binaires.
J'ai essayé de les récupérer à l'aide de OnRxChar, avec Comport.read(data,1). Je lis donc mes octets un par un puis les visualise à l'aide de Memo.text.

Ca marche à peu près, mais j'ai rencontré les problèmes suivants:
- Si l'émission des trames s'interrompt, mon affichage de données s'interrompt aussi alors que le buffer contient encore une cinquantaine de data. Je m'en suis apperçu en reprenant l'émission avec des trames de tests (donc différentes au niveau des datas), et les 50 (environ) premières datas affichées correspondaient à des anciennes données stockées dans le buffer.

- Si la trame est composé de la valeur 0 (en binaire et non pas le caractère), cette valeur ne semble pas vu par comport.Read. J'ai vu sur le forum qu'un autre développeur a rencontré exactement le même problème. Il n'y a pas eu de solution proposé, et il a contourné le problème en générant des trames ASCII et non pas binaire. Dans mon application je ne peux pas modifier le format des trames de données reçues. Je suis donc condamné à trouver une solution.

Mes questions:
- Le composant TCOMPORT est il conçu pour recevoir des données binaires? Si oui, y a-t-il un paramétrage à effectuer pour l'informer d'interpréter correctement une donnée reçue?
- Comment résoudre le traitement de la valeur 0 lors d'une réception?
- Comment utiliser Tcomport pour que les datas reçues soient instantannément lues au lieu d'être stockée plus ou moins longtemps dans le buffer?
- Quelqu'un peut il me faire parvenir un programme fonctionnel complet pour Delphi 4 utilisant Tcomport pour recevoir des trames binaires sans contrôle de flux afin que je comprenne bien toutes les subtilités afin de ne pas perdre des données en cours de route, ou bloquer le programme à cause d'une absence de Time-out? (Je le redis, je suis totalement novice POO).

Merci à tous pour votre aide


Christian
Afficher la suite 

Votre réponse

22 réponses

Meilleure réponse
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
11 avril 2006 à 11:23
3
Merci
salut,

c'est pas un problème de recevoir de données binaire avec Tcomport.

1ere chose quand tu utilises l'évèment OnRxChar utilises la variable
count pour lire l'ensemnble des données contenue dans le buffer faire
read (data,1) risque de te faire perdre des données (c'est du à la
gestion des évent par Windows)

si ta trame fait toujours 10 octets le plus simple c'est de faire:



si tu lance un timeout externe;

timeout.enabled:=true;

while (Comport1.inputcount<10)and timeout.enabled do Application.ProcessMessages;//pour ne pas bloquer

if not timeout.enabled then // si le temps est dépassé

begin

showmessage('Erreur Communication')

exit; on sort sinon ça plante

end;

comport1.read(data,10);//on reçoit la totalité de la trame

ton data peut être un record si tes données peuvent être structurées;



personnellement je préfère passer par un string même pour des données
binaires; en effet dans ce cas Comport se moque éperdument du type de
données qui sont reçues et on crée un tableau dynamique dont chaque
caractère est un octet ensuite il suffit de transtyper pour récuperer
les valeurs

par exemple si tu reçois une trame binaire constituée :

d'un char+2 integer+un boolean

var

chaine:string;

caractere:char

val1,val2:integer;

flag:boolean;

valint:^integer

valbool:^boolean;

begin

while Comport1.inputcount<10 do Application.ProcessMessages;//pour ne pas bloquer


comport1.readstr(chaine,10);//on reçoit la totalité de la trame

caractere:=chaine[1];

valint:=@chaine[2];

val1:=valint^;

valint:=@chaine[6];


val2:=valint^;

valboolt:=@chaine[10];

flag:=valbool^;



@+

jlen

Merci jlen100 3

Avec quelques mots c'est encore mieux Ajouter un commentaire

Codes Sources a aidé 93 internautes ce mois-ci

Commenter la réponse de jlen100
Meilleure réponse
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
11 avril 2006 à 12:17
3
Merci
je n'ai jamais eu le problème bien je communique en binaire avec des
microprocesseurs (les données que je reçois n'ont pas toujours de
trames constantes en général je formate au niveau du micropross ainsi:
type de données + données binaires et c'est à la réception que
j'interprète dans certains cas pour diminuer la charge du CPU je passe
par un scanner(timer) et une doblebufferisation si cela t'interesse vas
voir ma source: pilotage 4 axes) tu trouveras dans la méthode scanner la façon de faire



@+

jlen

Merci jlen100 3

Avec quelques mots c'est encore mieux Ajouter un commentaire

Codes Sources a aidé 93 internautes ce mois-ci

Commenter la réponse de jlen100
Meilleure réponse
Messages postés
329
Date d'inscription
lundi 30 décembre 2002
Dernière intervention
10 mars 2012
11 avril 2006 à 22:17
3
Merci
il faut faire attention au piège du caratère 0, #0 ou chr(0) c'est la même chose c'est un caractère zero terminal, qui signifie la fin du fichier ou texte
tu peux faire un Test d'ailleurs !!!

TEdit.Text := 'Hello' + #0 + 'World' , seul Hello apparaîtra !!!

c'est notamment le cas dans un tableau de type Char
si ton tableau est de type chaîne tu ne pourras plus lire les octets suivants le caractère #0 avec un Edit mais ça ne veut pas dire qu'il n'ya plus de données !!!

pour y remedier utilises un Tableau de type Byte
var
Buffer :array[0..512] of Byte;

on recoit l'évènement OnRxChar

on a alors le paramètre Count qui nous donne le nombres d'octets dans le tampon

dans ce cas on utilise ComPort.Read(Buffer , Count); en prenant le soin de vidé le tableau au préalable FillChar(Buffer , SizeOf(Buffer) , 0);
okay tu va me dire mais là on mets bien un #0 dans le Buffer, certes sauf que nous allons convertir ces données en Hexadécimal

var
S : String;
I : Integer;
begin
S := '';
for i := 0 to ComPort.InputCount -1 do
S := S + IntToHex(Buffer[I] , 2) + ' '; <== l'espace pour avoir une meilleur lisibilité des trames mais pas obligatoire !!!
TEdit1.Text := TRimRight(S);
donc en cas de valeur "0" tu auras "00" en valeur de type chaîne tout en sachant qu'on règle général les micro-contrôleurs communiquent par blocs d'octets en 'Byte' et rarement en Char 'Ascii' telles les vieux modems dans le pire des cas tu pourra refaire la même manip sauf qu'au lieu de mettre 'IntToHex' tu peux mettre chr(Buffer[I]) pour avoir la valeur Ascii

Merci cs_shining 3

Avec quelques mots c'est encore mieux Ajouter un commentaire

Codes Sources a aidé 93 internautes ce mois-ci

Commenter la réponse de cs_shining
Meilleure réponse
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
11 avril 2006 à 22:54
3
Merci
salut shining;

dans le cas de Tcomport tu peux très recevoir un caractere #0 avec la
méthode ReadStr; il le rangera dans la chaine comme tout autre
caractere il ne termine la chaine que quand il a reçu count caracteres
et peu importe leur valeur.

il suffit d'ailleurs de regarder les methodes de CPORT pour constater
que les méthodes read et readstr utilisent les mêmes procédures de
lecture



function TCustomComPort.Read(var Buffer; Count: Integer): Integer;

var

AsyncPtr: PAsync;

begin

InitAsync(AsyncPtr);

try

ReadAsync(Buffer, Count, AsyncPtr);

Result := WaitForAsync(AsyncPtr);

finally

DoneAsync(AsyncPtr);

end;

end;



// perform asynchronous read operation

function TCustomComPort. ReadStrAsync (var Str: string; Count: Integer; var AsyncPtr: PAsync): Integer;

begin

SetLength(Str, Count);

if Count > 0 then

Result : = ReadAsync(Str[1], Count, AsyncPtr)

else

Result := 0;

end;



// perform synchronous read operation

function TCustomComPort. ReadStr (var Str: string; Count: Integer): Integer;

var

AsyncPtr: PAsync;

begin

InitAsync(AsyncPtr);

try

ReadStrAsync(Str, Count, AsyncPtr);

Result : = WaitForAsync(AsyncPtr);

SetLength(Str, Result);

finally

DoneAsync(AsyncPtr);

end;

end;

seule l'affectation des variables diffère --> variable typé dans un cas non typée dans l'autre



on peut donc utiliser l'une ou l'autre des méthodes sans perte de
donnée les cas de read ne se justifie que quand on veut affecter
directement une variable.

l'avantage de readstr étant de garder une constante dans la lecture et
de pouvoir affecter à posteriori les variables (definition d'un
proctocole plus souple avec lecture en bloc et traitement asynchrone en
batch) contrairement à read qui impose de connaitre le type de variable
à priori.



@+

jlen

Merci jlen100 3

Avec quelques mots c'est encore mieux Ajouter un commentaire

Codes Sources a aidé 93 internautes ce mois-ci

Commenter la réponse de jlen100
Messages postés
8
Date d'inscription
dimanche 9 avril 2006
Dernière intervention
14 avril 2006
11 avril 2006 à 12:06
0
Merci
Bonjour,
Merci Jlen pour ta réponse. Je vais essayer ta solution. As tu des infos concernant l'absence de détection de la valeur 0 lors d'une réception de données?

@+

Christian
Commenter la réponse de cs_CHARLI78
Messages postés
8
Date d'inscription
dimanche 9 avril 2006
Dernière intervention
14 avril 2006
11 avril 2006 à 18:21
0
Merci
Merci encore pour ton aide.


Christian
Commenter la réponse de cs_CHARLI78
Messages postés
8
Date d'inscription
dimanche 9 avril 2006
Dernière intervention
14 avril 2006
12 avril 2006 à 10:30
0
Merci
Bonjour,

Merci à Shining et à Jlen pour leur aide. Tout cela me demande de prendre un peu de recul (car novice en POO). Je vais donc refaire des tests pour bien comprendre comment tout cela fonctionne en prenant en compte les conseils qui m'ont été donnés. Ca n'a pas l'air si simple que cela de recevoir des données binaire par le port série sous Windows...

@+


Christian
Commenter la réponse de cs_CHARLI78
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
12 avril 2006 à 10:54
0
Merci
le caractere #0 dans une chaineest surtout gènant à l'affichage ou il
est considéré comme un 0 terminal ou quand affecte la chaine à un
TCaption pour la même raison

maintenant

si tu fais

var buffer:string;

begin

buffer:='Hello' + #0 + 'World';

edit1.Text:buffer;---->resultat 'Hello'

edit2.Text:=inttostr(length(buffer));---->'11' soit la totalité de 'Hello' + #0 + 'World';


edit3.Text:=inttostr(length(buffer));---->'5' soit seulement 'Hello'

end;

ceci demontre clairement que l'on peut utiliser un string pour
transmettre des données binaire : il se comporte comme un tableau
dynamique et c'est comme cela que je l'utilise; c'est quand même plus
simple que de déclarer le tableau et de l' intialiser d'autant plus que
tu récupères facilement le nombre de caractères reçus en faisant
nombrecaracteres:=length(buffer); alors que dans le cas d'un tableau
type array[]of byte tu dois assigner une variable pour concerver cette
valeur.



@+

jlen
Commenter la réponse de jlen100
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
12 avril 2006 à 10:55
0
Merci
j'ai cliqué trop vite

var buffer:string;

begin

buffer:='Hello' + #0 + 'World';

edit1.Text:buffer;---->resultat 'Hello'

edit2.Text:=inttostr(length(buffer));---->'11' soit la totalité de 'Hello' + #0 + 'World';

edit3.Text:=inttostr(lengt( Edit2.Text));---->'5' soit seulement 'Hello'

end;



@+

jlen
Commenter la réponse de jlen100
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
12 avril 2006 à 11:17
0
Merci
note aussi que je fais la même chose pour envoyer mes trames sur le
port série, elles sont ainsi constituées indifféremment de caractères
et de données binaires et pour la constituer il suffit de faire une
concaténation de l'ensemble par exemple cema peut être:

buffer:=commande+parametre1+parametre2+.....+parametreN+checksum;

buffer:=chr(length(buffer))+buffer; //ici on ajoute au début le nombre
de carctères a recevoir; généralement je limite la trame à 253
caractéres et les parametres peuvent êter de tout types



@+

jlen
Commenter la réponse de jlen100
Messages postés
8
Date d'inscription
dimanche 9 avril 2006
Dernière intervention
14 avril 2006
13 avril 2006 à 09:28
0
Merci
Bonjour à tous

Plus j'essaie d'avancer dans la compréhension du fonctionnement de Tcomport, plus je regresse. Je n'ai toujours pas compris comment Tcomport gère la réception des données sur le port série, ni comment faire pour récupérer mes données. Plus j'avance et moins ca marche. (mon niveau en POO est toujours "débutant niveau -1".
J'ai bien vu qu'il faut saupoudrer un peu de "timeOut", peut être un peu de "inputCount", que l'on peut lire ses data 1 par 1 ou par bloc de N, que l'on peut utiliser une méthode ou une autre pour récupérer ses datas. Mais finalement quelle est la bonne méthode?

Faut- il commencer par vider le buffer, si oui comment
Faut - il lire à intervale régulier la présence de data dans le buffer à l'aide d'un timer
Faut-il utiliser la méthode OnRxChar.

J'ai fait un petit programme tout simple avec l'instruction ReadByte (qui provient du livre' j'exploite les interface de mon PC sous Windows') qui s'interroge sous timer. Tout marche parfaitement. Il n'y a pas de données perdues, ni de données qui restent dans le buffer et la valeur 0 est correctement affichée à l'écran. Mais avec Tcomport je n'arrive à rien de satisfaisant.

Serait-il donc possible que quelqu'un me fournisse un exemple basic de réception de données en continu par la liaison série gérant les problèmes de time out, de buffer etc... Cela me permettrait de démarrer d'une base fonctionnelle pour bien comprendre les mécanismes de ce composant.

Merci encore


Christian
Commenter la réponse de cs_CHARLI78
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
13 avril 2006 à 10:04
0
Merci
salut;

si tu nous disais ce que tu veux faire exactement et comment tu traite tes données ensuite on pourrais t'aider un peu plus.

toutes les méthodes qu'on t'as donnée fonctionnent

Utilser un timer ou le OnRxChar dépend surtout de la façon dont tu traites les données ensuite

le OnRxChar permet de déclencher l'évènement sur un caractère de début
de trame s'il ton début de trame est constant c'est lui qu'il faut
utiliser en définissant dans le concepteur le eventchar et en mettant
le EvRxChar à true;

ensuite dans le OnRxChar



while comport1.inputcount<10 do begin end;

comport1.readstr(buffer,10);



le probleme que l'on peut rencontrer avec le OnRxChar c'est que si l'on
pas traité les donnée avant la trame suivante on perd des données



si tu utilises un timer par exemple toute les 200ms

en variable globale

var buffer:string;

procedure Form1.timer(Sender: TObject)

var buf:string;

i:integer;

begin

with comport1 do

begin

i:=inputcount;//on récupère le nombre de caractère à lire

if i<>0 then readstr(buf,i);//on lit les caractères

end;

buffer:=buffer+buf; on les ajoutes au buffer et l'on traite après

end;

ensuite tu traite tes données comme avec un tableau le premier
caractère étant à la position 1 (buffer[1) et la dernière à
length(buffer);

avec l'une de ces 2 méthodes tu devrais pouvoir t'en sortir.



@+

jlen
Commenter la réponse de jlen100
Messages postés
8
Date d'inscription
dimanche 9 avril 2006
Dernière intervention
14 avril 2006
13 avril 2006 à 16:24
0
Merci
Bonjour Jlen,
Merci pour ta réponse. J'ai opté pour la première solution (attente de caractère). Ca marche à un détail prêt. Comme je te l'ai dit dans mon premier message, je cherche à recevoir des trames de 10 octets binaires. Le début de trame a une entête de reconnaissance contante faite de 2 octets: 0AAH puis 055H. Les 8 autres octets ont des valeurs quelconques entre 00H et 0FFH.

Comme je n'ai pas réussi à me synchroniser sur mon début de trame, je reçois bien mes 10 octets, mais à cheval sur 2 trames.
Comment est il possible de paramétrer Tcomport pour me synchroniser sur le début de trame. Le paramétrage de EventChar porte apparemment sur un char et non pas sur une valeur hexadécimale.
L'autre solution serait peut-être d'utiliser le temps mort entre mort entre 2 trames consécutives (mini 100 mS) pour synchroniser la réception sur un début de trame, mais je ne sais pas le programmer.

@+

Christian
Commenter la réponse de cs_CHARLI78
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
13 avril 2006 à 16:56
0
Merci
pour le caractere 0AAH c'est 170 en décimal dans eventchar tu mets #170

me probleme c'est omme tu reçois des valeurs binaire il est
probable que dans ta trame ru te retrouve avec un second couple de
valeur identique au début de trame la difficulté est surtout de
synchroniser ta trame au départ. En principe avec les micro controleurs
on leur envoie une commande de synchronisation qui permet d'être sur
sur qu'on a bien un début de trame sinon tu ne sera jamais sur
d'avoir un début de trame.

une solution consiste à détecter un début de trame et d'attendre
la trame entiere+10caractère. tu vérifies que le 11eme
caractère est bien un caractère de début de trame si c'est le cas tu as
des chances d'être synchrnisé. sinon tu relance la procedure.

Tu peux améliorer cette pocedure en recevant plusieurs trames (entre 5
et dix)et en recherchant les débuts de trames jusqu'à la fin du buffer
; si tu trouves bien l' occurence au moins 4 fois avant la fin de
du buffer tu peux considérer être synchronisé il te suffit de
calculer la position de la dernière occurence d'attendre la fin de
cette trame et tu seras synchronisé.



@+

jlen
Commenter la réponse de jlen100
Messages postés
8
Date d'inscription
dimanche 9 avril 2006
Dernière intervention
14 avril 2006
13 avril 2006 à 18:19
0
Merci
Ca y est je recois mes trames correctement synchronisées. Ce n'est pas du travail de pro, mais ça fonctionne. Cela me permet d'avoir à présent une base de travail fonctionnelle pour faire différents tests sur Tcomport.

Merci encore Jlen pour ton aide et ta patience.


Christian
Commenter la réponse de cs_CHARLI78
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
13 avril 2006 à 18:41
0
Merci
tu vois qu'on y arrive avec un peu de patiente et beaucoup de persévérance

aller bon courage pour la suite



@+

jlen
Commenter la réponse de jlen100
Messages postés
329
Date d'inscription
lundi 30 décembre 2002
Dernière intervention
10 mars 2012
13 avril 2006 à 20:35
0
Merci
Dans le cas où ton tableau est de type Byte une autre méthode consiste à lire les 1er octets transmis

var
Buffer : array[0..$FF] of Byte;
Header : WORD;// soit 2 Octets
begin
...
if ComPort.InputCount = 2 then
begin
ComPort.Read(Header , 2); // lecture de 2 Octets
Header := Header and $FFFF;
if Header = $55AA then
begin
while not(ComPort.inputCount=8)
begin
Application.ProcessMessages;
// on fait rien on attend juste que le buffer se remplisse !!!
end;
if ComPort.Count = 8 then // on s'assure que le compte est bon !!
begin
FillChar(Buffer , SizeOf(Buffer) , 0);// on vide le buffer
Comport.Read(Buffer[0] , 8); // le Buffer[0] n'est pas vraiment obligatoire on peut très bien mettre Buffer tout simplement
end;
// maintenant les données son stockées à partir de zero dans le Buffer, accessible comme suit Buffer[0]

en tout cas c'est comme ça que procéderait un micro-contrôleur
autre astuce encore étant données qu'un micro contrôleur est cadencé par son horloge interne il nécessite donc un certain delay entre chaque "octet" dans ce cas là juste à la reception de OnRxChar tu peux mettre
Sleep(I1*I2)
I1 étant le nombre d'octets attendus !!!
I2 etant environs le temps que prend le cpu pour transmettre 1 Octet en principe 2 ms c'est suffisant

Donc dans ton cas ça fait
Sleep(10*2), ainsi tu auras un bloc plus complet !! et tu évites de bouffer du CPU avec les "while InputCount" !!!
Commenter la réponse de cs_shining
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
13 avril 2006 à 21:04
0
Merci
lire 2 octets puis les 8 autres après n'est pas très judicieux du point
de vue utilisation du cpu---> on répète 2 fois la procédure il vaut
mieux la totalité des caractères avec inputcount et n'appeller
qu'une fois la procédure read.



De même read surchargeant obligatoirement le buffer par les caractères
reçus et attendant que la totalité soit transmise il est inutile de
vider la buffer avant d'appeler la procedure tu peux donc supprimer :


FillChar(Buffer , SizeOf(Buffer) , 0);// on vide le buffer

sans aucun risque

enfin il me semple que le sleep est bloquant et qu'ainsi non
seulement il bouffe autant de CPU que while inputcount mais qu'en plus
il n'autorise pas l'exécution des messages windows il vaut mieux
utiliser un timer et de ne récuperer que les octets transmis mais dans
ce cas il faut gérer un pointeur (l'utilisation de while inputcount ou
lancer un read() avec un nombre de caracteres à lire>nombre de
caractères reçu dans un ontimer entraine une instabilité de windows :
de l'écran instable au déplacement des fenêtres au plantage complet de
l'appli voir de windows)



@+

jlen
Commenter la réponse de jlen100
Messages postés
8
Date d'inscription
dimanche 9 avril 2006
Dernière intervention
14 avril 2006
14 avril 2006 à 07:57
0
Merci
Bonjour,
Merci encore à Jlen et à Shining pour votre aide. Mon niveau de développeur ne me permet pas de rentrer dans des considérations trop techniques. J'avais eu la même idée que celle de Shining pour détecter mon entête de trame. Dans un premier temps ça répond à mon cahier des charges, donc je vais continuer à faire une lecture de 2 +8 octets.

@+


Christian
Commenter la réponse de cs_CHARLI78
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
14 avril 2006 à 08:47
0
Merci
si ta trame à une structure constante tu peux définir une variable:



trame: record

header:word;

variable1: integer; //ici tu mets tes variables correspondant au type dont tu as besoin

variable2:integer;

end;

seule condition que la taille de trame soit supérieure à 10



une fois que tu as synchronisé ta réception tu peux faire

with comport1 do

begin

while inputcount<10 do Application.ProcessMessages

read(trame,10);//tu récupères ta trame formatée

end;

@+

jlen
Commenter la réponse de jlen100

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.