Servo-Steuerung einer Modellbahn-Bahnschranke mit Attiny
Von einem Leser meiner Servotest Attiny - Seite wurde ich angesprochen, ob ich nicht eine Servo-Steuerung für eine zweigleisige Modellbahn-Bahnschranke mit Blinkanlage (Andreaskreuz) mit einem Attiny45/85 realisieren könnte. Die Betätigung der Schranke soll über Reed-Kontakte erfolgen, die von Magneten, die an der Unterseite der Lokomotiven angebracht sind, gesteuert werden. Die Reed-Kontakte befinden sich in den Gleisen vor und hinter der Schranke, in einem Abstand von der Schranke, von jeweils knapp drei Zuglängen. Für den Testaufbau habe ich zur Simulation der Reed-Kontakte einfache Taster verwendet.
Testaufbau:
Verwendete Bauteile:
- 1 Attiny45/85 / 8 MHz
- 2 Analog-Servos (z.B. Modelcraft RS2 MG/BB)
- 4 Taster (Simulation von Reed-Kontakten)
- 1 Reset-Taster
- 3 Widerstände 10 kOhm
- 2 Widerstände 220 Ohm
- 1 Widerstand 1 kOhm
- 1 NPN-Transistor, z.B. BC337
- 2 LEDs 5mm
- 1 Trimmpoti 10 kOhm
- 1 Programmierstecker 4-pol
- 1 Netzteil 5 VDC, 1A
Abbildung 4-1: Testaufbau der Servo-Steuerung einer Modellbahn-Bahnschranke
Programm:
Funktionen:
- Erkennen, ob sich Züge auf den Gleisen innerhalb der Taster (Reed-Kontakte) befinden und Öffnen bzw. Schließen der Schranke
- Einstellung der Schließ- bzw. Öffnungszeit der Schranken mit Trimmpoti
- Programmstart nach RESET fährt die Schranke in die Offen-Stellung
- Schalten der Blinkanlage (Andreaskreuz) mit Vor- und Nachblinkzeit
Mit folgenden Konstanten kann das Verhalten der Schranke und der Blinkanlage eingestellt werden:
- pwmTimeZu, pwmTimeAuf: Servo-Impulseinstellung in Mikrosekunden (µs) für die Schranken-Stellung ZU bzw. AUF 1)
const int pwmTimeZu = 2000; //Servostellung fuer ZU in Mikrosekunden (µs)
const unsigned int pwmTimeAuf = 1000; //Servostellung fuer AUF in µs
- freilauf: TRUE, wenn die Servos der Schranke nach dem Fahren in die Endpositionen nicht mehr mit dem PWM-Signal beaufschlagt werden. Dadurch kann die Schranke auch händisch verstellt werden 2)
const bool freilauf = true;
- triggerLockTime: Auslösesperre nach Ansprechen eines Reed-Kontaktes in Millisekunden (ms) bis zum nächsten Ansprechen 3)
const unsigned int triggerLockTime = 5000; //Zeit in ms
- blinkFrequenz: Blinkfrequenz der Blinkanlage
const float blinkFrequenz = 1.5; //Blinkfrequenz in Hz
- vorBlinkZeit: Zeit, die die Blinkanlage vor dem Schließen des Schrankens blinkt
const unsigned int vorBlinkZeit = 5000; //Zeit in ms
- nachBlinkZeit: Zeit, die die Blinkanlage nach dem Öffnen des Schrankens blinkt
const unsigned int nachBlinkZeit = 3000; //Zeit in ms
1) In der Standardeinstellung (pwmTimeZu = 2000 µs, pwmTimeAuf = 1000 µs) fährt der von mir verwendete Servo ein Winkel von ca. 90 Grad, für eine Drehung von 180 Grad ist eine Einstellung von 560 - 2460 µs erforderlich. Bewegt sich der Servo in die falsche Richtung, kann man die Vorgabe umdrehen (z.B. pwmTimeZu = 1000 und pwmTimeAuf = 2000). Wie die Einstellung eines Servos funktioniert und getestet werden kann, kann hier nachgelesen werden: Servosteuerung
Achtung: Die Drehbewegung der Servos unbedingt zuerst ohne mechanische Verbindung mit den Schranken testen. Eine falsche Einstellung kann die Schranken selbst oder die Mechanik der Schranken beschädigen.
2) Ohne PWM-Signal hat ein Servo fast keine Haltekraft um einem anstehenden Drehmoment entgegen zu wirken. Ist das anstehende Drehmoment sehr klein, wie z.B. bei einer Modellbahnschranke wo die Masse sehr gering ist, verhindert die innere Reibung der Servos, dass sich die Servos auch ohne PWM-Signal verstellen. Eventuell liegen die Schranken in der ZU-Stellung sogar auf Stützen auf.
3) Wird ein Zug von 2 Lokomotiven gezogen (Doppeltraktion) würde die erste Lok beim Überfahren des Reed-Kontaktes den Schranken schließen und die zweite Lok diesen sofort wieder öffnen. Durch die Auslösesperre nach Ansprechen eines Reed-Kontaktes soll genau das verhindert werden. Der Einstellwert sollte, je nach Erfordernis, im Bereich von einigen Sekunden bis zu einer Minute betragen, auf keinen Fall aber so lange, bis die erste Lok den zweiten Reed-Kontakt erreicht, der die Schranke wieder öffnen muss.
Nachtrag: Meine Servos machen nach Anlegen der Versorgungsspannung - unabhängig davon, ob ein PWM-Signal ansteht oder nicht - eine ruckartige Bewegung um einen Winkel von ca. 5 bis 10 Grad. Um dieses "Einschalthupfen" der Servos nach einem Neustart zu unterbinden, wurde der Ausgang zu den Servos (PB3) mit einem 10 kOhm Pullup-Widerstand versehen. Da die letzte Schrankenposition nicht gespeichert wird, kommt es natürlich weiterhin zu einem schnellen Anfahren der Anfangsposition, wenn diese mit der Ist-Position der Schranke nach dem Neustart nicht übereinstimmt.
Version 1.3:
//Steuerung fuer Modellbahn-Eisenbahnschranken
//Code fuer Attiny45/85 / 8 MHz
//Author Retian
//Version 1.3
//Definiere Prototypen
void fahreZu(void);
void fahreAuf(void);
void setLaufzeit(void);
#define servoPin 3 //Servo auf PB3
#define tastPinGl1 0 //Taster Gleis 1 auf PB0
#define tastPinGl2 2 //Taster Gleis 2 auf PB2
#define ledPin 1 //LED auf PB1
#define potiPin 2 //Trimmer auf PB4
#define ZU false
#define AUF true
extern volatile unsigned long timer0_millis;
//Servo-Impuls-Einstellung
const int pwmTimeZu = 2000; //Servostellung fuer ZU in Mikrosekunden (µs)
const int pwmTimeAuf = 1000; //Servostellung fuer AUF in µs
const bool freilauf = true;
//Ausloesesperre nach Ansprechen eines Reedkontaktes in ms
const unsigned int triggerLockTime = 5000; //ms
//Blink-Einstellung
const float blinkFrequenz = 2.5; //Hz
const unsigned int vorBlinkZeit = 5000; //ms
const unsigned int nachBlinkZeit = 5000; //ms
int pwmTime;
bool statusSchranke = AUF;
bool fahreSchranke = true;
int laufzeitDelay;unsigned long altBlinkZeitAuf = 0;
volatile bool freigabeGl1 = true;
volatile bool freigabeGl2 = true;
bool blinke = false;
int potiWert;
float maxZaehlWert;
void setup() {
pwmTime = (pwmTimeZu + pwmTimeAuf) / 2;
pinMode(servoPin, OUTPUT);
digitalWrite(servoPin, 0);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, 0);
pinMode(tastPinGl1, INPUT);
pinMode(tastPinGl2, INPUT);cli(); // Loesche globales Interruptflag
//Setzen der Register fuer 20 ms Timerinterrupt
TCNT1 = 0; //Loesche Timer Counter 1
TCCR1 = 0; //Loesche Timer Counter Controll Register
OCR1C = 155; //Setze Output Compare Register C
// Setze CS10, CS11 und CS13 - Clock Select Bit 10,11,13 (Prescaler 1024)
TCCR1 |= (1 << CS10) | (1 << CS11) | (1 << CS13);
//CTC-Mode ein
TCCR1 |= (1 << CTC1); // CTC-Mode (Clear Timer and Compare)
//Timer/Counter Interrupt Mask Register
TIMSK |= (1 << OCIE1A); //Setze Output Compare A Match Interrupt Enable
//Setzen der Register fuer Pin Change Interrupt
GIMSK |= (1 << PCIE); //Setze Pin Change Interrupt Enable
PCMSK |= (1 << PCINT0) | (1 << PCINT2); //Setze Pin Change Interrupt 0 und 2
//millis() auf vordefinierten Wert setzen,
//so dass nach Neustart keine Ausloeseverzoegerung auftritt
timer0_millis = triggerLockTime;
setLaufzeit();
sei(); //Setze globales Interruptflag
fahreAuf(); //Anfangsstellung Schranke offen
//Funktionsbereitschaft durch LED (Leuchtdauer 1 Sekunde)
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);maxZaehlWert = (25.0 / blinkFrequenz) + 0.5;
}
void loop() {
if ((!freigabeGl1 || !freigabeGl2) && statusSchranke == AUF)
{
blinke = true;
digitalWrite(ledPin, false);
if (millis() - altBlinkZeitAuf > 300) delay(vorBlinkZeit);
fahreZu();
}
else if (freigabeGl1 && freigabeGl2 && statusSchranke == ZU) fahreAuf();
else if (blinke && statusSchranke == AUF)
{
delay(nachBlinkZeit);
while(digitalRead(ledPin) == true); //Warte bis LED aus ist
blinke = false;altBlinkZeitAuf = millis();
}setLaufzeit();
delay(10);
}
ISR(TIMER1_COMPA_vect) //Interrupt-Serviceroutine fuer Timer-Interrupt
{
static byte zaehler = 0;
if (fahreSchranke)
{
//Setzen und Ruecksetzen des Servopins durch Registermanipulation
PORTB |= (1 << PORTB3);
delayMicroseconds(pwmTime);
PORTB &= ~(1 << PORTB3);
}
//Blinken der Warnanlage
zaehler++;
if (zaehler >= (byte)maxZaehlWert)
{
zaehler = 0;
if (blinke) digitalWrite(ledPin, !digitalRead(ledPin));
}
}
ISR(PCINT0_vect) //Interrupt-Serviceroutine fuer Pin-Change-Interrupt
{
static unsigned long time1 = 0;
static unsigned long time2 = 0;
byte tastWertGl1;
byte tastWertGl2;
tastWertGl1 = (PINB & (1 << PINB0)) >> PINB0; // Abfrage PB0
tastWertGl2 = (PINB & (1 << PINB2)) >> PINB2; // Abfrage PB2
if (millis() - time1 > triggerLockTime)
{
if (tastWertGl1 == 1)
{
freigabeGl1 = !freigabeGl1;
time1 = millis();
}
}
if (millis() - time2 > triggerLockTime)
{
if (tastWertGl2 == 1)
{
freigabeGl2 = !freigabeGl2;
time2 = millis();
}
}
}
void fahreZu()
{
fahreSchranke = true;
if (pwmTime < pwmTimeZu)
{
for (int i = pwmTime; i <= pwmTimeZu; i++)
{
pwmTime = i;
delayMicroseconds(laufzeitDelay);
}
}
else
{
for (int i = pwmTime; i >= pwmTimeZu; i--)
{
pwmTime = i;
delayMicroseconds(laufzeitDelay);
}
}
statusSchranke = ZU;
if (freilauf) fahreSchranke = false;
}
void fahreAuf()
{
fahreSchranke = true;
if (pwmTime < pwmTimeAuf)
{
for (int i = pwmTime; i <= pwmTimeAuf; i++)
{
pwmTime = i;
delayMicroseconds(laufzeitDelay);
}
}
else
{
for (int i = pwmTime; i >= pwmTimeAuf; i--)
{
pwmTime = i;
delayMicroseconds(laufzeitDelay);
}
}
statusSchranke = AUF;
if (freilauf) fahreSchranke = false;
}
//Laufzeit der Schranken
void setLaufzeit()
{
potiWert = analogRead(potiPin);
laufzeitDelay = map(potiWert, 0, 1024, 0, 2000);
}