PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Mehrere Servo-Signale einlesen, ggf. manipulieren, ausgeben



Bomberpilot
17.12.2014, 12:54
Tag zusammen,

ich hänge momentan an einem kleinen Problem.
Ich möchte von einem RC-Empfänger, der mir leider kein Summensignal liefert (zumindest mit dem Oszi keins gefunden (EK2-0424))
alle 6 Servo-Signale mit einem AVR erfassen.
Diese sollen dann je nach Verwendungszweck manipuliert werden (Mischer, Teilautomatik, etc.) und dann wieder als 6 Servo-Signale ausgegeben werden.

Die Ausgabe erfolgt momentan per Timer1 im CTC Mode mit einer Frequenz von 50Hz (20ms Paketlänge des Servo Signals).
Nur die Eingabe macht mir etwas Probleme. Sobald sich nämlich Einlesen und die Ausgabe Interrupt mäßig in die Quere kommen, führt dies logischerweise zu starkem Jitter auf den Servo Kanälen, da das Timing nicht mehr stimmt.

Hat jemand von euch eine Idee wie ich das lösen könnte, oder sowas ähnliches schon mal versucht.

Grüße Basti

Searcher
17.12.2014, 13:38
Hallo,
6 Servosignale einlesen und ausgeben mit einem AVR wird sehr kritisch. Hier was mit 3 Signalen:
https://www.roboternetz.de/community/threads/63750-Mehrere-RC-Signale-einlesen-und-mehrere-Servos-ausgeben?p=592054&viewfull=1#post592054

plus thread von vorne :)

Gruß
Searcher

PICture
17.12.2014, 15:34
Hallo!


Ich möchte von einem RC-Empfänger, der mir leider kein Summensignal liefert (zumindest mit dem Oszi keins gefunden (EK2-0424))alle 6 Servo-Signale mit einem AVR erfassen.

Per Funk werden die Impulse angeblich in gleicher Reihenfolge seriel nacheinander gesendet und im RC-Empfänger müssten sie schon alle richtig verteilt sein. :confused:

Bomberpilot
17.12.2014, 15:52
Per Funk werden die Impulse angeblich in gleicher Reihenfolge seriel nacheinander gesendet und im RC-Empfänger müssten sie schon alle richtig verteilt sein. :confused:

Ich hätte villt erwähnen sollen, dass es sich um einen 2,4GHz Empfänger handelt *KopfaufTisch*

Der Emfänger IC ist ein nRF2401
https://www.sparkfun.com/datasheets/RF/nRF2401rev1_1.pdf

Und danach gehen die Daten digital (meine ich zumindest dem Datenblatt des nRF zu entnehmen) an einen Controller: C8051F330 von Silicon Labs
https://www.silabs.com/Support%20Documents/TechnicalDocs/C8051F33x.pdf

Also meiner Meinung nach keine Stelle, an der ich ein Summensignal hätte.


@Searcher
Danke, diesen Thread habe ich bereits gelesen.
Das es mit 6 Kanälen recht Zeitkritisch wird, habe ich mir fast gedacht.

Villt bringt es was, die Ausgabe Frequenz auf 25Hz zu reduzieren, dadurch wird ein Servosignal 40ms lang, da dürfte sich doch dann eine genügend Lange Pause ergeben, um mal eben die Eingänge zu sampeln? Oder ist dieser Ansatz totaler Mist?

Gruß Basti

PICture
17.12.2014, 15:58
Sorry, ich habe dein Problem so verstanden, dass du alle Servoimpulse einzeln einlesen möchtest._.

Bomberpilot
17.12.2014, 16:07
Sorry, ich habe dein Problem so verstanden, dass du alle Servoimpulse einzeln einlesen möchtest._.

Da hast du mich schon richtig verstanden, denn mangels Summensignal, in dem alle Servosignale zusammen liegen bleibt mir leider nur die Möglichkeit 6 getrennte Kanäle einzulesen. Aber eben auf 6 einzelnen Controller Pins. Und das wird mit der Ausgabe, zeitlich schon gewaltig eng.

Peter(TOO)
17.12.2014, 16:19
Hallo Basti,

Hat jemand von euch eine Idee wie ich das lösen könnte, oder sowas ähnliches schon mal versucht.
Die Frage ist ob alle Servo-Impulse gleichzeitig ausgegeben werden oder zeitlich versetzt?

Der Servo-Impuls ist maximal 3ms breit. Ursprünglich wurde im Zeitmultiplex gesendet. So in der Art:
0ms Kanal1
5ms Kanal2
10ms Kanal3
15ms Kanal4
20ms Kanal5
25ms Kanal6
30ms Kanal7
40ms Kanal1
...

Zwischen Kanal 7 und 1 fehlt ein Kanal, dadurch kann Kanal1 identifiziert werden.
Entsprechend waren die Impulse an den Servos auch entsprechend zeitlich versetzt.

Also miss das mal.

MfG Peter(TOO)

Bomberpilot
17.12.2014, 16:26
Hatte ich schon mal gemessen, und auch versucht mit Diodenmatrix wieder ne Art Summensignal aus den Kanälen zu generieren.

Die Kanäle kommen zeitlich versetzt, aber direkt nacheinander, ohne Pause!
Wenn Kanal 1 wieder auf LOW zieht geht exakt im selben Moment Kanal 2 schon auf HIGH. Hinter der Diodenmatrix hatte ich dann ein langes Signal.

Gruß Basti

oberallgeier
17.12.2014, 16:34
... RC-Empfänger, der ... kein Summensignal liefert ... 6 Servo-Signale ... erfassen ... manipuliert ... als 6 Servo-Signale ausgegeben ...Hi Basti,

So etwas hab ich noch nie versucht. Aber wenn ich das so überlege: der Stress entsteht beim Auslesen, weil Du da im worst case alle Signale gleichzeitig oder so bekommst. Und gleichzeitig einlesen geht ja nu nich - oder doch ?? *ggg*

