Archiv für den Autor: Matthias

Frequenzzähler/ Periodenmesser mit einem PIC32 – Teil 7

Heute habe ich mich mal hingesetzt um den Schaltplan und die Funktionsweise der Counter Teils aufzuzeichnen.

Die Funktionsweise ist eigentlich einfach. Wenn man einfach nur die Signalimpulse über einen definierten Zeitraum (der Torzeit) zählen würde, wäre es praktisch nicht möglich bei niedrigen Frequenzen eine vernünftige Auflösung zu bekommen. Für eine 6-stellige Anzeige benötigt man 1 Millionen Takte. Bei 100 Hertz wäre dafür eine Torzeit von 10.000 Sekunden nötig – rund 3 Stunden.

Alternativ dazu kann man die Periode messen. Dazu gibt das Messsignal die Torzeit vor und man zählt, wie viele Takte das Referenzsignal in dieser Zeit abgibt. Das ist bei 100 Hertz gut machbar, bei 1 MHz wiederum kaum mit guter Genauigkeit möglich.

Das hier verwendete Messprinzip ist im Wesentlichen eine Kombination der beiden Messmethoden. Bei einer Messung werden gleich zwei Zähler aktiv. Über den Zeitraum der Torzeit zählt ein Counter die Takte der Referenzfrequenz und ein weiterer die Takte der Signalfrequenz. Über das D FlipFlop wird sicher gestellt, dass die tatsächliche Torzeit immer nur ganze Perioden der Signalfrequenz beträgt. Andernfalls würde man bei niedrigen Frequenzen einen erheblichen Messfehler bekommen.

Schaltplan Counter

Das Diagramm zeigt ein Eingangssignal von 12,5 MHz an Pin A.

Wenn der Microcontroller eine neue Messsequenz einleiten will, setzt er Pin B auf 1. Da das D FlipFlop ein invertiertes Signal bekommt, wird mit der nächsten fallenden Signalflanke die Torzeit beginnen, der Ausgang Q des FlipFlop springt dann auf 1 (Pin C)

Mit dem Beginn der Torzeit werden über die beiden UND Gatter die beiden Counter für Signal- und Referenzfrequenz freigeschaltet und zählen nun hoch (Pin D und E an Pin 2 und 4 des Microcontrollers).

Der Microcontroller kann den Beginn der Torzeit über seinen Input – Pin C (an Pin 3 des PIC) lesen. Jetzt wartet er die gewünschte Zeit ab (z.B. ca. eine Sekunde) und setzt den Pin B wieder auf 0 zurück. Die genaue Zeit ist hier nicht wichtig, sie geht nicht in das Messergebnis ein solange sie ausreichend groß für die gewünschte Auflösung ist.

Das Tor bleibt jetzt aber noch offen – bis zur nächsten fallenden Flanke des Signals. Damit ist sicher gestellt, dass nur ganze Signalperioden gelesen werden. Wenn der Microcontroller feststellt, dass am Pin C wieder 0 anliegt, weiß er, dass eine Messperiode abgeschlossen ist. Nun kann er die Counter einlesen und das Ergebnis berechnen.

Im Bild sieht man die (viel zu kurze) Torzeit des Microcontrollers am Pin B von 200 nS (5 Referenz-Takte). Durch die Synchronisierung mit dem Signal wird die tatsächliche Torzeit auf 6 Takte gedehnt (Pin C). In dieser Zeit werden 3 steigende Flanken des Signals gezählt und 6 steigende Flanken der Referenz.

Aus diesen Werten kann nun die tatsächliche Signalfrequenz ermittelt werden:
Signalfrequenz = Signal-Takte * Referenzfrequenz / Referenz-Takte = 3 * 25000000 / 6 = 12500000

Natürlich ist die Torzeit in der Zeichnung viel zu kurz. Tatsächlich würde man hier eher eine Torzeit von ca. einer halben Sekunde wählen. Das kann ich nur nicht vernünftig Zeichnen. In diesem Fall würde die Rechnung vielleicht so aussehen ( bei einer Torzeit 0,51 Sekunden):
Signalfrequenz = Signal-Takte * Referenzfrequenz / Referenz-Takte = 6375000 * 25000000 / 12750000 = 12500000

Weiter zum Teil 8 und letzten Teil

Frequenzzähler/ Periodenmesser mit einem PIC32 – Teil 6

Heute bin ich dazu gekommen den internen Oszillator einzubauen. Ich habe dafür einen TCXO mit 25 MHz bestellt – in der Hoffnung, dass ich damit eine gute Stabilität erreiche. Laut Datenblatt sollte die Genauigkeit im Bereich 50ppm liegen. Zur Messung steht mir nur mein Rigol DG1062 zur Verfügung. Ich weiß nicht sicher, wie genau dieses Gerät ist, aber da es recht teuer war, hoffe ich auf gute Eigenschaften. Mein TCXO liegt 23ppm neben der Nomialfrequenz. Im Laufe der letzten Stunde hat die Anzeige meines Frequenzmessers nur um 4 Digits auf der 8. Stelle geschwankt. Das akzeptiere ich mal als sehr gutes Ergebnis.

