티스토리 뷰
개요
* 일단 돌아가게만 구현한 것이라, 코드에 민망한 부분이 있을 수 있음.
* 전체 코드 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[] = { 0xBE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; byte ip[] = { 192, 168, 0, 92 }; // Arduino IP byte server[] = { 192, 168, 0, 140 }; // 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[] = { 0xBE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; byte ip[] = { 192, 168, 0, 92 }; // Arduino IP byte server[] = { 192, 168, 0, 140 }; // 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
|
|
'embedded' 카테고리의 다른 글
ScriptCommunicator - Serial packet test (0) | 2018.12.20 |
---|---|
Arduino - Memory and PROGMEM, F() macro (0) | 2018.09.28 |
Arduino Serial 로 hex 전송하기 (5) | 2018.09.04 |
UART, TTL, RS232, RS422, RS485 간단 구분 (3) | 2018.08.24 |
아두이노에서 ds18b20 온도센서 다루기 (0) | 2018.08.22 |

- Total
- Today
- Yesterday
- 엉클 밥
- 독서후기
- 클린 아키텍처
- clean agile
- agile
- bun
- Bug
- API
- backend
- notion
- 독서
- 클린 애자일
- websocket
- postgres
- golang
- 인텔리제이
- strange
- Gin
- go
- 노션
- OpenAI
- ChatGPT
- solid
- 영화
- intellij
- 잡학툰
- 티스토리챌린지
- 오블완
- 2023
- 2024년
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |