본 자료는
1. 송/수신 데이터 구조(Protocol)
2. CSerialPort Class Interface (기능과 내부인자)
3. CQueue Class Interface (기능과 내부인자)
4. 포트 설정 방법
5. 수신 스레드(Thread)
순서로 진행합니다.
본 시리얼(Serial) 통신 예제는 C++로 제작되었으며 데이터는 프레임(Frame) 단위(STX + Data + CheckSum + ETX)로 구현되었습니다. 여기서 프레임은 프로토콜을 말하며 사용하시는 장치에 따라서 프로토콜의 구조는 달라질 것입니다. 수신 처리는 스레드(Thread)로 동작하며 수신된 데이터는 프레임 단위로 수신큐(Queue)에 저장되도록 구현하였습니다.
1. 송/수신 데이터 구조(Protocol)
시리얼 통신을 위한 송/수신 데이터의 프로토콜(Protocol) 정의
데이터의 프레임(Frame) 구조STX[1byte] | Data[N Byte] | CheckSum[1Byte] | ETX[1Byte] |
STX는 프레임의 시작 코드로서 기본 설정값은 0x02입니다.
ETX는 프레임의 종료 코드로서 기본 설정값은 0x03입니다.
체크섬(CheckSum)은 에러 체크 코드(Error Check Code)로 여기서는 CheckSum8을
사용하였습니다.
void CSerialPort::SendCommand(CString strCmd, CString strData) { BYTE sendData[FRAMESIZE]; // FrameSize만큼의 바이트 배열 선언
} sendData[0] = STX; // Frame의 첫번째 바이트에 STX 입력 sendData[FRAMESIZE - 2] = GetCheckSum8(sendData, 0, FRAMESIZE - 2); // Frame의 마지막에서 2번째 바이트에 CheckSum 입력 sendData[FRAMESIZE - 1] = ETX; // Frame의 마지막 바이트에 ETX 입력 |
---|
(인자) CString strCmd (인자) CString strData (반환값) 없음 |
수신 데이터의 구조 변경은 INT CSerialPort::ReadThreadProc(LPVOID lParam) 코드를 수정해야 합니다.
2. CSerialPort Class Interface (기능과 내부인자)
시리얼(Serial) 통신에 관련된 모든 기능을 담당하는 CSerialPort 클래스로서 세부 내용은 다음과 같이 이루어져 있습니다.
시리얼 통신을 위한 포트 오픈(Open) bool CSerialPort::OpenPort(int Port, DWORD Baud) |
---|
(인자) int Port : 포트 번호 1부터 시작 (인자) DWORD Baud : 통신 속도 (반환값) bool : 포트 정상 오픈(Open)이면 True이고 실패하면 False 반환 |
포트 사용을 마치고 포트를 닫음 void CSerialPort::ClosePort() |
---|
(인자) 없음 (반환값) 없음 |
데이터를 송신할 때 사용 void CSerialPort::WritePort(BYTE* pData, DWORD dwWrite) |
---|
(인자) BYTE* pData : 송신 데이터 (인자) DWORD dwWrite : 송신 데이터의 사이즈 (반환값) 없음 |
데이터를 수신할 때 사용 DWORD CSerialPort::ReadPort (BYTE* pBuffer, DWORD dwR) |
---|
(인자) BYTE* pBuffer : 수신 데이터 (인자) DWORD dwR : 수신 블록 사이즈 (반환값) DWORD |
통신 버퍼의 초기화에 사용. 포트를 비울 때 사용 void CSerialPort::ResetPort() |
---|
(인자) 없음 (반환값) 없음 |
포트를 셋팅할 때 사용 BOOL CSerialPort::ConfigPort(DWORD Baud, int ByteSize, int Parity, int StopBits) m_DCB(구조체)의 변수들을 초기화함 |
---|
(인자) DWORD Baud : 통신속도 (인자) int ByteSize : 바이트 크기 (인자) int Parity : 패리티 비트 (인자) int StopBits : 스탑 비트 (반환값) bool : 포트 관련 설정 이후 SetCommState()가 정상 동작하는 경우 TRUE를 반환 |
모뎀 제어신호를 감시하거나 상태를 확인 BYTE CSerialPort::GetModemStatus() |
---|
(인자) 없음 (반환값) BYTE : 모뎀의 제어 레지스터 값의 상태를 반환 |
수신 스레드(Thread) 수신된 데이터의 처리를 담당하는 스레드로 프레임 구조 및 데이터의 Parsing 처리와 수신된 데이터에 따른 모든 처리를 하고 있는 함수로 제작하고자 하시는 프로그램에 맞게 변경하여 처리하시면 됩니다. UINT CSerialPort::ReadThreadProc(LPVOID lParam) |
---|
(인자) LPVOID lParam (반환값) UINT |
포트 오픈하고 수신 스레드 생성 bool CSerialPort::ReadStart(int Port, DWORD Baud) |
---|
(인자) int Port : 포트 번호 (인자) DWORD Baud : 통신 속도 (반환값) bool : 수신스레드가 정상 생성되면 TRUE 반환, 정상 생성 안되면 FALSE |
포트닫는 기능 수행. ClosePort()를 호출 void CSerialPort::ReadStop() |
---|
(인자) 없음 (반환값) 없음 |
수신된 데이터가 에러가 발생하였는지 확인 BYTE CSerialPort::GetCheckSum8(BYTE *pByte, int iStart, int iLen) |
---|
(인자) BYTE *pByte : 송신 데이터 (인자) int iStart : 시작 위치 0 (인자) int iLen : 프레임 사이즈. FRAMESIZE -2 (반환값) BYTE |
3. CQueue Class Interface (기능과 내부인자)
큐 데이터를 관리하는 CQueue 클래스(Class).
Queue에서 알아야 할 중요한 함수는 Init(큐의 사이즈 지정 및 초기화), Insert(EnQueue. 큐에 데이터 삽입), Extract(DeQueue. 큐에서 데이터 추출)입니다.
Init : 큐의 사이즈를 정하고 메모리를 할당 void init(int size) |
---|
(인자) int size : Queue의size(프레임 개수)를 정하고 메모리 초기화. 예를 들어 100으로 지정하는 경우 100개의 프레임 사이즈를 정하고 초기화합니다. (반환값) 없음 |
EnQueue : 큐에 데이터 삽입 bool insert(const T& val) |
---|
(인자) const T& val : 큐에 입력하는 데이터 (반환값) BOOL : 큐에 데이터가 정상 삽입되는 경우 TRUE 반환 |
DeQueue : 큐에서 데이터 추출 bool extract(T* val) |
---|
(인자) T* val : 큐에서 추출하는 데이터 (반환값) BOOL : 큐에서 데이터가 정상 추출되는 경우 TRUE 반환 |
큐에 데이터 삽입 예제
////////////////////////////// CQueueDlg.h에서 //////////////////////////////
private:
CQueue
////////////////////////////// CQueueDlg.cpp에서 //////////////////////////////
m_Queue1.init(500); // 초기화, Queue의 size를 정하고 메모리 할당
// EnQueue. 삽입
void CCQueueDlg::OnBnClickedButton1()
{
dwRead =3;
BYTE* pEnQueueData; // BYTE *형 변수 선언
pEnQueueData = new BYTE[dwRead];
memset(pEnQueueData, 0, dwRead); // pEnQueueData를 dwRead 사이즈 0으로 초기화
pEnQueueData[0] = 'a';
pEnQueueData[1] = 'b';
pEnQueueData[2] = 'c';
m_Queue1.insert(pEnQueueData); // 큐에 데이터 프레임(‘abc’) 삽입
////////////////////////////// CQueue.h에서 //////////////////////////////
// 큐에 데이터 삽입
bool insert(const T& val)
{
EnterCriticalSection(&cs);
//Full인 경우는 false를 return
if(nNum+1 > nSize)
{
return false;
nNum++;
Rear → Next → value = val; // val를 큐의 마지막(Rear)에 삽입
Rear → Next = Rear → Next → Next;
LeaveCriticalSection(&cs);
return true;
큐에서 데이터 추출 예제
////////////////////////////// CQueueDlg.cpp 에서//////////////////////////////
// DeQueue.
void CCQueueDlg::OnBnClickedButton2()
{
TCHAR recData[1024];
if(m_Queue1.extract(&recRaw) == true)
{
AsciiToUnicode((LPCSTR)recRaw, recData); // ASCII를 UniCode로 변경
m_RecList.AddString(recData); // 데이터 ListBox에 추가
}
////////////////////////////// CQueue.h에서 //////////////////////////////
// 큐에 데이터를 추출
bool extract(T* val)
{
// 자료가 없는 경우는 false를 return
if(nNum==0)
{
LeaveCriticalSection(&cs); // 크리티컬 섹션 탈출
return false;
nNum--;
*val = Front → Next → value; // 큐의 맨 앞(Front)를 *val에 저장
Front->Next = Front → Next → Next;
LeaveCriticalSection(&cs);
return true;
4. 포트 설정 방법
포트 Open 설명 bool CSerialPort::OpenPort(int Port, DWORD Baud) { // 포트 생성
} m_hCom = CreateFile(m_strPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); // 통신 장치에 모니터링할 이벤트 세트를 지정 SetCommMask( m_hCom, EV_RXCHAR); // InQueue, OutQueue 크기 설정 SetupComm( m_hCom, 1024, 1024); // 포트 비우기. PurgeComm() 호출 ResetPort(); // timeout 설정 timeouts.ReadIntervalTimeout = 500; // 다음 문자를 받기까지 기다리는 시간 timeouts.ReadTotalTimeoutMultiplier = 0; // 읽은 문자 수에 비례하여 기다리는 시간 늘여줌 timeouts.ReadTotalTimeoutConstant = 100; // 읽기에서 총 기다리는 시간을 밀리초로 설정 timeouts.WriteTotalTimeoutMultiplier = 10; // 쓰기에서 문자 수에 비례하여 기다리는 시간 늘여줌 timeouts.WriteTotalTimeoutConstant = 0; // 쓰기에서 총 기다리는 시간을 밀리초로 설정 |
---|
(인자) int Port : 포트 번호 (인자) DWORD Baud : 통신속도 (반환값) bool : 정상적으로 포트가 오픈되면 TRUE |
지정된 통신 장치의 통신 매개변수를 초기화 SetupComm( HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue) |
---|
(인자) HANDLE hFile : 통신장치의 핸들, CreateFile 함수의 리턴된 핸들 (인자) DWORD dwInQueue : 장치의 내부 입력 버퍼크기를 byte 단위로 지정 (인자) DWORD dwOutQueue : 장치의 내부 출력 버퍼크기를 byte 단위로 지정 (반환값) Bool은 정상적으로 매개변수의 초기화가 되면 TRUE 반환 |
지정된 통신 장치의 통신 매개변수를 초기화 SetupComm( HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue) |
---|
(인자) HANDLE hFile : 통신 장치의 핸들, CreateFile 함수의 리턴된 핸들 (인자) DWORD dwInQueue : 장치의 내부 입력 버퍼크기를 byte 단위로 지정 (인자) DWORD dwOutQueue : 장치의 내부 출력 버퍼크기를 byte 단위로 지정 (반환값) Bool은 정상적으로 매개변수의 초기화가 되면 TRUE 반환 |
포트 비우기 PurgeComm( m_hCom,PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR); |
---|
(인자) m_hCom는 통신 자원의 핸들, CreateFile 함수의 리턴된 핸들 (인자) PURGE_TXABORT : 모든 미해결된 Overlapped 쓰기 작업을 종료하고 쓰기 작업이 완료가 되지 않았더라도 즉시 반환 (인자) PURGE_TXCLEAR : 출력 버퍼를 클리어 (인자) PURGE_RXABORT : 입력 버퍼를 클리어 (인자) PURGE_RXCLEAR : 모든 미해결된 Overlapped 읽기 작업을 종료하고 읽기 작업이 완료가 되지 않았더라도 즉시 반환 |
5. 수신 스레드(Thread)
수신 스레드(Thread) 수신된 데이터의 처리를 담당하는 스레드로 프레임 구조 및 데이터의 Parsing 처리와 수신된 데이터에 따른 모든 처리를 하고 있는 함수로 제작하고자 하시는 프로그램에 맞게 변경하여 처리하시면 됩니다. UINT CSerialPort::ReadThreadProc(LPVOID lParam) |
---|
(인자) LPVOID lParam (반환값) UINT |
// 수신 스레드(ReadThreadProc) 설명 UINT CSerialPort::ReadThreadProc(LPVOID lParam) { // 포트를 감시하는 루프
} while (pSerial->m_bOpen) { // COM 포트에서 메시지가 들어오면 리턴하는 함수
}// 메시지가 안들어 오면 무한 블러킹 WaitCommEvent(pSerial->m_hCom, &dwEvent, NULL); // 데이터가 수신되었다는 메시지가 발생하면 if ((dwEvent & EV_RXCHAR) == EV_RXCHAR) { // 데이터 수신부
} if(dwRead = pSerial->ReadPort(lnData, pSerial->m_nMaxBlock)) //*************[ Parsing - Start Code ] *************// //*************[ Parsing - End Code ] *************/ |
---|
COM(m_hComPort)에서 메시지가 들어오면 리턴하는 함수. 메시지가 안 들어오면 무한 블러킹 Bool WaitCommEvent( HANDLE hFile, LPDWORD lpEvtMask, LPOVERLAPPED lpOverlapped ); |
---|
(인자) HANDLE hFile : 장치 핸들. CreateFile 함수에서 리턴한 핸들 (인자) LPDWORD lpEvtMask : 수신 이벤트 받을 포인터 (인자) LPOVERLAPPED lpOverlapped : 중첩 구조체 포인터. 에러 시 0값 (반환값) BOOL |
데이터 수신을 담당 pSerial → ReadPort(lnData, pSerial → m_nMaxBlock) |
---|
(인자) lnData : 수신 측에 들어오는 데이터 (인자) pSerial → m_nMaxBlock 블록의 사이즈 (반환값) DWORD는 읽은 데이터의 사이즈 |