Odbiór danych z RS232 poprzez ComPort

0

Witam.
Mam problem z odbiorem danych z RS232 za pomocą komponentu ComPort. Za każdym razem, gdy do urządzenia wyślemy jakąkolwiek komendę zwraca ono cyfrę 2. Na próbę napisałem taki prosty program. Na początku wysyłamy pierwszą komendę a następną (z resztą cały czas to samo) wysyłamy dopiero, gdy urządzenia odeśle nam znak 2 - i tak robimy 10 razy. Odbiór zrealizowałem w zdarzeniu ComPort1RxChar
Cały program wygląda tak:

 unit Test;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, CPort, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ComPort1: TComPort;
    Button2: TButton;
    Button3: TButton;
    procedure Button2Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure ComPort1RxChar(Sender: TObject; Count: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
    stop : byte;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button2Click(Sender: TObject);
begin
  if ComPort1.Connected then
  begin
    ComPort1.Close;
    button2.Caption:='Połącz';
  end else begin
    ComPort1.Open;
    button2.Caption:='Rozłącz';

  end;
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
ComPort1.ShowSetupDialog;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
st : string;
licznik : integer;
begin
for licznik := 1 to 10 do
begin
if licznik > 1 then
while stop <> 1 do
begin
end;
stop := 0;
st:=#27 + '008couTEST'  + #27 ;
if ComPort1.Connected then
ComPort1.WriteStr(st);
end;
end ;



procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer);
var
int: byte;
begin
  ComPort1.Read(int, count);
   if int = 2 then
   stop:=1;
end;


end.

Pętla While jest wprowadzona tutaj do testu. Jednak program ten, nie działa. Raz wyśle to co powinien, potem się zawiesza, a teoretycznie nie powinien, bo zmienna Stop powinna przyjąć wartość 1. Robiłem już testy, które polegały na dodaniu w zdarzeniu ComPort1RxChar instrukcji która dane z RS-a wpisywała do memo. Podczas gdy powinny być one wpisane po każdym przejściu pętli for to w rzeczywistości następowało to naraz dopiero po ostatnim przejściu pętli. Wygląda to tak jakby zdarzenie ComPort1RxChar nie było wywoływane za każdym razem gdy program odbierze jakiś znak. Co może być tego przyczyną?

0

Nie mam za dużego doświadcxenia z TComPort. Poza tym jestem po urodzinach kolegi i znacznej ilości Tyskich. Ale jak robiłem kiedyś program z tym komponentem dawno temu, to posłużyłem się zdarzeniem OnRxBuf. I nie było problemów. Przykładowy kod poniżej.

Ale pewnie ktoś jeszcze na trzeźwo coś tutaj doradzi być może lepszego. Tylko zachodzę w głowę, że do tej pory nikt jednak nie raczył odpisać. Może jednak to, że mamy weekend i mistrzostwa w piłkę kopaną w Brazylii - są przyczyną.

procedure TMainForm.VictronComPortRxBuf(Sender : TObject; const Buffer; Count : Integer);
var
  Edt : TEdit;
  S, LabStr, ValStr : string;
  Cnt, I, J, Idx, X, V : integer;
