/* RC engine sound & LED controller for Arduino ESP32. Written by TheDIYGuy999
    Based on the code for ATmega 328: https://github.com/TheDIYGuy999/Rc_Engine_Sound

 *  ***** ESP32 CPU frequency must be set to 240MHz! *****

   Parts of automatic transmision code from Wombii's fork: https://github.com/Wombii/Rc_Engine_Sound_ESP32
*/

// Added Author: Volker Frauenstein
// Date: 24/02/2021 Version: 1.1 added functions x_RCpwmRange to easy RC-signal measurement
//                               for the RC_min, RC_mid and RC_max arrays

// Date: 02/01/2021 Version: 1.0 all for RC signal reading not needed functions deleted,
//                               added functions for Channel bounds and scatering protection

// =======================================================================================================
// ! ! I M P O R T A N T ! !   SETTINGS (ADJUST THEM BEFORE CODE UPLOAD), REQUIRED ESP32 BOARD DEFINITION
// =======================================================================================================
//

// Install ESP32 board according to: https://randomnerdtutorials.com/installing-the-esp32-board-in-arduino-ide-windows-instructions/
// Adjust board settings according to: https://github.com/TheDIYGuy999/Rc_Engine_Sound_ESP32/blob/master/Board%20settings.png

// DEBUG options can slow down the playback loop! Only uncomment them for debugging, may slow down your system!
// #define CHANNEL_DEBUG 1 // uncomment for input signal debugging informations and measurement of the calibration values
// #define SIM_RC 1        // uncomment if static RC signals should be simulated for development without RC equipment

// =======================================================================================================
// LIRBARIES & HEADER FILES
// =======================================================================================================

// Header files
// Libraries (you have to install all of them in the "Arduino sketchbook"/libraries folder)
#include "driver/rmt.h" // No need to install this, comes with ESP32 board definition (used for PWM signal detection)

// =======================================================================================================
// PIN ASSIGNMENTS & GLOBAL VARIABLES (Do not play around here)
// =======================================================================================================
//
// Pin assignment and wiring instructions ****************************************************************
// -------------------------------------------------------------------------------------------------------
// For all old RC receiver with 5 Volt a Voltage divider for each GIO is needed.
// Use a 2200Ohm resitor between RC receiver output and GIO-Pin
// and a 3300Ohm resistor between GIO-Pin and GND
// -------------------------------------------------------------------------------------------------------

// Channel numbers may be different on your recveiver!
//CH1: steering in mixed mode /
//CH2:                        / left throttle in manuel mode
//CH3: throttle in mixed mode / right throttle in manuel mode
//CH4: Multischwich: motorsound on / sirene on / gunfire on / flashlight on

// definition for pinout for RC input signals *********************************************
// arrays must have same ammount of values as defined with PWM_CHANNELS_NUM (in Strawap38MVF_ESP.h)
const uint8_t PWM_CHANNELS[PWM_CHANNELS_NUM] = { 1, 2, 3, 4}; // Channel numbers
const uint8_t PWM_PINS[PWM_CHANNELS_NUM] = {34, 35, 32, 33};  // Input pin numbers

// Control input signals
#define PULSE_ARRAY_SIZE PWM_CHANNELS_NUM+1          // 4 channels (+ the unused CH0)
volatile uint16_t pulseWidthRaw[PULSE_ARRAY_SIZE];   // Current RC signal RAW pulse width [X] = channel number

// definitions for adjustable values (VF 02.02.2021) **************************************
// Robbe Terratop (yellow with Multiswitch encoder on channel 6) 8CH 40MHz with Robbe FMSS receiver
// used values -> definition Takeover from pwmread_rcfailsafe (VF 02.01.2021)
//                                 CH1     CH2     CH4     CH6
int RC_min[PULSE_ARRAY_SIZE-1] = {  697,    640,    672,    620};
int RC_mid[PULSE_ARRAY_SIZE-1] = { 1344,   1312,   1284,   1400};
int RC_max[PULSE_ARRAY_SIZE-1] = { 1942,   1933,   1956,   2024};

int RCpwmCount = 1;                                 // counter for RC channels with big signal deltas per loop
const int th_scat = 99;                             // treshold for scatering determination
const int RC_bound_min = 600;                       // boundery definitionen for RC signal (min. value depends on RC System)
const int RC_bound_max = 2040;                      // boundery definitionen for RC signal (max. value depends on RC System)

