Programmierkurs
für PIC-Mikrocontroller in C (CC5X Compiler)
Teil 2
Bit-Operationen
ODER - Funktion
Zweck der Funktion
ist es, bestimmte Bits in einem Byte zu setzen, und dabei andere Bits
nicht zu beeinflussen.
| a = 0b.0000.1000; | // a=8 |
| b = 0b.0000.0101; | // Bit 0 und 2 setzen |
| a ||= b ; | // "a" und "b" ODER-verknüpfen und das Ergebnis in "a" speichern |
| //__0b.0000.1101 | //Dies ist Ergebnis in "a" |
Anstatt “ a ||= b “ kann man auch „ a = a || b “ oder „ a = a || 0b.0000.0101 “ schreiben.
UND - Funktion
Zweck der Funktion ist es, bestimmte Bits in einem Byte zurückzusetzen,
und dabei andere Bits nicht zu beeinflussen.
a = 0b.0100.1010;
a &= 0b.1111.0011;
Hier werden Bits 2 und 3 zurückgesetzt. Das Ergebnis in "a" lautet 0b.0100.0010
Schiebeoperationen
Alle Bits in einem Register (Variable) werden in
eine Richtung verschoben.
a = 0b.0000 .0001;In dem obigen Beispiel wird die "1" nach Bit 1 geschoben und Bit 0 wird auf den Wert "0" gesetztt.
a = a << 1; //Schieben nach links um eine Stelle Ergebnis in "a" lautet 0b.0000 .0010
a = 0b.1000 .0000;
a = a >> 1; //Schieben nach rechts um eine Stelle Ergebnis in "a" lautet 0b.0100 .0000
Das Bit 6 erhält den vorherigen Wert des Bit 7 und Bit7 wird "0".
Ich habe herausgefunden,
dass in dem Programm "CC5x" das Carry-Flag vor Schiebeoperationen auf 0 zurückgesetzt
wird.
Man kann auch mit Hilfe einer Funktion schieben, die im Compiler eingebaut ist.
a = rr(a); // Alle Bits um eine Stelle nach rechts verschieben. a = rl(a); // Alle Bits um eine Stelle nach links verschieben.Dabei wird auch das Carry-Flag beeinflusst, indem "durch" das Carry-Flag geschoben wird :
Flags sind spezielle
Bits, dessen Wert automatisch bei einigen Operationen geändert wird.
"Carry" ist ein Übertrags-Flag und wird immer gesetzt, wenn ein 8-Bit-Register
überläuft (255+5 = 4).
"Zero" wird immer gesetzt, wenn ein 8-Bit-Register nach einer mathematischer
Operation den Wert 0 annimmt (250+6=0).
Flags sind wichtig bei Programmierung mit Assembler, in C werden sie automatisch
berücksichtigt.
Als Übung machen wir ein Lauflicht-Programm.
Unser Unterprogramm mit
Zeitschleife wird zur Funktion. Diese Funktion erhält einen Wert. Den übergebenen
Wert finden wir in der Variable "ms". Die wird in "void pause(uns16 ms)" als
16 Bit Variable deklariert, d.h. sie kann Werte im Bereich 0...65535 annehmen.
"uns" -heißt "Unsigned", also eine Zahl ohne Vorzeichen, man kann also keine
negativen Zahlen in "ms" speichern.
// Lauflicht, Taktfrequenz 4MHz
#include <C:\cc5\16F84.h> // Prozessor-Typ definieren
#pragma config |= 0b.1111.1111.0010 // Konfigurations-Wort
void pause(uns16 ms) // Unterprogramm zum Abwarten einer
{ // angegebenen Anzahl von Millisekunden
while(ms) // Schleife verlassen, wenn ms=0 ist
{
OPTION = 2; // Vorteiler auf 8 einstellen
TMR0 = 131; // 125 * 8 = 1000 (= 1 ms)
while (TMR0); // Abwarten einer Millisekunde
ms--; // "ms" mit jeder Millisekunde erniedrigen
}
}
void main(void) // Hier beginnt das Hauptprogramm
{
char i;
TRISB = 0b.0000.0000; // PortB als Ausgang definieren
PORTB = 0;
Sprungmarke:
PORTB.0 = 1 ; // Bit 0 von PortB setzen
pause(500); // LED 0 halbe Sekunde leuchten lassen
for (i=0 ;i<7 ;i++) // folgende Befehle 8-mal wiederholen
{
PORTB = PORTB << 1 ; // 1 kommt ins nächste Bit links
pause(500); // jede LED halbe Sekunde leuchten lassen
}
PORTB = 0; // Alle LEDs löschen
goto Sprungmarke; // Alles wiederholen
}
Bits Maskierenchar Schaltern ; Schaltern = PORTA & 0b. 0000.1111 ;
Nachdem wir in der Variable "Schalter" eine Zahl haben, entsprechend dem Schalterzustand, können wir es mit Hilfe folgender Anweisung auswerten:
switch(Schaltern)
{
case 1 : // wenn nur Schalter 1 betätigt ist
// tu dies
break;
case 2 : // wenn nur Schalter 2 betätigt ist
// tu das
break;
case 15 : // wenn alle Schalter betätigt sind
// tu jenes
break;
}
Hier ist ein Beispiel: ein komplizierteres Lauflicht zum Herunterladen. Mit Auswertung der Schalterzustände und verschiedener Lichteffekte.
Programmstruktur und Interruptanwendung
Grundsetzlich
gint es 3 arten von Steuerungen.
1 Ablaufsteuerung
Programm Abarbeitet befehle nacheinander, Zeitschleifen können verwendet werden
um Ablaufzeiten einzustellen.
2 EreignisOrientirteSteuerung
Programm läuft in einer Schleife wo alle mögliche Ereignisse abgefragt werden.
Wenn ein Ereignis passiert Schleife
wird verlassen, entsprechenden Handlungen ausgeführt. Dann wird wieder zurück
zu Schleife übergegangen um weitere Ereignisse abzufangen.
3 Mischung von Ablaufsteuerung
und EreignisOrientirteSteuerung.
Erst hier braucht man ein Interrupt wo Ablaufsteuerung durch ein Ereignis unterbrochen
werden soll und man nicht weis wann dieser Ereignis passiert.
Bei nicht zeitkritischen Programmen wird Interrupt (Ereignis) direkt nach dem
Eintritt abgearbeitet.
Bei zeitkritischen Programmen Iterruptroutine erkennt welche genau Ereignis
passiert ist und setzt eine Variable(Flag). Das Hautprogramm an bestimmten Stellen
abfragt ob der Flag gesetzt ist und falls "ja" führt entsprechende
befehle aus.
Wenn Programm nicht mahl für 20 Mikrosekunden unterbrochen werden darf (softwaremäßige
serielle Übertragung ist der Fall) sollen alle Inteerups vor dieser Routine
ausgeschaltet werden und nach der Routine
wieder eingeschaltet. So wird auch Interrupt während Routine empfangen aber
erst nach Beendigung abgearbeitet.
Wenn etwas ohne Interruptanwendung realisierbar ist empfehle ich auch keine Interrupts zu verwenden. Weil Interrupts sporadische und schwer erklärbare Fehlern verursachen können oder Zeitplan des Programms durcheinander bringen können.
Interrupt
Mit ganz einfachen
Worten : Wenn ein bestimmtes Ereignis passiert, dann wird das Hauptprogramm
unterbrochen und eine speziell zu diesem Ereignis zugewiesenes Unterprogramm
ausgeführt. Danach wird das Hauptprogramm fortgesetzt. Unterprogramme, die Interrupts
bearbeiten, fangen immer bei Adresse 0x0004 an. Wenn ein Interrupt passiert,
wird das Programm an Adresse 0x0004 aufgerufen. Seine Aufgabe ist es, zu unterscheiden,
welches Ereignis aufgetreten ist und entsprechend zu handeln. Durch Setzen von
speziellen Flags kann man Interrupts einschalten. Wenn ein Interrupt aufgerufen
wird, kann man durch andere Flags abfragen, welcher Interrupt genau aufgetreten
ist .
Beispiel: Pegeländerung an RB0 , steigende Flanke. GIE=1; // - Interrupts überhaupt erlauben. INTE=1; // - Interrupt von RB0 erlauben. INTEDG=1; // - Bei steigender Flanke.
Beim Programmieren in C ist es gegenüber Assembler vorteilhaft, dass man sich nicht um Flags und Speicherbanke kümmern muss. Man braucht lediglich aus dem Datenblatt des Mikrocontrollers die Flag-Namen und ihre Funktion zu entnehmen. Wenn nur ein Interrupt erlaubt ist, dann ist es nicht erforderlich, die Interruptursache zu ermitteln, denn es ist ja nur eine möglich.
#include <C:\cc5\16F84.h> // Prozessor-Typ definieren
#include "C:\cc5\int16CXX.H" // Ist notwendig um Interrupts zu nutzen
#pragma config |= 0b.1111.1111.0010 // Konfigurations-Wort
bit beep @ PORTB.1 ; // Pin 7 erhält Name "beep"
void Ton(void); // Unterprogramm deklarieren, damit sie in
// "InterruptRoutine" bekannt wird
char i; // 8-Bit Variable (0...255)
#pragma origin 4 // Der folgende Code steht ab Adresse 4
interrupt InterruptRutine(void) // Interruptroutine
{
int_save_registers // W, STATUS (und PCLATH) retten
if (INTF) // Ursache des Interrupts feststellen
{
Ton(); // Reaktion auf Interrupt ist "Piepsen"
INTF = 0; // Flag zurücksetzen
}
int_restore_registers // W, STATUS (und PCLATH) Wiederherstellen
}
void Ton(void) // Erzeugung des Piepstones mit 500Hz, Dauer 0,5sek
{
for(i=0;i<250;i++)
{
beep=1; // Impuls Generieren
OPTION = 2; // Vorteiler auf 8 einstellen
TMR0 = 131; // 125 * 8 = 1000 (= 1 ms)
while (TMR0); // warten, bis TMR0=0 wird
beep=0; // Pause Generieren
OPTION = 2; // Vorteiler auf 8 einstellen
TMR0 = 131; // 125 * 8 = 1000 (= 1 ms)
while (TMR0); // warten, bis TMR0=0 wird
}
}
void main(void) // Hier beginnt das Hauptprogramm
{
TRISB = 0b.1111.1101; // Pin 6 als Ausgang deklarieren
INTEDG=0; // Fallende Flanke am RB0
GIE=1; // Interrupts erlauben
INTE=1; // Interrupt von RB0 erlauben.
Ton(); // Ein Mal Piepsen beim Einschalten
Sprungmarke: // Unendliche Programm-Schleife
goto Sprungmarke;
} // Ende des Hauptprogramms
Die Hauptprogramm kann
an beliebiger Stelle durch den Interrupt unterbrochen werden. Dabei können Flags
z. B. "Carry" oder "Zero" durch die Interruptroutine geändert werden und dadurch
im Hauptprogramm zu Fehler führen. Damit das nicht passiert, müssen einige Register
am Anfang der Interruptroutine gespeichert werden und am Ende wiederhergestellt
werden. Diesen Vorgang erledigen die Befehle "int_save_registers" und "int_restore_registers"
Diese Befehle sind in der "int16CXX.H" - Datei definiert.