Ethernut Home Hardware Firmware Tools Download Community
 
Ethernuts  /  Ethernut 3  /  Timer  /  Einfacher Rechteckgenerator
Suche | Impressum | English

Einfacher Rechteckgenerator

Programmschleife

Es gibt eine Reihe von Möglichkeiten, an einem Pin des Expansionports eine Rechteckschwingung zu erzeugen. Die einfachste ist sicher, den Pin als Ausgang zu konfigurieren und diesen in einer Softwareschleife periodisch ein- und auszuschalten.

Die folgende Routine erzeugt eine Frequenz von etwa 8 MHz an einem gegebenen Pin:

/*
 * Generate square wave at given port bit.
 *
 * Parameter bit specifies the port bit number.
 */
void LoopToggle(int bit)
{
    outr(PIO_OER, _BV(bit));
    for (;;) {
        outr(PIO_SODR, _BV(bit));
        outr(PIO_CODR, _BV(bit));
    }
}

Der Vorteil ist hier, dass für die Ausgabe jeder freie GPIO-Pin am Expansionport verwendbar ist. Achten Sie unbedingt darauf, dass einige Pins bereits auf dem Board selbst verwendet werden. Einige Pins dürfen auf keinen Fall als Ausgang konfiguriert werden. Es besteht die Gefahr, dass das Board zerstört wird. Portbit 4 ist z.B. eine gute Wahl. Der entsprechende Aufruf der Funktion wäre dann:

LoopToggle(4);

In echten Anwendungen ist diese Routine aber praktisch unbrauchbar. Die Endlosschleife verhindert nicht nur, dass die Funktion zum Aufrufer zurückkehrt, sondern blockiert auch alle übrigen Threads des Betriebssystems. Dagegen werden mögliche Interrupts weiterhin behandelt. Da diese die Schleife an beliebiger Stelle unterbrechen können, wird die Ausgabe dadurch gestört und die Frequenz unterliegt kurzen Schwankungen. Überhaupt ist die tatsächlich ausgegebene Frequenz zu einem gewissen Grad dem Zufall überlassen, da Sie davon abhängt, welchen Binärcode der Compiler aus den C-Anweisungen erstellt. Und die Kurvenform ist mit Sicherheit asymetrisch (Puls-/Pausenverhältnis), da die CPU zusätzliche Befehle benötigt, um zum Anfang der Schleife zurück zu springen.

Einfacher Hardwaretimer

Die CPU auf Ethernut 3 stellt drei 16-Bit Timer/Counter zur Verfügung, die mit TC0, TC1 und TC2 bezeichnet werden. TC0 wird bereits als Systemtimer verwendet, steht also für eigene Anwendungen, die unter Nut/OS laufen, nicht zur Verfügung. Mit den beiden übrigen Timern kann man exakte Rechteckschwingungen verschiedener Frequenzen generieren, die auf bestimmten Pins ausgegeben werden können.

Für unser erstes Beispiel wählen wir TC1, mit dem eine Rechteckschwingung an Portbit 4 erzeugt werden kann. Dazu muss dieses Bit in den Peripheriemodus geschaltet werden. Man erreicht dies dadurch, dass man Bit 4 im PIO Disable Register setzt:

outr(PIO_PDR, _BV(4));

Als nächstes werden die Timer-Register konfiguriert. Grundsätzlich kann man TC1 im Capture Mode oder im Waveform Mode betreiben. Ersterer wird eher zum Messen von Frequenzen genutzt, für die Frequenzausgabe eignet sich letzterer. Dazu setzen wir im Clock Mode Register das Bit TC_WAVE. Im gleichen Register wird festgelegt, aus welcher Quelle der Timer gespeist wird (Bit TC_CLKS_MCK2 für halbe CPU-Taktfrequenz) und was passieren soll, wenn der Zähler einen bestimmten Wert erreicht hat (TC_ACPA_TOGGLE_OUTPUT).

outr(TC1_CMR, TC_WAVE | TC_ACPA_TOGGLE_OUTPUT | TC_CLKS_MCK2);

Das ganze soll nun so funktionieren: Der CPU-Takt wird durch 2 geteilt und inkrementiert mit jeder steigenden Flanke einen 16-Bit Zähler. Erreicht dieser Zähler einen bestimmten Wert, wird der Ausgangspin umgeschaltet. Der Zähler läuft weiter und springt beim Überlauf (nach 0xFFFF oder 65535) auf Null zurück. Der Vorgang wiederholt sich und erzeugt am Ausgang eine Rechteckschwingung mit Quarz-genauer Frequenz.

