Zur Druckansicht wechseln Visit the English version of this page

www.jb-electronics.de » Elektronik » Digitaltechnik » PIC16F627A - Lexikon

PIC16F627A - Lexikon

Eigentlich verwenden alle meine digitalen Projekte, wenn sie denn einen Mikrocontroller beinhalten, den PIC16F627A. Das liegt daran, dass dieser Controller zum einen der erste war, mit dem ich mich je beschäftigt habe, zum anderen aber auch daran, dass er viele nette Features mit sich bringt.

Aber der beste Controller nutzt nichts, wenn mensch diese Features nicht zu nutzen weiß, oder noch schlimmer, gar nicht weiß, dass der entsprechende Controller sie unterstützt.

Daher möchte ich hier nach und nach aus der Praxis erprobte Codeschnipsel und Erklärungen speziell zum PIC16F627A sammeln.

Inhaltsübersicht:

Die noch nicht verlinkten Einträge sind Ideen für spätere Ergänzungen, diese Seite hier wird kontinuierlich erweitert werden. Wenn Sie ein besonderes Interesse an einem der Punkte haben, oder etwas wissen möchten, was noch gar nicht hier steht, dann schreiben Sie mir doch eine Email.

Anmerkung: Alle Register- und Bitbezeichner stammen aus den Include-Files des PICC Compilers von HTSOFT. Für andere Compiler müssen die C-Codes auf dieser Webseite daher eventuell leicht modifiziert werden.

Datenblatt

Ein richtiges Datenblatt zu einem Controller ist unerlässlich. Ich nutzte fast ein ganzes Jahr lang ein verkürztes, sodass mir viele Funktionen des PIC16F27A verborgen blieben.

Daher stelle ich hier einmal das vollständigere Datenblatt zum PIC16F627A zur Verfügung, in dem alles (und noch viel viel mehr), was auf dieser Seite steht, ausführlich dokumentiert ist:

PIC16F62X_microchip.pdf (4.09 MB)

Nach oben

PORTA nicht als Eingang verwendbar?

PORTA ist einer der beiden Eingangsports des PIC16F627A, von denen die unteren 5 Bits (RA0..RA4) problemlos (d.h. ohne Änderungen des Config-Words) benutzbar sind. Als Ausgänge lassen sie sich problemlos verschalten:

// PORTA aus Ausgang setzen
// (TRISA = Tristate PORTA)
TRISA = 0b00000;

// schreibe einen Wert an die Ausgänge (das setzt RA4:RA0 auf 11111)
PORTA = 31;

Aber als Eingang funktionieren sie so nicht:

// PORTA als Eingang setzen
TRISA = 0b11111;

// warte bis der Eingang (z.B. RA0) ungleich null wird
while (RA0 != 0) {};

Doch die WHILE-Schleife wird nie verlassen. Warum?

Nach dem Studium des Datenblattes wird ersichtlich, dass auf PORTA auch das Analogkomparator-Modul sitzt. Das ist standardmäßig eingeschaltet, wenn PORTA als Eingang konfiguriert wird (S. 29). Daher muss es erst abgeschaltet werden, das geht so:

// Analogkomparator bei PORTA abschalten
// (CMCON = Comparator mode configuration)
CMCON = 0b111;

Danach kann PORTA problemlos als Eingang genutzt werden.

Nach oben

Dimmen mit 10Bit Auflösung mit dem internen CCP-Modul im PWM-Modus

Der PIC16F627A hat ein CCP-Modul (Capture/Compare/PWM) eingebaut, das eine Auflösung von 10 Bit besitzt. Es wird im Datenblatt ab Seite 61 beschrieben.

Wichtig ist, dass der Ausgang des PWM-Moduls immer der Pin RB3 ist, den wir ganz zu Beginn schon als Ausgang setzen können:

TRISB3 = 0;

Prinzipiell funktioniert Pulsweitenmodulation (PWM) so, dass ein Ausgang (hier wie gesagt RB3) über ein festes Zeitfenster einen bestimmten Prozentsatz lang ein- und den Rest des Zeitfensters ausgeschaltet ist. Durch die Trägheit des Auges entgeht uns das Flimmern und das Signal (zum Beispiel eine LED) kommt uns schwächer vor. Für die Mathematiker: Das Auge ist ein Integrator.

