Frequenzzähler/ Periodenmesser mit einem PIC32 – Teil 4

Als nächstes benötige ich die Zähler für das Eingangssignal. Praktischerweise besitzt der PIC32 neben dem Timer1, den ich für die Anzeige verwende, noch vier weitere 16 Bit Zähler, die sich zu zwei 32 Bit Zählern zusammenschalten lassen. Heute habe ich mich darum gekümmert, die Timer 2 und 3 als 32 Bit Zähler zu verwenden.

In einem ersten Schritt habe ich mich auf die Zusammenschaltung konzentriert und mit dem internen Takt wie beim Timer1 gearbeitet. Das geht relativ einfach und ist nach einem kurzen Blick in das Datenbuch leicht zu erledigen.

    OpenTimer23(T23_ON | T23_SOURCE_INT | T23_PS_1_1, 0xffffffff);

Statt Timer1 wird nun die Kombination aus Timer2 und Timer3 verwendet. Die vordefinierten Hilfsfunktionen und Konstanten sind erfreulicherweise dafür vorbereitet. Der Prescaler wird auf 1 gesetzt, der maximale Timerwert auf volle 32 Bit (die aber nicht komplett benötigen werde).

In der Hauptschleife warte ich nun immer rund eine halbe Sekunde, lese den Timer23 aus und übertrage den aktuellen Wert in den Bildspeicher.

    while(1) {
        int del;
        // wait for 500 milliseconds
        for (del = 0; del < 80000; del++);

        setInt(ReadTimer23());
    };

Das war der einfache Teil, da es im Internet eine Vielzahl von Beispielen gibt, wie man einen Timer mit einem internen Takt verwendet. Für einen Frequenzzähler benötige aber einen Zähler für einen externen Takt. An dieser Stelle habe ich erst mal einen Schreck bekommen, da mir klar wurde, dass ich die Pins für die Ausgangssignale ohne Rücksicht darauf verteilt habe, ob sie eventuell auch von den Timern benötigt werden. Aber ich hatte Glück – das Timer2 Clock Signal, welches auch Eingang für den kombinierten Timer 2 + 3 ist, hat als Eingang einen programmierbaren Pin (PPS). Da ich auf Anhieb kein Beispiel dafür im Internet gefunden habe, musste ich mich jetzt also erst mal mit der Programmierung der Pinbelegung auseinandersetzen. Erfreulicherweise ist es dann aber doch recht einfach. Es gibt zu jedem Eingang eines Funktionsbausteins eine Liste mit den möglichen Pins. Aus dieser Liste muss man einen passenden Pin auswählen, den Mapping-Wert auslesen und in ein Register eintragen. Das war alles – wenn man es erst mal verstanden hat ist es ganz simpel: T3CKRbits.T3CKR = 0;.

void startCounter() {
    OpenTimer23(T23_ON | T23_SOURCE_EXT | T23_PS_1_1, 0xffffffff);
    T3CKRbits.T3CKR = 0; // RA0
}

Wie man im Video sieht, habe ich mittlerweile auch das im Teil 3 beschriebene Übersprechen zwischen den Anzeigestellen in Griff. Das Flackern kommt durch eine Überlagerung der Kamerafrequenz mit der Anzeigefrequenz, es ist im Original nicht sichtbar. Die Wiederholfrequenz liegt bei ca. 90 Hertz.

Aktueller Software Stand:

/* 
 * File:   main.c
 * Author: Matthias Thiele
 *
 * Created on 23. Januar 2015, 21:08
 */

#include <stdio.h>
#include <stdlib.h>
#include <plib.h>

// Configuration Bit settings
// SYSCLK = 80 MHz (8MHz Crystal/ FPLLIDIV * FPLLMUL / FPLLODIV)
// PBCLK = 40 MHz
// Primary Osc w/PLL (XT+,HS+,EC+PLL)
// WDT OFF
// Other options are don't care
//
#pragma config FPLLMUL = MUL_20, FPLLIDIV = DIV_2, FPLLODIV = DIV_1, FWDTEN = OFF
#pragma config POSCMOD = HS, FNOSC = PRIPLL, FPBDIV = DIV_8
#pragma config OSCIOFNC = OFF // CLKO Output Signal Active on the OSCO disabled
#pragma config JTAGEN = OFF // JTAG Enable (JTAG Disabled)

#define SYS_FREQ             (80000000L)

static int digits[9];
static int dark[9];

// Ermittelt das Port B Bitmuster für einen Wert
// an einer Position im Bildspeicher
int calcPattern(int pos, int value) {
  int hexDigit = (value & 0xf) << 7;
  int position = ((pos & 1) << 11) | ((pos & 0xe) << 12);
  int comma = (pos == 1) ? 0x20 : 0;

  return hexDigit | position | comma;
}

// Schreibt eine Ziffer an eine Position im Bildspeicher
void setDigit(int pos, int value) {
  int pattern = calcPattern(pos, value);
  digits[pos] = pattern;
}

// Schreibt einen Integer Wert in den Bildspeicher
void setInt(int value) {
    int i, j;

    for (i = 8; i >= 0; i--) {
        int part = value % 10;
        setDigit(i, part);

        value = value / 10;
        if (value == 0) {
            // clear leading digits
            for (j = i - 1; j >= 0; j--) {
                setDigit(j, 0xf);
            }
            break;
        }
    }
}

// Zeigt in einer Schleife die 9 Stellen des
// Displays an. Wird aus dem Heartbeat Interrupt
// ca. 1000 mal pro Sekunde aufgerufen.
void displayTick() {
    static int pos = 0;

    // dunkel schalten
    mPORTBWrite(dark[pos]);
    int delay;
    for (delay = 0; delay < 3; delay++);

    // neuen Wert eintragen
    mPORTBWrite(digits[pos]);

    // nächste Stelle ermitteln (round robin)
    pos++;
    if (pos > 8) {
        pos = 0;
    }
}

// Interrupt Service Routine für Timer1
void __ISR(_TIMER_1_VECTOR, ipl2) Timer1Handler(void) {
    displayTick();
    mT1ClearIntFlag();
}

// Initialisert das Display
void initDisplay() {
    int i;
    for (i = 0; i < 9; i++) {
        dark[i] = calcPattern(i, 0xf);
    }
}

// Initialisiert und startet den Heartbeat Timer
void startHeartbeat() {
    OpenTimer1(T1_ON | T1_SOURCE_INT | T1_PS_1_256, 4);
    INTEnableSystemMultiVectoredInt();
    ConfigIntTimer1(T1_INT_ON | T1_INT_PRIOR_2);
    mT1ClearIntFlag();
}

// Initialisiert und startet die Zähler Timer
void startCounter() {
    OpenTimer23(T23_ON | T23_SOURCE_EXT | T23_PS_1_1, 0xffffffff);
    T3CKRbits.T3CKR = 0; // RA0
}

int main(void)
{
    SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE);
    mJTAGPortEnable(DEBUG_JTAGPORT_OFF);

    ANSELA = 0;
    ANSELB = 0;
    CM1CON = 0;
    CM2CON = 0;
    CM3CON = 0;
    
    mPORTBSetPinsDigitalOut( 0xfffc );
    mPORTASetPinsDigitalIn( 0x3 );

    initDisplay();
    startHeartbeat();
    startCounter();

    while(1) {
        int del;
        // wait for 500 milliseconds
        for (del = 0; del < 80000; del++);

        setInt(ReadTimer23());
    };
    
}

Weiter zum Teil 5

Schreibe einen Kommentar