Hintergrund: ich habe bei (wenigen) Servos die Auflösung gemessen (https://www.roboternetz.de/community/threads/61379-Kopfsache-und-ein-m1284-etliche-Servos-viel-Alu?p=583165&viewfull=1#post583165), die liegt bestenfalls um die 4 .. 5 µs, Ähnliches steht in Herstellerangaben.

Also schlage ich vor:
- Ein controller, der die Eingangssignale an EINEM Port hat, z.B. Mega328 - Port B (am D hängt der Quarz :-/ )
- 20 MHz Takt - das kann der 328er, meine jedenfalls laufen so primstens.
- Eine schnelle Timerroutine, vielleicht 10 µs, die NUR den Port einliest, das ist EIN CPU-Takt, dazu noch ne Zeitmarke hochtickert und abspeichert. Damit wirst Du mit Overhead von den 200 verfügbaren Takten höchstens 50 für die ISR brauchen (nur meine Schätzung). Leider ist das nur die halbe theoretische Auflösung des Servos - aber schneller gibts dann sicher Zeitstress für den "Rest".
- Du erhältst also ALLE Servosignale die innerhalb einer Zeitspanne von 10µs umschlagen.
- Ausserhalb dieser ISR wird der jeweilige Portumschlag gesucht und mit Zeitmarke abgespeichert.
- Nu kannst Du auswerten - und hast dazu reichlich Zeit. Wieviel? Mal sehen:
- 6 Servosignale belegen maximal 6 x 2 ms, also 12 ms - von den standardmässigen 20. Servopuls setzen und löschen braucht höchstens die Hälfte für "alles" - siehe im Link und ähnlichen Threads ne wirtschaftliche Servoansteuerung. Und da denke ich eben bleibt genug Zeit für Deine Signalmanipulation (nicht geschätzt - nur erhofft).
- Sinn dürfte es machen die ISR´s zur Servo-Ansteuerung als nested Interrupts laufen zu lassen, damit Du Deine Einleseroutine nie ausbremst !!!

Funktion, Machbarkeit und Richtigkeit wird von mir nicht gewährleistet.

Peter(TOO)
17.12.2014, 16:38
Hallo,

Die Kanäle kommen zeitlich versetzt, aber direkt nacheinander, ohne Pause!
Wenn Kanal 1 wieder auf LOW zieht geht exakt im selben Moment Kanal 2 schon auf HIGH. Hinter der Diodenmatrix hatte ich dann ein langes Signal.
Wirklich gleichzeitig? Oder sind da nicht ein paar CPU-Takte als Lücke vorhanden.


Dann halt mit 2 Matrixen:
Matrix1: Kanal 1,3 und 5
Matrix2: Kanal 2,4 und 6

Dann bräuchte man nur noch 2 Timer, welche die Impulsbreite messen können!

Dazu kenne ich aber die AVR zu wenig, müsste ich erst mal im Datenblatt nachsehen.

MfG Peter(TOO)

Bomberpilot
17.12.2014, 16:59
@oberallgeier
Ich führe mir das nach dem Abendessen nochmal genauer zu gemüte und denk drüber nach, klingt aber nach einen Sinnvollen Ansatz

@Peter
Ich vermute wirklich gleichzeitig, das Analog Oszi sieht nicht das kleinste Zucken.
Aber - Man ohhh Man, wieso bin ich da nicht selber drauf gekommen - 2 Matrixen...
dann wären es wirklich erst mal nur 2 Kanäle zum auswerten, deren Signale sich ja auch NIE "über"schneiden sondern nur aneinander anschließen.

Gruß Basti

EDIT:
@Oberallgeier
Die Idee an sich ist nicht schlecht - Bauchschmerzen macht mir die Tatsache, das mir dann alle 10uS ein Interrupt geworfen wird, wovon so ca 99,9% sinnlos passieren, da sich die Zustände in einem Zyklus ja nur 12 mal ändern.

@Peter
Aber da kommt mir mit der 2 Matrix Methode eine geniale und einfache Idee.

Beide Signale kommen an Interrupt Eingänge.
Ein Timer läuft einfach vor sich hin. Bei Überlauf wird KEIN Interrupt geworfen. Bzw ein Überlauf könnte auf Signalverlust hinweisen (Fail-Safe Funktion)

-INT0 is aktiv und wartet auf High Flanke
-INT1 is deaktiviert
-Kommt nun ein Signal an INT0 wird der Timer auf 0 gesetzt INT0 deaktiviert und INT1 aktiviert
-Bekommt dann INT1 sein Signal wird der Timerwert gespeichert (=Länge Kanal 1), der Timer auf 0 gesetzt, ein Kanal Zähler erhöht und INT1 deaktiviert + INT0 wieder aktiviert.
-Kommt nun wieder auf INT0 ein Signal wieder Wert speichern, Timer Reset, Kanal Zähler erhöhen, INTs tauschen usw...

-Wenn dann der Kanalzähler auf 6 steht, wird der Interrupt nicht getauscht sondern der aktuelle auf Fallende Flanke umkonfiguriert, gibt ja keine weitere High Flanke mehr in diesem Zyklus.
-Wenn diese LOW Flanke dann kommt, wieder Wert speichern, und alles auf Anfang:
INT0 aktiv
INT1 deaktiv
Kanal Zähler 1

Ping Pong Prinzip Quasi...

müsste doch klappen oder nicht?

Vorteil:
Es wird wirklich nur ein Interrupt geschmissen wenn auch wirklich was passiert.

Gruß Basti

Searcher
17.12.2014, 18:04
Hallo,

Villt bringt es was, die Ausgabe Frequenz auf 25Hz zu reduzieren, dadurch wird ein Servosignal 40ms lang, da dürfte sich doch dann eine genügend Lange Pause ergeben, um mal eben die Eingänge zu sampeln? Oder ist dieser Ansatz totaler Mist?
40ms empfinde ich als zu lang. Das geht auf jedenfall auf die Performance von analogen Servos. Ob digitale so etwas überhaupt mitmachen weiß ich nicht. In ein paar ms Unterschied (vielleicht +-5ms) zu 20ms würd ich kein Problem sehen.

Eine so große Abweichung von +20ms würde ich planmäßig so ziemlich als letztes in Betracht ziehen sondern erstmal schauen, ob man nicht irgendwie den Standard erreichen kann. Gab ja schon interessante Beiträge.

Gruß
Searcher

stardust19322
17.12.2014, 18:48
Hallo.

Ich verfolge das Thema und habe da ein paar Fragen:

Das Servo-Signal ist max. 3ms lang? Was hast du denn da für Servo-Signale anliegen? Die kann kein Servo auswerten. Üblicherweise sind Servo-Signale zwischen 1ms und 2ms lang, Mittelstellung bei 1,5ms. Der Drehbereich kann dabei auch mal auf 0,8ms bis 2,2ms erweitert werden, aber 3ms erzeugt bei mir erst einmal Fragezeichen. Sicher, dass du das Signal korrekt ausgewertet hast?

Eine weitere Frage: Gibt es deiner Meinung nach denn einen Unterschied in der Datenpaket-Übertragung zwischen 40MHz und 2,4GHz? Meiner Meinung nach nicht wirklich. Die Signal-Intervalle liegen weiterhin an jedem Kanal bei ca. 20ms, egal ob 27MHz, 35MHz, 40MHz oder 2,4GHz.

Was das Auswerten der Signale am Eingang und die Ausgabe am Ausgang betrifft: Die Daten werden in fertigen Paketen von der Fernsteuerung an den Empfänger gesendet. Eventuell mal alle Kanäle mit dem Oszi durchmessen, ob die PWM-Signale einzeln abgearbeitet werden oder simultan. Wenn sie nacheinander abgefertigt werden, solltest du eigentlich genügend Zeit für weitere Aufgaben haben.


LG - Maik

Slowly
17.12.2014, 19:08
Zu dem Thema ist hier eine sehr schöne Applikation für einen Basic Tiger, mit guten Erklärungen die sich auf andere Systeme und Anwendungen übertrage lassen. Das habe ich auch schon mal nachgebaut und es funktioniert Spitze.
https://wilke.de/fileadmin/templates/Bilder/success/pdf_komplett/Applikationsbericht_049_WilkeTechnology.pdf

Bomberpilot
17.12.2014, 19:51
Das Servo-Signal ist max. 3ms lang?
Hat doch niemand behauptet, oder?

Naja bei meinem 2,4GHz Empfänger macht das sehr wohl einen Unterschied, da ich das Summensignal NIE in die Finger bekomme....
Der Empfänger Trennt Summensignal vom Trägersignal ja im Chip, ab da wird es an den Controller der die PWM für die Servos erzeugt digital weiter gereicht, da is nix mit abgreifen.

Ja von den 25 Hz bin ich auch schon wieder weg, war nur so eine Idee, aber eher als letzte Möglichkeit.

Ich werde morgen mal versuchen, das im Edit meines letzten Post's angesprochene Umzusetzen, das könnte super funktionieren, vor allem mit sehr niedriger Prozessor Last.

Gruß Basti

EDIT:
@Slowly
Danke für den Link, aber leider nicht zu gebrauchen, die nutzen dort auch das mir nicht zur Verfügung stehende Summensignal... Mit dem wäre das ein Klacks, aber alle getrennt auszuwerten erfordert schon etwas mehr Aufwand.

Peter(TOO)
17.12.2014, 19:57
Hallo Basti,

@Peter
Ich vermute wirklich gleichzeitig, das Analog Oszi sieht nicht das kleinste Zucken.
Welche Bandbreite hat der?
Die Pulse müssten in der Grössenordnung <1µs liegen, das sollte ein 20MHz-Ozi noch anzeigen können.
Wenn du 2 Kanäle hast, kannst du doch mal 2 benachbarte Kanäle anzeigen lassen und die Differenz der Flanken messen.
Möglicherweise war deine Diodenmatrix nur zu langsam.


Aber da kommt mir mit der 2 Matrix Methode eine geniale und einfache Idee.

Beide Signale kommen an Interrupt Eingänge.
Ein Timer läuft einfach vor sich hin. Bei Überlauf wird KEIN Interrupt geworfen. Bzw ein Überlauf könnte auf Signalverlust hinweisen (Fail-Safe Funktion)

-INT0 is aktiv und wartet auf High Flanke
-INT1 is deaktiviert
-Kommt nun ein Signal an INT0 wird der Timer auf 0 gesetzt INT0 deaktiviert und INT1 aktiviert
-Bekommt dann INT1 sein Signal wird der Timerwert gespeichert (=Länge Kanal 1), der Timer auf 0 gesetzt, ein Kanal Zähler erhöht und INT1 deaktiviert + INT0 wieder aktiviert.
-Kommt nun wieder auf INT0 ein Signal wieder Wert speichern, Timer Reset, Kanal Zähler erhöhen, INTs tauschen usw...

-Wenn dann der Kanalzähler auf 6 steht, wird der Interrupt nicht getauscht sondern der aktuelle auf Fallende Flanke umkonfiguriert, gibt ja keine weitere High Flanke mehr in diesem Zyklus.
-Wenn diese LOW Flanke dann kommt, wieder Wert speichern, und alles auf Anfang:
INT0 aktiv
INT1 deaktiv
Kanal Zähler 1

Noch genauer wird das Ganze, wenn du anstatt des INTx den Capture-Eingang des Timers nimmst. :-)
Der einzige Nachteil ist, dass du dann die Differenz zum letzten Zählerstand berechnen musst und beim Überlauf musst du aufpassen mit der Rechnung.
Der Jitter liegt dann aber bei +/-1 Zählerauflösung, egal wie lange es dauert bis der Interrupt abgearbeitet wird.
Mit 1MHz-Timertakt hast du schon eine Auflösung von +/- 2µs.

Der Capture-Eingang müsste auch mit den µs-Signalen zurecht kommen, dann müsstest du nicht mal die Flanke umschalten.

MfG Peter(TOO)

Bomberpilot
17.12.2014, 20:18
Jo is ein 20MHz, sogar noch n gutes altes Hameg 203-6 aber wie gesagt, hab schon versucht mit 2 Kanälen zu messen, da sieht man absolut keine Pause zwischen den Pulsen.

Das mit dem Capture Eingang werde ich prüfen. Der AVR hat aber wahrscheinlich nur einen? In meinem Fall Mega32, aber nur zum testen. Wenn der nur einen hat, fällt das fast wieder flach, wenn Ausgabe vom Empfänger so wie im nächsten Absatz beschrieben.

Aber wenn die die Ausgabe ähnlich wie ich machen, gibt es keine Pause.
Ich mache es mit nem Timer im CTC Mode und schiebe einfach ein Bit durch den Port.
Immer wenn die erforderliche Zeit für einen Kanal verstrichen ist, wirds weiter geschoben. Danach wird dann noch so lange "leer" gewartet, bis die 20ms voll sind. Also eigentlich die beste und Ressourcen schonendste Methode, da nur ein Interrupt geworfen wird, wenn auch nötig.

Gruß Basti

Besserwessi
17.12.2014, 21:03
Das zusammenschalten in 2 Gruppe ist eine gute Idee. Die meisten der kleineren AVRs haben zwar nur eine Input-capture Einheit (an Timer 1), können aber dafür sowohl den ICP Pin als auch den analogen Komparator als als Eingang nutzen. Die Input Capture Einheit erlaubt eine genaue Zeitmessung ohne von Interrupts gestört zu werden.

Wenn man es geschickt macht, kann man den Timer 1 trotzdem noch für die Erzeugung der Ausgangssignale nutzen - die output Compare Einheiten bleiben erhalten. Man muss nur den Timer durchlaufen lassen und die passenden Zeiten für die Interrupts programmieren.

Um die Ausgabe nicht zu stören wäre ein freigeben von Interrupts angesagt und ggf. auch die Benutzung von etwa ASM, denn BASCOM erzeugt recht viel Overhead beim Interrupt.

Wenn sicher ist, dass die Eingangsignal ungestört sind, könnte man die auch per PinChange Interrupt erfassen, und auch da ein Unterbrechen der Interrupts erlauben.

Bomberpilot
17.12.2014, 21:30
Wenn man es geschickt macht, kann man den Timer 1 trotzdem noch für die Erzeugung der Ausgangssignale nutzen - die output Compare Einheiten bleiben erhalten. Man muss nur den Timer durchlaufen lassen und die passenden Zeiten für die Interrupts programmieren.


Mehr dazu bitte :-)

Peter(TOO)
17.12.2014, 21:43
Hallo,

Wenn sicher ist, dass die Eingangsignal ungestört sind, könnte man die auch per PinChange Interrupt erfassen, und auch da ein Unterbrechen der Interrupts erlauben.

Die Zeiterfassung mit Interrupt hat immer das Problem mit Jitter. Kommt natürlich auf die nötige Genauigkeit an, ob das stört.

Die kürzeste Ansprechzeit, hängt schon vom gerade bearbeiten CPU-Befehl ab. Dieser wird bei den meisten CPUs erst mal zu Ende abgearbeitet. Komplexe Befehle wie MUL und DIV können da schon einige Takte benötigen.

Dann kommt der Interrupt-Controller ins Spiel. Der setzt Prioritäten und dann können andere Interrupts zuerst bedient werden.
Normalerweise sind Interrupts während der Ausführung einer ISR gesperrt. Je nach Laufzeit der ISR gibt dies weitere Verzögerungen.

Der Rest ist dann Glückssache, was in der Praxis wie oft zusammenfällt!

Ich hatte da. vor langer Zeit, auch mal ein Interrupt-Problem:
Ein 6502 mit 1MHz Takt.
Für 1µs war da der Interrupt enabled, wo das nicht sein sollte.
Bei manchen Geräten führte dies nur so 1x in der Woche zu einem Absturz, bei anderen schon nach einigen Minuten.
Selbst auf den Zufall ist nicht verlass :-)
Ob da eine Quantenverschränkung mitgespielt hat??

MfG Peter(TOO)

wkrug
17.12.2014, 22:10
Auch die größeren AVR Controller haben nicht genug einzelne Interrupt Eingänge um 6 bzw. 7 Servosignale einlesen zu können.
Ich würde mir mal die Pinchange Interups zu Gemüte führen, die fast alle neueren AVR Controller haben.
Man muss dann nur in der Software rauskriegen welcher Eingang den Interupt mit welcher Flanke ausgelöst hat.
Die Zeitmessung könnte dann über das auslesen des TCNT Registers eines 16Bit Timers stattfinden.
Dazu vom TCNT Wert der fallenden Flanke den abgespeicherten TCNT Wert der steigenden Flanke bei jedem Kanal abziehen.
Dieser 16Bit Timer kann dann auch gleich die Impulsgenerierung für das Summensignal übernehmen.
Ein Problem könnte noch sein, das auf die Fallende Flanke sofort eine steigende des nächsten Kanals folgt.
Wie sich das auswirkt wird man wohl nur experimentell rauskriegen.

