// 16 Kanal Schalter für Lichtsteuerung
// Fernsteuerung: Flysky i6X
// CPU: Arduino Nano
// https://forum.arduino.cc/t/neues-rc-projekt-16-kanal-schalter-fur-lichtsteuerung/1143800/23
// 2023-07-02 noiasca
// work in progress - Testfeld
enum State {IDLE, PULSE, MEMA, MEMB}; // die einzelnen Zustände der Kanäle/Pins
class Output {
protected:
const byte pin; // der GPIO
const byte mem; // Gibt das Verhalten des Pins an
State state; // Status des Output Pins (Kanal)
uint32_t previousMillis = 0; // Zeitmanagement
public:
Output(const byte pin, const byte mem) : pin(pin), mem(mem) {}
void fire() {
Serial.print(F("outpin=")); Serial.print(pin); Serial.println(F(": fire"));
switch (state) {
case IDLE:
if (mem == 0) {
previousMillis = millis();
digitalWrite(pin, HIGH);
state = PULSE;
}
else {
previousMillis = millis();
digitalWrite(pin, HIGH);
state = MEMA;
}
break;
case MEMB:
digitalWrite(pin, LOW);
state = IDLE;
break;
}
Serial.print(F("outpin=")); Serial.print(pin); Serial.print(F(": state=")); Serial.println(state);
}
void begin() {
pinMode(pin, OUTPUT);
}
void update(uint32_t currentMillis = millis()) {
switch (state) {
case PULSE :
if (currentMillis - previousMillis > 1000) { // nach n millisekunden abschalten
digitalWrite(pin, LOW);
state = IDLE;
Serial.print(F("outpin=")); Serial.print(pin); Serial.print(F(": state=")); Serial.println(state);
}
break;
case MEMA :
if (currentMillis - previousMillis > 1000) { // nach n millisekunden in den nächsten Status
state = MEMB;
Serial.print(F("outpin=")); Serial.print(pin); Serial.print(F(": state=")); Serial.println(state);
}
break;
}
}
};
Output output [] = {
//Pin, mem, befehl auf serieller
{ 4, 1 }, // a 0
{ 5, 0 }, // b 1
{ 6, 1 }, // c 2
{ 7, 1 }, // d 3
{ 8, 0 }, // e 4
{ 9, 0 }, // f 5
{10, 0 }, // g 6
{11, 0 }, // h 7
{12, 0 }, // i 8
{13, 0 }, // j 9
{A0, 0 }, // k
{A1, 0 }, // l
{A2, 0 }, // m
{A3, 0 }, // o
{A4, 0 }, // p
//{A5, 0 } // q
{A4, 0 } // Testweise. noiasca braucht den A5 zum Simulieren der PWM
};
struct RC {
const uint8_t pin; // the GPIO to read from
int8_t previousChannel = -1; // last fired channel (or -1 if last pwm signal was invalid)
int8_t newChannel = -1; // a new readed channel
uint32_t newMillis = 0; // when was the new channel readed
uint16_t previousPulseLength = 0; // just for debug print
RC(uint8_t pin) : pin(pin) {} // constructor as we only want to handover the const member variable
};
RC rc[] {
{2},
//{3} // @todo: zweiter RC Pin noch nicht aktiviert, macht das debuggen nur schwieriger ;-)
};
// nur testweise um Befehle einzugeben
void handleSerial() {
int c = Serial.read();
if (c >= 'a' && c <= 'p') { // a = 0 ... p = 15
output[c - 'a'].fire(); // entsprechenden Kanal triggern
} else if (c >= '0' && c <= '9') { // die ersten 10 Ziffern gehen auch ;-)
output[c - '0'].fire();
}
}
// translate pulseIn length into channel
// returns -1 if invalid
int pulseToChannel(int pulseLength) {
switch (pulseLength) {
case 950 ... 1050:
return 0;
break; // können später mal gelöscht werden
case 1950 ... 2050:
return 1;
break;
case 1100 ... 1200:
return 2;
break;
case 1800 ... 1900:
return 3;
break;
case 1240 ... 1340:
return 4;
break;
case 1350 ... 1450:
return 5;
break;
case 1530 ... 1630:
return 6;
break;
case 1660 ... 1760:
return 7;
break;
default:
return -1; // invalid pwm range
}
}
// Simulation von pulseIn mit einem Schiebepoti
uint16_t pulseInSimu(uint8_t pin, uint8_t level, uint16_t interval) {
(void)pin;
(void)level;
(void)interval;
int raw = analogRead(A5);
return map(raw, 0, 1023, 900, 2000);
}
// Version mit Anti-Shake
void readRCinput() {
for (size_t i = 0; i < sizeof(rc) / sizeof(rc[0]); i++) {
uint16_t pulseLength = pulseInSimu(rc[i].pin, HIGH, 20000); // read analog instead of PWM pulse
//uint16_t pulseLength = pulseIn(rc[i].pin, HIGH, 20000); // Read PWM pulse
int8_t actualChannel = pulseToChannel(pulseLength); // try to get an channel from the pwm pulse length (or -1)
// just a debug print of value changes:
if (rc[i].previousPulseLength != pulseLength) {
rc[i]. previousPulseLength = pulseLength;
Serial.print(F("rc pin=")), Serial.print(rc[i].pin); Serial.print(F(": pulseLength=")); Serial.print(pulseLength);
if (actualChannel >= 0) {
Serial.print("\t (channel="); Serial.print(actualChannel); Serial.print(')');
}
Serial.println();
}
// 1: check for new changes:
if (actualChannel >= 0 && actualChannel != rc[i].newChannel) {
rc[i].newMillis = millis(); // remember Timestamp of change
rc[i].newChannel = actualChannel;
Serial.print(F("rc pin=")), Serial.print(rc[i].pin); Serial.print(F(": newChannel=")); Serial.println(rc[i].newChannel);
}
// 2: check if new change is old enough
if (actualChannel >= 0 && actualChannel != rc[i].previousChannel && millis() - rc[i].newMillis > 300) { // only react on channel state changes
Serial.print(F("rc pin=")); Serial.print(rc[i].pin); Serial.print(F(": actualChannel=")); Serial.println(actualChannel);
if (actualChannel >= 0 && actualChannel <= 7) output[actualChannel].fire(); // @todo cleanup dirty hardcoded 7
else if (actualChannel >= 8 && actualChannel <= 15) output[actualChannel].fire();
rc[i].previousChannel = actualChannel;
}
}
}
/*
// Alte Version ohne Anti-Shake
void readRCinput_old() {
for (size_t i = 0; i < sizeof(rc) / sizeof(rc[0]); i++) {
uint16_t pulseLength = pulseIn(rc[i].pin, HIGH, 20000); // Read PWM pulse
int actualChannel = pulseToChannel(pulseLength); // try to get an channel from the pwm pulse length
if (actualChannel >= 0 && actualChannel != rc[i].previousChannel) { // only react on channel state changes
Serial.print(F("Neuer Wert auf rc pin=")); Serial.print(rc[i].pin);
Serial.print(F(" ist ")); Serial.println(actualChannel);
if (actualChannel >= 0 && actualChannel <= 7) output[actualChannel].fire(); // @todo cleanup dirty hardcoded 7
else if (actualChannel >= 8 && actualChannel <= 15) output[actualChannel].fire();
rc[i].previousChannel = actualChannel;
}
}
}
*/
void setup() {
Serial.begin(115200);
for (auto &i : rc) pinMode(i.pin, INPUT);
for (auto &i : output) i.begin();
Serial.println("und go");
}
void loop() {
readRCinput();
handleSerial(); // Eingaben prüfen/verarbeiten
uint32_t currentMillis = millis(); // der Zeitstempel wird 16 mal benötigt, daher holen wir ihn uns in eine Variable
for (auto &i : output) {
i.update(currentMillis);
}
}