티스토리 뷰

embedded

Arduino - TCP Client로 명령받기

주먹불끈 2018. 9. 18. 20:49

개요

 

* 일단 돌아가게만 구현한 것이라, 코드에 민망한 부분이 있을 있음.

* 전체 코드 gist:  https://gist.github.com/nicewook/214e1fca4a9f3783129bff91b7320c2a


 

1. Arduino Ethernet Shield 이용하여 TCP Client 구현하고

2. 대기하다가 TCP Server 명령 (OPCode) 받으면 특정 동작을 수행한다.

 

덧붙여

1. Arduino Ethernet IP 할당하는 방법을 알아보고

2. Hercules SETUP utility 통해 TCP 송수신을 테스트 해본다.

 

 

준비물

 

- Arduino UNO

- Arduino Ethernet Shield W5100

- SPI 이용해 UNO 통신 (Pin 10, 11, 12, 13)

- 연결속도 10 / 100Mb

- 작동전압 5V

 

 

아두이노의 IP Address 알아보자

 

방법1. 아두이노 기본제공 예제로 알아볼 있다.

 

- 링크: https://www.arduino.cc/en/Tutorial/DhcpAddressPrinter

- 업로드하고 시리얼 모니터 (Ctrl+Shift+M) 실행하면 할당된 IP 출력됨

- 부분을 구현하려는 소스코드에 넣으면 코드 사이즈가 많이 늘어난다.

 

 


 


 

 

 

방법2. 임의의 IP 할당해주기

 

- 그냥 Static 하게 박아넣을 수도 있다.

- 여기서는, 처음 실행시 시리얼을 통해 설정하도록 구현하였다.

 

 

아두이노 IP, 서버 IP/Port 설정하도록 해보자

 

간단한 설정 코드. 그냥 참고만 하세요

* 인터넷 여기저기서 짜집기 것이라 출처를 남기지 못하는 양해를 구합니다.

 

byte mac[] = { 0xBE0xAD0xBE0xEF0xFE0xED };

byte ip[] = { 192168092 };  // Arduino IP

byte server[] = { 1921680140 }; // Server IP

int tcp_port = 9090;

전역변수로 선언해둔 값들

1) MAC Address 임의의

2) ip 아두이노의 local IP

3) server, tcp_port 아두이노가 연결할 TCP Server 정보임.

 

2) 3) 아래 코드를 통해 아두이노 시작시 시리얼을 통해 입력받을 것임

void setTcpInfo(void)

{

  Serial.print("Set Arduino IP (ex.192.168.0.92): ");

  recvWithEndMarker();

  showNewData();

  parseIPAddress();

 

  Serial.print("Set Server IP (ex.192.168.0.140): ");

  recvWithEndMarker();

  showNewData();

  parseServerAddress();

 

  Serial.print("Set Server Port (ex.9090): ");

  recvWithEndMarker();

  showNewData();

  parseServerPort();

} 

setTcpInfo()

 

1) Arduino IP, Server IP, Port 설정

2) 각각 아래 프로세스를 따른다.

- 시리얼로부터의 문자열을 받아서 char array 저장

- 문자열을 출력하여 검증

- 문자열을 Parsing 하여 ip[], server[], tcp_port 저장

 

void recvWithEndMarker() 

{

  byte ndx = 0;

  char endMarker = '\n';

  char rc;

 

  while (newData == false) {

    delay(100);

 

    while (Serial.available() > 0)  {

      rc = Serial.read();

 

      if (rc != endMarker) {

        receivedChars[ndx] = rc;

        ndx++;

        if (ndx >= numChars) {

          ndx = numChars - 1;

        }

      }

      else {

        receivedChars[ndx] = '\0'// terminate the string

        ndx = 0;

        newData = true;

      }

    }

  }

}

 

void showNewData() 

{

  if (newData == true) {

    Serial.println(receivedChars);

    newData = false;

  }

}

recvWithEndMarker()

 

- 시리얼 입력으로 받은 문자열을 char array 저장한다

 

showNewData()

 

- 저장된 문자열을 출력한다

void parseIPAddress()

{

  byte ndx = 0;

  char delim[] = ".";

  char *ptr;

  char *context = NULL;

 

  ptr = strtok_r(receivedChars, delim, &context);

  ip[ndx] = atoi(ptr);

 

  while (ptr = strtok_r(NULL, delim, &context)) {

    ndx++;

    ip[ndx] = atoi(ptr);

  }

 

  Serial.print("Arduino IP: ");

  for (int i = 0; i < 4; i++) {

    Serial.print(ip[i]);

    if (i != 3) Serial.print(".");

    else Serial.println();

  }

}

 

