cs_CHARLI78
Messages postés8Date d'inscriptiondimanche 9 avril 2006StatutMembreDernière intervention14 avril 2006
-
11 avril 2006 à 09:13
jlen100
Messages postés1606Date d'inscriptionsamedi 10 juillet 2004StatutMembreDernière intervention25 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).
jlen100
Messages postés1606Date d'inscriptionsamedi 10 juillet 2004StatutMembreDernière intervention25 juillet 201413 11 avril 2006 à 11:23
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
jlen100
Messages postés1606Date d'inscriptionsamedi 10 juillet 2004StatutMembreDernière intervention25 juillet 201413 11 avril 2006 à 12:17
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
cs_shining
Messages postés304Date d'inscriptionlundi 30 décembre 2002StatutMembreDernière intervention10 mars 2012 11 avril 2006 à 22:17
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 !!!
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
jlen100
Messages postés1606Date d'inscriptionsamedi 10 juillet 2004StatutMembreDernière intervention25 juillet 201413 11 avril 2006 à 22:54
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
Vous n’avez pas trouvé la réponse que vous recherchez ?
cs_CHARLI78
Messages postés8Date d'inscriptiondimanche 9 avril 2006StatutMembreDernière intervention14 avril 2006 11 avril 2006 à 12:06
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?
cs_CHARLI78
Messages postés8Date d'inscriptiondimanche 9 avril 2006StatutMembreDernière intervention14 avril 2006 12 avril 2006 à 10:30
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...
jlen100
Messages postés1606Date d'inscriptionsamedi 10 juillet 2004StatutMembreDernière intervention25 juillet 201413 12 avril 2006 à 10:54
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.
jlen100
Messages postés1606Date d'inscriptionsamedi 10 juillet 2004StatutMembreDernière intervention25 juillet 201413 12 avril 2006 à 11:17
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:=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
cs_CHARLI78
Messages postés8Date d'inscriptiondimanche 9 avril 2006StatutMembreDernière intervention14 avril 2006 13 avril 2006 à 09:28
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.
jlen100
Messages postés1606Date d'inscriptionsamedi 10 juillet 2004StatutMembreDernière intervention25 juillet 201413 13 avril 2006 à 10:04
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.
cs_CHARLI78
Messages postés8Date d'inscriptiondimanche 9 avril 2006StatutMembreDernière intervention14 avril 2006 13 avril 2006 à 16:24
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.
jlen100
Messages postés1606Date d'inscriptionsamedi 10 juillet 2004StatutMembreDernière intervention25 juillet 201413 13 avril 2006 à 16:56
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é.
cs_CHARLI78
Messages postés8Date d'inscriptiondimanche 9 avril 2006StatutMembreDernière intervention14 avril 2006 13 avril 2006 à 18:19
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.
cs_shining
Messages postés304Date d'inscriptionlundi 30 décembre 2002StatutMembreDernière intervention10 mars 2012 13 avril 2006 à 20:35
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" !!!
jlen100
Messages postés1606Date d'inscriptionsamedi 10 juillet 2004StatutMembreDernière intervention25 juillet 201413 13 avril 2006 à 21:04
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)
cs_CHARLI78
Messages postés8Date d'inscriptiondimanche 9 avril 2006StatutMembreDernière intervention14 avril 2006 14 avril 2006 à 07:57
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.