-
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, 블루투스 동글을 전부 구매하면 얼마냐고?- Arduino Pro Mini 3.3V - 3,740₩
- Arduino Mini USB Host Shield - 13,950₩
- 아이피타임 BT50XR - 5,900₩
계 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와의 호환성을 챙긴 물건이라...
- Arduino Nano 33 IoT - 36,300₩
- Arduino Nano RP2040 Connect - 45,700₩
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