oberallgeier
17.12.2014, 22:23
... Die Zeiterfassung mit Interrupt hat immer das Problem mit Jitter. Kommt natürlich auf die nötige Genauigkeit an, ob das stört ...Genau das. In der Zeiterfassung steckt das Problem der Genauigkeit. Daher muss die Abtastung so schnell wie möglich erfolgen, meine skizzierter Vorschlag dazu siehe oben. Die zeitlich gerasterte Erfassung ist mit Verlusten in der Eingangsauflösung verbunden, ist mir klar (und oben angemerkt). Da macht es sicher Sinn, die sechs Eingangskanäle mit einem Logikbaustein zu überwachen (1 || 2 || 3 || 4 || 5 || 6) *) und das Ergebnis auf einen externen Interrupt zu geben (nach /RES die höchste Priorisierung). Getriggert wird hier natürlich auf beide Flanken (und ich weiß leider nicht, wie das mit Logikbausteinen realisiert wird oder ob es überhaupt realisiert werden kann). Die Portkonfiguration (siehe mein Posting oben) aller sechs Eingänge gleichzeitig (Zustand_alle = PINB ... gibts sowas in Bascom?) NUR in der extINT-ISR einlesen und mit Zeitmarke versehen ist dann an ein - oder mehrere gleichzeitige oder fast gleichzeitige - Signalereigniss/e gebunden. Spart unnötige Abfragen und wenn die restlichen ISR mit nested Interrupt gefahren werden wird das Jitterproblem noch geringer.

Der Signalausgang nach all den Überarbeitungen ist wenig zeitkritisch, da es analoge Servos sind.

*) Anmerkung: in C ist "||" ein "oder".

PS: ja ich habe die anders gelagerten Vorschläge auch gelesen ;.-.)

stardust19322
17.12.2014, 22:45
@ Bomberpilot:

Hallo Basti,
...
Der Servo-Impuls ist maximal 3ms breit. Ursprünglich wurde im Zeitmultiplex gesendet. So in der Art:
0ms Kanal1
5ms Kanal2
...

MfG Peter(TOO)

Hier wurde das geschrieben ^^.


Bei dem Rest kann ich dir dann so leider nicht mehr weiterhelfen. Ich habe zwar schon ein paar Steuerungen aufgebaut, bei denen mehrere PWM-Kanäle einer 2,4GHz-Anlage von den einzelnen Empfängerports abgegriffen und ausgewertet wurden, das aber meist nur als direkte Schaltoption, also nur einen Ausgang toggeln (beispielsweise Blinker oder Bremsleuchten). Direkte Signalweitergabe - das kann ich leider auch noch nicht.


LG - Maik

Peter(TOO)
17.12.2014, 22:50
Hallo Geier,

Getriggert wird hier natürlich auf beide Flanken (und ich weiß leider nicht, wie das mit Logikbausteinen realisiert wird oder ob es überhaupt realisiert werden kann).
Beide Flanken sind in diesem Fall gar nicht nötig. Die fallende Flanke des einen Kanals entspricht zeitlich der Steigenden des nächsten.

Einfach geht es mit einem RC-Glied als Differenzierer, dann bekommt man aus der Flanke einen Impuls.
Dann bekommt man so etwas:
http://pi.physik.uni-bonn.de/~kortmann/Elektro/024%20Wechselstroeme/0970%20I_U_Beziehung_R_C_L/0441_R-C_Glied_Differenzierer.html
Die negativen Impulse kann man dann absägen, indem man noch eine Diode parallel zum Widerstand einbaut.

Dahinter sollte dann noch ein Schmitt Trigger und ein ODER-Gatter.

Eine andere Möglichkeit ist diese hier (Absatz: Taktflankengesteuerte Flipflops):
http://www.netzmafia.de/skripten/digitaltechnik/flipflop.html
Allerdings ist dann der Impuls nur so breit, wie die Gatterlaufzeit des Inverters.
Damit müsste man dann erst noch Monoflop ansteuern, welches eine vernünftige Impulslänge erzeugt. Wie z.B. so etwas:
http://www.nxp.com/documents/data_sheet/74HC_HCT221_CNV.pdf


MfG Peter(TOO)

Bomberpilot
18.12.2014, 06:26
Morgen zusammen,

also Flip-Flop mit Mono-Flop und Oder-Glied geht mir Hardware mäßig schon fast etwas zu weit :-D
Eingänge zu Pollen wollte ich eigentlich auch soweit als möglich vermeiden. Gerade bei Zeitkritischen Sachen empfinde ich Polling als sinnlose Rechenzeitverschwendung, in Zeiten von Interrupt gesteuerten Abläufen ^^

Aber halten wir mal fest:
-2 Dioden Matrixen
-Signale kommen exakt abwechselnd an den beiden Kanälen (LOW Flanke auf dem einen = HIGH Flanke auf dem anderen)

Somit stehen ja nur noch 2 Kanäle zur Auswertung.

Idee:
Beide Diodenmatrixen bekommen ein RC-Glied (Impulsgenerierung) negative Flanke natürlich weggeschnitten mit Diode.
Diese beiden Signale könnte man dann ODER'n. Somit sollte sich doch ein Signal aus 6 kurzen Impulsen ergeben?
Der Abstand von Puls zu Puls entspricht dann der Signal Dauer.
Problem besteht nur beim letzten Kanal, der ließe sich nur mit einer follgenden negativen Flanke messen... *hmmm*

Gruß Basti

PICture
18.12.2014, 06:35
Idee:
... mit 2 bzw. mehr kleinsten µC auswerten. :confused:

Bomberpilot
18.12.2014, 06:47
... mit 2 kleinsten µC auswerten. :confused:

Ein- und Ausgabe zu trennen würde ich noch mitmachen.
Die Eingabe sollte dann aber schon einer alleine machen. Sonst verschwende ich ja mehr Rechenzeit am Daten synchronisieren als an allem anderen ;-)

PICture
18.12.2014, 06:56
Sorry, aber für mich ist heutzutage ein kleinster 8-pinner in SMD, wie ein "pull up". Ausserdem könnten sie gleich die Signale manipulieren und ausgeben. :D

wkrug
18.12.2014, 09:52
Beide Flanken sind in diesem Fall gar nicht nötig. Die fallende Flanke des einen Kanals entspricht zeitlich der Steigenden des nächsten.

Das stimmt so nicht ganz.
Nach dem letzten Kanal und am Anfang des ersten Kanal ist eine Pause von mindestens 4ms.
Aus dieser Pause heraus wird im Empfänger der Start eines neuen Pulspaketes ermittelt.

Du kannst alle InterruptQuellen eines AVR Controllers nutzen die von einem externen Pin aus angesteuert werden können.
Also INT0, INT1, ICP, Comperator. Wenn Du einen Controller findest der 8 solcher Interruptquellen hat, kannst Du die verwenden.
Wenn nicht bleibt Dir noch die Möglichkeit mit den Pin Change Interrupts wie oben beschrieben.
Ein möglicher Kandidat wäre hier der ATMEGA88. Guck mal ins Datenblatt ab Seite 69.
Um überschneidungen bei den Interrupt Flags zu vermeiden würde ich für die geraden Kanäle den PCINT1 und für die ungeraden Kanäle den PCINT2 verwenden.
Somit wird der gleiche Interrupt maximal 1mal pro Millisekunde aufgerufen.
Das sind 20000 Taktzyklen. Da in dieser Zeit ja 2mal der Pinchange aufgerufen wird hat jeder dieser Interrupts 10000 mögliche Taktzyklen. Das sollte auch für umfangreichere Auswertungen ausreichen.
Der TCNT Wert, sowie die Zustände der Ports sollten ganz am Anfang der Interruptroutine eingelesen werden.
Das sollte den Jitter verringern.
Die Impulsausgabe selber könnte man mit Comparematch Interrupts machen.
Man zählt in den Comparematch Interrupts eine Variable hoch und lädt in das Comparematch Register den Wert für den nächsten Potentialwechsel ( TCNT + gewünschter Änderungspunkt ).
Dann brauchst Du nur noch per Swich Case Anweisung abhängig von der Zähl Variable das Ausgangssignal nach 1 oder 0 zu setzen.
Um die Reaktionszeiten möglichst kurz zu halten müsste man einen 20MHz Quarz verwenden und eventuell auch die Interruptroutinen in Assembler schreiben und die Register Sicherung im Interrupt optimieren.

Das wäre meine Lösung des Problems, wie das Timing hinhaut kannst Du mit dem Simulator vom AVR Studio 6.xx austesten.

Bomberpilot
18.12.2014, 10:06
Du kannst alle InterruptQuellen eines AVR Controllers nutzen die von einem externen Pin aus angesteuert werden können.
Also INT0, INT1, ICP, Comperator. Wenn Du einen Controller findest der 8 solcher Interruptquellen hat, kannst Du die verwenden.
Wenn nicht bleibt Dir noch die Möglichkeit mit den Pin Change Interrupts wie oben beschrieben.

Wenn ich mir die 6 Eingänge über 2 Diodenmatrixen auf nur noch 2 Kanäle reduziere, wird es doch gar nicht nötig so viele Interrupts zu nutzen. Dann reichen mir ja 2. Dann muss nur in der Software mitgezählt werden bei welchem Kanal wir sind, und bei der langen Pause alles zurückgesetzt werden.

Außerdem reicht es bis auf den letzten Impuls, die positive Flanke zu triggern. Und durch den Zähler weis ich ja, wann ich bei der letzten angelangt bin, und kann den INT ganz einfach auf fallen umstellend.

wkrug
18.12.2014, 10:29
Wenn ich mir die 6 Eingänge über 2 Diodenmatrixen auf nur noch 2 Kanäle reduziere, wird es doch gar nicht nötig so viele Interrupts zu nutzen. Dann reichen mir ja 2. Dann muss nur in der Software mitgezählt werden bei welchem Kanal wir sind, und bei der langen Pause alles zurückgesetzt werden.
Das ist natürlich auch ne gute Lösung und vermutlich sogar noch platzsparender.

Allgemein muss man natürlich dann aufpassen, das sich die Ausgangssignale nicht überschneiden.
Sprich egal wie man es macht wird es nicht mit allen Empfängern funktionieren.

Vieleicht wär es auch sinnvoll dem ersten Kanal einen eigenen Interrupt zu geben, damit man sicher eine Synchronisation bekommt.

Bomberpilot
18.12.2014, 11:04
Allgemein muss man natürlich dann aufpassen, das sich die Ausgangssignale nicht überschneiden.
Sprich egal wie man es macht wird es nicht mit allen Empfängern funktionieren.

Vieleicht wär es auch sinnvoll dem ersten Kanal einen eigenen Interrupt zu geben, damit man sicher eine Synchronisation bekommt.

Korrekt, war aber auch nie mein Anspruch mit jedem Empfänger zu funktionieren, sondern mit meinem ;-)
Hier kommen die Servopulse direkt nacheinander ohne Pause, deshalb auch 2 Diodenmatrixen (Ch1,3,5 und Ch2,4,6)
Werde ich heute Abend auf jeden Fall mal ausprobieren, wie das dann mit 2 INTs funktioniert.

Die Sync-Pause wird rechnerisch im Worst-Case "nur" 8ms lang sein (6*2sek.), das sollte sich SICHER detektieren lassen denke ich.
Also ist ein separater INT für Ch1 eher nicht notwendig.

Gruß Basti

Peter(TOO)
18.12.2014, 11:12
Hallo Bombi,

Mit 2 ICs bekommst du das Ganze auf einen Eingang:






- 74x27
-- --------- 1 - __ 74x02 74x02
- ----\ \ __
---- ------- 3 ------| |o-.-------------\ \
- ----/__/ | .-\ \ | |o---.
------- ---- 5 - '--' | |o---/__/ |
-/__/ | 74x27
| __
'-\ \
74x27 .--| |o-
2 - __ 74x02 74x02 |-/__/
----\ \ __ |
4 ------| |o---------------\ \ |
----/__/ | .-\ \ | |o---'
6 - --- | |o- -/__/
-/__/
(created by AACircuit v1.28.6 beta 04/19/05 www.tech-chat.de)

