• Die Forumsregeln und Nutzungsbedingungen findet ihr in der Navbar unter Impressum !
    Bitte unbedingt beachten!
    Wie überall im Leben gibt es Spielregeln, so auch hier!
    Die Datenschutzerklärung zum DSGVO findet ihr ebenfalls in der Navbar unter Datenschutzerklärung !
    Hinweis nach DSGVO :
    Es ist hier keinerlei Angabe erforderlich. Alle Angaben in diesem Bereich sind öffentlich sichtbar und werden freiwillig gemacht. Mit einem Eintrag in diesem Bereich erkenne ich dieses an, und bestätige die Datenschutzerklärung zur DSGVO für das Forum gelesen zu haben.

    Danke
  • Hallo Gast, beschränke dich hier bitte auf den Bereich der Elektronik. Die Fahrzeuge oder Gebäude, wo diese Elektronik eingebaut wird bitte in dem passenden Fachbereiich behandeln. Auch wenn Teile des Projektes dadurch im Forum doppelt vorhanden sind! Danke.

Arduino Lichtmodul für Bremslicht, Rückfahrscheinwerfer und Pieper

Bording

Well-known member
Supermoderator
VIP Unterstützt modelltruck.net 2024
Registriert
08.08.2007
Beiträge
1.949
Hallo,

ich habe mir kurz vor Weihnachten einen Arduino Nano gekauft.
Es gibt ja schon einige Programme im Netz, dennoch habe ich mir ein Lichtmodul mit automatischen Bremslicht, Rückfahrscheinwerfer und Pieper programmiert.

Hardware:
Signal vom Empfänger: Digitaler Eingang D2
Bremslicht LED(270 Ohm): Digitaler Ausgang D12
Rückfahrscheinwerfer LED(on Board): Digitaler Ausgang D13
Pieper (1k Ohm): Digitaler Ausgang D3

Software:
Code:
// Schaltmodul über RCKanal mit Bremslicht und Rückfahrscheinwerfer und Rückfahrwarner

const byte PIN_RC = 2; // Pin 2 wo das Empfängersignal eingelesen wird
unsigned long RcValue;  // Eingelesener Wert

boolean Null = 0; // Nullstellung wird erkannt
boolean vorher_Null = 0; // Nullstellung vorher
const byte LED=13; // Rückfahr LED weiß
const byte LEDrt=12; // Bremslicht rot
int timer_loop = 0; //Schleife bis Brems LED aus

const byte speakerPin = 3;    // Lautsprecher an Pin 3
int frequency  = 1300; // 1300Hz 
int duration = 400;   // Dauer des Tons
int ton_loop = 0;  // Intervallzeit für den Ton


void setup() 
{ 
pinMode (LED, OUTPUT); // Rückfahrlicht
pinMode (LEDrt, OUTPUT); // Bremslicht LED

// Eingang für Empfängersignal an Pin 2
pinMode(PIN_RC, INPUT);     
digitalWrite(PIN_RC, HIGH);
} 

void loop() 
{ 
// Aktuellen RC-Wert vom Empfänger einlesen
RcValue = pulseIn(PIN_RC, HIGH, 50000); // PIN_RC = Eingang, HIGH = Flanke, 50000 = Timeout 
delay(10);  // Zeitbasis für die Timer_loop 
 
if (RcValue < 1450) {
  digitalWrite (LED,HIGH); // Rückfahrscheinwerfer ein
  ton_loop = ton_loop + 1; // als Ersatz für den Pausetimer gibt es dises Schleife
    if (ton_loop <=1) { // am Anfang Ton einschalten
    tone(speakerPin, frequency, duration); // 400 ms Ton ausgeben
    }
    if (ton_loop == 60) { // Intervallzeit für den Ton 60*10ms = 600 ms
    ton_loop = 0;} // wenn Intervall voll, Loop wieder zurück setzen
}
if (RcValue >1451) {
  digitalWrite (LED,LOW); // Rückfahrscheinwerfer aus
  ton_loop = 0; // Ton_loop aus
 }
if ((RcValue >1450) &&(RcValue <1550)) {//Abfrage Nullstellung 
  Null = true;
} 
    if (vorher_Null == false && Null == true){ //vorher kein Null -> jetzt Null-> Bremslicht an
    digitalWrite (LEDrt,HIGH);
    timer_loop = 0; // timer auf 0 setzen
    }
if (timer_loop == 200 || Null == false) { // wenn timer = 200 oder wieder FWD bzw RVS, dann Bremslicht aus
digitalWrite (LEDrt,LOW);
}
vorher_Null = Null; // alten Null Wert speichern
Null = false; // aktuellen Nullwert zurück setzen
timer_loop = timer_loop +1; // Timer um 1 hochzählen
}
 
