Beim Betrachten von Skizzen, die andere Leute geschrieben haben, stoße ich gelegentlich auf Code, der ungefähr so aussieht:
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);
Ich weiß nur, dass es etwas mit Timing / Timern zu tun hat (glaube ich). Wie kann ich solchen Code entschlüsseln und erstellen? Was ist TCCR1A
, TCCR1B
, TCNT1
, CS12
, TIMSK1
, und TOIE1
?
timers
programming
Der Typ mit dem Hut
quelle
quelle
Antworten:
Das sieht nicht komisch aus. So sieht normaler MCU-Code tatsächlich aus.
Was Sie hier haben, ist ein Beispiel für das Konzept von Peripheriegeräten mit Speicherzuordnung . Grundsätzlich hat die MCU-Hardware spezielle Positionen im SRAM-Adressraum der ihr zugewiesenen MCU. Wenn Sie in diese Adressen schreiben, steuern die Bits des Bytes, das in die Adresse n geschrieben wurde, das Verhalten des Peripheriegeräts m .
Grundsätzlich haben bestimmte Speicherbänke buchstäblich kleine Drähte, die von der SRAM-Zelle zur Hardware verlaufen. Wenn Sie in dieses Byte eine "1" in dieses Bit schreiben, wird diese SRAM-Zelle auf ein logisches Hoch gesetzt, wodurch dann ein Teil der Hardware eingeschaltet wird.
Wenn Sie sich die Header für die MCU ansehen, gibt es große Tabellen mit Schlüsselwort- <-> Adresszuordnungen. So werden Dinge wie
TCCR1B
etc ... beim Kompilieren gelöst.Dieser Speicherzuordnungsmechanismus wird in MCUs äußerst häufig verwendet. Die ATmega-MCU im Arduino verwendet sie ebenso wie die MCU-Serien PIC, ARM, MSP430, STM32 und STM8 sowie viele MCUs, mit denen ich nicht sofort vertraut bin.
Arduino- Code ist das seltsame Zeug, mit Funktionen, die indirekt auf die MCU-Steuerregister zugreifen. Dies sieht zwar etwas "schöner" aus, ist aber auch viel langsamer und benötigt viel mehr Programmraum.
Die mysteriösen Konstanten sind alle im ATmega328P-Datenblatt ausführlich beschrieben , das Sie unbedingt lesen sollten, wenn Sie mehr als nur gelegentlich das Umschalten von Stiften auf einem Arduino tun möchten.
Wählen Sie Auszüge aus dem oben verlinkten Datenblatt aus:
So setzt zum Beispiel
TIMSK1 |= (1 << TOIE1);
das BitTOIE1
inTIMSK1
. Dies wird erreicht, indem die binäre 1 (0b00000001
) umTOIE1
Bits nach links verschoben wird , wobeiTOIE1
sie in einer Header-Datei als 0 definiert wird. Dies wird dann bitweise in den aktuellen Wert von ODER-verknüpftTIMSK1
, wodurch dieses Bit effektiv hoch gesetzt wird.Wenn
TIMSK1
wir uns die Dokumentation für Bit 0 von ansehen, können wir sehen, dass es als beschrieben wirdAlle anderen Zeilen sollten auf die gleiche Weise interpretiert werden.
Einige Notizen:
Sie können auch Dinge wie sehen
TIMSK1 |= _BV(TOIE1);
._BV()
ist ein häufig verwendetes Makro, das ursprünglich aus der AVR libc-Implementierung stammt ._BV(TOIE1)
ist funktional identisch mit(1 << TOIE1)
, mit dem Vorteil einer besseren Lesbarkeit.Möglicherweise sehen Sie auch Zeilen wie:
TIMSK1 &= ~(1 << TOIE1);
oderTIMSK1 &= ~_BV(TOIE1);
. Dies hat die entgegengesetzte FunktionTIMSK1 |= _BV(TOIE1);
, indem er führt zum Löschen des BitsTOIE1
inTIMSK1
. Dies wird erreicht, indem die von erzeugte Bitmaske genommen_BV(TOIE1)
, eine bitweise NOT-Operation an ihr ausgeführt wird (~
) und dannTIMSK1
durch diesen NOTed-Wert (0b11111110) UND-verknüpft wird.Beachten Sie, dass in all diesen Fällen der Wert von Dingen wie
(1 << TOIE1)
oder_BV(TOIE1)
zur Kompilierungszeit vollständig aufgelöst wird , sodass sie funktional auf eine einfache Konstante reduziert werden und daher zur Laufzeit keine Ausführungszeit für die Berechnung benötigen.Richtig geschriebener Code enthält im Allgemeinen Kommentare, die dem Code entsprechen und genau angeben, wozu die Register zugewiesen werden. Hier ist eine ziemlich einfache Soft-SPI-Routine, die ich kürzlich geschrieben habe:
PORTC
ist das Register, das den Wert der Ausgangspins imPORTC
ATmega328P steuert.PINC
ist das Register, in dem die Eingabewerte vonPORTC
verfügbar sind. Grundsätzlich passieren solche Dinge intern, wenn Sie die FunktionendigitalWrite
oderdigitalRead
verwenden. Es gibt jedoch eine Suchoperation, die die "PIN-Nummern" des Arduino in tatsächliche Hardware-Pin-Nummern umwandelt, was irgendwo im Bereich von 50 Taktzyklen dauert. Wie Sie wahrscheinlich erraten können, ist es ein bisschen lächerlich, 50 Taktzyklen für eine Operation zu verschwenden, für die nur 1 erforderlich sein sollte, wenn Sie versuchen, schnell zu fahren.Die obige Funktion benötigt wahrscheinlich irgendwo im Bereich von 100-200 Taktzyklen, um 8 Bits zu übertragen. Dies beinhaltet 24 Pin-Schreibvorgänge und 8 Lesevorgänge. Dies ist um ein Vielfaches schneller als die Verwendung der
digital{stuff}
Funktionen.quelle
TCCR1A
ist Timer / Zähler 1 Steuerregister A.TCCR1B
ist Timer / Zähler 1 Steuerregister B.TCNT1
ist der Zählerwert von Timer / Zähler 1CS12
ist das 3. Taktauswahlbit für Timer / Zähler 1TIMSK1
ist das Interrupt-Maskenregister von Timer / Zähler 1TOIE1
ist die Überlaufunterbrechungsfreigabe für Timer / Zähler 1Der Code aktiviert also Timer / Zähler 1 bei 62,5 kHz und setzt den Wert auf 34286. Dann aktiviert er den Überlauf-Interrupt. Wenn er 65535 erreicht, wird die Interrupt-Funktion ausgelöst, die höchstwahrscheinlich als gekennzeichnet ist
ISR(timer0_overflow_vect)
quelle
CS12 hat den Wert 2, da es Bit 2 des TCCR1B-Registers darstellt.
(1 << CS12) nimmt den Wert 1 (0b00000001) und verschiebt ihn zweimal nach links, um (0b00000100) zu erhalten. Die Reihenfolge der Operationen schreibt vor, dass die Dinge in () zuerst geschehen. Dies erfolgt also, bevor das "| =" ausgewertet wird.
(1 << CS10) nimmt den Wert 1 (0b00000001) und verschiebt ihn 0 Mal nach links, um (0b00000001) zu erhalten. Die Reihenfolge der Operationen schreibt vor, dass die Dinge in () zuerst geschehen. Dies erfolgt also, bevor das "| =" ausgewertet wird.
Jetzt erhalten wir also TCCR1B | = 0b00000101, was mit TCCR1B = TCCR1B | identisch ist 0b00000101.
Seit "|" ist "ODER", alle Bits außer CS12 in TCCR1B sind nicht betroffen.
quelle