RS 232C – praktyczne
                           programowanie. Od Pascala
                           i C++ do Delphi i Buildera.
                           Wydanie III
                           Autor: Andrzej Daniluk
                           ISBN: 978-83-246-0778-5
                           Format: B5, stron: 504




                           Na uczelniach, w szko³ach i biurach pojawia siê coraz wiêcej zaawansowanych
                           urz¹dzeñ komputerowych pod³¹czanych przez port szeregowy. Czy koniecznie trzeba
                           p³aciæ wysokie stawki informatykom, aby wykorzystaæ pe³niê mo¿liwoœci tych
                           nowoczesnych narzêdzi? Na szczêœcie nie. Obs³uga transmisji szeregowej przy u¿yciu
                           standardu RS 232C mo¿e byæ na tyle ³atwa, ¿e uczniowie, studenci, nauczyciele,
                           pracownicy naukowi czy in¿ynierowie mog¹ samodzielnie tworzyæ potrzebne im
                           oprogramowanie.
                           Dziêki ksi¹¿ce „RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi
                           i Buildera. Wydanie III” tak¿e i Ty szybko nauczysz siê pisaæ programy steruj¹ce
                           urz¹dzeniami pod³¹czanymi przez port szeregowy. Dowiesz siê, jak dzia³a transmisja
                           asynchroniczna oraz czym jest standard RS 232C. Poznasz interfejs RS 232C dla
                           systemu Windows i nauczysz siê go u¿ywaæ w œrodowiskach programistycznych
                           Builder i Delphi, co pozwoli Ci pisaæ potrzebne oprogramowanie w jêzyku Pascal lub
                           C++. Najnowsze, poprawione wydanie zawiera jeszcze wiêcej przyk³adów, dziêki którym
                           b³yskawicznie bêdziesz móg³ sprawdziæ nabyt¹ wiedzê w praktyce.
                               • Standard RS 232C
                               • Transmisja asynchroniczna
                               • Obs³uga RS 232C w systemach MS-DOS i Windows
                               • Wykorzystanie elementów interfejsu Windows API w œrodowiskach Builder i Delphi
                               • Testowanie programów do obs³ugi transmisji szeregowej
                               • Tworzenie aplikacji wielow¹tkowych
                               • Narzêdzia graficzne
                               • Przyk³adowe aplikacje i ich analiza
                               • Specyfikacje najwa¿niejszych funkcji
Wydawnictwo Helion
ul. Koœciuszki 1c
44-100 Gliwice
tel. 032 230 98 63
e-mail: helion@helion.pl
Spis treści                                                                                                                                       5




Spis treści
                Przedmowa do wydania trzeciego ...................................................... 9
                Wprowadzenie ................................................................................ 11
Rozdział 1. Definicja interfejsu ......................................................................... 15
Rozdział 2. Nowoczesna transmisja asynchroniczna oraz standard RS 232C ...... 19
                RTS-CTS handshaking .................................................................................................... 24
                Konwertery interfejsu RS 232C ...................................................................................... 28
                Konwertery USB/RS 232C .............................................................................................. 29
                    Właściwości portu konwertera .................................................................................. 31
                Protokół XON-XOFF ...................................................................................................... 33
                Protokół ENQ-ACK ........................................................................................................ 33
                Protokół ETX-ACK ......................................................................................................... 34
                Protokół SOH-ETX ......................................................................................................... 34
                Protokoły typu master-slave ............................................................................................ 34
                Rola oprogramowania a podstawowe funkcje interfejsu ................................................. 36
                Podsumowanie ................................................................................................................. 38
Rozdział 3. Jak testować programy do transmisji szeregowej? ........................... 39
                Mirror w MS-DOS ........................................................................................................... 39
                Terminal dla Windows .................................................................................................... 41
                Podsumowanie ................................................................................................................. 43
Rozdział 4. Transmisja szeregowa w MS-DOS .................................................... 45
                Borland C++ .................................................................................................................... 45
                Borland Pascal ................................................................................................................. 53
                   Funkcja 00h ............................................................................................................... 55
                   Funkcja 01h ............................................................................................................... 56
                   Funkcja 02h ............................................................................................................... 56
                   Funkcja 03h ............................................................................................................... 56
                Podsumowanie ................................................................................................................. 58
                Ćwiczenia ........................................................................................................................ 58
Rozdział 5. Programowa obsługa interfejsu RS 232C w Windows ...................... 59
                Typy danych Windows .................................................................................................... 61
                Proces projektowania oprogramowania ........................................................................... 64
                Wykorzystanie elementów Windows API w C++Builderze. Część I ............................. 64
                   Struktura DCB ........................................................................................................... 65
                   Funkcja CreateFile() .................................................................................................. 65
6     RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


        Funkcja GetCommState() .......................................................................................... 70
        Funkcja SetCommState() .......................................................................................... 71
        Funkcja CloseHandle() .............................................................................................. 71
    Testowanie portu szeregowego ....................................................................................... 74
        Struktura COMMPROP ............................................................................................ 78
        Funkcja GetCommProperties() ................................................................................. 82
        Struktura COMMCONFIG ....................................................................................... 88
        Funkcje GetCommConfig() i SetCommConfig() ...................................................... 88
        Funkcja CommConfigDialog() ................................................................................. 89
        Struktura COMMTIMEOUTS .................................................................................. 90
        Funkcje GetCommTimeouts() i SetCommTimeouts() .............................................. 91
    Nawiązanie połączenia. Wariant I ................................................................................... 91
        Segment inicjalizująco-konfiguracyjny ..................................................................... 92
        Segment wysyłający komunikaty. Funkcja WriteFile() ............................................ 92
        Segment odbierający komunikaty. Funkcja ReadFile() ............................................ 93
        Przykładowa aplikacja ............................................................................................... 94
    Nawiązanie połączenia. Wariant II .................................................................................. 97
        Funkcja SetupComm() .............................................................................................. 98
        Funkcja ClearCommError() ...................................................................................... 98
        Struktura COMSTAT .............................................................................................. 100
        Przykładowa aplikacja ............................................................................................. 102
        Zamknięcie portu komunikacyjnego ....................................................................... 106
    Nawiązanie połączenia. Wariant III .............................................................................. 107
        Funkcje GetCommMask() i SetCommMask() ........................................................ 107
        Funkcja WaitCommEvent() .................................................................................... 109
        Przykładowa aplikacja działająca w środowisku tekstowym .................................. 110
        Przykładowa aplikacja działająca w środowisku graficznym ................................. 118
    Nawiązanie połączenia. Wariant IV .............................................................................. 123
        Funkcja BuildCommDCB() .................................................................................... 123
        Funkcja BuildCommDCBAndTimeouts() .............................................................. 125
    Inne użyteczne funkcje .................................................................................................. 126
    Podsumowanie ............................................................................................................... 128
    Ćwiczenia ...................................................................................................................... 128
    Wykorzystanie elementów Windows API w C++Builderze. Część II .......................... 129
        Wysyłamy znak po znaku. Funkcja TransmitCommChar() .................................... 129
        Wysyłamy pliki. Funkcje _lopen, _lread(), _lwrite(), _lclose() .............................. 133
        Wykorzystanie komponentu klasy TTimer ............................................................. 143
        Aplikacja nie lubi milczeć. Funkcja GetLastError() ............................................... 162
        Break Time — czas oczekiwania aplikacji ............................................................. 167
        Podsumowanie ........................................................................................................ 176
        Ćwiczenia ................................................................................................................ 176
    Wykorzystanie elementów Windows API w Delphi. Część I ....................................... 177
        Testowanie portu szeregowego — inaczej .............................................................. 177
        Rekord TCOMMPROP ........................................................................................... 183
        Nawiązanie połączenia ............................................................................................ 191
        Przykładowe aplikacje ............................................................................................. 194
        Podsumowanie ........................................................................................................ 203
        Ćwiczenia ................................................................................................................ 203
    Wykorzystanie elementów Windows API w Delphi. Część II ...................................... 203
        Wysyłamy znak po znaku ....................................................................................... 204
        Wysyłamy pliki ....................................................................................................... 209
        Timer w Delphi ....................................................................................................... 224
    Podsumowanie ............................................................................................................... 238
    Ćwiczenia ...................................................................................................................... 239
Spis treści                                                                                                                                        7


Rozdział 6. Aplikacje wielowątkowe ............................................................... 241
               Najważniejszy jest użytkownik ..................................................................................... 242
                  Użytkownik steruje programem .............................................................................. 242
                  Możliwość anulowania decyzji ............................................................................... 243
                  Możliwość odbioru komunikatu nawet w trakcie wysyłania danych ..................... 243
                  Możliwość wysłania odrębnej informacji w trakcie transmisji pliku ..................... 243
               Delphi ............................................................................................................................ 244
                  Funkcja BeginThread() ........................................................................................... 244
                  Konkurencja dla Timera .......................................................................................... 256
                  Klasa TThread ......................................................................................................... 264
               Wielowątkowość i DLL-e ............................................................................................. 272
               C++Builder .................................................................................................................... 280
                  Zamiast Timera ....................................................................................................... 289
                  Zamiast Timera. Inny sposób .................................................................................. 296
                  Klasa TThread ......................................................................................................... 304
               Podsumowanie ............................................................................................................... 315
               Ćwiczenia ...................................................................................................................... 315
Rozdział 7. Wykorzystanie niektórych narzędzi graficznych .............................. 317
               Komponent klasy TChart ............................................................................................... 318
               Podsumowanie ............................................................................................................... 328
               Ćwiczenia ...................................................................................................................... 328
Rozdział 8. Przykładowe aplikacje wykorzystywane
            w systemach pomiarowych ........................................................... 329
               Kontroler temperatury ................................................................................................... 330
               Aplikacja obsługująca kilka urządzeń ........................................................................... 347
               Programowanie inteligentne .......................................................................................... 358
                  Brak powtarzalności kodu ....................................................................................... 359
                  Czytelność kodu ...................................................................................................... 360
                  Łatwość testowania ................................................................................................. 364
               Podsumowanie ............................................................................................................... 366
               Ćwiczenia ...................................................................................................................... 366
Rozdział 9. Tworzenie komponentów .............................................................. 369
               Komponent TOpenSerialPort. Realizacja w Delphi ...................................................... 369
                  Testowanie komponentu ......................................................................................... 374
               Komponent TOpenSerialPort. Realizacja w C++Builderze .......................................... 380
                  Testowanie komponentu ......................................................................................... 386
               Komponenty aktywne .................................................................................................... 389
                  Kompilacja projektu zawierającego komponent aktywny ...................................... 393
                  Odczytywanie i modyfikacja wartości własności komponentu aktywnego ............ 395
               Komponenty w BDS 2006 ............................................................................................. 397
               Podsumowanie ............................................................................................................... 398
               Ćwiczenia ...................................................................................................................... 398
Rozdział 10. Modelowanie oprogramowania sterującego portem szeregowym .... 399
               Schematy dziedziczenia ................................................................................................. 400
               Ukrywanie konstruktora ................................................................................................ 405
               Interfejsy ........................................................................................................................ 409
               Delegowanie operacji .................................................................................................... 415
               Delegowanie realizacji interfejsu do własności ............................................................. 422
               Podsumowanie ............................................................................................................... 426
               Ćwiczenia ...................................................................................................................... 427
8                 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


Rozdział 11. POSIX .......................................................................................... 435
                Polecenie stty ................................................................................................................. 436
                    Ustawienia kontroli przesyłu danych (sterowanie transmisją) ................................ 437
                    Ustawienia wejściowe ............................................................................................. 437
                    Ustawienia wyjściowe ............................................................................................. 439
                    Ustawienia czasów oczekiwania ............................................................................. 439
                    Ustawienia lokalne .................................................................................................. 440
                    Specjalne znaki sterujące ........................................................................................ 441
                    Łączenie atrybutów ................................................................................................. 442
                Podstawowe funkcje obsługi portu szeregowego .......................................................... 442
                    Funkcja open() ......................................................................................................... 442
                    Funkcja read() ......................................................................................................... 443
                    Funkcja write() ........................................................................................................ 443
                    Funkcja close() ........................................................................................................ 443
                Struktura termios ........................................................................................................... 444
                    Funkcja tcgetattr() ................................................................................................... 448
                    Funkcja tcsetattr() .................................................................................................... 448
                    Funkcje cfgetispeed() i cfgetospeed() ..................................................................... 449
                    Funkcje cfsetispeed() i cfsetospeed() ...................................................................... 449
                    Funkcja tcflush() ..................................................................................................... 450
                    Funkcja tcdrain() ..................................................................................................... 451
                QNX ............................................................................................................................... 451
                    Funkcja dev_insert_chars() ..................................................................................... 453
                    Funkcja dev_ischars() ............................................................................................. 453
                    Funkcja dev_read() .................................................................................................. 454
                    Funkcja Receive() ................................................................................................... 455
                    Funkcja Send() ........................................................................................................ 455
                    Funkcja Creceive() .................................................................................................. 455
                    Funkcja Reply() ....................................................................................................... 456
                    Funkcja qnx_proxy_attach() ................................................................................... 456
                    Funkcja qnx_proxy_detach() ................................................................................... 456
                Podsumowanie ............................................................................................................... 457
                Ćwiczenia ...................................................................................................................... 457
Dodatek A Specyfikacja funkcji CreateFile() — operacje plikowe ................... 461
Dodatek B Specyfikacja struktur MODEMDEVCAPS, MODEMSETTINGS
          oraz funkcji GetCommModemStatus() ........................................... 467
                MODEMDEVCAPS ...................................................................................................... 467
                MODEMSETTINGS ..................................................................................................... 470
                GetCommModemStatus() .............................................................................................. 471
Dodatek C Transmisja asynchroniczna. Funkcje rozszerzone ........................... 473
                Funkcja WriteFileEx() ................................................................................................... 473
                Funkcja ReadFileEx() .................................................................................................... 474
                Funkcja FileIOCompletionRoutine() ............................................................................. 474
                Funkcja SleepEx() ......................................................................................................... 475
                Funkcja WaitForSingleObjectEx() ................................................................................ 475
Dodatek D Zamiana liczb z postaci dziesiętnej na binarną .............................. 477
Dodatek E       Funkcje CreateThread(), CreateMutex() i CreateSemaphore() ....... 481
                Skorowidz .................................................................................... 487
Rozdział 5.
Programowa obsługa
interfejsu RS 232C
w Windows
   Czwarte prawo Murphy’ego
       Gdy dojdziesz do wniosku, że są cztery sposoby, na jakie może się nie powieść
       dane przedsięwzięcie, i zabezpieczysz się przed nimi, rychło pojawi się piąta
       możliwość.
                               Murphy’s Law and other reasons why things go wrong!,
                                           Artur Bloch, Price Stern Sloan Inc. 1977.

   Rozdział ten ma za zadanie zapoznać Czytelnika ze sposobami konstrukcji algorytmów
   realizujących transmisję szeregową w środowisku Windows, które charakteryzuje się
   pewnymi cechami niemającymi odpowiedników w MS-DOS. Poznanie i umiejętne
   wykorzystanie tych cech sprawi, iż problem obsługi interfejsów szeregowych z poziomu
   Windows — uważany powszechnie za trudny — przestanie być dla nas tajemnicą.
   Pokażemy, w jaki sposób należy tworzyć aplikacje służące do programowej obsługi łącza
   szeregowego RS 232C zarówno w C++, C++Builderze, jak i w Delphi. Wśród programi-
   stów istnieje zauważalny podział na osoby programujące głównie w Delphi oraz na prefe-
   rujące Buildera lub ogólnie C++ dla Windows. Jednak zdaniem wielu osób uniwersalność
   jest jedną z tych cech, jakie powinny charakteryzować programistę. W rozdziale tym
   przybliżymy Czytelnikowi podobieństwa i różnice w sposobie konstrukcji algorytmów
   realizujących transmisję szeregową, pisanych w Delphi oraz Builderze.

   W dalszej części książki będziemy się spotykać z typami danych, których poznanie i zro-
   zumienie ma kluczowe znaczenie w projektowaniu aplikacji obsługujących urządzenia
   zewnętrzne. Zacznijmy od ich przypomnienia. W tabeli 5.1 przedstawiono porównanie
   podstawowych typów zmiennych wykorzystywanych w kompilatorach, które będą dla nas
   istotne. Większości z nich można używać zamiennie, pisząc zarówno w Delphi, jak
   i w C++Builderze.
60                RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


Tabela 5.1. Typy zmiennych stosowanych w Delphi oraz w C++Builderze
 Delphi         Rozmiar w bajtach    Znak +/–        Typ                      C++Builder
 ShortInt          1                                 Integer                  signed char
 SmallInt          2                                 Integer                  short
 LongInt           4                                 Integer
 Byte              1                 Bez znaku       Integer                  unsigned char
 Word              2                 Bez znaku       Integer                  unsigned short
 Integer           4                                 Integer                  int
 Cardinal          4                 Bez znaku       Integer                  unsigned int
 Boolean           1                 true/false                               bool
 ByteBool          1                 true/false                               unsigned char
                                     Bez znaku       Integer
 WordBool          2                 true/false                               unsigned short
                                     Bez znaku       Integer
 LongBool          4                 true/false
                                     Bez znaku       Integer
 AnsiChar          1                 1 znak ANSI     Character                char
 WideChar          2                 1 znak Unicode Character                 wchar_t
 Char              1                 Bez znaku       Character                char
 AnsiString      ≈3GB                ANSIChar        AnsiString               AnsiString
 String[n]      n = 1.255            ANSIChar        String                   SmallString<n>
 ShortString    255                  ANSIChar        String                   SmallString<255>
 String         255 lub ≈3GB         ANSIChar        AnsiString               AnsiString
 Single            4                                 Floating point number    float
                                                     (liczba
                                                     zmiennoprzecinkowa)
 Double            8                                 Floating point number    double
 Extended         10                                 Floating point number    long double
 Real              4                                 Floating point number    double
 Pointer           4                                 Generic pointer          void *
                                                     (wskaźnik ogólny,
                                                     adresowy)
 PChar             4                 Bez znaku       Pointer to characters    unsigned char *
 PAnsiChar         4                 Bez znaku       Pointer to ANSIChar      unsigned char *
 Comp              8                                 Floating point number    Comp


           Konstruując nasze programy, będziemy starali się jak najszerzej wykorzystywać standar-
           dowe zasoby Windows, w szczególności tzw. interfejs programisty Windows API (ang.
           Application Programming Interface). Jego umiejętne wykorzystanie umożliwi naszym
           aplikacjom błyskawiczne skonfigurowanie i uzyskanie dostępu do portu komunikacyjne-
           go. Błędem jest twierdzenie, że sama — nawet bardzo dobra — znajomość języka pro-
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                                 61


       gramowania wystarczy, żeby stworzyć poprawnie działający w Windows program. Otóż
       musimy zdawać sobie sprawę z faktu, o którym często się zapomina — niemożliwe jest
       napisanie udanej aplikacji mającej pracować w pewnym środowisku (czytaj — systemie
       operacyjnym) bez znajomości tego środowiska. Wiele już zostało powiedziane na temat
       dobrych i złych stron Windows, należy jednak pamiętać, że oferuje on nam swoją wizy-
       tówkę, ofertę współpracy, czyli API. Już nie wystarczy umiejętność wykorzystywania
       ulubionego kompilatora. Zasoby Delphi czy Buildera połączymy z zasobami systemu
       operacyjnego, a spoiwem będzie właśnie uniwersalne Windows API. Istnieje wiele
       warstw API używanych w zależności od potrzeb. W tym i dalszych rozdziałach zajmiemy
       się szeroko rozumianą warstwą komunikacyjną.

       Windows API korzysta ze specjalnego systemu nazewnictwa zmiennych, z tzw. notacji
       węgierskiej wprowadzonej przez Karoja Szimoniego. Zgodnie z nią do rdzenia nazwy
       zadeklarowanej zmiennej dodaje się przedrostek (ang. prefix). Chociaż istnieją pod tym
       względem pewne rozbieżności pomiędzy nazewnictwem Microsoftu i Borlanda, to
       jednak zapis taki bardzo ułatwia szybkie ustalenie roli zmiennej w programie oraz jej typ.
       W następnych rozdziałach będziemy się starali — wszędzie gdzie jest to możliwe —
       zachowywać samokomentujące się nazewnictwo API (większość nazw API będziemy
       traktować jako nazwy własne). Z doświadczenia wiadomo, że stosowanie takiej konwen-
       cji bardzo pomaga w studiowaniu plików pomocy. Oczywiście moglibyśmy silić się na
       oryginalność, wprowadzając własne zmienne, zrozumiałe tylko dla piszącego dany pro-
       gram — wówczas przykłady musiałyby być zapisane jako wręcz humorystyczna miesza-
       nina języków polskiego i angielskiego. Trzeba też przyznać, że byłby to bardzo skuteczny
       sposób zaciemnienia obrazu API. Zrozumienie znaczenia nazw tam stosowanych okaże
       się w przyszłości niezwykle cenne, gdyż API można czytać jak książkę. Aby pomóc Czy-
       telnikom, którzy nie zetknęli się dotąd z tymi pojęciami, w tabeli 5.2 przedstawiono ogól-
       ne zasady tworzenia niektórych przedrostków.

       Windows oferuje nam ponadto kilka typów danych, z których część tylko nieznacznie
       różni się sposobem zapisu w implementacjach Delphi i Buildera. Typy te mają najczę-
       ściej postać struktury lub klasy i są bardzo często wykorzystywane w warstwie komunika-
       cyjnej programów.



Typy danych Windows
       Nowoczesna idea programowania w Windows oparta na wykorzystaniu narzędzi pro-
       gramistycznych typu RAD, do których zaliczają się C++Builder oraz Delphi, pozwala
       programistom na maksymalne uproszczenie procesu tworzenia oprogramowania. Jednym
       z przykładów dążenia do zminimalizowania czasu tworzenia aplikacji jest zastosowanie
       w Windows pewnych bardzo zwartych w zapisie typów danych, które oczywiście mają
       swoje odpowiedniki w typach standardowych. W tabeli 5.3 zebrano najistotniejsze typy
       danych, którymi bardzo często posługują się programy Windows. Należy zdawać sobie
       sprawę z faktu, iż typy takie jak np. LPVOID i LPSTR nie są w dosłownym słowa tego zna-
       czeniu typami nowymi, tzn. od początku stworzonymi na potrzeby aplikacji Windows,
       gdyż zostały zdefiniowane w plikach nagłówkowych za pomocą instrukcji typedef po to,
       aby uprościć zapis niektórych standardowych typów danych. W tabeli 5.3 przedstawiono
       wybrane typy danych, którymi posługuje się API Windows.
