LED 와 저항


LED를 사용할 때 왜 저항을 사용해야 하나요?
몇 Ω 의 저항을 사용하는 것이 적당한가요?



아두이노에서 digitalWrite 로 HIGH 를 출력하면 5V의 전압이 출력됩니다.
(https://www.arduino.cc/en/Reference/digitalWrite)

전자부품의 사양이 적힌 문서를 Datasheet 라고 합니다.
LED의 Datasheet를 보면...
(각 부품의 제조사마다 사양이 다르기 때문에 해당 부품의 제조사에서 제공되는 문서를 참조해야 합니다.)

순방향 전류 (Foward Current) 값이 100 mA 로 표시되어 있습니다.
"이 부품을 사용하시려면 100 mA 의 전류가 흐르도록 해주세요" 라는 뜻이 됩니다.

전자부품은 각각 동작 범위가 정해져 있고 이를 벗어나면 동작하지 않거나 고장나 버립니다.
그래서 LED에 적당한 저항을 붙여 동작 환경을 범위 수치 내에 들어가도록 조정해주는 것입니다.


옴의 법칙 (Ohm's Law)


독일의 물리학자 게오르그 시몬 옴(Georg Simon Ohm) 의 이름을 딴 전기 법칙입니다.

전류는 전압에 비례하고 저항에 반비례 한다.

전압이 크면 전류도 커지고, 저항이 커지면 전류는 작아진다는 뜻입니다.
수식으로는
 이렇게 됩니다.



LED에 사용할 저항값 계산


그럼 수식을 알았으니 LED에 사용할 저항값 R을 계산해 보겠습니다.
I = V / R 이니까 R을 구하려면 R = V / I 가 됩니다.
V = 5v 이고 , I = 100mA = 0.1A 니까
R = V / I = 5 / 0.1 = 50 Ω 이 됩니다.

결론 : 50 Ω 정도 되는 저항을 사용해서 LED 를 사용하면 되겠습니다. 
이 결과는 위 데이터 시트에 따라 계산한 결과 입니다.
LED의 종류와 그에 맞는 데이터시트의 수치에 따라 값이 달라질 수 있습니다.


한걸음 더 들어가기


위 데이터 시트에 보면 Foward Current - Peak Pulse 라고 된 항목이 있습니다 
이 값은 순간적으로 전류가 높아져서 Peak 가 1 A 까지 되는 경우는 견딜 수 있다는 뜻입니다.
그러니까 그 이상이 되면 고장나서 못쓰게 된다는 것을 알려주고 있습니다.

1A 가 되는 R 값은  V / I = 5 / 1 = 5,   즉 5 Ω 이 됩니다.
아무리 저항값을 줄인다 해도 5 Ω 보다 낮추면 LED가 고장나버리게 될 것입니다.
물론 저 값은 Peak 의 순간 전류를 뜻하는 것이기 때문에 그 전에 고장나 버릴 겁니다.


Trinus 3D 프린터 리뷰

[3D 프린터 리뷰]


Kodama 사의 Trinus 3D 프린터 입니다.





킥스타터에서 주문했습니다. 2016년 7월에 주문했는데, 해를넘겨 2017년 1월에 배송됐네요.


이렇게 패키징 되어 있었습니다.


비닐을 벗겨내고 다시 한 번 샷.
보시다 시피 조립하는 부품들이 모두 컴포넌트화 되어 있습니다.
모두 메탈바디로 되어 있어 꽤나 묵직하고 아주 튼튼해 보입니다.
조립설명서 다운로드 ]

조립 설명서를 보면서 따라해 보면 총 10단계로 간단히 조립이 끝납니다.
나사는 모두 육각나사로 되어 있고, 육각봉과 핀셋이 함께 동봉되어 있어 아무것도 없이 조립을 완료할 수 있습니다.



조립 완성! 30분이 채 걸리지 않았습니다.
배선은 모두 커넥터가 색상 별로 구분되어 있어서 보드에 색깔맞춰 연결만 시켜주면 됩니다.

완성된 외양을 보시면 우선 아주 튼튼합니다. 그리고 베드가 너무 작네요. (120x125x125 입니다.)
베드는 플라스틱(?) 재질로 히트베드가 아닙니다.
위쪽에 불 들어온 부분이 보드구요, 여기에 SD 카드를 꽂을 수 있습니다.
LCD 가 없습니다.!!!!!!! 카드를 꽂으면 어떻게 파일을 선택해서 프린트 하지???? 걱정했는데,
SD카드를 꽂으니까 알아서 프린트를 시작합니다.... 이 부분은 뒷부분에서 다시 설명하죠.


프린트 되는 동영상을 타임랩스로 다 찍고 싶었으나... 촬영도중에 전화가 와서 여기까지만...
출력 결과는 이렇습니다.


출력물 입니다. 이게 데모 출력물 입니다. 역시.. 끝까지 출력하고 싶었으나, 중간에 코드를 잘못 뽑아서 멈춰버렸네요. 암튼, 퀄리티는 매우 좋습니다. 오히려 사진이 실물보다 못나와 보이네요.
Z축 레벨링을 하지 않은 상태라서 간격이 매우 컸는데, 라프트가 받쳐줘서 잘 나오네요.


Thingiverse 에 있는 프린터 테스트용 모델을 출력해 봤습니다.
슬라이싱은 포럼에서 받은 설정으로 Simplify3D 를 사용했는데, top 면이 빈 틈이 좀 보이는게 약간 수정이 필요할 듯 합니다.

보시면 베드에 금이 그어져 있는 것이 특이한데요, 출력을 몇 번 해보니 그 용도를 알게 됐습니다.
첫 레이어용 필라멘드를 뿌리면 저 틈새에도 들어가면서 출력물이 좌우로 흔들리거나 떨어지지 않게 잡아주는 역할을 하게 됩니다. 그래서 출력하고 나면 출력물 바닥면에 틈새에 들어가서 굳은 격자무늬가 보입니다.
일단, 이 프린터는 무조건 라프트(Raft)를 설정해서 뽑아야 할 것 같습니다.


이 프린터는 슬라이서 프로그램을 따로 지원하고 있습니다.
Pango 라는 프로그램인데 별로 좋아보이진 않습니다.


USB 케이블을 연결해서 프린트 할 수 있구요, SD카드에 담아서  프린트 할 수 있습니다. (전 LCD가 없으니 못하네요)
이 프로그램으로 Z 오프셋을 설정할 수 있습니다. 오프셋 설정하고 나서는 이 프로그램 쓸 일이 없어 보입니다. 슬라이서는 제가 주로 사용하는 Simplify3D 를 사용할 예정입니다.
Kodama 의 포럼 사이트에서  Simplify3D 용 설정 파일을 찾을 수 있었습니다.

SD카드에 autoprint.gcode 라는 파일명으로 저장해 놓으면 SD카드를 꽂았을 때 자동으로 프린트 됩니다.
데모 프린트 파일은 autoprint.pcode 라는 파일로 저장되어 있었습니다. gcode, pcode 모두 지원하고 있습니다. (pcode 는 paladin 펌웨어용 머신 코드 입니다.)
동봉되는 SD카드에는 autoprint.pcode 와 함께, 조립 메뉴얼(pdf), Pango 슬라이서 (윈도우용, 맥용) 가 들어 있습니다. 또 샘플용 나사뭉치 모델 파일(stl)파일도 있습니다.

이 프린터의 특징은 익스트루더 뭉치를 레이저 장치로 바꿀 수 있다는 건데요. 그래서 2-in-1 이라는 것이 컨셉이죠. 그 외에도 히드베드나 LCD, 케이스 등등 모두 컴포넌트화 되어서 부속별로 별도로 판매되고 있습니다.

저는 가장 기본 패키지만 구매해서 레이저나 히드베드, LCD 등이 없습니다. 히드베드나 케이스, 레이저는 아직 없어도 될것 같고, LCD 패널은 당장 사야할 것 같습니다.

기본 사양을 정리해 보면 이렇습니다.

  • Bed Size : 120 x 125 x 125 (y axis reversed)
  • Filament : 1.75mm (ABS, PLA, wood, aluminum, bronze, PC, flex and many others)
  • File format : gcode, pcode
  • Slicer : Pango
  • Max. Temp: 255 degrees
  • firmware : Paladin

총 평입니다.

1. 컴포넌트화 되어 있는 퀄리티가 높고 하드웨어 구성이 인상적이다.
2. 베드가 너무 작아 아쉽다.
3. 풀메탈 바디라 정말 견고하다. 흔들림, 뒤틀림 없고 심지어 프린트 중에 들어 옮겨도 잘 출력된다.
4. 출력물 퀄리티는 매우 만족스럽다. (productivity quality 가 광고 문구 였음)
5. 출력할 때는 Raft를 해야 한다.

크기가 작은 고퀄리티 출력물을 원하거나
작은 레이터 커팅기와 프린터를 모두 원한다면 선택할만 합니다.

여담으로,
배송이 너무 느렸는데, 그동안 담당자와 계속 메일도 주고받고, 사이트에 소식도 보면, 공장이나 배송 체계에서 문제가 있었던 모양입니다. 스타트업이라 그 부분이 정착되지 않았었는데, 이제 마무리 되어 밀린 주문을 해결 중이라고 합니다. 이제 부터 구매한다면 저처럼 많이 기다리지는 않겠네요.
LCD 패널을 미리 확인하지 못하고 구매하지 못해 아쉽습니다. 이 리뷰를 마치고 바로 주문해야 겠어요.
전원 스위치가 없드라구요! 끄려면 아답타 코드를 뽑아야 합니다. 스위치 달린 멀티탭도 하나 사야겠네요.


점프 케이블 만들기

빵판에 꽂을 수 있는 케이블 (점프케이블 - 연결용 전선) 을 만들어 보자.

보통 전기선은 얇은 구리선 가닥 모음으로 되어 있어서 빵판에 꽂기에는 힘이 많이 부족하다

빵판에 꽂을 수 있는 힘있는 부분이 필요하고 여기에 스테플러 심을 사용할 계획이다.

심을 하나를 반으로 잘라 사용하면 길이가 적당하다... 고 생각했는데,
그냥 하나를 길게 펴서 사용하고 길면 잘라내는 것이 더 좋다.

전선의 끝에 스테플러 심을 납땜으로 붙여준다.

그리고 연결 부를 수축튜브로 마무리 한다.

빵판용 점프케이블 완성


 Tip.
스테플러 심은 서로 본드로 붙어 있어서 하나씩 떼고 나면 본드의 잔해때문에 인두에 대면 타서 거름과 불순물들이 생기게 된다. 그러면 납이 잘 붙지 않는다.
그래서 납을 잘 붙게 하려면 페이스트를  사용하는 것이 좋다.

납을 붙이려는 부분에 페이스트를 묻히고 인두로 슥슥 문지르면 면이 깨끗해지고 납도 잘 붙는다.



IR로 조종하기



주요부품

모터 드라이버 ( WB291111 / L298N )



  1. VMS 에 모터용 전원 연결
  2. GNS 에는 아두이노 GND와도 연결
  3. ENA, Int1, Int2 - 모터 A용 (속도, 앞, 뒤)
  4. Int3, Int4, ENB - 모터 B용 (앞, 뒤 속도)
    • 속도 : PWM 으로 속도 제어.
    • 앞,귀 :  (HIGH, LOW) , (LOW, HIGH) 값으로 전진/후진 제어



회로구성



  1. 9V 배터리 : 아두이노 Vin, MotorDriver-VMS
  2. IR 센서 : 3번핀
  3. MotorDriver-Enable : 9번핀
  4. MotorDriver-Int1, Int2 : 12, 11번핀
  5. MotorDriver-Int3, Int4 : 6, 5번핀



하두이노 소스

#include "IRRemote.hpp"

int RECV_PIN = 3;
IRRemote irrecv(RECV_PIN);

int PIN_MOTOR_R_FORWARD = 11;
int PIN_MOTOR_R_BACK = 12;
int PIN_MOTOR_L_FORWARD = 6;
int PIN_MOTOR_L_BACK = 5;
int PIN_SPEED = 9;

void setup()
{
  pinMode(PIN_MOTOR_L_FORWARD, OUTPUT);
  pinMode(PIN_MOTOR_L_BACK, OUTPUT);
  pinMode(PIN_MOTOR_R_FORWARD, OUTPUT);
  pinMode(PIN_MOTOR_R_BACK, OUTPUT);
  pinMode(PIN_SPEED, OUTPUT);

  Serial.begin(9600);
  irrecv.start();
  irrecv.useRepeat(true);
}

void loop() {
  if (irrecv.receive()) {
    Serial.println(irrecv.toString());

    analogWrite(PIN_SPEED, 255);

    switch (irrecv.value()) {
      case NUM1 :
        digitalWrite(PIN_MOTOR_L_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_L_BACK, LOW);
        digitalWrite(PIN_MOTOR_R_FORWARD, HIGH);
        digitalWrite(PIN_MOTOR_R_BACK, LOW);
        break;
      case NUM2 :
        digitalWrite(PIN_MOTOR_L_FORWARD, HIGH);
        digitalWrite(PIN_MOTOR_L_BACK, LOW);
        digitalWrite(PIN_MOTOR_R_FORWARD, HIGH);
        digitalWrite(PIN_MOTOR_R_BACK, LOW);
        break;
      case NUM3 :
        digitalWrite(PIN_MOTOR_L_FORWARD, HIGH);
        digitalWrite(PIN_MOTOR_L_BACK, LOW);
        digitalWrite(PIN_MOTOR_R_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_R_BACK, LOW);
        break;
      case NUM4 :
        digitalWrite(PIN_MOTOR_L_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_L_BACK, HIGH);
        digitalWrite(PIN_MOTOR_R_FORWARD, HIGH);
        digitalWrite(PIN_MOTOR_R_BACK, LOW);
        break;
      case NUM5 :
        digitalWrite(PIN_MOTOR_L_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_L_BACK, LOW);
        digitalWrite(PIN_MOTOR_R_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_R_BACK, LOW);
        break;
      case NUM6 :
        digitalWrite(PIN_MOTOR_L_FORWARD, HIGH);
        digitalWrite(PIN_MOTOR_L_BACK, LOW);
        digitalWrite(PIN_MOTOR_R_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_R_BACK, HIGH);
        break;
      case NUM7 :
        digitalWrite(PIN_MOTOR_L_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_L_BACK, LOW);
        digitalWrite(PIN_MOTOR_R_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_R_BACK, HIGH);
        break;
      case NUM8 :
        digitalWrite(PIN_MOTOR_L_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_L_BACK, HIGH);
        digitalWrite(PIN_MOTOR_R_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_R_BACK, HIGH);
        break;
      case NUM9 :
        digitalWrite(PIN_MOTOR_L_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_L_BACK, HIGH);
        digitalWrite(PIN_MOTOR_R_FORWARD, LOW);
        digitalWrite(PIN_MOTOR_R_BACK, LOW);
        break;
    }
  } else {
    digitalWrite(PIN_MOTOR_R_FORWARD, LOW);
    digitalWrite(PIN_MOTOR_R_BACK, LOW);
    digitalWrite(PIN_MOTOR_L_FORWARD, LOW);
    digitalWrite(PIN_MOTOR_L_BACK, LOW);
  }
  delay(300);
}

IR센서 사용하기

IR Sensor 와 리모콘



위 사진에 나오는 리모콘을 사용합니다. 키 배열이 다르거나 다른 종류의 리모콘이라면
입력되는 값이 다르기 때문에 그에 맞게 소스를 수정해야 할 것입니다.
IrRemote.hpp 의 소스에서 키값 정의 부분을 수정하시면 됩니다.


회로구성


센서의 다리가 세개이고, 한쪽은 - 로 반대편은 S 로 표시되어 있습니다.
- 족은 GND 에 연결하고 가운데는  5V,  S  쪽은 3번핀에 연결하였습니다.


소스코드


#include "IRRemote.hpp"

int RECV_PIN = 3;
IRRemote irrecv(RECV_PIN);

void setup()
{
  Serial.begin(9600);
  irrecv.start();
  irrecv.useRepeat(true);
}

void loop() {
  if (irrecv.receive()) {
    Serial.println(irrecv.toString());
  }
}

리모콘으로 부터 입력받은 키를 시리얼 모니터에 출력합니다.
IRRemote.hpp 를 사용하는데, 아래 코드를 사용하면 됩니다.


<IrRemote.hpp>


#include <IRremote.h>

#define Unkown 0x0
#define CHm   0xFFA25D
#define CH    0xFF629D
#define CHp   0xFFE21D
#define REW   0xFF22DD
#define FF    0xFF02FD
#define PLAY  0xFFC23D
#define Minus 0xFFE01F
#define Plus  0xFFA857
#define EQ    0xFF906F
#define NUM0  0xFF6897
#define p100  0xFF9867
#define p200  0xFFB04F
#define NUM1  0xFF30CF
#define NUM2  0xFF18E7
#define NUM3  0xFF7A85
#define NUM4  0xFF10EF
#define NUM5  0xFF38C7
#define NUM6  0xFF5AA5
#define NUM7  0xFF42BD
#define NUM8  0xFF4AB5
#define NUM9  0xFF52AD

#define _CHm   0xE318261B
#define _CH    0x511DBB
#define _CHp   0xEE886D7F
#define _REW   0x52A3D41F
#define _FF    0xD7E84B1B
#define _PLAY  0x20FE4DBB
#define _Minus 0xF076C13B
#define _Plus  0xA3C8EDDB
#define _EQ    0xE5CFBD7F
#define _NUM0  0xC101E57B
#define _p100  0x97483BFB
#define _p200  0xF0C41643
#define _NUM1  0x9716BE3F
#define _NUM2  0x3D9AE3F7
#define _NUM3  0x6182021B
#define _NUM4  0x8C22657B
#define _NUM5  0x488F3CBB
#define _NUM6  0x449E79F
#define _NUM7  0x32C6FDF7
#define _NUM8  0x1BC0157B
#define _NUM9  0x3EC3FC1B

class IRRemote {
  private:

    long _lastValue = 0;
    bool use_repeat = true;

    IRrecv *irrecv;
    decode_results results;

  public:

    IRRemote(int signalPin) {
      irrecv = new IRrecv(signalPin);
    }

    ~IRRemote() {
      delete irrecv;
    }

    /**
       IR센싱 시작
    */
    void start() {
      irrecv->enableIRIn();
    }

    /**
       버튼을 누르고 있는 동안 값을 전달할지 여부 결정 (기본값 : true)
       @param repeatable 반복키 사용여부
    */
    void useRepeat(bool repeatable) {
      use_repeat = repeatable;
    }

    /**
       IR센서값 읽기
       @return 값 입력 여부
    */
    bool receive() {
      int received = irrecv->decode(&results);
      if (received) {
        irrecv->resume();
        adjustValue(&results);
        //if (results.decode_type == UNKNOWN) return false;
        if (!isValid(results.value))  return false;
        if (!use_repeat && results.value == 0xFFFFFFFF) {
          _lastValue = Unkown;
          return false;
        }
        parseValue();
      }
      return received ? true : false;
    }

    /**
       입력된 센서값 조회
       @return 센서값
    */
    long value() {
      return _lastValue;
    }

    /**
       입력된 센서값을 텍스트로 반환
       @return 눌려진 버튼 텍스트
    */
    const char* toString() {
      switch (_lastValue) {
        case CHm : return "CH-";
        case CH  : return "CH";
        case CHp : return "CH+";
        case REW : return "|<<";
        case FF  : return ">>|";
        case PLAY : return ">||";
        case Minus : return "-";
        case Plus : return "+";
        case EQ : return "EQ";
        case NUM0 : return "0";
        case p100 : return "100+";
        case p200 : return "200+";
        case NUM1 : return "1";
        case NUM2 : return "2";
        case NUM3 : return "3";
        case NUM4 : return "4";
        case NUM5 : return "5";
        case NUM6 : return "6";
        case NUM7 : return "7";
        case NUM8 : return "8";
        case NUM9 : return "9";
      }
      return "";
    }

  protected:

    void adjustValue(decode_results *recv) {
      long value = recv->value;
      switch (value) {
        case _CHm : recv->value = CHm; break;
        case _CH  : recv->value = CH; break;
        case _CHp : recv->value = CHp; break;
        case _REW : recv->value = REW; break;
        case _FF  : recv->value = FF; break;
        case _PLAY : recv->value = PLAY; break;
        case _Minus : recv->value = Minus; break;
        case _Plus : recv->value = Plus; break;
        case _EQ : recv->value = EQ; break;
        case _NUM0 : recv->value = NUM0; break;
        case _p100 : recv->value = p100; break;
        case _p200 : recv->value = p200; break;
        case _NUM1 : recv->value = NUM1; break;
        case _NUM2 : recv->value = NUM2; break;
        case _NUM3 : recv->value = NUM3; break;
        case _NUM4 : recv->value = NUM4; break;
        case _NUM5 : recv->value = NUM5; break;
        case _NUM6 : recv->value = NUM6; break;
        case _NUM7 : recv->value = NUM7; break;
        case _NUM8 : recv->value = NUM8; break;
        case _NUM9 : recv->value = NUM9; break;
      }
    }
    
    long parseValue() {
      long value = results.value;
      if (value == 0xFFFFFFFF) { //repeat
        return _lastValue;
      }
      _lastValue = value;
      return value;
    }

    bool isValid(long value) {
      switch (value) {
        case CHm :
        case CH  :
        case CHp :
        case REW :
        case FF  :
        case PLAY :
        case Minus :
        case Plus :
        case EQ :
        case NUM0 :
        case p100 :
        case p200 :
        case NUM1 :
        case NUM2 :
        case NUM3 :
        case NUM4 :
        case NUM5 :
        case NUM6 :
        case NUM7 :
        case NUM8 :
        case NUM9 :
        case 0xFFFFFFFF:
          return true;
      }
      _lastValue = Unkown;
      return false;
    }
};

키를 한 땀 한 땀 입력하면서 얻은 값을 바탕으로 만들었습니다.
같은 키인데도 여러경우의 값이 나타나게 되서 두가지 케이스만 적어 넣었습니다.
입력 각도가 기울어지거나 거리가 멀어지면 쓰레기값이 입력되거나
엉뚱한 값이 나오게 -값이 중간에 잘리거나 하는 것 같습니다- 되어 인식률이 빠릿빠릿 하진 않습니다.

조향장치가 없는 자동차

조향장치가 없는 자동차의 경우
  • 좌/우 바퀴를 한쪽만 돌리거나 서로 반대방향으로 돌리는 방식으로 방향 조종을 수행한다.
  • 예) 탱크