Aufgrund des Messprinzips kann man sehr niedrige Frequenzen messen. Ich bin mir nicht sicher, ob ich die Originalschaltung falsch gelesen habe oder ob sie tatsächlich eine Race Condition enthält – bei mir hat das dazu geführt, dass ich mit ca. 50%er Wahrscheinlichkeit einen Takt zu viel gezählt habe. Bei 20 MHz und einem Messintervall von 1 Sekunde ist das kein Problem. Bei 1 Hertz gibt das völlig falsche Ergebnisse. Ich habe die Schaltung deshalb so abgeändert, dass das Gate mit fallenden Takt statt mit steigenden Takt geschaltet wird. Bei steigenden Takt zählt der Zähler hoch, so dass ich eine halbe Taktperiode Zeit zwischen den beiden wichtigen Ereignissen bekomme. Nach dieser Änderung steht die Anzeige auch bei sehr niedrigen Frequenzen exakt.

Bei sehr niedrigen Frequenzen unter 2 Hertz verlängert sich das Messintervall, im Durchschnitt auf das 2-fache der Periodendauer. Unter 100 Millihertz bekomme ich aber auch keine sinnvolle Anzeige mehr. Das ist aber auch nicht wichtig, es liegt weiter außerhalb des angepeilten Messbereichs. Nach oben komme ich bis 24 MHz, darüber gibt es ebenfalls erhebliche Messfehler.

Jetzt fehlt noch eine verbesserte Software mit Anzeige des Messbereichs (Hertz, Kilohertz, Megahertz), das Netzteil und das Gehäuse.

Weiter zum Teil 7

Frequenzzähler/ Periodenmesser mit einem PIC32 – Teil 5

Nach einigen Fehlversuchen bin ich nun wieder einen Schritt weiter. Geplant war es eigentlich, dass ich mit einen Referenztakt von 25 MHz arbeite. Bei meinem aktuellen Aufbau komme ich aber nicht zuverlässig über 10 MHz. Allerdings habe ich noch viele lang frei fliegende Leitungen.

Bei einem Referenztakt von 10 MHz und einem Signal von 1,234567 MHz komme ich auf eine Anzeige, die zwischen 1,234567 und 1,234568 pendelt. Besser kann ich es nicht erwarten. Meine Messperiode liegt bei ziemlich genau einer Sekunde.

Meine Hand liegt nicht zufällig links neben dem Zählermodul auf dem Kabel der Spannungsversorgung. Sie dient als Filter damit ich nicht über dieses Kabel erhebliche Störungen einfange. Ohne diesen Filter sind meine Messwerte immer ca. ein Promille zu hoch.

Die nächsten Schritte bestehen darin, dass ich den externen Referenztakt von meinem Funktionsgenerator auf den internen Oszillator umstelle. Dabei werde ich dann nochmal versuchen auf 25 MHz zu gehen und die Messperiode auf eine halbe Sekunde verkürzen. Falls das nicht klappt, bleibe ich eben bei 10 MHz.

Weiter zum Teil 6

Konfigurationsbits im PIC32

Bei meinen ersten Schritten mit dem PIC32 habe ich natürlich mit dem „Hello World“ der embedded Welt begonnen: ein Port Bit wird ein- und ausgeschaltet. Das hat auf Anhieb funktioniert.

Als nächstes stellt sich dann die Frage: wie schnell kann man einen Port ein- und ausschalten. Hier bin ich auf ca. 400 nS gekommen, eigentlich zu langsam für so einen schnellen Prozessor. Unglücklicherweise habe ich das Ergebnis einfach so hingenommen, da es schnell genug für meine Aufgaben war.

Hätte ich mich damals um eine Klärung bemüht, wären mir gestern mehrere Stunden Fehlersuche erspart geblieben. Mein Frequenzzählerprojekt kommt nicht so schnell voran wie erhofft (das liegt zum einen an der verfügbaren Zeit aber noch mehr daran, dass ich mehr lernen muss als ich vorher gedacht hätte) – aber es macht Fortschritte. Mittlerweile haben ich das Anzeigemodul und einen einfachen durchlaufenden Zähler. Als ich gestern meinen Referenztakt von 25 MHz auf den Zähler gegeben habe, hat sich gezeigt, dass der Zähltakt unter einem MHz lief. Der Oszillator war unschuldig, das Oszilloskop hat ordentliche 25 MHz angezeigt.

Die Fehlersuche hat sich etwas mühsam gestaltet, da ich gleich zwei Fehler in der Prozessorkonfiguration hatte. Diese hatte ich einfach aus einem Beispiel übernommen, welches offensichtlich für eine andere Prozessorversion war.

Zum einen wurde der Takt für die externen Komponenten durch den Wert 8 geteilt. Wenn man die kryptischen Kurznamen der Konfiguration verinnerlicht hat, ist das offensichtlich. Mir ist es aber vorher nicht aufgefallen.

