Hackster is hosting Hackster Holidays, Ep. 4: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Wednesday!Stream Hackster Holidays, Ep. 4 on Wednesday!
Published © MIT

Making Arduino-Based RC Transmitter of USB Flight Simulator

My variant of converting a four-channel USB flight simulator into an RC transmitter using Arduino.

IntermediateShowcase (no instructions)11,611
Making Arduino-Based RC Transmitter of USB Flight Simulator

Things used in this project

Hardware components

USB flight simulator
×1
Arduino Pro Mini 328 - 5V/16MHz
SparkFun Arduino Pro Mini 328 - 5V/16MHz
×1
USB-TTL adapter
×1
Breadboard 47х35mm (170 holes)
×1
Li-Ion battery 18650
×1
LI CHARGER microUSB TP4056-module
×1
Battery holder 18650 x1
×1
DC-DC StepUp 3v-to-5v 1A module
×1
Radio module NRF24L01-SMA
×1
Resistor 10k ohm
Resistor 10k ohm
×1
Adapter 5V-3,3V for NRF24L01
×1
Piezobuzzer LD-BZPN-2203
×1
Rotary encoder EC11 20mm
×1
Aluminum cap for encoder
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Hot glue gun (generic)
Hot glue gun (generic)
Soldering iron (generic)
Soldering iron (generic)
Multitool, Screwdriver
Multitool, Screwdriver

Story

Read more

Schematics

Circuit of the transmitter

Code

Main program

C/C++
Main ARDUINO code from TR.ino
// Arduino code for RC transmitter
// Tested: Arduino Pro Mini (ATMega328P, 5V)
// Copyright: Anton Tsaritsynskyy, January 2020
// E-mail: tsaritsynskyy.a.a@gmail.com
//
// This software is provided "as is" without any warranties.
// Author is not responsible for any undesired effects caused by using this software.
// Commercial distribution of this software is not permitted.
// Third-party libraries and components are properties of their respective developers.

#include <RF24.h>
#include <EEPROM.h>
#include "PIEZO.h"
#include "VMETER.h"
#include "ENC.h"

RF24 radio(8, 9);
VMETER battery(A5);
PIEZO buzz(2);
ENC enc(4, 5, 3);

const float K_MIN = -1.0;
const float K_MAX = 1.0;
const float K_STEP = 0.1;

const byte EEPROM_KEY = 54;
const int EEPROM_KEY_ADDR = 0;

const byte BATTERY_CRITICAL = 20;
const unsigned long CHECK_INTERVAL = 4000;

const int CH_COUNT = 4;
const byte pin1 = A0;
const byte pin2 = A1;
const byte pin3 = A2;
const byte pin4 = A3;

typedef struct {
  unsigned int Min;
  unsigned int Max;
  float k;
} ch;

ch chs[CH_COUNT] = {
  {700, 2100, K_MAX},
  {700, 2100, K_MAX},
  {700, 2100, K_MAX},
  {700, 2100, K_MAX}
};

typedef struct {
  unsigned int val1 = 0;
  unsigned int val2 = 0;
  unsigned int val3 = 0;
  unsigned int val4 = 0;
} package;

package data;

unsigned long prevTime;
boolean isCritical = false;

int trMode = CH_COUNT;
encState eState;

unsigned int convert(unsigned int v, int c) {
  float val = chs[c].Min+v*float(chs[c].Max-chs[c].Min)/1023.0;
  val = val*chs[c].k+0.5*float(chs[c].Min+chs[c].Max)*(1.0-chs[c].k);
  return round(val);
}

void writeSettings(int c) {
  EEPROM.put(EEPROM_KEY_ADDR+c*sizeof(float)+1, chs[c].k);
}