아두이노 코드

  • 조향부분을 한쪽 바퀴만 돌리는 방식으로 수정



#include <SoftwareSerial.h>

#define CENTER  90
#define LEFT    50
#define RIGHT   130

int blueTx = 2;
int blueRx = 3;
int MotorPin1 = 5;
int MotorPin2 = 6;
int MotorPin3 = 10;
int MotorPin4 = 11;

int direction = CENTER;

SoftwareSerial BTSerial(blueTx, blueRx);

void setup() {
  Serial.begin(9600);
  BTSerial.begin(9600);
  pinMode(MotorPin1, OUTPUT);
  pinMode(MotorPin2, OUTPUT);
  pinMode(MotorPin3, OUTPUT);
  pinMode(MotorPin4, OUTPUT);

  digitalWrite(MotorPin1, LOW);
  digitalWrite(MotorPin2, LOW);
  digitalWrite(MotorPin3, LOW);
  digitalWrite(MotorPin4, LOW);
}

void loop() {
  if (Serial.available() > 0) {
    int val = Serial.read();
    doRemote(val);
  }
  if (BTSerial.available() > 0) {
    int val = BTSerial.read();
    doRemote(val);
  }
}

void doRemote(int val) {
  //HANDLE
  if (val == 'l') {
    direction = LEFT;
  } else if (val == 'c') {
    direction = CENTER;
  } else if (val == 'r') {
    direction = RIGHT;
  }

  //MOTOR
  else if (val == 'f') {
    if (direction == RIGHT) {
      digitalWrite(MotorPin1, HIGH);
      digitalWrite(MotorPin2, LOW);
      digitalWrite(MotorPin3, LOW);
      digitalWrite(MotorPin4, LOW);
    }
    else if (direction == LEFT) {
      digitalWrite(MotorPin1, LOW);
      digitalWrite(MotorPin2, LOW);
      digitalWrite(MotorPin3, HIGH);
      digitalWrite(MotorPin4, LOW);
    }
    else if (direction == CENTER) {
      digitalWrite(MotorPin1, HIGH);
      digitalWrite(MotorPin2, LOW);
      digitalWrite(MotorPin3, HIGH);
      digitalWrite(MotorPin4, LOW);
    }
  } else if (val == 's') {
    digitalWrite(MotorPin1, LOW);
    digitalWrite(MotorPin2, LOW);
    digitalWrite(MotorPin3, LOW);
    digitalWrite(MotorPin4, LOW);
  } else if (val == 'b') {
    if (direction == RIGHT) {
      digitalWrite(MotorPin1, LOW);
      digitalWrite(MotorPin2, HIGH);
      digitalWrite(MotorPin3, LOW);
      digitalWrite(MotorPin4, LOW);
    }
    else if (direction == LEFT) {
      digitalWrite(MotorPin1, LOW);
      digitalWrite(MotorPin2, LOW);
      digitalWrite(MotorPin3, LOW);
      digitalWrite(MotorPin4, HIGH);
    }
    else if (direction == CENTER) {
      digitalWrite(MotorPin1, LOW);
      digitalWrite(MotorPin2, HIGH);
      digitalWrite(MotorPin3, LOW);
      digitalWrite(MotorPin4, HIGH);
    }
  }
}


