산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

개요

이 튜토리얼에서는 DIY 아두이노 RC 송신기를 만드는 방법을 배웁니다. 제가 만드는 프로젝트들에 무선 제어가 자주 필요해서, 거의 모든 용도로 사용할 수 있는 이 다기능 라디오 컨트롤러를 제작했습니다.

아래 비디오를 시청하거나 작성된 튜토리얼을 읽어보세요.

작동 원리

이제 수신 측에서 약간의 조정만으로 모든 아두이노 프로젝트를 무선으로 제어할 수 있습니다. 이 송신기는 상용 RC 송신기처럼 RC 장난감, 자동차, 드론 등을 제어하는 데도 사용할 수 있습니다. 이를 위해서는 해당 상용 RC 기기를 제어하는 적절한 신호를 생성하는 간단한 아두이노 수신기가 필요합니다.

이 튜토리얼에서는 아두이노 로봇 자동차 제어, 이전 영상의 아두이노 개미 로봇 제어, ESC와 서보 모터를 이용한 브러시리스 DC 모터 제어 등의 예제를 통해 작동 원리를 설명합니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

이 컨트롤러의 무선 통신은 NRF24L01 트랜시버 모듈 기반이며, 증폭 안테나와 함께 사용하면 개방된 공간에서 최대 700미터까지 안정적인 통신 거리를 가집니다. 총 14개 채널을 제공하며, 그중 6개는 아날로그 입력, 8개는 디지털 입력입니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

두 개의 조이스틱, 두 개의 포텐쇼미터, 두 개의 토글 스위치, 여섯 개의 버튼이 있으며, 추가로 가속도계와 자이로스코프로 구성된 내부 측정 장치(IMU)가 있어 컨트롤러를 움직이거나 기울이는 것만으로도 제어가 가능합니다.

아두이노 RC 송신기 회로도

먼저 회로도를 살펴보겠습니다. 이 RC 컨트롤러의 두뇌는 아두이노 프로 미니(Arduino Pro Mini)이며, 약 7.4볼트를 출력하는 2개의 리포(LiPo) 배터리로 전원을 공급합니다. 프로 미니의 RAW 핀에 직접 연결할 수 있는데, 내장 전압 레귤레이터가 전압을 5V로 낮춰줍니다. 아두이노 프로 미니는 5V 버전과 3.3V 버전 두 가지가 있으니 주의하세요.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

반면 NRF24L01 모듈은 반드시 3.3V가 필요하며, 전용 소스에서 공급하는 것이 권장됩니다. 따라서 3.3V 전압 레귤레이터를 배터리에 연결해 7.4V를 3.3V로 변환해야 합니다. 또한 모듈 바로 옆에 디커플링 커패시터를 배치해 전압을 더 안정적으로 유지하면 무선 통신도 더 안정적이 됩니다. NRF24L01 모듈은 SPI 프로토콜로 아두이노와 통신하며, MPU6050 가속도계/자이로 모듈은 I2C 프로토콜을 사용합니다.

필요한 부품

PCB 설계

아두이노 프로 미니의 모든 아날로그/디지털 핀을 사용하게 되어 점퍼 와이어로 연결하면 매우 복잡해집니다. 그래서 EasyEDA 무료 온라인 회로 설계 소프트웨어로 맞춤 PCB를 설계했습니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

양손으로 쉽게 잡을 수 있고 모든 컨트롤이 손가락 닿는 범위에 있도록 인체공학을 고려했습니다. 모서리는 둥글게 처리했고, 나중에 PCB를 무언가에 고정할 수 있도록 3mm 구멍을 추가했습니다. 아두이노 프로 미니 프로그래밍 핀을 컨트롤러 상단에 배치해 재프로그래밍 시 쉽게 접근할 수 있게 했습니다. RX/TX 핀을 조이스틱 버튼용으로 사용했는데, 스케치 업로드 시 이 두 라인이 아무것에도 연결되지 않아야 하므로 점퍼 캡으로 쉽게 연결/분리할 수 있게 핀으로 끊어두었습니다.