void readSettings(void) {
  if (EEPROM.read(EEPROM_KEY_ADDR) != EEPROM_KEY) {
    EEPROM.write(EEPROM_KEY_ADDR, EEPROM_KEY);
    for (int i=0; i<CH_COUNT; i++) {
      writeSettings(i);
    }
  }
  else {
    for (int i=0; i<CH_COUNT; i++) {
      EEPROM.get(EEPROM_KEY_ADDR+i*sizeof(float)+1, chs[i].k);
    }
  }  
}

void setup() {
  battery.init();
  
  radio.begin();       
  radio.setChannel(100); 
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_MAX);
  radio.openWritingPipe(0xF0F1F2F3F4LL);
  radio.powerUp();      
  radio.stopListening();

  readSettings();
  
  prevTime = millis();

  buzz.enable(true);
  buzz.longBeep();
}

void loop() {
  eState = enc.getState();
  
  if (eState != esNone) {
    if (eState == esPressed) {
      if (trMode < CH_COUNT) {
        writeSettings(trMode);
      }
      
      trMode = (++trMode) % (CH_COUNT+1);
      if (trMode < CH_COUNT) {
        buzz.shortBeeps(trMode+1);
      }
      else {
        buzz.longBeep();
      }
    }
    else {
      if (trMode < CH_COUNT) {
        if (enc.getPos() < 0) {
          if (chs[trMode].k > K_MIN) chs[trMode].k -= K_STEP;
        }
        else {
          if (chs[trMode].k < K_MAX) chs[trMode].k += K_STEP;
        }
      } 
    }
  }
  
  data.val1 = convert(analogRead(pin1), 0);
  data.val2 = convert(analogRead(pin2), 1);
  data.val3 = convert(analogRead(pin3), 2);
  data.val4 = convert(analogRead(pin4), 3);
  
  radio.write(&data, sizeof(data));

  if (millis()-prevTime > CHECK_INTERVAL) { 
    if (isCritical) {
      buzz.longBeep();
    }
    else {
      if ( battery.check() ) { 
        isCritical = (battery.getPercent() < BATTERY_CRITICAL);
      }  
    }

    prevTime = millis();  
  }
}

Code for VMETER class

C/C++
Header file
// Arduino code for battery voltage meter
// Part of Arduino code for RC transmitter
// Tested: Arduino Pro Mini (ATMega328P, 5V)
// Copyright: Anton Tsaritsynskyy, January 2020
// E-mail: tsaritsynskyy.a.a@gmail.com
//
// This software is provided "as is" without any warranties.
// Author is not responsible for any undesired effects caused by using this software.
// Commercial distribution of this software is not permitted.
// Third-party libraries and components are properties of their respective developers.

#ifndef VMETER_H
#define VMETER_H

#include <Arduino.h>

const float SM_FACTOR = 0.1;
const float V_RF = 5.1;
const float V_MIN = 3.0;
const float V_MAX = 4.2;
const float V_DELTA = V_MAX-V_MIN;
const byte P_MIN = 0;
const byte P_MAX = 100;

class VMETER {
  private:
    byte _pin;
    unsigned int _v;
    unsigned int _vMin;
  public:
    VMETER(byte pin);
    void init(void);
    boolean check(void);
    byte getPercent(void);
};

#endif

Code for VMETER class

C/C++
CPP-file
// Arduino code for battery voltage meter
// Part of Arduino code for RC transmitter
// Tested: Arduino Pro Mini (ATMega328P, 5V)
// Copyright: Anton Tsaritsynskyy, January 2020
// E-mail: tsaritsynskyy.a.a@gmail.com
//
// This software is provided "as is" without any warranties.
// Author is not responsible for any undesired effects caused by using this software.
// Commercial distribution of this software is not permitted.
// Third-party libraries and components are properties of their respective developers.

#include "VMETER.h"
#include <Arduino.h>

VMETER::VMETER(byte pin) {
  _pin = pin; 
}

boolean VMETER::check(void) {
  boolean res = false;
  
  _v = SM_FACTOR*analogRead(_pin)+(1-SM_FACTOR)*_v;

  if (_v < _vMin) {
    _vMin = _v;
    res = true;
  }

  return res;
}