BLDC 모터

BLDC Motor - Brushless DC Motor


ESC - Electronic Speed Controller


Circuits


Arduino


#include "Servo.h"

Servo servo;

void setup() {
  Serial.begin(9600);
  servo.attach(9);
}

int motorSpeed = 700;
int adding = 50;

void loop() {
  motorSpeed += adding;
  if(motorSpeed > 2500 || motorSpeed < 700) adding = adding * -1;

  servo.writeMicroseconds(motorSpeed);

  Serial.print("Speed: ");
  Serial.println(motorSpeed);

  delay(100);
}

non-PID 제어

Tester

STL files



Circuits


Arduino

#include "MPU6050_DMP6.h"
#include "Servo.h"

Servo servo;

void setup() {
  Serial.begin(9600);

  servo.attach(9);

  int mpuInit = mpuSetup();
  if (mpuInit == 0) {
    Serial.println("MPU6050 connected");
  } else {
    Serial.print("DMP Init Fail(");
    Serial.print(mpuInit);
    Serial.println(")");
  }
}

int motorSpeed = 700;

void loop() {
  mpuLoop();

  float roll = mpuRoll();
  if(roll > 0) {
    motorSpeed++;
  } else if(roll < 0) {
    motorSpeed--;
  }

  if(motorSpeed > 2500) motorSpeed = 2500;
  if(motorSpeed < 700) motorSpeed = 700;

  servo.writeMicroseconds(motorSpeed);
  Serial.print("Angle: ");
  Serial.print(roll);
  Serial.print("\t Speed: ");
  Serial.println(motorSpeed);
  delay(50);
}



