[SKKU DT] 29일차 -유니티(Unity)와 아두이노(Arduino) 연결하기

2023. 12. 8. 11:11SKKU DT

728x90
반응형

아두이노의 구성

Digital In/Out과 Analog In을 주로 사용할 것이다.

전원은 컴퓨터에서 오는 USB전원을 받게 되거나 소형 건전지로 공급할 수 있다.

 

 

BreadBoard

두 줄과 다섯 줄로 이루어져있다. 두 줄은 전원선으로, 버스(Bus)라고 한다.

 

 


 

 

https://www.tinkercad.com/

 

Tinkercad | From mind to design in minutes

Tinkercad is a free, easy-to-use app for 3D design, electronics, and coding.

www.tinkercad.com

로그인을 하고

회로를 새로 생성한다.

오른쪽에서 필요한 부품들을 꺼내서 쓸 수 있다.

 

전원의 공급은 3.3V 또는 5V에서 시작하고 GND(그라운드)로 끝나면 된다.

 

 

LED 켜기 예시. 마우스로 전선을 만들 수 있고 오른쪽 위에 시뮬레이션 시작을 누르면 시뮬레이션이 된다.

저항이 없어서 LED가 깨졌다.

저항을 달면 LED가 깨지지 않는다.

 

 


 

 

브레드 보드를 이용해서 불을 켜보자.

위와 같은 방식으로 브레드보드를 이용해서 LED에 불을 켤 수 있다.

 

 

이해하기 쉽게 빨간 선을 추가하면 회로 모양이 나오는 것을 볼 수 있다.

 

 


 

 

LED를 주황색으로 바꾸고 누름 버튼을 추가하면 누를 때마다 LED가 꺼지는 것을 볼 수 있다.

 

 

슬라이드 스위치로 LED를 켜기 위해서는 아래와 같은 배치를 할 수 있다.

 

 

마찬가지로 할 수 있는 LED 2개 불켜기(병렬연결)

 

 


 

 

아두이노 통합 개발 환경(Arduino IDE)

 

Arduino - Home

 

www.arduino.cc

Software에서 다운로드한다.

 

 

Arduino IDE를 설치하면 다음과 같은 화면이 나타난다.

왼쪽 위 체크표시는 컴파일 버튼, 화살표는 아두이노에 업로드 버튼, 그 옆 드롭다운 박스는 시리얼 포트를 선택하는 부분이다. 이후 컴퓨터와 아두이노를 연결하면 장치관리자에서 통신 포트를 확인할 수 있다.

 

 


 

 

컴퓨터와 아두이노가 연결되면 장치관리자에 Port가 뜨며 Arduino IDE에서 Arduino Uno를 해당 포트에 연결한다.

 

 

[File] - [Examples] - [01.Basics] - [Blink] 하면 아두이노의 불빛이 깜빡거린다.

 

 

[File] - [Examples] - [01.Basics] - [Fade] 하면 아두이노의 불빛이 켜져있는 상태로 깜빡거리지 않는다.

 

 


 

 

실제로 LED 램프 설치하기

 

스케치에서 13pin이 중요한 점이다. LED 등의 긴 부분이 +, 짧은 부분이 - 이다.

void setup() {
  pinMode(13, OUTPUT); //13번 핀을 출력으로 설정
}

void loop() {
  digitalWrite(13, HIGH); //LED 켜기
  delay(1000);            //1초 기다리기
  digitalWrite(13, LOW);  //LED 끄기
  delay(1000);            //1초 기다리기
}

HIGH 뜻 : 전력이 들어갔다

파워 5V 또는 3.3V 대신 13번 핀에서 전원이 나와서 LED가 깜빡깜빡한다.

 

 


 

 

유니티에서 시리얼 통신으로 연결하기

스케치에서 코드 넣기

int ledPin = 13; //LED를 연결할 핀 번호

void setup() {
  pinMode(ledPin, OUTPUT); //13번 핀을 출력으로 설정
  Serial.begin(19200);     //시리얼 통신 시작 (바우드레이트 9600)
}