Hier ist das Prinzip der Pulsweitenmodulation einmal als Grafik zu sehen:

DPrinzip der Pulsweitenmodulation (PWM)

Wir arbeiten mit einer 10-Bit-PWM, daher ist das angesprochene Zeitfenster 1024 Takte lang. Mensch kann sich leicht denken, dass die PWM umso besser wird, je schneller die 1024 Takte abgearbeitet werden, da dann das Flackern nicht mehr sichtbar ist. Das wird auch PWM-Frequenz genannt. Die PWM-Auflösung hingegen ist auch wichtig; sie ist dadurch gegeben, wie viele Takte eine PWM-Periode umfasst.

Ein kleines Beispiel: Ginge es nur um die Frequenz, dann könnte man aus der 1024 oben einfach eine 2 oder 3 machen, und die Frequenz wäre viel höher. Aber dann wären auch nur noch 3 bzw. 4 verschiedene Helligkeitswerte möglich: die Auflösung ginge in den Keller. Wenn anders herum die Auflösung zu gut ist, und der Maximalwert beispielsweise bei 1000000 liegt, dann ist die PWM-Frequenz nicht mehr hoch genug, und es wird ein Flackern wahrgenommen.

Bei 10 Bit ist die Länge des Zeitfensters wie gesagt mit 1024 Takten bemessen. Der DC-Wert (Duty-Cycle-Wert, auch Tastverhältnis genannt) gibt nun an, wie lange der Ausgang auf 1 gesetzt wird. 512 als Obergrenze entspräche zum Beispiel etwa einem DC von 0.5 (= 512:1024), 768 entspräche 0.75 (= 768:1024), und so weiter.

Zunächst schalten wir das CCP-Modul in den PWM-Modus, das geht so:

// CCP-Modul in PWM-Modus schalten (die beiden LSB sind egal)
CCP1CON = 0b1100;

Der PIC16F627A nutzt im PWM-Modus intern dem Timer2 zum Zählen. Wenn wir die Obergrenze des Timer2 maximal wählen, haben wir also die höchste PWM-Auflösung, aber die geringste PWM-Frequenz. Diese Frequenz lässt sich so berechnen:

Die Berechnung der PWM-Frequenz

Bei 4MHz liegt sie bei knapp 4kHz, wenn wir den Vorteiler des Timer2 auf 1 setzen. Das ist so ganz gut, weil 4kHz für das Auge nicht sichtbar sind. Die Obergrenze von Timer2 bestimmt zudem die Auflösung der PWM. Wie setzen sie auf den Maximalwert, also 255:

// Obergrenze von Timer2 setzen:
PR2 = 0xff;

// Prescaler von Timer2 auf 1:1 setzen (Bits Nr. 0 und 1)
// (00 = 1:1, 01 = 1:4, 1x = 1:16)
// und Timer einschalten (Bit Nr. 2)
T2CON = 0b100;

Wird der Vorteiler von Timer2 höher gewählt, dann wird die PWM-Frequenz geringer.

Jetzt wird es den einen oder die andere vielleicht wundern: Warum haben wir denn 10 Bit Auflösung, wenn Timer2 nur bis 255 zählt (8 Bit)? Da bedient der PIC sich anderer Register und baut dort zwischenzeitlich einen Zähler bis 4 ein, so bekommt er die höhere Auflösung.

Wie gesagt, da der PIC16F627A ein 8-Bit-Controller ist, muss auch der DC-Wert gesplittet werden. Die MSB sind im Register CCPR1L zu finden, die LSB in CCP1X und CCP1Y. Das LSB des 10-Bit-Wertes ist also CCP1Y, dann kommt CCP1X, und dann CCPR1L. Wenn also nur eine PWM-Auflösung von 1:255 DC benötigt wird, können CCP1X und CCP1Y einfach auf 0 belassen und ein 8-Bit-Wert in CCPR1L geschrieben werden. Das machen wir so:

// DC-Wert einstellen
CCPR1L = DC >> 2;
CCP1X = DC & 1;
CCP1Y = (DC >> 1) & 1;

Das war es zum PWM-Modul, weitere Feinheiten sind im Datenblatt ab S. 61 zu finden.

Nach oben

TIMER: 1Hz-Takt mit dem Timer0