Result

센서 인식 테스트

Tester

STL files




Circuit


Arduino

#include "MPU6050_DMP6.h"

void setup() {
  Serial.begin(9600);

  int mpuInit = mpuSetup();
  if (mpuInit == 0) {
    Serial.println("MPU6050 connected");
  } else {
    Serial.print("DMP Init Fail(");
    Serial.print(mpuInit);
    Serial.println(")");
  }
}

void loop() {
  mpuLoop();

  float roll = mpuRoll();

  Serial.print("Angle: ");
  Serial.println(roll);

  delay(50);
}


Result

MPU-6050



Circuit



Arduino Library

I2CDev : Download / Source
MPU6050 : Download / Source


Arduino Code

#include "MPU6050_DMP6.h"

void setup() {
  Serial.begin(9600);

  int mpuInit = mpuSetup();
  if (mpuInit == 0) {
    Serial.println("MPU6050 connected");
  } else {
    Serial.print("DMP Init Fail(");
    Serial.print(mpuInit);
    Serial.println(")");
  }
}

void loop() {
  mpuLoop();

  Serial.print("YAW: ");
  Serial.print(mpuYaw());
  Serial.print("\tPITCH: ");
  Serial.print(mpuPitch());
  Serial.print("\tROLL: ");
  Serial.println(mpuRoll());
}