Der größere Fehler bestand aber darin, dass die Konfiguration für einen externen 8 MHz Quarzoszillator eingestellt war. Ich habe aber keinen Quarz in meiner Schaltung (der Prozessortakt geht nicht in die Messgenauigkeit ein). Irgendwie lief der Prozessor aber trotzdem, vermutlich blieb er auf einem langsamen internen Oszillator für den Start hängen.

Nachdem ich die Konfiguration in Ordnung gebracht hatte (interner schneller RC Oszillator mit 8 MHz, PLL auf 64 MHz), lief mein Zähler problemlos mit den 25 MHZ (da der Zähler intern synchronisiert wird, ist die Zählertaktrate durch den Prozessortakt beschränkt).

#pragma config FPLLIDIV = DIV_2         // PLL Input Divider (2x Divider)
#pragma config FPLLMUL = MUL_16         // PLL Multiplier (16x Multiplier)
#pragma config FPLLODIV = DIV_1         // System PLL Output Clock Divider (PLL Divide by 1)
#pragma config FWDTEN = OFF

#pragma config FNOSC = FRCPLL           // Oscillator Selection Bits (Fast RC Osc with PLL)
#pragma config FSOSCEN = OFF            // Secondary Oscillator Enable (Disabled)
#pragma config IESO = OFF               // Internal/External Switch Over (Disabled)
#pragma config POSCMOD = OFF            // Primary Oscillator Configuration (Primary osc disabled)
#pragma config OSCIOFNC = OFF           // CLKO Output Signal Active on the OSCO Pin (Disabled)
#pragma config FPBDIV = DIV_1           // Peripheral Clock Divisor (Pb_Clk is Sys_Clk/1)
#pragma config FCKSM = CSDCMD           // Clock Switching and Monitor Selection (Clock Switch Disable, FSCM Disabled)

 

Als nächstes habe ich nun noch mal mein Blinky Programm korrigiert. Jetzt komme ich auf eine Periode von ca. 100 nS für ein Bit Setzen und Zurücksetzen – also ca. 50 nS für eine Portausgabe. Das ist schon viel näher an den erwarteten Werten.

 

Schneller Takt

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

Frequenzzähler/ Periodenmesser mit einem PIC32 – Teil 3

An diesem Wochenende hatte ich ein paar Stunden Zeit um mich um die Anzeigeroutine zu kümmern. Ziel war es, dass die Anzeige Interruptgesteuert aus einem Bildspeicher heraus erfolgt.

Da die Anzeige 9-stellig ist und ich über eine 10. Stelle noch ein paar externe LEDs ansteuern möchte, brauche ich für eine Wiederholrate von 100 Hertz eine Interruptrate von 1 kHz. Diese Frequenz wird intern über den Timer1 erzeugt.

OpenTimer1(T1_ON | T1_SOURCE_INT | T1_PS_1_256, 4);

Die Teilerfaktoren habe ich experimentell ermittelt, sie unterscheiden sich fast um den Faktor 10 von den theoretisch notwendigen Werten. Ich habe den Verdacht, dass mein PIC im Augenblick nicht mit der vollen Taktrate läuft. Das muss ich bei Gelegenheit mal genauer untersuchen, im Augenblick stört es mich aber nicht.

Als nächstes benötige ich eine Interrupt Service Routine (ISR), welche bei jedem Timer-Überlauf aufgerufen wird. Dazu muss die Routine definiert werden und der Timer-Interrupt aktiviert werden.

void __ISR(_TIMER_1_VECTOR, ipl2) Timer1Handler(void) {
    displayTick();
    mT1ClearIntFlag();
}

Die Funktion meldet sich als ISR für den Timer1 an. Bei jedem Aufruf wird die nächste Stelle angezeigt, das ganze immer im Kreis. Am Ende der Routine wird das Interrupt Flag zurückgesetzt damit sie im nächsten Intervall wieder aufgerufen wird. Später wird diese Routine auch noch die Abfrage der Eingabetaster durchführen.

void startHeartbeat() {
    OpenTimer1(T1_ON | T1_SOURCE_INT | T1_PS_1_256, 4);
    INTEnableSystemMultiVectoredInt();
    ConfigIntTimer1(T1_INT_ON | T1_INT_PRIOR_2);
    mT1ClearIntFlag();
}

Hier nun die komplette Timer-Initialisierung. Nach der Programmierung der Teilerfaktoren wird der Interrupt eingeschaltet und konfiguriert. Das war schon alles. Jetzt wird die Funktion Timer1Handler rund 1000 mal pro Sekunde aufgerufen.