Du bekommst für jede Flanke einen Nadelimpuls am Ausgang (Je nach Logikfamilie 4-10ns).
Um die Impulse zu verlängern muss am Ausgang der beiden Inverter noch ein C gegen Masse, bzw. ein RC-Glied dazwischen.

Etwas Aufwändiger wäre es ein Monoflop am Ausgang nachzuschalten.

MfG Peter(TOO)

Bomberpilot
18.12.2014, 11:23
Du bekommst für jede Flanke...

Auch die negativen? Sonst kann ich das Ende von Ch6 nicht detektieren ;-)

EDIT: Wenn ich drüber nachdenke.... Ja für jede Flanke :-D
Hab zum Glück beide grad zur Hand, kann ich also nach dem Versuch mit der Diodenmatrix auch nochmal testen.

Peter(TOO)
18.12.2014, 11:41
Hallo,

Allgemein muss man natürlich dann aufpassen, das sich die Ausgangssignale nicht überschneiden.

Dies ist sehr unwahrscheinlich, wenn man sich das Übertragungsprotoll mal angesehen hat.

Die Impulse werden direkt hintereinander gesendet. Alles andere am Ausgang ist nur Möglich wenn zwischengespeichert wird.
Die einzig mögliche Überschneidung bewegt sich im Bereich der Laufzeit-Differenzen des I/O-Ports.

Softwaremässig ist es am Einfachsten eine 1 durch das Port, bzw. eine Variable, zu schieben.



unsigned char Out;

void sync(void)
{
Out = 0x1;
Port = 0x00;
}

ISR_Flanke(void)
{
Port = Out;
Out <<= 1;
{


Drumrum braucht es natürlich noch Timeouts, um Störungen zu erkennen und simulierte ISR um in diesem Fall Standardstellungen an die Servos auszugeben.

MfG Peter(TOO)

- - - Aktualisiert - - -

Hallo,

Auch die negativen? Sonst kann ich das Ende von Ch6 nicht detektieren ;-)

EDIT: Wenn ich drüber nachdenke.... Ja für jede Flanke :-D
Hab zum Glück beide grad zur Hand, kann ich also nach dem Versuch mit der Diodenmatrix auch nochmal testen.
Jo, für beide!
Im Prinzip produziert die Schaltung immer zwei Impulse, aber dies überlagern sich.

Die obere Hälft erzeugt z.B. aus der fallenden Flanke von Kanal1 einen Impuls und gleichzeitig erzeugt die untere Hälfte einen aus der steigenden Flanke von Kanal2.

MfG Peter(TOO)

Bomberpilot
18.12.2014, 11:46
Softwaremässig ist es am Einfachsten eine 1 durch das Port, bzw. eine Variable, zu schieben.

Genau so läuft derzeit ja auch meine Ausgabe.
Und vermutlich gestaltet auch der Controller im Empfänger die Ausgabe so, sonst würden sich ja Pausen zwischen den Pulsen ergeben. Nur durch direktes Weiterschieben des Bits kann das direkt ohne Pause fabriziert werden.

Wie gesagt, ich werde heute Abend mal die Methode mit 2 Matrixen und 2 INTs testen.
Und wenn das nicht läuft wie ich das gerne hätte die Methode mit den beiden 74##ern.

Wobei mir 2 belegte INTs lieber sind, als 2 zusätliche ICs ;-)

Gruß Basti

Bomberpilot
18.12.2014, 18:55
Was mach ich falsch, das lauter wirre Zahlen bei rauskommen?
Erst mal nur ein INT der Kanäle 1,3,5...


$regfile "m32def.dat"$baud = 9600
$crystal = 16000000
$framesize = 32
$swstack = 32
$hwstack = 64


Config Porta.0 = Output


Config Timer0 = Timer , Prescale = 256 'Läuft etwa alle 4 ms über
Config Int0 = Rising


On Int0 Int0_isr
On Timer0 T0_ovf


Dim Ch_cnt As Byte
Dim Sig(3) As Byte


Enable Int0
Enable Timer0
Start Timer0


Enable Interrupts




Do
Waitms 200
Print Sig(1) ; "," ; Sig(2) ; "," ; Sig(3)
Loop
End






Int0_isr:
If Ch_cnt > 0 Then Sig(ch_cnt) = Timer0 'Bei erster Flanke soll erst mal nur der Timer auf 0 gestellt werden, bei weiteren dann Wert gespeichert werden
Timer0 = 0 'Timer auf 0 stellen
Incr Ch_cnt 'Kanal Zähler erhöhen
If Ch_cnt = 3 Then Config Int0 = Falling 'Wenn alle 3 Pulse durch sind, INT0 auf fallende Flanke setzen für letzten Puls
Return




T0_ovf:
Config Int0 = Rising 'Wieder auf steigende Flanke
Ch_cnt = 0 'Kanalzähler auf 0


'um auf dem Oszi einen Überlauf des Timers zu sehen
Porta.0 = 1
Waitus 25
Porta.0 = 0
Return

Besserwessi
18.12.2014, 19:00
Bei 2 Interrupts kann man ICP und den analogen Komparator (Vergleich mit 1,1 V Ref intern) nutzen. Auf den beiden Pins kann man die ICP Funktion nutzen, und damit wenigstens die Messung unabhängig von Latenzzeiten machen, ggf. auch ganz ohne Interrupts.

Bomberpilot
19.12.2014, 06:56
Hab gestern noch 2 Stunden probiert, irgendwie bekomm ich nur sinnlose Werte raus :-/
Wie viel würde es denn wohl etwa an Ressourcen bringen, auf C umzusteigen?

Gruß Basti

wkrug
19.12.2014, 08:25
Hab gestern noch 2 Stunden probiert, irgendwie bekomm ich nur sinnlose Werte raus
Bevor Du da die Flinte ins Korn wirfst, guck doch erst mal wo es hängt.
Simulieren heisst hier das Zauberwort.
Auch ein mit Bascom generiertes Programm sollte sich mit AVR Studio simulieren lassen.
Dann kriegst Du schon raus ob es ein Timing Problem, oder ein Softwarefehler ist.

Bomberpilot
19.12.2014, 10:57
Bevor Du da die Flinte ins Korn wirfst ...

Niemals! Dann wirds ja nie fertig. Außerdem kann man ne Flinte immer mal gebrauchen... ;-)
Ich hoff übers Wochenende, oder dann über die Feiertage findet sich ein bisschen Zeit da noch etwas rumzuprobieren.
Evtl. bekomm ich das Programm ja im AVR Studio Sim zum laufen, muss ich mal schaun.

Gruß Basti

wkrug
19.12.2014, 20:02
Ok, Ich bin dann mal so frech und hau nen Code raus.
Im Simulator hab ich das mal getestet - Da scheints zu funktionieren.
Mit nem Controller kann ich leider nicht testen ( Kein ATMEGA 88 hier ).
Ich hab das Prinzip mit dem Pinchange umgesetzt.
Die Simulation war mit 8MHz, dabei kann ein Versatz ( Jitter ) von 30µS bei den Signalen entstehen.


/************************************************** ***

Project : Einzelimpulse zu Summensignal
Version : 0.1
Date : 19.12.2014
Author :
Company :
Comments:


Chip type : ATmega88
Program type : Application
Clock frequency : 8,000000 MHz
Memory model : Small
External SRAM size : 0
Data Stack size : 256

Zeitversatz beim Einlesen der Impulse 30µs!
************************************************** ***/

/********
PC0 = Kanal 1
PC1 = Kanal 3
PC2 = Kanal 5
PC3 = Kanal 7
PD0 = Kanal 2
PD1 = Kanal 4
PD2 = Kanal 6
PD3 = Kanal 8
PB1 = Summensignalausgang
********/

#include <mega88.h>

volatile unsigned int ui_timerold[8]; //Speicherwerte für die Steigende Flanke der entsprecheden Kanäle
volatile unsigned int ui_pulslengh[8]={1500,1500,1500,1500,1500,1500,1500,1500}; //Ermittelte Pulslängen
volatile unsigned char uc_oldgerade=0; //Zwischenspeicher für den alten Zustand der geraden Kanäle 2,4,6,8
volatile unsigned char uc_oldungerade=0; //Zwischenspeicher für den alten Zustand der geraden Kanäle 1,3,5,7
volatile unsigned char uc_cyclecount; //Zyklenzähler für die Impulsausgabe
#define maxcycle 15
#define interpulspause 50 //50 = 50µs
#define interframepause 4000 //4000 = 4ms Interframepause

//#define normal //Bestimmt ob die Impulsausgabe normal oder invers geschehen soll
//#define revers
#define portsungerade PINC
#define portsgerade PIND
#define outport PORTB.1
// Pin change 8-14 interrupt service routine
interrupt [PCINT1] void pin_change_isr1(void)
{
unsigned char uc_portungerade,uc_result;
unsigned int ui_timer;
ui_timer=TCNT1H;
ui_timer=(ui_timer<<8)+TCNT1L;
uc_portungerade=portsungerade&0x0F;
uc_result=uc_portungerade^uc_oldungerade;
if(uc_result&0x01)
{
if(uc_portungerade&0x01) //Es war ne steigende Flanke
{
ui_timerold[0]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[0]=ui_timer-ui_timerold[0];
};
}

if(uc_result&0x02)
{
if(uc_portungerade&0x02) //Es war ne steigende Flanke
{
ui_timerold[2]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[2]=ui_timer-ui_timerold[2];
};
}

if(uc_result&0x04)
{
if(uc_portungerade&0x04) //Es war ne steigende Flanke
{
ui_timerold[4]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[4]=ui_timer-ui_timerold[4];
};
}

if(uc_result&0x08)
{
if(uc_portungerade&0x08) //Es war ne steigende Flanke
{
ui_timerold[6]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[6]=ui_timer-ui_timerold[6];
};
}
uc_oldungerade=uc_portungerade;
}

// Pin change 16-23 interrupt service routine
interrupt [PCINT2] void pin_change_isr2(void)
{
unsigned char uc_portgerade,uc_result;
unsigned int ui_timer;
ui_timer=TCNT1H;
ui_timer=(ui_timer<<8)+TCNT1L;
uc_portgerade=portsgerade&0x0F;
uc_result=uc_portgerade^uc_oldgerade;
if(uc_result&0x01)
{
if(uc_portgerade&0x01) //Es war ne steigende Flanke
{
ui_timerold[1]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[1]=ui_timer-ui_timerold[1];
};
}

if(uc_result&0x02)
{
if(uc_portgerade&0x02) //Es war ne steigende Flanke
{
ui_timerold[3]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[3]=ui_timer-ui_timerold[3];
};
}

if(uc_result&0x04)
{
if(uc_portgerade&0x04) //Es war ne steigende Flanke
{
ui_timerold[5]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[5]=ui_timer-ui_timerold[5];
};
}

if(uc_result&0x08)
{
if(uc_portgerade&0x08) //Es war ne steigende Flanke
{
ui_timerold[7]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[7]=ui_timer-ui_timerold[7];
};
}
uc_oldgerade=uc_portgerade;
}