MPU6050_DMP6.h

#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"

volatile bool mpuInterrupt = false;
void dmpDataReady() {
  mpuInterrupt = true;
}

MPU6050 mpu;
Quaternion quant;
VectorFloat gravity;
bool dmpReady = false;
int packetSize;
int fifoCount;
int mpuIntStatus;
byte fifoBuffer[64];
float ypr[3];

int mpuSetup() {
  Wire.begin();
  TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz)

  mpu.initialize();
  if (!mpu.testConnection()) {
    return -1;
  }

  int dmpInit = mpu.dmpInitialize();
  if (dmpInit == 0) {
    mpu.setDMPEnabled(true);
    attachInterrupt(0, dmpDataReady, RISING);
    mpuIntStatus = mpu.getIntStatus();
    packetSize = mpu.dmpGetFIFOPacketSize();
    dmpReady = true;
  } else {
    return dmpInit;
  }

  return 0;
}

void mpuLoop() {
  if (!dmpReady) return;
  while (!mpuInterrupt && fifoCount < packetSize);

  mpuIntStatus = mpu.getIntStatus();
  fifoCount = mpu.getFIFOCount();

  if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
    mpu.resetFIFO();  //FIFO overflow
    return;
  }

  if (!(mpuIntStatus & 0x02)) {
    return;
  }

  while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount();
  mpu.getFIFOBytes(fifoBuffer, packetSize);
  fifoCount -= packetSize;

  mpu.dmpGetQuaternion(&quant, fifoBuffer);
  mpu.dmpGetGravity(&gravity, &quant);
  mpu.dmpGetYawPitchRoll(ypr, &quant, &gravity);

  ypr[0] = ypr[0] * 180 / M_PI;
  ypr[1] = ypr[1] * 180 / M_PI;
  ypr[2] = ypr[2] * 180 / M_PI;
}

