직렬통신컴포넌트의제작 (Creating Serial Communication Components) 이번장에서는윈도우 API 함수를이용해서시리얼통신을제어할수있는방법을소개하고, 이를바탕으로간단한시리얼통신컴포넌트를제작하도록할것이다. 시리얼통신과관련해서는여러가지프리웨어컴포넌트를인터넷에서찾을수있으므로, 자신에게맞는컴포넌트를찾아서사용하는것도하나의방법이될것이다. 그러나, 기본적으로직렬통신포트에접근해서이를사용하는방법은익혀두는것이좋다. 시리얼통신의기초 시리얼포트는데이터를보내고받는두가지일을한다. 이것이대단히간단하게생각되겠지만, 실제로는이를위해서여러가지일들이벌어진다. 시리얼포트는컴퓨터의연산속도에비해훨씬느리게동작하기때문에, 파일등을시리얼포트로보낸다고생각할때적절한버퍼의활용과흐름제어 (flow control) 가필수적이다. 이러한흐름제어의방법으로가장흔히쓰이는것이 RTS/CTS 와 XON/XOFF 제어이다. RTS(request to send) 와 CTS(clear to send) 는시리얼포트의하드웨어적인흐름제어방법이다. 포트의 RTS 라인은원격디바이스의 CTS 라인과연결되어있고, CTS 라인은 RTS 라인과연결되어있다. 원격디바이스가데이터를받을준비가되면 RTS 라인을활성화시키며, 이렇게되면이와연결된 CTS 라인에서데이터를보내기시작한다. 원격디바이스가충분한양의데이터를받으면 RTS 라인을끊게되고, 이것이데이터를보내지말라는신호가된다. 이러한사이클이데이터가모두전송될때까지반복된다. XON/XOFF 방법은소프트웨어적인흐름제어방법으로 XON/XOFF 문자를보내서제어하는방법이다. 일단시리얼포트에서데이터를전송하기시작해서버퍼가차게되면 XOFF 문자 (ASCII 13h) 를전송한다. 이문자를받으면시리얼포트는데이터의전송을일단중단한다. 데이터를계속받아서버퍼에여유가생기면이번에는 XON 문자 (ASCII 11h) 를전송해서시리얼포트가데이터전송을계속하게된다. 이두가지방법은서로다른방식으로결국비슷한역할을하게되는데, 동시에두가지모두를사용할수도있다. 일단데이터가전송되는도중에는보내고, 받은데이터가제대로전송했는지검사하는것도중요하다. 데이터의전송이제대로이루어졌는지검사하는방법에는여러가지가있는데, 그중가장원시적인것인패리티 (parity) 에러를검사하는것이다. 패리티에러검사의방법에는 even, odd, mark, space 등의것이있는데, 실용성이많이떨어지기때문에거의사용되지않는다.
이러한패리티검사방법외에실제로사용되는것으로 CRC(cyclic redundancy check) 검사를많이한다. 이방법은전송된데이터의순서와양을체크섬을이용해서검사하는것으로비교적효과적이면서도정확하다. 흐름제어만큼이나중요한것이연결된두시리얼포트가서로의상태를알아보고이를제어하는방법이다. 여기에대한방법에도하드웨어적인방법과소프트웨어적인방법이있다. 하드웨어적인방법은 DSR(data set ready)/dtr(data terminal ready) 라인을이용하는것이다. 예를들어두개의모뎀이연결되면각각의모뎀은 DTR 라인을활성화시킨다 ( 이를 hi 또는 hot 이라고도한다.). 한모뎀의 DTR 라인은다른모뎀의 DSR 라인과연결되어있는데, 양쪽모뎀이다른모뎀으로부터신호를받게되면바로하드웨어적인핸드쉐이킹을하게된다. 그렇지만이방법은비교적오래된방법으로, 최근에는소프트웨어적인방법이더욱효과적이고더많이쓰인다. DSR/DTR 방법은흐름제어의한수단으로사용되고있다. 소프트웨어적인방법은모뎀이연결될때잡음이많이섞이게되는데흐름제어, 압축레벨, 보드전송율등을조절해서여기에대처하게된다. 간단한시리얼통신에서는이러한방법이사용되지않지만, 파일전송등의비교적복잡한작업을할때에는필수적으로사용된다. Win32 API 에는이러한시리얼통신을지원하기위해비교적많은함수가준비되어있다. 지금부터이들을하나씩알아보고, 이를쉽게사용할수있는컴포넌트를하나만들어보자. 포트열기 가장중요한것이처음으로포트를여는것이다. 이를위해서 CreateFile 함수가사용된다. 이함수의선언부는다음과같다. function CreateFile(lpFileName: PChar; dwdesiredaccess, dwsharemode: Integer; lpsecurityattributes: PSecurityAttributes; dwcreationdisposition, dwflagsandattributes: DWORD; htemplatefile: THandle): THandle; 이함수가성공적으로수행되면시리얼포트에대한핸들을돌려주게된다. 첫번째파라미터로사용되는 lpfilename 에는사용할포트의이름을지정한다, 예를들어 COM1, COM2 등을지정한다. dwdesiredaccess 파라미터에는포트에접근하는방법을지정하는데, 포트를통해서데이터를읽고, 쓰기를모두한다면 GENERIC_READ OR GENERIC_WRITE 로설정한다. dwsharemode 파라미터에는지정한포트를다른어플리케이션과공유할것인지여부를정한다. 보통의경우에는 0 으로지정하여다른어플리케이션이접근할수없도록한다. lpsecurityattibutes 파라미터는핸들에대한보안레벨을지정하는구조체의포인터를넘
겨주게되는데, 보통의경우 nil 로설정한다. dwcreationdisposition 파라미터는윈도우가파일을열거나생성하는방법을지정하는데, 시리얼통신의경우에는파일이언제나지정되어있고, 존재하는상태이므로 OPEN_EXISTING 으로설정된다. dwflagsandattributes 파라미터는시리얼포트가통신하는방법을지정할수있는데, 예를들어 FILE_FLAG_OVERLAPPED 로지정된경우시리얼포트가동시에읽기, 쓰기가가능한비동기 (asynchronous) 통신을지원한다. 보통의경우에는 0 으로지정해서동기 (synchronous) 통신을지정한다. 마지막으로 htemplatefile 파라미터는시리얼통신과아무런관계가없기때문에보통 0 으로설정한다. 장치제어블록 (Device Control Block, DCB) 시리얼포트를제어하는데가장중요한데이터구조가장치제어블록 (DCB) 구조체이다. 이구조체에는시리얼포트에적용할모든설정사항이저장된다. Windows.pas 유닛에선 언되어있는 DCB 구조체의선언부는다음과같다. type TDCB = record DCBLength: DWORD; BaudRate: DWORD; Flags: LongInt; // 비트플래그 //DCB 구조체의크기 //Baud rate wreserved: Word; XONLim: Word; XOFFLim: Word; ByteSize: Byte; Parity: Byte; StopBits: Byte; XONChar: Char; XOFFChar: Char; ErrorChar: Char; EOFChar: Char; EvtChar: Char; //XON 스위치에대한바이트한계 //XOFF 스위치에대한바이트한계 // 바이트의비트수 // 패리티종류 // 스톱비트 //XON 문자 //XOFF 문자 // 패리티에러대체문자 //EOF 문자 // 이벤트문자 wreserved1: Word; DCB 파라미터를얻어오고, 설정하는데사용되는함수가 GetCommState, SetCommState 함수이다. 이들함수에포트에대한핸들을넘겨주면 DCB 구조체의주소를얻을수있다.
이들함수의선언부는다음과같다. function GetCommState(hFile: THandle; var lpdcb: TDCB): BOOL; stdcall; function SetCommState(hFile: THandle; const lpdcb: TDCB): BOOL; stdcall; GetCommTimeouts, SetCommTimeouts 함수 데이터를얻어올때몇가지문제로데이터가전송되지않을수가있다. 예를들어, 불의의사고로선로가끊어진경우시리얼포트는데이터를전송받을수가없다. 이때적절한시간이넘게지나가면더이상데이터를기다리지않는다. 이러한시간을읽거나설정할때에 GetCommTimeouts, SetCommTimeouts 함수를사용한다. 이함수에포트의핸들과 TCommTimeouts 데이터형의레코드를전달하면제한시간이설정된다. 이들의선언부는다음과같다. function GetCommTimeouts(hFile: THandle; var lpcommtimeouts: TCommTimeouts): BOOL; stdcall; function SetCommTimeouts(hFile: THandle; const lpcommtimeouts: TCommTimeouts): BOOL; stdcall; TCommTimeouts = record ReadIntervalTimeout: DWORD; ReadTotalTimeoutMultiplier: DWORD; ReadTotalTimeoutConstant: DWORD; WriteTotalTimeoutMultiplier: DWORD; WriteTotalTimeoutConstant: DWORD; 보통의경우에는마이크로소프트의디폴트값을그대로사용한다. PurgeComm, CloseHandle, ClearCommError 함수 PurgeComm 함수를이용하면읽기, 쓰기를진행하거나취소할수있다. 버퍼를비우게할수도있다. 선언부는다음과같다. 또한입력, 출력 function PurgeComm(hFile: THandle; dwflags: DWORD): BOOL; stdcall;
첫번째파라미터에는포트의핸들을지정하면되고, 두번째파라미터에플래그를설정한다. 플래그로사용할수있는것으로 PURGE_TXABORT, PURGE_TXCLEAR, PURGE_RXABORT, PURGE_RXCLEAR 등이있다. 여기서 abort 는진행중인작업을즉시중지하라는의미이며, clear 는해당버퍼를비우라는의미이다. TX 와 RX 는각각 transfer, receive 를의미한다. 이렇게사용한포트를닫을때에는 CloseHandle 함수를사용한다. 수행되면 True 가반환된다. 선언부는다음과같다. 이함수가성공적으로 function CloseHandle(hFile: THandle): BOOL; stdcall; ClearCommError 함수는지정된장치의현재상태를검사해서에러가있으면이를리포트한다. 또한, 통신에러가발생하면호출되어장치의에러플래그를지우고데이터의읽기, 쓰기작업을계속하게한다. 이함수의 lpstat 파라미터는 TCommStat 구조체의포인터로, 이구조체에발생한에러의내용과현재장치의상태에대한내용이담기게된다. 함수와 TCommStat 구조체의선언부는다음과같다. function ClearCommError(hFile: THandle; var lperrors: DWORD; lpstat: PComStat): BOOL; stdcall; TComStat = record Flags: TCommStateFlags; Reserved: array[0..2] of Byte; cbinque: DWORD; cboutque: DWORD; 데이터읽기와쓰기 (ReadFile and WriteFile) 데이터를읽고쓰는데가장중요한함수는 ReadFile, WriteFile 함수이다. 이함수들은시 리얼포트에서실제데이터를일고쓸때사용된다. 선언부는다음과같다. function WriteFile(hFile: THandle; const Buffer: nnumberofbytestowrite: DWORD; var lpnumberofbyteswritten: DWORD; lpoverlapped: POverlapped): BOOL; stdcall; function ReadFile(hFile: THandle; var Buffer: nnumberofbytestoread: DWORD; var lpnumberofbytesread: DWORD; lpoverlapped: POverlapped): BOOL; stdcall;
기본적으로파라미터가의미하는부분은거의비슷하다. hfile 파라미터는사용할포트의핸들이고, Buffer 는데이터를보내거나읽어와야되는버퍼의포인터를가리킨다. nnumberofbyteswritten, nnumberofbytesread 파라미터는실제로포트에전송되거나받게되는데이터의바이트수를나타낸다. 그리고, lpnumberofbyteswritten, lpnumberofbytesread 파라미터는실제로읽고쓴바이트수를나타낸다. 마지막으로 lpoverlapped 파라미터는시리얼포트를비동기로사용할때사용되는 TOverlapped 구조체에대한포인터이다. TSerialPort 컴포넌트 그러면, 이러한지식을바탕으로시리얼통신을쉽게사용할수있는컴포넌트를하나구현해보자. 다음부터설명하는컴포넌트는 Jason Wedge Perry 가공개한 TSerialPort 컴포넌트에기초한것임을미리밝혀둔다. 그러면이컴포넌트에서사용할프로퍼티를미리정의하도록한다. 사실시리얼포트에대한프로퍼티로정의할만한것들은대부분 DCB 구조체의내용이다. 비교적중요한프로퍼티를나열하면아래와같다. COM 포트 (COM1 ~ COM8) Baud Rate (110 ~ 256,000) 패리티검사의종류 (None, Even, Odd, Mark, Space) 스톱비트 (1, 1.5, 2) 데이터비트 (4, 5, 6, 7, 8) XON 문자 ( 디폴트 11h) XOFF 문자 ( 디폴트 13h) XON Limit (1024k) XOFF Limit (2048k) 에러문자 (0) 흐름제어종류 (RTS/CTS, XON/XOFF, DSR/DTR) 이들을각각의프로퍼티로제어하려면 Set 메소드를정의해야한다. 이를바탕으로컴포넌 트를다음과같이선언한다. unit Serial; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type TCommPort = (cpcom1, cpcom2, cpcom3, cpcom4, cpcom5, cpcom6, cpcom7, cpcom8); TBaudRate = (br110, br300, br 600, br1200, br2400, br4800, br9600, br14400, br19200, br38400, br56000, br128000, br256000); TParityType = (pcnone, pceven, pcodd, pcmark, pcspace); TStopBits = (sbone, sboneptfive, sbtwo); TDataBits = (db4, db5, db6, db7, db8); TFlowControl = (fcnone, fcxon_xoff, fcrts_cts, fsdsr_dtr); TNotifyTXEvent = procedure (Sender: TObject; Data: String) of Object; TNotifyRXEvent = procedure (Sender: TObject; Data: String) of Object; const dflt_commport = cpcom2; dflt_baudrate = br14400; dflt_paritytype = pcnone; dflt_parityerrorchecking = False; dflt_parityerrorchar = 0; dflt_parityerrorreplacement = False; dflt_stopbits = sbnone; dflt_databits = db8; dflt_xonchar = $11; dflt_xoffchar = $13; dflt_xonlim = 1024; dflt_xofflim = 2048; dflt_errorchar = 0; dflt_flowcontrol = fcnone; dflt_stripnullchars = False; dflt_eofchar = 0; TSerialPort = class(tcomponent) private hcommport: THandle; FCommPort: TCommPort; FBaudRate: TBaudRate; FParityType: TParityType;
FParityErrorChecking: Boolean; FParityErrorChar: Byte; FParityErrorReplacement: Boolean; FStopBits: TStopBits; FDataBits: TDataBits; FXONChar: Byte; FXOFFChar: Byte; FXONLim: Word; FXOFFLim: Word; FErrorChar: Byte; FFlowControl: TFlowControl; FStripNullChars: Boolean; FEOFChar: Byte; FOnTransmit: TNotifyTXEvent; FOnReceive: TNotifyRXEvent; FAfterTransmit: TNotifyTXEvent; FAfterReceive: TNotifyRXEvent; FRXBytes: DWORD; FTXBytes: DWORD; ReadBuffer: String; procedure SetCommPort(Value: TCommPort); procedure SetBaudRate(Value: TBaudRate); procedure SetParityType(Value: TParityType); procedure SetParityErrorChecking(Value: Boolean); procedure SetParityErrorChar(Value: Byte); procedure SetParityErrorReplacement(Value: Boolean); procedure SetStopBits(Value: TStopBits); procedure SetDataBits(Value: TDataBits); procedure SetXONChar(Value: Byte); procedure SetXOFFChar(Value: Byte); procedure SetXONLim(Value: Word); procedure SetXOFFLim(Value: Word); procedure SetErrorChar(Value: Byte); procedure SetFlowControl(Value: TFlowControl); procedure SetStripNullChars(Value: Boolean); procedure SetEOFChar(Value: Value);
procedure Initialize_DCB; protected public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function OpenPort(MyCommPort: TCommport): Boolean; function ClosePort: Boolean; procedure SendData(Data: PChar; Size: DWORD); function GetData: String; function PortIsOpen: Boolean; procedure FlushTX; procedure FlushRX; published property CommPort: TCommPort read FCommPort write SetCommPort default dflt_commport; property BaudRate: TBaudRate read FBaudRate write Set FBaudRate default dflt_baudrate; property ParityType: TParityType read FParityType write SetParityType default dflt_paritytype; property ParityErrorChecking: Boolean read FParityErrorChecking write SetParityErrorChecking default dflt_parityerrorchecking; property ParityErrorChar: Byte read FParityErrorChar write SetParityErrorChar default dflt_parityerrorchar; property StopBits: TStopBits read FStopBits write SetStopBits default dflt_stopbits; property DataBits: TDataBits read FDataBits write SetDataBits default dflt_databits; property XONChar: Byte read FXONChar write SetXONChar default dflt_xonchar; property XOFFChar: Byte read FXOFFChar write SetXOFFChar default dflt_xoffchar; property XONLim: Word read FXONLim write SetXONLim default dflt_xonlim; property XOFFLim: Word read FXOFFLim write SetXOFFLim default dflt_xofflim; property ErrorChar: Byte read FErrorChar write SetErrorChar default dflt_errorchar; property FlowControl: TFlowControl read FFlowControl write SetFlowControl default dflt_flowcontrol; property StripNullChars: Boolean read FStripNullChars write SetStripNullChars default dflt_stripnullchars; property EOFChar: Byte read FEOFChar write SetEOFChar default dflt_eofchar; property OnTransmit: TNotifyTXEvent read FOnTransmit write FOnTransmit; property OnReceive: TNotifyRXEvent read FOnReceive write FOnReceive; property AfterTransmit: TNotifyTXEvent read FAfterTransmit write FAfterTransmit; property AfterReceive: TNotifyRXEvent read FAfterReceive write FAfterReceive;
procedure Register; 이들각각의프로퍼티에대해앞에서보다시피 dflt_ 로시작하는디폴트상수값을지정하 고, Set 메소드를정의하였다. 이들 Set 메소드의구현방법은기본적으로다음과같은형 태를가진다. procedure TSerialPort.SetProperty(Value: TDefinedType); if Value <> FProperty then FProperty := Value; Initialize_DCB; 프로퍼티를설정하는방법은비교적쉽게이해할수있을것이다. 이번에는이벤트를정의할차례이다. TSerialPort 클래스에서는 OnTransmit, OnReceive, AfterTransmit, AfterRecieve 라는 4 개의이벤트를제공한다. 각각의이벤트는 Sender 와 Data 라는파라미터를가진다. 이제부터앞에서선언한컴포넌트의메소드를구현해보자. 먼저 Create, Destroy, PortIsOpen 메소드를다음과같이구현한다. constructor TSerialPort.Create(AOwner: TComponent); inherited Create(AOwner); hcommport := INVALID_HANDLE_VALUE; FCommPort := dflt_commport; FBaudRate := dflt_baudrate; FParityCheck := dflt_paritycheck; FStopBits := dflt_stopbits; FDataBits := dflt_databits; FXONChar := dflt_xonchar; FXOFFChar := dflt_xoffchar; FXONLim := dflt_xonlim;
FXOFFLim := dflt_xofflim; FErrorChar := dflt_errorchar; FFlowControl := dflt_flowcontrol; FOnTransmit := nil; FOnReceive := nil; destructor TSerialPort.Destroy; ClosePort; inherited Destroy; function TSerialPort.PortIsOpen: Boolean; Result := hcommport <> INVALID_HANDLE_VALUE; Create 메소드에서는데이터필드의초기값을설정하게된다. 여기서눈여겨볼것은 hcommport 핸들을 INVALID_HANDLE_VALUE 로설정한부분이다. 이는시리얼포트가성공적으로열렸는지테스트하고, 포트에특정작업이이루어지는것을막는등의역할을하게된다. 이와연관되어서 PortIsOpen 메소드가사용된다. Destroy 메소드에서는열린포트를닫는 ClosePort 메소드를호출한다. 그러면실제로포트를열고, 닫는 OpenPort 와 ClosePort 메소드를구현하도록하자. OpenPort 메소드는앞에서설명한 CreateFile API 함수를이용해서포트에대한핸들을얻어오게되며, 성공적으로이작업이이루어지면 True 를반환한다. 이때포트를열기위해서는이미열려있는포트를닫아야하므로 ClosePort 메소드를먼저호출한다. 그리고, Initialize_DCB 메소드를호출해서포트를초기화한다. ClosePort 메소드는 CloseHandle API 함수를호출해서포트에대한핸들을해제하게되는데, 이때 PurgeComm API 함수를사용해서버퍼의값을비우게되는 FlushTX, FlushRX 메소드를먼저호출한다. 그리고, 포트에대한핸들에아무것도지정되지않았다는것을나타내는 INVALID_HANDLE_VALUE 값을지정한다. function TSerialPort.OpenPort(MyCommPort: TCommPort): Boolean; var MyPort: PChar;
ClosePort; MyPort := PChar( COM + IntToStr(Ord(MyCommPort) + 1)); hcommport := CreateFile(MyPort, GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, 0, 0); Initialize_DCB; if hcommport <> INVALID_HANDLE_VALUE then Result := True; FCommPort := MyCommPort; end else Result := False; function TSerialPort.ClosePort: Boolean; FlushTX; FlushRX; Result := CloseHandle(hCommPort); hcommport := INVALID_HANDLE_VALUE; function TSerialPort.FlushRX; if hcommport = INVALID_HANDLE_VALUE then Exit; PurgeComm(hCommPort, PURGE_RXABORT or PURGE_RXCLEAR); ReadBuffer := ; function TSerialPort.FlushTX; if hcommport = INVALID_HANDLE_VALUE then Exit; PurgeComm(hCommPort, PURGE_TXABORT or PURGE_TXCLEAR); 포트를초기화하는부분은 Initialize_DCB 메소드에의해서구현된다. 이메소드는프로퍼
티가바뀔때마다이변화를 DCB 구조체에반영하는역할을한다. 실제구현방법은다음 코드를보면쉽게이해할수있을것이다. procedure TSerialPort.Initialize_DCB; var MyDCB: TDCB; if hcommport = INVALID_HANDLE_VALUE then Exit; GetCommState(hCommPort, MyDCB); case FBaudRate of br110: MyDCB.BaudRate := 110; br300: MyDCB.BaudRate := 300; br600: MyDCB.BaudRate := 600; br1200: MyDCB.BaudRate := 1200; br2400: MyDCB.BaudRate := 2400; br4800: MyDCB.BaudRate := 4800; br9600: MyDCB.BaudRate := 9600; br14400: MyDCB.BaudRate := 14400; br19200: MyDCB.BaudRate := 19200; br38400: MyDCB.BaudRate := 38400; br56000: MyDCB.BaudRate := 56000; br128000: MyDCB.BaudRate := 128000; br256000: MyDCB.BaudRate := 256000; case FParityType of pcnone: MyDCB.Parity := NOPARITY; pceven: MyDCB.Parity := EVENPARITY; pcodd: MyDCB.Parity := ODDPARITY; pcmark: MyDCB.Parity := MARKPARITY; pcspace: MyDCB.Parity := SPACEPARITY; if FParityErrorChecking then inc(mydcb.flags, $0002); if FParityErrorReplcement then inc(mydcb.flags, $0021); MyDCB.ErrorChar := Char(FErrorChar);
case FStopBits of sbone: MyDCB.StopBits := ONESTOPBIT; sboneptfive: MyDCB.StopBits := ONE5STOPBITS; sbtwo: MyDCB.StopBits := TWOSTOPBITS; case FDataBits of db4: MyDCB.ByteSize := 4; db5: MyDCB.ByteSize := 5; db6: MyDCB.ByteSize := 6; db7: MyDCB.ByteSize := 7; db8: MyDCB.ByteSize := 8; case FFlowControl of fcxon_xoff: MyDCB.Flags := MyDCB.Flags or $0020 or $0018; fcrts_cts: MyDCB.Flags := MyDCB.Flags or $0004 or $0024 * RTS_CONTROL_HANDSHAKE; fcdsr_dtr: MyDCB.Flags := MyDCB.Flags or $0008 or $0010 * DTR_CONTROL_HANDSHAKE; if FStripNullChars then inc(mydcb.flags, $0022); MyDCB.XONChar := Char(FXONChar); MyDCB.XOFFChar := Char(FXOFFChar); MyDCB.XONLim := FXONLim; MyDCB.XOFFLim := FXOFFLim; if FEOFChar <> 0 then MyDCB.EOFChar := Char(EOFChar); SetCommState(hCommPort, MyDCB); 이메소드를구현하기위해서 DCB 구조체에비트플래그를사용하였다. 이러한플래그에느여러가지종류가있는데, 대부분은흐름제어와패리티검사에사용된다. 사용가능한플래그를나열하면다음과같다.
fparity = $0002; // 설정되면패리티검사를한다. foutxctsflow = $0004; foutxdsrflow = $0008; //CTS 가 high 가아니면데이터가전송되지않음 //DSR 이 high 가아니면데이터가전송되지않음 fdtrcontrol = $0010; //DTR_CONTROL_ENABLE, DTR_CONTROL_DISABLE, DTR_CONTROL_HANDSHAKE fdsrsensitivity = $0012; //DSR 이 high 가아니면모든바이트가무시됨 ftxcontinueonxoff = $0014; // 이플래그가설정되어있으면 XON 문자가전송되어도데이터를보내며, 그렇지않으면 XONLim 이도달할때까지데이터를보내지않는다. foutx = $0018; finx = $0020; ferrorchar := $0021; fnull = $0022; frtscontrol = $0024; // 전송시 XON/XOFF 흐름제어를사용 // 수신시 XON/XOFF 흐름제어를사용 // 패리티에러가발생하면이문자로교체 //Null 문자를잘라냄 //RTS 흐름제어사용 이제실제로데이터를받고, 전송하는 GetData, SetData 메소드를구현해보자. SendData 가보다쉽게구현이가능하므로, 여기에대해서먼저알아본다. 이메소드가호출되면먼저 OnTransmit 이벤트핸들러를호출해서데이터를전송하기전에여러가지조작을할수있도록허용한다. 마찬가지로이메소드의마지막에는 AfterTransmit 이벤트핸들러를호출해서데이터전송후에여러가지작업을할수있도록한다. 실제로데이터를전송하는작업은 WriteFile API 함수를사용한다. 파라미터로포트에대한핸들과전송할데이터의포인터, 그리고전송할데이터의크기를넘겨주면실제로전송된데이터의크기가결과값으로반환된다. 마지막파라미터에는전송하는방법을지정할수있는데, nil 로설정해서동시에읽고, 쓰는작업을할수없도록하였다. 이값을 overlapped 스타일의비동기통신모드로설정하면동시에읽고, 쓰는작업을할수있는데이를구현하기위해서는버퍼처리, 메모리재할당기법등의고급스런테크닉을사용해야한다. procedure TSerialPort.SendData(Data: PChar; Size: DWORD); var NumBytesWritten: DWORD; if hcommport = INVALID_HANDLE_VALUE then Exit; if Assigned(FOnTransmit) then FOnTransmit(Self, Data); WriteFile(hCommPort, Data^, Size, NumBytesWritten, nil); if Assigned(FAfterTransmit) then FAfterTransmit(Self, Data);
GetData 메소드에서도프로시저의처음과끝부분에 OnReceive, AfterReceive 이벤트핸들러를호출한다. 그리고, 데이터를읽어오는데에는 ReadFile API 함수를사용하는데, 이함수에서읽어온데이터를저장할버퍼와데이터의크기를지정한다. 이때데이터의크기를적절하게지정하기위해 ClearCommError API 함수를이용해서 TComStat 형의데이터를읽어와서, 버퍼의크기를결정하는과정을거친다. function TSerialPort.GetData: String; var NumBytesRead: DWORD; BytesInQueue: LongInt; ostatus: TComStat; // 에러코드를담기위해서사용한다. dwerrorcode: DWORD; if hcommport = INVALID_HANDLE_VALUE then Exit; if Assigned(FOnReceive) then FOnReceive(Self, ReadBuffer); ClearCommError(hCommPort, dwerrorcode, @ostatus); BytesInQueue := ostatus.cbinque; if BytesInQueue > 0 then SetLength(ReadBuffer, BytesInQueue + 1); ReadFile(hCommPort, PChar(ReadBuffer)^, BytesInQueue, NumBytesRead, nil); SetLength(ReadBuffer, StrLen(PChar(ReadBuffer))); if Assigned(FAfterReceive) then FAfterReceive(Self, ReadBuffer); Result := ReadBuffer; 이렇게해서가장기본적인작동을하는시리얼포트컴포넌트를하나완성하였다. 이를바탕으로보다기능을강화해서좋은컴포넌트로만들수도있는데, 예를들어서 CRC 검사를하고, 비동기통신이가능하며, 데이터를읽고쓸때멀티쓰레딩기법을이용하는컴포넌트등을생각할수있겠다. TSerialPort 컴포넌트의활용 이컴포넌트를이용하는방법을알아보자. TSerialPort 컴포넌트의기능과간단한예제코
드를나열하면다음과같다. 1. 포트열기 : SerialPort1.OpenPort(cpCOM2); or if SerialPort1.OpenPort(cpCOM2) then do something. 2. 데이터전송 : var S: PChar; S := PChar(Edit1.Text); SerialPort1.SendData(S, SizeOf(s)); SerialPort1.SendData((chr(13)), 1); // 캐리지리턴 3. 데이터읽기 : Memo1.Text := SerialPort1.GetData; 4. 버퍼비우기 : SerialPort1.FlushRX; SerialPort1.FlushTX; 5. 포트가열려있는지확인 : if SerialPort1.PortIsOpen then do something. 6. 포트닫기 SerialPort1.ClosePort; 정리 (Summary)
이번장에서는직렬포트를이용한통신을하는기본적인방법에대해서알아보았다. 최근의인터넷환경의발달로모뎀과직렬포트를직접이용하는사례가많이줄어들고있는것이사실이다. 그렇지만, 직렬포트는새로운기계를제작하여컴퓨터로제어를하거나, 그밖에도여러가지아이디어를가지고가장쉽게컴퓨터와연결할수있는수단이다. 그러므로, 직렬포트를활용하는방법을알아두는것은의미가있다.