/* 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