float mpuYaw() {
  return ypr[0];
}

float mpuPitch() {
  return ypr[1];
}

float mpuRoll() {
  return ypr[2];
}



DC 모터 드라이버

HG7881

  •  외부전원 인가
  • 스피드 컨트롤 가능 (PWM)
  • DC모터 2개 연결 가능


 사용예



int MOTOR_A_DIR =  8;
int MOTOR_A_PWM =  9;
int MOTOR_B_DIR = 12;
int MOTOR_B_PWM = 11;

int PWM_SLOW  = 50;
int PWM_FAST  = 200;

void setup()
{
  pinMode( MOTOR_A_DIR, OUTPUT );
  pinMode( MOTOR_A_PWM, OUTPUT );
  pinMode( MOTOR_B_DIR, OUTPUT );
  pinMode( MOTOR_B_PWM, OUTPUT );
  digitalWrite( MOTOR_A_DIR, LOW );
  digitalWrite( MOTOR_A_PWM, LOW );
  digitalWrite( MOTOR_B_DIR, LOW );
  digitalWrite( MOTOR_B_PWM, LOW );
}
 
void loop()
{
  //FOWARD
  digitalWrite( MOTOR_A_DIR, LOW );
  analogWrite( MOTOR_A_PWM, PWM_FAST );
  digitalWrite( MOTOR_B_DIR, LOW );
  analogWrite( MOTOR_B_PWM, PWM_FAST );

  //STOP
  digitalWrite( MOTOR_A_DIR, LOW );
  digitalWrite( MOTOR_A_PWM, LOW );
  digitalWrite( MOTOR_B_DIR, LOW );
  digitalWrite( MOTOR_B_PWM, LOW );

  //BACKWARD
  digitalWrite( MOTOR_A_DIR, HIGH );
  analogWrite( MOTOR_A_PWM, 255 - PWM_FAST );
  digitalWrite( MOTOR_B_DIR, HIGH );
  analogWrite( MOTOR_B_PWM, 255 - PWM_FAST );
}



