Etiket arşivi: seri port

Synapse ile Seri Port Arayüzü

Lazarus/Freepascal ile seri port kullanımına basit bir örnek olarak bu notları paylaşıyorum.
Aşağıdaki notları Lazarus ile yaptığım bir veri toplama ünitesi bağlantı programından özetledim.
Synapse kütüphanesi hakkında gerekli bilgilere şu bağlantıdan ulaşılabilir:
https://wiki.freepascal.org/Synapse

Synapse, kurulması gereken bir kütüphane değil. Kütüphane klasörünü bir yere olduğu gibi kopyalayıp klasörün içindeki laz_synapse.lpk kütüphane projesi dosyasını açmış ve derlemiş olmamız yeterli. (Package > Open Package File (.lpk) seçimi ile yapılıyor)

Bu kütüphaneyi kullanacak bir uygulama projesi başlattığında,
Project Inspector’de sol üstteki ADD butonuna ve sonra New Requirement’e tıkla.
(Project inspector görünür değilse Project > Project Inspector seçimi yap, bunun tuş kısayolu yok!)
Gelen listede laz_synapse seçilebilir olmalı, (çünkü derlemiştik) onu seç.
Project Inspector’de artık projenin Required Packages dalının altında laz_synapse da gözüküyor olmalı.
Artık, projenin seri port kullanması gereken ünitesinin uses kısmında synaser unit’ini ekleyebilirsin.
Bu ünite görünür bir form da içeriyorsa (ayarlar için içerse iyi olur) o formun private alanında programın ana mantığının bilmeye ihtiyaç duyacağı olayları tanımla:

 FonDeviceConnect : TNotifyEvent; 
 FonSpecificPacketArrived : TNotifyEvent; 

Olaylar, belli bir paketin gelmesi ya da belli bir işlemin tamamlanması gibi durumlara ait olabilirler. Kaç tane olacağı uygulamanın karmaşıklığına kalmış.

Ardından, gelen veriyi almak için bir dinleyici nesne yaz. Bu, doğal olarak bir TThread ardılı olacak:

   TCommListenThread = class(TThread)
    private
      fCom: TBlockSerial;
      fcmdTerminate: boolean;
      fPacketTimeout: integer;
      fErrorDesc: String;
      frxdata: AnsiString;
      rxstate: TRXState;
      rxstring: AnsiString;
      Frxsize: integer;

      FRXEvent : TNotifyEvent;
      FErrEvent: TNotifyEvent;

    public
      constructor Create(Acom: TBlockSerial); overload;
      procedure Start_Listening(poll_period: integer);
      procedure Stop_Listening;
      property onRX : TNotifyEvent read FRXEvent write FRXEvent;
      property onErr: TNotifyEvent read FErrEvent write FErrEvent;
      property ReceivedString: AnsiString read frxdata;
      property rxsize: integer read Frxsize;
    protected
       procedure Execute; override;
  end;


Overload ettiğimiz Create constructor’unda kullanıcıdan TBlockSerial tipli bir seri port erişim nesnesi vermesini istiyoruz. Bu, ünite içinde tanımlı bir değişken olmalı ve bu dinleme nesnesinden önce yaratılmış olmalı.
Thread’in execute’u içinde state’ler tanımlayarak veri gelirken anında bazı işler yapabiliriz ama seri portta buna çok gerek yok. O yüzden execute döngüsü şöyle olsa şimdilik yeterli:

procedure TCommListenThread.Execute;
begin
  while not(fcmdTerminate) do
  begin
    case rxstate of
    //////////////////////////////////////////////////////////////////////////
    RX_IDLE:
    begin

    end;
    //////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////
    RX_LISTEN:
    begin
      // gelen veriyi bekle:
      rxstring:= Fcom.RecvPacket(fPacketTimeout);
      if Fcom.LastError = ErrTimeout then continue;
      // hata döndüyse
      if Fcom.LastError <> sOK then
      begin
        rxstate:= RX_ERROR;
      end else
      begin
        // rxdata string'ini kullanıcıyla property olarak paylaştığımız
        // nesne alanına kopyala:
        Frxsize:= Length(rxstring);
        frxdata:= Copy(rxstring,0, Frxsize);
        if Assigned(onRX) then onRX(self);
      end;
    end;
    //////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////
    RX_ERROR:
    begin
      FErrorDesc:= Fcom.LastErrorDesc;
      Fcom.Flush;
      if Assigned(onErr) then onErr(self);
      rxstate:= RX_IDLE;
      self.Suspended:= true;
    end;
    //////////////////////////////////////////////////////////////////////////
    end;
  end;
 end;   

rxstring, içine com dosyasından veri gelmesini beklediğimiz temel değişken. Veri geldiğinde bunu frxdata ‘ya kopyalayıp dinleyiciyi serbest bırakıyoruz.
frxdata da ReceivedString property’si ile okunabilen bir değişken.
Böylece, onRX olayı ile tetiklenen bir fonksiyon ReceivedString’de gelen veriyi hazır bulacak.

Bir veri alındığında, dinleyici thread onRX olayını tetikleyecek. Bu olayı karşılayan bir procedure olmalı.
Bu procedure’ü yukarıda bahsettiğim, formun private alanında tanımlı bir fonksiyona bağla. Bu bağlama işini de
haberleşmeyi başlatmak (/ayarlamak) için kullanılan bir kullanıcı buton tıklamasının içine yaz. Burada nesneler de başlatılıyor olmalı:

  com:= TBlockSerial.Create;
  data_processor:= TCommListenThread.Create(com);
  data_processor.onRX:= @fcomm.Process_RXData;
  data_processor.onRX:= @commform.Process_RXData;
  data_processor.onErr:= @commform.Process_CommError;   

Process_RXData, seri porta bir veri geldiğinde, her ne gelirse gelsin çalışacak fonksiyondur.
Gelen veriyi data_processor thread’inin ReceivedString property’sinden okuyup rxdata dizisine (ünite değişkeni) yazarak işe başla:

  len:= Length(data_processor.ReceivedString);
  SetLength(rxdata, len);
  for i:=1 to len do
  begin
    x:= Byte(data_processor.ReceivedString[i]);
    rxdata[i-1]:= x;
  end;   

Buradan sonrası daha uygulamaya has kısım.
Paket tümleşikliğini (integrity) burada kontrol edip hata varsa işi sonlandırabilirsin.
Paket iyi gözüküyorsa beklediğin paketlerden hangisi olduğuna burada bakıp bir yönlendirme mantığı çalıştırabilirsin.
Başta yazmış olduğum
FonSpecificPacketArrived gibi olayları bu ayrımı yaptıktan sonra çağırabilirsin.
Bağlantı işleminin başında, bu olayların çağıracağı ana program procedure’lerini de bunlara bağlamış olmalısın (farklı ünitelerde çalışan farklı procedure’ler).