주의: 사용하는 아두이노 프로 미니 버전에 맞는 PCB인지 확인하거나, 버전에 맞게 PCB 설계를 수정하세요. 아두이노와 전압 레귤레이터에 따른 세 가지 버전 비교 사진입니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

PCB 설계 프로젝트 파일 링크 (세 가지 버전이 별도 탭으로 열립니다)

설계를 마치면 PCB 제조용 거버 파일을 생성합니다.

거버 파일: 다운로드

이 프로젝트의 후원사인 JLCPCB에서 PCB를 주문했습니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

거버 파일을 드래그 앤 드롭으로 업로드하면 거버 뷰어에서 PCB를 검토할 수 있습니다. 문제가 없으면 원하는 속성(이번엔 검은색 PCB 선택)을 선택하고 주문하면 합리적인 가격에 제작 가능합니다. JLCPCB 첫 주문 시 최대 10장까지 $2에 제작할 수 있습니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

도착한 PCB입니다. 검은색이라 정말 멋지고, 품질도 훌륭하며 설계와 정확히 일치합니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

PCB 조립

이제 PCB를 조립합니다. 아두이노 프로 미니 핀 헤더 납땜부터 시작합니다. 브레드보드에 꽂아 고정한 상태에서 납땜하면 편합니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

프로 미니 측면에도 핀이 있는데, 제조사마다 위치가 다를 수 있습니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

제가 사용한 모델은 양쪽에 각각 5핀씩 필요하며, PCB 하단에 배선을 위해 GND 핀 하나는 비워둡니다. 핀 헤더를 PCB에 직접 납땜하고 남은 부분을 잘라냅니다. 바로 옆에 MPU6050 가속도계/자이로 모듈을 장착합니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

3.3V 전압 레귤레이터와 커패시터, NRF24L01 모듈 근처에도 커패시터를 납땜합니다. 이 모듈은 세 가지 버전이 있는데 모두 사용 가능합니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

아두이노 프로그래밍 핀, RX/TX 핀, 전원 공급 핀, 파워 스위치를 납땜합니다.

포텐쇼미터 납땜을 위해 핀 헤더로 핀을 연장했습니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

노브 길이를 미리 잘라 캡이 제대로 맞도록 했습니다. 포텐쇼미터는 나중에 정확한 높이를 확인한 후 납땜합니다.

두 개의 토글 스위치와 두 개의 조이스틱을 장착하고 납땜합니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

마지막으로 네 개의 푸시 버튼을 납땜하는데, 높이가 맞지 않아 핀 헤더로 핀을 연장했습니다.

PCB가 완성되었으니 커버를 만듭니다. PCB가 예쁘게 보여 투명 아크릴로 커버를 만들기로 했습니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

4mm 두께 투명 아크릴에 보호 필름이 붙어 있어 파란색으로 보입니다. PCB 모양의 판 두 개를 만들어 위아래로 고정하는 방식입니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드
아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

PCB 모양을 표시하고 금속 톱으로 아크릴을 자른 후, 줄로 모양을 다듬었습니다. 두 판 모두 PCB와 완벽하게 맞습니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

부품이 통과할 구멍 위치를 표시하고 3mm 드릴로 고정용 4개 구멍을 뚫었습니다. 볼트가 판과 평행하게 들어가도록 카운터싱크도 가공했습니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

토글 스위치와 포텐쇼미터 구멍은 6mm 드릴, 조이스틱 구멍은 25mm 포스너 비트를 사용했습니다. 역시 줄로 구멍을 다듬었습니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

커버 조립 전 참고: 전원 공급 핀 헤더를 거꾸로 납땜해 배터리가 있을 뒷면에서 접근할 수 있게 했습니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

보호 필름을 벗겨내니 아크릴이 정말 깨끗해졌습니다. 먼저 상판에 포텐쇼미터 두 개를 고정하고, 3mm 마운팅 볼트와 11mm 거리 링을 끼웁니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

