Forum: Mikrocontroller und Digitale Elektronik [C] Programmablauf funktioniert nicht richtig - Fehlerhafter Ablauf?


von Sarah S. (magicsarah)



Lesenswert?

Hallo Leute,

anbei habe ich mein Problemkind gepackt. Die Schaltung, wie sie im PDF 
zu sehen ist, habe ich mit Ausnahme des Adapters K5 auf einem Testboard 
nachgebaut. Zur optischen Anzeige habe ich noch an den Servoausgängen je 
eine LED hängen.

Es ist soll eine Servosteuerung werden, welche recht einfach gehalten 
ist. Sie dient für meine Experimentierstrecke zur Weichensteuerung durch 
Servos. Je eine LED über den Tasten soll den Umlauf optisch anzeigen 
(Blinklicht).

Die Bedienung soll durch einfachen Tastendruck auf S2 für Linkslage, 
bzw. S3 für Rechtslage erfolgen.

Ausgangslage der Steuerung ist eine enfache Servoansteuerung, welche ich 
mir aus den Videos von Stefan Wintgen auf YouTube entnommen habe.

Wenn ich nun das System starte, werde in der Initialisierung die drei 
LED's (PB0 .. PB2) für ca. 1 sec. aktiviert und dann wieder deaktiviert. 
Dies dient der Kontrolle. Danach, nach ca. 1. sec. fangen die LED's PB1 
und PB2 an zu leuchten. Gleichzeitig habe die Kontroll-LED'S der Servo 
ein schnelles Flackern. Eine Taste wurde nicht gedrückt. Auf den 
externen Taster (PD3 / INT 1 - siehe unten) wird nicht reagiert. Ein 
angeschlossener Servo macht was er will, wer er was macht.

Der Funktionsablauf sollte allerdings so sein:

Nach dem Systemstart sollen die beiden Servos (Servo 1 und Servo 2) in 
Linkslage gehen (falls noch nicht geschehen). Wenn ich jetzt die Taste 
S3 drücke, sollen die Servos (beide) in Rechtslage wechseln. Während 
einer Zeit von ca. 5 sec. soll die LED 2 (PB1) blinken. Wenn die Weiche 
dann in Rechtslage ist, soll beim drücken der Taste S2 soll dann die 
Servos (wieder beide) in Linkslage wechseln. Gleichzeitigt soll wieder 
für ca. 5 sec. die LED 1 (PB0) blinken.

Der Taster S1, welcher an PD2 (INT0) liegt, wird erstmal nicht 
verwendet. Der externe Taster 1, welcher an PD3 (INT1) liegt, soll das 
System in den Programiermodus setzten. Der Programiermodus dient später 
dazu, die Werte für die Servoendlage fein einzustellen und ins EEPROM zu 
schreiben. Noch ist diese Funktion nicht implementiert, lediglich die 
ISR ( INT1_vect ) ist hierfür vorhanden. Sobald der MC in dem 
Programiermodus ist, soll die LED 3 (PB2) blinken und kein Umstellen der 
Servos mehr erlauben.

Der Timer0 (8-Bit) dient dazu, die Taktperiode von 25ms zu generieren. 
Da es nur ein 8-Bit ist, musste ich mir etwas einfallen lassen, um auf 
25000 Impule zu kommen.

Der Timer1 (16-Bit) dient dazu, die Impulsweite zu generieren. Dazu 
verwende ich den Timer1_CompA für den Servo 1 und den Timer-CompB für 
den Servo 2.

Was ich jetzt noch nicht implementiert habe, ist das Entprellen der 
Taster und auch das Speichern der Servoendlagenwerte im EEPROM. Daher 
arbeite ich mit festen Werten für die Servoendlage. Zum testen sollte 
dies reichen.

Ich weis, das es nicht der Weisheit letzter Schluss ist und auch der 
Code sicher noch optimiert werden kann. Und ja, es ist noch Test-Code 
drin, welcher später dann entfernt werden wird.

Weis jemand, wo ich den Fehler gemacht habe, bzw. was die Ursache ist, 
weshalb es nicht funktioniert? Woher kommt das Dauerleuchten von LED 1 
und LED 2 (PB0 und PB1)? Das Flacker der Servo-LED's könnte ich mir 
erklären, wenn der angeschlossene Servo beim Start in die Linkslage 
gehen würde und dort bleibe. Aber da er teilweis hin und her geht, kann 
da auch etwas nicht stimmen.

Liebe grüße,
Sarah

von Karl H. (kbuchegg)


Lesenswert?

Viel zu kompliziert.

Sieh dir in
Modellbauservo Ansteuerung
den letzten Codeabschnitt an.

