ich möchte an einem Arduino Pin eine Rechteckfrequenz von 38kHz ausgeben. Dafür ist micros() leider zu langsam, da ich den Chip auch nur mit maximal 8MHz schwingen lassen kann. Kennt jemand ein gutes, anfängerfreundliches Tutorial, dass die Verwednung der Hardwaretimer gut beschreibt. Das was ich bisher gefunden habe bei google steigt alles relativ weit fortgeschritten ein, und es bringt mir sicherlich für die Zukunft nicht viel, wenn es zwar funktioniert, ich bei der Hälfte aber nicht weiß warum. Also kennt jemand ein gutes Tutorial, oder würdet ihr eine Rechteckfrequenz vieleicht ganz anders machen?
Dazu auch immer das Datenblatt des Prozessors ansehen! Gerade auf dem ATtiny können die Register schon mal etwas anders aufgebaut sein. Aber die Grundlagen sind gleich.
Beachte dabei, dass du da i.d.R reinen AVR Code findest. Das heißt es gibt main() und eine while(1) statt loop(). Und das schalten von Pins wird da mit direkter Port-Manipulation gemacht. Aber der Timer Kram an sich ist gleich.
Was du willst ist der CTC Modus. Dabei zählt der Timer hoch, wird mit einem bestimmten Wert verglichen und löst dann einen Interrupt aus. Mit diesem Interrupt kann man automatisch einen Port-Pin toggeln. Letzteres muss man über ein bestimmtes Bit in einem der Control Register aktivieren. Die Compare Match Control Bits.
Da nimmt man normalerweise Timer2, da der die meisten Prescaler Optionen hat. Außerdem läuft auf Timer0 schon millis().
sven222:
... 38kHz ...
Also kennt jemand ein gutes Tutorial, oder würdet ihr eine Rechteckfrequenz vieleicht ganz anders machen?
38 kHz ist die Standardfrequenz von Infrarot-Fernbedienungen, dafür sollte eine Funktion aus der IR-Library reichen, um eine solche Frequenz zu erzeugen. Einfach eine fertige Funktion benutzen.
Das funktioniert selbst dann, wenn man keine Ahnung von Timern hat, keine einschlägige Fachausbildung um mit dem Atmega-Datenblatt klarzukommen und nicht mal ausreichende Kenntnisse, um einem Timer-Tutorial im Internet folgen zu können.
Danke euch beiden. Das Tutorial von mc.net kannte ich schon, aber da fehlt mir irgendwo der Zugang. Die beiden englischsprachigen werde ich mir mal anschauen. Vorallem das von den AVRFreaks sieht vielversprechend für meinen Kenntnisstand aus. Das mit der IR Library ist prinzipiell eine gute Idee, aber dann habe ich ja wieder nichts gelernt. Es soll ja irgendwann soweit kommen, dass ich auch komplexere Tutorials verstehe, oder irgendwann die Datenblätter.
Grüße,
Das 38 kHz Signal an sich zu erzeugen ist keine große Sache.
Wobei das die IRRemote Lib nicht mit CTC macht, sondern über PWM mit einem Tastverhältnis von 1:1. Die 38 kHz beziehen sich ja auf den Abstand von einer positiven (bzw. negativen) Flanke zu andern. Und dazwischen muss man toggeln. Also braucht man mit CTC die doppelte Frequenz. Daher geht das mit reiner Hardware PWM etwas einfacher. Theoretisch zumindest.
Die PWM Modi finde ich aber sehr kompliziert (da es da zig verschiedene Modi gibt) und das ist der einzige Teil den ich nicht im Detail verstehe. Da steht aber ich auch hier was: http://maxembedded.com/2011/08/07/avr-timers-pwm-mode-part-i/ http://maxembedded.com/2012/01/07/avr-timers-pwm-mode-part-ii/
Im Teil 2 gibt es fertigen Code. Probier aber zum Einstieg erst mal eine LED im CTC Modus blinken zu lassen oder ähnliches. Da lernst du auch was. Außerdem geht PWM auch so ähnlich wie CTC, nur dass es da ein Compare Register für Low gibt und eins für High. Also wenn du CTC verstehst bist du schon mal auch dem richtigen Weg auch PWM zu verstehen.
Dann hast du aber das Problem, dass du deine Daten noch auch das 38kHz Signal modulieren musst. Eine einfache Lichtschranke die immer läuft ist einfach, aber dann musst du das Signal ja noch irgendwie ein- und ausschalten
Ja, PWM wäre ideal, aber ich fange mal Stück für Stück an. Das Aufmodulieren des Signals soll Hardwaremäßig passieren wie hier: dirt cheap wireless | CHEAP, FAT and OPEN
Da sind auch Codebeispiele dabei, aber ohne Erläuterung. Ich lese mir gerade das erste Tutorial von maxembedded durch, und das scheint mir so, dass ich was damit anfangen kann.
Pass halt wie gesagt damit auf dass das reines AVR C ist und verwende statt main() und while(1) das Arduino spezifische loop(). Und statt den init() Methoden setup().
Genaus kannst du du dir die includes am Anfang sparen.
Aber das ist das einzige was unterschiedlich zum Arduino ist.
EDIT:
Außerdem solltest du das am Ende auf Timer1 oder Timer2 laufen lassen. Timer0 kann man aber mal zum Test probieren. Die Timer sind zwar ähnlich aufgebaut, aber haben zum Teil leicht andere Optionen. Timer2 hat z.B. andere Prescaler Optionen (der Vorteiler für die Frequenz) und die PWM Modi sind glaube auch leicht anders also auf den anderen. Das ist im Datenblatt ersichtlich, wenn nach den Register-Bezeichnungen suchst.
Das ist halt so Standard und vor allem kann kann die Bits mit ihren Namen ansprechen. Dadurch ist besser ersichtlich was da überhaupt gemacht wird. CS00 und ähnliche Makros ist die Nummer des entsprechenden Bits. z.B. das 5. Bit hat den Wert 4.
Wenn man dann die 1 vier mal nach links schiebt hat man aus 0000 0001 -> 0001 0000 gemacht.
Dafür gibt es auch das _BV() Makro (für Bit Value). Das übernimmt das Schieben für dich:
TCCR0 |= _BV(CS00) | _BV(CS01);
Auf Null setzen geht mit einem Und auf den invertierten Wert
TCCR0 &= ~_BV(...);
Der Compiler übersetzt das dann schon in Befehle die nur einen Takt-Zyklus brauchen.
Das mit dem Makro ist wirklich elegant, das ist einfach gut lesbar. Welches Bit in welchem Byte gesetzt wid. Schön. Da versteht man dann auch noch in zwei Monaten, was man beim Programmieren damit erreichen wollte. Wenn ich ein Timer Register im setup initialisieren will, kann ich das dann auch direkt beschreiben? Also falls ich das Bit0 und das Bit7 von TCCR0 setzen will, und der Rest 0 sein soll, könnte ich das dann auch so machen?
TCCR0 = 0x81;
Später garantiert nicht mehr lesbar, vorallem wenn man nicht anständig kommentiert, aber mir fällt gerade kein Grund ein, warum das nicht auch funktionieren sollte.
Funktionieren tut es, aber es ist halt kaum bis gar nicht verständlich ohne ins Datenblatt zu gucken. Die Namen der Bits hat man eher im Kopf also wo diese genau stehen. Und wenn jemand anders deinen Code ansieht, versteht er ihn auch nicht.
Ich würde auch in den meisten Fällen den Compound Oder Operator |= verwenden statt eine einfache Zuweisung, da damit die restlichen Bits so belassen werden wie sie gerade sind. Das ist nicht immer nötig (vor allem am Anfang, da die Register i.d.R. auf 0 stehen), aber meiner Meinung auch guter Stil, da man so auch nicht überlegen muss ob die anderen Bits vielleicht gesetzt sind und so bleiben müssen. Das ist vor allem wichtig wenn man im Betrieb Bits verändert und nicht nur in der Setup Phase.
Da stimme ich mit Dir überein. Menschenlesbarkeit ist wichtig. Ich wollte nur wissen, ob theoretisch etwas dagegen spricht. Danke für Deine wie immer gute und verständliche Hilfe!
Ich habe mir jetzt mal etwas zusammen gesponnen, von dem ich ausgehe, dass es funktionieren könnte. Sieht irgendjemand noch irgendwo einen größeren Fehler, dass es vieleicht doch nicht geht? Es soll eine 38kHz (ca 38080Hz) Schwingung auf einem ATTiny45 mit 8MHz auf PB1 (Pin6) erzeugen.
Als grober Anfang mal. Aber nicht wirklich zu gebrauchen. Das ist so ineffizient wie es nur geht
Fehler 1: DDRB ist das Direction Register. Damit schaltest du Ausgang/Eingang um. Das Ausgangsregister ist PORTB
Fehler 2: Das Counter Register von Timer1 ist TCNT1
Fehler 3: PB1 == LOW geht nicht. Du musst das Eingangsregister PINB einlesen. z.B. bitRead(PINB, PB1). PB1 ist nur "1". Also die Nummer des entsprechenden Bits.
OCR1C wird da nie verwendet.
Wenn, dann muss das in einen Interrupt. Und das entsprechende Interrupt Enable Bit im TIMSK Register aktiviert werden. Dazu noch CTC1 im TCCR1 Register. Der Compare Wert kommt dann ins OCR1A Register. Dann wird der Interrupt ausgelöst wenn TCNT1 == OCR1A. Um da auf 38 kHz zu kommen, brauchst du aber die doppelte Frequenz, da du bei der Hälfte umschalten musst.
Einfacher geht das Umschalten wenn du den Pin mit einem XOR toggelst. Oder am schnellsten wenn du eine 1 auf das Pin-Eingangsregister PINB schreibst (hört sich falsch an, ist aber eine dokumentierte Sonderfunktion. Datenblatt S.53).
Wenn das mal läuft schau dir die Comparator A Output Mode Bits im TCCR1A Register an. Du kannst PB1 automatisch bei einem Compare Match toggeln ohne dass du irgendwas machst und auch ohne dass ein Interrupt ausgelöst wird. Siehe Seite 100.
Und wenn das geht versuche PWM. Das ist im Datenblatt im Gegensatz zum Atmel328 Datenblatt einigermaßen gut erklärt. Da wird mit dem OCR1C die Frequenz eingestellt (dazu gibt es eine Formel und eine Tabelle. Seite 99) und mit OCR1A oder OCR1B stellt man das Tastverhältnis ein. Wenn also das Compare Register die Hälfte von OCR1C hat, hat man ein Verhältnis von 1:1, bzw. ein normales Rechtecksignal. Die Pins toggeln dann automatisch je nach Einstellung.
So, da habe ich jetzt erstmal was zum Knabbern. Das noch DInge vom Timer 0 abgefragt werden liegt wohl daran, dass man nach einem Arbeitstag Nachts um zwülf nicht mehr ganz so fit ist, der Rest der Fehler daran, dass ich wohl einfach das Thema immer noch nicht ganz verstanden habe. Danke, ich werde mir Deine Hilfe jetzt Zeile für Zeile zu Gemüte führen und schuen, dass ich das dan auch richtig kapiere.
Wenn du Fehler 1 bis 3 behebst, könnte sich was tun.
Aber das ist nur als erster Schritt im Lernen der Timer zu gebrauchen. Das ständig loop() abzufragen bringt halt nicht viel in der Praxis. Der Sinn der Timer ist dass die etwas selbstständig erledigen.
Hast du ein Oszilloskop? Wenn nicht solltest du vielleicht erst mal mit ganz langsamen Frequenzen anfangen um zu sehen ob was an einer normalen LED blinkt.
EDIT:
Du kannst das erst mal so machen, aber dann musst du schrittweise verbessern:
1.) Wie du es jetzt hast das Counter Register per Hand auslesen und zurücksetzen
2.) Interrupt-gesteuerter CTC Modus, der das Register automatisch auf 0 setzt. Es wird nur was gemacht wenn wirklich nötig. Schalten des Pins erfolgt noch per Hand
3.) Schalten des Pins automatisch mit den Compare Output Mode Bits. Im Gegensatz zu 2. wird hier kein Interrupt mehr ausgelöst (also das Interrupt Enable Bit ist hier nicht gesetzt).
Das sind auch die Schritte im MaxEmbedded Tutorial. 3. ist praktisch schon brauchbar, aber die IRRemote Lib verwendet dafür PWM. Damit ist die Frequenz etwas besser einstellbar.
Jetzt bin ich mir nicht ganz sicher, ob ich das Datenblatt richtig verstanden habe, weil es mir zu einfach vorkommt.
Auf Seite 100 wird das Bit COM1A0 aus dem TCCR1 beschrieben mit den Worten:
Toggle the OC1A output line.
Dann steht noch dabei:
Since this is an alternative function to an
I/O port, the corresponding direction control bit must be set (one) in order to control an output pin.
Habe ich das jetzt richtig verstanden, und folgender Code könnte schon tun was ich will?
Mit dem pinMode schalte ich den PB1 (OC1A) als Ausgang, das könnte ich ja auch über das passende Register machen, ich will aber soviel wie möglich im Arduinosystem belassen. Dann setze ich die beiden bits des Timerregisters, und beschreibe den OCR1A (Im Datenblatt steht "Compare register A", ich hoffe, dass das dasselbe ist) mit den Taktanzahlen für die halbe Frequenz, und dann wird der PIN PB1 permanent mit 38kHz getaktet. Stimmt das so, oder habe ich mich jetzt komplett verrannt?
Grüße,
Sven
EDIT: Hallo Serenifly, ich sah gerade erst, dass Du nochmal was zu meinem stümperhaften Programmierversuchen geschrieben hast, und mir echt gute Tipps in die richtige Richtung schreibst, damit ich auch was verstehe, und nicht nur Code abschreibe. Danke dafür. Kann man einem Menschen für das gleiche Thema mehrfach Karma zukommen lassen? Oder ist das unseriös?
Ja, damit ist gemeint, dass man den Pin erst mal auf Ausgang schalten muss. Dafür pinMode() zu nehmen ist völlig ok. Du solltest nur auf digitalWrite() verzichten, da das langsam ist. Zumindest in ISRs, wenn es wirklich schnell gehen soll.
Du musst jetzt noch das CTC1 Bit in TCCR1 setzen (Clear Timer on Compare Match), damit das Counter Register automatisch auf 0 zurück gesetzt wird wenn ein Compare Match erfolgt. Ansonsten zählt der Timer bis 255 weiter.
Bei CTC brauchst du einen Compare Match alle 13,16µs, sofern ich das richtig sehe (bei einer Periodendauer von ca. 26,3µs). 1 / 8 MHz * 105 = 13,125µs. Das sollte also theoretisch gehen
Da ist wie gesagt ein Oszi gut. Da sieht man genau was gemacht wird. Sonst ist etwas problematisch rauszufinden was man da jetzt genau eingestellt hat. Der wird was machen, aber eventuell nicht auf der korrekten Frequenz.
Alternativ wie gesagt mal versuchen lassen das alle paar hundert ms blinken zu lassen. Sollte problemlos gehen, da der ATtiny riesige Prescaler Einstellungen hat. Du kannst den Takt um bis zu 16384 runterteilen (auf dem Atmega max. 1024)! Damit geht langsames Blinken sogar auf einem 8 Bit Timer ohne den Timerüberlauf auszuwerten.
8 MHz / 16384 = ca. 488 Hz -> 2ms pro Timer Tick. Mit Compare Match bei 250 hast du damit 500ms. Das ist auf einer LED schon geradezu langsam. Damit siehst du erst mal was abläuft und ob sich was tut
Der Vater meiner Freundin hat sein Oszilloskop leider verkauft, und wir haben selbst noch keines. Da meine Freundin aber sowieso gerne eines hätte, wird es wohl nicht mehr lange dauern, bis wir uns eines kaufen. Bis dahin werde ich das mit dem runterscalen auf alle Fälle mal versuchen, damit man was sieht, habe aber leider im Moment weder Arduino noch ISP noch ATTiny hier, und kann erst am nächsten Wochenende testen.
Mir gefällt, dass das ganze jetzt ein Dreizeiler ist, und der Loop ist noch komplett leer. Da die Timer ja außerhalb des restlichen CPU Ablaufs funktionieren könnte ich im Loop ja jetzt noch so allerhand sogar mit delay() machen. Da muss mir unbedingt noch was einfallen, und wenn es nur ein paar blinkende LEDs sind
Danke nochmal für Deine tolle, fundierte und geduldige Hilfestellung!