void VMETER::init(void) {
  _v = analogRead(_pin);
  _vMin = _v;
}

byte VMETER::getPercent(void) {
  float res = 100*(float(_vMin)/1024*V_RF-V_MIN)/V_DELTA;
  if (res < P_MIN) res = P_MIN;
  if (res > P_MAX) res = P_MAX;

  return byte(res);
}

Code for receiver

C/C++
ARDUINO code for receiver
// Arduino code to test RC transmitter via Serial port
// Tested: Arduino Nano (ATMega328P)
// Copyright: Anton Tsaritsynskyy, January 2020
// E-mail: tsaritsynskyy.a.a@gmail.com
//
// This software is provided "as is" without any warranties.
// Author is not responsible for any undesired effects caused by using this software.
// Commercial distribution of this software is not permitted.
// Third-party libraries and components are properties of their respective developers.


#include <RF24.h>

RF24 radio(8, 9);

//Data structure
typedef struct {
  unsigned int val1 = 0;
  unsigned int val2 = 0;
  unsigned int val3 = 0;
  unsigned int val4 = 0;
} package;

package data;

unsigned long prevTime, currTime;

void setup() {
  Serial.begin(9600);
  
  radio.begin();       
  radio.setChannel(100); 
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_MAX);
  radio.openReadingPipe(1, 0xF0F1F2F3F4LL);
  radio.powerUp();
  radio.startListening();

  prevTime = millis();
}

void loop() {
  if (radio.available()) {
    radio.read(&data, sizeof(data));

    currTime = millis();
    if (currTime-prevTime >= 500) {
      Serial.print(data.val1);
      Serial.print('\t');
      Serial.print(data.val2);
      Serial.print('\t');
      Serial.print(data.val3);
      Serial.print('\t');
      Serial.println(data.val4);

      prevTime = currTime;
    }
  } 
}

Code for PIEZO class

C/C++
Header file
// Arduino code for piezo buzzer
// Part of Arduino code for RC transmitter
// Tested: Arduino Pro Mini (ATMega328P, 5V)
// Copyright: Anton Tsaritsynskyy, January 2020
// E-mail: tsaritsynskyy.a.a@gmail.com
//
// This software is provided "as is" without any warranties.
// Author is not responsible for any undesired effects caused by using this software.
// Commercial distribution of this software is not permitted.
// Third-party libraries and components are properties of their respective developers.

#ifndef PIEZO_H
#define PIEZO_H

#include <Arduino.h>

const unsigned int PIEZO_P2 = 167;
const unsigned int PIEZO_N = 300;
const unsigned int PIEZO_DELAY = 20;

class PIEZO {
  private:
    byte _pin;
    boolean _enabled;

    void writePin(boolean isHigh);
    void beep(unsigned int n);
  public:
    PIEZO(byte pin);
    void enable(boolean enabled);
    boolean isEnabled(void);
    void shortBeeps(byte n);
    void longBeep(void);
};

#endif

Code for PIEZO class

C/C++
CPP-file
// Arduino code for piezo buzzer
// Part of Arduino code for RC transmitter
// Tested: Arduino Pro Mini (ATMega328P, 5V)
// Copyright: Anton Tsaritsynskyy, January 2020
// E-mail: tsaritsynskyy.a.a@gmail.com
//
// This software is provided "as is" without any warranties.
// Author is not responsible for any undesired effects caused by using this software.
// Commercial distribution of this software is not permitted.
// Third-party libraries and components are properties of their respective developers.

#include "PIEZO.h"

PIEZO::PIEZO(byte pin) {
  _enabled = false;
  
  _pin = (1 << pin);
  DDRD |= _pin;
}

void PIEZO::writePin(boolean isHigh) {
  PORTD = (isHigh ? PORTD | _pin : PORTD & ~_pin);
}

void PIEZO::enable(boolean enabled) {
  _enabled = enabled;
}