Dort findest du Code, der maximal 8 Servos mit einem einzigen Timer 
ansteuern kann. Alles was du noch einbauen musst sind deine Taster. Da 
du sowieso für jede Endlage eine eigene Taste benutzt, brauchst du 
nichts entprellen und du brauchst auch keine Interrupts um die Tasten 
abzufragen. Alles was du tun musst, ist bei gedrückter Taste dem 
jeweiligen Servo im globalen Array den richtigen Verdrehwert zuzuweisen.


Im Beispiel wird der Timer 2 verwendet, weil sich bei 11Mhz noch 
einigermassen vernünftige Servo-Positionsabstufungen ergeben. (*) Wenn 
dir die nicht reichen, dann kann man auch den 16 Bit Timer mit einem 
anderen Vorteiler benutzen um die Auflösung zu erhöhen. Da aber der 16 
Bit Timer ein 'weretvolleres' Gut ist, als der 8-Bit Timer 2, wollte ich 
den absichtlich nicht für so etwas Profanes wie die Generierung von 
Servopulsen verwenden.

Tip: wenn du weniger als 8 Servos ansteuern willst, dann lass den Code 
trotzdem so wie er ist und verwende ganz einfach die entsprechenden 
Ausgänge nicht (bzw. steuere die in der ISR erst gar nicht an). Dann 
brauchst du dich nämlich nicht um die Wiederholfrequenz der Servopulse 
kümmern. Die ergibt sich automatisch im richtigen Zeitrahmen, einfach 
durch das hintereinander Abarbeiten von 8 Pulsen, die sich im Zeitraum 
von 1 bis 2 ms bewegen und da Servos diesem Detail der Wiederholfrequenz 
keine allzugroße Bedeutung beimessen, ist es ihnen egal, ob sich der 
Puls im Zeitraster von 8 ms aufwärts bis ca 16ms wiederholt. Stelle die 
nicht benötigten Servos auf 1.5ms ein (also Mittelstellung), übergeh sie 
in der ISR, wenn der entsprechende zugehörige Pin geschaltet werden soll 
und gut ists. 6*1.5 macht 9ms, dann noch im Mittel 3ms für die beiden 
übrig gebliebenen Servos mit dazu, macht in Summe 12ms 
Pulswiederholzeit, was im Normalfall keinerlei Probleme macht. Und wenn 
doch, dann stellst du eben die nicht benötigten Servos auf 2ms um die 
Kompletzeit für einen Durchlauf durch alle Servos in die Höhe zu 
treiben.
Diese Zeit für die Pulswiederholung ist bei der Generierung von 
Servopulsen das Unwichtigste.


(*) gerade nachgerechnet.
Bei 8 Mhz kriegst du immerhin noch 64 unterscheidbare Servo-Positionen. 
Wenn dir das reicht, dann brauchst du am angegebenen Code nicht viel 
grundsätzliches ändern, sondern nur die Anpassung an den Tiny in den 
Timer-Konfigurationsregistern machen. Die Änderungen sollten aber 
überschaubar sein, schliesslich basiert alles auf einem ganz 
gewöhnlichem Timer-Modus mit aktiviertem Compare-Match Interrupt.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

> Servo beim Start in die Linkslage gehen würde und dort bleibe.
> Aber da er teilweis hin und her geht, kann da auch etwas nicht stimmen.

Wenn ich mal annehme, dass es sich nicht um einen Programmfehler 
handelt:

Servos können Dreckschweine sein, wenn es darum geht die 
Versorgungsspannung zu verunstalten. Bei mehreren Servos und einem µC an 
derselben Stromversorgung sollte man der Qualität und der Siebung der 
Versorgungsspannung besonderes Augenmerk schenken.

von Sarah S. (magicsarah)


Lesenswert?

Hallo Karl Heinz,

erst einmal vielen dank für deine Antworten. Und auch ein kleines sorry, 
das ich mich erst jetzt melde. Auch wenn ich gerade Urlaub habe, so gibt 
es doch zu viele Dinge, die ich zu erledigen haben.

Aber nun zu deinen Antworten. Ja, es ist schon ein wenig Aufwändig, was 
ich hier entwickelt habe (auch wenn der Wurm drin ist). Für mich selbst 
ist es mein erstes wirkliches Porjekt. Und ich finde diese Konfiguration 
ganz akzeptabel. Sicherlich werde ich im Laufe der Zeit, bzw. bei den 
weiteren Projekten noch sehr viel lernen und auch sicherlich vieles 
optimieren. Aktuell aber würde ich mich gerne an diesem Aufwand halten.

Ich habe mir den Code mal angeschaut, den du mir empfohlen hast. Ist 
natürlich kein Vergleich zu meinem, zumal auch eine andere Ausgangslage 
zugrunde liegt. Aber ich gebe zu, das ich diesen Code zu adaptieren 
versuche.