Als nächstes kommt dann die eigentliche Anzeige in der Funktion displayTick(). Sie müsste eigentlich die aktuelle Ziffer aus dem Bildspeicher auslesen, das dafür benötigte Bitmuster für den Ausgabeport ermitteln (jeweils 4 Bit für die Siebensegmentanzeige und den Multiplexer für die Stellenauswahl) und dieses Muster auf den Port B ausgeben. Es ist aber verschenkte Zeit, das Bitmuster 1000 mal pro Sekunde ständig neu zu errechnen, deshalb wird es beim Füllen des Bildspeichers errechnet und statt einfach nur der Ziffer wird das Bitmuster dort abgelegt. Die Ausgaberoutine muss also immer nur die nächste Stelle aus dem Bildspeicher auslesen und auf den Port ausgeben. Die Position des Ausgabecursors wird in der lokalen Variablen pos gespeichert.

static int digits[9];

void displayTick() {
    static int pos = 0;

    mPORTBWrite(digits[pos]);

    pos++;
    if (pos > 8) {
        pos = 0;
    }
}

Nun fehlt noch eine Möglichkeit, den Bildspeicher zu füllen. Als low level Funktion gibt es einen Aufruf, der die Position und einen Wert enthält. Diese Funktion errechnet daraus das Bitmuster für den Port B.

void setDigit(int pos, int value) {
  int hexDigit = (value & 0xf) << 7;
  int position = ((pos & 1) << 11) | ((pos & 0xe) << 12);
  int pattern = hexDigit | position;
  digits[pos] = pattern;
}

Darauf aufbauend gibt es nun eine Funktion, die einen 32 Bit Integer Wert in den Bildspeicher einträgt.

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;
        }
    }
}

7-Segment AnzeigeBeim Betrachten der Bilder ist mir aufgefallen, dass es ein sichtbares Übersprechen zwischen den einzelnen Stellen gibt. Ich vermute, dass die Abschaltung der Stelle nicht schnell genug ist und jede Stelle deshalb ein paar Mikrosekunden lang noch aktiv ist, während bereits die nächste Stelle angezeigt wird.

[Edit] Das habe ich mir mittlerweile mal genauer angesehen. Man bekommt den Effekt leicht per Software weg, indem man vor dem Wechsel der Stelle den Wert 0xf (alles dunkel) ausgibt und ein paar Mikrosekunden wartet. Ich vermute, dass der PNP Darlington Transistor nicht besonders schnell schaltet. Da er einen großen Vorwiderstand hat, dauert es vermutlich mehrere Mikrosekunden bis er abschaltet.

 
Weiter zum Teil 4

Management Mythen

Heute bin ich durch Zufall über einen älteren Blog Beitrag von Steven Sinofsky gestolpert: „Management Clichés That Work„. Da er auf diesem Gebiet als (ehemaliger) verantwortlicher Manager für Windows und Office viel Erfahrung haben sollte, habe ich den Beitrag natürlich gelesen. Ein paar Punkte daraus möchte ich hier aufgreifen.

Sinnvollerweise liest man erst den Beitrag von Steven Sinofsky. Anschließend geht es hier weiter…

Promise and deliver: Für den Teamleiter ist es wichtig, dass die Entwickler realistische Schätzungen abgeben. Deshalb bin ich genauso wie er der Meinung, dass die Masche “under promise and over deliver” (weniger zusagen als man leisten kann und am Ende des Projekts deshalb mehr abliefern als gefordert wurde) schlecht ist. Seine Begründung finde ich nur unzureichend. Das Problem bei dieser Vorgehensweise liegt für den Manager darin, dass er nur schlecht planen kann. Das Problem für den Entwickler wiegt noch schwerer: über fast die gesamte Projektlaufzeit wird er als wenig leistungsfähiger Mitarbeiter eingestuft. Die Erleichertung am Ende – „er hat ja doch einiges abgeliefert“ macht meiner Meinung nach die über längere Zeit aufgebaute Meinung nicht wett.

Richtig ist es (meiner Meinung nach), eine möglichst realistische Schätzung über die erreichbaren Ziele abzugeben. Gemeinsam mit einer Risikoabschätzung, was mindestens erreicht werden kann. Es ist die Aufgabe des Managers, sich Gedanken darüber zu machen, welche Zahlen nach außen kommuniziert werden. Dazu muss er aber möglichst realistische Zahlen haben. Wenn der Entwickler sich durch übermäßig hohe Sicherheitszuschläge vor Enttäuschungen schützen will, ist das unaufrichtig gegenüber der Firma und dem Team.

Diese Forderung funktioniert natürlich nur dann, wenn der Manager im Falle unerwarteter Probleme und nicht erreichter Ziele seine Verantwortung nicht einfach auf den Entwickler abschiebt. Er hatte die Zahlen und kannte die Risiken. Wenn er das nicht passend nach außen kommuniziert, muss er dafür auch selber die Verantwortung übernehmen.

Make sure bad news travels fast: dieser Punkt geht Hand in Hand mit dem bereits gesagten. Wenn die Schätzung aufgrund unerwarteter Gründe nicht aufgeht, dann muss man es auch so früh wie möglich kommunizieren. Nur dann hat der Manager die Möglichkeit Veränderungen vorzunehmen. Im schlimmsten Fall, dass der Releasetermin frühzeitig verschoben wird.