Moin Bert,

sieht doch prima aus, mit wenig Aufwand eine Lösung.

Ich beschäftige mich auch gerade mit den Thema "Analyse von Servo Signalen". Aus dieser Erfahrung hier ein paar Hinweise, die Du evtl. berücksichtigen könntest/solltest.

- Wenn Du den Knüppel nicht bewegst, bekommst Du nie konstant den gleichen Wert. Er wird immer um einen Mittelwert mit einer gewissen Abweichung schwanken. Somit hast Du bei den Servo Signalen den gleichen Effekt die bei einem Taster, das Prellen. Um das ganze zu minimieren gehe ich hin und "glätte" den Wert. Ich sammele immer die letzten 8 Werte und bilde den Mittelwert. Kommt ein neues Signal fliegt das 8. raus, usw.. Dadurch kannst Du auch "Ausreißer" in den Messwerten glatt bügeln.
- Nullstellung : Hier empfehle ich im Setup in einer Schleife 32 Signale per pulseIn zu messen und die Werte zu summieren. Danach die Summe durch 32 teilen und du hast den sauberen Mittelwert. So funktioniert Dein Programm an nahezu jeder Funke automatisch korrekt. Die Prüfung auf Nullstellung erfolgt dann durch Addieren bzw. Subtrahieren des Mittelbereichs (+/- 50)
- Interrupts : Wenn Du Zeit und Lust hast setze Dich mal mit Interrupts auseinander. So kannst Du Verlustfrei Servosignale messen ohne dass deine loop() Funktion hängt. Die Interrupt Funktion wird durch attachInterrupt() aktiviert. In der Funktion nimmst Du den aktuellen Zeitwert in Mikrosekunden und ziehst davon den Zeitwert des letzten Aufrufs ab. So hast Du die Länge des Pegels zwischen 2 Pegelwechseln. Nun musst Du noch feststellen ob es das HIGH oder LOW Signal gemessen hast. Ich der Wert < 2100 ist es das HIGH Signal >2200 ist es das LOW Signal und kann ignoriert werden. Den Wert < 2100 speicherst Du in einer volatile Variable und kannst auf diese dann in der loop() Funktion zugreifen.
- Bei Deiner schleife empfehle ich mit echten Zeiten zu arbeiten, bei Zählungen über Counter variiert die Dauer je nach Ausführungszeit des loop(). Hierzu definiere ich mir immer eine Variable und setze sich z. B. auf millis() + 500. Im loop() prüfe ich dann ob der aktuelle millis() Wert größer ist als der gemerkte. Wenn ja, wird die LED wieder abgeschaltet.

Ich freue mich dass du Spaß am Arduino mit RC hast, da kann man so viele wunderschöne Sachen mit machen.

Den ATMega328 kannst Du auch auf einer eigenen Platine betreiben, Du brauchst nur einen 16MHz Quarz und zwei 22pF Kondensatoren. Die Stromversorgung ziehst Du über den BEC und schon hast du eine wunderbare, kompakte Platine mit deinen Basteleien. Ich verwende dazu eine Arduino UNO mit dem DIL Chip. So kann ich ihn programmieren, setze ihn auf meine Platine und teste. Man kann auch die platine auch einen Anschluss löten und kann dann über ein TinyUSB Modul den Chip direkt programmieren, man braucht keine Arduino Platine mehr.

So genug geschwafelt, ich wünsche Dir weiter sehr viel Spaß mit dem Board und freue mich von weiteren Basteleien aus Deiner Werkstatt zu lesen.
 
Hallo Peter,

genau an den Thema bin ich gerade dran. Leider hat das mit dem glätten der Signale noch nicht geklappt.
Kannst du dein Programm mal hier rein stellen, dann kann ich mir das mal ansehen.

Es gibt den Arduino Nano auch als Ausführung ohne USB Treiber usw. da ist die Platine dann nur noch 34x18 mm groß. Nennt sich dann Pro-Mini-Atmega328P. Gibts bei ebay für 5€ oder drunter. Den werde ich dann eher nehmen.
 
Moin Bert,

hier meine Lösung zur Glättung:

