한국어
Windows Programming
 

시리얼 통신 (COM Port open)

pjk 2014.11.17 18:15 조회 수 : 7242

시리얼 통신은 기본적으로 "포트열기, 포트설정, 데이터쓰기, 데이터읽기" 와 같이 크게 4부분으로 구성된다. 윈도우즈에서 시리얼 포트 제어는 리눅스 계열과 유사하게 파일 개념으로 제어한다. 즉, 파일을 열면 포트가 열리고, 파일에 데이터를 쓰면 시리얼 포트에 데이터가 전송되는 것이다. 다만, 시리얼 포트와 같은 디바이스 파일은 우리가 말하는 통상의 파일과는 성격이 다르기 때문에, 디바이스 파일을 열거나 제어를 함에 있어서 약간의 주의가 필요하다.


1. 포트 열기


앞서 말한대로 윈도우즈는 시리얼 포트를 "디바이스 파일"이라는 개념을 사용한다고 했다. 일단, 디바이스라는 용어보다 파일이라는 용어에 초점을 맞추기로 하자. 통신을 하려면 일단 파일을 열어야 한다. Win32는 파일을 열기 위한 함수로서 CreateFile()이라는 함수를 제공한다. (파일을 연다고해서 OpenFile이라고 생각하지 말자^^)



HANDLE CreateFile(
  LPCTSTR lpFileName,           // file name
  DWORD dwDesiredAccess,        // access mode
  DWORD dwShareMode,            // share mode
  LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD
  DWORD dwCreationDisposition,  // how to create
  DWORD dwFlagsAndAttributes,   // file attributes
  HANDLE hTemplateFile          // handle to template file
);

 

- lpFileName : 열고자 하는 디바이스파일의 이름. 시리얼 포트의 경우 "COM1", "COM3" 등이 된다.

- dwDesiredAccess : 어떤 용도로 파일을 사용할 지를 결정. fopen()함수에서 "w", "a", "r" 등을 생각하면 되겠다. 보통 읽기와 쓰기를 병행하므로 GENERIC_READ | GENERIC_WRITE를 사용한다.

- dwShareMode : 파일을 공유할지를 결정하는 인자로서, 디바이스 파일은 유한한 자원이므로 exclusive하게 사용해야한다(다시말해 공유를 해선 안된다). 따라서, 무조건 0으로 둔다.

- lpSecurityAttributes : 자식 프로세스에서 핸들을 상속할지를 정하는 인자인데, Standalone형태의 프로그램을 작성할 것이므로 NULL을 사용하면 되겠다.

- dwCreationDisposition : fopen()에서 "w+", "a+" 등의 의미를 생각하면 된다. 디바이스가 있어야 통신이 가능하므로 디바이스 파일은 사전에 존재해야 한다. 따라서, OPEN_EXISTING 을 사용하자.

- dwFlagAndAttributes : 열고자 하는 디바이스파일의 속성과 플래그를 정한다. 논오버랩 방식을 사용할 경우 FILE_ATTRIBUTE_NORMAL을 사용하고, 오버랩 방식으로 오픈할 경우에는 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED 를 사용한다. (논오버랩 방식과 오버랩 방식의 개념은 차후에 설명할 예정이니 일단 넘어 가도록 하자.)

- hTemplateFile : 플랫폼에 따라 값이 다른데, 시리얼통신의 경우에는 플랫폼에 관계없이 무조건 0 이다.


위의 내용을 다시 정리해보면 시리얼 포트를 여는 방식은 논오버랩 방식과 오버랩 방식으로 나뉘며, 결국 CreateFile()함수는 2가지 중의 하나의 형태로 호출될 것이다.




[논-오버랩 방식]


HANDLE hComm = CreateFile("COM1",

                    GENERIC_READ | GENERIC_WRITE,

                    0, NULL,

                    OPEN_EXISTING,

                    FILE_ATTRIBUTE_NORMAL,

                    0);



[오버랩 방식]


HANDLE hComm = CreateFile("COM1",

                    GENERIC_READ | GENERIC_WRITE,

                    0, NULL,

                    OPEN_EXISTING,

             FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,

                    0);


CreateFile()함수는 성공적으로 디바이스 파일이 여릴 경우 해당 디바이스 파일에 대한 핸들값을 리턴한다. 만약, 실패할 경우 INVALID_HANDLE_VALUE를 리턴한다.


파일이 재대로 열렸으면 hComm 변수에 핸들값이 얻어졌을 것이다. 그 다음에 해야 할 일은 통신을 위해 사용하게 될 디바이스의 버퍼를 설정하는 작업이다. 디바이스 파일은 사용자의 프로그램과 장치간에 효율적인 데이터 전송을 위해 중간에 Input용 버퍼와 Output용 버퍼를 제공한다. 우리가 할 일은 이 버퍼의 사이즈를 설정 한 다음 통신을 시작하기 전에 해당 버퍼를 깔끔히 청소해 주는 일이다. 이를 위해 사용되는 함수가 바로 SetupComm() 함수와 PurgeComm() 함수이다.




BOOL SetupComm(
  HANDLE hFile,     // handle to communications device
  DWORD dwInQueue,  // size of input buffer
  DWORD dwOutQueue  // size of output buffer
);



BOOL PurgeComm(
  HANDLE hFile,  // handle to communications resource
  DWORD dwFlags  // action to perform
);


SetupComm() 과 PurgeComm() 함수의 각각 첫번째 인자는 CreateFile() 을 통하여 획득한 디바이스 파일의 핸들이다.


SetupComm() 함수는 Input/Output 버퍼의 사이즈를 지정해주는 함수이다. 대체로, 각각 4096바이트 정도면 무난하다.(참고로 VT-100의 화면의 사이즈가 약 4KB 정도라고 한다.)


PurgeComm() 함수는 SetupComm()에서 설정해준 I/O 버퍼를 비우는 일을 담당한다. 우리는 Input 및 Output 버퍼 각각을 비워줘야 하기 때문에 두번째 인자에다가 PURGE_TXCLEAR | PURGE_RXCLEAR 를 넣어준다. 만약, 오버랩 방식으로 사용하는 경우에는 PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TX_ABORT | PURGE_RXABORT와 같이 넣어주면 되겠다.