62              RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


Tabela 5.2. Ogólne zasady tworzenia przedrostków według notacji węgierskiej
 Przedrostek     Skrót angielski               Znaczenie
 a                array                        Tablica
 b                bool                         Zmienna logiczna true lub false
 by               byte unsigned char           Znak (bajt)
 cb               count of bytes               Liczba bajtów
 ch               char                         Znak
 dw               double word                  Podwójne słowo
 evt              event                        Zdarzenie
 f                flag                         Znacznik
 fdw              flag of double word          Znacznik typu dw
 fn               function                     Funkcja
 h                handle                       Identyfikator (uchwyt)
 i                integer                      Typ całkowity 4-bajtowy
 id               (ID) identification          Identyfikacja
 in               input                        Wejście, dane wejściowe
 l                long int                     Typ całkowity długi 4-bajtowy
 lp               long pointer                 Wskaźnik typu long int
 lpc              long pointer to C-string     Wskaźnik typu long int do C-łańcucha
 lpfdw            long pointer to flag of dw   Wskaźnik typu lp do znacznika typu double word
 lpfn             long pointer to function     Wskaźnik typu lp do funkcji
 n                short or int                 Typ krótki lub całkowity
 np               near pointer                 Bliski wskaźnik (w środowisku 32-bitowym to samo
                                               co lp)
 out              output                       Wyjście, dane wyjściowe (przetworzone)
 p                pointer                      Wskaźnik (w środowisku 32-bitowym to samo co lp)
 pfn              pointer to function          Wskaźnik do funkcji
 que              queue                        Kolejka, bufor danych
 s (sz)           string                       Łańcuch znaków
 st               struct                       Struktura
 t                type                         Typ
 u                unsigned                     Bez znaku
 w                (word) unsigned int          Słowo
 wc              WCHAR                         Znak zgodny z Unicode
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                                    63


Tabela 5.3. Niektóre typy danych stosowane w Windows
 Typ Windows             Znaczenie
 BOOL                    int z dwoma wartościami TRUE oraz FALSE
 BYTE                    unsigned char
 DWORD                   unsigned long
 LPDWORD                 unsigned long *
 LONG                    long
 LPLONG                  long *
 LPCSTR                  const char *
 LPCTSTR                 unsigned const char *
 LPSTR                   char *
 LPVOID lub Pointer      void *
 LPCVOID                 const void *
 UINT                    unsigned int
 WORD                    unsigned short
 DWORD32                 32-bitowy typ całkowity bez znaku
 DWORD64                 64-bitowy typ całkowity bez znaku
 INT                     32-bitowy typ całkowity ze znakiem
 INT32                   32-bitowy typ całkowity ze znakiem
 INT64                   64-bitowy typ całkowity ze znakiem
 LONG32                  32-bitowy typ całkowity ze znakiem
 LONG64                  64-bitowy typ całkowity ze znakiem
 LONGLONG                64-bitowy typ całkowity ze znakiem

           Osobnym typem danych, bardzo często stosowanym w aplikacjach Windows, jest typ
           HANDLE. Jest on 32- lub 64-bitowym typem danych całkowitych oznaczającym tzw. uchwyt
           (ang. handle). Należy rozumieć, iż w rzeczywistości dane typu HANDLE nie obrazują jakichś
           tajemniczych uchwytów zakładanych na elementy aplikacji — są to po prostu 32- lub
           64-bitowe liczby identyfikujące określony zasób aplikacji, systemu operacyjnego lub
           samego komputera. Z tego względu dane typu HANDLE często wygodniej i zręczniej jest
           określać mianem identyfikatorów, których wartości przechowywane są w określonym
           miejscu w pamięci. Cechą charakterystyczną identyfikatorów jest to, iż jeśli na początku
           programu inicjuje się je określonymi wartościami, w momencie zakończenia pracy
           aplikacji lub jej fragmentu należy przydzieloną im pamięć odpowiednio zwalniać. W tym
           celu wykorzystuje się funkcję API Windows:
             BOOL CloseHandle(HANDLE hObject);

           z argumentem w postaci określonego identyfikatora.

           Zaopatrzeni w powyższą terminologię pójdźmy dalej i zobaczmy, do czego mogą nam być
           przydatne poszczególne struktury oraz funkcje interfejsu programisty — Windows API.
64          RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera



Proces projektowania oprogramowania
     Zanim przejdziemy do szczegółowego omawiania aspektów tworzenia programów obsłu-
     gujących port szeregowy w Windows, należy wybrać jedną z metod projektowania tego
     rodzaju aplikacji.

     Praktyka wskazuje, że dla pojedynczych użytkowników lub niewielkich organizacji
     dobrze sprawdza się metodologia oparta na programowaniu przyrostowym i iteracyjnym
     (ang. iterative and incremental development).

     W dalszej części książki będziemy korzystać z metody projektowania iteracyjnego. Takie
     podejście do zagadnienia sprawi, iż tworzone aplikacje oprócz wysokiej sprawności
     działania będą jeszcze miały dwie bardzo ważne i nieczęsto spotykane w literaturze cechy.
     Będą mianowicie:
          w pełni rozbudowywalne,
          łatwe do samodzielnej modyfikacji nawet przez osoby dopiero poznające zasady
          programowania w środowiskach Buildera i Delphi.

     Wszystkie prezentowane algorytmy będziemy się starali konstruować w ten sposób, aby
     pewne słynne twierdzenie wypowiedziane niegdyś przez Murphy’ego w omawianych
     programach nie miało zastosowania. Brzmi ono następująco:

     Twierdzenie o komplikacji procedur
          Każdą dowolnie skomplikowaną procedurę można skomplikować jeszcze bardziej.
          Twierdzenie odwrotne nie jest prawdziwe: nadzwyczaj rzadko się zdarza, aby
          skomplikowaną procedurę można było uprościć.
          Murphy’s Law and other reasons why things go wrong!, Artur Bloch, Price Stern
          Sloan Inc. 1977.



Wykorzystanie elementów
Windows API w C++Builderze. Część I
     Poznawanie tajników obsługi portu szeregowego w Windows rozpoczniemy, z czysto
     praktycznych względów, od pisania programów w C++Builderze. C++ ma składnię taką
     jak API, dlatego prościej nam będzie zapoznać się z budową funkcji oraz struktur ofero-
     wanych przez interfejs programisty. Ułatwi to też zrozumienie, w jaki sposób i w jakiej
     kolejności należy umieszczać je w programie.
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                                    65


Struktura DCB
       Fundamentalne znaczenie ma struktura kontroli urządzeń zewnętrznych DCB (ang. Device
       Control Block). W Windows struktura DCB w pewnym sensie odpowiada funkcji 00h
       przerwania 14h BIOS-u. Udostępnia nam jednak nieporównywalnie większe możliwości
       programowej obsługi łącza szeregowego; umożliwia bezpośrednie programowanie reje-
       strów układu UART. W tabelach 5.4 oraz 5.5 przedstawiono specyfikację bloku kontroli
       urządzeń zewnętrznych DCB.

       Większość pól tej struktury to pola jednobitowe. fDtrControl, fRtsControl są polami
       dwubitowymi. Aktualnie nieużywane w XP pole fDummy2 jest siedemnastobitowe. W per-
       spektywie, wraz z wReserved oraz wReserved1, będzie wykorzystane na potrzeby innych
       protokołów komunikacyjnych. W Windows API blok kontroli urządzeń deklarowany jest
       w sposób następujący:
          typedef struct _DCB {
                  DWORD DCBlength;
                  ...
          } DCB;

       Deklaracja ta tworzy nowe słowo kluczowe typu DCB (struktura). Zalecane jest, aby przed
       użyciem tej struktury jako parametru do elementu DCBlength wpisać wartość sizeof(DCB).

          Strukturę tworzy zbiór logicznie powiązanych elementów, np. zmiennych lub (i) pól
          bitowych. Pole bitowe stanowi zbiór przylegających do siebie bitów, znajdujących się
          w jednym słowie. Adres struktury pobieramy za pomocą operatora referencji &, co
          umożliwia nam działania na jej składowych. Do struktury jako całości możemy odwołać
          się przez jej nazwę, zaś do poszczególnych jej elementów, czyli zmiennych oraz pól
          bitowych, przez podanie nazwy zmiennej reprezentującej strukturę oraz — po kropce —
          nazwy konkretnej zmiennej lub pola struktury, np.: dcb.fDtrControl = DTR_CONTROL_
          DISABLE. Operator składowych struktur "." jest lewostronnie łączny. Grupa związanych
          ze sobą zmiennych i pól bitowych traktowana jest jako jeden obiekt.


       Zanim przejdziemy do praktycznego zastosowania poznanych pól struktury DCB, musimy
       zapoznać się z czterema podstawowymi funkcjami Windows API służącymi do progra-
       mowej konfiguracji portów szeregowych. W dalszej części książki funkcji takich będzie
       przybywać, ale te przedstawione poniżej należy traktować jako najbardziej podstawowe.


Funkcja CreateFile()
       Jest to funkcja służąca do utworzenia i otwarcia pliku lub urządzenia. Już sama nazwa
       wskazuje, że może być wykorzystywana nie tylko do obsługi portu szeregowego. Teraz
       jednak będzie nas interesować tylko to konkretne zastosowanie. Specyfikacja zasobów
       funkcji CreateFile() najczęściej używanych do operacji plikowych zamieszczona jest
       w dodatku A. Funkcja ta da nam 32- lub 64-bitowy identyfikator danego portu przecho-
       wywany pod właściwością HANDLE, do którego będą adresowane wszystkie komunikaty.
66              RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


Tabela 5.4. Zmienne struktury DCB reprezentujące dopuszczalne parametry ustawień portu szeregowego
                                                               Wartość,
 Typ      Zmienna      Znaczenie
                                                               stała symboliczna
 DWORD    DCBlength    Rozmiar struktury                       Należy wpisać
 DWORD    BaudRate     Określenie prędkości transmisji (b/s)   CBR_110 CBR_19200 CBR_300 CBR_38400
                                                               CBR_600 CBR_56000 CBR_1200 CBR_57600
                                                               CBR_2400 CBR_115200 CBR_4800
                                                               CBR_128000 CBR_9600 CBR_256000
                                                               CBR_14400
 WORD     wReserved    Nieużywane                              0
 WORD     XonLim       Określenie minimalnej liczby bajtów     Domyślnie: 65 535; w praktyce XonLim
                       w buforze wejściowym przed              ustala się jako ½ rozmiaru deklarowanego
                       wysłaniem specjalnego znaku             wejściowego bufora danych
                       sterującego XON
 WORD     XoffLim      Określenie maksymalnej liczby           Domyślnie: 65535; w praktyce XoffLim
                       bajtów w buforze wejściowym             ustala się jako ¾ rozmiaru
                       przed wysłaniem specjalnego znaku       deklarowanego bufora wejściowego
                       sterującego XOFF
 BYTE     ByteSize     Wybór liczby bitów danych               5, 6, 7, 8
 BYTE     Parity       Określenie kontroli parzystości         EVENPARITY — parzysta;
                                                               MARKPARITY — bit parzystości stale
                                                               równy 1;
                                                               NOPARITY — brak kontroli;
                                                               ODDPARITY — nieparzysta
 BYTE     StopBits     Wybór bitów stopu                       ONESTOPBIT — 1 bit stopu;
                                                               ONE5STOPBITS — w przypadku słowa
                                                               5-bitowego bit stopu wydłużony o ½;
                                                               TWOSTOPBITS — 2 bity stopu
 char     XonChar      Określenie wartości znaku XON           Standardowo (char) DC1, dziesiętnie: 17
                       dla nadawania i odbioru (wysłanie
                       znaku przywraca transmisję)
 char     XoffChar     Określenie wartości znaku XOFF          Standardowo (char) DC3, dziesiętnie: 19
                       dla nadawania i odbioru (wysłanie
                       XOFF wstrzymuje transmisję do czasu
                       odebrania znaku XON)
 char     ErrorChar    Określenie wartości znaku               Opcjonalnie: 0 lub SUB
                       zastępującego bajty otrzymane
                       z błędem parzystości
 char     EofChar      Określenie wartości znaku końca         Opcjonalnie: 0
                       otrzymanych danych
 Char     EvtChar      Określenie wartości znaku               Opcjonalnie: 0
                       służącego do sygnalizowania
                       wystąpienia danego zdarzenia
 WORD     wReserved1   Obecnie nieużywane
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                                        67


Tabela 5.5. Pola bitowe reprezentujące dopuszczalne wartości znaczników sterujących struktury DCB
                                                             Wartość, znaczenie,
 Typ      Pole bitowe         Właściwości pola
                                                             stała symboliczna
 DWORD    fBinary             Tryb binarny (Win API          TRUE
                              podtrzymuje jedynie ten
                              tryb transmisji danych)
 DWORD    fParity             Umożliwia ustawienie           TRUE — kontrola parzystości włączona;
                              sprawdzania parzystości        FALSE — bit parzystości nie jest
                              — sposobu reakcji na bit       sprawdzany
                              parzystości
 DWORD    fOutxCtsFlow        Umożliwia ustawienie           TRUE — jeżeli sygnał CTS jest
                              sprawdzania sygnału na linii   nieaktywny, transmisja jest
                              CTS w celu kontroli danych     wstrzymywana do czasu ponownej
                              wyjściowych                    aktywacji linii CTS;
                                                             FALSE — włączenie sygnału na linii
                                                             CTS nie jest wymagane do rozpoczęcia
                                                             transmisji
 DWORD    fOutxDsrFlow        Umożliwia ustawienie           TRUE — jeżeli sygnał DSR
                              sprawdzania sygnału na linii   jest nieaktywny, transmisja
                              DSR w celu kontroli danych     jest wstrzymywana do czasu ponownej
                              wyjściowych                    aktywacji linii DSR;
                                                             FALSE — włączenie sygnału na linii DSR
                                                             nie jest wymagane do rozpoczęcia
                                                             transmisji
 DWORD    fDtrControl         Specyfikacja typu kontroli     DTR_CONTROL_DISABLE / 0 — sygnał
                              sygnału DTR                    na linii DTR jest nieaktywny;
                                                             DTR_CONTROL_ENABLE / 1 — sygnał na linii
                                                             DTR jest aktywny;
                                                             DTR_CONTROL_HANDSHAKE / 2 — włączenie
                                                             potwierdzania przyjęcia sygnału DTR
                                                             — potwierdzenie musi być odebrane
                                                             na linii DSR. Używane w trybie
                                                             półdupleksowym. Ewentualne błędy
                                                             transmisji w tym trybie są usuwane
                                                             przez funkcję EscapeCommFunction()
 DWORD    fTXContinueOnXoff   Kontrola przerwania            TRUE — wymuszanie kontynuowania
                              transmisji w przypadku         transmisji nawet po wystąpieniu znaku
                              przepełnienia bufora           XOFF i wypełnieniu wejściowego bufora
                              wejściowego i ewentualnie      danych powyżej XoffLim bajtów;
                              wystąpienia znaków             FALSE — transmisja nie jest
                              XoffChar oraz XonChar
                                                             kontynuowana, dopóki bufor wejściowy
                                                             nie zostanie opróżniony do pułapu XonLim
                                                             bajtów i nie nadejdzie znak XON
                                                             potwierdzenia dalszego odbioru
 DWORD    fDsrSensitivity     Specyfikacja wykorzystania     TRUE — otrzymane bajty są ignorowane,
                              poziomu sygnału na linii DSR   o ile linia DSR nie jest w stanie wysokim;
                                                             FALSE — stan linii DSR jest ignorowany
68                RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


Tabela 5.5. Pola bitowe reprezentujące dopuszczalne wartości znaczników sterujących struktury DCB
— ciąg dalszy
                                                             Wartość, znaczenie,
 Typ      Pole bitowe         Właściwości pola
                                                             stała symboliczna
 DWORD    fInX                Programowe ustawienie          TRUE — znak XoffChar jest wysyłany,
                              protokołu XON-XOFF w czasie    kiedy bufor wejściowy jest pełny lub
                              odbioru danych                 znajduje się w nim XoffLim bajtów; znak
                                                             XonChar jest wysyłany, kiedy bufor
                                                             wejściowy pozostaje pusty lub znajduje
                                                             się w nim XonLim bajtów;
                                                             FALSE — XON-XOFF w czasie odbioru nie
                                                             jest ustawiony
 DWORD    fRtsControl         Specyfikacja kontroli          RTS_CONTROL_DISABLE / 0 — sygnał
                              sygnału na linii RTS           na linii RTS jest nieaktywny;
                                                             RTS_CONTROL_ENABLE / 1 — sygnał
                                                             na linii RTS jest aktywny;
                                                             RTS_CONTROL_HANDSHAKE / 2 — włączenie
                                                             potwierdzania przyjęcia sygnału RTS
                                                             (potwierdzenie musi być odebrane
                                                             na linii CTS). Używane w trybie
                                                             półdupleksowym. Sterownik podwyższa
                                                             stan linii RTS, gdy wypełnienie bufora
                                                             wejściowego jest mniejsze od ½. Stan
                                                             linii RTS zostaje obniżony, gdy bufor
                                                             wypełniony jest w ¾. Ewentualne błędy
                                                             transmisji w tym trybie usuwane są przez
                                                             funkcję EscapeCommFunction();
                                                             RTS_CONTROL_TOGGLE / 3 — linia RTS
                                                             jest w stanie wysokim, jeżeli są bajty
                                                             do transmisji i jest ona możliwa; po
                                                             opróżnieniu bufora komunikacyjnego
                                                             linia RTS pozostaje w stanie niskim
 DWORD    fOutX               Programowe ustawienie          TRUE — transmisja zostaje przerwana po
                              protokołu XON-XOFF w czasie    odebraniu znaku XoffChar i wznowiona
                              wysyłania danych               po otrzymaniu znaku XonChar;
                                                             FALSE — XON-XOFF w czasie wysyłania
                                                             nie jest ustawiony
 DWORD    fErrorChar          Umożliwia zastąpienie bajtów   TRUE — zastąpienie jest wykonywane,
                              otrzymanych z błędem           ponadto fParity musi być ustawione
                              parzystości znakiem            jako TRUE;
                              ErrorChar
                                                             FALSE — zastąpienie nie jest wykonane
 DWORD    fNull               Odrzucenie odebranych          TRUE — nieważne bajty zostaną
                              nieważnych lub                 odrzucone przy odbiorze;
                              uszkodzonych bajtów            FALSE — nieważne bajty nie będą
                                                             odrzucane
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                                           69


Tabela 5.5. Pola bitowe reprezentujące dopuszczalne wartości znaczników sterujących struktury DCB
— ciąg dalszy
                                                                Wartość, znaczenie,
    Typ      Pole bitowe          Właściwości pola
                                                                stała symboliczna
    DWORD    fAbortOnError        Ustawienie wstrzymywania      TRUE — wszelkie operacje nadawania
                                  operacji nadawanie-odbiór     i odbioru są wstrzymywane, zaś dalsza
                                  przy wykryciu błędu           komunikacja nie jest możliwa, dopóki
                                  transmisji                    błąd nie zostanie usunięty przez
                                                                wywołanie funkcji ClearCommError();
                                                                FALSE — nawet jeżeli wystąpi błąd,
                                                                transmisja jest kontynuowana — błąd
                                                                może być usunięty przez wywołanie
                                                                funkcji ClearCommError()
    DWORD    fDummy2              Zarezerwowane, nieużywane

            Ogólnie rzecz ujmując, przed rozpoczęciem czytania z portu szeregowego (lub innego
            urządzenia) należy o powyższym fakcie poinformować system operacyjny. Czynność tę
            określa się jako otwieranie portu do transmisji. Jednak zanim zaczniemy wykonywać
            jakiekolwiek operacje na porcie, system operacyjny musi sprawdzić, czy wybrany port
            komunikacyjny istnieje i czy w danym momencie nie jest już przypadkiem w jakiś sposób
            wykorzystywany. W przypadku uzyskania dostępu do portu system operacyjny przeka-
            zuje do aplikacji jego identyfikator. We wszystkich operacjach wejścia-wyjścia zamiast
            szczegółowej nazwy portu komunikacyjnego używa się właśnie jego identyfikatora.

            Składnia CreateFile() wygląda następująco1:
              HANDLE CreateFile(LPCTSTR lpFileName,
                                DWORD dwDesiredAccess,
                                DWORD ShareMode,
                                LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                                DWORD dwCreationDistribution,
                                DWORD dwFlagsAndAttributes,
                                HANDLE hTemplateFile);


               Niekiedy identyfikatory tego typu nazywa się uchwytami. Niestety, dosłowne prze-
               tłumaczenie angielskiego słowa handle jako uchwyt, np. handle of drawer — uchwyt,
               rączka szuflady, nie jest w pełni adekwatne. Właściwsze wydaje się utożsamianie
               handle z identyfikatorem (unikalną wartością zlokalizowaną w danym obszarze pa-
               mięci i skojarzoną z konkretnym portem komunikacyjnym, oknem czy plikiem). W po-
               tocznej angielszczyźnie handle może również oznaczać ksywę pozwalającą na szybką
               identyfikację danej osoby lub rzeczy. Koncepcja identyfikatorów nie jest niczym nowym,
               stosowano ją już w MS-DOS, jednak dopiero w Windows zyskała nową jakość.


            Na tym etapie naszych rozważań tylko trzy parametry powyższej funkcji są istotne dla
            kompletnej konfiguracji portu szeregowego. Wyjaśnimy teraz ich znaczenie.




1
    Pełna specyfikacja funkcji CreateFile() została zamieszczona w dodatku A.