// Global variables **********************************************************************

// PWM processing variables
#define RMT_TICK_PER_US 1
// determines how many clock cycles one "tick" is
// [1..255], source is generally 80MHz APB clk
#define RMT_RX_CLK_DIV (80000000/RMT_TICK_PER_US/1000000)
// time before receiver goes idle (longer pulses will be ignored)
#define RMT_RX_MAX_US 3500
uint32_t maxPwmRpmPercentage = 400; // Limit required to prevent controller fron crashing @ high engine RPM

// definitions for the RC Signal protection functions (VF 02.01.2021)
int PWmin[PULSE_ARRAY_SIZE];                        // an array to buffer min value of pulsewidth measurements
int PWmax[PULSE_ARRAY_SIZE];                        // an array to buffer max value of pulsewidth measurements
int PWarea[PULSE_ARRAY_SIZE];                       // an array to buffer area between min/max of pulsewidth measurements

const int size_RC_min = sizeof(RC_min)/sizeof(int); // measure the size of the calibration and failsafe arrays
const int size_RC_mid = sizeof(RC_mid)/sizeof(int);
const int size_RC_max = sizeof(RC_max)/sizeof(int);
// end Takeover

// definitions for RC PWM Signal calibration function init_RCpwmRange (VF 25.02.2021)
#ifdef CHANNEL_DEBUG
 uint16_t PWminA[PULSE_ARRAY_SIZE];                  // an array to buffer min value of pulsewidth measurements
 uint16_t PWmaxA[PULSE_ARRAY_SIZE];                  // an array to buffer max value of pulsewidth measurements
#endif

// Our main tasks
TaskHandle_t Task1;

//
// =======================================================================================================
// PWM SIGNAL READ INTERRUPT
// =======================================================================================================
//

// Reference https://esp-idf.readthedocs.io/en/v1.0/api/rmt.html
static void IRAM_ATTR rmt_isr_handler(void* arg) {

  uint32_t intr_st = RMT.int_st.val;

  uint8_t i;
  for (i = 0; i < PWM_CHANNELS_NUM; i++) {
    uint8_t channel = PWM_CHANNELS[i];
    uint32_t channel_mask = BIT(channel * 3 + 1);

    if (!(intr_st & channel_mask)) continue;

    RMT.conf_ch[channel].conf1.rx_en = 0;
    RMT.conf_ch[channel].conf1.mem_owner = RMT_MEM_OWNER_TX;
    volatile rmt_item32_t* item = RMTMEM.chan[channel].data32;
    if (item) {
      pulseWidthRaw[i + 1] = item->duration0;
    }

    RMT.conf_ch[channel].conf1.mem_wr_rst = 1;
    RMT.conf_ch[channel].conf1.mem_owner = RMT_MEM_OWNER_RX;
    RMT.conf_ch[channel].conf1.rx_en = 1;

    //clear RMT interrupt status.
    RMT.int_clr.val = channel_mask;
  }
  #ifdef SIM_RC                                 // for development only
    simulateRC ();
  #endif
  pwmRCcycle++;                                 // count input interrupts for 20ms loop
}

//
// =======================================================================================================
// MAIN ARDUINO SETUP (1x during startup)
// =======================================================================================================
//