Für den Entwickler ist es hier wichtig, dass er sich nicht selber belügt. Der lustig gemeinte Satz „ein Softwareprojekt ist über 80% der Laufzeit fast fertig“ hat einen ernsten Hintergrund. Wenn ich nicht erkennen will, dass ich mit meinen Terminen im Verzug bin, kann ich es auch nicht weiterleiten. Mittelfristig ist so etwas für einen Entwickler ein ernsthaftes Karrierehindernis – er wird von seinem Manager ständig als Projektrisiko angesehen. Und dieses negative Gefühl überdeckt einen Teil der Leistungen.

Writing is thinking: es ist meiner Meinung nach offensichtlich, dass man beim Niederschreiben von Gedanken und Plänen gezwungen ist, tiefer über ein Problem nachzudenken. Wenn man einen Plan nur „im Kopf“ hat, kann man sich leichter selber beschummeln und offensichtliche Lücken leichter übersehen. Sobald man es schreibt, bekommt es eine andere Qualität. Damit ist nicht gemeint, dass man ständig umfangreiche Reports schreibt und nicht mehr zum Arbeiten kommt. Einfach in ein paar Sätzen die wichtigen Punkte aufschreiben und bei Bedarf noch ein paar Handskizzen reichen oft schon aus.

Noch besser ist es, wenn man es jemand anderen erklärt. Wenn ich es nicht erklären kann, habe ich es auch nicht verstanden. Damit ich es erklären kann, muss ich besser darüber nachdenken. Ich glaube, dass ein Teil des Erfolgs von Teamarbeit darin liegt, dass man seine Gedanken ständig erklären muss.

Practice transparency within your team: ein Softwareentwickler ist kein Einzelkämpfer in einer dunklen Höhle. Er ist ein Teamplayer – andere sind auf ihn angewiesen und er ist auf andere angewiesen. Und schon eine einzige Primadonna kann ein ganzes Team zerstören.

Don’t ask for information or reports unless they help those you ask to do their jobs: eine wichtige Aufgabe des Manager liegt darin, dafür zu sorgen, dass sein Team möglichst gute Arbeitsmöglichkeiten hat und somit die maximale Leistung bringen kann. Das ist keine reine Menschenfreundlichkeit sondern auch ganz egoistischer Selbstzweck. Ein egozentrischer Manager, der seine Eitelkeiten bedingungslos über sein gesamtes Team stellt, ist ein unfähiger Manager und gehört ausgetauscht.

Don’t keep two sets of books: den Vorgesetzten über den Projektfortschritt zu täuschen ist ein Verhalten, welches fast zwangsläufig zu Problemen führt. Ich glaube nicht, dass man das noch weiter erläutern muss.

Never vote on anything: hier bin ich abweichender Meinung. Vielleicht ist seine Einstellung auch der amerikanischen Eigenart geschuldet, dass man ein offenes Nein vermeiden sollte weil es unhöflich ist. Es gibt nun mal Situationen in denen alle Argumente ausgetauscht wurden, jeder hat die Position der anderen verstanden und trotzdem kommt man zu unterschiedlichen Ansichten. Hier kann es schon sinnvoll sein, einfach mal abzustimmen. Die Mehrheit hat nicht immer recht – aber wenn viele kluge Leute anderer Ansicht sind als ich es bin, dann ist es ein Grund, die eigene Position nochmal ernsthaft zu überdenken. Am Ende muss der Leiter entscheiden – er muss es schließlich auch verantworten.

When presenting the boss with n alternatives he/she will always choose option n+1: Autsch – das geht gegen die „pointy haired bosses„. Wenn der Vorgesetzte nicht in der Lage ist, die Möglichkeiten zu beurteilen, ist das eine Nothilfe. Ein normaler Manager möchte hier lieber eine ehrliche Liste der Möglichkeiten und Risiken haben. Niemand möchte gerne manipuliert werden, die Entwickler nicht – und die Manager auch nicht.

Products don’t ship with a list of features you thought you’d do but didn’t: wenn ein Feature so unwichtig ist, dass es ständig von anderen (neueren) wichtigen Features verdrängt wird, dann sollte man auch konsequent sein und es streichen. Sonst kommt man irgendwann in die Verlegenheit, dass man an unwichtigen Dingen arbeitet, „weil sie schon so lange da liegen“ – statt an den wichtigen Themen zu arbeiten. Es ist auch ehrlicher gegenüber dem Stakeholder dieses Features – lieber ein Ende mit Schrecken als ein Schrecken ohne Ende. Die Ausnahme hier: wenn es dem Einreicher mehr um persönliche Eitelkeiten als um das Produkt geht, kann es „humaner“ sein, den Featurewunsch in einer dunklen Ecken verschimmeln zu lassen als einen Grabenkrieg zu führen.

 

 

Frequenzzähler/ Periodenmesser mit einem PIC32 – Teil 2