Was ich aber ändern werde, ist der verwendete Timer. Da ich zum einen 
nur einen ATtiny habe, stehen mir nur ein 8-Bit und ein 16-Bit zur 
Verfügung. Und den 16'er habe ich für nichts anderes vorgesehen, wenn 
ich dieses Code zugrunde lege. In meiner Version habe ich ihn ja schon 
verwendet, weswegen ich ja auf den 8'er ausweichen musste.

Bei der Frequenz liege ich bei 8 MHz, so das ich hier bei dem 8'er nur 
64 Servostufen habe und ich nicht glaube, das diese wirklich ausreichen 
werden. Wie schon geschrieben, soll diese Schaltung zur Ansteuerung 
einer Weiche via Servo erfolgen und ich denke, das hier eine weitaus 
feiner Abstufung notwendig ist. Die Weiche soll ja auch gut anliegen und 
nicht zu weit abstehen, bzw. zu weit an die Schiene drücken soll. Ich 
möchte weder die Backenschiene überlasten, noch den Servo.

Karl Heinz schrieb:
> Wenn ich mal annehme, dass es sich nicht um einen Programmfehler
> handelt:

Was ich nicht wirklich ausschließen kann. Ich habe mich bei der 
Entwicklung an dem Code von dem Stefan Windgen orientiert und bei der 
8-Bit versucht, die einzelnen Register aus dem Datenblatt zu lesen. Da 
mein Englisch aber für die Tonne ist, kann es sein, das ich den ein oder 
anderen Registereintrag falsch gesetzt habe.

1
TCCR0B |= (1<<WGM02) | (1<<CS01);    // CTC-Modus + Prescaler = 8 (CPUFrequenz / 8 = 1MHz)
2
TIMSK |= (1<<OCIE0B);                // Timer-Interupt's für Vergleichswerte

Bei dem (1<<WGM02) bin ich nicht sicher, da ich glaube im Datenblatt 
etwas von WGM2 gelesen zu haben.

> Servos können Dreckschweine sein, wenn es darum geht die
> Versorgungsspannung zu verunstalten. Bei mehreren Servos und einem µC an
> derselben Stromversorgung sollte man der Qualität und der Siebung der
> Versorgungsspannung besonderes Augenmerk schenken.

Ja, davon habe ich schon gehört. Zumal ich auch recht günstige vom C 
verwende. Was allerdings keine pauschale Ausage sein muss.

Ich habe in Sachen Spannung/Versorgung ja auch vorgesehen, diese ein 
wenig zu entkoppeln (wenn ich das so nennen kann). Wie in der Schaltung 
zu sehen, habe ich einige Kondensatoren vorgesehen, die auch noch in der 
Größe variabel sind. Die Stromversorgung des MC ist durch eine Diode und 
einem eigenen Spannungsregler abgetrennt. Die eigentliche 
Stromversorgung ist eine kleine Platine, welche 5 V liefert, auch hier 
sind einige Kondensatoren vorgesehen, die auch noch an Kapazität 
vergrößert werden können.

Letztlich wird es der wirklich Einsatz am Modul zeigen, ob es was 
bringt.

Sobald ich den Code angepasst habe, werde ich berichten.

Liebe grüße,
Sarah

von Jürgen S. (jurs)


Lesenswert?

Sarah S. schrieb:
> Sobald ich den Code angepasst habe, werde ich berichten.

Dein Code ist wirklich viel, viel, viel zu kompliziert, wenn Du nur 3 
Tasterstellungen auswerten und danach drei Servos ansteuern möchtest.

Im Endeffekt brauchen die Servos nur alle 20 Millisekunden (=20000µs) je 
einen Refresh-Impuls mit einer Dauer zwischen ca. 1500 und 2500 µs.

Solange nichts anderes zu steuern ist, brauchst Du kaum mehr als das von 
Karl Heinz verlinkte Beispiel "Signalerzeugung für 1 Servo" auf drei 
Servos aufzubohren.

Pseudocode für die Hauptschleife:
- int servotime[3];
- Wenn Taster1 unten servotime[0]=1500; else servotime[0]=2500;
- Wenn Taster2 unten servotime[1]=1500; else servotime[1]=2500;
- Wenn Taster3 unten servotime[2]=1500; else servotime[2]=2500;
- Ersten Servo HIGH
- _delay_us(servotime[0]);
- Ersten Servo LOW
- Zweiten Servo HIGH
- _delay_us(servotime[1]);
- Zweiten Servo LOW
- Dritten Servo HIGH
- _delay_us(servotime[2]);
- Dritten Servo LOW
- _delay_us(20000-servotime[0]-servotime[1]-servotime[2]);

