gismow
Active member
- Registriert
- 03.03.2012
- Beiträge
- 2.203
Moin, moin...
In meinen Fahrplatinen benötige ich eine Möglichkeit Funktionen auszulösen. Gleichzeitig wollte ich aber keinen weiteren Kanal "verschwenden" und habe mich entschieden mich einfach auf den EKMFA Kanal meiner Beier USM-RC-2 aufzuschalten. Also habe ich einen Multiswitch entwickelt der sich exakt so verhält wie der EKMFA von Beier bedient wird. Das Modul zählt die Tipps nach oben bzw. unten und führt dann eine definierte Funktion aus.
So kann z. B. das Beier die Tipps bis 3 für oben (A) und unten (D) verwenden und mein Multiswitch kann dann z. B. ab 4 mal getippt eigene Funktionen ausführen, die das Beier Modul dann ignoriert. Natürlich kann auch passend zu einer Beier Funktion eine Funktion im Multiswitch ausgelöst werden.
Der Code ist ausgelegt für den Arduino Uno und lauscht an Pin2 auf ein Servosignal.
Beim Start geht das Modul in einen "Startup" Modus. Hier werden erst einmal eine definierte Anzahl valider Signale gezählt um sicher zu stellen dass das Modul erst "los legt" wenn auch Servo Signale vorhanden sind. Sind diese empfangen wird der dann aktuelle Servowert des Kanals als Mittelwert des Kanals verwendet. Aus diesem Wert werden dann Schwellenwerte berechnet ab denen ein Tipp gezählt wird. Wenn der Startup abgeschlossn ist, wechselt das Programm in den "Running" Modus. Aber hier werden dann die Servowerte ausgewertet.
Ich habe den Quelltext entsprechend dokumentiert, sollten dennoch Fragen aufkommen, einfach posten.
Und nun noch etwas rechtliches (Winni, ich hoffe du erlaubst dass ich diesen Text von dir kopiert habe):
In meinen Fahrplatinen benötige ich eine Möglichkeit Funktionen auszulösen. Gleichzeitig wollte ich aber keinen weiteren Kanal "verschwenden" und habe mich entschieden mich einfach auf den EKMFA Kanal meiner Beier USM-RC-2 aufzuschalten. Also habe ich einen Multiswitch entwickelt der sich exakt so verhält wie der EKMFA von Beier bedient wird. Das Modul zählt die Tipps nach oben bzw. unten und führt dann eine definierte Funktion aus.
So kann z. B. das Beier die Tipps bis 3 für oben (A) und unten (D) verwenden und mein Multiswitch kann dann z. B. ab 4 mal getippt eigene Funktionen ausführen, die das Beier Modul dann ignoriert. Natürlich kann auch passend zu einer Beier Funktion eine Funktion im Multiswitch ausgelöst werden.
Der Code ist ausgelegt für den Arduino Uno und lauscht an Pin2 auf ein Servosignal.
Beim Start geht das Modul in einen "Startup" Modus. Hier werden erst einmal eine definierte Anzahl valider Signale gezählt um sicher zu stellen dass das Modul erst "los legt" wenn auch Servo Signale vorhanden sind. Sind diese empfangen wird der dann aktuelle Servowert des Kanals als Mittelwert des Kanals verwendet. Aus diesem Wert werden dann Schwellenwerte berechnet ab denen ein Tipp gezählt wird. Wenn der Startup abgeschlossn ist, wechselt das Programm in den "Running" Modus. Aber hier werden dann die Servowerte ausgewertet.
Ich habe den Quelltext entsprechend dokumentiert, sollten dennoch Fragen aufkommen, einfach posten.
Und nun noch etwas rechtliches (Winni, ich hoffe du erlaubst dass ich diesen Text von dir kopiert habe):
Den Code kann und darf jeder für sich ändern, solange es eine private Nutzung ist. Der Code wurde nur zum Einsatz in privaten Modellfahrzeugen erstellt. Jede andere Nutzung ist widerrechtlich. Gewerbliche Nutzung des Codes ist untersagt und wird rechtlich verfolgt. Eine gewerbliche Nutzung ist nur gegen Lizenzgebühr möglich. Es wird keine Haftung für Folgeschäden welche sich aus der Nutzung des Codes ergeben könnten übernommen. Der Einsatz erfolgt auf eigene Gefahr und eigenes Risiko. Eine Veröffentlichung in anderen digitalen oder Printmedien außerhalb dieses Forums ist nicht gestattet. Mit der Nutzung diese Codes erklärt sich der Nutzer hiermit einverstanden.
Code:
/*
Name: MyEKMFA.ino
Created: 19.02.2019 18:16:46
Author: Peter Hartmann
*/
// Wenn keine serielle Ausgabe erfolgen soll, dann dieses Define veraendern, z. B. in NO_SERIAL_OUTPUT
// Dies spart Rechenleistung wenn man den Sketch in finaler Version einspielt.
#define SERIAL_OUTPUT
#pragma region Multiswitch Methoden
// Speichervariablen fuer das Auslesen des Multiswitch Kanals
volatile uint16_t ReceivedMultiswitchValues[4] = {1500, 1500, 1500, 1500};
volatile uint16_t ReceivedMultiswitchValuesSum = 6000;
volatile uint8_t ReceivedMultiswitchValuesIndex = 0;
volatile uint32_t LastChannelMultiswitchChange = micros();
// Diese Variable beinhaltet immerden zuletzt ermittelten Servowert in ms
volatile uint16_t ActualMeasuredMultiswitchValue = 1500;
// Variablen und Defines fuer die Realisierung eines Multiswitch Kanals
#define MultiswitchStateUndefined 0
#define MultiswitchStateCenter 1
#define MultiswitchStateUp 2
#define MultiswitchStateDown 3
// Maximal- bzw. Minimalwert des Kanals. Zur Initialisierung werden diese mit festen Werten definiert,
// welche eine Standard Funke liefern koennen sollte. Bei Bedarf koennen diese auch angepasst werden.
uint16_t MeasuredMultiswitchUp = 1950;
uint16_t MeasuredMultiswitchDown = 1100;
// Der Nullpunkt des Multiswitch Kanals wird beim Start ermittelt. Der hier festgelegte Wert ist eigentlich nicht notwendig.
uint16_t MeasuredMultiswitchCenter = 1500;
// Das Programm geht davon aus dass eine Bewegung des Knueppels (Tasters, Schalters, what ever) ueber die Mitte des moeglichen Weges
// einen zaehlbaren Tippwert ergeben. Alle Bewegungen unterhalb dieser Mittelwerte werden nicht beruecksichtigt.
volatile uint16_t CalculatedMultiSwitchUpperStart = ((MeasuredMultiswitchCenter + MeasuredMultiswitchUp) >> 1);
volatile uint16_t CalculatedMultiSwitchLowerStart = ((MeasuredMultiswitchCenter + MeasuredMultiswitchDown) >> 1);
// Die Zaehler fuer die Tipps nach oben bzw. nach unten
volatile uint8_t MultiswitchUpCounter;
volatile uint8_t MultiswitchDownCounter;
// Der Zeitstempel wann das letzte Tippen gezaehlt wurde. Er wird im Programmablauf geprueft. Ist die letzte Tippaktion eine bestimmte Zeit her,
// werden die bis dahin gezaehlten Tipps ausgewertet und die entsprechende Funktion ausgefuehrt. Danach werden die Counter wieder auf 0 gesetzt.
volatile uint32_t MultiswitchLastCounterChange;
// Der Zustand waehrend der letzten Signalauswertung. Er wird gebraucht um ihn mit dem aktuellen Zustand zu vergleichen. Gibt es eine Aenderung
// muss reagiert werden.
volatile uint8_t MultiswitchLastState;
#define ProcessingMode_Startup 1 // Die Phase des Einmessens des Nullpunkts
#define ProcessingMode_Running 2 // Die eigentliche Programmausfuehrung
volatile uint8_t ProcessingMode = ProcessingMode_Startup;
#define NumberOfNeededValidSignals 10
volatile uint8_t StartupValidSignalCounter = 0;
#pragma endregion
#pragma Multiswitch Funktionen
void InterruptMultiswitch( )
{
uint32_t nMicros = micros();
uint16_t nDifference = (uint16_t)(nMicros - LastChannelMultiswitchChange);
LastChannelMultiswitchChange = nMicros;
if ((nDifference > 900) && (nDifference < 2000))
{
ReceivedMultiswitchValuesSum -= ReceivedMultiswitchValues[ReceivedMultiswitchValuesIndex];
ReceivedMultiswitchValues[ReceivedMultiswitchValuesIndex] = nDifference;
ReceivedMultiswitchValuesSum += nDifference;
ReceivedMultiswitchValuesIndex = ( ( ReceivedMultiswitchValuesIndex + 1 ) & 0x03 ); // Index erhoehen und ggf. von 4 auf 0 springen
nDifference = ( ReceivedMultiswitchValuesSum >> 2 ); // durch 4 teilen
ActualMeasuredMultiswitchValue = nDifference;
if (ProcessingMode == ProcessingMode_Startup)
{
StartupValidSignalCounter++;
return;
// Da wir uns im Startup Modus befinden ist eine weitere Bearbeitung des Signals nicht notwendig
}
uint8_t NewMultiSwitchstate = MultiswitchStateCenter;
// Nun wird aus dem Servowert berechnet ob sich der Kanal in der Mitte, oben oder unten befindet
if (nDifference >= CalculatedMultiSwitchUpperStart)
NewMultiSwitchstate = MultiswitchStateUp; // Der Kanal ist oberhalb des oberen Schwellenwerts
else
{
if (nDifference <= CalculatedMultiSwitchLowerStart)
NewMultiSwitchstate = MultiswitchStateDown;// Der Kanal ist unterhalb des unteren Schwellenwerts
}
if ( MultiswitchLastState != NewMultiSwitchstate )
{
// Der Wert unterscheidet sich vom vorherigen, entweder ist der Kanal von der Mitte in einen der ausseren gewechselt,
// oder er ist in die Mitte zurueck gekehrt.
if (NewMultiSwitchstate == MultiswitchStateCenter)
{
// Der Kanal ist aus einem der aeusseren Bereiche in die Mitte zurueck gewechselt.
// Diesen Status bearbeiten
// Der aktuelle Status muss ggf. in die Multiswitch Informationen als Auswahl eingepflegt werden
switch(MultiswitchLastState)
{
case MultiswitchStateUp:
// Der Kanal ist aus dem oberen Bereich in die Mitte gewechselt.
// Der Zaehler fuer Tipps nach oben wird erhoeht und der fuer unten wird resettet
MultiswitchUpCounter++;
MultiswitchDownCounter = 0;
break;
case MultiswitchStateDown:
// Der Kanal ist aus dem unteren Bereich in die Mitte gewechselt.
// Der Zaehler fuer Tipps nach unten wird erhoeht und der fuer oben wird resettet
MultiswitchUpCounter = 0;
MultiswitchDownCounter++;
break;
}
// Zusaetzlich wird die Uhrzeit gemerkt wann dieser Wechsel statt gefunden hat.
MultiswitchLastCounterChange = millis();
}
// Den letzten Status merken.
MultiswitchLastState = NewMultiSwitchstate;
}
}
}
void CalculateMultiSwitchSignalAreas()
{
// Der Mittelwert zwischen der Mitte und unten bzw. oben wird durch Addition der Mitte mit dem oberen bzw. unteren Wert und das anschließende
// Teilen durch 2 errechnet.
CalculatedMultiSwitchUpperStart = ((MeasuredMultiswitchCenter + MeasuredMultiswitchUp) >> 1);
CalculatedMultiSwitchLowerStart = ((MeasuredMultiswitchCenter + MeasuredMultiswitchDown) >> 1);
#ifdef SERIAL_OUTPUT
Serial.print(F("Lower: "));
Serial.println(CalculatedMultiSwitchLowerStart);
Serial.print(F("Upper: "));
Serial.println(CalculatedMultiSwitchUpperStart);
#endif
}
void InitializeMultiswitchVariables()
{
MultiswitchLastState = MultiswitchStateCenter;
MultiswitchLastCounterChange = 0;
MultiswitchUpCounter = 0;
MultiswitchDownCounter = 0;
// Nun berechnen wir noch die Bereiche fuer den Multiswitch
CalculateMultiSwitchSignalAreas();
}
void HandleMultiswitch()
{
int nMultiswitchCommand = 0;
if (MultiswitchLastCounterChange > 0)
{
// Es liegt ein Zeitstempel eines Wechsel in die Mitte vor.
// Wenn die letzte Aenderung der Zaehlerstaende mehr als 750ms her ist, wird dies als das gewaehlte Kommando angesehen und gesetzt
if (MultiswitchLastCounterChange < (millis() - 750))
{
// Die Zaehler werden in einen Wert umgewandelt. Positive Werte sind Tipps nach oben, negative Werte sind Tipps nach unten
if (MultiswitchUpCounter > 0)
{
nMultiswitchCommand = (int)MultiswitchUpCounter;
MultiswitchUpCounter = 0;
}
else
{
if (MultiswitchDownCounter > 0)
{
nMultiswitchCommand = -((int)MultiswitchDownCounter);
MultiswitchDownCounter = 0;
}
}
// Der Zeitstempel wird wieder geloescht, denn der Zaehlerwert wurde ja umgewandelt.
MultiswitchLastCounterChange = 0;
}
}
// Auswertung des Multiswitch
if (nMultiswitchCommand > 0)
{
#if defined SERIAL_OUTPUT
Serial.print(nMultiswitchCommand);
Serial.println("x nach oben");
#endif
}
if (nMultiswitchCommand < 0)
{
#if defined SERIAL_OUTPUT
Serial.print(-nMultiswitchCommand);
Serial.println("x nach unten");
#endif
}
// Hier wird nun der errechnete Wert ausgewertet und die entsprechenden Funktionen ausgefuehrt.
switch (nMultiswitchCommand)
{
case 1: // 1x oben
// hier die entsprechende Aktion ausfuehren
break;
case -1: // 1x unten
// hier die entsprechende Aktion ausfuehren
break;
case 2: // 2x oben
// hier die entsprechende Aktion ausfuehren
break;
case -2: // 2x unten
// hier die entsprechende Aktion ausfuehren
break;
}
}
#pragma endregion
// Diese Funktion auskommentieren wenn attachInterrupt() verwendet wird.
// ------- von hier ---------
volatile uint8_t LastInterruptState = PIND;
ISR(PCINT2_vect)
{
uint8_t PinState = PIND;
uint8_t PinChanges = PinState ^ LastInterruptState;
if (PinChanges & (1<<PCINT18))
InterruptMultiswitch();
LastInterruptState = PinState;
}
// ---------- bis hier -----------
// The setup() function runs once each time the micro-controller starts
void setup()
{
#ifdef SERIAL_OUTPUT
Serial.begin( 115200 );
Serial.println(F("Start..."));
#endif
// Den Interrupt initialisieren
// attachInterrupt(digitalPinToInterrupt(2), InterruptMultiswitch, CHANGE);
// Die folgenden 3 Zeilen auskommentieren wenn attachInterrupt() verwendet werden soll
PCICR |= (1 << PCIE2);
PCMSK2 |= (1<<PCINT18); // Pin 2
sei();
}
// Add the main program code into the continuous loop() function
void loop()
{
if (ProcessingMode == ProcessingMode_Startup)
{
if (StartupValidSignalCounter >= NumberOfNeededValidSignals)
{
// Es wurde die benoetigte Anzahl valider Signale erreicht.
// Der bisher eingemessene Wert wird als der Nullpunkt des Kanals eingetragen
MeasuredMultiswitchCenter = ActualMeasuredMultiswitchValue;
InitializeMultiswitchVariables();
// Umschalten auf den Standardmodus
ProcessingMode = ProcessingMode_Running;
#ifdef SERIAL_OUTPUT
Serial.print(F("Measuring done..."));
Serial.println(MeasuredMultiswitchCenter);
#endif
}
}
else
{
// Standardmodus
// Um ein Einmessen des Kanals zu vermeiden wird der aktuelle Wert des kanals getestet.
// Ist er groesser als der bekannte Grenzwert, wird dieser ausgeweitet und die Schwellenwerte
// für den Multiswitch erneut kalibriert
// Diese Kalibrierung kann aber auch weg gelassen und feste Werte definiert werden.
uint8_t Calibrate = 0;
if (ActualMeasuredMultiswitchValue < MeasuredMultiswitchDown)
{
// Der Wert is groesser als der obere Grenzwert, also wird er als neuer oberster Grenzwert gesetzt und die Neuberechnung
// der Schwellenwerte angefordert
MeasuredMultiswitchDown = ActualMeasuredMultiswitchValue;
Calibrate = 1;
}
if (ActualMeasuredMultiswitchValue > MeasuredMultiswitchUp)
{
// Der Wert is kleiner als der untere Grenzwert, also wird er als neuer oberster Grenzwert gesetzt und die Neuberechnung
// der Schwellenwerte angefordert
MeasuredMultiswitchUp = ActualMeasuredMultiswitchValue;
Calibrate = 1;
}
if (Calibrate) CalculateMultiSwitchSignalAreas();
// Das Signal des Multiswitch auswerten und ggf. eine Funktion ausfuehren.
HandleMultiswitch();
}
// Dieser Delay ist OK, da ja jedes Servosignal 20ms lang ist, ein Wert kleiner 20
// wuerde also nur das bereits ausgewertete Signal erneut bearbeiten.
delay(20);
}