void parseServerAddress()

{

  byte ndx = 0;

  char delim[] = ".";

  char *ptr;

  char *context = NULL;

 

  ptr = strtok_r(receivedChars, delim, &context);

  server[ndx] = atoi(ptr);

 

  while (ptr = strtok_r(NULL, delim, &context)) {

    ndx++;

    server[ndx] = atoi(ptr);

  }

 

  Serial.print("Server IP: ");

  for (int i = 0; i < 4; i++) {

    Serial.print(server[i]);

    if (i != 3) Serial.print(".");

    else Serial.println();

  }

}

 

void parseServerPort()

{

  tcp_port = atoi(receivedChars);

  Serial.print("Server Port: ");

  Serial.println(tcp_port);

}

parseIPAddress()

parseServerAddress()

parseServerPort()

 

- IP Address 경우 strtok_r() 함수를 이용하여

- delimiter "." 으로 나눈뒤

- integer 변환하여 저장 (atoi() 함수)

 

 

참고링크: https://goo.gl/Rtd5EN

 

1) strtok() 쓰레드 관련 문제가 있으니 사용을 지양한다.

2) 윈도우에서는 strtok_s, 리눅스에서는 strtok_r 사용하면 된다.

 

인터넷 시작 대기하기

 

byte mac[] = { 0xBE0xAD0xBE0xEF0xFE0xED };

byte ip[] = { 192168092 };  // Arduino IP

byte server[] = { 1921680140 }; // Server IP

int tcp_port = 9090;

EthernetClient client;

1) Arduino MAC IP Address

2) 연결할 서버의 IP Port

 

- setTcpInfo() 함수에서 재설정되기는

const byte numChars = 32;

char receivedChars[numChars];   // an array to store the received data

boolean newData = false;

boolean tcpStarted = false;

byte tcpOpcode = 0x00;

int tcpIndex = 0;

- TCP 받는 패킷의 데이터를 받기 위한 변수들

#define STX 0x02

#define ETX 0x03

#define OPCODE_1 0x11

#define OPCODE_2 0x12

#define OPCODE_3 0x13

- 패킷 데이터들

- STX, ETX: 패킷의 시작과 끝을 의미함

- OPCODE 1, 2, 3 (Operation Code)

- 전송되어 오는 OPCODE 값을 보고

- 서버가 요구하는 명령을 인지한다.

union Packet_t {

  byte data[3]; // STX(1) OPCODE(1) ETX(1)

  byte BytePacket[sizeof(data) / sizeof(byte)];

};

 

Packet_t packet;

- union 이용하며 다양한 data type 으로 되어 있는 구조체를

- byte array 바꾸어서 전송할 있다.

여기의 구현에서는 의미가 없기는 하다.

void setup(void

{

 

  // 시리얼소프트웨어시리얼 시작

  Serial.begin(9600);

  Serial.println("Start");

  Serial.println("--");

 

  // TCP 설정 아두이노 IP, 서버 IP, 서버 Port

  setTcpInfo();

 

  //TCP 시작하기

  Ethernet.begin(mac, ip);

  delay(1000);

  Serial.println("Connecting...");

  clientConnect();

}

1) 아두이노의 SW 시리얼 통신을 시작

 

2) 만들어둔 setTcpInfo() 통해 인터넷 통신을 위한 정보를 설정

 

3) Ethernet.begin() 으로 아두이노 인터넷 초기화

 

4) clientConnect()

- client.connect() 서버와 연결

void clientConnect(void)

{

  if (client.connect(server, tcp_port)) {  // 서버에 연결하기

    Serial.println("Connected to server");

    client.write(0xFF);  // 연결 확인용 한바이트 보내보기

  else {

    Serial.println("connection failed");

  }

}

 

void loop(void

{

 

  delay(500);

 

  // 수신대기 및 처리

  if (checkTcpReceived()) {

    sendToTcp(tcpOpcode);

  }

 

  //연결 확인

  if (!client.connected()) {

    Serial.println();

    Serial.println("reconnecting.");

    client.stop();

    clientConnect();

    delay(2000);

  }

}

핵심은 함수로 되어있다.

 

1) checkTcpReceived()

 

- 서버로부터의 패킷을 수신대기한다

- 수신하면 특정 OPCODE 있는지 확인한다

있으면 해당 OPCODE 전역변수에 저장해둠

  이런 OPCODE 서버, 클라이언트간 사전 약속된 것임

 

2) sendToTcp()

 

- 수신한 패킷이 true 경우 호출되며

- 수신한 데이터를 송신한다

 

 

서버 명령을 받고 동작하기

 

