ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 4. 컨트롤러 구성
    만들어보기/모노콥터 2023. 4. 24. 17:16

    합리화의 시작

    본래는 가지고 있던 Arduino Pro Mini에 블루투스로 Xbox One S 게임패드를 연결하여 사용할 생각이었다.


    그러나 이를 위해서는 Pro Mini용 USB Host Shield와 BLE를 지원하는 블루투스 동글이 필요했고,
    무엇보다 3.3v로 동작하는 Pro Mini(Pro Mini는 5V/16Mhz 모델과 3.3V/8Mhz 모델이 존재한다)가 필요했다.
    문제는, 내가 가지고 있는 Arduino Pro Mini가 5v/16Mhz 모델이라는 것이다.


    그래서, 새 보드와 USB Host Shield, 블루투스 동글을 전부 구매하면 얼마냐고?

     

    23,590₩

    배송비까지 포함하면 3만 3천원 정도 되겠다.

     

    USB 호스트 실드에 Xbox Wireless Adapter를 연결하는 방법도 생각해 보았는데, 우선 USB 호스트 실드 라이브러리 중 이를 지원하는 게 없고, 저 리시버가 유선 연결된 Xbox One 게임패드와 동일하게 동작할 것이라는 아주 희망적인 관측 아래에서 3만 3천원을 태워야 한다.

    이 돈이면 블루투스가 지원되는 새 보드를 사는 게 낫지 않나?


    '이왕이면'

    마침 찾아보니 이런 라이브러리가 있었다. 아주 좋다. 그러니까 이제 호환되는 보드를 구매하면 되겠다.

    • Arduino MKR WiFi 1010
    • Arduino UNO WiFi Rev2
    • rduino MKR Vidor 4000

     

    이들 중 MKR 라인업은 굳이? 라는 느낌이고, UNO WiFi Rev2는 너무 크다. EDF 옆에 붙이면 EDF가 거의 다 가려지는 수준이고, 무엇보다 Nano 33 IoT가 있으니 이 녀석을 쓸 이유가 없다.

    • Arduino Nano 33 IoT
    • Arduino Nano RP2040 Connect

     

    Nano 33 IoT는 Uno WiFi Rev2를 Nano 시리즈 폼팩터로 줄인 모델이다. SAMD21 Cortex®-M0+ 32bit 48Mhz MCU에 u-blox NINA-W102 WiFi/Bluetooth 모듈과 LSM6DS3 6-Axis IMU가 달려 있다.
    Nano RP2040 Connect는 Raspberry Pi RP2040 32bit 133Mhz MCU에 u-blox NINA-W102 WiFi/Bluetooth 모듈, LSM6DSOX 6-Axis IMU, ATECC608A Cryptographic IC, MP34DT06J 마이크와 16MB 플래시 메모리가 달려 있다.

     

    Board Nano 33 IoT Nano RP2040 Connect
    MCU SAMD21 Cortex®-M0+ 32bit low power ARM MCU Raspberry Pi RP2040 Cortex®-M0+ 32bit Dual Core ARM MCU
    Clock 48 Mhz 133 Mhz
    Digital I/O Pins 14 20
    Analog Input Pins 8 8
    PWM Pins 5 20
    External Interrupts 14 20
    Wireless Module u-blox NINA-W102 u-blox NINA-W102
    IMU LSM6DS3 LSM6DSOX
    I/O Voltage 3.3V 3.3V
    Vin Voltage 5-18V 5-18V
    DC Current per I/O Pin 7 mA 12 mA

     

    Nano RP2040 Connect쪽이 비교할 것도 없이 고사양이다. 이쪽은 사실상 Raspberry Pi Pico에 이것저것 붙이고 기존 Arduino와의 호환성을 챙긴 물건이라...

     

    9,400원 차이인데, 이 정도면 이왕 사는 거 RP2040 Connect를 사는 게 낫지 않을까? 동작 클럭도 높고, GPIO 수도 많고, 나중에 RPi Pico로써 써먹을 수도 있으니까.


    아무튼 질렀죠?

    그래서 주문하고 배송받았다.

    빵판 맞춤 사이즈

    3.3만원 -> 3.7만원 -> 4.6만원

    머리에서 뭔가 흐르는 것 같긴 한데, 그래도 송/수신기를 새로 사는 것보단 저렴하니까 OK.

    NINA-W102에 펌웨어를 올리고, Arduino 코드를 작성해서 RP2040에 올려보자.

    #include <Servo.h>
    #include <Bluepad32.h>
    
    #define SERVO_ANGLE_MIN -45.0
    #define SERVO_ANGLE_MAX 45.0
    #define SERVO_RAW_MIN 1000
    #define SERVO_RAW_MAX 2000
    #define SERVO_RAW_CENTER 1440
    #define SERVO_1_ANGLE 30.0
    #define SERVO_2_ANGLE 150.0
    #define SERVO_3_ANGLE 270.0
    #define TARGET_POWER_FACTOR 384400
    
    ControllerPtr myControllers[BP32_MAX_CONTROLLERS];
    
    Servo EDF;
    Servo Vane1;
    Servo Vane2;
    Servo Vane3;
    
    int throttle;
    int pitch;
    int roll;
    int yaw;
    int motorVal;
    int servo1Angle;
    int servo2Angle;
    int servo3Angle;
    int servo1Val;
    int servo2Val;
    int servo3Val;
    double target_angle;
    double target_power;
    
    // Arduino setup function. Runs in CPU 1
    void setup() {
      pinMode(LED_BUILTIN, OUTPUT);
    
      // Initialize serial
      Serial.begin(2000000);
      while (!Serial) {
        // wait for serial port to connect.
        ;
      }
    
      // "forgetBluetoothKeys()" should be called when the user performs
      // a "device factory reset", or similar.
      // Calling "forgetBluetoothKeys" in setup() just as an example.
      // Forgetting Bluetooth keys prevents "paired" gamepads to reconnect.
      // But might also fix some connection / re-connection issues.
      // BP32.forgetBluetoothKeys();
    
      String fv = BP32.firmwareVersion();
      Serial.print("Firmware version installed: ");
      Serial.println(fv);
    
      // To get the BD Address (MAC address) call:
      const uint8_t* addr = BP32.localBdAddress();
      Serial.print("BD Address: ");
      for (int i = 0; i < 6; i++) {
        Serial.print(addr[i], HEX);
        if (i < 5)
          Serial.print(":");
        else
          Serial.println();
      }
    
      // BP32.pinMode(27, OUTPUT);
      // BP32.digitalWrite(27, 0);
    
      // This call is mandatory. It setups Bluepad32 and creates the callbacks.
      BP32.setup(&onConnectedController, &onDisconnectedController);
    }
    
    // This callback gets called any time a new gamepad is connected.
    // Up to 4 gamepads can be connected at the same time.
    void onConnectedController(ControllerPtr ctl) {
      digitalWrite(LED_BUILTIN, HIGH);
      bool foundEmptySlot = false;
      for (int i = 0; i < BP32_MAX_GAMEPADS; i++) {
        if (myControllers[i] == nullptr) {
          Serial.print("CALLBACK: Controller is connected, index=");
          Serial.println(i);
          myControllers[i] = ctl;
          foundEmptySlot = true;
    
          // Optional, once the gamepad is connected, request further info about the
          // gamepad.
          ControllerProperties properties = ctl->getProperties();
          char buf[80];
          sprintf(buf,
                  "BTAddr: %02x:%02x:%02x:%02x:%02x:%02x, VID/PID: %04x:%04x, "
                  "flags: 0x%02x",
                  properties.btaddr[0], properties.btaddr[1], properties.btaddr[2],
                  properties.btaddr[3], properties.btaddr[4], properties.btaddr[5],
                  properties.vendor_id, properties.product_id, properties.flags);
          Serial.println(buf);
    
          // Attatch EDF and Servos
          EDF.attach(9, SERVO_RAW_MIN, SERVO_RAW_MAX);
          Vane1.attach(3, SERVO_RAW_MIN, SERVO_RAW_MAX);
          Vane2.attach(4, SERVO_RAW_MIN, SERVO_RAW_MAX);
          Vane3.attach(5, SERVO_RAW_MIN, SERVO_RAW_MAX);
          EDF.writeMicroseconds(1000);
          Vane1.writeMicroseconds(SERVO_RAW_CENTER);
          Vane2.writeMicroseconds(SERVO_RAW_CENTER);
          Vane3.writeMicroseconds(SERVO_RAW_CENTER);
          break;
        }
      }
      if (!foundEmptySlot) {
        Serial.println(
          "CALLBACK: Controller connected, but could not found empty slot");
      }
    }
    
    void onDisconnectedController(ControllerPtr ctl) {
      digitalWrite(LED_BUILTIN, LOW);
      bool foundGamepad = false;
    
      for (int i = 0; i < BP32_MAX_GAMEPADS; i++) {
        if (myControllers[i] == ctl) {
          Serial.print("CALLBACK: Controller is disconnected from index=");
          Serial.println(i);
          myControllers[i] = nullptr;
          foundGamepad = true;
    
          // detach EDF and Servos
          EDF.detach();
          Vane1.detach();
          Vane2.detach();
          Vane3.detach();
          break;
        }
      }
    
      if (!foundGamepad) {
        Serial.println(
          "CALLBACK: Controller disconnected, but not found in myControllers");
      }
    }
    
    void processGamepad(ControllerPtr gamepad) {
    
      throttle = gamepad->throttle();
      pitch = gamepad->axisY();
      roll = gamepad->axisX();
      yaw = gamepad->axisRX();
    
      // Set EDF pulse time
      motorVal = (double)throttle * (1000.0 / 1024.0) + 1000.0;
    
      // Set control target angle and power
      target_angle = degrees(atan2(-pitch, roll));
      target_power = (double)(sq(pitch) + sq(roll)) / TARGET_POWER_FACTOR;
    
      // Calculate each servo's angle
      servo1Angle = 45.0 * sin(radians(target_angle - SERVO_1_ANGLE)), -1, 1, SERVO_ANGLE_MIN, SERVO_ANGLE_MAX;
      servo2Angle = 45.0 * sin(radians(target_angle - SERVO_2_ANGLE)), -1, 1, SERVO_ANGLE_MIN, SERVO_ANGLE_MAX;
      servo3Angle = 45.0 * sin(radians(target_angle - SERVO_3_ANGLE)), -1, 1, SERVO_ANGLE_MIN, SERVO_ANGLE_MAX;
    
      // Calculate servo pulse time
      servo1Val = servo1Angle * target_power * (500.0 / 45.0) + SERVO_RAW_CENTER;
      servo2Val = servo2Angle * target_power * (500.0 / 45.0) + SERVO_RAW_CENTER;
      servo3Val = servo3Angle * target_power * (500.0 / 45.0) + SERVO_RAW_CENTER;
    
      // Write to servo
      EDF.writeMicroseconds(motorVal);
      Vane1.writeMicroseconds(servo1Val);
      Vane2.writeMicroseconds(servo2Val);
      Vane3.writeMicroseconds(servo3Val);
    
      // Debug output
      char buf[200];
      snprintf(
        buf, sizeof(buf) - 1,
        "throttle: %4d, pitch: %4li, roll: %4li, yaw: %4li, target_angle: %4.2lf, target_power: %4.2lf, "
        "motorVal: %4d, servo1Val: %4d, servo2Val: %4d, servo3Val: %4d",
        throttle, pitch, roll, yaw, target_angle, target_power, motorVal, servo1Val, servo2Val, servo3Val);
      Serial.println(buf);
    }
    
    // Arduino loop function. Runs in CPU 1
    void loop() {
      // Fetch all the controller info
      BP32.update();
    
      // It is safe to always do this before using the controller API.
      // This guarantees that the controller is valid and connected.
      for (int i = 0; i < BP32_MAX_CONTROLLERS; i++) {
        ControllerPtr myController = myControllers[0];
    
        if (myController && myController->isConnected()) {
          if (myController->isGamepad())
            processGamepad(myController);
        }
      }
    }

    라이브러리의 예제 코드와 이전에 작성했던 코드들을 적당히 섞어서 작성.

    멀쩡히 동작한다. EDF와 서보들을 연결해서 봐도 정상인 것 같은데...

     

    이제 진짜 하드웨어를 조립해야 다음 단계를 진행할 수 있을 것 같다.

    어떻게 EDF에 기판과 ESC와 배터리와 서보들을 고정해야 할까..?

    '만들어보기 > 모노콥터' 카테고리의 다른 글

    6. 프레임 제작  (0) 2023.06.09
    5. 가조립  (0) 2023.04.30
    3. 추력 편향 베인  (0) 2023.04.09
    2. EDF 테스트  (0) 2023.03.30
    1. 부품 선정  (0) 2023.03.13
Designed by Tistory.