Hallo! Mein Programm soll: -RC Signal eines Kanals auswerten -I2C einlesen -eine ADC Wandlung durchführen -PID berechnen -an ein LCD ausgeben -Zeit vertrödeln (hierauf bezieht sich meine Frage) -Ergebnis der PID Regelung an Servos ausgeben Da die Vorgänge unterschiedlich lange dauern müsste der Punkt "Zeit vertrödeln" sich so verändern das ein "Durchlauf" immer gleich lange dauert und somit auch die Abtastrate der PID Regelung bildet. Wie mache ich das am geschicktesten? Ich überlege alles in die ISR eines Timers zu packen bin aber nicht sicher ob es der richtige Ansatz ist? Vielen Dank!
Hallo Attila, ich mache das meistens so, dass ich eine Zeitscheibe für die langsamen Prozesse definiere, so dass die Summe aller Ausführungszeiten unter meiner Zeitscheibe, sagen wie 5ms liegt. Alles was schnell abgearbeitet werden muss, z.B. Uartdaten in eine Puffer schreiben und daraus lesen, wird über die zugehörigen Interruptroutinen realisiert. Am langsamsten wird bei die die LCD Ausgabe sein, speziell, wenn man Clear Screan oder Cursor Home verwendet.
1 | bool_t timer_tick = false; |
2 | |
3 | int main(void) { |
4 | // init timer
|
5 | |
6 | while(1) // main - loop |
7 | {
|
8 | // schnelle ereignisorientierte Prozesse
|
9 | if (event_uart) { |
10 | |
11 | }
|
12 | |
13 | // zyklische Prozesse
|
14 | if (timer_tick) { |
15 | timer_tick = 0 |
16 | // Prozess 1
|
17 | // Prozess 2
|
18 | // Prozess 3
|
19 | // usw.
|
20 | } // endif |
21 | } // while |
22 | |
23 | return 0; |
24 | }
|
Nach dem Schema: ISR Set Flag xxmS Set Flag yymS ... Main If Flag xxmS ... If Flag yymS ... ...
Attila Ciftci schrieb: > Ich überlege alles in die ISR eines > Timers zu packen Jo. Wenn in einem Durchlauf für alle Funktionen nicht die Zeit eines IRQs überschritten wird. Gruß Jobst
Naja Jobst, das ist doch nicht gut, so kann man auf externe Ereignisse, die sich per Interrupt ankündigen, nicht mehr reagieren. Und man verliert evtl. auch einige Events. Für Interrupt-Routinen gilt weiterhin, so wenig Code wie möglich, so viel wie absolut nötig. Z.B. in einer Uartbearbeitung das neue Zeichen in ein FiFo zu speichern, incl. der Fifo-Index-/Zeigerverwaltung.
Hört sich nach einem Betriebsystem an was du suchst, oder wenigstens den scheduler davon
Uwe S. schrieb: > so kann man auf externe Ereignisse, die sich per > Interrupt ankündigen, nicht mehr reagieren. Wieso sollte man nicht können? Ich mache sowas ... Ich unterbreche Interrupts auch durch Interrupts - Wenn es nötig sein sollte. Uwe S. schrieb: > Für Interrupt-Routinen gilt weiterhin, so wenig Code wie möglich, so > viel wie absolut nötig. Naja, das ist aber nur eine grobe Faustformel. Ich kenne Projekte, wo das ganz anders ist ... Gruß Jobst
Hallo Jobst, klar kann man, wenn man alles über die AVRs und den AVR GCC weiss, aber der TO benötigt doch erst mal Starthilfe und keine Sonderfälle. Oder wie sieht Du das ?
Leute! Vielen Dank für den Input. Ich fummel mal etwas damit herum und melde mich wenn ich nicht weiterkomme! Da ich das Auslesen der RC Signale so gestaltet habe das solange gezählt wird wie die Pins high sind und somit im schlechtesten Fall 4,5 ms (3 Servos) rumgetrödelt wird, muss ich mich erst mal darum kümmern bevor ich eure Ansätze durchprobieren kann. Vielen Dank!
Ein RC-Signal ist ein Puls mit 1,5-2,5ms Dauer. Den kann man SPIELEND nebenbei mit der Input Capture Funktion und dem dazugehörigen Interrupt messen.
Hallo Falk, ja sicher, wenn mein 16bit Timer nicht schon damit beschäftigt wäre Signale zum steuern von Servos zu bauen. Aber mal sehen, vielleicht schaffe ich es ja ihm zu erklären das er beides abwechselnd machen soll! :-)
Mit etwas geschickter Programmierung kann der 16 Bit Timer beides gleichzeitg tun, zumal es ja in beiden Fällen um den gleichen Signaltyp geht (Servosignal).
Ok es geht dann wohl nicht ohne Hilfe: Wie erkläre ich meinem Timer das er erst das eine und dann das andere machen soll? Im Moment macht er dies um 2 Servos (möglich sind 4) zu steuern: ISR (TIMER1_COMPA_vect) { static int channel=0; channel=channel+1; switch(channel) { case 1: PORTB=0b00000001; OCR1A=ocr[0]; break; case 2: PORTB=0b00000010; OCR1A=ocr[1]; break; case 3: PORTB=0b00000000; break; case 4: PORTB=0b00000000; channel=0; break; default: PORTB=0b00000000; } }
@ Attila Ciftci (attila) >Wie erkläre ich meinem Timer das er erst das eine und dann das andere >machen soll? >Im Moment macht er dies um 2 Servos (möglich sind 4) zu steuern: Was hast du denn für einen Prozessor? Die größeren haben 2 oder mehr OCR Einheiten am 16 Bit TImer, da kann man die Servosignale rein in Hardware generieren.
Man soll ja laut Forenregeln den Prozessor im Betreff angeben ;-) ;-) ;-) Es ist ein Atmega 8
Oh, da hab ich wohl den Wald vor lauter Bäumen nicht gesehen 8-0 Naja. Trotzdem die Frage, an welchen Pins deine Servos hängen? ZWEI Kanäle kann auch der ATmega8 per Hardware mit minimaler CPU-Last erzeugen, nämlcih OCR1A und OCR1B. Man muss nur den Vorteiler passend einstellen, damit das alles passt. Wenn man das so macht, hat man auch den Timer für die ICP Funktion ganz normal zu Verfügung. Wie das dann geht, siehe hier. High-Speed capture mit ATmega Timer
Falk! Du siehst den Wald tatsächlich nicht vor lauter Bäumen! :-) :-) :-) Schau mal auf meinem Codeschnipsel :-) :-) :-) Es sind PB0 und PB1. Das werde ich jetzt ändern und dann an deinem Vorschlag arbeiten wie auch den Artikel lesen. Habe ich das so richtig verstanden: -Timer für die Servos configurieren -Timer an nach einem Durchlauf -Timer aus Timer für Input capture configurieren -Timer an nach einem Durchlauf -Timer aus Ist das so richtig? Und wie erkläre ich dem Timer das er bei steigender Flanke loszählen soll? Mit INT0? Am Port D hängt aber mein LCD dran :-( Nichts für ungut mit dem Wald! :-)
@ Attila Ciftci (attila) >Falk! Du siehst den Wald tatsächlich nicht vor lauter Bäumen! :-) :-) >:-) Schau mal auf meinem Codeschnipsel :-) :-) :-) Es sind PB0 und PB1. Ja und? Ich hab irgendwann einfach keine Lust mehr auf Raten, Glaskugel lesen und Reverse Engineering. DU willst was von Forum, nicht umgekehrt. Siehe Netiquette. >Habe ich das so richtig verstanden: Nein. >-Timer für die Servos configurieren >-Timer an Ja. >nach einem Durchlauf >-Timer aus NEIN! Der läuft dauerhaft! OCR und ICP funktionieren PARALLEL! >Ist das so richtig? Und wie erkläre ich dem Timer das er bei steigender >Flanke loszählen soll? Gar nicht. Bei ICP wird der Zählerstand automatisch von der Hardware in ein Register kopiert. Im Interrupt ein paar Mikrosekunden später kopiert man das in eine Variable und konfiguriert die aktive Flanke des ICP um. Beim nächsten Interrupt kann man die Zählerdifferenz bilden und hat die gesuchte Pulsbreite. Dann wieder ICP Flanke umkonfigurieren und das Spiel beginnt von vorn. > Mit INT0? Am Port D hängt aber mein LCD dran :-( Für ICP MUSST du zwingend den direkt dafür vorgesehenen Pin nutzen, das geht nicht anders. Das LCD kann man mit jeder beliebigen Pinkompbination ansteuern, selbst wenn die über alle Ports verstreut wären. Peter Fleury hat das vor Jahren vorgemacht.
Vielen Dank Falk! Und sorry noch mal: War echt nicht bös gemeint! :-)
Hallo Falk Also ich habe jetzt herumprobiert und bin zu dem Schluss gekommen das das so nicht geht wie Du das vorschlägst. Wahrscheinlich ist das aber falsch und ich bin nur zu blöd. Es kommt doch nur der normal mode des 16bit Timers in Frage, richtig?
@Attila Ciftci (attila) >Also ich habe jetzt herumprobiert und bin zu dem Schluss gekommen das >das so nicht geht wie Du das vorschlägst. Doch, aber es ist noch ganz trivial. > Wahrscheinlich ist das aber falsch und ich bin nur zu blöd. Das hast du gesagt ;-) >Es kommt doch nur der normal mode des 16bit Timers in Frage, richtig? Ja. Man muss die beiden Servosignale über die Output Compare Funktion zeitversetzt erzeugen. Damit kann man nacheinander im OCRx Interrupt die Register jeweils neu laden. Parallel dazu läuft der ICP Interrupt und kümmert sich um die Pulsmessung. In etwa so (nur als Skizze)
1 | volatile uint16_t servo1, servo1_pause, servo1_gap; |
2 | volatile uint16_t servo2, servo2_pause, servo2_gap; |
3 | volatile uint16_t rc_input_width; |
4 | |
5 | ISR (TIMER1_COMPA_vect) { |
6 | if (TCCR1A & (1<<COM1A0)) { // steigende Flanke aktiv |
7 | OCR1A += servo1; |
8 | TCCR1A &= ~(1<<COM1A0); // Umschalten auf fallende Flanke |
9 | } else { // fallende Flanke aktiv |
10 | OCR1A += servo1_pause; |
11 | OCR1B = OCR1A + servo2_gap; |
12 | TCCR1A |= (1<<COM1A0); // Umschalten auf steigende Flanke |
13 | }
|
14 | }
|
15 | |
16 | ISR (TIMER1_COMPB_vect) { |
17 | if (TCCR1A & (1<<COM1B0)) { // steigende Flanke aktiv |
18 | OCR1B += servo2; |
19 | TCCR1A &= ~(1<<COM1B0); // Umschalten auf fallende Flanke |
20 | } else { // fallende Flanke aktiv |
21 | OCR1B += servo2_pause; |
22 | OCR1A = OCR1B + servo1_gap; |
23 | TCCR1A |= (1<<COM1B0); // Umschalten auf steigende Flanke |
24 | }
|
25 | }
|
26 | |
27 | ISR (TIMER1_CAPT_vect) { |
28 | static uint16_t time_rise; |
29 | |
30 | if (TCCR1B & (1<<ICES1)) { // steigende Flanke aktiv |
31 | time_rise = ICR1; |
32 | TCCR1B &= ~(1<<ICES1); // Umschalten auf fallende Flanke |
33 | } else { // fallende Flanke aktiv |
34 | rc_input_width = ICR1-time_rise; |
35 | TCCR1B |= (1<<ICES1); // Umschalten auf steigende Flanke |
36 | }
|
37 | |
38 | TIFR = (1<<ICF1); // Flag nochmal loeschen, wegen Umstellung der Flanke |
39 | }
|
Deine Aufgabe ist es jetzt, den zeitliche Ablauf der Servosignalerzeugung mal aufzuzeichnen und die Zeiten, welche oben im Quelltext verwendet werden, einzuzeichnen. Damit erkennst du dann auch, wie diese Zeiten berechnet werden müssen. Wenn du fertig bist, solltest du ein Bild deines Zeitablaufs incl. Beschriftung hier senden. Viel Erfolg!
Ein Zeiter kann bequem, mehrere Aktionen ausführen. Ein einfaches Beispiel mit Einzelereignissen. volatil int Aktion1 = 0; volatil int Aktion2 = 0; volatil int Aktion3 = 0; ISR ZaehlRunter () { If Aktion1 { --Aktion1; If !Aktion1 { HauRein1(); // Reload wenn zyklisch gearbeitet wird } } If Aktion2 { --Aktion2; If !Aktion2 { HauRein2(); } } If Aktion3 { --Aktion3; If !Aktion3 { HauRein3(); } } // von ISR() In main () { ... Aktion1 = 50; // Warte 50 Ticks und hau einen Drauf // one reload }
Hallo Falk, dein Vorschlag klappt ausgezeichnet! Ich habe deinen Code für den Input Capture so übernommen und es funktioniert. Vielen Dank dafür! Hätte ich selber so nicht hinbekommen! Die Steuerung der Servoc habe ich allerdings so gelöst und ich hoffe es spricht da nichts gegen. Funktionieren tut es auf jeden Fall: ISR (TIMER1_OVF_vect) { PORTB |=(1<<PB1)|(1<<PB2); } ISR (TIMER1_COMPA_vect) { PORTB &= ~(1<<PB1); } ISR (TIMER1_COMPB_vect) { PORTB &= ~(1<<PB2); } Es hat den Vorteil das ich mir die Pins an denen das Signal anliegt aussuchen kann und ich glaube mit etwas gefummel könnte man auch mehr als 2 Servos anfahren. Ist aber noch unausgegoren.
Ein LCD muss man nicht am Stueck ansteuern. Ich lass das auch im Main auf den Tick von 10ms machen. Ein byte aufs mal an den LCD. Gesteuert durch einen Zustandsmaschine.
@ Attila Ciftci (attila) >dein Vorschlag klappt ausgezeichnet! Ich habe deinen Code für den Input >Capture so übernommen und es funktioniert. Schön. >Die Steuerung der Servoc habe ich allerdings so gelöst und ich hoffe es >spricht da nichts gegen. Doch. > Funktionieren tut es auf jeden Fall: Aber nicht reibungslos! Denn das Problem ist hierbei, dass je nach Zufall und Signallage die Interrupts sich gegenseitig verzögern, und somit ein Jitter in deinen Servosignalen entsteht. Gute Servos folgen dem und zittern dann auch, mal mehr mal weniger. >Es hat den Vorteil das ich mir die Pins an denen das Signal anliegt >aussuchen kann Sicher, aber 2 Pins mit Draht umlegen ist besser. >und ich glaube mit etwas gefummel könnte man auch mehr >als 2 Servos anfahren. Ist aber noch unausgegoren. Kann man, aber dazu braucht es noch etwas mehr Grips. Der Trick ist, dass die Servopile zeitversetzt erzeugt werden. Dann reicht auch ein OCR Interrupt und etwas Logik. Is aber im Endeffekt der gleiche Ansatz wie du jetzt schon hast.
Falk Brunner schrieb: > Kann man, aber dazu braucht es noch etwas mehr Grips. Der Trick ist, > dass die Servopile zeitversetzt erzeugt werden. Dann reicht auch ein OCR > Interrupt und etwas Logik. Wurde bei den Fernsteuerungen ursprünglich auch so gemacht, man hatte ja nur einen Funkkanal, da gingen die Pulse der x Fernsteuer-Kanäle nacheinender auf den Sender, und der Empfänger hat die dann nur nacheinender auf die einzelnen Ausgänge geschaltet, deshalb hat man den Impuls ja auch deutlich kürzer gemacht, als die Periodendauer. Bei manchen Empfängern kann man das als sogenanntes 'Summensignal' abgreifen, wenn man die Daten mehrerer Kanäle weiterverarbeiten möchte, z.B. in einem Qadrokopter. Mit freundlichen Grüßen - Martin
Hallo! Ein kurzer Nachtrag: Dank google und ebenda ein paar Freaks habe ich folgendes gefunden: "Der Mega48 macht nur die Kanalbildung und der Mega169 die Aufbereitung des PCM Signals. Reden über Uart miteinander. Format: 115200,8 N,1. LSB first was fürn Zufall." Und genau da habe ich meinem RC Empfänger angezapft und lese jetzt alles per UART ein. Genial! Ich kann alle RC Kanäle einlesen UND mit meiner ursprünglichen Verwendung des 16bit Timers zig Servos ansteuern! :-)
Hallo! Da habe ich mich deutlich zu früh gefreut und komme hier leider nicht weiter: Jetzt ist es so das der RC Empfänger zwar ungefähr alle 20ms die Daten per UART raustut aber eben leider nicht gleichmäßig. Meine einzige Idee ist dem Controller ein 30ms Zeitfenster zu geben um die Daten abzuholen und beim nächsten 30ms Zeitfenster den Rest abzufeiern. Da kann ich aber die Signale für die Servos auch "zu Fuss" mit delays bauen so uneffizient wie der Ansatz ist. Wer hat einen Rat? Danke!
Da es höflich ist die threads auch "abzuschliessen": Ich lese die UART Daten jetzt per Interrupt aus. Es war nicht schlau 2 ms auf den Empfang zu warten. Jetzt bin ich "unabhängig" von der Taktung der Ausgabe beim RC Empfänger. Ich schalte den 16bit Timer für die Servos im Overflow Interrupt des a 8 bit Timers an und am Ende des 16 bit OCRA interrupts wieder aus. Im Moment gebe ich also mit einem Konstanten Intervall Updates an die Servos und hoffe so eine Zeitkonstante für eine noch zu implementierende PID Regelung zu haben! Velen Dank an alle, ich habe mal wieder sehr viel gelernt!
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.