void loop() {
  if(Serial.available() > 0){ //시리얼 데이터가 들어오면
    char receivedChar = Serial.read(); //데이터 읽기
    if(receivedChar == '1'){
      digitalWrite(ledPin, HIGH); //LED 켜기
    }
    else if(receivedChar == '0'){
      digitalWrite(ledPin, LOW); //LED 끄기
    }
  }
}

 

 

유니티에서 스크립트 생성하기

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;

public class ArduinoController : MonoBehaviour
{
    SerialPort serialPort = new SerialPort("COM8", 19200); //COM 포트와 바우드레이트 설정
    private bool ledState = false;
    void Start()
    {
        serialPort.Open(); //시리얼 포트 열기
        serialPort.ReadTimeout = 50; //읽기 타임아웃 설정
    }

    private void Update()
    {
        if(Input.GetKeyDown(KeyCode.Space)) //스페이스바가 눌리면
        {
            ledState = !ledState; //LED 상태 토글
            serialPort.Write(ledState ? "1" : "0"); //아두이노에 데이터 보내기
        }
    }

    private void OnDestroy()
    {
        if(serialPort != null && serialPort.IsOpen)
        {
            serialPort.Close(); //시리얼 포트 닫기
        }
    }
}

 

Systme.IO.Ports 네임스페이스에서 오류가 발생한다면, [Project Settings] - [Player] - [Other Settings] - [Configuration]에서 [Scripting Backend]와 [Api Compatibility Level]을 아래와 같이 수정하면 된다.

using System.IO.Ports

시리얼 포트를 열기 위해서 두 설정을 수정하면 된다.

 

 

아두이노를 컴퓨터와 연결하고 유니티 상에서 게임 오브젝트에 스크립트를 넣고 실행하고 [Space] 버튼을 누르면 LED가 켜졌다 꺼졌다 한다.

 

 


 

 

응용) LED 3개 번갈아가면서 켜기

int ledPin =13; // LED를 연결할 핀 번호
int newledPin =12; // LED를 연결할 핀 번호
int newnewledPin =11; // LED를 연결할 핀 번호
int i = 0;

void setup() {
  // put your setup code here, to run once:
  pinMode(ledPin, OUTPUT); // LED 핀을 출력으로 설정
  pinMode(newledPin, OUTPUT); // NEWLED 핀을 출력으로 설정
  pinMode(newnewledPin, OUTPUT); // NEWLED 핀을 출력으로 설정
  Serial.begin(19200); // 시리얼 통신 시작(바우드레이트 9600)
}

void loop() {
  // put your main code here, to run repeatedly:
  if (Serial.available() > 0) { //시리얼 데이터가 들어오면
    i++; 
    char receivedChar = Serial.read(); // 데이터 읽기
    if(i % 3 == 1) {
        digitalWrite(ledPin, HIGH); // LED 켜기
        digitalWrite(newledPin, LOW); // LED 끄기
        digitalWrite(newnewledPin, LOW); // LED 끄기
      }
    else if (i % 3 == 2){
      digitalWrite(ledPin, LOW); // LED 끄기
      digitalWrite(newledPin, HIGH); // LED 켜기
      digitalWrite(newnewledPin, LOW); // LED 끄기
      }
    else if(i % 3 == 0) {
      digitalWrite(ledPin, LOW); // LED 끄기
      digitalWrite(newledPin, LOW); // LED 끄기
      digitalWrite(newnewledPin, HIGH); // LED 켜기
    } 
  }  
}

 

 


 

 

조이스틱 값 표시하기

스케치에서 아래와 같은 스크립트를 생성한다.

Serial Monitor를 켜면 출력 값을 볼 수 있다.

Serial Monitor 오른쪽에 baud를 19200으로 맞춘다.

int x = A0;
int y = A1;

void setup(){
  Serial.begin(19200);
}

void loop(){
  int x_val = analogRead(x);
  int y_val = analogRead(y);

  Serial.print("x 좌표는 : ");
  Serial.print(x_val);
  Serial.print(" / ");
  Serial.print("y 좌표는 : ");
  Serial.println(x_val);
  delay(1000);
}

조이스틱의 좌표가 출력된다. 총 5개의 핀이 있으며 각각(조이스틱-아두이노 순서로) GND-GND, 5V-5V, URX-A0 ,URY-A1, SW-03에 연결했다.

중립일 때 (512, 512)가 출력된다.

 

 


 

 