Code:
//	Variablen fuer die Geschwindigkeit
volatile	uint16_t	ReceivedSpeedValues[8]				= {1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500};
volatile	uint16_t	ReceivedSpeedValuesSum				= 12000;
volatile	uint8_t	ReceivedSpeedValuesIndex				= 0;

volatile	uint32_t	LastSpeedChange						= micros();
volatile	uint16_t	LastSpeedValue						= 0;

volatile	uint16_t	MeasuredSpeedUp					= 1950; // eingemessener Maximalwert des Kanals
volatile	uint16_t	MeasuredSpeedCenter					= 1500; // eingemessener Mittelwert des kanals (Knueppel in Ruhe)
volatile	uint16_t	MeasuredSpeedDown					= 1100; // eingemessener Minimalwert des Kanals

void SpeedPositionInterrupt() 
{
	uint32_t    nMicros		= micros();
	uint16_t    nDifference  = (uint16_t)(nMicros - LastSpeedChange);

	if ( (nDifference > ( MeasuredSpeedDown - 20 ) ) && ( nDifference < ( MeasuredSpeedUp + 20 )))
	{
		ReceivedSpeedValuesSum							-= ReceivedSpeedValues[ReceivedSpeedValuesIndex];
		ReceivedSpeedValues[ReceivedSpeedValuesIndex]	 = nDifference;
		ReceivedSpeedValuesSum							+= nDifference;
		ReceivedSpeedValuesIndex						 = ( ( ReceivedSpeedValuesIndex + 1 ) & 0x07 );	//	Index erhoehen und ggf. von 8 auf 0 springen
		LastSpeedValue								 = ( ReceivedSpeedValuesSum >> 3 );	//	durch 8 teilen
	}
	LastSpeedChange	= nMicros;
}

Aktiviert wird das Ganze im setup() mit

Code:
attachInterrupt(0, SpeedPositionInterrupt, CHANGE);

Danach steht Dir im loop() immer das aktuelle signal am Kanal (Pin 2) in der Variable LastSpeedValue zur Verfügung.

Hilft Dir das weiter ?
 
Moin Bert,

noch ein paar Erläuterungen. Ich arbeite mit einem Array von 8 Werten. Der Index wandert über diesen Pool. Die Konstruktion ( ... + 1 ) & 0x07 sorgt dafür, dass bei einem Index von 7 der nächste Wert nicht auf 8 und somit außerhalb des Arrays läuft, sondern wieder bei 0 beginnt, so wird es eine zyklische Bewegung durch den Array. An der aktuellen Position ziehe ich von der Summe erst den letzten Wert ab, überschreibe ihn mit dem Neuen und addiere den neuen Wert. Am Ende teile ich den Wert durch 8 ( >> 3 Konstrukt). Ich nehme 8 Werte, da so die Summe durch eine sogenannte Bit-shift Operation durch 8 geteilt wird. Dies ist schneller eine reale Division. In Interrupts musst Du immer darauf achten nicht zu viel zu tun.

Ich arbeite mit Variablen die "MeasuredSpeed...". sie stellen Werte dar, die ich in einem speziellen Konfigurationsmodus ermittelt habe. Dies erfolgt ähnlich wie das Einmessen von Servonaut Modulen. Das Programm lernt die obere und untere Grenze des zu erwartenden Wertebereichs kennen und lernt gleichzeitig welcher Wert die Mitte darstellt. so kannst Du den Quelltext mit jeder Funke bzw. Empfänger einsetzen. einlernen und fertig.
 
Hallo Peter,

die Mittelwertbildung hat geklappt!
Ich habe dann weiter versucht die Interrupt Funktion einzubauen. Dabei handelt es sich ja einfach um einen Sprung in ein Unterprogramm wenn sich am Interrupt Pin was ändert.

Irgendwie passt die Declaration meines "Unterprogramms" nicht :sauer
 
So sieht das Programm aus. Wie oder wo muss ich da das Unterprogramm zum Interrupt einbauen?
Ich habe schon diverse Programme bei google gefunden, aber irgendwie hab ich das nicht kapiert wies gehen soll :heilig

/* Im Programm wird das Eingangssignal für einen Servo eingelesen und über 8 Werte geglättet */

#include <Servo.h>
Servo myservo; // Servo Libary

const byte PIN_RC = 2; // das ist INT 0 aber Pin 2 wo das Empfängersignal eingelesen wird!