// Timer 1 output compare A interrupt service routine
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{
unsigned char uc_low,uc_high;
unsigned int ui_buffer;
switch(uc_cyclecount)
{
case 0:
outport=1; //Der erste Impuls
ui_buffer=OCR1AH; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+OCR1AL;
ui_buffer+=ui_pulslengh[0]; //Pulslänge dazuzählen
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;
case 1: //Die Pausen
case 3:
case 5:
case 7:
case 9:
case 11:
case 13:
outport=0;
ui_buffer=OCR1AH; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+OCR1AL;
ui_buffer+=interpulspause;
uc_low=ui_buffer&0x00FF;
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;
case 2:
outport=1; //Der zweite Impuls
ui_buffer=OCR1AH; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+OCR1AL;
ui_buffer+=ui_pulslengh[1]; //Pulslänge dazuzählen
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 4:
outport=1; //Der dritte Impuls
ui_buffer=OCR1AH; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+OCR1AL;
ui_buffer+=ui_pulslengh[2]; //Pulslänge dazuzählen
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 6:
outport=1; //Der vierte Impuls
ui_buffer=OCR1AH; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+OCR1AL;
ui_buffer+=ui_pulslengh[3]; //Pulslänge dazuzählen
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 8:
outport=1; //Der fünfte Impuls
ui_buffer=OCR1AH; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+OCR1AL;
ui_buffer+=ui_pulslengh[4]; //Pulslänge dazuzählen
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 10:
outport=1; //Der sechste Impuls
ui_buffer=OCR1AH; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+OCR1AL;
ui_buffer+=ui_pulslengh[5]; //Pulslänge dazuzählen
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 12:
outport=1; //Der siebte Impuls
ui_buffer=OCR1AH; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+OCR1AL;
ui_buffer+=ui_pulslengh[6]; //Pulslänge dazuzählen
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 14:
outport=1; //Der achte Impuls
ui_buffer=OCR1AH; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+OCR1AL;
ui_buffer+=ui_pulslengh[7]; //Pulslänge dazuzählen
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 15:
outport=0;
ui_buffer=OCR1AH; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+OCR1AL;
ui_buffer+=interframepause;
uc_low=ui_buffer&0x00FF;
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

default:
uc_cyclecount=0;
}
uc_cyclecount++;
if(uc_cyclecount>maxcycle){uc_cyclecount=0;}
}

// Declare your global variables here

void main(void)
{
// Declare your local variables here

// Crystal Oscillator division factor: 1
#pragma optsize-
CLKPR=0x80;
CLKPR=0x00;
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif

// Input/Output Ports initialization
// Port B initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=Out Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=0 State0=T
PORTB=0x00;
DDRB=0x02;

// Port C initialization
// Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State6=T State5=T State4=T State3=P State2=P State1=P State0=P
PORTC=0x0F;
DDRC=0x00;

// Port D initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=P State2=P State1=P State0=P
PORTD=0x0F;
DDRD=0x00;

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: Timer 0 Stopped
// Mode: Normal top=FFh
// OC0A output: Disconnected
// OC0B output: Disconnected
TCCR0A=0x00;
TCCR0B=0x00;
TCNT0=0x00;
OCR0A=0x00;
OCR0B=0x00;

// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 1000,000 kHz
// Mode: Normal top=FFFFh
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer 1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: On
// Compare B Match Interrupt: Off
TCCR1A=0x00;
TCCR1B=0x02;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: Timer 2 Stopped
// Mode: Normal top=FFh
// OC2A output: Disconnected
// OC2B output: Disconnected
ASSR=0x00;
TCCR2A=0x00;
TCCR2B=0x00;
TCNT2=0x00;
OCR2A=0x00;
OCR2B=0x00;

// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
// Interrupt on any change on pins PCINT0-7: Off
// Interrupt on any change on pins PCINT8-14: On
// Interrupt on any change on pins PCINT16-23: On
EICRA=0x00;
EIMSK=0x00;
PCICR=0x06;
PCMSK1=0x0F;
PCMSK2=0x0F;
PCIFR=0x06;

// Timer/Counter 0 Interrupt(s) initialization
TIMSK0=0x00;
// Timer/Counter 1 Interrupt(s) initialization
TIMSK1=0x02;
// Timer/Counter 2 Interrupt(s) initialization
TIMSK2=0x00;

// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
ADCSRB=0x00;

// Watchdog Timer initialization
// Watchdog Timer Prescaler: OSC/128k
// Watchdog Timer interrupt: Off
/*#pragma optsize-
#asm("wdr")
WDTCSR=0x1E;
WDTCSR=0x0E;
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif */

// Global enable interrupts
#asm("sei")

while (1)
{
#asm("wdr")

// Place your code here

};
}


Auch wenn mehrere Ports ihre Level zu gleichen Zeit ändern, sollte der Code funzen.

Besserwessi
19.12.2014, 20:16
Auch BASCOM hat einen internen Simulator. So grundlegende Dinge wie der Timer und Interrupts sollten auch da funktionieren.

Der Umstieg von BASCOM auf C bringt vielleicht einen Faktor 2-3 bei der Geschwindigkeit . ist aber unterschiedlich je nach Aufgabe. Der Overhead bei der Interruptsroutine ist in BASCOM recht groß - etwa 100 Zyklen extra gegenüber GCC bei einer eher kurzen ISR. In BASCOM hat man den Vorteil das man leichter mit inline ASM kombinieren kann - das geht auch in C, aber nicht so einfach. Da BASCOM einige Register normal nicht nutzt, kann man mit BASCOM und ISR in ASM ggf. sogar schneller werden als mit GCC und ASM.

wkrug
20.12.2014, 08:53
....inline ASM kombinieren kann
Das hatte ich schon ein paar Post's weiter vorne auch vorgeschlagen.
Ob man's dann mit BASCOM oder nen C-Compiler macht dürfte relativ egal sein.
Auch bei CodeVision AVR lässt sich inline Assembler problemlos einbinden.
Wie das bei AVR GCC geht weiss ich leider nicht.
Wenn man in der Interruptroutine nur Assembler verwendet kann man zusätzlich die automatische Registersicherung abschalten, da BASCOM normalerweise bei jedem ISR-Aufruf ja alle 32 Register sichert und wieder zurückschreibt.
Bei ASM müssen nur das SREG und die Register gesichert werden, die man selber benotigt.
Zusätzlich ist es sinnvoll den Controller mit der maximal möglichen Taktrate zu takten weil damit der Jitter minimiert wird.
Also einen Typen wählen der mit 20MHz läuft und auch 20MHz verwenden.

Ein Bekannter von mir hat mit BASCOM und inline Assembler einen Testbildgenerator für Fernseher gebaut und das ist noch wesentlich zeitkritischer als Servoimpulse einlesen.

Besserwessi
20.12.2014, 12:17
Inline ASM geht auch mit GCC. Es ist sogar recht mächtig und erlaubt dem Compiler den Code davor und dahinter entsprechend anzupassen / optimieren. Allerdings ist deshalb das Interface etwas umständlich, auch wenn man es für die ISR nicht braucht. Man kann in C auch so etwas wie die ISR als extra ASM Code teil einbinden und erst vom Linker dazu packen lassen.

ASM in BASCOM geht halt sehr einfach, weil die Registernutzung bei BASCOM fest ist. Normal sind einige Register ungenutzt, die man dann in der ISR nicht mal retten muss.

peterfido
24.12.2014, 13:34
Gibt es da irgendwo eine zuverlässige Übersicht?

Bomberpilot
24.12.2014, 14:40
Euch allen erst mal Frohe Weihnachten.

Hab nun endtlich das AVR Studio 4.19 zum laufen gebracht ;-) das 6er Atmel Studio ist mir einfach viel zu überladen.
Ich denke über die Feiertage habe ich dann auch etwas Zeit mich näher damit zu befassen.
Die letzten Tage waren Arbeits-Technisch etwas stressiger....

Gruß Basti

Bomberpilot
26.12.2014, 14:32
In C spuckt er mir leider die selben Mist Werte wie im Bascom Prog aus. Also scheint irgendwas an meinem Prinzip völlig daneben zu gehen.

main.c


#include <avr/io.h>
#include <stdlib.h>
#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

#include "uart.h"
#include "input.h"

/* define CPU frequency in Mhz here if not defined in Makefile */
#ifndef F_CPU
#define F_CPU 16000000UL
#endif

/* 9600 baud */
#define UART_BAUD_RATE 9600


int main(void)
{


//Uart initialisieren
uart_init( UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU) );


//Eingänge INT0 und INT1 initialisieren
init_inputs();


while(1)
{
char tmp[5];
itoa(input_channel[3], tmp, 10);
uart_puts(tmp);
uart_puts("\n");
_delay_ms(50);
}; //Endlosschleife

}


input.c


#include <avr/io.h>
#include <stdint.h>
#include <avr/interrupt.h>

#include "input.h"

// **************************
// Prototypen
// **************************
void init_inputs(void);//Initialisiert INT0 und INT1

// **************************
// Deklarationen
// **************************
uint16_t (input_channel)[7]; //Array für die einzelnen Kanäle
uint8_t (fail_flag); //Flag für Fail Safe Modus

uint8_t (ch_cnt); //Counter für aktuellen Kanal


void init_inputs(void)
{


//INT0 und INT1 als Eingang
//DDRD = 0b11111001;
DDRD &= ~((1 << DDD1) | (1 << DDD2));

//INT0 und INT1 aktivieren
GICR |= ((1 << INT0) | (1 << INT1));

//Beide INT auf steigene Flanke konfigurieren
MCUCR |= ((1 << ISC01) | (1 << ISC00)); //INT0
MCUCR |= ((1 << ISC11) | (1 << ISC10)); //INT1

//Timer1 Prescaler auf 1 stellen
TCCR1B |= (1 << CS10);

//Timer1 Overflow INT aktivieren
TIMSK |= (1 << TOIE1);

//Interrupts anschalten
sei();
};


//ISR von INT0
ISR(INT0_vect)
{
//Wenn der CH != 0 dann speichere Zählerstand
if(ch_cnt != 0){input_channel[ch_cnt] = TCNT1;}

//Ch Zähler erhöhen
ch_cnt++;

//Zähler zurücksetzen
TCNT1 = 0;

//INT0 auf fallende Flanke
if(ch_cnt == 6){MCUCR &= ~(1 << ISC00);}
}


//ISR von INT1
ISR(INT1_vect)
{
//Wenn der CH != 0 dann speichere Zählerstand
if(ch_cnt != 0){input_channel[ch_cnt] = TCNT1;}

//Ch Zähler erhöhen
ch_cnt++;

//Zähler zurücksetzen
TCNT1 = 0;

//INT1 auf fallende Flanke
if(ch_cnt == 6){MCUCR &= ~(1 << ISC10);}
}


//Timerüberlauf nach ~4ms, wenn nicht manuell zurückgesetzt wird
ISR(TIMER1_OVF_vect)
{
//Kanalzähler zurücksetzen
ch_cnt = 0;

//Beide INT auf steigene Flanke konfigurieren
MCUCR |= ((1 << ISC01) | (1 << ISC00)); //INT0
MCUCR |= ((1 << ISC11) | (1 << ISC10)); //INT1
}


Aus hTerm


...
...
...
9618<\n>
9618<\n>
9618<\n>
9618<\n>
9618<\n>
9618<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
8837<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
6762<\n>
-2320<\n>
-2320<\n>
-2320<\n>
-2320<\n>
-2320<\n>
-2320<\n>
-2320<\n>
...
...
...


Ich versteh nur langsam nicht mehr so ganz wieso dieses Prinzip nicht funktioniert.
Bei jedem INT den Zähler speichern und zurücksetzen.
Beim letzten auf fallende Flanke, damit das Ende des letzten Puls erfasst wird.

Die Pause nach den Pulsen ist dann größer als 4ms, sodass der Timer überläuft.
Der setzt dann alles auf Anfang zurück. Die 4ms Pause ist quasi das Syncronistations Signal.