70          RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


     Pierwszy parametr, lpFileName, jest wskaźnikiem do zadeklarowanego ciągu znaków
     zakończonego zerem (zerowym ogranicznikiem), tzw. null terminated string, lub do
     C-łańcucha (dokładniej: do pierwszego znaku tego łańcucha), w którym przechowywana
     będzie nazwa (wartość) portu. Z poprzednich rozdziałów pamiętamy, że ogólnie przyjęte
     jest stosowanie nazewnictwa portów szeregowych jako COMn (nazwy COMn znajdują
     się na liście nazw zastrzeżonych), gdzie n oznacza numer portu. Deklaracja numeru portu
     szeregowego, np. 2., będzie więc przedstawiać się w sposób bardzo prosty:
       LPCTSTR portName = "COM2";

     lub, co jest równoważne:
       unsigned const char   *portName = "COM2";

     Można też zmienną, pod którą przechowywać będziemy numer portu, zadeklarować
     w sposób tradycyjny, używając typu char. Deklaracja taka będzie w pełni poprawna:
       char portName[5] = "COM2";

     Parametr dwDesiredAccess typu DWORD umożliwia ustalenie rodzaju dostępu do portu
     szeregowego. Z praktycznego punktu widzenia najwygodniej jest ustalić rodzaj dostępu
     jako GENERIC_READ | GENERIC_WRITE (zapisuj do portu lub odczytuj z portu). Umożliwi
     nam to płynne wysyłanie i odbieranie komunikatów, co w pełni odpowiada półduplek-
     sowemu wariantowi transmisji. Jeżeli zechcemy korzystać jedynie z trybu simpleksowe-
     go, do dwDesiredAccess wystarczy przypisać jeden z wybranych rodzajów dostępu.

        Windows API posługuje się łańcuchami o długości większej niż 256 znaków. Aby
        przełamać to ograniczenie, zrezygnowano z zapamiętywania w pierwszym bajcie liczby
        określającej długość łańcucha znaków. W C-łańcuchach ostatnim znakiem, kończą-
        cym ciąg jest 0 (NULL lub heks. 00), którego nie należy mylić ze znakiem zero (48
        lub heks. 30). Stąd nazwa null terminated string.C-łańcuchy osiągają długość 65535
        znaków plus końcowy, tzw. NULL-bajt. Są one dynamicznie alokowane w pamięci, zaś
        ilość pamięci zajmowanej przez C-łańcuch jest automatycznie dostosowywana do
        jego długości, co w pełni odpowiada architekturze Windows.


     Parametrowi dwCreationDistribution należy przypisać właściwość OPEN_EXISTING —
     otwórz istniejący (port). Pozostałym przyporządkujemy następujące wartości:
       DWORD ShareMode = 0 (FALSE);
       LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL;
       DWORD dwFlagAndAttributes = 0 (FALSE);
       HANDLE hTemplateFile = NULL.


Funkcja GetCommState()
     Funkcja zwraca ustawienia portu ostatnio zapamiętane w strukturze DCB:
       BOOL GetCommState(HANDLE hCommDev, LPDCB lpdcb),
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                                   71


       gdzie:
            hCommDev jest identyfikatorem danego portu, CreateFile() zwraca nam ten
            identyfikator, a lpdcb jest wskaźnikiem do struktury DCB zawierającej informację
            o aktualnych ustawieniach parametrów łącza szeregowego.

       Funkcja GetCommState() (jak i wszystkie inne typu BOOL) zwraca wartość TRUE w przypad-
       ku pomyślnego jej wykonania, ewentualnie wartość FALSE w sytuacji przeciwnej.


Funkcja SetCommState()
       Wybrany przez nas port szeregowy ostatecznie skonfigurujemy zgodnie ze specyfikacją
       struktury DCB za pomocą funkcji SetCommState(), która reinicjalizuje i uaktualnia wszyst-
       kie dostępne parametry w ustawieniach łącza szeregowego:
          BOOL SetCommState(HANDLE hCommDev, LPDCB lpdcb)

       Jednak tutaj parametr, na który wskazuje lpdcb, musi już zawierać informacje o nowych,
       wybranych przez nas parametrach ustawień portu komunikacyjnego. Należy też pamiętać,
       że funkcja SetCommState() nie zostanie wykonana pomyślnie, jeżeli posługując się struk-
       turą DCB, element XonChar ustalimy identycznie z XoffChar.


Funkcja CloseHandle()
       Przed zakończeniem działania aplikacji otwarty port szeregowy należy koniecznie
       zamknąć i zwolnić obszar pamięci przydzielony na jego identyfikator, korzystając z:
          BOOL CloseHandle(HANDLE hCommDev)

       We wszystkich przedstawionych powyżej funkcjach hCommDev w pełni identyfikuje dany
       port szeregowy, zawierając kompletną informację o tym, do którego łącza szeregowego
       będziemy wysyłać komunikaty. Ponieważ funkcje te mogą obsługiwać komunikaty
       wysyłane do wielu portów komunikacyjnych (jak również odbierane od wielu portów),
       zatem każdy otwarty i zainicjalizowany port szeregowy będzie identyfikowany właśnie
       za pomocą swojego własnego hCommDev. Nie należy przydzielać tego samego identyfikato-
       ra do dwóch różnych portów komunikacyjnych, tak samo jak nie należy z jednym portem
       kojarzyć dwóch różnych identyfikatorów.

          Przy pisaniu aplikacji obsługujących łącze szeregowe należy koniecznie zamknąć port
          przed opuszczeniem programu. W razie korzystania z zegara systemowego przy ob-
          słudze RS-a lub techniki programowania wielowątkowego trzeba pamiętać, że samo
          zamknięcie aplikacji nie powoduje automatycznego zamknięcia portu. Jego identyfi-
          kator dalej będzie przechowywany w pamięci.
          W pewnych przypadkach aplikacja z niezamkniętym portem szeregowym może stać się
          programem rezydentnym i uniemożliwić powtórne otwarcie wybranego portu. Dobrym
          zwyczajem jest w pierwszej kolejności zaprojektowanie funkcji lub procedury obsługi
          zdarzenia zamykającego otwarty port. System operacyjny powinien być zawsze poin-
          formowany o fakcie zamknięcia portu.
72             RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


        W praktyce zdarzają się jednak sytuacje, w których zamknięcie portu okaże się niemoż-
        liwe, np. z powodu jakiegoś błędu w algorytmie lub niewłaściwego sposobu wywołania
        danej funkcji. Mówimy wówczas, że program się załamał lub zawiesił. Ten problem
        powtarza się często w trakcie testowania programów komunikacyjnych. Nie należy wów-
        czas od razu używać kombinacji klawiszy Ctrl, Alt, Del. W takich przypadkach wygodniej
        jest rozwinąć z głównego menu opcję Project oraz wybrać Compile Unit, tak jak poka-
        zano to na rysunku 5.1. Nazwa działającej aplikacji powinna się pojawić na dolnym
        pasku zadań.




Rysunek 5.1. Przykładowy sposób wstrzymywania działania aplikacji z otwartym portem szeregowym

        Po pojawieniu się informacji Debug session in progress. Terminate? (Usuwanie sesji
        w toku. Zakończyć?) (rysunek 5.2) należy nacisnąć przycisk OK.

        Po kolejnym komunikacie (rysunek 5.3) znów należy dokonać potwierdzenia.

        Tak postępując, w większości przypadków odzyskamy program oraz odblokujemy łącze
        szeregowe. Sposób ten okaże się szczególnie przydatny przy kłopotach z aplikacją
        komunikacyjną korzystającą z komponentu typu TTimer, generującego zdarzenia w rów-
        nych odstępach czasu.

           Może oczywiście zdarzyć się sytuacja, w której nie będziemy w stanie powtórnie
           skompilować programu i samodzielnie prawidłowo zamknąć portu komunikacyjnego.
           Wówczas program należy usunąć z pamięci poleceniem menu Run/Program Reset.
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows   73




Rysunek 5.2. Okno dialogowe Debug session




Rysunek 5.3. Kompilacja projektu
74               RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera



Testowanie portu szeregowego
         Mając na uwadze wszystko, co powiedzieliśmy do tej pory, spróbujemy napisać w C++Bu-
         ilderze prosty program wykorzystujący przedstawione powyżej funkcje Windows API
         oraz niektóre zasoby struktury DCB.

         Zadaniem naszej aplikacji będzie ustawienie wybranych parametrów danego portu szere-
         gowego, otwarcie go oraz odczytanie nowych ustawień. W tym celu stwórzmy nową
         standardową aplikację (polecenie File — New Application). Niech jej formularz składa
         się z dwóch przycisków klasy TButton, pięciu komponentów klasy TEdit oraz pięciu
         TLabel.

         Korzystając z inspektora obiektów (Object Inspector) oraz z karty własności (Properties),
         własność Name przycisku Button1 zmieńmy na CloseComm, zaś jego własność Caption na
         &Zamknij. Podobnie własność Name przycisku Button2 zmieńmy na OpenComm, zaś Cap-
         tion na &Otwórz port. Własności Caption komponentów z klas TLabel zmieńmy odpo-
         wiednio na Prędkość transmisji, Liczbę bitów danych, Parzystość, Bity stopu, Linia DTR.
         Własności Text komponentów klasy TEdit wyczyśćmy.

         Formularz naszej aplikacji, wyglądającej podobnie jak na rysunku 5.4, znajduje się na
         dołączonym CD w katalogu KODYBUILDERR05P05_01. Na listingu 5.1 pokazano
         kod głównego modułu omawianej aplikacji.

Rysunek 5.4.
Formularz
główny projektu
Projekt_05_01.bpr




Listing 5.1. Kod głównego modułu aplikacji testującej podstawowe parametry transmisji portu szeregowego
            #include <vcl.h>
            #pragma hdrstop
            #include "Unit_05_01.h"
            #pragma package(smart_init)
            #pragma resource "*.dfm"

            TForm1 *Form1;

                HANDLE hCommDev; // identyfikator portu szeregowego
                // void *hCommDev;
                DCB     dcb;       // struktura kontroli portu
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                 75


              LPCTSTR portName = "COM2"; // wskaźnik do nazwy portu
              // const char *portName = "COM2";

              LPCTSTR sbuffer2 = "Uwaga!";
              LPCTSTR sbuffer1 = "Niewłaściwa nazwa portu lub port jest"
                                  " aktywny.";
          //--------------------------------------------------------------
          __fastcall TForm1::TForm1(TComponent* Owner)
              : TForm(Owner)
          {

          }
          //----funkcja zamyka otwarty port szeregowy-----------------
          BOOL __fastcall closeSerialPort(HANDLE hCommDev)
          {
             if ((hCommDev == 0) || (hCommDev == INVALID_HANDLE_VALUE))
               return FALSE;
               else {
                  CloseHandle(hCommDev);
                  return TRUE;
               }
          }
          //-----zamknięcie portu i aplikacji---------------------------
          void __fastcall TForm1::CloseCommClick(TObject *Sender)
          {
               closeSerialPort(hCommDev);
               Application->Terminate();
          }
          //---otwarcie portu i ustawienie jego parametrów---------------
          void __fastcall TForm1::OpenCommClick(TObject *Sender)
          {
              hCommDev = CreateFile(portName, GENERIC_READ |
                                    GENERIC_WRITE, 0, NULL,
                                    OPEN_EXISTING, 0, NULL);

              if (hCommDev != INVALID_HANDLE_VALUE)
              // sprawdza, czy port jest otwarty prawidłowo
                {
                  dcb.DCBlength = sizeof(dcb); // aktualny rozmiar
                                                // struktury DCB
                  GetCommState(hCommDev, &dcb); // udostępnienie aktualnych
                                                // parametrów DCB
                  dcb.BaudRate = CBR_1200;      // prędkość transmisji
                  dcb.fParity = TRUE;           // sprawdzanie parzystości
                  dcb.Parity = NOPARITY;        // ustawienie parzystości
                  dcb.StopBits = TWOSTOPBITS;   // bity stopu
                  dcb.ByteSize = 7;             // bity danych
                  dcb.fDtrControl = 1;          // np. kontrola linii DTR

                  SetCommState(hCommDev, &dcb); // reinicjalizacja DCB

                }
                else
                  {
                   switch ((int)hCommDev)
                     {
                      case IE_BADID:
76       RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


              // W przypadku błędnej identyfikacji portu
              // BADIDentify pokaż komunikat
                   MessageBox(NULL, sbuffer1, sbuffer2, MB_OK);
                 break;
                };
              }
     //--sprawdzenie i wyświetlenie ustawionej prędkości------
         switch (dcb.BaudRate)
           {
             case CBR_9600:
                 Edit1->Text = IntToStr(dcb.BaudRate);
                 break;
             case CBR_1200:
                 Edit1->Text = IntToStr(dcb.BaudRate);
                 break;
             case CBR_300:
                 Edit1->Text = IntToStr(dcb.BaudRate);
                 break;
             case CBR_110:
                 Edit1->Text = IntToStr(dcb.BaudRate);
                 break;
           }
     //--sprawdzenie i wyświetlenie ustawionych bitów danych-
         switch (dcb.ByteSize)
           {
             case 8:
                 Edit2->Text = IntToStr(dcb.ByteSize);
                 break;
             case 7:
                 Edit2->Text = IntToStr(dcb.ByteSize);
                 break;
             case 6:
                 Edit2->Text = IntToStr(dcb.ByteSize);
                 break;
             case 5:
                 Edit2->Text = IntToStr(dcb.ByteSize);
                 break;
           }
     //--sprawdzenie i wyświetlenie ustawionej parzystości----
         switch (dcb.Parity)
           {
             case NOPARITY:
                 Edit3->Text = "Brak";
                 break;
             case ODDPARITY:
                 Edit3->Text = "Nieparzysta";
                 break;
             case EVENPARITY:
                 Edit3->Text = "Parzysta";
                 break;
             case MARKPARITY:
                 Edit3->Text = "Znacznik: 1";
                 break;
           }
     //--sprawdzenie i wyświetlenie ustawionych bitów stopu---
         switch (dcb.StopBits)
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                                77


                {
                    case ONESTOPBIT:
                        Edit4->Text = "1";
                        break;
                    case TWOSTOPBITS:
                        Edit4->Text = "2";
                        break;
                    case ONE5STOPBITS:
                        Edit4->Text = "1.5";
                        break;
                }
          //--sprawdzenie i wyświetlenie stanu linii DTR-----------
              switch (dcb.fDtrControl)
                {
                  case DTR_CONTROL_DISABLE:
                      Edit5->Text = "Nieaktywna";
                      break;
                  case DTR_CONTROL_ENABLE:
                      Edit5->Text = "Aktywna";
                      break;
                  case DTR_CONTROL_HANDSHAKE:
                      Edit5->Text = "Handshaking";
                      break;
                }
          }
          //-----------------------------------------------------------
          void __fastcall TForm1::FormClose(TObject *Sender,
                                            TCloseAction &Action)
          {
              Action=caFree;
          }
          //--------------------------------------------------------------


       Stworzyliśmy zatem bardzo prostą, wręcz „dydaktyczną” aplikację, ale taki właśnie był
       nasz cel. Możemy zauważyć, że obsługa tego programu sprowadza się do wywołania
       funkcji obsługi zdarzenia OpenCommClick(). Naciśnięcie przycisku Otwórz port powoduje
       automatyczne skonfigurowanie wybranego wcześniej portu szeregowego oraz odczytanie
       jego aktualnie wybranych ustawień. Dobrze byłoby, gdyby Czytelnik spróbował samo-
       dzielnie skonfigurować port z większą liczbą parametrów, a następnie je odczytał. Nabiera
       się przez to większej wprawy w manipulowaniu znacznikami struktury DCB. Zamknięcie
       portu i aplikacji nastąpi po wywołaniu funkcji obsługi zdarzenia CloseCommClick(),
       w której z kolei dokonujemy wywołania funkcji CloseSerialPort() zamykającej port sze-
       regowy i aplikację. Przyglądając się kodowi funkcji obsługi zdarzenia OpenCommClick(),
       zauważymy, że tuż po wywołaniu CreateFile() zastosowaliśmy następującą instrukcję
       warunkową sprawdzającą, czy funkcja ta zwróciła prawidłowy identyfikator zadeklaro-
       wanego portu:
          if (hCommDev != INVALID_HANDLE_VALUE) {
             ...
            }
            else {
               switch ((int)hCommDev) {
78                 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


                        case IE_BADID: // W przypadku błędnej identyfikacji portu
                                       // BADIDentify pokaż komunikat
                        ...
                        break;
                   };
               }

           Łatwo można się przekonać, że w przypadku błędnego przydzielenia identyfikatora dla
           portu COMn, funkcja CreateFile() zwraca wartość INVALID_HANDLE_ VALUE (niewłaściwa
           wartość identyfikatora) zdefiniowaną w Windows API. Jest to bardzo skuteczna metoda
           zabezpieczenia się przed próbą otwarcia nieistniejącego lub już otwartego portu (urządze-
           nia). Zauważmy też, że aby odczytać aktualną wartość hCommDev, musieliśmy wymusić
           przekształcenie typów, używając operacji rzutowania (int)hCommDev. Każdy już się chyba
           przekonał, że identyfikator czy — jak kto woli — uchwyt typu HANDLE nie jest żadnym
           numerem bezpośrednio nadanym portowi komunikacyjnemu, lokalizuje jedynie unikalny
           obszar pamięci, do którego należy się odwołać, by uzyskać dostęp do danego urządzenia.

              Raz otwartego portu komunikacyjnego nie można otworzyć powtórnie, podobnie jak
              nie uda się otworzyć już otwartego okna. Nie można też powtórnie skorzystać z ob-
              szaru pamięci, z którego właśnie korzystamy.


           Jeżeli mimo wszystko port nie został otwarty prawidłowo, dobrze by było, gdyby apli-
           kacja powiadomiła nas o tym fakcie. W tym celu można skorzystać z komunikatów
           Windows typu IE_ (ang. Identify Error — błąd identyfikacji portu) urządzenia lub jego
           ustawień. W tabeli 5.6 przedstawiono najczęściej otrzymywane od Windows komunikaty
           tego typu.

Tabela 5.6. Najczęściej używane komunikaty błędnej identyfikacji ustawień portu szeregowego
 Identyfikacja komunikatu         Wartość     Znaczenie
 IE_BADID                          –1         Niewłaściwa identyfikacja urządzenia
 IE_BAUDRATE                      –12         Błędnie określona szybkość transmisji
 IE_BYTESIZE                      –11         Błędnie określona liczba bitów danych
 IE_DEFAULT                        –5         Niewłaściwie określone parametry domyślne urządzenia
 IE_HARDWARE                      –10         Odbiornik jest zablokowany
 IE_MEMORY                         –4         Niewłaściwie ustalony rozmiar buforów
 IE_NOPEN                          –3         Urządzenie nie jest otwarte do transmisji
 IE_OPEN                           –2         Urządzenie pozostaje otwarte



Struktura COMMPROP
           W celu dokładniejszego zapoznania się z możliwościami testowania systemów komunika-
           cyjnych dostępnych w Windows w tabeli 5.7 przedstawiono bardzo użyteczną strukturę
           oferowaną przez API. Zawarte w niej informacje mogą być wykorzystywane do pełnego
           odczytywania wszystkich istotnych parametrów interesującego nas łącza komunikacyj-
           nego oraz usług potencjalnie przez nie oferowanych.
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                                79


Tabela 5.7. Zasoby struktury COMMPROP
                                                               Zawartość elementu, maska
 Typ     Element struktury   Znaczenie
                                                               określająca włączony bit
 WORD    wPacketLength       Określa (w bajtach) rozmiar       Należy odczytać, zależy też
                             porcji pakietu danych             od typu sterownika
 WORD    wPacketVersion      Wersja struktury                  Nr 2 w Win 9x, XP
 DWORD   dwServiceMask       Określenie maski bitowej          SP_SERIALCOMM jest zawsze
                             wskazującej na typ aktualnie      określone
                             dostępnej usługi komunikacyjnej
 DWORD   dwReserved1         Zarezerwowane, nieużywane
 DWORD   dwMaxTxQueue        Maksymalny rozmiar                0 oznacza, że nie ustalono
                             wewnętrznego bufora wyjściowego   maksymalnej wartości
                             nadajnika (w bajtach)
 DWORD   dwMaxRxQueue        Maksymalny rozmiar                0 oznacza, że nie ustalono
                             wewnętrznego bufora wejściowego   maksymalnej wartości
                             odbiornika (w bajtach)
 DWORD   dwMaxBaud           Maksymalna dostępna prędkość      BAUD_075             75 b/s
                             transmisji w bitach na sekundę    BAUD_110            110 b/s
                                                               BAUD_134_5        134.5 b/s
                                                               BAUD_150            150 b/s
                                                               BAUD_300            300 b/s
                                                               BAUD_600            600 b/s
                                                               BAUD_1200          1200 b/s
                                                               BAUD_1800          1800 b/s
                                                               BAUD_2400          2400 b/s
                                                               BAUD_4800          4800 b/s
                                                               BAUD_7200          7200 b/s
                                                               BAUD_9600          9600 b/s
                                                               BAUD_14400       14 400 b/s
                                                               BAUD_19200       19 200 b/s
                                                               BAUD_38400       38 400 b/s
                                                               BAUD_56K            56K b/s
                                                               BAUD_57600       57 600 b/s
                                                               BAUD_115200     115 200 b/s
                                                               BAUD_128K         128K b/s
                                                               BAUD_USER programowalne
80              RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


Tabela 5.7. Zasoby struktury COMMPROP — ciąg dalszy
                                                               Zawartość elementu, maska
 Typ      Element struktury    Znaczenie
                                                               określająca włączony bit
 DWORD    dwProvSubType        Typ usługi komunikacyjnej       PST_FAX — faks
                                                               PST_LAT — protokół LAT
                                                               (Local — Area Transport)
                                                               PST_MODEM — modem
                                                               PST_NETWORK_BRIDGE
                                                               — niewyspecyfikowana sieć
                                                               PST_PARALLELPORT — port
                                                               równoległy
                                                               PST_RS232      RS 232
                                                               PST_RS422      RS 422
                                                               PST_RS423      RS 423
                                                               PST_RS449      RS 449
                                                               PST_SCANNER — skaner
                                                               PST_TCPIP_TELNET — protokół
                                                               TCP/IP
                                                               PST_UNSPECIFIED — brak
                                                               specyfikacji
                                                               PST_X25 — protokół X.25
 DWORD    DwSettableParams     Specyfikacja maski bitowej      SP_BAUD — prędkość
                               identyfikującej parametry       SP_DATABITS — długość słowa
                               transmisji podlegające          danych
                               ewentualnym zmianom
                                                               SP_HANDSHAKING — kontrola
                                                               przepływu danych
                                                               SP_PARITY — parzystość
                                                               SP_PARITY_CHECK — sprawdzanie
                                                               parzystości
                                                               SP_RLSD — sygnał RLSD
                                                               SP_STOPBITS — bity stopu

         W Windows API COMMPROP deklaruje się następująco:
           typedef struct _COMMPROP {
                   WORD wPacketLength;
                   ...
           } COMMPROP;

         Powyższa deklaracja tworzy nowe słowo kluczowe typu COMMPROP (struktura).

         Zbudujmy teraz aplikację, za pomocą której będziemy mogli selektywnie odczytywać stan
         poszczególnych masek bitowych udostępnianych przez COMMPROP.
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                                    81