2초마다 값이 변하는 아두이노의 n 값을 유니티에 가져오기

아두이노 스케치에 아래의 스크립트를 생성한다.

int n = 0;
void setup(){
  Serial.begin(19200);
}

void loop(){
  Serial.print(n);
  Serial.println("sec");
  delay(2000);
  n++;
}

 

유니티에서는 아래와 같은 스크립트를 생성한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
using System;

public class SerialReader : MonoBehaviour
{
    SerialPort serialPort;
    public string portName = "COM8"; //시리얼 포트 이름
    public int baudRate = 19200; //보드레이트
    void Start()
    {
        serialPort = new SerialPort(portName, baudRate);
        try
        {
            serialPort.Open(); //시리얼 포트 열기
        }
        catch(Exception e)
        {
            Debug.LogError("Serial port open error: " + e.Message);
        }
    }
    void Update()
    {
        if(serialPort != null && serialPort.IsOpen)
        {
            try
            {
                string data = serialPort.ReadLine();
                Debug.Log("Data received: " + data);
            }
            catch(TimeoutException) { }
        }
    }

    private void OnDestroy()
    {
        if(serialPort != null && serialPort.IsOpen)
        {
            serialPort.Close(); //시리얼 포트 닫기
        }
    }
}

 

게임 오브젝트에 스크립트를 넣고 실행을 하면 Console 창에 아래와 같은 출력 값이 나온다. 이때, Arduino IDE를 끄지 않으면 엑세스 거부 오류가 뜨고 IDE를 꺼야 정상적으로 출력이 된다.

 

 


 

 

아두이노에서 버튼 값 가져오기

스케치에서 아래의 스크립트를 입력한다.

const int buttonPin = 2; //버튼이 연결된 핀 번호

void setup(){
  pinMode(buttonPin, INPUT); //버튼 핀을 입력으로 설정
  Serial.begin(19200);        //시리얼 통신 시작
}

void loop(){
  int buttonState = digitalRead(buttonPin); //버튼 상태 읽기
  if(buttonState == HIGH){                  //버튼 눌렸을 경우
    Serial.println("jump");                 //'jump' 메시지 보내기
    delay(500);                             //디바운싱을 위한 딜레이
  }
}

 

유니티 스크립트는 아래와 같다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;

public class BoxJump : MonoBehaviour
{
    SerialPort serialPort = new SerialPort("COM8", 19200);
    public float jumpForce = 5f;
    void Start()
    {
        serialPort.Open(); //시리얼 포트 열기
        serialPort.ReadTimeout = 50;
    }

    private void Update()
    {
        if (serialPort.IsOpen)
        {
            try
            {
                string message = serialPort.ReadLine();
                if (message.Contains("jump"))
                {
                    GetComponent<Rigidbody>().AddForce(0, jumpForce, 0, ForceMode.VelocityChange);
                }
            }
            catch (System.TimeoutException)
            {
                //메시지가 없을 경우 예외 처리
            }
        }
    }

    private void OnDestroy()
    {
        if(serialPort != null && serialPort.IsOpen)
        {
            serialPort.Close(); //시리얼 포트 닫기
        }
    }
}

 

Rigidbody를 넣은 큐브에 해당 스크립트를 넣으면 버튼을 누를 때마다 큐브가 Y축 방향으로 점프를 하는 것을 볼 수 있다.

 

 


 

 

조이스틱을 움직여서 실제로 큐브 움직이기

실시간으로 들어오는 데이터를 나눠서 숫자 값만 들어오게 한 후 유니티의 transform.position에 Update 함수에서 적용되게 하면 될 것 같다.

 

 

문자열 중 숫자 값만 받아올 수 있게 하는 함수를 추가하였다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
using System;
using System.Text.RegularExpressions;

public class CubeMove : MonoBehaviour
{
    SerialPort serialPort = new SerialPort("COM8", 19200);
    public float moveX;
    public float moveZ;
    void Start()
    {
        serialPort.Open(); //시리얼 포트 열기
        serialPort.ReadTimeout = 50;
    }