boolean PIEZO::isEnabled(void) {
  return _enabled;
}

void PIEZO::beep(unsigned int n) {
  for (unsigned int j=0; j<n; j++) {
    writePin(HIGH);
    delayMicroseconds(PIEZO_P2);
    writePin(LOW);
    delayMicroseconds(PIEZO_P2);
  }
}

void PIEZO::shortBeeps(byte n) {
  if (_enabled) {
    for (byte i=1; i<=n; i++) {
      beep(PIEZO_N);

      if (i<n) delay(PIEZO_DELAY);
    }
  }
}

void PIEZO::longBeep(void) {
  if (_enabled) {
      beep(PIEZO_N << 1);
  }
}

Code for ENC class

C/C++
Header file
// Arduino code for encoder
// Part of Arduino code for RC transmitter
// Tested: Arduino Pro Mini (ATMega328P, 5V)
// Copyright: Anton Tsaritsynskyy, January 2020
// E-mail: tsaritsynskyy.a.a@gmail.com
//
// This software is provided "as is" without any warranties.
// Author is not responsible for any undesired effects caused by using this software.
// Commercial distribution of this software is not permitted.
// Third-party libraries and components are properties of their respective developers.

#ifndef ENC_H
#define ENC_H

#include <Arduino.h>

const int DEBOUNCE_DELAY = 50;
const int LONG_PRESS = 500;

enum encState {esNone, esRotated, esPressed, esLongPressed};

class ENC {
  private:
    byte _pinA, _pinB, _pinS; 
    
    unsigned long _prevTime;
    boolean _prevA, _prevS, _prevSL;
    int _pos;
  public:
    ENC(byte pinA, byte pinB, byte pinS);
    
    encState getState(void);
    int getPos(void);
};

#endif

Code for ENC class

C/C++
CPP-file
// Arduino code for encoder
// Part of Arduino code for RC transmitter
// Tested: Arduino Pro Mini (ATMega328P, 5V)
// Copyright: Anton Tsaritsynskyy, January 2020
// E-mail: tsaritsynskyy.a.a@gmail.com
//
// This software is provided "as is" without any warranties.
// Author is not responsible for any undesired effects caused by using this software.
// Commercial distribution of this software is not permitted.
// Third-party libraries and components are properties of their respective developers.

#include "ENC.h"

ENC::ENC(byte pinA, byte pinB, byte pinS) {
  _pinA = pinA;
  _pinB = pinB;
  _pinS = pinS;

  pinMode(_pinA, INPUT_PULLUP);
  pinMode(_pinB, INPUT_PULLUP);
  pinMode(_pinS, INPUT_PULLUP);

  _prevA = LOW;
  _prevS = HIGH;
  _prevSL = HIGH;
  _pos = 0;
  _prevTime = millis();
}

encState ENC::getState(void) {
  encState res = esNone;

  unsigned long currTime = millis();
    
  if (!digitalRead(_pinS)) {
    if (_prevS && ((currTime - _prevTime) > DEBOUNCE_DELAY)) {
       _prevTime = currTime;
       _prevS = LOW;
       _prevSL = HIGH;
    }

    if (_prevSL && ((currTime - _prevTime) > LONG_PRESS)) {
      _prevSL = LOW;
      _pos = 0;
      res = esLongPressed;
    }
  }
  else {
    if (!_prevS) {
      if ((currTime - _prevTime) > DEBOUNCE_DELAY) {
        _prevTime = currTime;
        _prevS = HIGH;
        if (_prevSL) res = esPressed; 
      }
    }
    else {
      byte A = digitalRead(_pinA);
      byte B = digitalRead(_pinB);
    
      if (!A && _prevA) { 
        if (B) _pos = 1;
        else _pos = -1;

        res = esRotated;
      }

      _prevA = A;
    } 
  }
  
  return res;
}

int ENC::getPos(void) {
  return _pos;
}

Credits

Comments