Jemand ne Idee was ich falsch mache?

Gruß Basti
PS.: Schönen Feiertag noch :-)

EDIT:
gerade festgestellt, für Ch1 passt es eigentlich. Da bringt er mir einen Wert zwischen etwa 18000 und 30000 Ticks des Timers.
Nur die weiteren Ch haben dann falsche oder sogar negative Werte. Wie kann der Timer negativ Zählen?

Das sind die 6Ch Daten die er mir ausspuckt....

23925 | -239 | 2331 | -14653 | 0 | 0<\n>
Nur Ch1 verhält sich wie gewollt, äquivalent zum Knüpel der Fernbedienung, alle anderen springen ab und zu mal wild umher, aber nicht unbedingt auf Knüpelbewegungen.

EDIT2:
Weitere Erkenntnis, wenn ich beide Oszi Kanäle an die INTS halte und mir die Signale anschauen will, kommen die auch auf einmal korrekt an :-D scheinbar hängt der ganze scheiß irgendwie in der Luft und fängt sich Müll ein, sobal ich das aber - wenn auch nur minimal - mit dem Oszi belaste, haut's hin. Mal überlegen was ich da nun mache....

EDIT3:
Tatsache, die Ausgänge des Empfängers hingen bei Low Pegel völlig in der Luft. 10k gegen Masse und die Nummer läuft wie geschmiert.
Is ja klar, das der mir nur Müll auswertet, wenn die INTs sich alles einfangen, nur nicht das richtige Signal :-D

Aber aus lauter Trotz bleibe ich jetzt bei C, dann lern ich das auch endlich mal - bis dato noch NIE angefasst.
Alles was oben steht habe ich mir in etwa 48h angelesen ;-)

wkrug
27.12.2014, 08:35
Aber aus lauter Trotz bleibe ich jetzt bei C
Ich denk mal, da machst Du sicher keinen Fehler.
Eine der wichtigsten Erkenntnisse von C ist, das der Compiler von oben nach unten arbeitet.
Nur das was oben definiert ist kann dein Programm darunter auch nutzen.
Deshalb steht da auch die main Routine ganz unten.
Wenn er mal ne Routine nicht findet, obwohl sie vorhanden ist, denk mal wie der Compiler und guck, ob die Routine vor der aufrufenden steht.
Damit hatte ich Anfangs die meisten Probleme.

Was mich persönlich bei C auch etwas nervt ist, das man für "Sonderfunktionen" immer erst die entsprechenden Includes im Programm machen muss.
Also für Mathematische Funktionen, String Verarbeitung usw. müssen immer die entsprechenden Includes eingefügt werden.
Aber C ist halt ein Baukasten, aus dem man sich halt nur nimmt, was man auch für seine Anwedung braucht.

Bomberpilot
27.12.2014, 08:54
Eine der wichtigsten Erkenntnisse von C ist, das der Compiler von oben nach unten arbeitet.
Hat mich 8 Std gekostet bis ich das im Zusammenhang mit den Header Dateien komplett verstanden habe. Jetzt ist es Logisch und ich finde die Herangehensweise, jede C Datei als "eigenständig" kompilierbares Programm zu generieren auch nicht mal schlecht.


Deshalb steht da auch die main Routine ganz unten.
Wenn ich das Richtig verstanden habe ist es egal, wo die main() steht, solange ich oben bzw. in der Header Datei die Funktionen als Prototyp definiert habe, oder nicht?


Was mich persönlich bei C auch etwas nervt ist, das man für "Sonderfunktionen" immer erst die entsprechenden Includes im Programm machen muss.
Also für Mathematische Funktionen, String Verarbeitung usw. müssen immer die entsprechenden Includes eingefügt werden.
Genau das schätze ich an C ;-) Kein byte zu viel Schrott im Code, sondern nur was auch Sinn macht.

BTT:
Also die Eingänge laufen jetzt, Jitter werde ich mir mal ansehen, denke aber das ein gleitender Mittelwert über 3 Werte da schon einiges rausholen kann.
Die Frage ist, was ich nun mit den Ausgängen mache. Timer1 ist ja nun blockiert :-/

Gruß Basti

wkrug
27.12.2014, 09:12
Die Frage ist, was ich nun mit den Ausgängen mache. Timer1 ist ja nun blockiert :-/
Wenn Du den Timer nicht im CTC ( Clear Timer at Comparematch ) Mode laufen lässt, kannst Du problemlos auch die Impulsgenerierung darüber machen.
Du gibst den Comparematch Interrupt frei und stellst Ihn auf das nächste Ereignis ein.
Wie weit Du bei der Impulsgenerierung schon bist, kannst Du mit einer Zählvariable abspeichern.
Guck mal den Code ein paar Post's weiter vorne von mir an und zwar den Abschnitt

// Timer 1 output compare A interrupt service routine
interrupt [TIM1_COMPA] void timer1_compa_isr(void)........
Der Code ist für CodeVision, sollte aber auch mit ein paar Änderungen auf AVR GCC laufen.
Elementar ist, das das TCNT1 Register nicht manipuliert werden darf, also keine Werte reinschreiben und kein CTC.
Dann kannst Du, wenn Du lustig bist, sogar noch den Comparematch B für die Generierung einer weiteren Impulskette benutzen.
Wenn Du die Variablen unsigned machst, sind auch Timer Überläufe kein Problem.

Bomberpilot
27.12.2014, 09:26
Dann kannst Du, wenn Du lustig bist, sogar noch den Comparematch B für die Generierung einer weiteren Impulskette benutzen..
Danke, schau ich mir nochmal genauer an. Aber ich brauch keine Impulskette sondern wieder die 8 einzelnen Servo Signale ;-) sollen ja dann direkt die Servos dran.

EDIT:
Ich versteh das jetzt so:

Also der Timer läuft einfach vor sich hin, kein Eingriff durch mich.
Ich speicher die zeit beim beginn des Puls und beim Ende. Differenz ist dann die Dauer (aktuell Messe ich ja immer von 0 ab).
Durch unsigned ist der Überlauf irrelevant.

Wenn ich den Timer jetzt mit den ~4ms Überlauf so lasse, könnte ich bei jedem Überlauf einfach den Wert des nächsten Kanals in das Compare Register schreiben und den Ausgang high setzen. Und durch den Compare INT dann wieder auf LOW. Beim nächsten Timerüberlauf das gleich für den nächsten Kanal.

Dann komme ich bei 6 Kanälen mit 4ms Paketdauer auf 24ms Dauer eines Signals für einen einzelnen Servo, statt der 20ms, denke das ist noch im vertretbaren Rahmen?

Nur wie mache ich dann die Synchronistation auf CH1?

wkrug
27.12.2014, 11:27
Also der Timer läuft einfach vor sich hin, kein Eingriff durch mich.
Ich speicher die zeit beim beginn des Puls und beim Ende. Differenz ist dann die Dauer (aktuell Messe ich ja immer von 0 ab).
Durch unsigned ist der Überlauf irrelevant.
Genau richtig!


Beim nächsten Timerüberlauf das gleich für den nächsten Kanal.
Lass doch den Timer Überlauf Interrupt komplett weg und füg die Pausen zwischen den Pulsen auch bei der Comparematchroutine mit ein.
Dadurch bleibt die Pausezeit auch variabel.
Du musst nur darauf achten, das das Comparematchregister einen höheren Wert hat als das TCNT1 Register, sonst wird da kein Interrupt mehr ausgelöst.
Eine Pause von 50µs reicht da völlig aus. Die CodeZeilen für das Setzen des TCC1Ax Register müssen abgearbeitet sein, bevor der TCNT1 diesen Zählerstand erreicht - Simulieren.

Für die Mehrfachkanalausgabe auf verschiedenen Ports geht das im Prinzip genauso, weil wer sagt, das in einem Comparematchinterrupt nur ein Port verändert werden darf.

Ich mach das so, das nach dem ersten Impuls der erste Port nach 0 gesetzt dann gleich der 2te gesetzt wird usw. bis alle Kanäle - Ausgänge durch sind.
Dann fügst Du eine Pause ein, damit die Wiederholrate nicht zu groß wird und fängst dann wieder von vorne an.

Für die Sync mit dem Empfänger gibt's im Prinzip mehrere Möglichkeiten.
Einmal kannst Du für Kanal 1 einen eigenen Interrupt verwenden, dann ist Kanal 1 klar definiert.
Zum anderen kannst Du die Pausen zwischen 2 Impulsketten auswerten, die ja mindestens 4ms betragen muß.
Zum dritten kannst Du auch die Idee mit den Pin Change Interrupts realisieren, dann sind die Kanäle ohnehin klar erkennbar.