Machen Sie sich keine Sorgen, wenn das alles verwirrend klingt. Sie können die Einstellungen später variieren, um ein Gefühl dafür zu bekommen, welche Möglichkeiten diese Hardwarefunktion bietet. Weitere Informationen zum Peripheriemodus und den Timer-/Counter-Registern finden Sie im Datenblatt der CPU. Die unter Nut/OS verwendeten Symbole entsprechen, soweit möglich, den im Datenblatt verwendeten Namen und sind in der Header-Datei arch/arm/atmel/at91_tc.h definiert.

Das Bit TC_ACPA_TOGGLE_OUTPUT im Register TC1_CMR bestimmt, dass der Ausgang umgeschaltet werden soll, sobald der Zähler den im Register TC1_RA gespeicherten Wert erreicht hat. Zwar ist dieser Wert zunächst ohne Bedeutung, da jedes mal ein vollständiger Zählerdurchlauf notwendig ist, um den Ausgang von Low nach High oder zurück nach Low zu schalten. Dummerweise funktioniert das aber nur, wenn der Wert in TC1_RA ungleich Null ist.

outr(TC1_RA, 0x8000);

Nun müssen wir den Zähler nur noch aktivieren und starten. Ersteres geschieht mit

outr(TC1_CCR, TC_CLKEN);

Und gestartet wird der Zähler mit

outr(TC1_CCR, TC_SWTRG);

Wenn Sie nun die Messspitze eines Oszilloskops an P4 am Expansionport halten, sollten Sie die Rechteckschwingung erkennen können. Die tatsächliche Frequenz lässt sich wie folgt berechnen:

Natürlich hat die Genauigkeit Ihre Grenzen. Der CPU-Takt wird auf Ethernut 3 von einer PLL erzeugt, die von einem 25 MHz Quarz gespeist wird. Dieser Quarz hat gewisse Ungenauigkeiten, die von der Umgebungstemperatur abhängig sind.

Die Frequenz lässt sich nur in sehr begrenztem Rahmen variieren, nämlich durch Änderung des Vorteilers. Die folgende Funktion fasst die genannten Befehle in einer Funktion zusammenfassen, wobei der Vorteiler als Parameter übergeben wird.

/*
 * Generate waveform at TIO1A.
 *
 * The parameter clksrc specifies the source of the timer clock.
 * It may be one of the following:
 * - TC_CLKS_MCK2 selects CPU clock divided by 2
 * - TC_CLKS_MCK8 selects CPU clock divided by 8
 * - TC_CLKS_MCK32 selects CPU clock divided by 32
 * - TC_CLKS_MCK128 selects CPU clock divided by 128
 * - TC_CLKS_MCK1024 selects CPU clock divided by 1024
 */
void SimpleWave1A(uint32_t clksrc)
{
    outr(PIO_PDR, _BV(4));
    outr(TC1_CMR, TC_WAVE | TC_ACPA_TOGGLE_OUTPUT | clksrc);
    outr(TC1_RA, 0x8000);
    outr(TC1_CCR, TC_CLKEN);
    outr(TC1_CCR, TC_SWTRG);
}

Nach Aufruf dieser Funktion läuft der Vorgang rein in Hardware und die CPU kann sich anderen Aufgaben widmen. Zur Überprüfung der Funktion kann man parallel in einer Schleife den aktuellen Stand des Zählers ausgeben:

#include <dev/board.h>
#include <sys/timer.h>
#include <stdio.h>

int main(void)
{
    NutRegisterDevice(&DEV_DEBUG, 0, 0);
    freopen(DEV_DEBUG_NAME, "w", stdout);
    puts("Simple Hardware Timer");

    SimpleWave1A(TC_CLKS_MCK1024);
    for (;;) {
        printf("%04lx\n", inr(TC1_CV));
    }
}

Die ausgegebene Schwingung ist symetrisch und liegt beim Aufruf der Funktion mit TC_CLKS_MCK1024 bei etwa 550 mHz (Milli nicht Mega!). Mit der im folgenden Artikel beschriebenen Konfiguration werden feinere Frequenzeinstellungen möglich und das Impuls-/Pausen-Verhältnis lässt sich in weiten Bereichen einstellen.