Der PIC16F627A besitzt mehrere Timer, unter anderem den 8-Bit-Timer0. Es gibt viele Konfigurationsmöglichkeiten, hier arbeitet er als einfacher Timer, der mit einem Viertel (wichtig!) des externen Taktes hochzählt.

Es besitzt ebenso noch einen Prescaler, der auf die Werte 1:2 bis 1:256 einstellbar ist. Die Tabelle dazu sieht so aus:

PS2PS1PS0Prescaler-Value
0001:2
0011:4
0101:8
0111:16
1001:32
1011:64
1101:128
1111:256

Die Prescaler-Value kann übrigens per Software - auch im Betrieb - dynamisch gesetzt werden, was sehr praktisch ist.

Hier die nötigen Einstellungen im Programmcode:

// Timer0 auf Timer-Mode setzen (T0CS = Timer0 clock select)
T0CS = 0;

Den Vorteiler brauchen wir hier nicht.

Beim Überlauf des Timer0 (d.h. beim Übergang von 255 → 0) soll noch ein Interrupt ausgelöst werden:

// Interrupt bei Timerüberlauf auslösen
// TOIE = Timer0 interrupt enable)
T0IE = 1;

// Global Interrupts aktivieren (sonst passiert nichts!)
// (GIE = Global interrupt enable)
GIE = 1;

Verwenden wir nun zum Beispiel einen 4.194304MHz-Quarz, dann ist die interne Timer0-Frequenz ein Viertel davon, also 1.048576MHz. Nun zählt der Timer ja bis 256 (8 Bit!), daher landen wir bei einer Frequenz von 4096Hz, mit der die Interrupt-Service-Routine (wird oft mit ISR abgekürzt) dann aufgerufen wird:

// Interrupt-Routine
static void interrupt isr (void) {

  // kommt der Interrupt vom Timer0?
  // (T0IF = Timer0 interrupt flag)
  if (T0IF) {

    // z.B. Variable hochzählen, um 1Hz-Takt zu generieren
    counter++;

    // Sekunde um!
    if (counter >= 4095) {

      // resette die Hilfsvariable counter auf 0
      counter = 0;

      // hier steht dann das, was 1x pro Sekunde ausgeführt werden soll:
      // [...]

    }

    // Interrupt-Flag T0IF löschen
    T0IF = 0;

  }

}

In diesem Beispiel ist counter natürlich eine globale Variable.

Wichtig zu der ISR ist noch, dass diese bei allen Interrupts aufgerufen wird - es muss also erst überprüft werden, woher der Interrupt kam. Immer, wenn ein Timer0-Überlauf ausgelöst wurde, ist das Bit T0IF auf 1 gesetzt. Es ist daher wichtig, es vor dem Verlassen der ISR wieder zu resetten, da ansonsten die Interrupt-Routine direkt noch einmal aufgerufen wird.

Es kann ein beliebiges Quarz verwendet werden, im Falle des 1Hz-Taktes bietet sich jedoch eine Zweiterpotenz in der Frequenz an. Ich wähle deshalb gerne diese Kombination, weil sie einen sehr guten 1Hz-Takt erzeugt, und die ISR-Frequenz von ca. 4kHz ganz nett ist, da sie knapp unter einer ms liegt, die mensch vielleicht manchmal gerne auflösen möchte.

Aber übertrieben werden sollte es nicht; eine sehr hohe ISR-Frequenz kann zur Folge haben, dass die ISR so oft aufgerufen wird, dass dem Controller gar nicht genug Zeit bleibt, alles was in der ISR steht abzuarbeiten, geschweige denn das, was sonst noch so im Programmcode steht. Das Programm landet dann in einer Endlosschleife und hängt sich auf.

Nach oben

Mit dem USART-Modul Daten via RS232 austauschen

Alles Wichtige steht dazu im Datenblatt ab Seite 67. Zunächst müssen einige Überlegungen zur Baud-Rate getroffen werden; wir verwenden hier ein 4.194304MHz Quarz, was einer eher langsameren Baud-Rate entspricht. Das teilen wir dem Controller so mit:

// langsames Quarz
// (Baud Rate Generator High)
BRGH = 0;

Es gilt laut Datenblatt diese Formel:

Die Berechnung der Baud-Rate