begin
  if VictronSL = nil then
  begin
    VictronSL := TStringList.Create;
  end;
  SetLength(S, Count);
  Move(Buffer, S[1], Count);
  if VictronSL <> nil then
  begin
    if Copy(S, 1, Length(CRLF)) = CRLF then
    begin
      VictronSL.Clear;
      VictronWholeStr := '';
    end;
    VictronWholeStr := VictronWholeStr + S;
    VictronSL.Text := VictronWholeStr;
    Cnt := 0;
    for I := 0 to VictronSL.Count - 1 do
    begin
      X := Pos(Victron_Label_separator, VictronSL[I]);
      if X > 0 then
      begin
        LabStr := Copy(VictronSL[I], 1, X - 1);
        ValStr := Copy(VictronSL[I], X + Length(Victron_Label_separator), MaxInt);
        Idx := VictronLabToIndex(LabStr);
        if Idx > 0 then
        begin
          if TryStrToInt(ValStr, V) then
          begin
            Edt := TEdit(Self.FindComponent(Victron_Edits_Base_Name + IntToStr(Idx)));
            if Edt <> nil then
            begin
              Cnt := Cnt + 1;
              case Idx of
                1, 2, 3, 4, 7, 8, 9, 12, 13, 14 :
                  begin
                    Edt.Text := FormatFloat('0.000', V * 0.001);
                  end;
                5 :
                  begin
                    Edt.Text := FormatFloat('0.0%', V * 0.1);
                  end;
                6 :
                  begin
                    Edt.Text := FormatFloat('0.00h', V * 0.01);
                  end;
                15 :
                  begin
                    Edt.Text := FormatFloat('0.000', V / 3600);
                  end;
              else
                begin
                  Edt.Text := ValStr;
                end;
              end;
              if Cnt = Max_Victron_Edits_Count - Victron_Empty_Edits_Count then
              begin
                VictronCsvDataStr := '';
                for J := 1 to Max_Victron_Edits_Count do
                begin
                  Edt := TEdit(Self.FindComponent(Victron_Edits_Base_Name + IntToStr(J)));
                  if Edt <> nil then
                  begin
                    if Edt.Text <> '' then
                    begin
                      VictronCsvDataStr := VictronCsvDataStr + Edt.Text + Default_Separator;
                    end;
                  end;
                end;
                if VictronCsvDataStr <> '' then
                begin
                  if VictronCsvDataStr[Length(VictronCsvDataStr)] = Default_Separator then
                  begin
                    Delete(VictronCsvDataStr, Length(VictronCsvDataStr), 1);
                    VictronCsvDataStr := VictronCsvDataStr + CRLF;
                  end;
                end;
                VictronTimer.Enabled := True;
                if VictronTimer.Tag = 0 then
                begin
                  VictronTimer.Tag := 1;
                  if @VictronTimer.OnTimer <> nil then
                  begin
                    VictronTimer.OnTimer(VictronTimer);
                  end;
                end;
              end;
            end;
          end;
        end;
      end;
    end;
  end;
end;

P.S.: Może powyższe jest mocno przekombinowane, ale w projekcie który realizowałem sprawdziło się jak należy i trzeba było kombinować w ten sposób by urządzenie docelowe ogarnać tak jak sobie ktoś założył.

0
Znawca tematu napisał(a):

Witam.
Mam problem z odbiorem danych z RS232 za pomocą komponentu ComPort. Za każdym razem, gdy do urządzenia wyślemy jakąkolwiek komendę zwraca ono cyfrę 2. Na próbę napisałem taki prosty program. Na początku wysyłamy pierwszą komendę a następną (z resztą cały czas to samo) wysyłamy dopiero, gdy urządzenia odeśle nam znak 2 - i tak robimy 10 razy. Odbiór zrealizowałem w zdarzeniu ComPort1RxChar

Widzę drobną, choć istotną nieścisłość: urządzenia zwraca znak '2' jako ASCII, czy binarnie? Twój kod obsługuje odbiór 2-ki binarnie. Co do kod, to podstawowym błędem jest wysyłanie kolejnych komend w pętli While w procedurze Button3Click. Nie zapewni to urządzeniu zareagować (przetworzyć, odpowiedzieć) na ostatnio wysyłaną komendę w pętli While w procedurze Button3Click, a już dostaje następną. Powinieneś kolejne komendy wysyłać dopiero po odebraniu odpowiedzi na ostatnio wysyłaną komendę w procedurze ComPort1RxChar, np.

procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer);
var
int: byte;
begin
  ComPort1.Read(int, count);
   if jakis_warunek_w_odpowiedzi then
   begin
     //wysyłanie dalszych komend
   end
  else
  begin
    //koniec wysyłania komend
  end;
end; 

Sorki, piszę bardzo skrótowo, ale bardzo się spieszę ;)

0
marogo napisał(a):