volatile int RcInValue; // Eingelesener Wert der immer aus dem RAM kommt (w.g. volatile)
volatile int ReceivedSpeedValues[8] = {1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500}; // Array mit 8 Werten für die gemessenen Werte
volatile int Summe = 12000; // Summe aus dem Array 8x1500, als Anfangswert
volatile int Mittelwert =1500; // Mittelwert aus der Summe
volatile int Zeiger =0; // Zeiger für das Array, fängt mit 0 an


void setup()
{
pinMode(PIN_RC, INPUT); // Eingang für Empfängersignal an Pin 2
digitalWrite(PIN_RC, HIGH); // hier wird der interne Pull-up Widerstand eingeschaltet
myservo.attach(5); // Ausgang zum Servo an Pin 5
}

void loop() {
Summe = Summe - ReceivedSpeedValues[Zeiger]; // substrahiere letzte Messung aus dem Array
RcInValue = pulseIn(PIN_RC, HIGH, 100000); // Aktuellen RC-Wert vom Empfänger einlesen, HIGH = Flanke, 100000 = Timeout
ReceivedSpeedValues[Zeiger] = RcInValue; // eingelesener Wert neu in das Array
Summe = Summe + ReceivedSpeedValues[Zeiger]; // addiere Wert zur Summe
Zeiger = Zeiger + 1; // zur naechsten Position im Array

if (Zeiger >=7) {
Zeiger = 0; }// wieder zum Anfang des Arrays

Mittelwert = (Summe >>3); // Mittelwert aus dem Array wird durch 8 geteilt
myservo.write(Mittelwert); // Durchschnitt wird am Servo ausgegeben


//delay(5); // zum besser sichtbar machen im Serial Monitor
}

// Ende
 
Moin Bert,

ich habe mal Dein Programm ein bisschen umgebaut um die Interrupt Geschichte zu verdeutlichen.

Da ich nicht weiß in wie weit Du dich mit Interrupts schon beschäftigt hast, hier eine kurze Erläuterung. Interrupts sind Programmunterbrechungen aufgrund vorher definierter Gegebenheiten. Dies kann z. B. wie in unserem Fall ein Wechsel eines Signals von LOW->HIGH bzw. HIGH->LOW sein, es kann aber auch ein Zeit gesteuerter Interrupt sein, der alle x Microsekunden ausgeführt wird. Wenn der Prozessor einen solchen Grund feststellt, hält er das laufende Programm (deine loop() Funktion) an und führt die Funktion aus, die an den Interrupt gebunden ist. Wenn die Funktion ausgeführt wurde, setzt der Prozessor Dein Programm weiter fort. Daher siehst Du im Quellcode keinen expliziten Aufruf der Interrupt Funktion, das tut der Prozessor für Dich.

Kommen wir nun zum Quellcode:

Code:
#include <Servo.h> 
Servo myservo; // Servo Libary 

const byte PIN_RC = 2; // das ist INT 0 aber Pin 2 wo das Empfängersignal eingelesen wird!

volatile int RcInValue; // Eingelesener Wert der immer aus dem RAM kommt (w.g. volatile)
volatile int ReceivedSpeedValues[8] = {1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500}; // Array mit 8 Werten für die gemessenen Werte
volatile int Summe = 12000; // Summe aus dem Array 8x1500, als Anfangswert
volatile int Mittelwert =1500; // Mittelwert aus der Summe 
volatile int Zeiger =0; // Zeiger für das Array, fängt mit 0 an

volatile uint32_t	LastSpeedChange	= micros();

void SpeedPositionInterrupt() 
{
	uint32_t    nMicros		= micros();
	uint16_t    nDifference  = (uint16_t)(nMicros - LastSpeedChange);

	if ( (nDifference > 900 ) && ( nDifference < 2100))
	{
		Summe							-= ReceivedSpeedValues[Zeiger];
		ReceivedSpeedValues[Zeiger]	 = nDifference;
		Summe							+= nDifference;
		Zeiger						 = ( ( Zeiger + 1 ) & 0x07 );	//	Index erhoehen und ggf. von 8 auf 0 springen
		Mittelwert								 = ( Summe >> 3 );	//	durch 8 teilen
	}
	LastSpeedChange	= nMicros;
}

void setup() 
{ 
	pinMode(PIN_RC, INPUT); // Eingang für Empfängersignal an Pin 2
	digitalWrite(PIN_RC, HIGH); // hier wird der interne Pull-up Widerstand eingeschaltet
	myservo.attach(5); // Ausgang zum Servo an Pin 5
	attachInterrupt( 0, SpeedPositionInterrupt, CHANGE);
} 