void setupRCpwm() {

  // Watchdog timers need to be disabled, if task 1 is running without delay(1)
  disableCore0WDT();
  disableCore1WDT();

  // Set pin modes
  for (uint8_t i = 0; i < PWM_CHANNELS_NUM; i++) {
    pinMode(PWM_PINS[i], INPUT_PULLDOWN);
  }

  // Communication setup --------------------------------------------
  // PWM ----

  // New: PWM read setup, using rmt. Thanks to croky-b
  uint8_t i;
  rmt_config_t rmt_channels[PWM_CHANNELS_NUM] = {};

  for (i = 0; i < PWM_CHANNELS_NUM; i++) {
    rmt_channels[i].channel = (rmt_channel_t) PWM_CHANNELS[i];
    rmt_channels[i].gpio_num = (gpio_num_t) PWM_PINS[i];
    rmt_channels[i].clk_div = RMT_RX_CLK_DIV;
    rmt_channels[i].mem_block_num = 1;
    rmt_channels[i].rmt_mode = RMT_MODE_RX;
    rmt_channels[i].rx_config.filter_en = true;
    rmt_channels[i].rx_config.filter_ticks_thresh = 100; // Pulses shorter than this will be filtered out
    rmt_channels[i].rx_config.idle_threshold = RMT_RX_MAX_US * RMT_TICK_PER_US;

    rmt_config(&rmt_channels[i]);
    rmt_set_rx_intr_en(rmt_channels[i].channel, true);
    rmt_rx_start(rmt_channels[i].channel, 1);
  }

  rmt_isr_register(rmt_isr_handler, NULL, 0, NULL); // This is our interrupt

  // Task 1 setup (running on core 0)
  TaskHandle_t Task1;
  //create a task that will be executed in the Task1code() function, with priority 1 and executed on core 0
  xTaskCreatePinnedToCore(
    Task1code,   /* Task function. */
    "Task1",     /* name of task. */
    100000,       /* Stack size of task (10000) */
    NULL,        /* parameter of the task */
    1,           /* priority of the task (1 = low, 3 = medium, 5 = highest)*/
    &Task1,      /* Task handle to keep track of created task */
    0);          /* pin task to core 0 */

   // wait for RC receiver to initialize
  while (millis() <= 1000);

  // Read RC signals for the first time (used for offset calculations)
  // measure PWM RC signals mark space ratio
  #ifdef CHANNEL_DEBUG
    Serial.println("Initializing PWM ...");
  #endif
   readPwmSignals();
  #ifdef CHANNEL_DEBUG
   init_RCpwmRange();
   Serial.println("PWM Setup done!");
  #endif
}

//
// =======================================================================================================
// MIGRATED: Volker Frauenstein 02.02.2021 main call to collect the RC PWM signals and do nomalization
// =======================================================================================================
//

void readPwmSignals() {

  // measure RC signal pulsewidth:
  // nothing is done here, the PWM signals are now read, using the
  // "static void IRAM_ATTR rmt_isr_handler(void* arg)" interrupt function

  // Normalize
  for (int i = 1; i < PULSE_ARRAY_SIZE; i++) {

    #ifdef CHANNEL_DEBUG
      print_RCpwmRange(i);
    #endif
    RC_in[i-1] = RC_decode(i);             // decode receiver channel and nomalize values
  }
}

//
// =======================================================================================================
// TAKEOVER FROM pwmread_rcfailsafe: 02.02.2021 calculation of RC signals for DualDriveMixer
// =======================================================================================================
//

float calibrate(float Rx, int Min, int Mid, int Max){
   float calibrated;
   if (Rx >= Mid)
   {
    calibrated = map(Rx, Mid, Max, 0, 1000);  // map from 0% to 100% in one direction
   }
   else if (Rx == 0)
   {
    calibrated = 0;                           // neutral
   }
   else
   {
    calibrated = map(Rx, Min, Mid, -1000, 0); // map from 0% to -100% in the other direction
   }
  return calibrated * 0.001;
}

float RC_decode(int CH){

   if(CH < 1 || CH > PWM_CHANNELS_NUM) return 0;     // if channel number is out of bounds return zero.

  int i = CH - 1;

  // determine the pulse width calibration for the RC channel. The default is 1000, 1500 and 2000us.

  int Min;
  if(CH <= size_RC_min) Min = RC_min[CH-1]; else Min = 1000;

  int Mid;
  if(CH <= size_RC_mid) Mid = RC_mid[CH-1]; else Mid = 1500;

  int Max;
  if(CH <= size_RC_max) Max = RC_max[CH-1]; else Max = 2000;

  float CH_output;
  CH_output = calibrate(pulseWidthRaw[i+1],Min,Mid,Max);       // calibrate the pulse width to the range -1 to 1.
  return CH_output;

  // The signal is mapped from a pulsewidth into the range of -1 to +1, using the user defined calibrate() function in this code.

  // 0 represents neutral or center stick on the transmitter
  // 1 is full displacement of a control input in one direction (i.e full left rudder)
  // -1 is full displacement of the control input in the other direction (i.e. full right rudder)
} // End TAKEOVER


//
// =======================================================================================================
// ADDED: Volker Frauenstein 02.02.2021 old RC systems can have scattering on pwm signal -> protection function
// =======================================================================================================
//

