고급 예제

다양한 도구들을 가지고 마음껏 응용해보세요.

시리얼통신- C++로 아두이노 제어하기

2014-10-23 09:49:45

개요

시리얼 통신으로 스케치내에서 시리얼모니터만으로 제어하는 것이 아닌 C++로 아두이노를 제어해보자.

프로세싱으로 시리얼통신으로 아두이노 스케치를 제어하는 방법과 동일하다. 다만 프로세싱은 스케치처럼 간단하게 포트에 데이터를 올리고 받으면 끝이지만  C++언어는 스케치나 프로세싱과 달리 시리얼통신을 사용하는 방법이 까다롭기 때문에 클래스를 생성하고 포트를 열고 통신 초기화를 시키고 연결을 하는 등 여러 과정을 거쳐야 시리얼통신이 가능하다.

사실상 쓰기 편한 프로세싱이나 자바가 존재하기 때문에 아두이노와 C++과의 시리얼통신은 거의 쓰이지 않는다.
하지만 특수한 경우가 생겨 사용해야할 경우도 생기기 때문에 연결하는 방법을 알고 있으면 두고두고 유용하게 쓰일 수 있다. 
물론 알기 위해서는 C언어의 문법이나 알고리즘 등은 기본적으로 깔고 들어가야 하지만 모른다면 그냥 복사-붙여넣기를 해서 사용해도 상관은 없다. 중요한건 서로간의 통신이 되는지 안되는지의 여부이다.
(소스는 http://blog.naver.com/onlywin7788/140161634165 와 http://mnlt.tistory.com/119 를 참조하였다.)
 

동영상

C++ 커맨드창으로 간단하게 아두이노 서보모터를 제어하는 영상




 

소프트웨어 Making

1. C++(VS2010기준)

SerialPort.h

 

#include <Windows.h>
#include <atlstr.h>

#define BUFFER_SIZE 128

class CSerialPort  
{  
public:  
	CSerialPort(void);  
	virtual ~CSerialPort(void);  

private:  
	HANDLE  m_hComm;  
	DCB     m_dcb;  
	COMMTIMEOUTS m_CommTimeouts;  
	bool    m_bPortReady;  
	bool    m_bWriteRC;  
	bool    m_bReadRC;  
	DWORD   m_iBytesWritten;  
	DWORD   m_iBytesRead;  
	DWORD   m_dwBytesRead;  

public:  
	void ClosePort();  
	bool ReadByte(BYTE &resp);  
	bool ReadByte(BYTE* &resp, UINT size);  
	bool WriteByte(char bybyte);  
	bool OpenPort(CString portname);  
	bool SetCommunicationTimeouts(DWORD ReadIntervalTimeout,  
		DWORD ReadTotalTimeoutMultiplier, DWORD ReadTotalTimeoutConstant,  
		DWORD WriteTotalTimeoutMultiplier,DWORD WriteTotalTimeoutConstant);  
	bool ConfigurePort(DWORD BaudRate, BYTE ByteSize, DWORD fParity,   
		BYTE  Parity,BYTE StopBits);  
};

SerialPort.h에서는 클래스를 선언해 준다.

클래스의 함수에는
포트를 닫는 함수인 ClosePort()
포트에서 데이터를 읽어오는 함수인 ReadByte()
포트로 데이터를 전송하는 함수인 WriteByte()
포트를 여는 함수인 OpenPort()
통신포트에서 Timeout을 설정하는 함수인 SetCommunicationTimeouts()
포트를 초기화설정하는 함수인 ConfigurePort()가 있다.

SerialPort.cpp

 

 

#include "serialport.h"
#include <iostream>

using namespace std;

CSerialPort::CSerialPort()  
{  
}  

CSerialPort::~CSerialPort()  
{  
}  

bool CSerialPort::OpenPort(CString portname)  
{  
	m_hComm = CreateFile(L"//./" + portname,GENERIC_READ | GENERIC_WRITE,0,0,OPEN_EXISTING,0,0); //시리얼 포트를 오픈한다. 
	if(m_hComm == INVALID_HANDLE_VALUE)  //정상적으로 포트가 열렸는지 확인
	{  
		return false;  //열리지 않았을 경우 false 반환
	}  
	else  
		return true;   //제대로 열렸을 경우 true 반환
}  

bool CSerialPort::ConfigurePort(DWORD BaudRate, BYTE ByteSize, DWORD fParity,   
	BYTE Parity, BYTE StopBits)  
{  
	if((m_bPortReady = GetCommState(m_hComm, &m_dcb))==0) //포트의 상태를 확인. 정상적으로 열리지 않았을 경우 false 반환
	{  
		printf("\nGetCommState Error\n");
		//"MessageBox(L, L"Error", MB_OK + MB_ICONERROR);  
		CloseHandle(m_hComm);  
		return false;  
	}  
        //포트의 대한 기본값을 설정
	m_dcb.BaudRate          = BaudRate;  
	m_dcb.ByteSize          = ByteSize;  
	m_dcb.Parity            = Parity ;  
	m_dcb.StopBits          = StopBits;  
	m_dcb.fBinary           = true;  
	m_dcb.fDsrSensitivity   = false;  
	m_dcb.fParity           = fParity;  
	m_dcb.fOutX             = false;  
	m_dcb.fInX              = false;  
	m_dcb.fNull             = false;  
	m_dcb.fAbortOnError     = true;  
	m_dcb.fOutxCtsFlow      = false;  
	m_dcb.fOutxDsrFlow      = false;  
	m_dcb.fDtrControl       = DTR_CONTROL_DISABLE;  
	m_dcb.fDsrSensitivity   = false;  
	m_dcb.fRtsControl       = RTS_CONTROL_DISABLE;  
	m_dcb.fOutxCtsFlow      = false;  
	m_dcb.fOutxCtsFlow      = false;  

	m_bPortReady = SetCommState(m_hComm, &m_dcb);  //포트 상태 확인

	if(m_bPortReady == 0)  //포트의 상태를 확인. 정상일 경우 true 반환 아닐 경우 false 반환
	{  
		//MessageBox(L"SetCommState Error");  
		printf("SetCommState Error");
		CloseHandle(m_hComm);  
		return false;  
	}  

	return true;  
}  

bool CSerialPort::SetCommunicationTimeouts(DWORD ReadIntervalTimeout,  
	DWORD ReadTotalTimeoutMultiplier, DWORD ReadTotalTimeoutConstant,  
	DWORD WriteTotalTimeoutMultiplier, DWORD WriteTotalTimeoutConstant) //통신 포트에 관한 Timeout 설정
{  
	if((m_bPortReady = GetCommTimeouts(m_hComm, &m_CommTimeouts)) == 0)  
		return false;  

	m_CommTimeouts.ReadIntervalTimeout          = ReadIntervalTimeout; //통신할때 한바이트가 전송 후 다음 바이트가 전송될때까지의 시간
        //통신에서 데이터를 읽을 때 Timeout을 사용할 것인지에 대한 여부
	m_CommTimeouts.ReadTotalTimeoutConstant     = ReadTotalTimeoutConstant;  
	m_CommTimeouts.ReadTotalTimeoutMultiplier   = ReadTotalTimeoutMultiplier;  
        //통신에서 데이터를 전송할 때 Timeout을 사용할 것인지에 대한 여부
	m_CommTimeouts.WriteTotalTimeoutConstant    = WriteTotalTimeoutConstant;  
	m_CommTimeouts.WriteTotalTimeoutMultiplier  = WriteTotalTimeoutMultiplier;  

	m_bPortReady = SetCommTimeouts(m_hComm, &m_CommTimeouts);  //포트 상태 확인

	if(m_bPortReady == 0) //포트 상태가 닫혀 있을 경우 false반환. 아닐 경우 true반환
	{  
		//MessageBox(L"StCommTimeouts function failed",L"Com Port Error",MB_OK+MB_ICONERROR);  
		printf("\nStCommTimeouts function failed\n");
		CloseHandle(m_hComm);  
		return false;  
	}  

	return true;  
}  

bool CSerialPort::WriteByte(char bybyte)  
{  
	//iBytesWritten=0;
	m_iBytesWritten=0;  
	cout<< bybyte << endl;

	if(WriteFile(m_hComm, &bybyte, 1, &m_iBytesWritten, NULL) == 0) //입력받은 값을 WriteFile을 통해 포트로 전송한다.
		return false;  
	else  
		return true;  
}  

bool CSerialPort::ReadByte(BYTE &resp)  
{  
	BYTE rx;  
	resp=0;  

	DWORD dwBytesTransferred=0;  

	if(ReadFile(m_hComm, &rx, 1, &dwBytesTransferred, 0)) //포트에 존재하는 데이터를 ReadFile을 통해 1바이트씩 읽어온다.
	{  
		if(dwBytesTransferred == 1) //데이터를 읽어오는데 성공했을 경우
		{  
			resp=rx;  //resp에 데이터를 저장하고 true 반환
			return true;  
		}  
	}  

	return false; //실패했을 경우 false 반환
}  

bool CSerialPort::ReadByte(BYTE* &resp, UINT size)  
{  
	DWORD dwBytesTransferred=0;  

	if(ReadFile(m_hComm, resp, size, &dwBytesTransferred, 0))  
	{  
		if(dwBytesTransferred == size)  
			return true;  
	}  

	return false;  
}  

void CSerialPort::ClosePort()  
{  
	CloseHandle(m_hComm); //포트를 닫는다.
	return;  
}  

헤더파일에서 선언한 클래스의 함수를 정의하는 부분이다

 

 

 

bool CSerialPort::OpenPort(CString portname)  
{  
	m_hComm = CreateFile(L"//./" + portname,GENERIC_READ | GENERIC_WRITE,0,0,OPEN_EXISTING,0,0);  //시리얼포트를 오픈한다

	if(m_hComm == INVALID_HANDLE_VALUE) //정상적으로 포트가 열렸는지 확인
	{  
		return false; //열리지 않았을 경우 false 반환 
	}  
	else  
		return true; //제대로 열렸을 경우 true 반환
}  

OpenPort에서는 통신할 시리얼포트 번호를 받아 CreateFile을 통해 시리얼포트를 열게 된다. 
m_hComm에는 통신을 할 포트의 정보가 들어가게 된다.
포트가 정상적으로 열리게 되면 True를 반환하고 포트를 열지 못했을 경우에는 False를 반환하게 된다.

 

 

 

 

bool CSerialPort::ConfigurePort(DWORD BaudRate, BYTE ByteSize, DWORD fParity,   
	BYTE Parity, BYTE StopBits)  
{  
	if((m_bPortReady = GetCommState(m_hComm, &m_dcb))==0) //포트의 상태를 확인. 정상적으로 열리지 않았을 경우 false 반환
	{  
		printf("\nGetCommState Error\n");
		//"MessageBox(L, L"Error", MB_OK + MB_ICONERROR);  
		CloseHandle(m_hComm);  
		return false;  
	}  
        //포트의 대한 기본값을 설정
	m_dcb.BaudRate          = BaudRate;  
	m_dcb.ByteSize          = ByteSize;  
	m_dcb.Parity            = Parity ;  
	m_dcb.StopBits          = StopBits;  
	m_dcb.fBinary           = true;  
	m_dcb.fDsrSensitivity   = false;  
	m_dcb.fParity           = fParity;  
	m_dcb.fOutX             = false;  
	m_dcb.fInX              = false;  
	m_dcb.fNull             = false;  
	m_dcb.fAbortOnError     = true;  
	m_dcb.fOutxCtsFlow      = false;  
	m_dcb.fOutxDsrFlow      = false;  
	m_dcb.fDtrControl       = DTR_CONTROL_DISABLE;  
	m_dcb.fDsrSensitivity   = false;  
	m_dcb.fRtsControl       = RTS_CONTROL_DISABLE;  
	m_dcb.fOutxCtsFlow      = false;  
	m_dcb.fOutxCtsFlow      = false;  

	m_bPortReady = SetCommState(m_hComm, &m_dcb); //포트 상태 확인

	if(m_bPortReady == 0)  //포트의 상태를 확인. 정상일 경우 true 반환 아닐 경우 false 반환
	{  
		//MessageBox(L"SetCommState Error");  
		printf("SetCommState Error");
		CloseHandle(m_hComm);  
		return false;  
	}  

	return true;  
}  

ConfigurePort()에서는 열린 포트에 대한 기본설정을 하게 된다. 포트의 상태를 확인하여 포트가 정상적으로 열려 있는지 닫혀있는지 확인한 후 정상적으로 열려 있을 경우 포트 기본값에 대한 설정을 받은 인자값을 통해 하게 된다. 

 

 

 

 

bool CSerialPort::SetCommunicationTimeouts(DWORD ReadIntervalTimeout,  
	DWORD ReadTotalTimeoutMultiplier, DWORD ReadTotalTimeoutConstant,  
	DWORD WriteTotalTimeoutMultiplier, DWORD WriteTotalTimeoutConstant) //통신 포트에 관한 Timeout 설정
{  
	if((m_bPortReady = GetCommTimeouts(m_hComm, &m_CommTimeouts)) == 0)  
		return false;  

	m_CommTimeouts.ReadIntervalTimeout          = ReadIntervalTimeout; //통신할때 한바이트가 전송 후 다음 바이트가 전송될때까지의 시간
        //통신에서 데이터를 읽을 때 Timeout을 사용할 것인지에 대한 여부
	m_CommTimeouts.ReadTotalTimeoutConstant     = ReadTotalTimeoutConstant;  
	m_CommTimeouts.ReadTotalTimeoutMultiplier   = ReadTotalTimeoutMultiplier;  
        //통신에서 데이터를 전송할 때 Timeout을 사용할 것인지에 대한 여부
	m_CommTimeouts.WriteTotalTimeoutConstant    = WriteTotalTimeoutConstant;  
	m_CommTimeouts.WriteTotalTimeoutMultiplier  = WriteTotalTimeoutMultiplier;  

	m_bPortReady = SetCommTimeouts(m_hComm, &m_CommTimeouts);  //포트 상태 확인

	if(m_bPortReady == 0) //포트 상태가 닫혀 있을 경우 false반환. 아닐 경우 true반환
	{  
		//MessageBox(L"StCommTimeouts function failed",L"Com Port Error",MB_OK+MB_ICONERROR);  
		printf("\nStCommTimeouts function failed\n");
		CloseHandle(m_hComm);  
		return false;  
	}  

	return true;  
}  

통신포트에 대한 Timeout을 설정하는 함수이다. 입력받은 값으로 Timeout값을 설정하게 되는데 Timeout을 설정하고 싶지 않을 경우 모든 인자값을 0으로 주면 된다.
보통의 경우에는 모두 0으로 입력한다.

 

 

 

 

bool CSerialPort::WriteByte(char bybyte)  
{  
	//iBytesWritten=0;
	m_iBytesWritten=0;  
	cout<< bybyte << endl;

	if(WriteFile(m_hComm, &bybyte, 1, &m_iBytesWritten, NULL) == 0) //입력받은 값을 WriteFile을 통해 포트로 전송한다.
		return false;  
	else  
		return true;  
}  

데이터를 전송할때 쓰는 WriteByte함수이다. 입력받은 인자값을 WriteFile을 통해 포트로 전송하게 된다. 전송에 성공하면 true, 전송에 실패하면 false를 반환한다.

 

 

 

 

bool CSerialPort::ReadByte(BYTE &resp)  
{  
	BYTE rx;  
	resp=0;  

	DWORD dwBytesTransferred=0;  

	if(ReadFile(m_hComm, &rx, 1, &dwBytesTransferred, 0)) //포트에 존재하는 데이터를 ReadFile을 통해 1바이트씩 읽어온다.
	{  
		if(dwBytesTransferred == 1) //데이터를 읽어오는데 성공했을 경우
		{  
			resp=rx;  //resp에 데이터를 저장하고 true 반환
			return true;  
		}  
	}  
	return false; //실패했을 경우 false 반환
}  

포트에 있는 데이터를 읽어올때 쓰는 ReadByte함수이다. ReadFile을 통해 포트에서 데이터를 읽어오며 1바이트씩 읽어오게 된다. 데이터를 읽어오는데 성공하면 true를 반환하고 실패했을 경우 false를 반환한다. 
readByte함수가 두개가 존재하는데 다른 하나는 ReadByte(BYTE* &resp, UINT size)로 인자값으로 읽어올 데이터의 크기를 size로 받아서 그 크기만큼 데이터를 읽어온다.

 

 

 

 

void CSerialPort::ClosePort()  
{  
	CloseHandle(m_hComm); //포트를 닫는다.
	return;  
}  

ClosePort에서는 CloseHandle을 통해 포트를 닫는다.



SerialComm.h

 

 

 

#include "serialport.h"

#define RETURN_SUCCESS 1
#define RETURN_FAIL 0


class CSerialComm
{
public :
	CSerialComm();
	~CSerialComm();

	CSerialPort	serial;
	int		connect(char* _portNum);
	int		sendCommand(char pos);
	void		disconnect();
};
 

CSerialComm는 위의 SerialPort에서 만든 클래스를 가지고 실질적인 통신에 필요한 함수를 가지는 클래스이다.

SerialComm.cpp

 

 

#include "serialcomm.h"


CSerialComm::CSerialComm(){}
CSerialComm::~CSerialComm(){}


int CSerialComm::connect(char* portNum)
{
	if(!serial.OpenPort(portNum)) //포트를 오픈하고 오픈에 실패하였으면 fail을 반환한다.
		return RETURN_FAIL;

	serial.ConfigurePort(CBR_9600, 8, FALSE, NOPARITY, ONESTOPBIT); //포트 기본값을 설정한다.
	serial.SetCommunicationTimeouts(0, 0, 0, 0, 0); //Timeout값 설정

	return RETURN_SUCCESS;
}


int CSerialComm::sendCommand(char pos) //데이터를 전송하는 함수
{
	if(serial.WriteByte(pos)) //WriteByte()를 통해 데이터 전송에 성공하면 SUCCESS, 실패하면 FAIL을 반환한다.
		return RETURN_SUCCESS;
	else
		return RETURN_FAIL;
}

void CSerialComm::disconnect() //포트를 다 쓰고 난뒤 닫는 함수
{
	serial.ClosePort();
}

SerialComm.cpp에서는 통신할 포트를 열어 기본값 설정과 Timeout값을 설정하는 connect()와WriteByte함수를 통해 데이터를 전송하는 sendCommand(), 포트를 닫는 disconnect()가 존재한다.
데이터를 읽어오는 ReceiveCommand()가 존재하지 않기 때문에 필요할 경우 작성하여 완성한다.

main

 

 

#include <stdio.h>
#include <iostream>
#include <string>
#include "serialcomm.h"

using namespace std;

int main()
{
	char buffer; 
	CSerialComm serialComm; //SerialComm 객체 생성


	if(!serialComm.connect("COM25")) //COM25번의 포트를 오픈한다. 실패할 경우 -1을 반환한다.
	{
		cout << "connect faliled" << endl;
		return -1;
	}
	else
		cout << "connect successed" << endl;


	while(1) { //오픈에 성공한 경우 sendCommand()를 통해 계속적으로 데이터를 전송한다. 전송에 실패 할 경우 failed 메시지를 출력한다.
		cin >> buffer;

		if(!serialComm.sendCommand(buffer))
		{	
			cout << "send command failed"<< endl;
		}
		else
			cout << "send Command success" << endl;
	}


	serialComm.disconnect(); //작업이 끝나면 포트를 닫는다

	cout << "end connect" << endl;
		return 0;

}

 


2. 아두이노 스케치

 

 

#include <Servo.h>   // 서보 라이브러리 사용


//서보모터 2개 객체 생성
Servo servo_1;
Servo servo_2;

int pos= 0;
char buffer[20];               //통신을 할때 buffer배열에 전송받은 데이터 입력
char bufferIndex = 0;                    

void setup() {
  //서보모터의 핀번호를 9번 10번에 입력
  servo_1.attach(9);
  servo_2.attach(10);
  Serial.begin(9600);          // 시리얼 통신 초기화 
  Serial.flush();                           
  Serial.println(">> ");                
}

void loop() {
  if (Serial.available() > 0) {// 데이터를 입력 받았을 경우

       buffer[bufferIndex] = Serial.read(); // 입력 데이터를 받고, buffer에 저장
       
    if (buffer[bufferIndex] == 'h') {  //h값을 입력 받았을 경우 가로로 움직이는 모터 제어            
      Serial.println("");
     
      buffer[bufferIndex] = 0;                                        
      pos = atoi(buffer); //입력받은 데이터를 int형으로 형변환 시킨다.
      servo_1.write(pos);
  
      bufferIndex = 0;                                                 
       Serial.print(pos);
    }
    
    else if (buffer[bufferIndex] == 'v') { //v값을 입력 받았을 경우 세로로 움직이는 모터 제어           
      Serial.println("");
     
      buffer[bufferIndex] = 0;                                        
      pos = atoi(buffer); //입력받은 데이터를 int형으로 형변환 시킨다.
      servo_2.write(pos);
  
      bufferIndex = 0;                                                 
       Serial.print(pos);
    }
    else {
      bufferIndex++;    // 배열을 초기화
      bufferIndex = bufferIndex%20;   //인덱스 번호가 20을 넘어갈 경우 다시 0부터 시작할 수 있게 나머지 값으로 인덱스 설정                  
    }
  }
}




스케치에서는 서보모터를 두개를 제어하기 위한 소스를 작성한다. 서보모터의 핀번호를 9번 10번으로 설정하고 Serial통신을 통해 포트에 있는 데이터를 읽어서 
buffer[]에 저장한다. 데이터를 읽을때 h와 v에 따라 어느 서보모터를 제어할 것인지의 여부가 나뉘게 된다. 
예를들어 150h을 C++에서 입력했을 경우 h에 해당하는 서보모터가 150도로 회전하고 40v를 입력했을 경우에는 v에 해당하는 서보모터가 40도로 회전하게 된다.

시리얼 통신으로 읽어온 데이터는 int형이 아닌 char형이기 때문에 atoi함수를 통해 int형으로 변환한다.

 

 

kocoafabeditor

항상 진취적이고, 새로운 것을 추구하는 코코아팹 에디터입니다!

시리얼통신, 아두이노, 기타