Da ich am Wochenende im Augenblick stark in die Vereinsarbeit (wsb-calw.de) eingebunden bin, geht es mit meinem kleinen Projekt nicht so schnell voran wie gewünscht. Ich habe in dieser Woche aber doch ein paar weitere Schritte geschafft.

Die erste Hürde lag darin, dass ich nicht den kompletten Microstick einbauen wollte. Zum einen ist es zu teuer, für jedes kleine Bastelprojekt ca. 40 EUR auszugeben. Zum anderen wollte ich ein echtes Microcontrollerprojekt haben und nicht einfach nur eine fertige Modulplatine einbauen.

Eigentlich ist es auch nicht kompliziert. Für die Programmierung und das Debugging sind 5 Leitungen vorgesehen: GND und +3.3V, Daten und Takt sowie Reset. Also habe ich den PIC aus dem Microstick herausgenommen und auf meine Platine gesetzt. Die 5 Leitungen habe ich von der Platine mit dem Adaptersockel auf der Unterseite verbunden. Et voià – der PIC wurde von der Entwicklungsumgebung nicht gefunden. An dieser Stelle hätte ich mir eine Stunde Fehlersuche ersparen können, wenn ich mir den Schaltplan des Microstick richtig angesehen hätte. Dort kann man nämlich genau sehen, dass einige Prozessorpins nicht mit dem Adaptersockel verbunden sind. Unter anderem Reset und +3.3V.

Nachdem ich die Verbindung vom Adaptersockel entfernt und direkt in PIC Fassung eingesteckt hatte, lief die Kontaktaufnahme problemlos. Nun hatte ich den Stand, dass ich einen externen PIC32 programmieren und debuggen konnte.

Der nächste Schritt war dann Fleißarbeit: den Siebensegmentdecoder und den 1 aus 16 Demultiplexer vom Steckbrett auf meine Lochrasterplatine versetzen. Das wäre eigentlich einfach gewesen. Unglücklicherweise habe ich für die Verbindungen einen Draht mit einer extrem weichen und wärmeempfindlichen Isolierung verwendet. Und mir dabei gleich einen Kurzschluss zwischen zwei Datenleitungen eingebaut.

Kurzschluss2

Um es noch etwas komplizierter zu machen, habe ich an dem Multiplexer auch noch den Enabled Eingang offen gelassen. Da ein offener CMOS Eingang auf irgend einem Pegel hängt, bekommt man eine extrem unzuverlässige Schaltung. Bei mir blieb das Display dunkel – bis ich mit dem Finger in die Nähe des ICs gekommen bin. Alleine die Nähe hat ausgereicht, dass der Pin mit 50 Hertz Netzfrequenz ein- und ausgeschaltet hat. Wieder ein Abend verloren, bis ich das gefunden hatte.

Der nächste Abend ging dafür drauf, die 9 PNP Transistoren für den Multiplexer auf der Anodenseite einzulöten und Basiswiederständen zu versehen. Dabei hatte ich im Vorfeld den benötigten Platz etwas knapp kalkuliert. Es hat zwar alles gepasst, ist aber etwas gedrängt und schief. Nun ja – später ist es in einem Gehäuse und man sieht es nicht mehr.

Widerstände in zwei Ebenen2

 

Jetzt läuft aber alles und der nächste Schritt besteht darin, eine interruptgesteuerte Anzeigeroutine für den Multiplexer zu schreiben. Der Lötkolben kann ein paar Tage ausruhen.

Langsamer Durchlauf Blick von Oben Blick von Unten

Weiter zum Teil 3

Frequenzzähler/ Periodenmesser mit einem PIC32 – Teil 1

Im Internet hatte ich vor ein paar Wochen ein Verfahren gefunden, wie man einen Frequenzzähler mit guter Genauigkeit und Messauflösung aufbauen kann. Das Original wurde mit einem AVR Prozessor aufgebaut. Da ich ein PIC32 Projekt durchführen wollte, habe ich diesen Prozessor gewählt, auch wenn ein einfacheres Modell gereicht hätte.

Hier findet man das Originalprojekt, welches sehr gut beschrieben ist und mir wirklich gut gefallen hat.

Siebensegmentanzeige

Bei ebay konnte ich für wenige Euro 9-stellige blaue Siebensegment Displays erwerben. Da mein PIC32 Modell nicht gerade üppig mit Pins bestückt ist, habe ich einen normalen Siebensegment Decoder 7447 verwendet. Das hat auch den angenehmen Vorteil, dass ich gleich eine Open Collector Ansteuerung für die LEDs habe. Den Dezimalpunkt kann ich im Augenblick noch nicht ansteuern, dafür werde ich noch ein Port-Pin spendieren müssen.