OPCode 01, 02, 03 따라서 다르게 문자전송하게 해보자

 

boolean checkTcpReceived(void)

{

  // 0x02, 0x01, 0x03 수신 확인부

  while (client.available() > 0)

  {

    // 수신내용 출력

    int8_t tmpData = client.read();

    Serial.print(tmpData);

    Serial.print(" ");

 

    if (tcpStarted == false &&  tmpData == STX) {

      Serial.println("STX");

      tcpStarted = true;

      tcpIndex = 0;

    }

    else if (tcpStarted == true && tmpData != ETX) {

      Serial.print("OPCODE: ");

      Serial.print(tmpData);

      Serial.print(" ");

      tcpOpcode = tmpData;

    }

    else if (tcpStarted == true && tmpData == ETX)

    {

      Serial.println("ETX");

      tcpStarted = false;

      if (tcpOpcode == OPCODE_1) {

        Serial.println("OPCODE_1");

        return true;

      else if (tcpOpcode == OPCODE_2) {

        Serial.println("OPCODE_2");

        return true;

      else if (tcpOpcode == OPCODE_3) {

        Serial.println("OPCODE_3");

        return true;

      else {

        Serial.print("ETX OPCODE ERROR: ");

        Serial.println(tcpOpcode);

        return false;

      }

    }

    else {

      return false;

    }

  }

  return false;

}

checkTcpReceived()

 

- loop() 함수에서 0.5초마다 호출됨

- 제대로 OPCODE 받았을때만 return true

 

1) 수신 data STX (=0x02) 일때만 tcpStarted = true 된다.

2) 이후 들어오는 data ETX (=0x03) 아니라면 OPCODE 로서

- tcpOpcode 저장된다.

3) 이후 들어오는 데이터가 ETX (=0x03) 일때

- 이때의 tcpOpcode 확인하여

- 각각의 OPCODE 맞는 명령을 수행한다.

여기서는 그저 OPCODE 몇번 인지만을 출력함

 

 

void sendToTcp(byte data)

{

 

  packet.data[0= STX;

  packet.data[1= data;

  packet.data[2= ETX;

 

  Serial.print("dataToServer: ");

  for (int i = 0; i < sizeof(packet.BytePacket); i++) {

    Serial.print(packet.BytePacket[i], HEX);

    Serial.print(" ");

  }

  Serial.println();

 

  client.write(packet.BytePacket, sizeof(packet.BytePacket));

}

sendToTcp()

 

1) packet.data 생성후 

2) 전송할 값을 (디버깅을 위해) 출력한 다음

3) packet.BytePacket 전송한다. client.write()

 

전체 프로세스 정리

 

1) setup()

 

- 시리얼 초기화

- 시리얼을 통해 인터넷 설정 (아두이노의 MAC, IP, 서버의 IP, Port)

- Ethernet 시작 서버 연결

 

2) loop()

 

- 0.5 마다 TCP 수신확인

- 수신하면 parsing 하여 서버로부터의 OPCODE (Operation Code) 확보

- OPCODE 따라 동작하고, 서버로 회신

 

 

허큘리스에서 확인하기

 

- TCP Server 역할을 해주는 프로그램

- 다운로드 링크: https://www.hw-group.com/software/hercules-setup-utility


 

1) TCP Server 프로그램 실행 (hurcules_3-2-8.exe)

 



서버역할을 해주는 프로그램

 

1) TCP Server 탭을 열고

 

2) Port 9090 으로 설정후 Listen

 

3) 추후 data 전송시에는 HEX 전송한다


HEX Enable 해두면, 송수신 데이터값을

HEX 보여준다.

 

2) 아두이노 프로그램 실행 IP 설정

 


 

업로드 시리얼 모니터 프로그램 실행 (Ctrl+Shift+M)

 

1) 아두이노는 192.168.0.92 설정

- 내에서 다른 Device IP 충돌하지 않으면 된다.

 

2) 서버의 IP 192.168.0.140

- hercules 프로그램이 실행된 PC IP

- command 창에서 ipconfig all 명령으로 알아낼 있다.

 

3) port 9090

 

Connected to server

 

3) 서버 패킷 전송, 클라이언트 (=아두이노) 패킷 수신 처리

 

1) 서버에서 0x02, 0x11, 0x03 전송 (=OPCODE_1)

2) 아두이노는 패킷을 수신하여 OPCODE 17 (=0x11) 수신, 다시 서버로 0x02, 0x11, 0x03 보낸다

3) 서버에서 보낸 패킷을 확인해보자

4) 서버가 수신한 패킷 확인 0x02, 0x11, 0x03

 



 

 

 


반응형
반응형
잡학툰 뱃지
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함