Widzę drobną, choć istotną nieścisłość: urządzenia zwraca znak '2' jako ASCII, czy binarnie? Twój kod obsługuje odbiór 2-ki binarnie. Co do kod, to podstawowym błędem jest wysyłanie kolejnych komend w pętli While w procedurze Button3Click. Nie zapewni to urządzeniu zareagować (przetworzyć, odpowiedzieć) na ostatnio wysyłaną komendę w pętli While w procedurze Button3Click, a już dostaje następną. Powinieneś kolejne komendy wysyłać dopiero po odebraniu odpowiedzi na ostatnio wysyłaną komendę w procedurze ComPort1RxChar, np.

Urządzenie wysyła dwóję oczywiście binarnie. Pętla While właśnie została tutaj wykorzystana (tylko tak dla przykładu) żeby czekać, aż urządzenie zwróci znak gotowości. Zmienna Stop ma wartość 0 więc warunek stop<>1 jest spełniony i program stoi w miejscu(wykonuje pustą pętlę). Gdy Zostanie odebrany przez Rs232 znak 2 to zmienna stop przyjmuje wartość 1 i warunek nie jest spełniony, a więc program leci dalej. Wysyłanie jest realizowane 10 razy w pętli for. Jak już pisałem ta pętla while została tutaj wykorzystana żeby tak szybko przedstawić o co mi chodzi. Docelowo wykonam to zapewne inaczej.
Zaraz spróbuję coś z kodem kolegi olesio. Mam jednak wrażenie, że jest jakiś problem z szybkim odbieraniem i wysyłaniem przez RS. Mianowicie gdy wysyłamy jakąś komendę to informację zwrotną dostajemy po kilku milisekundach. Próbowałem zrealizować odbiór w osobnym wątku i gdy program miał tylko odebrać jakąś informację było ok. Gdy jednak miał już coś wysłać przez RS-a i za chwilę odebrać(w osobnym wątku) odpowiedź to nie chciało to już działać.

0
Znawca tematu napisał(a):

Pętla While jest wprowadzona tutaj do testu. Jednak program ten, nie działa. Raz wyśle to co powinien, potem się zawiesza, a teoretycznie nie powinien, bo zmienna Stop powinna przyjąć wartość 1.

Po kliknięciu przycisku Button3 program się zawiesza, bo wpada w pustą pętlę While (co zapewne niepotrzebnie bardzo obciąża procesor), co dodatkowo uniemożliwia zakończenie procedury Button1Click, przez co program nie może wykonać procedury odbioru danych z urządzenia ComPort1RxChar. Gdyby chociaż w pętli While było Application.ProcessMessages, pozwoliłoby to przetworzyć kolejkę komunikatów, m.in. zdarzenie ComPort1RxChar, a tak, program praktycznie wisi w pętli While.
Tak jak napisałem wcześniej, kolejne wysyłanie komend powinno odbywać się dopiero po odbiorze pierwszej komendy, ale w procedurze ComPort1RxChar (jako reakcja na odpowiedź urządzenia). Nie ma potrzeby stosowania dodatkowego wątku, bo jak być sobie popatrzył w kod źródłowy ComPort-u, to każde zdarzenie w tym komponencie to już jest osobny wątek. Warto też użyć timera odmierzającego timeout odpowiedzi urządzenia, bo jeśli z jakiegoś powodu (np. brak połączenia) urządzenie nie odpowie (np. w ciągu 3 s), to program powinien zgłosić takie zdarzenie.

0

W pętli miało się znaleźć jeszcze sleep (xx) żeby trochę ją spowolnić. Źle trochę zrozumiałem zasadę działania zdarzeń. Programy na komputer piszę sporadycznie, więcej piszę na mikroprocesory i tam mam coś takiego jak przerwanie. Ma ono znaczenie nadrzędne nad programem i adres aktualnie wykonywanej procedury jest odkładany na stos, a program "leci" wykonać to co w przerwaniu. Myślałem(teraz już wiem, że błędnie), że tutaj działa to na podobnej zasadzie.
Faktycznie "magiczne" Application.ProcessMessages powoduje, że program działa jak powinien.
Co do timeout-a to zapewne coś takiego będzie. To jest tylko mała część z programu, jaki muszę stworzyć.
Dziękuję Wam za dotychczasową pomoc. Muszę już tylko poskładać to w jakąś sensowną całość bez tej pętli While.

1 użytkowników online, w tym zalogowanych: 0, gości: 1