Und damit ist ein 20ms Intervall voll und Du fängst von vorne an.

: Bearbeitet durch User
von Sarah S. (magicsarah)


Lesenswert?

Jürgen S. schrieb:

Servus Jürgen,

> Dein Code ist wirklich viel, viel, viel zu kompliziert, wenn Du nur 3
> Tasterstellungen auswerten und danach drei Servos ansteuern möchtest.

Letztlich sind es nur zwei Taster und auch nur 2 Servos. Also noch 
weniger Code, als du unten vorgeschlagen hast. Eventuell kommt noch eine 
zweite Option dazu, wo jeder Servo mit je einem Tasten gestellt wird.

>
> Im Endeffekt brauchen die Servos nur alle 20 Millisekunden (=20000µs) je
> einen Refresh-Impuls mit einer Dauer zwischen ca. 1500 und 2500 µs.
>
> Solange nichts anderes zu steuern ist, brauchst Du kaum mehr als das von
> Karl Heinz verlinkte Beispiel "Signalerzeugung für 1 Servo" auf drei
> Servos aufzubohren.

Nein, bis auf die beiden Servos und je eine LED pro Seite gibt es nicht 
viel, was zu machen ist im Regelbetrieb. Der geplante Programmiermodus 
ist davon unabhängig.

Eventuell kommt noch die dritte Tase ins Spiel, welche als "Not-Aus" 
angedacht war. Da müsste ich aber noch wissen, wie ich einen Servo in 
einem Umlauf, der doch recht kurz ist, sofort anhalten kann. Aber das 
ist jetzt nicht wichtig, sondern lediglich eine Option.

>
> Pseudocode für die Hauptschleife:
> - int servotime[3];
> - Wenn Taster1 unten servotime[0]=1500; else servotime[0]=2500;
> - Wenn Taster2 unten servotime[1]=1500; else servotime[1]=2500;
> - Wenn Taster3 unten servotime[2]=1500; else servotime[2]=2500;
> - Ersten Servo HIGH
> - _delay_us(servotime[0]);
> - Ersten Servo LOW
> - Zweiten Servo HIGH
> - _delay_us(servotime[1]);
> - Zweiten Servo LOW
> - Dritten Servo HIGH
> - _delay_us(servotime[2]);
> - Dritten Servo LOW
> - _delay_us(20000-servotime[0]-servotime[1]-servotime[2]);
>
> Und damit ist ein 20ms Intervall voll und Du fängst von vorne an.

Einfaches Prinzip, was mir sogar gut gefällt. Karl Heinz hatte ja den 
Code von den 8 Servos ins Spiel gebracht, was natürlich umfangreicher 
wäre, als dein Vorschlag. Wenn man mal von dem Beiwerk wie LED's blinken 
lassen, verzögerungen einbauen, gegenläufige Umstellung verhindern, etc. 
absieht.

Liebe grüße,
Sarah

von Jürgen S. (jurs)


Lesenswert?

Sarah S. schrieb:
> Wenn man mal von dem Beiwerk wie LED's blinken
> lassen, verzögerungen einbauen, gegenläufige Umstellung verhindern, etc.
> absieht.

Der Code dauert pro Umlauf ziemlich genau 20 Millisekunden. Durch 
einfaches Aufaddieren von 20 pro Durchlauf auf einen beispielsweise 
unsigned long, erhältst Du einen Millisekundenzähler mit einer Auflösung 
von 20 Millisekunden.

Solange die Aktionen, die Du zwischendurch noch durchführen möchtest, 
z.B. Blinken an LEDs, recht schnell angesteuert werden im Vergleich zu 
einem 20ms Intervall, und solange keine höhere zeitliche Auflösung als 
diese ca. 20ms  verlangt wird, läßt sich das dann wunderbar über den 
Stand des Millisekunden-Zählers abstoppen, z.B. eine LED nach Bedarf 
jeweils 0,58s ein zu schalten dann wieder für 0,42s aus zu schalten. 
Oder wie auch immer.

Da in meinem Pseudocode Eingabe- und Ausgabe bereits getrennt sind, 
kannst Du durch Logikbedingungen, die Du nach dem Abfragen der Taster 
aber vor dem Ansteuern der Servos einfügst, auch ganz leicht unsinnige 
Tasterstellungen ausfiltern.

Mit dem oben skizzierten Pseudocode bekommst Du sowohl die 
Servorefresh-Impulse alle (circa) 20 Millisekunden sehr exakt zeitlich 
gesteuert, UND darüber bekommst Du einen 20-Millisekundentakt, den Du 
einfach nur aufaddieren brauchst, um weitere, langsame Aktionen zu 
steuern.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.