oberallgeier
27.12.2014, 12:25
... bei 6 Kanälen mit 4ms Paketdauer auf 24ms Dauer eines Signals für einen einzelnen Servo, statt der 20ms ... im vertretbaren Rahmen? ...Die Antwort heißt eigentlich "im Prinzip könnte es gehen" - aber bei mir gehts wirklich gut! Und ich hatte anfangs deutliche Zweifel: bei meinem Archie (z.B. Video vom Kopf (https://www.youtube.com/watch?v=Kt4UiEaTicc)) verbrauche ich für die von mir gewünschte hohe Auflösung (Puls wird auf 0,4µs-Scheiben aufgeteilt (https://www.roboternetz.de/community/threads/61379-Kopfsache-und-ein-m1284-etliche-Servos-viel-Alu?p=579801&viewfull=1#post579801) - aber der Servo reagiert eher erst nach zwei, drei, vier µs) bei den im Kopf verbauten zehn Servoansteuerungen 25,60 ms für eine Servoperiode. Läuft aber ziemlich ansprechend finde ich.

Searcher
27.12.2014, 17:59
Hallo,
@Bomberpilot: Ich bin mir nicht im Klaren, was für Signale bei Dir ankommen und wie Du sie weiterschicken möchtest.

Ist es richtig, daß es 6 Servosignale (jedes zwischen 1ms und 2ms high) auf 6 verschiedenen Leitungen sind.

Wenn das erste Signal low geht, geht direkt ohne Pause das zweite Signal auf high. Geht das auf low, sofort das dritte auf high usw.

Da alle Signale verschiedene Längen haben können, ist die Schlußpause bis zum 20ms Raster, bis das erste Signal wieder high wird, unbestimmt lang und ist abhängig von den high Zeiten aller 6 Signale. (20ms minus Gesamthighzeit = Lowschlußpause)

Du hast jetzt das erste, dritte, fünfte Signal per Dioden zusammengeführt und auf INT0 Pin vom µC gelegt. Das Gleiche mit dem zweiten, vierten und sechsten Signal auf INT1.

Nun möchtest Du die Signale messen, modifizieren und in einem 4ms Raster ausgeben.

Erstes Signal ab Zeitpunkt 0 ausgeben, zB 1,5ms lang high, dann low und bis 4ms warten, dann zweites Signal auf high, zB 1,2ms high, dann low, wieder bis Zeitpunkt 8ms warten, nächstes high usw.

Die Ausgabe der Signale sollen alle auf dem gleichen µC Pin passieren.

Großes Fragezeichen :lol:

Gruß
Searcher

Bomberpilot
27.12.2014, 19:04
Ist es richtig, daß es 6 Servosignale (jedes zwischen 1ms und 2ms high) auf 6 verschiedenen Leitungen sind.

Wenn das erste Signal low geht, geht direkt ohne Pause das zweite Signal auf high. Geht das auf low, sofort das dritte auf high usw.

Da alle Signale verschiedene Längen haben können, ist die Schlußpause bis zum 20ms Raster, bis das erste Signal wieder high wird, unbestimmt lang und ist abhängig von den high Zeiten aller 6 Signale. (20ms minus Gesamthighzeit = Lowschlußpause)

Du hast jetzt das erste, dritte, fünfte Signal per Dioden zusammengeführt und auf INT0 Pin vom µC gelegt. Das Gleiche mit dem zweiten, vierten und sechsten Signal auf INT1.

Bis hierhin absolut korrekt...



Nun möchtest Du die Signale messen, modifizieren und in einem 4ms Raster ausgeben.

Erstes Signal ab Zeitpunkt 0 ausgeben, zB 1,5ms lang high, dann low und bis 4ms warten, dann zweites Signal auf high, zB 1,2ms high, dann low, wieder bis Zeitpunkt 8ms warten, nächstes high usw.

Die Ausgabe der Signale sollen alle auf dem gleichen µC Pin passieren.
Will die 6 Signale wieder getrennt voneinander ausgeben. Die sollen dann quasi direkt zu den einzelnen Servos.

Die Ausgabe wird jetzt etwas anders realisiert.
Die mache ich jetzt im Prinzip etwa so wie sie auch mein Empfänger macht.
Schiebe einfach ein Bit durchs Port und warte per Compare Register immer so lange wie es an sein muss. (Dadruch ensteht der direkte Wechsel - ein Pin geht LOW, anderer gleichzeitig HIGH)
Danach warte ich dann noch so lange bis 20ms voll sind.

Das ganze mit Timer1 und Prescaler 1 (Eingabe UND Ausgabe) also mehr Auflösung wird sich nicht rausholen lassen.
(Aktuell läuft Timer0 nur mit um die lange Pause zum synchronisieren zu erkennen. Der wird bei jedem INT resetet, läuft er nach 4ms über, gabs zu lange kein Signal mehr, also wird alles auf Anfang gestellt)

Aktuell bin ich soweit, das nun beides funktioniert. Jitter scheint extrem gering zu sein, kann gerade nicht genau messen. Probiere ich aber noch.
Muss noch optimieren. Aber ich reiche natürlich alles an Infos nach ;-)

Gruß Basti

EDIT:
Wie am besten mit Analog Oszi den Jitter messen?
Bekomm die Flanke leider nur bis 200uS/Div. ins Bild. Dort kann man auf jeden Fall einen Jitter < 10uS erahnen.
Vermutlich noch kleiner, aber das hat dann mit messen weis Gott nix mehr zu tun.

bigbenball
29.12.2014, 04:15
Aktuell bin ich soweit, das nun beides funktioniert. Jitter scheint extrem gering zu sein, kann gerade nicht genau messen. Probiere ich aber noch.
Muss noch optimieren. Aber ich reiche natürlich alles an Infos nach

Ja, Ich freue mich auf diese Informationen. Das klingt sehr gut, das beides funktioniert.

Gruß
Enre



iphone 6 tasche (http://www.hulle6.com)

wkrug
30.12.2014, 09:06
Nachtrag:
Ich hab mir mal nen ATMEGA88 beschafft und meine Idee mit den PinChanges realisiert.
Das klappt auch, wenn alle Servoimpulse zur gleichen Zeit eintreffen.

/************************************************** ***
This program was produced by the
CodeWizardAVR V1.25.3 Standard
Automatic Program Generator
© Copyright 1998-2007 Pavel Haiduc, HP InfoTech s.r.l.
http://www.hpinfotech.com

Project : Einzelimpulse zu Summensignal
Version : 1.0
Date : 30.12.2014
Author :
Company : Germany
Comments:


Chip type : ATmega88
Program type : Application
Clock frequency : 16,00000 MHz
Memory model : Small
External SRAM size : 0
Data Stack size : 256

Zeitversatz beim Einlesen der Impulse 15µs!
************************************************** ***/

/********
PC0 = Kanal 1
PC1 = Kanal 3
PC2 = Kanal 5
PC3 = Kanal 7
PD0 = Kanal 2
PD1 = Kanal 4
PD2 = Kanal 6
PD3 = Kanal 8
PB1 = Summensignalausgang
PB2 = Syncsignal startet bei Pausenbeginn
********/

#include <mega88.h>

volatile unsigned int ui_timerold[8]; //Speicherwerte für die Steigende Flanke der entsprecheden Kanäle
volatile unsigned int ui_pulslengh[8]={3000,3000,3000,3000,3000,3000,3000,3000}; //Ermittelte Pulslängen
volatile unsigned int ui_lastchange; //Letzter Impulsstart für Summensignalgenerierung
volatile unsigned char uc_oldgerade=0; //Zwischenspeicher für den alten Zustand der geraden Kanäle 2,4,6,8
volatile unsigned char uc_oldungerade=0; //Zwischenspeicher für den alten Zustand der geraden Kanäle 1,3,5,7
volatile unsigned char uc_cyclecount; //Zyklenzähler für die Impulsausgabe
#define maxcycle 17 //Maximale Zyklenzahl für die Impulsausgabe
#define minlengh 1200 //Minimal zulässige Impulslänge 600µs
#define midlengh 3000 //Servo Mittelstellung
#define maxlengh 5400 //Maximal zulässige Impulslänge 2700µs
#define interpulspause 1000 //1000 = 500µs
#define interframepause 12000 //12000 = 6ms Interframepause

//#define normal //Bestimmt ob die Impulsausgabe normal oder invers geschehen soll
//#define revers //zur Zeit noch nicht implementiert - Bedingte Assemblierung
#define portsungerade PINC //Ungerade Kanäle auf Port C
#define portsgerade PIND //Gerade Kanäle auf Port D
#define outport PORTB.1 //Summen Ausgangssignal
#define syncport PORTB.2 //Sync Signal für Oszilloskop

// Pin change 8-14 interrupt service routine Kanal 1,3,5,7
interrupt [PCINT1] void pin_change_isr1(void)
{
unsigned char uc_portungerade,uc_result,uc_low,uc_high;
unsigned int ui_timer;
uc_portungerade=portsungerade;
uc_low=TCNT1L;
uc_high=TCNT1H;
ui_timer=uc_high;
ui_timer=ui_timer<<8;
ui_timer+=uc_low;
uc_portungerade&=0x0F;
uc_result=uc_portungerade^uc_oldungerade;
if(uc_result&0x01)
{
if(uc_portungerade&0x01) //Es war ne steigende Flanke
{
ui_timerold[0]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[0]=ui_timer-ui_timerold[0];
};
}

if(uc_result&0x02)
{
if(uc_portungerade&0x02) //Es war ne steigende Flanke
{
ui_timerold[2]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[2]=ui_timer-ui_timerold[2];
};
}

if(uc_result&0x04)
{
if(uc_portungerade&0x04) //Es war ne steigende Flanke
{
ui_timerold[4]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[4]=ui_timer-ui_timerold[4];
};
}

if(uc_result&0x08)
{
if(uc_portungerade&0x08) //Es war ne steigende Flanke
{
ui_timerold[6]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[6]=ui_timer-ui_timerold[6];
};
}
uc_oldungerade=uc_portungerade;
}

// Pin change 16-23 interrupt service routine Kanal 2,4,6,8
interrupt [PCINT2] void pin_change_isr2(void)
{
unsigned char uc_portgerade,uc_result,uc_low,uc_high;
unsigned int ui_timer;
uc_portgerade=portsgerade;
uc_low=TCNT1L;
uc_high=TCNT1H;
ui_timer=uc_high;
ui_timer=ui_timer<<8;
ui_timer+=uc_low;
uc_portgerade&=0x0F;
uc_result=uc_portgerade^uc_oldgerade;
if(uc_result&0x01)
{
if(uc_portgerade&0x01) //Es war ne steigende Flanke
{
ui_timerold[1]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[1]=ui_timer-ui_timerold[1];
};
}

if(uc_result&0x02)
{
if(uc_portgerade&0x02) //Es war ne steigende Flanke
{
ui_timerold[3]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[3]=ui_timer-ui_timerold[3];
};
}

if(uc_result&0x04)
{
if(uc_portgerade&0x04) //Es war ne steigende Flanke
{
ui_timerold[5]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[5]=ui_timer-ui_timerold[5];
};
}

if(uc_result&0x08)
{
if(uc_portgerade&0x08) //Es war ne steigende Flanke
{
ui_timerold[7]=ui_timer;
}
else //Es war ne fallende Flanke
{
ui_pulslengh[7]=ui_timer-ui_timerold[7];
};
}
uc_oldgerade=uc_portgerade;
}

// Timer 1 output compare A interrupt service routine
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{
unsigned char uc_low,uc_high;
unsigned int ui_buffer;
switch(uc_cyclecount)
{
case 0: //Die Pausen
syncport=0; //Sychronosationsimpuls stoppen
outport=1;
uc_low=OCR1AL;
uc_high=OCR1AH;
ui_buffer=uc_high; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+uc_low;
ui_lastchange=ui_buffer;
ui_buffer+=interpulspause; //Preimpuls dazuzählen und einfügen
uc_low=ui_buffer&0x00FF;
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 1:
outport=0; //Der erste Impuls
ui_buffer=ui_lastchange+ui_pulslengh[0];
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 2: //Pausen zwischen den Impulsen einfügen
case 4:
case 6:
case 8:
case 10:
case 12:
case 14:
case 16:
outport=1;
uc_low=OCR1AL;
uc_high=OCR1AH;
ui_buffer=uc_high; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+uc_low;
ui_lastchange=ui_buffer;
ui_buffer+=interpulspause; //Pause zwischen den Impulsen dazuzählen und einfügen
uc_low=ui_buffer&0x00FF;
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 3:
outport=0; //Der zweite Impuls
ui_buffer=ui_lastchange+ui_pulslengh[1];
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 5:
outport=0; //Der dritte Impuls
ui_buffer=ui_lastchange+ui_pulslengh[2];
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 7:
outport=0; //Der vierte Impuls
ui_buffer=ui_lastchange+ui_pulslengh[3];
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 9:
outport=0; //Der fünfte Impuls
ui_buffer=ui_lastchange+ui_pulslengh[4];
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 11:
outport=0; //Der sechste Impuls
ui_buffer=ui_lastchange+ui_pulslengh[5];
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 13:
outport=0; //Der siebte Impuls
ui_buffer=ui_lastchange+ui_pulslengh[6];
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 15:
outport=0; //Der achte Impuls
ui_buffer=ui_lastchange+ui_pulslengh[7];
uc_low=ui_buffer&0x00FF; //OCR Register für nächsten Interrupt vorbelegen
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

case 17:
syncport=1; //Syncpuls Starten
outport=0;
ui_buffer=OCR1AH; //Aktuelle Werte holen
ui_buffer=(ui_buffer<<8)+OCR1AL;
ui_buffer+=interframepause; //Pause zwischen den Frames also nach 8 Impulsen einfügen
uc_low=ui_buffer&0x00FF;
uc_high=(ui_buffer>>8)&0x00FF;
OCR1AH=uc_high;
OCR1AL=uc_low;
break;

}
uc_cyclecount++;
if(uc_cyclecount>maxcycle){uc_cyclecount=0;}
}

// Declare your global variables here

void main(void)
{
// Declare your local variables here
unsigned char uc_i;
// Crystal Oscillator division factor: 1
#pragma optsize-
CLKPR=0x80;
CLKPR=0x00;
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif

// Input/Output Ports initialization
// Port B initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=Out Func1=Out Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=0 State0=T
PORTB=0x00;
DDRB=0x06;

// Port C initialization
// Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State6=T State5=T State4=T State3=P State2=P State1=P State0=P
PORTC=0x0F;
DDRC=0x00;

// Port D initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=P State2=P State1=P State0=P
PORTD=0x0F;
DDRD=0x00;

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: Timer 0 Stopped
// Mode: Normal top=FFh
// OC0A output: Disconnected
// OC0B output: Disconnected
TCCR0A=0x00;
TCCR0B=0x00;
TCNT0=0x00;
OCR0A=0x00;
OCR0B=0x00;

// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 1000,000 kHz
// Mode: Normal top=FFFFh
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer 1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: On
// Compare B Match Interrupt: Off
TCCR1A=0x00;
TCCR1B=0x02;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: Timer 2 Stopped
// Mode: Normal top=FFh
// OC2A output: Disconnected
// OC2B output: Disconnected
ASSR=0x00;
TCCR2A=0x00;
TCCR2B=0x00;
TCNT2=0x00;
OCR2A=0x00;
OCR2B=0x00;

// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
// Interrupt on any change on pins PCINT0-7: Off
// Interrupt on any change on pins PCINT8-14: On
// Interrupt on any change on pins PCINT16-23: On
EICRA=0x00;
EIMSK=0x00;
PCICR=0x06;
PCMSK1=0x0F;
PCMSK2=0x0F;
PCIFR=0x06;

// Timer/Counter 0 Interrupt(s) initialization
TIMSK0=0x00;
// Timer/Counter 1 Interrupt(s) initialization
TIMSK1=0x02;
// Timer/Counter 2 Interrupt(s) initialization
TIMSK2=0x00;

// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
ADCSRB=0x00;

while(PINC.0==0); //Warten auf Impulse
while(PINC.0>0);
// Watchdog Timer initialization
// Watchdog Timer Prescaler: OSC/128k
// Watchdog Timer interrupt: Off
#pragma optsize-
#asm("wdr")
WDTCSR=0x1E;
WDTCSR=0x0E;
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif

// Global enable interrupts
#asm("sei")

while (1)
{
#asm("wdr")
for (uc_i=0;uc_i<8;uc_i++)
{
if(ui_pulslengh[uc_i]<minlengh||ui_pulslengh[uc_i]>maxlengh)
{
ui_pulslengh[uc_i]=midlengh;
}
};
};
}


Der Code braucht 630 Words, also 1260Byte

Bomberpilot
30.12.2014, 09:53
Gut zu wissen, den Code werde ich mal in der Hinterhand halten.
Allerdings ganz schön viel Code, im Vergleich zu meinem ^^
Wie groß ist das ganze kompilliert? Mein Code kommt auf 1634 byte, lauffähig...

Gruß Basti

Bomberpilot
30.12.2014, 23:11
Hier mal meine aktuellen Ergebnisse

Ich hoffe einigermaßen sinnvoll programmiert und verständlich kommentiert.
Immerhin spreche ich GCC erst seit ner knappen Woche ;-)

Gruß Basti

Jitter konnte ich allein mit Analog Oszi nicht wirklich feststellen, und das obwohl ich alle Werte ca alle 100ms per UART ausgegeben habe.
Evtl kann das ja mal jemand nachmessen der besseres Gerät hat?

main.c


#include <avr/io.h>
#include <stdlib.h>
#include <stdint.h>


#include "inout.h"


/* define CPU frequency in Mhz here if not defined in Makefile */
#ifndef F_CPU
#define F_CPU 16000000UL
#endif


int main(void)
{
//Ein- und Ausgabe initialisieren
init_inout();


while(1)
{
set_ch(1,get_ch(1));
set_ch(2,get_ch(2));
set_ch(3,get_ch(3));
set_ch(4,get_ch(4));
set_ch(5,get_ch(5));
set_ch(6,get_ch(6));
}; //Endlosschleife

}


inout.h


//Name: inout.c
//Autor: Bastian Schroll
//Date: 31.12.2014
//Description:
//Ließt 6 getrennte Servo Kanäle über INT0 und INT1 ein
//Die Eingangs Signale werden dem Programm per get_ch(KANAL) zur Verfügung gestellt
//Nach Verarbeitung der Signale können diese per set_ch(KANAL,PULSLÄNGE) wieder Ausgegeben werden
//Dabei werden die Signale auf PORTA.0 - .5 per Compare INT generiert


#ifndef INOUT_H
#define INOUT_H


// **************************
// Prototypen
// **************************
void init_inout(void);//Initialisiert INT0 und INT1 sowie Timer1 und Timer0
uint16_t get_ch(uint8_t channel); //Liefert aktuellen Wert zurück
void set_ch(uint8_t channel, uint16_t puls); //Setzt Kanal 1-6 (Min. 1000 - Max. 2000)




// **************************
// Deklarationen
// **************************
uint8_t (fail_flag); //Flag für Fail Safe Modus


#endif




inout.c


//Name: inout.c
//Autor: Bastian Schroll
//Date: 31.12.2014
//Description:
//Ließt 6 getrennte Servo Kanäle über INT0 und INT1 ein
//Die Eingangs Signale werden dem Programm per get_ch(KANAL) zur Verfügung gestellt
//Nach Verarbeitung der Signale können diese per set_ch(KANAL,PULSLÄNGE) wieder Ausgegeben werden
//Dabei werden die Signale auf PORTA.0 - .5 per Compare INT generiert


#include <avr/io.h>
#include <stdint.h>
#include <avr/interrupt.h>


#include "inout.h"


// **************************
// Prototypen
// **************************
void init_inout(void);//Initialisiert INT0 und INT1 sowie Timer1 und Timer0
uint16_t get_ch(uint8_t channel); //Liefert aktuellen Wert zurück
void set_ch(uint8_t channel, uint16_t puls); //Setzt Kanal 1-6 (Min. 1000 - Max. 2000)




// **************************
// Deklarationen
// **************************
//IN
volatile uint16_t (input_channel)[7]; //Array für die einzelnen Kanäle
uint8_t (fail_flag); //Flag für Fail Safe Modus


volatile uint16_t (input_channel_tmp)[7]; //Array für Timer1 Messwerte
uint8_t (in_ch_cnt); //Counter für aktuellen Kanal
uint8_t (fail_cnt); //Zähler für Fehlmessungen


//OUT
volatile uint16_t (output_channel)[7]; //Array für die einzelnen Kanäle


uint32_t (sig_rest) = 320000; //Wird auf 20ms gesetzt und dann wird jede Ch Ausgabe abgezogen. Was übrig bleibt muss Pause sein
uint8_t (out_ch_cnt); //Counter für aktuellen Kanal




void init_inout(void)
{
//PORTA als Ausgang
DDRA = 0b11111111;


//INT0 und INT1 als Eingang (vermutlich unnötig bei INT Konfiguration?)
DDRD &= ~((1 << DDD2) | (1 << DDD3));
//Beide INT auf steigene Flanke konfigurieren
MCUCR |= ((1 << ISC01) | (1 << ISC00)); //INT0
MCUCR |= ((1 << ISC11) | (1 << ISC10)); //INT1
//INT0 und INT1 aktivieren
GICR |= ((1 << INT0) | (1 << INT1));


//Timer0 auf Pres 256 (~4ms)
TCCR0 |= (1 << CS02);
//Timer0 Overflow INT aktivieren
TIMSK |= (1 << TOIE0);


//Timer1 Prescaler auf 1 stellen
TCCR1B |= (1 << CS10);
//Timer1 CompareA Interrupt aktivieren
TIMSK |= (1 << OCIE1A);


//Interrupts anschalten
sei();
};




//ISR von INT0
ISR(INT0_vect)
{
//speichere Zählerstand von Timer1
input_channel_tmp[in_ch_cnt] = TCNT1;
//Ch Zähler erhöhen
in_ch_cnt++;
//Timer0 zurücksetzen
TCNT0 = 0;
//bei letztem Kanal INT0 auf fallende Flanke
if(in_ch_cnt == 6){MCUCR &= ~(1 << ISC00);}
}




//ISR von INT1
ISR(INT1_vect)
{
//speichere Zählerstand von Timer1
input_channel_tmp[in_ch_cnt] = TCNT1;
//Ch Zähler erhöhen
in_ch_cnt++;
//Timer0 zurücksetzen
TCNT0 = 0;
//bei letztem Kanal INT0 auf fallende Flanke
if(in_ch_cnt == 6){MCUCR &= ~(1 << ISC10);}
}




//Erkennt die lange Synronitäts Pause im Eingangssignal
//Wenn länger als 4ms kein Interrupt passiert wird diese ISR geworfen.
ISR(TIMER0_OVF_vect)
{
//Prüfen ob wirklich alle INT kamen (Alle Signale erfasst)
if(in_ch_cnt == 7)
{
//Wenn ja, Fail Flag und Zähler zurücksetzen
fail_flag = 0;
fail_cnt = 0;

uint8_t i;
for (i=1; i<=6; i++){
//Aktuelles Eingangssignal = Endwert - Startwert
input_channel[i] = input_channel_tmp[i] - input_channel_tmp[i-1];
}
}
else
{
//Wenn mehr als 5 Fehlmessungen detektiert wurden, wird das Fail Flag gesetzt
fail_cnt++;
if(fail_cnt >= 5){fail_flag = 1;}
}
//Kanalzähler zurücksetzen
in_ch_cnt = 0;
//Beide INT auf steigene Flanke konfigurieren
MCUCR |= ((1 << ISC01) | (1 << ISC00)); //INT0
MCUCR |= ((1 << ISC11) | (1 << ISC10)); //INT1
}




//Kümmert sich um die Ausgabe der 6 Signale
ISR(TIMER1_COMPA_vect)
{
//Wenn Ch1 beginnt, PORTA.0 auf High
if(out_ch_cnt == 0)
{
PORTA = 1;
}


//Solange noch nicht alle Channel abgearbeitet
if(out_ch_cnt <= 6)
{
//Bit in PORTA weiter schieben
PORTA = (1 << out_ch_cnt);
//Kanal Zähler erhöhen
out_ch_cnt ++;
//CompareA Register von T1 auf den Wert des Kanals setzen
OCR1A = TCNT1 + output_channel[out_ch_cnt];
//Zeit von den 20ms Gesamtlänge abziehen
sig_rest -= output_channel[out_ch_cnt];
}
else //Wenn alle Kanäle durch sind
{
//PORTA abschalten
PORTA = 0;
//Prüfen ob noch mehr Zeit, als ein kompletter Durchlauf abzuwarten ist
if(sig_rest > 65536)
{
//Einen vollen Zyklus abwarten und vom Rest-Zeitzähler abziehen
OCR1A = TCNT1 + 65536;
sig_rest -= 65536;
}
else//Wenn kein kompletter Durchlauf mehr möglich ist
{
//Die restliche Zeit warten und alles für einen neuen Durchgang voreinstellen
OCR1A = TCNT1 + sig_rest;
out_ch_cnt = 0;
sig_rest = 320000 - 60000; //Entsprich 20ms bei Timer1 16MHZ/Prescaler1
//Da dir ISR natürlich auch ein wenig Rechnenzeit verbraucht ziehen wir einfach 60.000 Ticks ab
//Damit kommen wir dann laut Oszi auf exakt 20ms Versatz zwischen den Pulsen (ausporbiert)
}
}
}




//Liefert Wert des Kanals zurück
uint16_t get_ch(uint8_t channel)
{
return input_channel[channel] / 16;
}




//Zum setzen einzelner Kanäle
void set_ch(uint8_t channel, uint16_t puls)
{
//Wert auf Min und Max begrenzen
if(puls < 1000){puls = 1000;}
else if(puls > 2000){puls = 2000;}


output_channel[channel] = puls * 16;
}