void loop() {
	myservo.write(Mittelwert); // Durchschnitt wird am Servo ausgegeben

	delay(20);

	//delay(5); // zum besser sichtbar machen im Serial Monitor 
}

Zerlegen wir das ganze in Module um es zu besprechen:

Zuerst die Definitionen der Variablen. Grundsätzlich müssen alle Variablen, die in einer Interrupt Funktion verwendet werden als "volatile" deklariert werden.
Ich habe Deiner Definition nur die Variable "LastSpeedChange" hinzugefügt. Sie beinhaltet immer den Zeitpunkt des letzten Signalpegelwechsels.

Code:
#include <Servo.h> 
Servo myservo; // Servo Libary 

const byte PIN_RC = 2; // das ist INT 0 aber Pin 2 wo das Empfängersignal eingelesen wird!

volatile int RcInValue; // Eingelesener Wert der immer aus dem RAM kommt (w.g. volatile)
volatile int ReceivedSpeedValues[8] = {1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500}; // Array mit 8 Werten für die gemessenen Werte
volatile int Summe = 12000; // Summe aus dem Array 8x1500, als Anfangswert
volatile int Mittelwert =1500; // Mittelwert aus der Summe 
volatile int Zeiger =0; // Zeiger für das Array, fängt mit 0 an

volatile uint32_t	LastSpeedChange	= micros();

Hier folgt nun die Interrupt Funktion, welche automatisch vom Prozessor ausgeführt wird, sobald sich das Signal am Pin 2 ändert. Die Funktion ermittelt die Zeit, die seit dem letzten Pegelwechsel verstrichen ist und prüft ob sie ein HIGH Signal ist. Dies kann man ja an Hand der Länge feststellen. Der Mittelwert wird berechnet und in Deiner Variable Mittelwert gespeichert. Bitte nicht wundern dass ich mir eine Variable nMicros definiert habe. Sie existiert damit der Prozessor die micros() Funktion nicht mehrfach ausführen muss (Zeitersparnis) und damit ich innerhalb der Funktion immer mit dem gleichen Wert arbeite.

Code:
void SpeedPositionInterrupt() 
{
	uint32_t    nMicros		= micros();
	uint16_t    nDifference  = (uint16_t)(nMicros - LastSpeedChange);

	if ( (nDifference > 900 ) && ( nDifference < 2100))
	{
		Summe							-= ReceivedSpeedValues[Zeiger];
		ReceivedSpeedValues[Zeiger]	 = nDifference;
		Summe							+= nDifference;
		Zeiger							 = ( ( Zeiger + 1 ) & 0x07 );	//	Index erhoehen und ggf. von 8 auf 0 springen
		Mittelwert							= ( Summe >> 3 );	//	durch 8 teilen
	}
	LastSpeedChange	= nMicros;
}

In der setup Funktion habe ich lediglich den Funktionsaufruf attachInterrupt() hinzugefügt. Er teilt dem Prozessor mit, dass ab jetzt die Funktion SpeedPositionInterrupt() aufzurufen ist, wenn sich an Pin 2 der Signalpegel ändert.

Code:
void setup() 
{ 
	pinMode(PIN_RC, INPUT); // Eingang für Empfängersignal an Pin 2
	digitalWrite(PIN_RC, HIGH); // hier wird der interne Pull-up Widerstand eingeschaltet
	myservo.attach(5); // Ausgang zum Servo an Pin 5
	attachInterrupt( 0, SpeedPositionInterrupt, CHANGE);
}

Die loop() Funktion ist nun sehr übersichtlich. Sie schreibt lediglich den kontinuierlich angepassten Wert aus der Variable Mittelwert in den Servo. Das delay(20) habe ich aus 2 Gründen eingefügt.
1. Das Servosignal ändert sich eh nur alle 20 Millisekunden, da muss nicht x mal der gleiche Wert geschrieben werden.
2. Der delay() sorgt dafür dass der Prozessor ruht, d. h. er verbraucht deutlich weniger Strom und wird nicht heiß.

Code:
void loop() {
	myservo.write(Mittelwert); // Durchschnitt wird am Servo ausgegeben

	delay(20);

	//delay(5); // zum besser sichtbar machen im Serial Monitor 
}

So, soweit meine Änderungen und Kommentare, ich hoffe es hilft Dir weiter. ;)
 
Danke Peter!

ich habe immer den Interruptblock im Hauptprogramm gehabt, da hat dann der Compiler gemeckert.
Nun ist mir klar wo der Block hingehört!
 

Servonaut
Zurück
Oben Unten