    string ExtractNumbers(string message)
    {
        string pattern = @"\d+";
        MatchCollection matches = Regex.Matches(message, pattern);
        string result = string.Join("", matches);
        return result;
    }
    private void Update()
    {
        if (serialPort.IsOpen)
        {
            try
            {
                string message = serialPort.ReadLine();
                string result = ExtractNumbers(message);
                Debug.Log(result);
                if (message.Contains("x"))
                {
                    transform.position = GetComponent<Transform>().position;
                    transform.position = new Vector3(Convert.ToInt32(result)*(float)0.0000001, transform.position.y, transform.position.z);
                }
                else if(message.Contains("y 좌표는 : "))
                {
                    transform.position = GetComponent<Transform>().position;
                    transform.position = new Vector3(Convert.ToInt32(result), transform.position.y, transform.position.z);
                }
            }
            catch (System.TimeoutException)
            {
                //메시지가 없을 경우 예외 처리
            }
        }
    }

    private void OnDestroy()
    {
        if (serialPort != null && serialPort.IsOpen)
        {
            serialPort.Close(); //시리얼 포트 닫기
        }
    }
}

숫자값만 문자열로 받아오는 데에 성공하였다. 하지만 십만 자리 숫자로 들어오기 때문에 나누는 작업이 필요하다.

...

 

 

스케치 정답

//조이스틱 x축 y축 연결 핀 설정
const int joystickXPin = A0;
const int joystickYPin = A1; //y축 추가

void setup(){
  //시리얼 통신 시작
  Serial.begin(9600);
}

void loop(){
  //조이스틱 x축과 y축 값 읽기
  int joystickXValue = analogRead(joystickXPin);
  int joystickYValue = analogRead(joystickYPin);

  //조이스틱 X축 값에 따라 조건 검사
  if(joystickXValue <= 300){
    //X축 값이 300 이하이면 "A" 전송
    Serial.println("A");
  }
  else if(joystickXValue >= 700){
    //X축 값이 700 이상이면 "D" 전송
    Serial.println("D");
  }

  //조이스틱 Y축 값에 따라 조건 검사 추가
  if(joystickYValue <= 300){
    //X축 값이 300 이하이면 "W" 전송
    Serial.println("W");
  }
  else if(joystickYValue >= 700){
    //X축 값이 700 이상이면 "S" 전송
    Serial.println("S");
  }

  //조금의 지연을 추가하여 무한 루프를 받자
  delay(100);
}

 

 

C# 정답 (Switch문 대소문자 구별도 중요하다)

using System;
using System.IO.Ports;
using UnityEngine;

public class ArduinoInputReader : MonoBehaviour
{
    SerialPort serialPort;
    public string portName = "COM8"; // 시리얼 포트 이름
    public int baudRate = 19200; // 보드레이트
    public float moveSpeed = 20f;

    void Start()
    {
        serialPort = new SerialPort(portName, baudRate);
        try
        {
            serialPort.Open(); // 시리얼 포트 열기
        }
        catch (Exception e)
        {
            Debug.LogError("Serial port open error: " + e.Message);
        }
    }

    void Update()
    {
        if (serialPort != null && serialPort.IsOpen)
        {
            try
            {
                string data = serialPort.ReadLine(); // 시리얼 포트에서 데이터 읽기
                ProcessData(data.Trim()); // 데이터 처리
            }
            catch (TimeoutException) { }
        }
    }

    void ProcessData(string data)
    {
        switch (data)
        {
            case "A":
                // 'a' 데이터 처리
                transform.Translate(Vector3.left * Time.deltaTime);
                Debug.Log("Received 'A'");
                break;
            case "D":
                // 'd' 데이터 처리
                transform.Translate(Vector3.right * Time.deltaTime);
                Debug.Log("Received 'D'");
                break;
            case "S":
                // 's' 데이터 처리
                transform.Translate(Vector3.back * Time.deltaTime);
                Debug.Log("Received 'S'");
                break;
            case "W":
                // 'w' 데이터 처리
                transform.Translate(Vector3.forward * Time.deltaTime);
                Debug.Log("Received 'W'");
                break;
        }
    }

    void OnDestroy()
    {
        if (serialPort != null && serialPort.IsOpen)
        {
            serialPort.Close(); // 시리얼 포트 닫기
        }
    }
}

 

 

다행히 잘 움직인다. 조금 버벅이는 느낌이 있다.

728x90
반응형