상판과 PCB를 볼트로 고정한 후, 포텐쇼미터를 PCB에 납땜합니다(높이가 확정되었으므로).

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

하판에 배터리 홀더를 볼트 2개로 고정하고, 네 개의 마운팅 볼트로 하판을 PCB 뒷면에 고정해 커버 조립을 마칩니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

배터리 선을 전원 핀에 연결하고, 포텐쇼미터 노브와 조이스틱 노브를 끼우고, NRF24L01 모듈에 안테나를 장착하면 DIY 아두이노 RC 송신기 완성입니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

아두이노 프로그래밍

프로 미니 보드 프로그래밍을 위해 USB-시리얼 UART 인터페이스가 필요합니다. 컨트롤러 상단의 프로그래밍 헤더에 연결하세요.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

아두이노 IDE 도구 메뉴에서 보드: Arduino Pro or Pro Mini, 프로세서 버전 선택, 포트 선택, 프로그래머: USBasp로 설정합니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

이제 코드를 업로드할 수 있습니다.

DIY 아두이노 기반 RC 송신기 코드

송신기 코드 작동 원리를 설명합니다. 무선 통신을 위해 SPI와 RF24 라이브러리, 가속도계 모듈을 위해 I2C 라이브러리(Wire.h)를 포함합니다. 디지털 입력 정의, 변수, 라디오 객체, 통신 주소를 정의합니다.

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>

// 디지털 입력 정의
#define jB1 1  // 조이스틱 버튼 1
#define jB2 0  // 조이스틱 버튼 2
#define t1 7   // 토글 스위치 1
#define t2 4   // 토글 스위치 2
#define b1 8   // 버튼 1
#define b2 9   // 버튼 2
#define b3 2   // 버튼 3
#define b4 3   // 버튼 4

const int MPU = 0x68; // MPU6050 I2C 주소
float AccX, AccY, AccZ;
float GyroX, GyroY, GyroZ;
float accAngleX, accAngleY, gyroAngleX, gyroAngleY;
float angleX, angleY;
float AccErrorX, AccErrorY, GyroErrorX, GyroErrorY;
float elapsedTime, currentTime, previousTime;
int c = 0;

RF24 radio(5, 6); // nRF24L01 (CE, CSN)
const byte address[6] = "00001"; // 통신 주소

컨트롤러의 14개 입력값을 저장할 구조체를 정의합니다. NRF24L01 버퍼 한계인 32바이트가 최대 크기입니다.

// 최대 크기 32바이트 - NRF24L01 버퍼 한계
struct Data_Package {
  byte j1PotX;
  byte j1PotY;
  byte j1Button;
  byte j2PotX;
  byte j2PotY;
  byte j2Button;
  byte pot1;
  byte pot2;
  byte tSwitch1;
  byte tSwitch2;
  byte button1;
  byte button2;
  byte button3;
  byte button4;
};
Data_Package data;

setup에서 MPU6050을 초기화하고 IMU 오차를 계산합니다(나중 각도 계산에 사용).

void initialize_MPU6050() {
  Wire.begin();
  Wire.beginTransmission(MPU);
  Wire.write(0x6B);
  Wire.write(0x00);
  Wire.endTransmission(true);
  // 가속도계 설정: +/- 8g
  Wire.beginTransmission(MPU);
  Wire.write(0x1C);
  Wire.write(0x10);
  Wire.endTransmission(true);
  // 자이로 설정: 1000dps
  Wire.beginTransmission(MPU);
  Wire.write(0x1B);
  Wire.write(0x10);
  Wire.endTransmission(true);
}

무선 통신을 초기화하고, 모든 디지털 입력에 내부 풀업 저항을 활성화하며, 변수 초기값을 설정합니다.

radio.begin();
radio.openWritingPipe(address);
radio.setAutoAck(false);
radio.setDataRate(RF24_250KBPS);
radio.setPALevel(RF24_PA_LOW);

