/*-----------------------------------------------------------------------------------------
* This software is in the public domain, furnished "as is", without technical
* support, and with no warranty, express or implied, as to its usefulness for
* any purpose.
*
* SwitchProp.ino
*
* Dualswitch decoder and prportional part combined in one RC Channel.
*
* The encoder should have 2 switches with different resistors and a trimmer with paralel resitor to compensate for
* the switch resistors to get the needed channel resistor bandwithe (normaly 0 to 5Kohms or 0 to 10 KOhms)
* This implementation on transmitter side is: 470Ohms (paralell to switch 1), 1KOhms (paralell to switch 2),
* 5K trimmer with paralell resistor of 12KOhms.
* Therefor the calibration steps of the RC input must be done as described in pwmread_rcfailsafe.ino as well.
*
* Author: Volker Frauenstein
* Date: 20/01/2021 Version: 1.0 implementation of switch-proportional decoder for Arduino Nano
*/
// pinout definitions for the switchfunctions
//const int spPin_a = 7; // uncomment because function in SW
//const int spPin_b = 8; // uncomment because function in SW
// Signal delta to separate switch states from proportional part
// must be adapted to real readings
const float S_delta[2] = {0.22 , 0.44}; // signal delta if switches are triggered
const float C_point = -0.44; // start position of encoder (s1 off, S2, off, Prop middle)
const float S_trigger = 0.09; // bandwithe for first recogition of trigger
const float S_range = 0.05; // bandwithe for save recogition of trigger
// internal pre definitions do not change
const float expand = 1/((1 - S_delta[0] - S_delta[1])*1.8); // multiplier to expand prop to full range
const float S_max[2] = {(S_delta[0] + S_trigger), (S_delta[1] + S_trigger)};
const float S_min[2] = {(S_delta[0] - S_trigger), (S_delta[1] - S_trigger)};
const float H_max[2] = {(S_delta[0] + S_range), (S_delta[1] + S_range)};
const float H_min[2] = {(S_delta[0] - S_range), (S_delta[1] - S_range)};
// generig definitions
int SP_S_trigger = 0; // semaphore on switch trigger state
int SP_S_threshold = 5; // hysterese on switch trigger semaphore
int SP_P_trigger = 0; // semaphore on proportional trigger state
int SP_P_threshold = 15; // hysterese on proportional trigger semaphore
int hysteresis = 0; // counter for hysteresis
float adelta; // absolut of delta
bool switching[2] = {false, false}; // switch states of last cycle
float SP_now = 0; // actual value of SP RC channel
float SP_last = 0; // last value of SP RC channel
float Prop_last = 0; // last calculated proportional value
const int SPthreshold = 5; // threshold for hysteresis
bool SPcalibrated = false; // flag to chek if SP encoder state is known
/* definitions for main.ino to handle switchprop state global
bool switchOn[3] = {false,false,false}; // one bit per switch plus calibration flag
float Prop_now = 0; // calculated proportional value
*/
// generig definitions for debug
//#define DEBUG_SP 1
void setup_SwitchProp() {
// pinMode(spPin_a, OUTPUT); // uncomment because function in SW
// pinMode(spPin_b, OUTPUT); // uncomment because function in SW
}
void close_SwitchProp() {
// digitalWrite(msPin_a, LOW); // close all digital pins
// digitalWrite(msPin_b, HIGH); // Switch on defined with pulldown
switchOn[0] = false; // and switch off internal function 1
switchOn[1] = false; // and switch off internal function 2
switchOn[2] = false; // and reset calibration flag
Prop_now = 0; // also the proportional part to failsafe
SPcalibrated = false; // new calibration needed after failsafe
} // function close_MultiSwitch closed
void run_SwitchProp(int ch) { // function to check states of the switches and proportional part
// and activate the pinouts if needed
float SP_now = RC_in[ch]; // value of used RC channel for switchprop
if (SPcalibrated) { // switchprop state was known, normal operation
checktrigger(SP_now); // check and set switch state and proportional part
}
else {
SP_Calibaration(SP_now); // SP not calibratet, check for next loop
}
#ifdef DEBUG_SP // show DEBUG_SP data if needed
Serial.print(" State (S: ");Serial.print(SP_S_trigger);
Serial.print(",P: ");Serial.print(SP_P_trigger);Serial.print("), ");
Serial.print(" SP_Ch: ");Serial.print(": ");
Serial.print(RC_in[ch]);Serial.print(" -> ");
for (int i = 0; i<2; i++){
Serial.print(switchOn[i]);Serial.print(",");
}
Serial.print(" -> Prop: ");Serial.print(Prop_now);
Serial.print(", SP_last: ");Serial.println(SP_last);
#endif
} // function run_SwitchProp closed
bool SP_Calibaration (float SP_in) { // check if (re)start condition is fullfillt
if ((SP_in+S_range > C_point) && (SP_in-S_range < C_point)) {
SPcalibrated = true; // encoder is in calibration range
switchOn[2] = true; // set calibration flag
SP_last = SP_in; // store this good value for next loop
Prop_last = 0; // store startpiont of proportional part
#ifdef DEBUG_SP // show DEBUG_SP data if needed
Serial.print(", SP calibrated: ");Serial.print(SPcalibrated);
#endif
return true;
}
else {
#ifdef DEBUG_SP // show DEBUG_SP data if needed
Serial.print(", SP NOT calibrated SP_in: ");Serial.print(SP_in);
#endif
return false;
}
} // function SP_Calibaration closed
void checktrigger (float SP_value) { // function to check if switches have been triggerd
SP_now = SP_value;
int SP_state;
float delta = SP_value - SP_last;
adelta = abs(delta);
if (adelta < S_range) { // no new input value
SP_state = 0; // state case 0, do nothing
if (SP_S_trigger > 0) SP_S_trigger--; // degrade last switch activ trigger
if (SP_P_trigger > 0) SP_P_trigger--; // degrade last proportional activ trigger
}
// check if delta is in switch trigger area and set state
else if ((adelta > S_min[0]) && (adelta < S_max[0])) {
if (SP_P_trigger > 0) { // proportional change was ongoing
SP_state = 3; // keep it running
}
else SP_state = 1; // check switch trigger for S1
}
else if ((adelta > S_min[1]) && (adelta < S_max[1])) {
if (SP_P_trigger > 0) { // proportional change was ongoing
SP_state = 3; // keep it running
}
else SP_state = 2; // check switch trigger for S2
}
else SP_state = 3; // no switch trigger conditions found
switch (SP_state) {
case 0: // no further action needed
Prop_now = AjustProp(); // but recalibrate proportional value
#ifdef DEBUG_SP
Serial.print(" idle,");
#endif
break;
case 1: // check if switch 1 has been altered
#ifdef DEBUG_SP
Serial.print(" S1 trigger,");
Serial.print("delta: ");Serial.print(delta);
#endif
SP_switch(delta, 0);
SP_S_trigger = SP_S_threshold; // and set switch trigger blocker
break;
case 2: // check if switch 2 has been altered
#ifdef DEBUG_SP
Serial.print(" S2 trigger,");
Serial.print("delta: ");Serial.print(delta);
#endif
SP_switch(delta, 1);
SP_S_trigger = SP_S_threshold; // and set switch trigger blocker
break;
case 3: // value has been changed outside of switch deltas
#ifdef DEBUG_SP // or propotional change was ongoing in the last loops
Serial.print(" prop trigger, : ");
#endif
Prop_now = CalcProp (delta); // now calculate proportional value
SP_last = SP_value; // store actual SP_value for next loop
Prop_last = Prop_now; // and prepare for next cycle
SP_P_trigger = SP_P_threshold; // set proportional trigger blocker
SP_S_trigger = SP_P_threshold; // and reset switch trigger blocker
break;
} // switch case closed
} // function checktrigger closed
void SP_switch (float delta, int i) { // function to trigger Switch change
if ((!switchOn[i]) && (delta > 0)) { // Switch i was off, now it looks like it is on
if (SP_run_hyst(i)) { // hyseresis done, change switch state
switchOn[i] = true; // from OFF to ON
SP_last = SP_now; // store actual SP_value for next loop
Prop_now = AjustProp(); // and set proportional value
#ifdef DEBUG_SP
Serial.print(" S");Serial.print(i+1);Serial.print(" ON,");
#endif
}
else { // hyseresis running, change switch state later
Prop_now = Prop_last; // and freeze proportional value
#ifdef DEBUG_SP
Serial.print(" S");Serial.print(i+1);Serial.print(" waitingON,");
#endif
}
}
else if (switchOn[i] && (delta < 0)) { // Switch was on, now we have to switch off
if (SP_run_hyst(i)) { // hyseresis done, change switch state
switchOn[i] = false; // from ON to OFF
SP_last = SP_now; // store actual SP_value for next loop
Prop_now = AjustProp(); // and set proportional value
#ifdef DEBUG_SP
Serial.print(" S");Serial.print(i+1);Serial.print(" OFF,");
#endif
}
else { // hyseresis running, change switch state later
Prop_now = Prop_last; // and frezze proportional value
#ifdef DEBUG_SP
Serial.print(" S");Serial.print(i+1);Serial.print(" waitingOFF,");
#endif
}
}
else { // Switch was on (off) and we should switch on (off)
Prop_now = CalcProp(delta); // so it must be a (fast) proportional change
float Prop_temp = AjustProp();
if (!((Prop_now + S_range) > Prop_temp) && ((Prop_now - S_range) < Prop_temp)) {
#ifdef DEBUG_SP
Serial.print(" Prop out of sync: ");Serial.print(Prop_now);
Serial.print(" != ");Serial.print(Prop_temp);
#endif // and set proportional value
}
SP_last = SP_now; // store actual SP_value for next loop
#ifdef DEBUG_SP
Serial.print(" just prop, ");
#endif
} // within range of Switch delta
} // function SP_switch closed
float CalcProp (float pdelta) { // function to strech proportional part to full
float delta = pdelta * expand; // strech delta to fit full way
float prop = Prop_last + delta;
#ifdef DEBUG_SP // show DEBUG_SP data if needed
// Serial.print(", Prop_last: ");Serial.print(Prop_last);
// Serial.print(", pdelta: ");Serial.print(delta);
// Serial.print(", calcprop: ");Serial.print(prop);
#endif
prop = constrain(prop, -1, 1); // and trim proportional value
return prop;
} // function CalcProp closed
float AjustProp () { // recalibration function of proportional part
float midpoint = C_point; // find midposition of proportional part
float aprop;
for (int i = 0; i < 2; i++) { // add switch parts to midpoint if needed
if (switchOn[i]) midpoint = midpoint + S_delta[i];
}
aprop = expand * (SP_now - midpoint); // calculate streched value
aprop = constrain(aprop, -1, 1); // and trim proportional value
#ifdef DEBUG_SP
// Serial.print("aprop: ");Serial.print(aprop);
#endif
return aprop;
} // function AjustProp closed
bool SP_run_hyst (int s) {
if (hysteresis > SPthreshold) { // yes we change
hysteresis = 0; // reset hysteresis
#ifdef DEBUG_SP
Serial.print(" Change NOW ");
#endif
return true; // for next trigger
}
else { // hysteresis is running
if (hysteresis > SPthreshold - (SPthreshold/2)) {
if ((adelta > H_min[s]) && (adelta < H_max[s])) {
hysteresis++; // we change later
#ifdef DEBUG_SP
Serial.print(" changecount_H: ");Serial.print(hysteresis);
#endif
}
else { // bandwithe reduction after debounce
hysteresis = 0; // not seen therefore try againg
#ifdef DEBUG_SP
Serial.print(" changecount_R: ");Serial.print(hysteresis);
#endif
return false;
}
}
else { // step one for debounce
hysteresis++; // we change later
#ifdef DEBUG_SP
Serial.print(" changecount_S: ");Serial.print(hysteresis);
#endif
}
return false;
}
} // function SP_run_hyst closed