Tabela 5.7. Zasoby struktury COMMPROP — ciąg dalszy
                                                                 Zawartość elementu, maska
 Typ     Element struktury     Znaczenie
                                                                 określająca włączony bit
 DWORD   dwProvCapabilities    Określa maskę bitową              PCF_16BITMODE — tryb 16-bitowy
                               identyfikującą rodzaj funkcji     PCF_DTRDSR — kontrola DTR-DSR
                               udostępnianych przez usługę
                               komunikacyjną (dostarczyciela     PCF_INTTIMEOUTS — czas
                               usługi)                           przeterminowania
                                                                 PCF_PARITY_CHECK — sprawdzanie
                                                                 parzystości
                                                                 PCF_RLSD — kontrola RLSD
                                                                 PCF_RTSCTS — kontrola RTS-CTS
                                                                 PCF_SETXCHAR — możliwość
                                                                 użycia protokołu XON/XOFF
                                                                 PCF_SPECIALCHARS — specjalny
                                                                 znak
                                                                 PCF_TOTALTIMEOUTS — kontrola
                                                                 czasu przeterminowania transmisji
                                                                 PCF_XONXOFF — podtrzymanie
                                                                 protokołu XON-XOFF
 DWORD   dwSettableBaud        Specyfikacja maski bitowej        Tak samo jak w dwMaxBaud
                               umożliwiającej ustawienie
                               prędkości transmisji
 WORD    wSettableData         Specyfikacja maski bitowej        DATABITS_5
                               identyfikującej możliwe do        DATABITS_6
                               użycia długości słowa danych
                                                                 DATABITS_7
                                                                 DATABITS_8
                                                                 DATABITS_16
                                                                 DATABITS_16X szczególna
                                                                 długość słowa danych
 DWORD   dwCurrentTxQueue      Aktualny maksymalny rozmiar       0 oznacza, że wartość ta nie jest
                               wewnętrznego bufora wyjściowego   aktualnie dostępna
                               nadajnika (w bajtach)
 WORD    wSettableStopParity   Specyfikacja maski bitowej        STOPBITS_10 — 1 bit stopu
                               identyfikującej możliwe do        STOPBITS_15 — 1,5 bitu
                               użycia wartości bitów stopu
                               i kontroli parzystości            STOPBITS_20 — 2 bity
                                                                 PARITY_NONE — brak
                                                                 PARITY_ODD — nieparzysta
                                                                 PARITY_EVEN — parzysta
                                                                 PARITY_MARK — 1
                                                                 PARITY_SPACE — 0
 DWORD   dwCurrentRxQueue      Aktualny maksymalny rozmiar       0 oznacza, że wartość ta nie jest
                               wewnętrznego bufora wejściowego   aktualnie dostępna
                               nadajnika (w bajtach)
82                 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


Tabela 5.7. Zasoby struktury COMMPROP — ciąg dalszy
                                                                     Zawartość elementu, maska
    Typ      Element struktury    Znaczenie
                                                                     określająca włączony bit
    DWORD    dwProvSpec1          Specyfikacja formatu danych        W zależności od dwProvSubType
                                  wymaganych przez daną usługę       aplikacje powinny ignorować
                                  komunikacyjną                      ten człon, chyba że zawierają
                                                                     szczegółowe informacje odnośnie
                                                                     do formatu danych wymaganych
                                                                     przez dostarczyciela usługi
    DWORD    dwProvSpec2          Jak wyżej
    WCHAR    wcProvChar[1]        Jak wyżej                          Jeżeli dwProvSubType przypisano
                                                                     PST_MODEM, musi nastąpić
                                                                     odwołanie do struktur
                                                                                                    2
                                                                     MODEMDEVCAPS oraz MODEMSETTINGS ;
                                                                     dwProvSpec1 i dwProvSpec2 nie są
                                                                     wówczas używane

            Wykorzystamy tu znany nam już proces maskowania z użyciem operatora iloczynu
            bitowego & (bitowe i). Program będzie odczytywał wartość wybranego elementu struktu-
            ry, a następnie poprzez wybranie kolejnych masek będzie selektywnie sprawdzał, czy
            włączone są konkretne bity odpowiedzialne za pewne parametry transmisji. Omawiany
            projekt znajduje się na dołączonym CD w katalogu KODYBUILDERR05P05_02.
            Do testowania wybierzmy elementy: dwSettableParams, w XP reprezentowany na 32
            bitach, oraz wSettableData i wSettableStopParity, reprezentowane na 16 bitach. Zasto-
            sujemy nieco odbiegający od przedstawionego wcześniej projekt formularza. Składać się
            on będzie z dwóch dobrze nam już znanych przycisków reprezentujących zdarzenia
            polegające na otwarciu oraz zamknięciu portu. Zastosowano ponadto dwa komponenty
            klasy TTrackBar, dwa TEdit oraz dwa typu TLabel, tak jak pokazuje to rysunek 5.5.
            Obsługa zdarzenia TrackBar1Change() polega na wybraniu interesującego nas elementu
            struktury COMMPROP oraz odczytaniu jego aktualnej wartości. Jeżeli zechcemy sprawdzić,
            czy włączony jest konkretny bit reprezentujący wybrany atrybut transmisji przechowywa-
            ny w danym elemencie struktury, wystarczy przesunąć wskaźnik uruchamiający funkcję
            obsługi zdarzenia TrackBar2Change().


Funkcja GetCommProperties()
            Funkcją, która zwraca aktualne właściwości portu komunikacyjnego identyfikowanego
            przez hCommDev, będzie:
              BOOL GetCommProperties(HANDLE   hCommDev, LPCOMMPROP   lpCommProp);

            lpCommProp jest wskaźnikiem do struktury COMMPROP, której format danych w ogólnym
            przypadku należy najpierw zainicjalizować, po uprzednim wpisaniu do pola wPac-
            ketLength aktualnego rozmiaru struktury:



2
    Miłośnikom modemów specyfikację tych struktur prezentujemy w dodatku B.
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                       83


Rysunek 5.5.
Formularz główny
uruchomionego
projektu
Projekt_R05_02.bpr




           commprop.wPacketLength = sizeof(COMMPROP);
           //...
           CommProp.dwProvSpec1 = COMMPROP_INITIALIZED;

        Informacje tam zawarte mogą być pomocne przy odwoływaniu się do rodziny funkcji
        SetCommState(), SetCommTimeouts() lub SetupComm().

        Na listingu 5.2 pokazano kod głównego modułu omawianej aplikacji.

Listing 5.2. Zawartość modułu Unit_R05_02.cpp
           #include <vcl.h>
           #pragma hdrstop
           #include "Unit_05_02.h"
           #pragma package(smart_init)
           #pragma resource "*.dfm"

           TForm1 *Form1;

             HANDLE hCommDev;           //   identyfikator portu
             COMMPROP commprop;         //   właściwości portu
             LPCTSTR portName = "COM2"; //   wskaźnik do nazwy portu
                                        //   szeregowego

             LPCTSTR sbuffer1 = "Niewłaściwa nazwa portu lub port jest"
                                 " aktywny.";
           //--------------------------------------------------------------
           __fastcall TForm1::TForm1(TComponent* Owner)
               : TForm(Owner)
           {
           }
           //-------funkcja zamyka otwarty port szeregowy------------------
           BOOL __fastcall closeSerialPort(HANDLE hCommDev)
           {
               if ((hCommDev == 0) || (hCommDev == INVALID_HANDLE_VALUE))
                 return FALSE;
                 else {
                    CloseHandle(hCommDev);
                    return TRUE;
84       RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


           }
     }
     //-------zamknięcie portu i aplikacji----------------------------
     void __fastcall TForm1::CloseCommClick(TObject *Sender)
     {
          closeSerialPort(hCommDev);
          Application->Terminate();
     }
     //----otwarcie portu i ustawienie jego parametrów---------------
     void __fastcall TForm1::OpenCommClick(TObject *Sender)
     {
         hCommDev = CreateFile(portName, GENERIC_READ |
                               GENERIC_WRITE, 0, NULL,
                               OPEN_EXISTING, 0, NULL);

         if (hCommDev != INVALID_HANDLE_VALUE) {
          // sprawdza, czy port jest otwarty prawidłowo
             commprop.wPacketLength = sizeof(COMMPROP);
             commprop.dwProvSpec1 = COMMPROP_INITIALIZED;
           // inicjalizuje format danych usługi.
           // Port szeregowy jest zawsze dostępny
             memset(&commprop, 0, sizeof(COMMPROP));
             GetCommProperties(hCommDev, &commprop);
           }
           else {
               switch ((int)hCommDev) {
                  case IE_BADID:
                     ShowMessage(sbuffer1);
                  break;
               };
           }
     }
     //------wybrane maski bitowe------------------------------------
     void __fastcall TForm1::TrackBar1Change(TObject *Sender)
     {
       switch (TrackBar1->Position) {
          case 1: {
            TrackBar2->Max = 7;
            Label1->Caption = "dwSettableParams";
            Edit1->Text=IntToStr(commprop.dwSettableParams);
            break;
          }
          case 2: {
            TrackBar2->Max = 6;
            Label1->Caption = "wSettableData";
            Edit1->Text=IntToStr(commprop.wSettableData);
            break;
          }
          case 3: {
            TrackBar2->Max = 8;
            Label1->Caption = "wSettableStopParity";
            Edit1->Text=IntToStr(commprop.wSettableStopParity);
            break;
          }
       } // koniec switch
     }
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                    85


          //---------------zawartość maski---------------------------
          void __fastcall TForm1::TrackBar2Change(TObject *Sender)
          {
            if (TrackBar1->Position == 1) {
              switch (TrackBar2->Position) {
                case 1: {
                  Label2->Caption = "SP_PARITY";
                  Edit2->Text=IntToStr(commprop.dwSettableParams & SP_PARITY);
                  break;
                }
                case 2: {
                  Label2->Caption = "SP_BAUD";
                  Edit2->Text=IntToStr(commprop.dwSettableParams & SP_BAUD);
                  break;
                }
                case 3: {
                  Label2->Caption = "SP_DATABITS";
                  Edit2->Text=IntToStr(commprop.dwSettableParams &
                                       SP_DATABITS);
                  break;
                }
                case 4: {
                  Label2->Caption = "SP_STOPBITS";
                  Edit2->Text=IntToStr(commprop.dwSettableParams &
                                       SP_STOPBITS);
                  break;
                }
                case 5: {
                  Label2->Caption = "SP_HANDSHAKING";
                  Edit2->Text=IntToStr(commprop.dwSettableParams &
                                       SP_HANDSHAKING);
                  break;
                }
                case 6: {
                  Label2->Caption = "SP_PARITY_CHECK";
                  Edit2->Text=IntToStr(commprop.dwSettableParams &
                                       SP_PARITY_CHECK);
                  break;
                }
                case 7: {
                  Label2->Caption = "SP_RLSD";
                  Edit2->Text=IntToStr(commprop.dwSettableParams & SP_RLSD);
                  break;
                }
              } // koniec switch
            } // koniec if

            if (TrackBar1->Position == 2) {
              switch (TrackBar2->Position) {
                case 1: {
                  Label2->Caption = "DATABITS_5";
                  Edit2->Text=IntToStr(commprop.wSettableData & DATABITS_5);
                  break;
                }
                case 2: {
                  Label2->Caption = "DATABITS_6";
86     RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


           Edit2->Text=IntToStr(commprop.wSettableData & DATABITS_6);
           break;
         }
         case 3: {
           Label2->Caption = "DATABITS_7";
           Edit2->Text=IntToStr(commprop.wSettableData   & DATABITS_7);
           break;
         }
         case 4: {
           Label2->Caption = "DATABITS_8";
           Edit2->Text=IntToStr(commprop.wSettableData   & DATABITS_8);
           break;
         }
         case 5: {
           Label2->Caption = "DATABITS_16";
           Edit2->Text=IntToStr(commprop.wSettableData   & DATABITS_16);
           break;
         }
         case 6: {
           Label2->Caption = "DATABITS_16X";
           Edit2->Text=IntToStr(commprop.wSettableData   &
                                DATABITS_16X);
           break;
         }
       } // koniec switch
     } // koniec if

     if (TrackBar1->Position == 3) {
       switch (TrackBar2->Position) {
         case 1: {
           Label2->Caption = "STOPBITS_10";
           Edit2->Text=IntToStr(commprop.wSettableStopParity   &
                                STOPBITS_10);
           break;
         }
         case 2: {
           Label2->Caption = "STOPBITS_15";
           Edit2->Text=IntToStr(commprop.wSettableStopParity   &
                                STOPBITS_15);
           break;
         }
         case 3: {
           Label2->Caption = "STOPBITS_20";
           Edit2->Text=IntToStr(commprop.wSettableStopParity   &
                                STOPBITS_20);
           break;
         }
         case 4: {
           Label2->Caption = "PARITY_NONE";
           Edit2->Text=IntToStr(commprop.wSettableStopParity   &
                                PARITY_NONE);
           break;
         }
         case 5: {
           Label2->Caption = "PARITY_ODD";
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                                87


                  Edit2->Text=IntToStr(commprop.wSettableStopParity &
                                       PARITY_ODD);
                  break;
                }
                case 6: {
                  Label2->Caption = "PARITY_EVEN";
                  Edit2->Text=IntToStr(commprop.wSettableStopParity &
                                       PARITY_EVEN);
                  break;
                }
                case 7: {
                  Label2->Caption = "PARITY_MARK";
                  Edit2->Text=IntToStr(commprop.wSettableStopParity &
                                       PARITY_MARK);
                  break;
                }
                case 8: {
                  Label2->Caption = "PARITY_SPACE";
                  Edit2->Text=IntToStr(commprop.wSettableStopParity &
                                       PARITY_SPACE);
                  break;
                }
              } // koniec switch
            } // koniec if
          }
          //--------------------------------------------------------------
          void __fastcall TForm1::FormClose(TObject *Sender,
                                            TCloseAction &Action)
          {
              Action=caFree;
          }
          //--------------------------------------------------------------


       Dla przykładu rozpatrzmy parametr dwSettableParams typu DWORD, w XP reprezentowany
       na 32 bitach. Odczytując odpowiednią wartość, przekonaliśmy się, że cała zawarta tam
       informacja zapisana jest na 7 pierwszych bitach dwSettableParams.

       Użyliśmy operatora &, aby sprawdzić, czy włączone są poszczególne bity reprezentujące
       atrybuty związane z konkretnymi parametrami komunikacyjnymi. Patrząc na wartości
       w postaci binarnej, łatwo zorientujemy się, jaki jest aktualny stan logiczny poszczegól-
       nych bitów zawartych w tej zmiennej i za co są one odpowiedzialne, tak jak pokazano to
       w zawartości tabeli 5.8.

       W analogiczny sposób możemy przetestować wszystkie maski bitowe udostępniane przez
       COMMPROP, odpowiadające właściwym pozycjom konkretnych bitów. Jeżeli jako rezultat
       iloczynu bitowego wartości elementu struktury z maską określającą włączony bit otrzy-
       mamy wartość 0, oznaczać to będzie, że testowany bit jest wyłączony i dany parametr
       komunikacyjny nie jest aktualnie dostępny. Lepiej znający temat Czytelnicy zapewne już
       zorientowali się, jakie niespodzianki oferuje nam ta struktura. Manipulowanie bitami jest
       tu sprawą dobrania odpowiednich operatorów przesuwania, maskowania i dopełniania.
       Można np. skasować bity SP_PARITY i SP_BAUD w elemencie dwSettableParams:
          CommProp.dwSettableParams &= ~(SP_PARITY | SP_BAUD);
88                RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


Tabela 5.8. Maski bitowe elementu dwSettableParams struktury COMMPROP
                         Wartość     Bit 7   Bit 6   Bit 5   Bit 4    Bit 3   Bit 2   Bit 1   Bit 0
 dwSettableParams        127         0       1       1       1        1       1       1       1
 Maska                   Rezultat maskowania
 SP_PARITY                 1        0        0       0       0       0        0       0       1
 SP_BAUD                   2        0        0       0       0       0        0       1       0
 SP_DATABITS               4        0        0       0       0       0        1       0       0
 SP_STOPBITS               8        0        0       0       0       1        0       0       0
 SP_HANDSHAKING           16        0        0       0       1       0        0       0       0
 SP_PARITY_CHECK          32        0        0       1       0       0        0       0       0
 SP_RLSD                  64        0        1       0       0       0        0       0       0

           Podobnie w jakimś fragmencie aplikacji można zastosować warunek:
               if ((CommProp.dwSettableParams & (SP_PARITY | SP_BAUD)) == 0) {
                  ...
               }

           który będzie prawdziwy, gdy oba bity będą skasowane. Jednak osobom mniej zaawanso-
           wanym w operacjach bitowych odradzałbym jakiekolwiek próby ingerowania w zawar-
           tość COMMPROP.


Struktura COMMCONFIG
           Struktura COMMCONFIG zawiera informacje o stanie konfiguracji danego urządzenia komu-
           nikacyjnego. Tabela 5.9 prezentuje jej zasoby.

           Windows API COMMCONFIG deklaruje następująco:
             typedef struct _COMM_CONFIG {
                     DWORD dwSize;
                     ...
             } COMMCONFIG, *LPCOMMCONFIG;

           Powyższa deklaracja tworzy dwa nowe słowa kluczowe typu COMMCONFIG (struktura) oraz
           LPCOMMCONFIG (wskaźnik do struktury).


Funkcje GetCommConfig() i SetCommConfig()
           Aktualną konfigurację łącza komunikacyjnego odczytamy, korzystając z funkcji API:
             BOOL GetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC,
                                LPDWORD lpdwSize);

           gdzie lpCC wskazuje na strukturę COMMCONFIG, zaś lpdwSize jest wskaźnikiem do zmiennej
           określającej rozmiar struktury.
Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows                                          89


Tabela 5.9. Specyfikacja struktury COMMCONFIG
 Typ      Element struktury    Znaczenie                                     Zawartość
 DWORD     dwSize              Rozmiar struktury w bajtach                   Należy wpisać
 WORD      wVersion            Wersja struktury                              Należy odczytać
 WORD      wReserved           Zarezerwowane
 DCB       dcb                 Struktura kontroli portu szeregowego          Patrz DCB
 DWORD     dwProviderSubType   Identyfikacja typu dostarczanej usługi        Patrz COMMPROP
                               komunikacyjnej, a tym samym
                               wymaganego formatu danych
 DWORD     dwProviderOffset    Określenie offsetu dla danych wymaganych      0, jeżeli nie określono
                               przez dostarczyciela usługi komunikacyjnej;   typu danych
                               offset (tzw. przesunięcie) określony jest
                               zwykle w stosunku do początku struktury
 DWORD     dwProviderSize      Rozmiar danych (w bajtach) wymaganych         Zależnie od typu usługi
                               przez usługę komunikacyjną (dostarczyciela
                               usługi)
 WCHAR     wcProviderData[1]   Dane dostarczane wraz z usługą (ponieważ      Jeżeli ustalono typ
                               przewidywane jest w przyszłości               usługi: PST_RS232
                               uzupełnienie struktury, aplikacja powinna     lub PST_PARALLELPORT,
                               używać dwProviderOffset w celu                człon ten jest pomijany;
                               określenia położenia wcProviderData)          jeżeli ustalono PST_MODEM,
                                                                             należy odwołać się do
                                                                             MODEMSETTINGS

         Bieżącą konfigurację portu komunikacyjnego zapiszemy za pomocą:
           BOOL SetCommConfig(HANDLE hCommDev, LPBYTE lpCC, DWORD dwSize);

         gdzie lpCC jest wskaźnikiem do COMMCONFIG, zaś dwSize określa (w bajtach) rozmiar
         struktury wskazywanej przez lpCC. Przed przekazaniem tej struktury jako parametru nale-
         ży do elementu dwSize wpisać wartość równą sizeof(COMMCONFIG).


Funkcja CommConfigDialog()
         Funkcja wyświetla okno dialogowe zawierające aktualne ustawienia parametrów portu
         szeregowego.
           BOOL CommConfigDialog(LPTSTR lpszName, HWND hWnd,
                                 LPCOMMCONFIG lpCC);

         lpszName jest wskaźnikiem do łańcucha znaków określającego nazwę portu, hWnd jest
         identyfikatorem właściciela aktualnie wyświetlanego okna dialogowego, a lpCC wskazuje
         na strukturę COMMCONFIG. Użycie tej funkcji, np. w kontekście obsługi wybranego zdarze-
         nia, może wyglądać następująco:
           //--------------------------------------------------------------
           void __fastcall TForm1::Button1Click(TObject *Sender)
           {
90              RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera


              LPCOMMCONFIG commconfig;
              CommConfigDialog("COM2", NULL, commconfig);
           }
           //--------------------------------------------------------------



Struktura COMMTIMEOUTS
         Zasoby struktury COMMTIMEOUTS przedstawione są w tabeli 5.10. Udostępniają one infor-
         macje o tzw. czasach przeterminowania transmisji w trakcie przesyłania danych (ang.
         time-out of transmission). Jest to ważny termin, z którym niektórzy na pewno już się
         zetknęli. W trakcie transmisji asynchronicznej COMMTIMEOUTS determinuje zachowanie się
         takich funkcji jak ReadFile() czy WriteFile().