Der Parameter X ist ein Byte groß und kann verwendet werden, um die Baud-Rate bei gegebener Quarz-Frequenz möglichst nahe an einen Standard-Wert anzunähern. Wie wählen hier 1200 Baud, also wählen wir demzufolge X = 54, damit landen wir bei 1191 Baud, das sind 0.7% Abweichung zu 1200. Erlaubte Abweichungen sind maximal 5%, es passt also ganz gut. Dieser Wert für X wird dann in das SPBRG-Register geschrieben:

// 1200 Baud @ 4.194304MHz Quarz
// (vermutl. Software Programmable Baud Rate Generator)
SPBRG = 54;

Für gängige Taktfrequenzen und Baud-Raten gibt es im Datenblatt auch Umrechentabellen.

Nun setzen wir das USART-Modul (das ja sowohl synchron als auch asynchron im Namen hat) auf asynchron, denn die RS232-Schnittstelle ist in ihrer einfachsten Form nun einmal asynchron. Zudem schalten wir die serielle Schnittstelle ein.

// USART-Modus auf "asynchron" stellen
SYNC = 0;

// die serielle Schnittstelle einschalten
// (vermutl. Serial Port Enable)
SPEN = 1;

Wenn ein Zeichen empfangen wird, wird immer das Bit RCIF (Interrupt-Flag) gesetzt. Das bedeutet, dass das Programm dieses Bit pausenlos abfragen muss (sogenanntes Polling) oder aber wir einen Interrupt zuschalten. Wir entscheiden uns natürlich für die Interrupt-Version, weil diese uns das Hauptprogramm freihält:

// Interrupt bei empfangenem Zeichen auslösen
RCIE = 1;

// danach global (GIE) und peripher (PEIE) Interrupts aktivieren
GIE = 1;
PEIE = 1;

Jetzt muss die serielle Schnittstelle nur noch eingeschaltet werden, und wir können Daten empfangen.

// Empfangsmodus einschalten
CREN = 1;

Die ISR wird nun immer aufgerufen, wenn ein Zeichen empfangen wurde. Dieses Zeichen steht dann im RCREG-Register:

static void interrupt isr (void) {

  // Zeichen empfangen?
  if (RCIF) {

    // Wert auslesen
    value = RCREG;

    // eventuelle Fehler clearen
    // (hier fehlt richtige Fehlerüberprüfung!)
    CREN = 0;
    CREN = 1;
    OERR = 0;
    FERR = 0;

  }

}

Dabei ist value natürlich eine globale Variable, in der nach der ISR dann das zuletzt eingelesene Zeichen steht. Es fehlt an dieser Stelle eine Behandlung der Fehler-Flags, aber darum soll es hier nicht gehen. Bei einer niedrigen Baud-Rate von 1200 kann auch gar nicht so viel schiefgehen.

Anmerkung: In den meisten Fällen muss in der ISR dasjenige Bit, das durch den Interrupt gesetzt wurde, wieder auf 0 zurückgesetzt werden, da sonst die ISR ununterbrochen aufgerufen würde. Hier betrifft dies das Bit namens RCIF. In diesem Sonderfall aber muss es nicht zurückgesetzt werden, das geschieht automatisch beim Auslesen des RCREG-Registers. Streng genommen kann die Software das RCIF-Bit gar nicht beschreiben, es ist read-only.

Nun wollen wir noch ein Zeichen senden. Initialisiert worden ist das USART-Modul ja schon oben; da sich sie Empfangs- und Sendeeinheit den selben Baud-Rate-Generator teilen, aber ansonsten unabhängig sind, geht es nun recht einfach:

// Wert in das Ausgangsregister schreiben
TXREG = value;

// Senden
TXEN = 1;

Danach sendet das USART-Modul automatisch das in TXREG enthaltene Zeichen und leert danach das TXREG-Register. value ist wieder eine Variable, in der das zu übertragende Zeichen steht.

Das war es auch schon; die weiteren Feinheiten entnehmen Sie bitte dem Datenblatt ab Seite 67.

Nach oben

show print layout Die deutsche Version der Seite besuchen

www.jb-electronics.de » Electronics » Digital Technology » PIC16F627A Reference

PIC16F627A Reference

My apologies...

There is no English translation of this page available yet. It will take some more time for me to translate the whole website.

If you have a particular interest in this page getting translated as fast as possible, please contact me; I will see what I can do. Please click here for the German version.

To the top