bool RCpwmScattering(){                            // calculate scattering of RC PWM signal

 int scater_CH = 0;                                // counter for channels with scatering

 for (int i = 1; i < PWM_CHANNELS_NUM; i++) {
  if (RCpwmBoundcheck(pulseWidthRaw[i])){
    Serial.print("Boundcheck failed  CH");Serial.print(i+1);Serial.print(" (");Serial.print(pulseWidthRaw[i]);Serial.print(")");
    return true;
  }
  if (RCpwmCount < 2) {
    PWmin[i-1] = pulseWidthRaw[i];                 // first time buffer min and max values per Channel
    PWmax[i-1] = pulseWidthRaw[i];
    PWarea[i-1] = 0;
  }
  else {
    if (PWmin[i-1] > pulseWidthRaw[i]) PWmin[i-1] = pulseWidthRaw[i];     // buffer min and max values per Channel
    if (PWmax[i-1] < pulseWidthRaw[i]) PWmax[i-1] = pulseWidthRaw[i];
    PWarea[i-1] = PWmax[-i-1] - PWmin[i-1];
    if (PWarea[i-1] > th_scat ) {                  // to much scattering -> failsafe
      Serial.print(" CH");Serial.print(i);
      Serial.print(" ( ");Serial.print(PWarea[i-1]);Serial.print(" )  ");
      scater_CH = scater_CH + 1;
    }
   }
  }
  if ( scater_CH > 1 ) {                           // more then 1 channel must have scatering
    RCpwmCount = 0;
    return true;
  }
  if (RCpwmCount > 4) {                            // prepare new hysteresis
      RCpwmCount = 0;
  }
 RCpwmCount = RCpwmCount +1;
 return false;
}                                                  // end of function

//
// =======================================================================================================
// ADDED: Volker Frauenstein 02.02.2021 out of signal range check for RC PWM -> protection function
// =======================================================================================================
//

bool RCpwmBoundcheck(uint16_t pwm_signal){         // check if RC PWM signal is in bound

 if(pwm_signal < RC_bound_min || pwm_signal > RC_bound_max) {
  return true;
 }
  else {
  return false;                                    // pwm signal good
  }
}                                                  // end of function

//
// =======================================================================================================
// ADDED: Volker Frauenstein 02.02.2021 for test and debug reasons only
// =======================================================================================================
//
#ifdef SIM_RC
void simulateRC () {

    pulseWidthRaw [0] = 0;
    pulseWidthRaw [1] = 1344;
    pulseWidthRaw [2] = 1312; // 1924;  // 1312;
    pulseWidthRaw [3] = 1284; // 1920;  // 1284;
    pulseWidthRaw [4] = 620;   // 620 / 1400 / 2024 / 1600
}
#endif
//
// =======================================================================================================
// ADDED: Volker Frauenstein 25.02.2021 calibration function to find raw RC pulse limits quicker
// if signals have lot of disorder.
// Uncomment #define CHANNEL_DEBUG and move the RC transmitter imput devices which are used slowly
// to both diretions as far as it will go. Then settel all inputs in default (middle) position.
// Now the Serial output will show the values to be used within (RC_min, RC_mid, RC_max) for each channel.
// =======================================================================================================
//
#ifdef CHANNEL_DEBUG
void init_RCpwmRange() {

   for (int i = 0; i < PULSE_ARRAY_SIZE; i++) {
     PWminA[i] = 1500;
     PWmaxA[i] = 1500;
   }
}
void print_RCpwmRange(int ch){                      // display the raw RC Channel PWM Inputs (min, actual, max)

    if (PWminA[ch] > pulseWidthRaw[ch]) {PWminA[ch] = pulseWidthRaw[ch];}     // buffer min and max values per Channel
    if (PWmaxA[ch] < pulseWidthRaw[ch]) {PWmaxA[ch] = pulseWidthRaw[ch];}

    Serial.print(" CH");Serial.print(ch);Serial.print(": ");
    Serial.print(" ( ");Serial.print(PWminA[ch]);Serial.print(", ");
    Serial.print(pulseWidthRaw[ch]);Serial.print(", ");
    Serial.print(PWmaxA[ch]);Serial.print(" ), ");
    if (ch == (PULSE_ARRAY_SIZE-1)) { Serial.println(); }
}
#endif