Tabela 5.10. Informacje zawarte w strukturze COMMTIMEOUTS
 Typ      Element struktury             Właściwości
 DWORD    ReadIntervalTimeout           Określa maksymalny czas (w milisekundach) pomiędzy
                                        pojawieniem się na linii komunikacyjnej dwu znaków.
                                        W trakcie wykonywania ReadFile() czas jest liczony od
                                        momentu pojawienia się pierwszego znaku. Jeżeli przedział
                                        czasu pomiędzy nadejściem dwu znaków przekracza wartość
                                        ReadIntervalTimeout, oznacza to, że operacja ReadFile()
                                        jest zakończona. Wartość 0 oznacza, że nie ustalono
                                        wymaganego okresu pomiędzy nadejściem dwu kolejnych
                                        znaków. Przypisanie wartości MAXDWORD powoduje, że czytany
                                        znak jest pobierany z bufora natychmiast po tym, jak się
                                        tam pojawi
 DWORD    ReadTotalTimeoutMultiplier    Określa mnożnik (w milisekundach) użyty do obliczenia
                                        całkowitego przedziału czasu (przeterminowanie) dla operacji
                                        czytania (odbioru). Dla wszystkich takich operacji wartość ta
                                        jest mnożona przez liczbę bajtów przewidzianą do odebrania
                                        z dysku lub łącza komunikacyjnego.
 DWORD    ReadTotalTimeoutConstant      Określa stałą (w milisekundach) użytą do obliczania czasu
                                        przeterminowania operacji czytania. Dla wszystkich takich
                                        operacji wartość ta jest dodawana
                                        do ReadTotalTimeoutMultiplier i do oczekiwanej liczby
                                        nadchodzących bajtów
 DWORD    WriteTotalTimeoutMultiplier   Określa mnożnik (w milisekundach) użyty do obliczenia
                                        całkowitego przedziału czasu (przeterminowanie) dla operacji
                                        zapisywania (wysyłania). Dla wszystkich takich operacji
                                        wartość ta jest mnożona przez liczbę bajtów przewidzianą
                                        do wysłania (zapisania). 0 oznacza, że nie ustalono czasu
                                        przeterminowania dla operacji zapisu na dysku lub do łącza
                                        komunikacyjnego
 DWORD    WriteTotalTimeoutConstant     Określa stałą (w milisekundach) użytą do obliczania
                                        czasu przeterminowania operacji wysyłania. Dla wszystkich
                                        takich operacji wartość ta jest dodawana
                                        do WriteTotalTimeoutMultiplier oraz do oczekiwanej
                                        liczby wysyłanych bajtów. 0 oznacza, że nie ustalono
                                        czasu przeterminowania dla operacji zapisu (wysyłania)

More Related Content

PDF
RS 232C - praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera. Wy...
PDF
Delphi. Szybki start
PDF
C# i ASP.NET. Szybki start
PDF
.NET Framework 2.0. Zaawansowane programowanie
PDF
Visual C# 2005 Express Edition. Od podstaw
PDF
Wstęp do programowania w języku C#
PDF
Programowanie w języku C. Szybki start
PDF
ABC Delphi 7
RS 232C - praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera. Wy...
Delphi. Szybki start
C# i ASP.NET. Szybki start
.NET Framework 2.0. Zaawansowane programowanie
Visual C# 2005 Express Edition. Od podstaw
Wstęp do programowania w języku C#
Programowanie w języku C. Szybki start
ABC Delphi 7

What's hot (20)

PDF
C#. Ćwiczenia
PDF
Aplikacje w Delphi. Przykłady. Wydanie II
PDF
Java 2. Podstawy
PDF
Uczta programistów
PDF
ABC Delphi 2006
PDF
C++. Styl i technika zaawansowanego programowania
PDF
C++BuilderX. Ćwiczenia
PDF
Visual C# .NET. Encyklopedia
PDF
Asembler dla procesorów Intel. Vademecum profesjonalisty
PDF
Język C++. Gotowe rozwiązania dla programistów
PDF
Język C. Programowanie
PDF
C++Builder 6. Ćwiczenia
PDF
.Net. Najpilniej strzeżone tajemnice
PDF
Delphi 7. Ćwiczenia zaawansowane
PDF
C++Builder Borland Developer Studio 2006. Kompendium programisty
PDF
J2ME. Almanach
PDF
Java. Kompendium programisty
PDF
C++. Wykorzystaj potęgę aplikacji graficznych
PDF
Wyjątkowy język C++. 40 nowych łamigłówek, zadań programistycznych i rozwiązań
PDF
ActionScript. Receptury
C#. Ćwiczenia
Aplikacje w Delphi. Przykłady. Wydanie II
Java 2. Podstawy
Uczta programistów
ABC Delphi 2006
C++. Styl i technika zaawansowanego programowania
C++BuilderX. Ćwiczenia
Visual C# .NET. Encyklopedia
Asembler dla procesorów Intel. Vademecum profesjonalisty
Język C++. Gotowe rozwiązania dla programistów
Język C. Programowanie
C++Builder 6. Ćwiczenia
.Net. Najpilniej strzeżone tajemnice
Delphi 7. Ćwiczenia zaawansowane
C++Builder Borland Developer Studio 2006. Kompendium programisty
J2ME. Almanach
Java. Kompendium programisty
C++. Wykorzystaj potęgę aplikacji graficznych
Wyjątkowy język C++. 40 nowych łamigłówek, zadań programistycznych i rozwiązań
ActionScript. Receptury
Ad

Viewers also liked (6)

PDF
TCP/IP. Szkoła programowania
PDF
Galileoskop Przewodnik Obserwacyjny
PPSX
Podział trójkątów ze względu na boki i kąty
PDF
Zaburzenia endokrynologiczne a znieczulenie
PDF
PR zawsze żywy
TCP/IP. Szkoła programowania
Galileoskop Przewodnik Obserwacyjny
Podział trójkątów ze względu na boki i kąty
Zaburzenia endokrynologiczne a znieczulenie
PR zawsze żywy
Ad

Similar to RS 232C - praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera. Wydanie III (19)

PDF
C++Builder i Turbo C++. Podstawy
PDF
Visual C# 2008. Projektowanie aplikacji. Pierwsze starcie
PDF
Aplikacje w Visual C++ 2005. Przykłady
PDF
Profesjonalne programowanie. Część 1. Zrozumieć komputer
PDF
C++Builder. Kompendium programisty
PDF
Delphi 2007 dla WIN32 i bazy danych
PDF
C++. Programowanie zorientowane obiektowo. Vademecum profesjonalisty
PDF
Delphi 7 dla każdego
PDF
PHP. 101 praktycznych skryptów. Wydanie II
PDF
PDF
Linux dla programistów i użytkowników
PDF
Po prostu PHP. Techniki zaawansowane
PDF
C++. Zaawansowane programowanie
PDF
ArchiCAD 10
PDF
Interfejsy sprzętowe komputerów PC
PDF
Visual C# 2005. Zapiski programisty
PDF
C++. Inżynieria programowania
PDF
C++ dla programistów gier. Wydanie II
PDF
Język C. Wskaźniki. Vademecum profesjonalisty
C++Builder i Turbo C++. Podstawy
Visual C# 2008. Projektowanie aplikacji. Pierwsze starcie
Aplikacje w Visual C++ 2005. Przykłady
Profesjonalne programowanie. Część 1. Zrozumieć komputer
C++Builder. Kompendium programisty
Delphi 2007 dla WIN32 i bazy danych
C++. Programowanie zorientowane obiektowo. Vademecum profesjonalisty
Delphi 7 dla każdego
PHP. 101 praktycznych skryptów. Wydanie II
Linux dla programistów i użytkowników
Po prostu PHP. Techniki zaawansowane
C++. Zaawansowane programowanie
ArchiCAD 10
Interfejsy sprzętowe komputerów PC
Visual C# 2005. Zapiski programisty
C++. Inżynieria programowania
C++ dla programistów gier. Wydanie II
Język C. Wskaźniki. Vademecum profesjonalisty

More from Wydawnictwo Helion (20)

PDF
Tworzenie filmów w Windows XP. Projekty
PDF
Blog, więcej niż internetowy pamiętnik
PDF
Access w biurze i nie tylko
PDF
Pozycjonowanie i optymalizacja stron WWW. Ćwiczenia praktyczne
PDF
E-wizerunek. Internet jako narzędzie kreowania image&#39;u w biznesie
PDF
Microsoft Visual C++ 2008. Tworzenie aplikacji dla Windows
PDF
Co potrafi Twój iPhone? Podręcznik użytkownika. Wydanie II
PDF
Makrofotografia. Magia szczegółu
PDF
Windows PowerShell. Podstawy
PDF
Java. Efektywne programowanie. Wydanie II
PDF
JavaScript. Pierwsze starcie
PDF
Ajax, JavaScript i PHP. Intensywny trening
PDF
PowerPoint 2007 PL. Seria praktyk
PDF
Excel 2007 PL. Seria praktyk
PDF
Access 2007 PL. Seria praktyk
PDF
Word 2007 PL. Seria praktyk
PDF
Serwisy społecznościowe. Budowa, administracja i moderacja
PDF
AutoCAD 2008 i 2008 PL
PDF
Bazy danych. Pierwsze starcie
PDF
Inventor. Pierwsze kroki
Tworzenie filmów w Windows XP. Projekty
Blog, więcej niż internetowy pamiętnik
Access w biurze i nie tylko
Pozycjonowanie i optymalizacja stron WWW. Ćwiczenia praktyczne
E-wizerunek. Internet jako narzędzie kreowania image&#39;u w biznesie
Microsoft Visual C++ 2008. Tworzenie aplikacji dla Windows
Co potrafi Twój iPhone? Podręcznik użytkownika. Wydanie II
Makrofotografia. Magia szczegółu
Windows PowerShell. Podstawy
Java. Efektywne programowanie. Wydanie II
JavaScript. Pierwsze starcie
Ajax, JavaScript i PHP. Intensywny trening
PowerPoint 2007 PL. Seria praktyk
Excel 2007 PL. Seria praktyk
Access 2007 PL. Seria praktyk
Word 2007 PL. Seria praktyk
Serwisy społecznościowe. Budowa, administracja i moderacja
AutoCAD 2008 i 2008 PL
Bazy danych. Pierwsze starcie
Inventor. Pierwsze kroki

