Könnte jemand diesen seltsam aussehenden Code erklären, der zum Einrichten von Timern verwendet wird?

10

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?

Der Typ mit dem Hut
quelle
Ich weiß nicht genug, um zu antworten, aber: electronic.stackexchange.com/questions/92350/… , forum.arduino.cc/index.php?topic=134602.0 und stackoverflow.com/questions/9475482/… . Ich weiß nicht, ob Sie diese bereits gesehen haben.
Anonymer Pinguin
1
Laden Sie das Datenblatt "Vollständig" für Ihr Gerät von der Atmel-Website herunter und lesen Sie die Kapitel über Timer. Das Datenblatt ist meiner Meinung nach überraschend gut zu lesen.
Jippie

Antworten:

15

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 TCCR1Betc ... 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:

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

So setzt zum Beispiel TIMSK1 |= (1 << TOIE1);das Bit TOIE1in TIMSK1. Dies wird erreicht, indem die binäre 1 ( 0b00000001) um TOIE1Bits nach links verschoben wird , wobei TOIE1sie in einer Header-Datei als 0 definiert wird. Dies wird dann bitweise in den aktuellen Wert von ODER-verknüpft TIMSK1, wodurch dieses Bit effektiv hoch gesetzt wird.

Wenn TIMSK1wir uns die Dokumentation für Bit 0 von ansehen, können wir sehen, dass es als beschrieben wird

Wenn dieses Bit in eins geschrieben wird und das I-Flag im Statusregister gesetzt ist (Interrupts global aktiviert), wird der Timer / Counter1-Überlauf-Interrupt aktiviert. Der entsprechende Interrupt-Vektor (siehe „Interrupts“ auf Seite 57) wird ausgeführt, wenn das in TIFR1 befindliche TOV1-Flag gesetzt ist.

Alle 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);oder TIMSK1 &= ~_BV(TOIE1);. Dies hat die entgegengesetzte Funktion TIMSK1 |= _BV(TOIE1);, indem er führt zum Löschen des Bits TOIE1in TIMSK1. Dies wird erreicht, indem die von erzeugte Bitmaske genommen _BV(TOIE1), eine bitweise NOT-Operation an ihr ausgeführt wird ( ~) und dann TIMSK1durch 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:

uint8_t transactByteADC(uint8_t outByte)
{
    // Transfers one byte to the ADC, and receives one byte at the same time
    // does nothing with the chip-select
    // MSB first, data clocked on the rising edge

    uint8_t loopCnt;
    uint8_t retDat = 0;

    for (loopCnt = 0; loopCnt < 8; loopCnt++)
    {
        if (outByte & 0x80)         // if current bit is high
            PORTC |= _BV(ADC_MOSI);     // set data line
        else
            PORTC &= ~(_BV(ADC_MOSI));  // else unset it

        outByte <<= 1;              // and shift the output data over for the next iteration
        retDat <<= 1;               // shift over the data read back

        PORTC |= _BV(ADC_SCK);          // Set the clock high

        if (PINC & _BV(ADC_MISO))       // sample the input line
            retDat |= 0x01;         // and set the bit in the retval if the input is high

        PORTC &= ~(_BV(ADC_SCK));       // set clock low
    }
    return retDat;
}

PORTCist das Register, das den Wert der Ausgangspins im PORTCATmega328P steuert. PINCist das Register, in dem die Eingabewerte von PORTCverfügbar sind. Grundsätzlich passieren solche Dinge intern, wenn Sie die Funktionen digitalWriteoder digitalReadverwenden. 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.

Connor Wolf
quelle
Beachten Sie, dass dieser Code auch mit Atmega32u4 (in Leonardo verwendet) funktionieren sollte, da er mehr Timer als ATmega328P enthält.
jfpoilpret
1
@ Ricardo - Wahrscheinlich verwenden mehr als 90% des eingebetteten Codes für kleine MCUs die direkte Registermanipulation. Dinge mit indirekten Dienstprogrammfunktionen zu tun, ist nicht die übliche Art, E / A / Peripheriegeräte zu manipulieren. Es gibt einige Toolkits zum Abstrahieren der Hardwaresteuerung (z. B. Atmel ASF), die jedoch im Allgemeinen so geschrieben sind, dass sie so weit wie möglich kompiliert werden, um den Laufzeitaufwand zu verringern, und fast immer das tatsächliche Verständnis der Peripheriegeräte durch Lesen der Datenblätter erfordern.
Connor Wolf
1
Grundsätzlich Arduino Sachen, mit den Worten „hier Funktionen, die X tun“, ohne wirklich die eigentliche Dokumentation zu verweisen stören oder wie die Hardware tut das , was er tut, ist nicht sehr viel normal. Ich verstehe, dass es als Einführungswerkzeug wertvoll ist, aber abgesehen vom schnellen Prototyping wird es in tatsächlichen professionellen Umgebungen nie wirklich durchgeführt.
Connor Wolf
1
Um es klar, dass die Sache Arduino Code ungewöhnlich für Embedded - MCU - Firmware macht nicht ist einzigartig auf Arduino Code, es ist eine Funktion des Gesamtkonzepts. Wenn Sie ein gutes Verständnis der eigentlichen MCU haben, dauert es im Grunde genommen wenig bis gar keine zusätzliche Zeit, die Dinge richtig zu machen (z. B. Hardware-Register direkt zu verwenden). Wenn Sie also echte MCU-Entwickler lernen möchten, ist es viel besser, sich einfach hinzusetzen und zu verstehen, was Ihre MCU tatsächlich tut, als sich auf die Abstraktion eines anderen zu verlassen , die dazu neigt, undicht zu sein.
Connor Wolf
1
Beachten Sie, dass ich hier vielleicht ein bisschen zynisch bin, aber viele der Verhaltensweisen, die ich in der Arduino-Community sehe, programmieren Anti-Patterns. Ich sehe viel "Copy-Paste" -Programmierung, die Behandlung von Bibliotheken als Blackbox und nur allgemeine schlechte Designpraktiken in der gesamten Community. Natürlich bin ich ziemlich aktiv bei EE.stackexchange, daher habe ich möglicherweise eine etwas schräge Ansicht, da ich einige Moderator-Tools habe und als solche viele der geschlossenen Fragen sehe. Die Arduino-Fragen, die ich dort gesehen habe, sind definitiv voreingenommen: "Sag mir, was C & P zu beheben hat", anstatt "Warum funktioniert das nicht?".
Connor Wolf
3

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 1

CS12 ist das 3. Taktauswahlbit für Timer / Zähler 1

TIMSK1 ist das Interrupt-Maskenregister von Timer / Zähler 1

TOIE1 ist die Überlaufunterbrechungsfreigabe für Timer / Zähler 1

Der 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)

Der Doktor
quelle
1

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.

VENKATESAN
quelle