pinMode(jB1, INPUT_PULLUP);
pinMode(jB2, INPUT_PULLUP);
pinMode(t1, INPUT_PULLUP);
pinMode(t2, INPUT_PULLUP);
pinMode(b1, INPUT_PULLUP);
pinMode(b2, INPUT_PULLUP);
pinMode(b3, INPUT_PULLUP);
pinMode(b4, INPUT_PULLUP);

loop에서 모든 아날로그 입력을 읽고 0~1023 값을 0~255 바이트 값으로 매핑해 구조체에 저장합니다.

data.j1PotX = map(analogRead(A1), 0, 1023, 0, 255);
data.j1PotY = map(analogRead(A0), 0, 1023, 0, 255);
data.j2PotX = map(analogRead(A2), 0, 1023, 0, 255);
data.j2PotY = map(analogRead(A3), 0, 1023, 0, 255);
data.pot1 = map(analogRead(A7), 0, 1023, 0, 255);
data.pot2 = map(analogRead(A6), 0, 1023, 0, 255);

풀업 저항 사용으로 버튼 눌림 시 디지털 핀 값은 0이 됩니다.

data.j1Button = digitalRead(jB1);
data.j2Button = digitalRead(jB2);
data.tSwitch2 = digitalRead(t2);
data.button1 = digitalRead(b1);
data.button2 = digitalRead(b2);
data.button3 = digitalRead(b3);
data.button4 = digitalRead(b4);

radio.write()로 14개 채널 전체 데이터를 수신기로 전송합니다.

radio.write(&data, sizeof(Data_Package));

토글 스위치 1이 켜지면(LOW) 조이스틱 1 대신 IMU 데이터로 제어합니다.

if (digitalRead(t1) == 0) {
  read_IMU(); // IMU로 좌우, 전후 제어
}

IMU에서 얻은 -90°~+90° 각도 값을 조이스틱처럼 0~255 바이트 값으로 매핑합니다.

data.j1PotX = map(angleX, -90, +90, 255, 0);
data.j1PotY = map(angleY, -90, +90, 0, 255);

완전한 송신기 코드