이동/회전

Roll / Pitch / Yaw



Roll



Pitch



Yaw



비행 원리

프로펠러 propeller


  • 4개의 프로펠러를 사용
  • 2가지 종류를 조합하여 사용
    추진용 프로펠러 (Pusher Type)
    견인식 프로펠러 (Tractor Type)
  • 지면을 향해 양력 발생 / 회전 방향은 반대



프로펠러 배치



  • 회전 방향을 교차해서 배치


Socket 통신

Protocol

  • packet size : 5 bytes
  • [0] : command
  • [1] : value
  • [2] : reserved
  • [3] : reserved
  • [4] : reserved


Server (Arduino)


#include <ESP8266WiFi.h>

typedef struct _protocol {
  char cmd;
  char val;
  char etc1;
  char etc2;
  char etc3;
} Protocol;

const char AP_SSID[] = "Makecube Drone";
const char AP_PASS[] = "12345678";
const int  SERVER_PORT = 7777;

WiFiServer server(SERVER_PORT);
WiFiClient client;

void setup() 
{
  Serial.begin(115200);

  WiFi.mode(WIFI_AP);
  WiFi.softAP(AP_SSID, AP_PASS);
    
  server.begin();

  Serial.println("*** Connection Info ***");
  Serial.print("SSID : "); Serial.println(AP_SSID);
  Serial.print("PASS : "); Serial.println(AP_PASS);
  Serial.print("IP   : "); Serial.println(WiFi.localIP());
  Serial.print("PORT : "); Serial.println(SERVER_PORT);
  Serial.println("+- Server Ready -+");
}