RS 232C - praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera. Wydanie III

  • 1. RS 232C – praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera. Wydanie III Autor: Andrzej Daniluk ISBN: 978-83-246-0778-5 Format: B5, stron: 504 Na uczelniach, w szko³ach i biurach pojawia siê coraz wiêcej zaawansowanych urz¹dzeñ komputerowych pod³¹czanych przez port szeregowy. Czy koniecznie trzeba p³aciæ wysokie stawki informatykom, aby wykorzystaæ pe³niê mo¿liwoœci tych nowoczesnych narzêdzi? Na szczêœcie nie. Obs³uga transmisji szeregowej przy u¿yciu standardu RS 232C mo¿e byæ na tyle ³atwa, ¿e uczniowie, studenci, nauczyciele, pracownicy naukowi czy in¿ynierowie mog¹ samodzielnie tworzyæ potrzebne im oprogramowanie. Dziêki ksi¹¿ce „RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera. Wydanie III” tak¿e i Ty szybko nauczysz siê pisaæ programy steruj¹ce urz¹dzeniami pod³¹czanymi przez port szeregowy. Dowiesz siê, jak dzia³a transmisja asynchroniczna oraz czym jest standard RS 232C. Poznasz interfejs RS 232C dla systemu Windows i nauczysz siê go u¿ywaæ w œrodowiskach programistycznych Builder i Delphi, co pozwoli Ci pisaæ potrzebne oprogramowanie w jêzyku Pascal lub C++. Najnowsze, poprawione wydanie zawiera jeszcze wiêcej przyk³adów, dziêki którym b³yskawicznie bêdziesz móg³ sprawdziæ nabyt¹ wiedzê w praktyce. • Standard RS 232C • Transmisja asynchroniczna • Obs³uga RS 232C w systemach MS-DOS i Windows • Wykorzystanie elementów interfejsu Windows API w œrodowiskach Builder i Delphi • Testowanie programów do obs³ugi transmisji szeregowej • Tworzenie aplikacji wielow¹tkowych • Narzêdzia graficzne • Przyk³adowe aplikacje i ich analiza • Specyfikacje najwa¿niejszych funkcji Wydawnictwo Helion ul. Koœciuszki 1c 44-100 Gliwice tel. 032 230 98 63 e-mail: helion@helion.pl
  • 2. Spis treści 5 Spis treści Przedmowa do wydania trzeciego ...................................................... 9 Wprowadzenie ................................................................................ 11 Rozdział 1. Definicja interfejsu ......................................................................... 15 Rozdział 2. Nowoczesna transmisja asynchroniczna oraz standard RS 232C ...... 19 RTS-CTS handshaking .................................................................................................... 24 Konwertery interfejsu RS 232C ...................................................................................... 28 Konwertery USB/RS 232C .............................................................................................. 29 Właściwości portu konwertera .................................................................................. 31 Protokół XON-XOFF ...................................................................................................... 33 Protokół ENQ-ACK ........................................................................................................ 33 Protokół ETX-ACK ......................................................................................................... 34 Protokół SOH-ETX ......................................................................................................... 34 Protokoły typu master-slave ............................................................................................ 34 Rola oprogramowania a podstawowe funkcje interfejsu ................................................. 36 Podsumowanie ................................................................................................................. 38 Rozdział 3. Jak testować programy do transmisji szeregowej? ........................... 39 Mirror w MS-DOS ........................................................................................................... 39 Terminal dla Windows .................................................................................................... 41 Podsumowanie ................................................................................................................. 43 Rozdział 4. Transmisja szeregowa w MS-DOS .................................................... 45 Borland C++ .................................................................................................................... 45 Borland Pascal ................................................................................................................. 53 Funkcja 00h ............................................................................................................... 55 Funkcja 01h ............................................................................................................... 56 Funkcja 02h ............................................................................................................... 56 Funkcja 03h ............................................................................................................... 56 Podsumowanie ................................................................................................................. 58 Ćwiczenia ........................................................................................................................ 58 Rozdział 5. Programowa obsługa interfejsu RS 232C w Windows ...................... 59 Typy danych Windows .................................................................................................... 61 Proces projektowania oprogramowania ........................................................................... 64 Wykorzystanie elementów Windows API w C++Builderze. Część I ............................. 64 Struktura DCB ........................................................................................................... 65 Funkcja CreateFile() .................................................................................................. 65
  • 3. 6 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Funkcja GetCommState() .......................................................................................... 70 Funkcja SetCommState() .......................................................................................... 71 Funkcja CloseHandle() .............................................................................................. 71 Testowanie portu szeregowego ....................................................................................... 74 Struktura COMMPROP ............................................................................................ 78 Funkcja GetCommProperties() ................................................................................. 82 Struktura COMMCONFIG ....................................................................................... 88 Funkcje GetCommConfig() i SetCommConfig() ...................................................... 88 Funkcja CommConfigDialog() ................................................................................. 89 Struktura COMMTIMEOUTS .................................................................................. 90 Funkcje GetCommTimeouts() i SetCommTimeouts() .............................................. 91 Nawiązanie połączenia. Wariant I ................................................................................... 91 Segment inicjalizująco-konfiguracyjny ..................................................................... 92 Segment wysyłający komunikaty. Funkcja WriteFile() ............................................ 92 Segment odbierający komunikaty. Funkcja ReadFile() ............................................ 93 Przykładowa aplikacja ............................................................................................... 94 Nawiązanie połączenia. Wariant II .................................................................................. 97 Funkcja SetupComm() .............................................................................................. 98 Funkcja ClearCommError() ...................................................................................... 98 Struktura COMSTAT .............................................................................................. 100 Przykładowa aplikacja ............................................................................................. 102 Zamknięcie portu komunikacyjnego ....................................................................... 106 Nawiązanie połączenia. Wariant III .............................................................................. 107 Funkcje GetCommMask() i SetCommMask() ........................................................ 107 Funkcja WaitCommEvent() .................................................................................... 109 Przykładowa aplikacja działająca w środowisku tekstowym .................................. 110 Przykładowa aplikacja działająca w środowisku graficznym ................................. 118 Nawiązanie połączenia. Wariant IV .............................................................................. 123 Funkcja BuildCommDCB() .................................................................................... 123 Funkcja BuildCommDCBAndTimeouts() .............................................................. 125 Inne użyteczne funkcje .................................................................................................. 126 Podsumowanie ............................................................................................................... 128 Ćwiczenia ...................................................................................................................... 128 Wykorzystanie elementów Windows API w C++Builderze. Część II .......................... 129 Wysyłamy znak po znaku. Funkcja TransmitCommChar() .................................... 129 Wysyłamy pliki. Funkcje _lopen, _lread(), _lwrite(), _lclose() .............................. 133 Wykorzystanie komponentu klasy TTimer ............................................................. 143 Aplikacja nie lubi milczeć. Funkcja GetLastError() ............................................... 162 Break Time — czas oczekiwania aplikacji ............................................................. 167 Podsumowanie ........................................................................................................ 176 Ćwiczenia ................................................................................................................ 176 Wykorzystanie elementów Windows API w Delphi. Część I ....................................... 177 Testowanie portu szeregowego — inaczej .............................................................. 177 Rekord TCOMMPROP ........................................................................................... 183 Nawiązanie połączenia ............................................................................................ 191 Przykładowe aplikacje ............................................................................................. 194 Podsumowanie ........................................................................................................ 203 Ćwiczenia ................................................................................................................ 203 Wykorzystanie elementów Windows API w Delphi. Część II ...................................... 203 Wysyłamy znak po znaku ....................................................................................... 204 Wysyłamy pliki ....................................................................................................... 209 Timer w Delphi ....................................................................................................... 224 Podsumowanie ............................................................................................................... 238 Ćwiczenia ...................................................................................................................... 239
  • 4. Spis treści 7 Rozdział 6. Aplikacje wielowątkowe ............................................................... 241 Najważniejszy jest użytkownik ..................................................................................... 242 Użytkownik steruje programem .............................................................................. 242 Możliwość anulowania decyzji ............................................................................... 243 Możliwość odbioru komunikatu nawet w trakcie wysyłania danych ..................... 243 Możliwość wysłania odrębnej informacji w trakcie transmisji pliku ..................... 243 Delphi ............................................................................................................................ 244 Funkcja BeginThread() ........................................................................................... 244 Konkurencja dla Timera .......................................................................................... 256 Klasa TThread ......................................................................................................... 264 Wielowątkowość i DLL-e ............................................................................................. 272 C++Builder .................................................................................................................... 280 Zamiast Timera ....................................................................................................... 289 Zamiast Timera. Inny sposób .................................................................................. 296 Klasa TThread ......................................................................................................... 304 Podsumowanie ............................................................................................................... 315 Ćwiczenia ...................................................................................................................... 315 Rozdział 7. Wykorzystanie niektórych narzędzi graficznych .............................. 317 Komponent klasy TChart ............................................................................................... 318 Podsumowanie ............................................................................................................... 328 Ćwiczenia ...................................................................................................................... 328 Rozdział 8. Przykładowe aplikacje wykorzystywane w systemach pomiarowych ........................................................... 329 Kontroler temperatury ................................................................................................... 330 Aplikacja obsługująca kilka urządzeń ........................................................................... 347 Programowanie inteligentne .......................................................................................... 358 Brak powtarzalności kodu ....................................................................................... 359 Czytelność kodu ...................................................................................................... 360 Łatwość testowania ................................................................................................. 364 Podsumowanie ............................................................................................................... 366 Ćwiczenia ...................................................................................................................... 366 Rozdział 9. Tworzenie komponentów .............................................................. 369 Komponent TOpenSerialPort. Realizacja w Delphi ...................................................... 369 Testowanie komponentu ......................................................................................... 374 Komponent TOpenSerialPort. Realizacja w C++Builderze .......................................... 380 Testowanie komponentu ......................................................................................... 386 Komponenty aktywne .................................................................................................... 389 Kompilacja projektu zawierającego komponent aktywny ...................................... 393 Odczytywanie i modyfikacja wartości własności komponentu aktywnego ............ 395 Komponenty w BDS 2006 ............................................................................................. 397 Podsumowanie ............................................................................................................... 398 Ćwiczenia ...................................................................................................................... 398 Rozdział 10. Modelowanie oprogramowania sterującego portem szeregowym .... 399 Schematy dziedziczenia ................................................................................................. 400 Ukrywanie konstruktora ................................................................................................ 405 Interfejsy ........................................................................................................................ 409 Delegowanie operacji .................................................................................................... 415 Delegowanie realizacji interfejsu do własności ............................................................. 422 Podsumowanie ............................................................................................................... 426 Ćwiczenia ...................................................................................................................... 427
  • 5. 8 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Rozdział 11. POSIX .......................................................................................... 435 Polecenie stty ................................................................................................................. 436 Ustawienia kontroli przesyłu danych (sterowanie transmisją) ................................ 437 Ustawienia wejściowe ............................................................................................. 437 Ustawienia wyjściowe ............................................................................................. 439 Ustawienia czasów oczekiwania ............................................................................. 439 Ustawienia lokalne .................................................................................................. 440 Specjalne znaki sterujące ........................................................................................ 441 Łączenie atrybutów ................................................................................................. 442 Podstawowe funkcje obsługi portu szeregowego .......................................................... 442 Funkcja open() ......................................................................................................... 442 Funkcja read() ......................................................................................................... 443 Funkcja write() ........................................................................................................ 443 Funkcja close() ........................................................................................................ 443 Struktura termios ........................................................................................................... 444 Funkcja tcgetattr() ................................................................................................... 448 Funkcja tcsetattr() .................................................................................................... 448 Funkcje cfgetispeed() i cfgetospeed() ..................................................................... 449 Funkcje cfsetispeed() i cfsetospeed() ...................................................................... 449 Funkcja tcflush() ..................................................................................................... 450 Funkcja tcdrain() ..................................................................................................... 451 QNX ............................................................................................................................... 451 Funkcja dev_insert_chars() ..................................................................................... 453 Funkcja dev_ischars() ............................................................................................. 453 Funkcja dev_read() .................................................................................................. 454 Funkcja Receive() ................................................................................................... 455 Funkcja Send() ........................................................................................................ 455 Funkcja Creceive() .................................................................................................. 455 Funkcja Reply() ....................................................................................................... 456 Funkcja qnx_proxy_attach() ................................................................................... 456 Funkcja qnx_proxy_detach() ................................................................................... 456 Podsumowanie ............................................................................................................... 457 Ćwiczenia ...................................................................................................................... 457 Dodatek A Specyfikacja funkcji CreateFile() — operacje plikowe ................... 461 Dodatek B Specyfikacja struktur MODEMDEVCAPS, MODEMSETTINGS oraz funkcji GetCommModemStatus() ........................................... 467 MODEMDEVCAPS ...................................................................................................... 467 MODEMSETTINGS ..................................................................................................... 470 GetCommModemStatus() .............................................................................................. 471 Dodatek C Transmisja asynchroniczna. Funkcje rozszerzone ........................... 473 Funkcja WriteFileEx() ................................................................................................... 473 Funkcja ReadFileEx() .................................................................................................... 474 Funkcja FileIOCompletionRoutine() ............................................................................. 474 Funkcja SleepEx() ......................................................................................................... 475 Funkcja WaitForSingleObjectEx() ................................................................................ 475 Dodatek D Zamiana liczb z postaci dziesiętnej na binarną .............................. 477 Dodatek E Funkcje CreateThread(), CreateMutex() i CreateSemaphore() ....... 481 Skorowidz .................................................................................... 487
  • 6. Rozdział 5. Programowa obsługa interfejsu RS 232C w Windows Czwarte prawo Murphy’ego Gdy dojdziesz do wniosku, że są cztery sposoby, na jakie może się nie powieść dane przedsięwzięcie, i zabezpieczysz się przed nimi, rychło pojawi się piąta możliwość. Murphy’s Law and other reasons why things go wrong!, Artur Bloch, Price Stern Sloan Inc. 1977. Rozdział ten ma za zadanie zapoznać Czytelnika ze sposobami konstrukcji algorytmów realizujących transmisję szeregową w środowisku Windows, które charakteryzuje się pewnymi cechami niemającymi odpowiedników w MS-DOS. Poznanie i umiejętne wykorzystanie tych cech sprawi, iż problem obsługi interfejsów szeregowych z poziomu Windows — uważany powszechnie za trudny — przestanie być dla nas tajemnicą. Pokażemy, w jaki sposób należy tworzyć aplikacje służące do programowej obsługi łącza szeregowego RS 232C zarówno w C++, C++Builderze, jak i w Delphi. Wśród programi- stów istnieje zauważalny podział na osoby programujące głównie w Delphi oraz na prefe- rujące Buildera lub ogólnie C++ dla Windows. Jednak zdaniem wielu osób uniwersalność jest jedną z tych cech, jakie powinny charakteryzować programistę. W rozdziale tym przybliżymy Czytelnikowi podobieństwa i różnice w sposobie konstrukcji algorytmów realizujących transmisję szeregową, pisanych w Delphi oraz Builderze. W dalszej części książki będziemy się spotykać z typami danych, których poznanie i zro- zumienie ma kluczowe znaczenie w projektowaniu aplikacji obsługujących urządzenia zewnętrzne. Zacznijmy od ich przypomnienia. W tabeli 5.1 przedstawiono porównanie podstawowych typów zmiennych wykorzystywanych w kompilatorach, które będą dla nas istotne. Większości z nich można używać zamiennie, pisząc zarówno w Delphi, jak i w C++Builderze.
  • 7. 60 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Tabela 5.1. Typy zmiennych stosowanych w Delphi oraz w C++Builderze Delphi Rozmiar w bajtach Znak +/– Typ C++Builder ShortInt 1 Integer signed char SmallInt 2 Integer short LongInt 4 Integer Byte 1 Bez znaku Integer unsigned char Word 2 Bez znaku Integer unsigned short Integer 4 Integer int Cardinal 4 Bez znaku Integer unsigned int Boolean 1 true/false bool ByteBool 1 true/false unsigned char Bez znaku Integer WordBool 2 true/false unsigned short Bez znaku Integer LongBool 4 true/false Bez znaku Integer AnsiChar 1 1 znak ANSI Character char WideChar 2 1 znak Unicode Character wchar_t Char 1 Bez znaku Character char AnsiString ≈3GB ANSIChar AnsiString AnsiString String[n] n = 1.255 ANSIChar String SmallString<n> ShortString 255 ANSIChar String SmallString<255> String 255 lub ≈3GB ANSIChar AnsiString AnsiString Single 4 Floating point number float (liczba zmiennoprzecinkowa) Double 8 Floating point number double Extended 10 Floating point number long double Real 4 Floating point number double Pointer 4 Generic pointer void * (wskaźnik ogólny, adresowy) PChar 4 Bez znaku Pointer to characters unsigned char * PAnsiChar 4 Bez znaku Pointer to ANSIChar unsigned char * Comp 8 Floating point number Comp Konstruując nasze programy, będziemy starali się jak najszerzej wykorzystywać standar- dowe zasoby Windows, w szczególności tzw. interfejs programisty Windows API (ang. Application Programming Interface). Jego umiejętne wykorzystanie umożliwi naszym aplikacjom błyskawiczne skonfigurowanie i uzyskanie dostępu do portu komunikacyjne- go. Błędem jest twierdzenie, że sama — nawet bardzo dobra — znajomość języka pro-
  • 8. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 61 gramowania wystarczy, żeby stworzyć poprawnie działający w Windows program. Otóż musimy zdawać sobie sprawę z faktu, o którym często się zapomina — niemożliwe jest napisanie udanej aplikacji mającej pracować w pewnym środowisku (czytaj — systemie operacyjnym) bez znajomości tego środowiska. Wiele już zostało powiedziane na temat dobrych i złych stron Windows, należy jednak pamiętać, że oferuje on nam swoją wizy- tówkę, ofertę współpracy, czyli API. Już nie wystarczy umiejętność wykorzystywania ulubionego kompilatora. Zasoby Delphi czy Buildera połączymy z zasobami systemu operacyjnego, a spoiwem będzie właśnie uniwersalne Windows API. Istnieje wiele warstw API używanych w zależności od potrzeb. W tym i dalszych rozdziałach zajmiemy się szeroko rozumianą warstwą komunikacyjną. Windows API korzysta ze specjalnego systemu nazewnictwa zmiennych, z tzw. notacji węgierskiej wprowadzonej przez Karoja Szimoniego. Zgodnie z nią do rdzenia nazwy zadeklarowanej zmiennej dodaje się przedrostek (ang. prefix). Chociaż istnieją pod tym względem pewne rozbieżności pomiędzy nazewnictwem Microsoftu i Borlanda, to jednak zapis taki bardzo ułatwia szybkie ustalenie roli zmiennej w programie oraz jej typ. W następnych rozdziałach będziemy się starali — wszędzie gdzie jest to możliwe — zachowywać samokomentujące się nazewnictwo API (większość nazw API będziemy traktować jako nazwy własne). Z doświadczenia wiadomo, że stosowanie takiej konwen- cji bardzo pomaga w studiowaniu plików pomocy. Oczywiście moglibyśmy silić się na oryginalność, wprowadzając własne zmienne, zrozumiałe tylko dla piszącego dany pro- gram — wówczas przykłady musiałyby być zapisane jako wręcz humorystyczna miesza- nina języków polskiego i angielskiego. Trzeba też przyznać, że byłby to bardzo skuteczny sposób zaciemnienia obrazu API. Zrozumienie znaczenia nazw tam stosowanych okaże się w przyszłości niezwykle cenne, gdyż API można czytać jak książkę. Aby pomóc Czy- telnikom, którzy nie zetknęli się dotąd z tymi pojęciami, w tabeli 5.2 przedstawiono ogól- ne zasady tworzenia niektórych przedrostków. Windows oferuje nam ponadto kilka typów danych, z których część tylko nieznacznie różni się sposobem zapisu w implementacjach Delphi i Buildera. Typy te mają najczę- ściej postać struktury lub klasy i są bardzo często wykorzystywane w warstwie komunika- cyjnej programów. Typy danych Windows Nowoczesna idea programowania w Windows oparta na wykorzystaniu narzędzi pro- gramistycznych typu RAD, do których zaliczają się C++Builder oraz Delphi, pozwala programistom na maksymalne uproszczenie procesu tworzenia oprogramowania. Jednym z przykładów dążenia do zminimalizowania czasu tworzenia aplikacji jest zastosowanie w Windows pewnych bardzo zwartych w zapisie typów danych, które oczywiście mają swoje odpowiedniki w typach standardowych. W tabeli 5.3 zebrano najistotniejsze typy danych, którymi bardzo często posługują się programy Windows. Należy zdawać sobie sprawę z faktu, iż typy takie jak np. LPVOID i LPSTR nie są w dosłownym słowa tego zna- czeniu typami nowymi, tzn. od początku stworzonymi na potrzeby aplikacji Windows, gdyż zostały zdefiniowane w plikach nagłówkowych za pomocą instrukcji typedef po to, aby uprościć zapis niektórych standardowych typów danych. W tabeli 5.3 przedstawiono wybrane typy danych, którymi posługuje się API Windows.
  • 9. 62 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Tabela 5.2. Ogólne zasady tworzenia przedrostków według notacji węgierskiej Przedrostek Skrót angielski Znaczenie a array Tablica b bool Zmienna logiczna true lub false by byte unsigned char Znak (bajt) cb count of bytes Liczba bajtów ch char Znak dw double word Podwójne słowo evt event Zdarzenie f flag Znacznik fdw flag of double word Znacznik typu dw fn function Funkcja h handle Identyfikator (uchwyt) i integer Typ całkowity 4-bajtowy id (ID) identification Identyfikacja in input Wejście, dane wejściowe l long int Typ całkowity długi 4-bajtowy lp long pointer Wskaźnik typu long int lpc long pointer to C-string Wskaźnik typu long int do C-łańcucha lpfdw long pointer to flag of dw Wskaźnik typu lp do znacznika typu double word lpfn long pointer to function Wskaźnik typu lp do funkcji n short or int Typ krótki lub całkowity np near pointer Bliski wskaźnik (w środowisku 32-bitowym to samo co lp) out output Wyjście, dane wyjściowe (przetworzone) p pointer Wskaźnik (w środowisku 32-bitowym to samo co lp) pfn pointer to function Wskaźnik do funkcji que queue Kolejka, bufor danych s (sz) string Łańcuch znaków st struct Struktura t type Typ u unsigned Bez znaku w (word) unsigned int Słowo wc WCHAR Znak zgodny z Unicode
  • 10. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 63 Tabela 5.3. Niektóre typy danych stosowane w Windows Typ Windows Znaczenie BOOL int z dwoma wartościami TRUE oraz FALSE BYTE unsigned char DWORD unsigned long LPDWORD unsigned long * LONG long LPLONG long * LPCSTR const char * LPCTSTR unsigned const char * LPSTR char * LPVOID lub Pointer void * LPCVOID const void * UINT unsigned int WORD unsigned short DWORD32 32-bitowy typ całkowity bez znaku DWORD64 64-bitowy typ całkowity bez znaku INT 32-bitowy typ całkowity ze znakiem INT32 32-bitowy typ całkowity ze znakiem INT64 64-bitowy typ całkowity ze znakiem LONG32 32-bitowy typ całkowity ze znakiem LONG64 64-bitowy typ całkowity ze znakiem LONGLONG 64-bitowy typ całkowity ze znakiem Osobnym typem danych, bardzo często stosowanym w aplikacjach Windows, jest typ HANDLE. Jest on 32- lub 64-bitowym typem danych całkowitych oznaczającym tzw. uchwyt (ang. handle). Należy rozumieć, iż w rzeczywistości dane typu HANDLE nie obrazują jakichś tajemniczych uchwytów zakładanych na elementy aplikacji — są to po prostu 32- lub 64-bitowe liczby identyfikujące określony zasób aplikacji, systemu operacyjnego lub samego komputera. Z tego względu dane typu HANDLE często wygodniej i zręczniej jest określać mianem identyfikatorów, których wartości przechowywane są w określonym miejscu w pamięci. Cechą charakterystyczną identyfikatorów jest to, iż jeśli na początku programu inicjuje się je określonymi wartościami, w momencie zakończenia pracy aplikacji lub jej fragmentu należy przydzieloną im pamięć odpowiednio zwalniać. W tym celu wykorzystuje się funkcję API Windows: BOOL CloseHandle(HANDLE hObject); z argumentem w postaci określonego identyfikatora. Zaopatrzeni w powyższą terminologię pójdźmy dalej i zobaczmy, do czego mogą nam być przydatne poszczególne struktury oraz funkcje interfejsu programisty — Windows API.
  • 11. 64 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Proces projektowania oprogramowania Zanim przejdziemy do szczegółowego omawiania aspektów tworzenia programów obsłu- gujących port szeregowy w Windows, należy wybrać jedną z metod projektowania tego rodzaju aplikacji. Praktyka wskazuje, że dla pojedynczych użytkowników lub niewielkich organizacji dobrze sprawdza się metodologia oparta na programowaniu przyrostowym i iteracyjnym (ang. iterative and incremental development). W dalszej części książki będziemy korzystać z metody projektowania iteracyjnego. Takie podejście do zagadnienia sprawi, iż tworzone aplikacje oprócz wysokiej sprawności działania będą jeszcze miały dwie bardzo ważne i nieczęsto spotykane w literaturze cechy. Będą mianowicie: w pełni rozbudowywalne, łatwe do samodzielnej modyfikacji nawet przez osoby dopiero poznające zasady programowania w środowiskach Buildera i Delphi. Wszystkie prezentowane algorytmy będziemy się starali konstruować w ten sposób, aby pewne słynne twierdzenie wypowiedziane niegdyś przez Murphy’ego w omawianych programach nie miało zastosowania. Brzmi ono następująco: Twierdzenie o komplikacji procedur Każdą dowolnie skomplikowaną procedurę można skomplikować jeszcze bardziej. Twierdzenie odwrotne nie jest prawdziwe: nadzwyczaj rzadko się zdarza, aby skomplikowaną procedurę można było uprościć. Murphy’s Law and other reasons why things go wrong!, Artur Bloch, Price Stern Sloan Inc. 1977. Wykorzystanie elementów Windows API w C++Builderze. Część I Poznawanie tajników obsługi portu szeregowego w Windows rozpoczniemy, z czysto praktycznych względów, od pisania programów w C++Builderze. C++ ma składnię taką jak API, dlatego prościej nam będzie zapoznać się z budową funkcji oraz struktur ofero- wanych przez interfejs programisty. Ułatwi to też zrozumienie, w jaki sposób i w jakiej kolejności należy umieszczać je w programie.
  • 12. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 65 Struktura DCB Fundamentalne znaczenie ma struktura kontroli urządzeń zewnętrznych DCB (ang. Device Control Block). W Windows struktura DCB w pewnym sensie odpowiada funkcji 00h przerwania 14h BIOS-u. Udostępnia nam jednak nieporównywalnie większe możliwości programowej obsługi łącza szeregowego; umożliwia bezpośrednie programowanie reje- strów układu UART. W tabelach 5.4 oraz 5.5 przedstawiono specyfikację bloku kontroli urządzeń zewnętrznych DCB. Większość pól tej struktury to pola jednobitowe. fDtrControl, fRtsControl są polami dwubitowymi. Aktualnie nieużywane w XP pole fDummy2 jest siedemnastobitowe. W per- spektywie, wraz z wReserved oraz wReserved1, będzie wykorzystane na potrzeby innych protokołów komunikacyjnych. W Windows API blok kontroli urządzeń deklarowany jest w sposób następujący: typedef struct _DCB { DWORD DCBlength; ... } DCB; Deklaracja ta tworzy nowe słowo kluczowe typu DCB (struktura). Zalecane jest, aby przed użyciem tej struktury jako parametru do elementu DCBlength wpisać wartość sizeof(DCB). Strukturę tworzy zbiór logicznie powiązanych elementów, np. zmiennych lub (i) pól bitowych. Pole bitowe stanowi zbiór przylegających do siebie bitów, znajdujących się w jednym słowie. Adres struktury pobieramy za pomocą operatora referencji &, co umożliwia nam działania na jej składowych. Do struktury jako całości możemy odwołać się przez jej nazwę, zaś do poszczególnych jej elementów, czyli zmiennych oraz pól bitowych, przez podanie nazwy zmiennej reprezentującej strukturę oraz — po kropce — nazwy konkretnej zmiennej lub pola struktury, np.: dcb.fDtrControl = DTR_CONTROL_ DISABLE. Operator składowych struktur "." jest lewostronnie łączny. Grupa związanych ze sobą zmiennych i pól bitowych traktowana jest jako jeden obiekt. Zanim przejdziemy do praktycznego zastosowania poznanych pól struktury DCB, musimy zapoznać się z czterema podstawowymi funkcjami Windows API służącymi do progra- mowej konfiguracji portów szeregowych. W dalszej części książki funkcji takich będzie przybywać, ale te przedstawione poniżej należy traktować jako najbardziej podstawowe. Funkcja CreateFile() Jest to funkcja służąca do utworzenia i otwarcia pliku lub urządzenia. Już sama nazwa wskazuje, że może być wykorzystywana nie tylko do obsługi portu szeregowego. Teraz jednak będzie nas interesować tylko to konkretne zastosowanie. Specyfikacja zasobów funkcji CreateFile() najczęściej używanych do operacji plikowych zamieszczona jest w dodatku A. Funkcja ta da nam 32- lub 64-bitowy identyfikator danego portu przecho- wywany pod właściwością HANDLE, do którego będą adresowane wszystkie komunikaty.
  • 13. 66 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Tabela 5.4. Zmienne struktury DCB reprezentujące dopuszczalne parametry ustawień portu szeregowego Wartość, Typ Zmienna Znaczenie stała symboliczna DWORD DCBlength Rozmiar struktury Należy wpisać DWORD BaudRate Określenie prędkości transmisji (b/s) CBR_110 CBR_19200 CBR_300 CBR_38400 CBR_600 CBR_56000 CBR_1200 CBR_57600 CBR_2400 CBR_115200 CBR_4800 CBR_128000 CBR_9600 CBR_256000 CBR_14400 WORD wReserved Nieużywane 0 WORD XonLim Określenie minimalnej liczby bajtów Domyślnie: 65 535; w praktyce XonLim w buforze wejściowym przed ustala się jako ½ rozmiaru deklarowanego wysłaniem specjalnego znaku wejściowego bufora danych sterującego XON WORD XoffLim Określenie maksymalnej liczby Domyślnie: 65535; w praktyce XoffLim bajtów w buforze wejściowym ustala się jako ¾ rozmiaru przed wysłaniem specjalnego znaku deklarowanego bufora wejściowego sterującego XOFF BYTE ByteSize Wybór liczby bitów danych 5, 6, 7, 8 BYTE Parity Określenie kontroli parzystości EVENPARITY — parzysta; MARKPARITY — bit parzystości stale równy 1; NOPARITY — brak kontroli; ODDPARITY — nieparzysta BYTE StopBits Wybór bitów stopu ONESTOPBIT — 1 bit stopu; ONE5STOPBITS — w przypadku słowa 5-bitowego bit stopu wydłużony o ½; TWOSTOPBITS — 2 bity stopu char XonChar Określenie wartości znaku XON Standardowo (char) DC1, dziesiętnie: 17 dla nadawania i odbioru (wysłanie znaku przywraca transmisję) char XoffChar Określenie wartości znaku XOFF Standardowo (char) DC3, dziesiętnie: 19 dla nadawania i odbioru (wysłanie XOFF wstrzymuje transmisję do czasu odebrania znaku XON) char ErrorChar Określenie wartości znaku Opcjonalnie: 0 lub SUB zastępującego bajty otrzymane z błędem parzystości char EofChar Określenie wartości znaku końca Opcjonalnie: 0 otrzymanych danych Char EvtChar Określenie wartości znaku Opcjonalnie: 0 służącego do sygnalizowania wystąpienia danego zdarzenia WORD wReserved1 Obecnie nieużywane
  • 14. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 67 Tabela 5.5. Pola bitowe reprezentujące dopuszczalne wartości znaczników sterujących struktury DCB Wartość, znaczenie, Typ Pole bitowe Właściwości pola stała symboliczna DWORD fBinary Tryb binarny (Win API TRUE podtrzymuje jedynie ten tryb transmisji danych) DWORD fParity Umożliwia ustawienie TRUE — kontrola parzystości włączona; sprawdzania parzystości FALSE — bit parzystości nie jest — sposobu reakcji na bit sprawdzany parzystości DWORD fOutxCtsFlow Umożliwia ustawienie TRUE — jeżeli sygnał CTS jest sprawdzania sygnału na linii nieaktywny, transmisja jest CTS w celu kontroli danych wstrzymywana do czasu ponownej wyjściowych aktywacji linii CTS; FALSE — włączenie sygnału na linii CTS nie jest wymagane do rozpoczęcia transmisji DWORD fOutxDsrFlow Umożliwia ustawienie TRUE — jeżeli sygnał DSR sprawdzania sygnału na linii jest nieaktywny, transmisja DSR w celu kontroli danych jest wstrzymywana do czasu ponownej wyjściowych aktywacji linii DSR; FALSE — włączenie sygnału na linii DSR nie jest wymagane do rozpoczęcia transmisji DWORD fDtrControl Specyfikacja typu kontroli DTR_CONTROL_DISABLE / 0 — sygnał sygnału DTR na linii DTR jest nieaktywny; DTR_CONTROL_ENABLE / 1 — sygnał na linii DTR jest aktywny; DTR_CONTROL_HANDSHAKE / 2 — włączenie potwierdzania przyjęcia sygnału DTR — potwierdzenie musi być odebrane na linii DSR. Używane w trybie półdupleksowym. Ewentualne błędy transmisji w tym trybie są usuwane przez funkcję EscapeCommFunction() DWORD fTXContinueOnXoff Kontrola przerwania TRUE — wymuszanie kontynuowania transmisji w przypadku transmisji nawet po wystąpieniu znaku przepełnienia bufora XOFF i wypełnieniu wejściowego bufora wejściowego i ewentualnie danych powyżej XoffLim bajtów; wystąpienia znaków FALSE — transmisja nie jest XoffChar oraz XonChar kontynuowana, dopóki bufor wejściowy nie zostanie opróżniony do pułapu XonLim bajtów i nie nadejdzie znak XON potwierdzenia dalszego odbioru DWORD fDsrSensitivity Specyfikacja wykorzystania TRUE — otrzymane bajty są ignorowane, poziomu sygnału na linii DSR o ile linia DSR nie jest w stanie wysokim; FALSE — stan linii DSR jest ignorowany
  • 15. 68 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Tabela 5.5. Pola bitowe reprezentujące dopuszczalne wartości znaczników sterujących struktury DCB — ciąg dalszy Wartość, znaczenie, Typ Pole bitowe Właściwości pola stała symboliczna DWORD fInX Programowe ustawienie TRUE — znak XoffChar jest wysyłany, protokołu XON-XOFF w czasie kiedy bufor wejściowy jest pełny lub odbioru danych znajduje się w nim XoffLim bajtów; znak XonChar jest wysyłany, kiedy bufor wejściowy pozostaje pusty lub znajduje się w nim XonLim bajtów; FALSE — XON-XOFF w czasie odbioru nie jest ustawiony DWORD fRtsControl Specyfikacja kontroli RTS_CONTROL_DISABLE / 0 — sygnał sygnału na linii RTS na linii RTS jest nieaktywny; RTS_CONTROL_ENABLE / 1 — sygnał na linii RTS jest aktywny; RTS_CONTROL_HANDSHAKE / 2 — włączenie potwierdzania przyjęcia sygnału RTS (potwierdzenie musi być odebrane na linii CTS). Używane w trybie półdupleksowym. Sterownik podwyższa stan linii RTS, gdy wypełnienie bufora wejściowego jest mniejsze od ½. Stan linii RTS zostaje obniżony, gdy bufor wypełniony jest w ¾. Ewentualne błędy transmisji w tym trybie usuwane są przez funkcję EscapeCommFunction(); RTS_CONTROL_TOGGLE / 3 — linia RTS jest w stanie wysokim, jeżeli są bajty do transmisji i jest ona możliwa; po opróżnieniu bufora komunikacyjnego linia RTS pozostaje w stanie niskim DWORD fOutX Programowe ustawienie TRUE — transmisja zostaje przerwana po protokołu XON-XOFF w czasie odebraniu znaku XoffChar i wznowiona wysyłania danych po otrzymaniu znaku XonChar; FALSE — XON-XOFF w czasie wysyłania nie jest ustawiony DWORD fErrorChar Umożliwia zastąpienie bajtów TRUE — zastąpienie jest wykonywane, otrzymanych z błędem ponadto fParity musi być ustawione parzystości znakiem jako TRUE; ErrorChar FALSE — zastąpienie nie jest wykonane DWORD fNull Odrzucenie odebranych TRUE — nieważne bajty zostaną nieważnych lub odrzucone przy odbiorze; uszkodzonych bajtów FALSE — nieważne bajty nie będą odrzucane
  • 16. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 69 Tabela 5.5. Pola bitowe reprezentujące dopuszczalne wartości znaczników sterujących struktury DCB — ciąg dalszy Wartość, znaczenie, Typ Pole bitowe Właściwości pola stała symboliczna DWORD fAbortOnError Ustawienie wstrzymywania TRUE — wszelkie operacje nadawania operacji nadawanie-odbiór i odbioru są wstrzymywane, zaś dalsza przy wykryciu błędu komunikacja nie jest możliwa, dopóki transmisji błąd nie zostanie usunięty przez wywołanie funkcji ClearCommError(); FALSE — nawet jeżeli wystąpi błąd, transmisja jest kontynuowana — błąd może być usunięty przez wywołanie funkcji ClearCommError() DWORD fDummy2 Zarezerwowane, nieużywane Ogólnie rzecz ujmując, przed rozpoczęciem czytania z portu szeregowego (lub innego urządzenia) należy o powyższym fakcie poinformować system operacyjny. Czynność tę określa się jako otwieranie portu do transmisji. Jednak zanim zaczniemy wykonywać jakiekolwiek operacje na porcie, system operacyjny musi sprawdzić, czy wybrany port komunikacyjny istnieje i czy w danym momencie nie jest już przypadkiem w jakiś sposób wykorzystywany. W przypadku uzyskania dostępu do portu system operacyjny przeka- zuje do aplikacji jego identyfikator. We wszystkich operacjach wejścia-wyjścia zamiast szczegółowej nazwy portu komunikacyjnego używa się właśnie jego identyfikatora. Składnia CreateFile() wygląda następująco1: HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD ShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); Niekiedy identyfikatory tego typu nazywa się uchwytami. Niestety, dosłowne prze- tłumaczenie angielskiego słowa handle jako uchwyt, np. handle of drawer — uchwyt, rączka szuflady, nie jest w pełni adekwatne. Właściwsze wydaje się utożsamianie handle z identyfikatorem (unikalną wartością zlokalizowaną w danym obszarze pa- mięci i skojarzoną z konkretnym portem komunikacyjnym, oknem czy plikiem). W po- tocznej angielszczyźnie handle może również oznaczać ksywę pozwalającą na szybką identyfikację danej osoby lub rzeczy. Koncepcja identyfikatorów nie jest niczym nowym, stosowano ją już w MS-DOS, jednak dopiero w Windows zyskała nową jakość. Na tym etapie naszych rozważań tylko trzy parametry powyższej funkcji są istotne dla kompletnej konfiguracji portu szeregowego. Wyjaśnimy teraz ich znaczenie. 1 Pełna specyfikacja funkcji CreateFile() została zamieszczona w dodatku A.
  • 17. 70 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Pierwszy parametr, lpFileName, jest wskaźnikiem do zadeklarowanego ciągu znaków zakończonego zerem (zerowym ogranicznikiem), tzw. null terminated string, lub do C-łańcucha (dokładniej: do pierwszego znaku tego łańcucha), w którym przechowywana będzie nazwa (wartość) portu. Z poprzednich rozdziałów pamiętamy, że ogólnie przyjęte jest stosowanie nazewnictwa portów szeregowych jako COMn (nazwy COMn znajdują się na liście nazw zastrzeżonych), gdzie n oznacza numer portu. Deklaracja numeru portu szeregowego, np. 2., będzie więc przedstawiać się w sposób bardzo prosty: LPCTSTR portName = "COM2"; lub, co jest równoważne: unsigned const char *portName = "COM2"; Można też zmienną, pod którą przechowywać będziemy numer portu, zadeklarować w sposób tradycyjny, używając typu char. Deklaracja taka będzie w pełni poprawna: char portName[5] = "COM2"; Parametr dwDesiredAccess typu DWORD umożliwia ustalenie rodzaju dostępu do portu szeregowego. Z praktycznego punktu widzenia najwygodniej jest ustalić rodzaj dostępu jako GENERIC_READ | GENERIC_WRITE (zapisuj do portu lub odczytuj z portu). Umożliwi nam to płynne wysyłanie i odbieranie komunikatów, co w pełni odpowiada półduplek- sowemu wariantowi transmisji. Jeżeli zechcemy korzystać jedynie z trybu simpleksowe- go, do dwDesiredAccess wystarczy przypisać jeden z wybranych rodzajów dostępu. Windows API posługuje się łańcuchami o długości większej niż 256 znaków. Aby przełamać to ograniczenie, zrezygnowano z zapamiętywania w pierwszym bajcie liczby określającej długość łańcucha znaków. W C-łańcuchach ostatnim znakiem, kończą- cym ciąg jest 0 (NULL lub heks. 00), którego nie należy mylić ze znakiem zero (48 lub heks. 30). Stąd nazwa null terminated string.C-łańcuchy osiągają długość 65535 znaków plus końcowy, tzw. NULL-bajt. Są one dynamicznie alokowane w pamięci, zaś ilość pamięci zajmowanej przez C-łańcuch jest automatycznie dostosowywana do jego długości, co w pełni odpowiada architekturze Windows. Parametrowi dwCreationDistribution należy przypisać właściwość OPEN_EXISTING — otwórz istniejący (port). Pozostałym przyporządkujemy następujące wartości: DWORD ShareMode = 0 (FALSE); LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL; DWORD dwFlagAndAttributes = 0 (FALSE); HANDLE hTemplateFile = NULL. Funkcja GetCommState() Funkcja zwraca ustawienia portu ostatnio zapamiętane w strukturze DCB: BOOL GetCommState(HANDLE hCommDev, LPDCB lpdcb),
  • 18. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 71 gdzie: hCommDev jest identyfikatorem danego portu, CreateFile() zwraca nam ten identyfikator, a lpdcb jest wskaźnikiem do struktury DCB zawierającej informację o aktualnych ustawieniach parametrów łącza szeregowego. Funkcja GetCommState() (jak i wszystkie inne typu BOOL) zwraca wartość TRUE w przypad- ku pomyślnego jej wykonania, ewentualnie wartość FALSE w sytuacji przeciwnej. Funkcja SetCommState() Wybrany przez nas port szeregowy ostatecznie skonfigurujemy zgodnie ze specyfikacją struktury DCB za pomocą funkcji SetCommState(), która reinicjalizuje i uaktualnia wszyst- kie dostępne parametry w ustawieniach łącza szeregowego: BOOL SetCommState(HANDLE hCommDev, LPDCB lpdcb) Jednak tutaj parametr, na który wskazuje lpdcb, musi już zawierać informacje o nowych, wybranych przez nas parametrach ustawień portu komunikacyjnego. Należy też pamiętać, że funkcja SetCommState() nie zostanie wykonana pomyślnie, jeżeli posługując się struk- turą DCB, element XonChar ustalimy identycznie z XoffChar. Funkcja CloseHandle() Przed zakończeniem działania aplikacji otwarty port szeregowy należy koniecznie zamknąć i zwolnić obszar pamięci przydzielony na jego identyfikator, korzystając z: BOOL CloseHandle(HANDLE hCommDev) We wszystkich przedstawionych powyżej funkcjach hCommDev w pełni identyfikuje dany port szeregowy, zawierając kompletną informację o tym, do którego łącza szeregowego będziemy wysyłać komunikaty. Ponieważ funkcje te mogą obsługiwać komunikaty wysyłane do wielu portów komunikacyjnych (jak również odbierane od wielu portów), zatem każdy otwarty i zainicjalizowany port szeregowy będzie identyfikowany właśnie za pomocą swojego własnego hCommDev. Nie należy przydzielać tego samego identyfikato- ra do dwóch różnych portów komunikacyjnych, tak samo jak nie należy z jednym portem kojarzyć dwóch różnych identyfikatorów. Przy pisaniu aplikacji obsługujących łącze szeregowe należy koniecznie zamknąć port przed opuszczeniem programu. W razie korzystania z zegara systemowego przy ob- słudze RS-a lub techniki programowania wielowątkowego trzeba pamiętać, że samo zamknięcie aplikacji nie powoduje automatycznego zamknięcia portu. Jego identyfi- kator dalej będzie przechowywany w pamięci. W pewnych przypadkach aplikacja z niezamkniętym portem szeregowym może stać się programem rezydentnym i uniemożliwić powtórne otwarcie wybranego portu. Dobrym zwyczajem jest w pierwszej kolejności zaprojektowanie funkcji lub procedury obsługi zdarzenia zamykającego otwarty port. System operacyjny powinien być zawsze poin- formowany o fakcie zamknięcia portu.
  • 19. 72 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera W praktyce zdarzają się jednak sytuacje, w których zamknięcie portu okaże się niemoż- liwe, np. z powodu jakiegoś błędu w algorytmie lub niewłaściwego sposobu wywołania danej funkcji. Mówimy wówczas, że program się załamał lub zawiesił. Ten problem powtarza się często w trakcie testowania programów komunikacyjnych. Nie należy wów- czas od razu używać kombinacji klawiszy Ctrl, Alt, Del. W takich przypadkach wygodniej jest rozwinąć z głównego menu opcję Project oraz wybrać Compile Unit, tak jak poka- zano to na rysunku 5.1. Nazwa działającej aplikacji powinna się pojawić na dolnym pasku zadań. Rysunek 5.1. Przykładowy sposób wstrzymywania działania aplikacji z otwartym portem szeregowym Po pojawieniu się informacji Debug session in progress. Terminate? (Usuwanie sesji w toku. Zakończyć?) (rysunek 5.2) należy nacisnąć przycisk OK. Po kolejnym komunikacie (rysunek 5.3) znów należy dokonać potwierdzenia. Tak postępując, w większości przypadków odzyskamy program oraz odblokujemy łącze szeregowe. Sposób ten okaże się szczególnie przydatny przy kłopotach z aplikacją komunikacyjną korzystającą z komponentu typu TTimer, generującego zdarzenia w rów- nych odstępach czasu. Może oczywiście zdarzyć się sytuacja, w której nie będziemy w stanie powtórnie skompilować programu i samodzielnie prawidłowo zamknąć portu komunikacyjnego. Wówczas program należy usunąć z pamięci poleceniem menu Run/Program Reset.
  • 20. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 73 Rysunek 5.2. Okno dialogowe Debug session Rysunek 5.3. Kompilacja projektu
  • 21. 74 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Testowanie portu szeregowego Mając na uwadze wszystko, co powiedzieliśmy do tej pory, spróbujemy napisać w C++Bu- ilderze prosty program wykorzystujący przedstawione powyżej funkcje Windows API oraz niektóre zasoby struktury DCB. Zadaniem naszej aplikacji będzie ustawienie wybranych parametrów danego portu szere- gowego, otwarcie go oraz odczytanie nowych ustawień. W tym celu stwórzmy nową standardową aplikację (polecenie File — New Application). Niech jej formularz składa się z dwóch przycisków klasy TButton, pięciu komponentów klasy TEdit oraz pięciu TLabel. Korzystając z inspektora obiektów (Object Inspector) oraz z karty własności (Properties), własność Name przycisku Button1 zmieńmy na CloseComm, zaś jego własność Caption na &Zamknij. Podobnie własność Name przycisku Button2 zmieńmy na OpenComm, zaś Cap- tion na &Otwórz port. Własności Caption komponentów z klas TLabel zmieńmy odpo- wiednio na Prędkość transmisji, Liczbę bitów danych, Parzystość, Bity stopu, Linia DTR. Własności Text komponentów klasy TEdit wyczyśćmy. Formularz naszej aplikacji, wyglądającej podobnie jak na rysunku 5.4, znajduje się na dołączonym CD w katalogu KODYBUILDERR05P05_01. Na listingu 5.1 pokazano kod głównego modułu omawianej aplikacji. Rysunek 5.4. Formularz główny projektu Projekt_05_01.bpr Listing 5.1. Kod głównego modułu aplikacji testującej podstawowe parametry transmisji portu szeregowego #include <vcl.h> #pragma hdrstop #include "Unit_05_01.h" #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; HANDLE hCommDev; // identyfikator portu szeregowego // void *hCommDev; DCB dcb; // struktura kontroli portu
  • 22. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 75 LPCTSTR portName = "COM2"; // wskaźnik do nazwy portu // const char *portName = "COM2"; LPCTSTR sbuffer2 = "Uwaga!"; LPCTSTR sbuffer1 = "Niewłaściwa nazwa portu lub port jest" " aktywny."; //-------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //----funkcja zamyka otwarty port szeregowy----------------- BOOL __fastcall closeSerialPort(HANDLE hCommDev) { if ((hCommDev == 0) || (hCommDev == INVALID_HANDLE_VALUE)) return FALSE; else { CloseHandle(hCommDev); return TRUE; } } //-----zamknięcie portu i aplikacji--------------------------- void __fastcall TForm1::CloseCommClick(TObject *Sender) { closeSerialPort(hCommDev); Application->Terminate(); } //---otwarcie portu i ustawienie jego parametrów--------------- void __fastcall TForm1::OpenCommClick(TObject *Sender) { hCommDev = CreateFile(portName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hCommDev != INVALID_HANDLE_VALUE) // sprawdza, czy port jest otwarty prawidłowo { dcb.DCBlength = sizeof(dcb); // aktualny rozmiar // struktury DCB GetCommState(hCommDev, &dcb); // udostępnienie aktualnych // parametrów DCB dcb.BaudRate = CBR_1200; // prędkość transmisji dcb.fParity = TRUE; // sprawdzanie parzystości dcb.Parity = NOPARITY; // ustawienie parzystości dcb.StopBits = TWOSTOPBITS; // bity stopu dcb.ByteSize = 7; // bity danych dcb.fDtrControl = 1; // np. kontrola linii DTR SetCommState(hCommDev, &dcb); // reinicjalizacja DCB } else { switch ((int)hCommDev) { case IE_BADID:
  • 23. 76 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera // W przypadku błędnej identyfikacji portu // BADIDentify pokaż komunikat MessageBox(NULL, sbuffer1, sbuffer2, MB_OK); break; }; } //--sprawdzenie i wyświetlenie ustawionej prędkości------ switch (dcb.BaudRate) { case CBR_9600: Edit1->Text = IntToStr(dcb.BaudRate); break; case CBR_1200: Edit1->Text = IntToStr(dcb.BaudRate); break; case CBR_300: Edit1->Text = IntToStr(dcb.BaudRate); break; case CBR_110: Edit1->Text = IntToStr(dcb.BaudRate); break; } //--sprawdzenie i wyświetlenie ustawionych bitów danych- switch (dcb.ByteSize) { case 8: Edit2->Text = IntToStr(dcb.ByteSize); break; case 7: Edit2->Text = IntToStr(dcb.ByteSize); break; case 6: Edit2->Text = IntToStr(dcb.ByteSize); break; case 5: Edit2->Text = IntToStr(dcb.ByteSize); break; } //--sprawdzenie i wyświetlenie ustawionej parzystości---- switch (dcb.Parity) { case NOPARITY: Edit3->Text = "Brak"; break; case ODDPARITY: Edit3->Text = "Nieparzysta"; break; case EVENPARITY: Edit3->Text = "Parzysta"; break; case MARKPARITY: Edit3->Text = "Znacznik: 1"; break; } //--sprawdzenie i wyświetlenie ustawionych bitów stopu--- switch (dcb.StopBits)
  • 24. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 77 { case ONESTOPBIT: Edit4->Text = "1"; break; case TWOSTOPBITS: Edit4->Text = "2"; break; case ONE5STOPBITS: Edit4->Text = "1.5"; break; } //--sprawdzenie i wyświetlenie stanu linii DTR----------- switch (dcb.fDtrControl) { case DTR_CONTROL_DISABLE: Edit5->Text = "Nieaktywna"; break; case DTR_CONTROL_ENABLE: Edit5->Text = "Aktywna"; break; case DTR_CONTROL_HANDSHAKE: Edit5->Text = "Handshaking"; break; } } //----------------------------------------------------------- void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { Action=caFree; } //-------------------------------------------------------------- Stworzyliśmy zatem bardzo prostą, wręcz „dydaktyczną” aplikację, ale taki właśnie był nasz cel. Możemy zauważyć, że obsługa tego programu sprowadza się do wywołania funkcji obsługi zdarzenia OpenCommClick(). Naciśnięcie przycisku Otwórz port powoduje automatyczne skonfigurowanie wybranego wcześniej portu szeregowego oraz odczytanie jego aktualnie wybranych ustawień. Dobrze byłoby, gdyby Czytelnik spróbował samo- dzielnie skonfigurować port z większą liczbą parametrów, a następnie je odczytał. Nabiera się przez to większej wprawy w manipulowaniu znacznikami struktury DCB. Zamknięcie portu i aplikacji nastąpi po wywołaniu funkcji obsługi zdarzenia CloseCommClick(), w której z kolei dokonujemy wywołania funkcji CloseSerialPort() zamykającej port sze- regowy i aplikację. Przyglądając się kodowi funkcji obsługi zdarzenia OpenCommClick(), zauważymy, że tuż po wywołaniu CreateFile() zastosowaliśmy następującą instrukcję warunkową sprawdzającą, czy funkcja ta zwróciła prawidłowy identyfikator zadeklaro- wanego portu: if (hCommDev != INVALID_HANDLE_VALUE) { ... } else { switch ((int)hCommDev) {
  • 25. 78 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera case IE_BADID: // W przypadku błędnej identyfikacji portu // BADIDentify pokaż komunikat ... break; }; } Łatwo można się przekonać, że w przypadku błędnego przydzielenia identyfikatora dla portu COMn, funkcja CreateFile() zwraca wartość INVALID_HANDLE_ VALUE (niewłaściwa wartość identyfikatora) zdefiniowaną w Windows API. Jest to bardzo skuteczna metoda zabezpieczenia się przed próbą otwarcia nieistniejącego lub już otwartego portu (urządze- nia). Zauważmy też, że aby odczytać aktualną wartość hCommDev, musieliśmy wymusić przekształcenie typów, używając operacji rzutowania (int)hCommDev. Każdy już się chyba przekonał, że identyfikator czy — jak kto woli — uchwyt typu HANDLE nie jest żadnym numerem bezpośrednio nadanym portowi komunikacyjnemu, lokalizuje jedynie unikalny obszar pamięci, do którego należy się odwołać, by uzyskać dostęp do danego urządzenia. Raz otwartego portu komunikacyjnego nie można otworzyć powtórnie, podobnie jak nie uda się otworzyć już otwartego okna. Nie można też powtórnie skorzystać z ob- szaru pamięci, z którego właśnie korzystamy. Jeżeli mimo wszystko port nie został otwarty prawidłowo, dobrze by było, gdyby apli- kacja powiadomiła nas o tym fakcie. W tym celu można skorzystać z komunikatów Windows typu IE_ (ang. Identify Error — błąd identyfikacji portu) urządzenia lub jego ustawień. W tabeli 5.6 przedstawiono najczęściej otrzymywane od Windows komunikaty tego typu. Tabela 5.6. Najczęściej używane komunikaty błędnej identyfikacji ustawień portu szeregowego Identyfikacja komunikatu Wartość Znaczenie IE_BADID –1 Niewłaściwa identyfikacja urządzenia IE_BAUDRATE –12 Błędnie określona szybkość transmisji IE_BYTESIZE –11 Błędnie określona liczba bitów danych IE_DEFAULT –5 Niewłaściwie określone parametry domyślne urządzenia IE_HARDWARE –10 Odbiornik jest zablokowany IE_MEMORY –4 Niewłaściwie ustalony rozmiar buforów IE_NOPEN –3 Urządzenie nie jest otwarte do transmisji IE_OPEN –2 Urządzenie pozostaje otwarte Struktura COMMPROP W celu dokładniejszego zapoznania się z możliwościami testowania systemów komunika- cyjnych dostępnych w Windows w tabeli 5.7 przedstawiono bardzo użyteczną strukturę oferowaną przez API. Zawarte w niej informacje mogą być wykorzystywane do pełnego odczytywania wszystkich istotnych parametrów interesującego nas łącza komunikacyj- nego oraz usług potencjalnie przez nie oferowanych.
  • 26. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 79 Tabela 5.7. Zasoby struktury COMMPROP Zawartość elementu, maska Typ Element struktury Znaczenie określająca włączony bit WORD wPacketLength Określa (w bajtach) rozmiar Należy odczytać, zależy też porcji pakietu danych od typu sterownika WORD wPacketVersion Wersja struktury Nr 2 w Win 9x, XP DWORD dwServiceMask Określenie maski bitowej SP_SERIALCOMM jest zawsze wskazującej na typ aktualnie określone dostępnej usługi komunikacyjnej DWORD dwReserved1 Zarezerwowane, nieużywane DWORD dwMaxTxQueue Maksymalny rozmiar 0 oznacza, że nie ustalono wewnętrznego bufora wyjściowego maksymalnej wartości nadajnika (w bajtach) DWORD dwMaxRxQueue Maksymalny rozmiar 0 oznacza, że nie ustalono wewnętrznego bufora wejściowego maksymalnej wartości odbiornika (w bajtach) DWORD dwMaxBaud Maksymalna dostępna prędkość BAUD_075 75 b/s transmisji w bitach na sekundę BAUD_110 110 b/s BAUD_134_5 134.5 b/s BAUD_150 150 b/s BAUD_300 300 b/s BAUD_600 600 b/s BAUD_1200 1200 b/s BAUD_1800 1800 b/s BAUD_2400 2400 b/s BAUD_4800 4800 b/s BAUD_7200 7200 b/s BAUD_9600 9600 b/s BAUD_14400 14 400 b/s BAUD_19200 19 200 b/s BAUD_38400 38 400 b/s BAUD_56K 56K b/s BAUD_57600 57 600 b/s BAUD_115200 115 200 b/s BAUD_128K 128K b/s BAUD_USER programowalne
  • 27. 80 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Tabela 5.7. Zasoby struktury COMMPROP — ciąg dalszy Zawartość elementu, maska Typ Element struktury Znaczenie określająca włączony bit DWORD dwProvSubType Typ usługi komunikacyjnej PST_FAX — faks PST_LAT — protokół LAT (Local — Area Transport) PST_MODEM — modem PST_NETWORK_BRIDGE — niewyspecyfikowana sieć PST_PARALLELPORT — port równoległy PST_RS232 RS 232 PST_RS422 RS 422 PST_RS423 RS 423 PST_RS449 RS 449 PST_SCANNER — skaner PST_TCPIP_TELNET — protokół TCP/IP PST_UNSPECIFIED — brak specyfikacji PST_X25 — protokół X.25 DWORD DwSettableParams Specyfikacja maski bitowej SP_BAUD — prędkość identyfikującej parametry SP_DATABITS — długość słowa transmisji podlegające danych ewentualnym zmianom SP_HANDSHAKING — kontrola przepływu danych SP_PARITY — parzystość SP_PARITY_CHECK — sprawdzanie parzystości SP_RLSD — sygnał RLSD SP_STOPBITS — bity stopu W Windows API COMMPROP deklaruje się następująco: typedef struct _COMMPROP { WORD wPacketLength; ... } COMMPROP; Powyższa deklaracja tworzy nowe słowo kluczowe typu COMMPROP (struktura). Zbudujmy teraz aplikację, za pomocą której będziemy mogli selektywnie odczytywać stan poszczególnych masek bitowych udostępnianych przez COMMPROP.
  • 28. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 81 Tabela 5.7. Zasoby struktury COMMPROP — ciąg dalszy Zawartość elementu, maska Typ Element struktury Znaczenie określająca włączony bit DWORD dwProvCapabilities Określa maskę bitową PCF_16BITMODE — tryb 16-bitowy identyfikującą rodzaj funkcji PCF_DTRDSR — kontrola DTR-DSR udostępnianych przez usługę komunikacyjną (dostarczyciela PCF_INTTIMEOUTS — czas usługi) przeterminowania PCF_PARITY_CHECK — sprawdzanie parzystości PCF_RLSD — kontrola RLSD PCF_RTSCTS — kontrola RTS-CTS PCF_SETXCHAR — możliwość użycia protokołu XON/XOFF PCF_SPECIALCHARS — specjalny znak PCF_TOTALTIMEOUTS — kontrola czasu przeterminowania transmisji PCF_XONXOFF — podtrzymanie protokołu XON-XOFF DWORD dwSettableBaud Specyfikacja maski bitowej Tak samo jak w dwMaxBaud umożliwiającej ustawienie prędkości transmisji WORD wSettableData Specyfikacja maski bitowej DATABITS_5 identyfikującej możliwe do DATABITS_6 użycia długości słowa danych DATABITS_7 DATABITS_8 DATABITS_16 DATABITS_16X szczególna długość słowa danych DWORD dwCurrentTxQueue Aktualny maksymalny rozmiar 0 oznacza, że wartość ta nie jest wewnętrznego bufora wyjściowego aktualnie dostępna nadajnika (w bajtach) WORD wSettableStopParity Specyfikacja maski bitowej STOPBITS_10 — 1 bit stopu identyfikującej możliwe do STOPBITS_15 — 1,5 bitu użycia wartości bitów stopu i kontroli parzystości STOPBITS_20 — 2 bity PARITY_NONE — brak PARITY_ODD — nieparzysta PARITY_EVEN — parzysta PARITY_MARK — 1 PARITY_SPACE — 0 DWORD dwCurrentRxQueue Aktualny maksymalny rozmiar 0 oznacza, że wartość ta nie jest wewnętrznego bufora wejściowego aktualnie dostępna nadajnika (w bajtach)
  • 29. 82 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Tabela 5.7. Zasoby struktury COMMPROP — ciąg dalszy Zawartość elementu, maska Typ Element struktury Znaczenie określająca włączony bit DWORD dwProvSpec1 Specyfikacja formatu danych W zależności od dwProvSubType wymaganych przez daną usługę aplikacje powinny ignorować komunikacyjną ten człon, chyba że zawierają szczegółowe informacje odnośnie do formatu danych wymaganych przez dostarczyciela usługi DWORD dwProvSpec2 Jak wyżej WCHAR wcProvChar[1] Jak wyżej Jeżeli dwProvSubType przypisano PST_MODEM, musi nastąpić odwołanie do struktur 2 MODEMDEVCAPS oraz MODEMSETTINGS ; dwProvSpec1 i dwProvSpec2 nie są wówczas używane Wykorzystamy tu znany nam już proces maskowania z użyciem operatora iloczynu bitowego & (bitowe i). Program będzie odczytywał wartość wybranego elementu struktu- ry, a następnie poprzez wybranie kolejnych masek będzie selektywnie sprawdzał, czy włączone są konkretne bity odpowiedzialne za pewne parametry transmisji. Omawiany projekt znajduje się na dołączonym CD w katalogu KODYBUILDERR05P05_02. Do testowania wybierzmy elementy: dwSettableParams, w XP reprezentowany na 32 bitach, oraz wSettableData i wSettableStopParity, reprezentowane na 16 bitach. Zasto- sujemy nieco odbiegający od przedstawionego wcześniej projekt formularza. Składać się on będzie z dwóch dobrze nam już znanych przycisków reprezentujących zdarzenia polegające na otwarciu oraz zamknięciu portu. Zastosowano ponadto dwa komponenty klasy TTrackBar, dwa TEdit oraz dwa typu TLabel, tak jak pokazuje to rysunek 5.5. Obsługa zdarzenia TrackBar1Change() polega na wybraniu interesującego nas elementu struktury COMMPROP oraz odczytaniu jego aktualnej wartości. Jeżeli zechcemy sprawdzić, czy włączony jest konkretny bit reprezentujący wybrany atrybut transmisji przechowywa- ny w danym elemencie struktury, wystarczy przesunąć wskaźnik uruchamiający funkcję obsługi zdarzenia TrackBar2Change(). Funkcja GetCommProperties() Funkcją, która zwraca aktualne właściwości portu komunikacyjnego identyfikowanego przez hCommDev, będzie: BOOL GetCommProperties(HANDLE hCommDev, LPCOMMPROP lpCommProp); lpCommProp jest wskaźnikiem do struktury COMMPROP, której format danych w ogólnym przypadku należy najpierw zainicjalizować, po uprzednim wpisaniu do pola wPac- ketLength aktualnego rozmiaru struktury: 2 Miłośnikom modemów specyfikację tych struktur prezentujemy w dodatku B.
  • 30. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 83 Rysunek 5.5. Formularz główny uruchomionego projektu Projekt_R05_02.bpr commprop.wPacketLength = sizeof(COMMPROP); //... CommProp.dwProvSpec1 = COMMPROP_INITIALIZED; Informacje tam zawarte mogą być pomocne przy odwoływaniu się do rodziny funkcji SetCommState(), SetCommTimeouts() lub SetupComm(). Na listingu 5.2 pokazano kod głównego modułu omawianej aplikacji. Listing 5.2. Zawartość modułu Unit_R05_02.cpp #include <vcl.h> #pragma hdrstop #include "Unit_05_02.h" #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; HANDLE hCommDev; // identyfikator portu COMMPROP commprop; // właściwości portu LPCTSTR portName = "COM2"; // wskaźnik do nazwy portu // szeregowego LPCTSTR sbuffer1 = "Niewłaściwa nazwa portu lub port jest" " aktywny."; //-------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //-------funkcja zamyka otwarty port szeregowy------------------ BOOL __fastcall closeSerialPort(HANDLE hCommDev) { if ((hCommDev == 0) || (hCommDev == INVALID_HANDLE_VALUE)) return FALSE; else { CloseHandle(hCommDev); return TRUE;
  • 31. 84 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera } } //-------zamknięcie portu i aplikacji---------------------------- void __fastcall TForm1::CloseCommClick(TObject *Sender) { closeSerialPort(hCommDev); Application->Terminate(); } //----otwarcie portu i ustawienie jego parametrów--------------- void __fastcall TForm1::OpenCommClick(TObject *Sender) { hCommDev = CreateFile(portName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hCommDev != INVALID_HANDLE_VALUE) { // sprawdza, czy port jest otwarty prawidłowo commprop.wPacketLength = sizeof(COMMPROP); commprop.dwProvSpec1 = COMMPROP_INITIALIZED; // inicjalizuje format danych usługi. // Port szeregowy jest zawsze dostępny memset(&commprop, 0, sizeof(COMMPROP)); GetCommProperties(hCommDev, &commprop); } else { switch ((int)hCommDev) { case IE_BADID: ShowMessage(sbuffer1); break; }; } } //------wybrane maski bitowe------------------------------------ void __fastcall TForm1::TrackBar1Change(TObject *Sender) { switch (TrackBar1->Position) { case 1: { TrackBar2->Max = 7; Label1->Caption = "dwSettableParams"; Edit1->Text=IntToStr(commprop.dwSettableParams); break; } case 2: { TrackBar2->Max = 6; Label1->Caption = "wSettableData"; Edit1->Text=IntToStr(commprop.wSettableData); break; } case 3: { TrackBar2->Max = 8; Label1->Caption = "wSettableStopParity"; Edit1->Text=IntToStr(commprop.wSettableStopParity); break; } } // koniec switch }
  • 32. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 85 //---------------zawartość maski--------------------------- void __fastcall TForm1::TrackBar2Change(TObject *Sender) { if (TrackBar1->Position == 1) { switch (TrackBar2->Position) { case 1: { Label2->Caption = "SP_PARITY"; Edit2->Text=IntToStr(commprop.dwSettableParams & SP_PARITY); break; } case 2: { Label2->Caption = "SP_BAUD"; Edit2->Text=IntToStr(commprop.dwSettableParams & SP_BAUD); break; } case 3: { Label2->Caption = "SP_DATABITS"; Edit2->Text=IntToStr(commprop.dwSettableParams & SP_DATABITS); break; } case 4: { Label2->Caption = "SP_STOPBITS"; Edit2->Text=IntToStr(commprop.dwSettableParams & SP_STOPBITS); break; } case 5: { Label2->Caption = "SP_HANDSHAKING"; Edit2->Text=IntToStr(commprop.dwSettableParams & SP_HANDSHAKING); break; } case 6: { Label2->Caption = "SP_PARITY_CHECK"; Edit2->Text=IntToStr(commprop.dwSettableParams & SP_PARITY_CHECK); break; } case 7: { Label2->Caption = "SP_RLSD"; Edit2->Text=IntToStr(commprop.dwSettableParams & SP_RLSD); break; } } // koniec switch } // koniec if if (TrackBar1->Position == 2) { switch (TrackBar2->Position) { case 1: { Label2->Caption = "DATABITS_5"; Edit2->Text=IntToStr(commprop.wSettableData & DATABITS_5); break; } case 2: { Label2->Caption = "DATABITS_6";
  • 33. 86 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Edit2->Text=IntToStr(commprop.wSettableData & DATABITS_6); break; } case 3: { Label2->Caption = "DATABITS_7"; Edit2->Text=IntToStr(commprop.wSettableData & DATABITS_7); break; } case 4: { Label2->Caption = "DATABITS_8"; Edit2->Text=IntToStr(commprop.wSettableData & DATABITS_8); break; } case 5: { Label2->Caption = "DATABITS_16"; Edit2->Text=IntToStr(commprop.wSettableData & DATABITS_16); break; } case 6: { Label2->Caption = "DATABITS_16X"; Edit2->Text=IntToStr(commprop.wSettableData & DATABITS_16X); break; } } // koniec switch } // koniec if if (TrackBar1->Position == 3) { switch (TrackBar2->Position) { case 1: { Label2->Caption = "STOPBITS_10"; Edit2->Text=IntToStr(commprop.wSettableStopParity & STOPBITS_10); break; } case 2: { Label2->Caption = "STOPBITS_15"; Edit2->Text=IntToStr(commprop.wSettableStopParity & STOPBITS_15); break; } case 3: { Label2->Caption = "STOPBITS_20"; Edit2->Text=IntToStr(commprop.wSettableStopParity & STOPBITS_20); break; } case 4: { Label2->Caption = "PARITY_NONE"; Edit2->Text=IntToStr(commprop.wSettableStopParity & PARITY_NONE); break; } case 5: { Label2->Caption = "PARITY_ODD";
  • 34. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 87 Edit2->Text=IntToStr(commprop.wSettableStopParity & PARITY_ODD); break; } case 6: { Label2->Caption = "PARITY_EVEN"; Edit2->Text=IntToStr(commprop.wSettableStopParity & PARITY_EVEN); break; } case 7: { Label2->Caption = "PARITY_MARK"; Edit2->Text=IntToStr(commprop.wSettableStopParity & PARITY_MARK); break; } case 8: { Label2->Caption = "PARITY_SPACE"; Edit2->Text=IntToStr(commprop.wSettableStopParity & PARITY_SPACE); break; } } // koniec switch } // koniec if } //-------------------------------------------------------------- void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { Action=caFree; } //-------------------------------------------------------------- Dla przykładu rozpatrzmy parametr dwSettableParams typu DWORD, w XP reprezentowany na 32 bitach. Odczytując odpowiednią wartość, przekonaliśmy się, że cała zawarta tam informacja zapisana jest na 7 pierwszych bitach dwSettableParams. Użyliśmy operatora &, aby sprawdzić, czy włączone są poszczególne bity reprezentujące atrybuty związane z konkretnymi parametrami komunikacyjnymi. Patrząc na wartości w postaci binarnej, łatwo zorientujemy się, jaki jest aktualny stan logiczny poszczegól- nych bitów zawartych w tej zmiennej i za co są one odpowiedzialne, tak jak pokazano to w zawartości tabeli 5.8. W analogiczny sposób możemy przetestować wszystkie maski bitowe udostępniane przez COMMPROP, odpowiadające właściwym pozycjom konkretnych bitów. Jeżeli jako rezultat iloczynu bitowego wartości elementu struktury z maską określającą włączony bit otrzy- mamy wartość 0, oznaczać to będzie, że testowany bit jest wyłączony i dany parametr komunikacyjny nie jest aktualnie dostępny. Lepiej znający temat Czytelnicy zapewne już zorientowali się, jakie niespodzianki oferuje nam ta struktura. Manipulowanie bitami jest tu sprawą dobrania odpowiednich operatorów przesuwania, maskowania i dopełniania. Można np. skasować bity SP_PARITY i SP_BAUD w elemencie dwSettableParams: CommProp.dwSettableParams &= ~(SP_PARITY | SP_BAUD);
  • 35. 88 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera Tabela 5.8. Maski bitowe elementu dwSettableParams struktury COMMPROP Wartość Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0 dwSettableParams 127 0 1 1 1 1 1 1 1 Maska Rezultat maskowania SP_PARITY 1 0 0 0 0 0 0 0 1 SP_BAUD 2 0 0 0 0 0 0 1 0 SP_DATABITS 4 0 0 0 0 0 1 0 0 SP_STOPBITS 8 0 0 0 0 1 0 0 0 SP_HANDSHAKING 16 0 0 0 1 0 0 0 0 SP_PARITY_CHECK 32 0 0 1 0 0 0 0 0 SP_RLSD 64 0 1 0 0 0 0 0 0 Podobnie w jakimś fragmencie aplikacji można zastosować warunek: if ((CommProp.dwSettableParams & (SP_PARITY | SP_BAUD)) == 0) { ... } który będzie prawdziwy, gdy oba bity będą skasowane. Jednak osobom mniej zaawanso- wanym w operacjach bitowych odradzałbym jakiekolwiek próby ingerowania w zawar- tość COMMPROP. Struktura COMMCONFIG Struktura COMMCONFIG zawiera informacje o stanie konfiguracji danego urządzenia komu- nikacyjnego. Tabela 5.9 prezentuje jej zasoby. Windows API COMMCONFIG deklaruje następująco: typedef struct _COMM_CONFIG { DWORD dwSize; ... } COMMCONFIG, *LPCOMMCONFIG; Powyższa deklaracja tworzy dwa nowe słowa kluczowe typu COMMCONFIG (struktura) oraz LPCOMMCONFIG (wskaźnik do struktury). Funkcje GetCommConfig() i SetCommConfig() Aktualną konfigurację łącza komunikacyjnego odczytamy, korzystając z funkcji API: BOOL GetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, LPDWORD lpdwSize); gdzie lpCC wskazuje na strukturę COMMCONFIG, zaś lpdwSize jest wskaźnikiem do zmiennej określającej rozmiar struktury.
  • 36. Rozdział 5. ♦ Programowa obsługa interfejsu RS 232C w Windows 89 Tabela 5.9. Specyfikacja struktury COMMCONFIG Typ Element struktury Znaczenie Zawartość DWORD dwSize Rozmiar struktury w bajtach Należy wpisać WORD wVersion Wersja struktury Należy odczytać WORD wReserved Zarezerwowane DCB dcb Struktura kontroli portu szeregowego Patrz DCB DWORD dwProviderSubType Identyfikacja typu dostarczanej usługi Patrz COMMPROP komunikacyjnej, a tym samym wymaganego formatu danych DWORD dwProviderOffset Określenie offsetu dla danych wymaganych 0, jeżeli nie określono przez dostarczyciela usługi komunikacyjnej; typu danych offset (tzw. przesunięcie) określony jest zwykle w stosunku do początku struktury DWORD dwProviderSize Rozmiar danych (w bajtach) wymaganych Zależnie od typu usługi przez usługę komunikacyjną (dostarczyciela usługi) WCHAR wcProviderData[1] Dane dostarczane wraz z usługą (ponieważ Jeżeli ustalono typ przewidywane jest w przyszłości usługi: PST_RS232 uzupełnienie struktury, aplikacja powinna lub PST_PARALLELPORT, używać dwProviderOffset w celu człon ten jest pomijany; określenia położenia wcProviderData) jeżeli ustalono PST_MODEM, należy odwołać się do MODEMSETTINGS Bieżącą konfigurację portu komunikacyjnego zapiszemy za pomocą: BOOL SetCommConfig(HANDLE hCommDev, LPBYTE lpCC, DWORD dwSize); gdzie lpCC jest wskaźnikiem do COMMCONFIG, zaś dwSize określa (w bajtach) rozmiar struktury wskazywanej przez lpCC. Przed przekazaniem tej struktury jako parametru nale- ży do elementu dwSize wpisać wartość równą sizeof(COMMCONFIG). Funkcja CommConfigDialog() Funkcja wyświetla okno dialogowe zawierające aktualne ustawienia parametrów portu szeregowego. BOOL CommConfigDialog(LPTSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC); lpszName jest wskaźnikiem do łańcucha znaków określającego nazwę portu, hWnd jest identyfikatorem właściciela aktualnie wyświetlanego okna dialogowego, a lpCC wskazuje na strukturę COMMCONFIG. Użycie tej funkcji, np. w kontekście obsługi wybranego zdarze- nia, może wyglądać następująco: //-------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) {
  • 37. 90 RS 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera LPCOMMCONFIG commconfig; CommConfigDialog("COM2", NULL, commconfig); } //-------------------------------------------------------------- Struktura COMMTIMEOUTS Zasoby struktury COMMTIMEOUTS przedstawione są w tabeli 5.10. Udostępniają one infor- macje o tzw. czasach przeterminowania transmisji w trakcie przesyłania danych (ang. time-out of transmission). Jest to ważny termin, z którym niektórzy na pewno już się zetknęli. W trakcie transmisji asynchronicznej COMMTIMEOUTS determinuje zachowanie się takich funkcji jak ReadFile() czy WriteFile(). Tabela 5.10. Informacje zawarte w strukturze COMMTIMEOUTS Typ Element struktury Właściwości DWORD ReadIntervalTimeout Określa maksymalny czas (w milisekundach) pomiędzy pojawieniem się na linii komunikacyjnej dwu znaków. W trakcie wykonywania ReadFile() czas jest liczony od momentu pojawienia się pierwszego znaku. Jeżeli przedział czasu pomiędzy nadejściem dwu znaków przekracza wartość ReadIntervalTimeout, oznacza to, że operacja ReadFile() jest zakończona. Wartość 0 oznacza, że nie ustalono wymaganego okresu pomiędzy nadejściem dwu kolejnych znaków. Przypisanie wartości MAXDWORD powoduje, że czytany znak jest pobierany z bufora natychmiast po tym, jak się tam pojawi DWORD ReadTotalTimeoutMultiplier Określa mnożnik (w milisekundach) użyty do obliczenia całkowitego przedziału czasu (przeterminowanie) dla operacji czytania (odbioru). Dla wszystkich takich operacji wartość ta jest mnożona przez liczbę bajtów przewidzianą do odebrania z dysku lub łącza komunikacyjnego. DWORD ReadTotalTimeoutConstant Określa stałą (w milisekundach) użytą do obliczania czasu przeterminowania operacji czytania. Dla wszystkich takich operacji wartość ta jest dodawana do ReadTotalTimeoutMultiplier i do oczekiwanej liczby nadchodzących bajtów DWORD WriteTotalTimeoutMultiplier Określa mnożnik (w milisekundach) użyty do obliczenia całkowitego przedziału czasu (przeterminowanie) dla operacji zapisywania (wysyłania). Dla wszystkich takich operacji wartość ta jest mnożona przez liczbę bajtów przewidzianą do wysłania (zapisania). 0 oznacza, że nie ustalono czasu przeterminowania dla operacji zapisu na dysku lub do łącza komunikacyjnego DWORD WriteTotalTimeoutConstant Określa stałą (w milisekundach) użytą do obliczania czasu przeterminowania operacji wysyłania. Dla wszystkich takich operacji wartość ta jest dodawana do WriteTotalTimeoutMultiplier oraz do oczekiwanej liczby wysyłanych bajtów. 0 oznacza, że nie ustalono czasu przeterminowania dla operacji zapisu (wysyłania)