/* DIY Arduino based RC Transmitter by Dejan Nedelkovski, www.HowToMechatronics.com
   Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>

#define jB1 1
#define jB2 0
#define t1 7
#define t2 4
#define b1 8
#define b2 9
#define b3 2
#define b4 3

const int MPU = 0x68;
float AccX, AccY, AccZ;
float GyroX, GyroY, GyroZ;
float accAngleX, accAngleY, gyroAngleX, gyroAngleY;
float angleX, angleY;
float AccErrorX, AccErrorY, GyroErrorX, GyroErrorY;
float elapsedTime, currentTime, previousTime;
int c = 0;

RF24 radio(5, 6);
const byte address[6] = "00001";

struct Data_Package {
  byte j1PotX; byte j1PotY; byte j1Button;
  byte j2PotX; byte j2PotY; byte j2Button;
  byte pot1; byte pot2;
  byte tSwitch1; byte tSwitch2;
  byte button1; byte button2; byte button3; byte button4;
};
Data_Package data;

void setup() {
  Serial.begin(9600);
  initialize_MPU6050();
  // calculate_IMU_error(); // 필요시 주석 해제

  radio.begin();
  radio.openWritingPipe(address);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_LOW);

  pinMode(jB1, INPUT_PULLUP);
  pinMode(jB2, INPUT_PULLUP);
  pinMode(t1, INPUT_PULLUP);
  pinMode(t2, INPUT_PULLUP);
  pinMode(b1, INPUT_PULLUP);
  pinMode(b2, INPUT_PULLUP);
  pinMode(b3, INPUT_PULLUP);
  pinMode(b4, INPUT_PULLUP);

  data.j1PotX = 127; data.j1PotY = 127;
  data.j2PotX = 127; data.j2PotY = 127;
  data.j1Button = 1; data.j2Button = 1;
  data.pot1 = 1; data.pot2 = 1;
  data.tSwitch1 = 1; data.tSwitch2 = 1;
  data.button1 = 1; data.button2 = 1;
  data.button3 = 1; data.button4 = 1;
}

void loop() {
  data.j1PotX = map(analogRead(A1), 0, 1023, 0, 255);
  data.j1PotY = map(analogRead(A0), 0, 1023, 0, 255);
  data.j2PotX = map(analogRead(A2), 0, 1023, 0, 255);
  data.j2PotY = map(analogRead(A3), 0, 1023, 0, 255);
  data.pot1 = map(analogRead(A7), 0, 1023, 0, 255);
  data.pot2 = map(analogRead(A6), 0, 1023, 0, 255);

  data.j1Button = digitalRead(jB1);
  data.j2Button = digitalRead(jB2);
  data.tSwitch2 = digitalRead(t2);
  data.button1 = digitalRead(b1);
  data.button2 = digitalRead(b2);
  data.button3 = digitalRead(b3);
  data.button4 = digitalRead(b4);

  if (digitalRead(t1) == 0) {
    read_IMU();
  }

  radio.write(&data, sizeof(Data_Package));
}

void initialize_MPU6050() {
  Wire.begin();
  Wire.beginTransmission(MPU);
  Wire.write(0x6B); Wire.write(0x00); Wire.endTransmission(true);
  Wire.beginTransmission(MPU);
  Wire.write(0x1C); Wire.write(0x10); Wire.endTransmission(true);
  Wire.beginTransmission(MPU);
  Wire.write(0x1B); Wire.write(0x10); Wire.endTransmission(true);
}

void calculate_IMU_error() {
  while (c < 200) {
    Wire.beginTransmission(MPU); Wire.write(0x3B); Wire.endTransmission(false);
    Wire.requestFrom(MPU, 6, true);
    AccX = (Wire.read() << 8 | Wire.read()) / 4096.0;
    AccY = (Wire.read() << 8 | Wire.read()) / 4096.0;
    AccZ = (Wire.read() << 8 | Wire.read()) / 4096.0;
    AccErrorX += atan(AccY / sqrt(pow(AccX, 2) + pow(AccZ, 2))) * 180 / PI;
    AccErrorY += atan(-1 * AccX / sqrt(pow(AccY, 2) + pow(AccZ, 2))) * 180 / PI;
    c++;
  }
  AccErrorX /= 200; AccErrorY /= 200; c = 0;
  while (c < 200) {
    Wire.beginTransmission(MPU); Wire.write(0x43); Wire.endTransmission(false);
    Wire.requestFrom(MPU, 4, true);
    GyroX = Wire.read() << 8 | Wire.read();
    GyroY = Wire.read() << 8 | Wire.read();
    GyroErrorX += GyroX / 32.8; GyroErrorY += GyroY / 32.8;
    c++;
  }
  GyroErrorX /= 200; GyroErrorY /= 200;
  Serial.print("AccErrorX: "); Serial.println(AccErrorX);
  Serial.print("AccErrorY: "); Serial.println(AccErrorY);
  Serial.print("GyroErrorX: "); Serial.println(GyroErrorX);
  Serial.print("GyroErrorY: "); Serial.println(GyroErrorY);
}

void read_IMU() {
  Wire.beginTransmission(MPU); Wire.write(0x3B); Wire.endTransmission(false);
  Wire.requestFrom(MPU, 6, true);
  AccX = (Wire.read() << 8 | Wire.read()) / 4096.0;
  AccY = (Wire.read() << 8 | Wire.read()) / 4096.0;
  AccZ = (Wire.read() << 8 | Wire.read()) / 4096.0;
  accAngleX = atan(AccY / sqrt(pow(AccX, 2) + pow(AccZ, 2))) * 180 / PI + 1.15;
  accAngleY = atan(-1 * AccX / sqrt(pow(AccY, 2) + pow(AccZ, 2))) * 180 / PI - 0.52;

  previousTime = currentTime;
  currentTime = millis();
  elapsedTime = (currentTime - previousTime) / 1000.0;

  Wire.beginTransmission(MPU); Wire.write(0x43); Wire.endTransmission(false);
  Wire.requestFrom(MPU, 4, true);
  GyroX = (Wire.read() << 8 | Wire.read()) / 32.8;
  GyroY = (Wire.read() << 8 | Wire.read()) / 32.8;
  GyroX += 1.85; GyroY -= 0.15;

  gyroAngleX = GyroX * elapsedTime;
  gyroAngleY = GyroY * elapsedTime;

  angleX = 0.98 * (angleX + gyroAngleX) + 0.02 * accAngleX;
  angleY = 0.98 * (angleY + gyroAngleY) + 0.02 * accAngleY;

  data.j1PotX = map(angleX, -90, +90, 255, 0);
  data.j1PotY = map(angleY, -90, +90, 0, 255);
}

수신기 코드

데이터 수신 방법을 알아봅니다. 간단한 아두이노와 NRF24L01 수신기 회로도입니다. 다른 아두이노 보드도 사용 가능합니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

송신기와 같은 라이브러리, 객체, 구조체를 사용하고, 설정에서 동일한 라디오 설정으로 모듈을 수신 모드(radio.startListening())로 설정합니다.

/* DIY Arduino based RC Transmitter Project == Receiver Code ==
   by Dejan Nedelkovski, www.HowToMechatronics.com
   Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(10, 9); // nRF24L01 (CE, CSN)
const byte address[6] = "00001";
unsigned long lastReceiveTime = 0;
unsigned long currentTime = 0;

struct Data_Package {
  byte j1PotX; byte j1PotY; byte j1Button;
  byte j2PotX; byte j2PotY; byte j2Button;
  byte pot1; byte pot2;
  byte tSwitch1; byte tSwitch2;
  byte button1; byte button2; byte button3; byte button4;
};
Data_Package data;

void setup() {
  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_LOW);
  radio.startListening(); // 수신 모드
  resetData();
}

void loop() {
  if (radio.available()) {
    radio.read(&data, sizeof(Data_Package));
    lastReceiveTime = millis();
  }

  currentTime = millis();
  if (currentTime - lastReceiveTime > 1000) {
    resetData(); // 연결 끊김 시 기본값으로 리셋
  }

  Serial.print("j1PotX: "); Serial.print(data.j1PotX);
  Serial.print("; j1PotY: "); Serial.print(data.j1PotY);
  Serial.print("; button1: "); Serial.print(data.button1);
  Serial.print("; j2PotX: "); Serial.println(data.j2PotX);
}

void resetData() {
  data.j1PotX = 127; data.j1PotY = 127;
  data.j2PotX = 127; data.j2PotY = 127;
  data.j1Button = 1; data.j2Button = 1;
  data.pot1 = 1; data.pot2 = 1;
  data.tSwitch1 = 1; data.tSwitch2 = 1;
  data.button1 = 1; data.button2 = 1;
  data.button3 = 1; data.button4 = 1;
}

loop에서 available()로 수신 데이터 확인 후 read()로 구조체에 저장합니다. millis()로 1초 이상 데이터 수신 없으면 연결 끊김으로 판단해 변수를 기본값으로 리셋합니다(드론 스로틀 업 상태에서 연결 끊김 시 폭주 방지).

이 수신 방식을 어떤 아두이노 프로젝트에도 적용할 수 있습니다. 예로 이전 영상의 아두이노 로봇 자동차 제어 코드가 있습니다.

업데이트: 전용 아두이노 기반 RC 수신기도 만들었습니다. 아두이노 프로 미니 기반에 서보와 ESC 연결 단자가 바로 있는 컴팩트한 PCB입니다.

아두이노 로봇 자동차 무선 제어

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

라이브러리, 구조체, 무선 통신 설정 후 메인 루프에서 수신 데이터를 읽고 조이스틱 1 값으로 자동차를 제어합니다.

아두이노 개미 로봇/헥사포드 제어

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

이전 영상의 아두이노 개미 로봇도 이 송신기로 무선 제어 가능합니다. 데이터를 읽고 전진, 좌우, 물기, 공격 등 적절한 기능을 실행하면 됩니다.

ESC와 서보 제어

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드

상용 RC 기기 제어를 위해 서보나 브러시리스 모터를 제어합니다. 수신 데이터로 서보 라이브러리(0~180°)로 서보 제어, ESC 제어엔 50Hz PWM 신호(1000~2000µs 듀티 사이클)로 모터 RPM 제어. ESC를 이용한 브러시리스 모터 제어는 다음 튜토리얼에서 자세히 다룹니다.

주의: 표준 RC 수신 시스템과 이 NRF24L01 2.4GHz 시스템은 바인딩할 수 없습니다. 대신 아두이노와 NRF24L01 모듈로 자체 수신기를 만들어 PWM/PPM 신호를 생성해 RC 기기를 제어해야 합니다.

아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드
/* DIY Arduino based RC Transmitter Project == Receiver Code - ESC and Servo Control ==
   by Dejan Nedelkovski, www.HowToMechatronics.com
   Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>

RF24 radio(10, 9);
const byte address[6] = "00001";
unsigned long lastReceiveTime = 0;
unsigned long currentTime = 0;

Servo esc; Servo servo1; Servo servo2;
int escValue, servo1Value, servo2Value;

struct Data_Package {
  byte j1PotX; byte j1PotY; byte j1Button;
  byte j2PotX; byte j2PotY; byte j2Button;
  byte pot1; byte pot2;
  byte tSwitch1; byte tSwitch2;
  byte button1; byte button2; byte button3; byte button4;
};
Data_Package data;

void setup() {
  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_LOW);
  radio.startListening();
  resetData();
  esc.attach(9); servo1.attach(3); servo2.attach(4);
}

void loop() {
  currentTime = millis();
  if (currentTime - lastReceiveTime > 1000) { resetData(); }

  if (radio.available()) {
    radio.read(&data, sizeof(Data_Package));
    lastReceiveTime = millis();
  }

  servo1Value = map(data.j2PotX, 0, 255, 0, 180);
  servo2Value = map(data.j2PotY, 0, 255, 0, 180);
  servo1.write(servo1Value);
  servo2.write(servo2Value);

  escValue = map(data.pot1, 0, 255, 1000, 2000);
  esc.writeMicroseconds(escValue);
}

void resetData() {
  data.j1PotX = 127; data.j1PotY = 127;
  data.j2PotX = 127; data.j2PotY = 127;
  data.j1Button = 1; data.j2Button = 1;
  data.pot1 = 1; data.pot2 = 1;
  data.tSwitch1 = 1; data.tSwitch2 = 1;
  data.button1 = 1; data.button2 = 1;
  data.button3 = 1; data.button4 = 1;
}

이상으로 튜토리얼을 마칩니다. 즐겁게 보셨고 새로운 걸 배우셨길 바랍니다. 질문은 댓글로 남겨주시고, 제 아두이노 프로젝트 모음도 확인해보세요.

제조공정

  1. 카주: 고대 밀리톤에서 현대 플라스틱 장난감까지 – 역사, 제조, 미래
  2. 나만의 LUMAZOID 만들기: DIY 아두이노 음악 비주얼라이저
  3. 집에서 직접 만드는 DIY 숙제 작성 머신 (펜 플로터)
  4. 아두이노와 스마트폰으로 만드는 스마트 전압계 – 초간단 DIY 프로젝트
  5. 컴팩트 기상 관측소: 아두이노 나노 & ESP8266, BMP280 기압계 & OLED/노키아 LCD
  6. 아두이노, 구글 카드보드, 블루투스로 DIY VR 스케이트보드 만들기
  7. DIY 아두이노 라디오닉스 EMR 치료 기기 제작
  8. 아두이노 나노와 SG90 서보로 만드는 DIY 2축 모션 레이싱 시뮬레이터
  9. 아두이노로 만드는 휴대용 RFID 도어락 - 단계별 완벽 가이드
  10. DIY 3축 CNC VMC: 정밀 밀링 머신 직접 제작하기