void loop() 
{
  if (!client) {
    client = server.available();
    if(client) {
      Serial.println("Client Connected");
    }
    return;
  }

  if(client.available() ) {
    Protocol command;
    command.cmd = client.read();
    command.val = client.read();
    command.etc1 = client.read();
    command.etc2 = client.read();
    command.etc3 = client.read();

    doControl(command);
  }
}

void doControl(Protocol cmd) {
  Serial.print("Control : ");
  Serial.print(cmd.cmd);
  Serial.print(":");
  Serial.println((int)cmd.val);

  if(cmd.cmd == 'q' && cmd.val == 0) {
    client.stop();
    Serial.println("Disconnected");
    return;
  }
}




Client (PC : Java)


package in.makecube.drone.controller;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {

 public static void main(String[] args) {
  Controller controller = new Controller();
  controller.setServer("192.168.4.1", 7777);
  controller.connect();

  BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  while (true) {
   try {
    String command = br.readLine();
    if ("q".equals(command))
     break;
    controller.send(command);
   } catch (Exception e) {
    e.printStackTrace();
   }
  }

  controller.disconnect();
 }

}


package in.makecube.drone.controller;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Controller {

 private String serverIP;
 private int port;

 Socket socket;
 InputStream in;
 OutputStream out;
 byte[] chunk;

 public Controller() {
  chunk = new byte[5];
 }

 public void setServer(String address, int port) {
  serverIP = address;
  this.port = port;
 }

 public void connect() {
  try {
   socket = new Socket(serverIP, port);
   in = socket.getInputStream();
   out = socket.getOutputStream();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 public void disconnect() {
  send("q0");
  try {
   if (in != null)
    in.close();
   if (out != null)
    out.close();
   if (socket != null)
    socket.close();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 public void send(String command) {
  if (socket == null || out == null)
   return;
  if (command == null || command.trim().length() == 0)
   return;

  try {
   chunk[0] = (byte) command.charAt(0);
   try {
    chunk[1] = (byte) Integer.parseInt(command.substring(1));
   } catch (Exception e) {
    chunk[1] = (byte) 0;
   }
   chunk[2] = (byte) 0;
   chunk[3] = (byte) 0;
   chunk[4] = (byte) 0;
   
   out.write(chunk);
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}




ESP8266 AP mode

WiFi Module : ESP8266

Arduino + ESP8266 : Wemod D1 mini


    $2.63 on aliexpress : LINK




ESP8266 Arduino Library

    Build-in Library

  #include <ESP8266WiFi.h>


ESP8266 AP Mode 


#include <ESP8266WiFi.h>

const char AP_SSID[] = "Makecube Drone";
const char AP_PASS[] = "12345678";

WiFi.mode(WIFI_AP);
WiFi.softAP(AP_SSID, AP_PASS);