Zum Multiplexen der einzelnen Stellen habe ich einen 1 aus 16 Decoder 74154 verwendet. Ich hatte auch überlegt, einen 1 aus 8 Decoder (74138) zu verwenden und die neunte Stelle dann direkt anzusteuern. Das hätte die Schaltung aber umfangreicher gemacht und bringt kaum eine Einsparung. Dann sind eben 7 Schaltzustände ungenutzt. Das kann ich später dazu verwenden um zusätzliche LEDs zur Statusanzeige anzusteuern.

Da ich die Segmente mit mindestens 30 mA ansteuern möchte (laut Datenblatt sind 30 mA erlaubt, deshalb wird das Display es auch überleben, wenn der Multiplexer mal stehen bleibt), kann ich die Stellenumschaltung nicht direkt vom IC aus ansteuern. Es müssen bei der Anzeige der Ziffer 8 ca. 240 Milliampere geliefert werden. Also erfolgt die Ansteuerung über einen PNP Darlington Transistor der mit dem Emitter an Plus liegt. Der Decoder zieht den ausgewählten Kanal freundlicherweise auf 0 – genau passend zur Ansteuerung des Transistors, natürlich mit einem Vorwiderstand. Vermutlich hätte hier statt eines Darlingtons auch ein normaler PNP Transistor gereicht.

Die TTL ICs betreibe ich mit den vorgeschriebenen 5 Volt, den PIC mit 3,3 Volt. Der High Level des PIC hat aber auch bei nur 3,3 Volt einen ausreichend hohen Pegel um von den TTL ICs als High erkannt zu werden.

Für den Prototypen auf dem Steckbrett habe ich nur 3 Stellen verkabelt. Der nächste Schritt besteht darin, das Ganze dann vollständig auf einer Platine aufzubauen. Sobald die Anzeige dann steht, geht es an den Zählerteil.

Zähler 1

Etwas zum Nörgeln habe ich zum Schluss aber noch: die Port-Pin Verteilung bei den 28 Pin PIC32 ist übel. Vom Port A stehen prinzipiell die ersten 5 Bit zur Verfügung – die Einschränkung deshalb, weil ich nur PA0 und PA1 tatsächlich zu einer Ausgabe überreden konnte. PA2 und PA3 gehören scheinbar dem Programmieradapter. Die Verwendbarkeit von PA4 habe ich nicht überprüft.

Beim Port B ist es auch nicht toll. Er ist auf den ersten Blick vollständig – aber nein: die Bits 6 und 12 fehlen ganz. Zwei weitere Bits sind durch den Oszillator belegt und ich habe noch nicht herausgefunden, wie man sie als Ausgang verwenden kann. Da ist es schon mühsam, vier zusammenhängende Bits als Bus zu finden. Das hätte man vielleicht auch sinnvoller aufteilen können.

Weiter zum Teil 2

Programmier-Un-tugenden, die man vermeiden sollte

Gestern ist mir mal wieder unschöner Programm-Code über den Weg gelaufen. Das hat mich dazu animiert, mal die häufigsten Fehler aufzulisten, die ich immer mal wieder sehe und über die ich mich dauerhaft ärgern kann.

Dabei geht es mir noch nicht mal um irgendwelche abstrakten Programmiermodelle oder best practices im Architekturbereich. Es geht um kurze, alltäglich Codestücke, die man eigentlich nicht falsch machen kann. Und doch wird es gerne falsch gemacht. Es geht auch nicht um Syntaxfehler, die filtert der Compiler raus. Sondern einfach nur um schlechten Code, der irgendwie funktioniert.

1. Unklare Vorstellungen über boolsche Werte

Wenn ich solche Abschnitte sehe, stellen sich mir die Nackenhaare auf:

boolean isEnabled = myButton.getEnabled();

if (isEnabled == true) { ...

Die Variable isEnabled enthält einen boolschen Wert, die IF-Entscheidung erwartet einen. Warum dann noch diesen zusätzlichen Vergleich dazu packen. Damit sich der Compiler auch wirklich sicher ist, dass der Wert true ist? Und dann könnte man konsequenterweise doch auch „if ((isEnabled == true) != false)“ schreiben.

Mir signalisiert so eine Vorgehensweise, dass der Entwickler nur eine vage Vorstellung von boolschen Werten und ihrer Verwendung besitzt.

2. Überflüssige Kontrollstrukturen

Dieses Beispiel geht in die gleiche Richtung wie Beispiel 1.

boolean isEnabled = myButton.getEnabled();

if (isEnabled) {

  otherButton.setEnabled(true);

} else {

  otherButton.setEnabled(false);

}

Hier wird ohne nachvollziehbaren Grund eine komplette Kontrollstruktur aufgebaut. Dabei kann ich mich gar nicht über die verschenken Nanosekunden ärgern. Aber jeder andere Entwickler erwartet hier, dass etwas passiert, was schwergewichtiger ist als eine einfache Zuweisung: „otherButton.setEnabled(isEnabled)“.

Leider kommt dieser Fehler in der Praxis immer wieder vor. Auch hier liegt vermutlich oft einfach ein Defizit im Bereich „mal darüber nachdenken, was ich gerade mache“ vor.

3. Verdrehte Ausdrücke

Eines meiner Lieblingsaufreger, besonders gern von alten C Programmierern gemacht:

if ( 7 == anzahlWochentage) ...

Das liest sich einfach schlecht – Obi-Wan Kenobi Grammatik. Wenn man den Ausdruck umgangssprachlich formulieren würde, würde man sagen „Wenn die Anzahl der Wochentage gleich 7 ist“ und keinenfalls „Wenn 7 die Anzahl der Wochentage ist“. Auch wenn der Inhalt der gleiche ist, stolpert man über die falsche Reihenfolge, der Gedankenfluss wird gestört, die Aufmerksamkeit ist beeinträchtigt.

Die Herkunft dieses Anti-Patterns ist klar. Uralte C Compiler (älter als 30 Jahre), haben die Anwender leichtfüßig in Fehler der Art „if (anzahlWochentage = 7)…“ laufen lassen. Durch das vergessene zweite Gleichheitszeichen wird aus dem Vergleich eine Zuweisung. In der umgedrehten Schreibweise führt das zu einem Compilerfehler, da ein konstanter Wert nicht als Left-Value verwendet werden kann. Aber auch vor 30 Jahren gab es schon „lint“ für solche Probleme. Alle modernen Compiler werfen hier deutliche Warnings aus, so dass man diesen Fehler eigentlich nicht mehr machen kann.

Ein Entwickler, der sich dieser Schreibweise bemächtigt, löst bei mir schnell den Verdacht aus, dass er sich nicht um seine Warnings kümmert. Oder, dass er so viele Warnings hat, dass er sie gar nicht mehr kontrollieren kann. Beides ist schlecht, der Programmierer bewegt sich Haarscharf an der Grenze zum Chaos.

4. Magic Numbers

Die Verwendung von symbolischen Konstanten statt Zahlen im Quellcode ist mittlerweile fast überall Standard. In vielen Firmen gibt es Style Guides, die die Verwendung von Magic Numbers ausdrücklich verbieten.

Der Nutzen von Konstanten liegt auf der Hand. Wenn ich fünf Produktgruppen habe und im Programmcode alle Schleifen mit der Ziffer 5 laufen lasse, habe ich bei einer Erweiterung auf 6 Gruppen massiven Aufwand. Man kann ja nicht einfach per Suchen und Ersetzen alle 5 gegen eine 6 austauschen. Statt dessen muss man sich jede Verwendung genau darauf hin ansehen, ob die Zahl der Gruppen gemeint ist. Zudem gibt es weitere Verwendungen, der Index der letzten Gruppe beträgt 4 und nicht 5. Darauf muss man also auch achten.

Übel wird es, wenn ein Programmierer zwar den Wortlaut der Regel befolgt aber den Sinn nicht verstanden hat (oder nicht verstehen will). Dann kommen solche Konstruktionen vor:

const FIVE = 5;

...

for (var group = 0; group < FIVE; group++) ...

Der Wortlaut der Regel wurde befolgt, im Programmcode selber gibt es keine Magic Numbers. Aber das Ziel wurde verfehlt. Ich sehe der FIVE in der for Schleife nicht an, was für einen Zweck sie hat. Besonders übel wird es, wenn sich die Zahl der Gruppen auf 6 erhöht:

const FIVE = 6;

So eine Vorgehensweise kann man nur begrenzt mit Unfähigkeit begründen. Das grenzt schon an Sabotage.

5. Unsinnige Kommentare

Vernünftige Kommentare zu erstellen, ist nicht unbedingt einfach. In manchen Betrieben gibt es eine Festlegung, was kommentiert werden muss. Bei uns ist es z.B. so, dass alle public Methoden mit einem JavaDoc Header versehen werden müssen. Kommentare innerhalb einer Funktion sind aber kaum über einen Style Guide definierbar. Wenn man dann einfach einen bestimmten Prozentsatz von Kommentarzeilen fordert, entstehen solche Konstruktionen:

customers++;  // customer wird eins hochgezählt

Ein unglaublich blöder Kommentar, der genau das wiederholt, was der Programmcode ganz offensichtlich ausführt. Solche Kommentare sind nicht nur nutzlos. Sie sind sogar schädlich. Spätestens dann, wenn sich der Programmcode mal ändert und der nutzlose Kommentar nicht angepasst wird. Dann habe ich einen Widerspruch im Quellcode der schwere Irrtümer auslösen kann.

Kommentare sollten im Normalfall NICHT beschreiben, was passiert. Das wird durch den Programmcode am Besten beschrieben. Und wenn der schwer verständlich ist, dann sollte man sich lieber Gedanken über eine bessere Schreibweise statt über einen Kommentar machen.

Kommentare sollten beschreiben WARUM etwas passiert. Denn dieser Teil ist nicht aus dem Code abzuleiten. Vielleicht aus dem Kontext. Oder aus dem Pflichtenheft (liegt das fünf Jahre später noch in einer aktualisierten Form vor?).