Röhren und µC

PIC32-Tutorial
- Teil 1: Einstieg und Peripherie -

Hardware, IDE und Einstieg

Mir ist mittlerweile aufgefallen, dass sich der Peripherie-Takt beim Mini-32 anscheinend nicht ändern lässt!
Dieser läuft bei mir immer mit 80MHz, egal welcher Teiler eingestellt wurde.

Moderne µC sind immer häufiger nur als SMD-Bauform erhältlich (in der PIC32-Familie gibt es allerdings auch noch einige Ausnahmen in DIP-Gehäuse). Dies erschwert einen schnellen Testaufbau auf Lochrasterplatine oder Steckbrett. Für dieses Tutorial verwende ich das Mini-32-Board von MikroElektronika. Es besitzt die Abmessungen eines DIP26-Gehäuses und auf dem Board ist nicht nur der PIC32MX534F064H-Controller, sondern auch die zum Betrieb unbedingt notwendige Grundausstattung an Periperiebeschaltung (Quarz, Kondensatoren, Reset-Taster, etc.) unter gebracht. Desweiteren gibt es auch noch eine Mini-USB-Buchse über die mittels eines Bootloaders die Software auf den µC übertragen wird. Somit enfällt für den Einstieg auch die Anschaffung eines Programmiergeräts. Die wenigen externen Bauteile, die in diesem Tutorial zu Anfang benötigt werden, können bequem zusammen mit dem Mini-32 auf einem Steckbrett untergebracht werden. Es kann natürlich auch ein anderes PIC32-Board verwendet werden. Als Entwicklungsumgebung für die Beispiele auf dieser Seite verwende ich MPLAB 8 IDE mit dem C32-Plugin als C-Compiler. Um ein neues Projekt anzulegen, verwendet man den "Project Wizard...". Dieser findet sich unter dem Reiter "Project". Im "Project Wizard" wählt man dann zunächst aus einer Liste ("select a device") den gewünschten PIC-µC aus (in unserem Fall PIC32MX534F064H) und anschließend die Sprache und den Compiler. Anschließend gibt man dem Projekt einen Namen und wählt den gewünschten Speicherort aus. Danach kann man noch bereits existierende Dateien zum Projekt hinzufügen und das neue Projekt erstellen. Jetzt fehlt noch eine .c-Datei in die man den Programmcode schreiben kann. Dafür klickt man unter "File" auf "New" und speichert die neue Textdatei als z.B. main.c im Projektordner ab. Dann bindet man sie ins Projekt ein, indem man mit Rechts auf "Source Files" klickt und anschließend auf "Add Files...". Im sich öffnenden Fenster muss man dann nur noch die Datei main.c auswählen und fertig ist das neue Projekt.

Hinweis: Mittlerweile (Stand: März 2013) verwende ich als IDE MPLAB X, weiterhin mit C32 als Compiler.

Jetzt kann es auch schon mit dem Programmieren los gehen. Als Erstes will ich kurz das Grundgerüst erläutern, aus dem eigentlich jedes Programm besteht.

 1 //Header-Dateien einbinden
 2 #include <p32xxxx.h>                    // einbinden der Header-Datei um die Chiptypischen Bezeichnungen für Register und Ports verwenden zu können
 3 #include <plib.h>                       // Header-Datei mit Funktion für die µC-Peripherie
 4 //Konfigurationseinstellungen
 5 #pragma config FSOSCEN = OFF            // Disable secondary oscillator
 6 #pragma config POSCMOD=HS               // High speed crystal mode
 7 #pragma config FNOSC=PRIPLL             // Use Primary Oscillator with PLL (XT, HS, or EC) 
 8 #pragma config FPLLIDIV = DIV_2         // Divide 8MHz to between 4-5MHz before PLL (now 4MHz)
 9 #pragma config FPLLMUL=MUL_20           // Multiply with PLL (now 80MHz)
10 #pragma config FPLLODIV=DIV_1           // Teilt durch 1
11 #pragma config FWDTEN = OFF             // Disable watchdog timer
12 
13 #define SYSCLK 80000000L                // SYSCLK kann jetzt synonym für 80MHz verwendet werden -> wird von einigen Funktion benötigt 
14 
15 int main(void)                          // Hauptprogramm
16 {
17         // Initialisierung
18         SYSTEMConfigPerformance(SYSCLK);        // für maximale Performence
19 
20         while(1)                                // Endlosschleife für eigentliches Programm
21         {
22                 ...code...
23         }
24         return 1;
25 } 

Zuerst kommen die Include-Anweisungen, mit denen die Header-Dateien eingebunden werden. Mittels "#pragma config" wird anschließend der µC konfiguriert. Eine Liste von config-Anweisungen findet sich in der Hilfsfunktion der MLAB-IDE. Die Hauptfunktion main() setzt sich dann aus zwei Teilen zusammen. Als Erstes die Intialisierung: Diese wird nach dem Reset nur einmal durchlaufen, weshalb man hier die Hardware/Peripherie (Timer, ADC, Schnittstellen, Interruptverhalten) des µC initialisiert bzw. in ihren Startzustand versetzt oder auch Variablen deklariert und initialisiert. Zweiter Bestandteil der Main-Funktion ist eine Endlosschleife. In ihr arbeitet der µC das eigentliche Hauptprogramm ab.

"Hello World!"

Als erste Programm soll eine Taste abgefragt werden. Der Zustand der Taste soll über eine LED ausgegeben werden.

 1 #include <p32xxxx.h>                    // include chip specific header file
 2 #include <plib.h>                       // include peripheral library functions
 3 //Konfigurationseinstellungen
 4 #pragma config FSOSCEN = OFF            // Disable secondary oscillator
 5 #pragma config POSCMOD=HS               // High speed crystal mode
 6 #pragma config FNOSC=PRIPLL             // Use Primary Oscillator with PLL (XT, HS, or EC) 
 7 #pragma config FPLLIDIV = DIV_2         // Divide 8MHz to between 4-5MHz before PLL (now 4MHz)
 8 #pragma config FPLLMUL=MUL_20           // Multiply with PLL (now 80MHz)
 9 #pragma config FPLLODIV=DIV_1           // Teilt durch 1
10 #pragma config FWDTEN = OFF             // Disable watchdog timer
11 
12 #define SYSCLK 80000000L
13 
14 int main(void)
15 {
16         SYSTEMConfigPerformance(SYSCLK);        //für maximale Performence
17 
18         TRISDbits.TRISD0 = 1;                   // RD0 -> Eingang
19         TRISEbits.TRISE0 = 0;                   // RE0 -> Ausgang
20         
21         
22         while(1)                                //Taster in Endlosschleife pollen um LED ein/auszuschalten
23         {
24                 if(PORTDbits.RD0==1)            //Taster betätigt?
25                 {
26                         LATEbits.LATE0 = 1;     //ja -> LED an
27                 }
28                 else
29                 {
30                         LATEbits.LATE0 = 0;     //nein -> LED aus
31                 }
32         }
33         return 1;
34 } 

Das Ergebnis ist nicht besonderst spektakulär. Drückt man die Taste geht die LED an, lässt man sie wieder los geht die LED aus. Dafür hätte man noch keinen µC gebraucht. Deshalb ändern wir das Programm jetzt so ab, dass die LED bei jedem Tastendruck getoggelt wird. Der Programm-Code sieht dann so aus:

 1 #include <p32xxxx.h>                    // include chip specific header file
 2 #include <plib.h>                       // include peripheral library functions
 3 //Konfigurationseinstellungen
 4 #pragma config FSOSCEN = OFF            // Disable secondary oscillator
 5 #pragma config POSCMOD=HS               // High speed crystal mode
 6 #pragma config FNOSC=PRIPLL             // Use Primary Oscillator with PLL (XT, HS, or EC) 
 7 #pragma config FPLLIDIV = DIV_2         // Divide 8MHz to between 4-5MHz before PLL (now 4MHz)
 8 #pragma config FPLLMUL=MUL_20           // Multiply with PLL (now 80MHz)
 9 #pragma config FPLLODIV=DIV_1           // Teilt durch 1
10 #pragma config FWDTEN = OFF             // Disable watchdog timer
11 
12 #define SYSCLK 80000000L
13 
14 int main(void)
15 {
16         SYSTEMConfigPerformance(SYSCLK);        //für maximale Performence
17 
18         TRISDbits.TRISD0 = 1;                   // RD0 -> Eingang
19         TRISEbits.TRISE0 = 0;                   // RE0 -> Ausgang
20         
21         
22         while(1)                                //Taster in Endlosschleife pollen um LED ein/auszuschalten
23         {
24                 if(PORTDbits.RD0==1)            //Taster betätigt?
25                 {
26                         LATEbits.LATE0 = !LATEbits.LATE0;       //ja -> LED toggeln
27                 }
28         }
29         return 1;
30 } 

Allerdings funktioniert das Programm auf dem µC noch nicht so wie gedacht. Manchmal reagiert die LED auf den Tastendruck wie gewünscht. Manchmal behält sie aber nach kurzem Zucken ihren Zustand. Grund dafür ist, dass der Taster in der while-Schleife sehr schnell hinter einander abgefragt wird. Somit wird die LED bei einmal drücken mehrmals getoggelt, da die Abfrage schneller wiederholt wird, als man los lassen kann. Damit das Programm richtig funktioniert müssen wir also dem Bediener Zeit lassen, die Taste los zu lassen. Nach dem Betätigen soll der Taster erst wieder abgefragt werden, wenn eine gewisse Zeit vergangen ist. Wir brauchen also eine Warteschleife. Diese soll mit einem Timer realisiert werden.

Timer

Wir schreiben uns jetzt also eine Funktion void wait(void). Doch zuerst muss der Timer konfiguriert werden, weshalb wir auch noch eine Funktion void config_timer(void) benötigen. Die notwendigen Informationen finden wir im Datenblatt unseres µCs und in der Timer-Application-Note von Microchip. Verwendet werden soll Timer1. Die Taste soll ungefähr 200ms lang nicht abgefragt werden. Das sollte locker ausreichen, um mit einem kurzen Drücken die LED sicher umzuschalten. Im Bild links kann man das Blockschaltbild des Timer1 sehen.

Man kann ihn aus unterschiedlichen Taktquellen speisen. Wir nehmen für dieses Programm den Peripheral-Takt. Dieser wird vom µC-Takt abgleitet. Über eine "#pragma config"-Anweisung lässt sich dessen Vorteiler einstellen.

 1 #include <p32xxxx.h>                    // include chip specific header file
 2 #include <plib.h>                       // include peripheral library functions
 3 //Konfigurationseinstellungen
 4 #pragma config FSOSCEN = OFF            // Disable secondary oscillator
 5 #pragma config POSCMOD=HS               // High speed crystal mode
 6 #pragma config FNOSC=PRIPLL             // Use Primary Oscillator with PLL (XT, HS, or EC)
 7 #pragma config FPLLIDIV = DIV_2         // Divide 8MHz to between 4-5MHz before PLL (now 4MHz)
 8 #pragma config FPLLMUL=MUL_20           // Multiply with PLL (now 80MHz)
 9 #pragma config FPLLODIV=DIV_1           // Teilt durch 1
10 #pragma config FWDTEN = OFF             // Disable watchdog timer
11 #pragma config FPBDIV = DIV_1           // stellt den Peripheral-Clock-Divisor auf 1:1 ein
12 
13 #define SYSCLK 80000000L
14 
15 void config_timer(void)
16 {
17     T1CON = 0x00;                           // hält Timer1 an und setzt setzt alle Einstellungen zurück -> PBCLK als Taktquelle
18     T1CONbits.TCKPS = 0b11;                 // Timer1 Vorteiler auf 1:256 eingestellt. -> Takt für Timer ca 312,5kHz
19     TMR1 = 0x00;                            // Timer1-Register auf Startwert setzen -> Überlauf nach ca. 200ms
20     PR1 = 0xFFFF;                           // Period-Register laden
21     return;
22 }
23 void wait(void)
24 {
25     T1CONbits.ON = 1;                       // startet Timer1
26     while(IFS0bits.T1IF!=1);                // pollen des Timer-Interruptflags -> zeigt an wenn Timer1 überläuft
27     T1CONbits.ON = 0;                       // nach Überlauf -> Timer1 anhalten...
28     IFS0bits.T1IF = 0;                      // ...und Interruptflag zurück setzen
29     TMR1 = 0x00;                            // Startwert für den nächsten Durchlauf wieder laden
30     return;
31 }
32 
33 int main(void)
34 {
35     SYSTEMConfigPerformance(SYSCLK);        //für maximale Performence
36 
37     TRISDbits.TRISD0 = 1;                   // RD0 -> Eingang
38     TRISEbits.TRISE0 = 0;                   // RE0 -> Ausgang
39 
40     config_timer();
41 
42     while(1)                                        //Taster in Endlosschleife pollen um LED ein/auszuschalten
43     {
44         if(PORTDbits.RD0==1)                        //Taster betätigt?
45         {
46             LATEbits.LATE0 = ~LATEbits.LATE0;       //ja -> LED toggeln
47             wait();                                 // Warteschleife aufrufen
48         }
49     }
50     return 1;
51 }
52

Jetzt funktioniert das Toggeln der LED wie gedacht. Allerdings vertrötteln wir im Moment noch einen Haufen Zeit in der Warteschleife und beim Pollen der Taste. Da gibt es aber auch einen eleganteren Weg.

Interrupt

Da man aber im seltesten Fall nur eine Taste abfragen möchte, ist das Pollen einer Taste eine sehr ungeschickte Lösung. Besser ist es auf den Tastendruck mit einem Interrupt zu reagieren. Interrupts unterbrechen das laufende Programm um auf ein bestimmtes Ereignis zu reagieren (Überlauf des Timers, Pegelwechsel an Pin, AD-Wandler beendet Wandlung, etc.) und kehren nach der Reaktion (die z.B. im Abarbeiten eines kurzen Unterprogramms besteht) wieder an die Stelle im Hauptprogramm zurück, wo der µC zu letzt war. Im Programm unten verwende ich als Interruptquelle den externen Interrupt0 (INT0), um auf einen Tastendruck zu reagieren. Dafür muss die Taste an Pin RD0 angeschlossen sein (ist sie auch in der vorherigen Programmen gewesen). Das x++ in der Endlosschleife im Hauptprogramm symbolisiert irgend einen Prozess, der ständig abgearbeitet werden soll und nur kurz für die Reaktion auf den Tastendruck unterbrochen werden soll. Die für die Konfiguration einer ISR (Interrupt-Service Routine) notwendingen Informationen findet man im "Section 8: Interrupt"-Datenblatt der PIC32-Familie. Informationen zur Verwendung von Interrupts in C findet man im C32 users guide von Microchip.


 1 #include <p32xxxx.h>                    // include chip specific header file
 2 #include <plib.h>                       // include peripheral library functions
 3 //Konfigurationseinstellungen
 4 #pragma config FSOSCEN = OFF            // Disable secondary oscillator
 5 #pragma config POSCMOD=HS               // High speed crystal mode
 6 #pragma config FNOSC=PRIPLL             // Use Primary Oscillator with PLL (XT, HS, or EC)
 7 #pragma config FPLLIDIV = DIV_2         // Divide 8MHz to between 4-5MHz before PLL (now 4MHz)
 8 #pragma config FPLLMUL=MUL_20           // Multiply with PLL (now 80MHz)
 9 #pragma config FPLLODIV=DIV_1           // Teilt durch 1
10 #pragma config FWDTEN = OFF             // Disable watchdog timer
11 #pragma config FPBDIV = DIV_1           // stellt den Peripheral-Clock-Divisor auf 1:1 ein
12 
13 
14 #define SYSCLK 80000000L
15 
16 // External Interrupt 0 Service Routine -> PIN RD0
17 
18 void config_timer(void)
19 {
20     T1CON = 0x00;                           // hält Timer1 an und setzt setzt alle Einstellungen zurück -> PBCLK als Taktquelle
21     T1CONbits.TCKPS = 0b11;                 // Timer1 Vorteiler auf 1:256 eingestellt. -> Takt für Timer ca 312,5kHz
22     TMR1 = 0x00;                            // Timer1-Register auf Startwert setzen -> Überlauf nach ca. 200ms
23     PR1 = 0xFFFF;                           // Period-Register laden
24     return;
25 }
26 void wait(void)
27 {
28     T1CONbits.ON = 1;                       // startet Timer1
29     while(IFS0bits.T1IF!=1);                // pollen des Timer-Interruptflags -> zeigt an wenn Timer1 überläuft
30     T1CONbits.ON = 0;                       // nach Überlauf -> Timer1 anhalten...
31     IFS0bits.T1IF = 0;                      // ...und Interruptflag zurück setzen
32     TMR1 = 0x00;                            // Startwert für den nächsten Durchlauf wieder laden
33     return;
34 }
35 
36 int main(void)
37 {
38     SYSTEMConfigPerformance(SYSCLK);// für maximale Performence
39 
40     TRISDbits.TRISD0 = 1;                   // RD0 -> Eingang
41     TRISEbits.TRISE0 = 0;                   // RE0 -> Ausgang
42 
43     INTEnableSystemMultiVectoredInt();
44     //config für Interrupt
45     IEC0bits.INT0IE = 0;                    // Interrupt disabel
46     IFS0bits.INT0IF = 0;                    // Flag für INT0 löschen
47     INTCONbits.INT0EP = 1;                  // Interrupt bei steigender Flanke an Pin RD0
48     IPC0 = 0x1C000000;                      // IP03 = 0b111 -> Priorität = 7
49     IEC0bits.INT0IE = 1;                    // Interrupt freigeben
50     config_timer();
51     int x=0;
52     while(1)                                // Taster in Endlosschleife pollen um LED ein/auszuschalten
53     {
54         /*Hier steht dann irgendein Vorgang, der in
55         der interruptfreien Zeit abgearbeitet wird*/
56         x++;                                //z.B. x immer um 1 erhöhen
57     }
58     return 1;
59 }
60 
61 //Interrupt Service Routine
62 void __ISR(3, ipl7) INT0IntHandler(void)
63 {
64     LATEbits.LATE0 = ~LATEbits.LATE0;               // LED toggeln
65     wait();                                         // Warteschleife aufrufen
66     IFS0bits.INT0IF = 0;                            // Flag muss wieder gelöscht werden
67 }
68 

Zwar müssen wir jetzt nicht mehr ständig die Taste pollen, aber nach einen Tastendruck hängen wir (für µC-Verhältnisse) immer noch eine halbe Ewigkeit in der Warteschleife fest. Also ersetzen wir diese auch noch durch einen Interrupt. Zur Konfiguration der beiden Interruptquellen greifen wir ab jetzt auf die Funktionen der Peripheral-Libary zurück. Das macht das Programm übersichtlicher und erspart uns Arbeit.

 1 #include <p32xxxx.h>                // hier wird die µC spezifische Header-Datei eingebunden
 2 #include <plib.h>                   // include peripheral library functions
 3 //Konfigurationseinstellungen
 4 #pragma config FSOSCEN = OFF            // Disable secondary oscillator
 5 #pragma config POSCMOD = HS             // High speed crystal mode
 6 #pragma config FNOSC = PRIPLL           // Use Primary Oscillator with PLL (XT, HS, or EC) 
 7 #pragma config FPLLIDIV = DIV_2         // Divide 8MHz to between 4-5MHz before PLL (now 4MHz)
 8 #pragma config FPLLMUL=MUL_20           // Multiply with PLL (now 80MHz)
 9 #pragma config FPLLODIV = DIV_1         // Sysclk = 80MHz
10 #pragma config FWDTEN = OFF             // Disable watchdog timer
11 #pragma config FPBDIV = DIV_1           // stellt den Peripheral-Clock-Divisor auf 1:1 ein    
12 
13 
14 #define SYSCLK 80000000L
15 
16 
17  
18 void config_timer(void)
19 {       
20         T1CON = 0x00;                                   // hält Timer1 an und setzt setzt alle Einstellungen zurück -> PBCLK als Taktquelle
21         T1CONbits.TCKPS = 0b11;                         // Timer1 Vorteiler auf 1:256 eingestellt
22         TMR1 = 0x00;                                    // Timer1-Register auf Startwert setzen
23         PR1 = 0xFFFF;                                   // Period-Register laden        
24         return;
25 }
26 int main(void)
27 {
28         SYSTEMConfigPerformance(SYSCLK);                // für maximale Performence
29 
30         TRISDbits.TRISD0 = 1;                           // RD0 -> Eingang
31         TRISEbits.TRISE0 = 0;                           // RE0 -> Ausgang
32         TRISEbits.TRISE1 = 0;
33         TRISEbits.TRISE2 = 0;
34         LATE = 0x00;
35 
36         
37 //config für Interrupt
38         INTEnableSystemMultiVectoredInt();              // ermöglicht Multi-Vector Interrupts
39         INTDisableInterrupts();                         // alle Interrupts sperren
40         INTEnable(INT_INT0,1);                          // ext. Interrupt erlauben
41         INTSetPriority(INT_INT0,7);                     // ext. Interrupt-Priorität setzen
42         INTEnable(INT_T1,1);                            // Timer1-Interrupt erlauben
43         INTSetPriority(INT_T1,6);                       // Timer1-Interrupt-Prioritär setzen
44         INTClearFlag(INT_INT0);                         // zur Sicherheit die Interrupt-Flags zurücksetzen
45         INTClearFlag(INT_T1);
46         config_timer();                                 // Timer1 konfigurieren
47         INTEnableInterrupts();                          // gibt allgemein Interrupts frei
48 
49 
50         int x=0;
51         while(1)                                                
52         {
53                 /*Hier steht dann irgendein Vorgang, der in 
54                 der interruptfreien Zeit abgearbeitet wird*/
55 
56                 x++;            //z.B. x immer um 1 erhöhen
57         }
58         return 1;
59 }
60 // Interrupt Service Routine für INT0
61 void __ISR(3, ipl7) INT0IntHandler(void)
62 {
63         LATEbits.LATE0 = ~LATEbits.LATE0;               // LED toggeln
64         INTEnable(INT_INT0,0);                          // ext. Interrupt sperren
65         T1CONbits.ON = 1;                               // startet Timer1
66         return;
67 }
68 // Interrupt Service Routine für INT0
69 void __ISR(4, ipl6) Timer1IntHandler(void)
70 {
71         LATEbits.LATE1= ~LATEbits.LATE1;                // Breakpoint-Ersatz zum debuggen ;-)
72         T1CONbits.ON = 0;                               // Timer1 anhalten
73         INTClearFlag(INT_T1);                           // Timer1-Interrupt-Flag zurücksetzen
74         INTClearFlag(INT_INT0);                         // Interrupt-Flag zurücksetzen
75         INTEnable(INT_INT0,1);                          // ext. Interrupts wieder erlauben
76         TMR1 = 0x00;                                    // Startwert für den nächsten Durchlauf wieder laden
77         return;
78 }

Kleiner Tipp noch wenn das Programm nicht auf Anhieb läuft und man keinen In-System-Debugger hat. Man kann anstatt Breakpoints zu setzen Portpins an bestimmten Programmstellen auf High oder Low setzen. Mittels LEDs lässt sich dann das Vorankommen des Programms beobachten.

AD-Wandler

Als nächstes verwenden wir den Analog-Digital-Wandler (ADC). Unser PIC32 enthält einen 10Bit-Wandler mit einer maximalen Abtastrate von 1Msps (Megasample per second), der mit Sukzessiver Approximation arbeitet. Um verschiedene Spannungen an den ADC-Eingang anlegen zu können, um damit unser Program zu testen, brauchen wir noch ein Potentiometer, das wir zwischen Masse und +3,3V schalten.

Im ersten Beispiel verzichten wir wieder auf die Verwendung von fertigen Funktionen und nehmen alle Einstellungen selbst "von Hand" vor. Es soll eine Spannung in einen digitalen Wert mit einer Auflösung von 3Bit gewandelt werden und das Ergebnis als Balkenanzeige auf den LEDs an Port E ausgeben werden. Die notwendigen Informationen finden sich im ADC-Datenblatt "Section 17. 10-bit Analog-to-Digital Converter (ADC)" von Microchip.

 1 #include <p32xxxx.h>                // hier wird die µC spezifische Header-Datei eingebunden
 2 #include <plib.h>                   // include peripheral library functions
 3 //Konfigurationseinstellungen
 4 #pragma config FSOSCEN = OFF            // Disable secondary oscillator
 5 #pragma config POSCMOD = HS             // High speed crystal mode
 6 #pragma config FNOSC = PRIPLL           // Use Primary Oscillator with PLL (XT, HS, or EC) 
 7 #pragma config FPLLIDIV = DIV_2         // Divide 8MHz to between 4-5MHz before PLL (now 4MHz)
 8 #pragma config FPLLMUL=MUL_20           // Multiply with PLL (now 80MHz)
 9 #pragma config FPLLODIV = DIV_1         // Sysclk = 80MHz
10 #pragma config FWDTEN = OFF             // Disable watchdog timer
11 #pragma config FPBDIV = DIV_2           // stellt den Peripheral-Clock-Divisor auf 1:2 ein      
12 
13 
14 #define SYSCLK 80000000L
15 
16 const unsigned char bargraph[] = {0, 1, 3, 7, 15, 31, 63, 127}; // Array zum Erzeugen der Balkenanzeige
17 
18 int main(void)
19 {
20         SYSTEMConfigPerformance(SYSCLK);        // für maximale Performence
21 
22         TRISE = 0x00;                                   // Port-E -> Ausgang
23         TRISB = 0x08;                                   // Port-B -> Eingang
24         LATE = 0x01;                                    // und mit 1 initialisieren
25 
26         //AD-Wandler konfigurieren
27         AD1CON1bits.FORM = 0b000;                       // Darstellung als 16Bit unsigned integer
28         AD1CON1bits.SSRC = 0b111;                       // Wandlung automatisch gestartet, wenn Sampling beendet ist
29         AD1CON1bits.ASAM = 0;                           // Sampling startet, wenn SAMP-Bit gesetzt wurde (AD1CON1bits.SAMP)
30 
31         AD1CON2bits.VCFG = 0b000;                       // Spannungsreferenz ist AVDD und AVSS
32         AD1CON2bits.OFFCAL = 0;                         // Offset-Kalibration ausschalten
33         AD1CON2bits.CSCNA = 0;                          // Eingänge nicht scannen
34         AD1CON2bits.BUFM = 0;
35         AD1CON2bits.ALTS = 0;                           // immer MUX A nutzen
36         AD1CON2bits.SMPI = 0b0000;                      // Ergebnis wird immer in ADC1BUF0 geschrieben
37         
38         AD1CON3bits.ADRC = 0;                           // PB-Clock wird als Taktquelle verwendet
39         AD1CON3bits.SAMC = 0b1000;                      // 8*T_AD
40         AD1CON3bits.ADCS = 0;                           // T_AD = 2* T_PB -> ergibt 1000ksps
41 
42         AD1PCFG = 0x00;                                 // alle Pins von PortB sind analog
43         
44         AD1CHSbits.CH0NB = 0;                           // negativer Wandlereingang auf VR-
45         AD1CHSbits.CH0SB = 0b0000;                      // positiver Wandlereingang auf AN0 == RB0
46         // Kanal A wird im Beispiel nicht verwendet und deshalb auch nicht konfigueriert
47 
48         AD1CON1bits.ON = 1;                             // ADC einschalten
49 
50         unsigned char x = 0;
51         
52         
53         while(1)                                                
54         {
55                 AD1CON1bits.SAMP=1;                             // Wandlung starten     
56                 while(AD1CON1bits.DONE!=1);                     // wartet auf das Ende der Wandlung
57                 x = (unsigned char)(ADC1BUF0 >> 7);             // Auflösung auf 3Bit beschränken -> 2^3=8 Zustände
58                 // LEDs als Bargraphanzeige verwenden
59                 LATE = bargraph[x];
60         }
61         return 1;
62 }

SPI

Viele ICs, die den Funktionsumfang von µC erweitern, verwenden zur Kommunikation SPI. Beispiele hier für sind externe Speicherbausteine (z.B. SRAMs und EEPROMs) oder AD- bzw. DA-Wandler-ICs. Die PIC32-Familie kann bei der Kommunikation sowohl die Rolle des Masters, als auch die des Slaves einnehmen. In den folgenden Beispielen, will ich zwei mögliche Master-Betriebsarten des SPI-Moduls zeigen. Die notwendigen Informationen finden sich wieder im entsprechenden Datenblatt: "Section 23. Serial Peripheral Interface (SPI)”

Das erste Beispiel ist ein einfaches "Hello World!"-Programm. Es sendet im Mastermode einen String über den Datenausgang des SPI-Moduls.

 1 #include <p32xxxx.h>                // hier wird die µC spezifische Header-Datei eingebunden
 2 #include <plib.h>                   // include peripheral library functions
 3 //Konfigurationseinstellungen
 4 #pragma config FSOSCEN = OFF            // Disable secondary oscillator
 5 #pragma config POSCMOD = HS             // High speed crystal mode
 6 #pragma config FNOSC = PRIPLL           // Use Primary Oscillator with PLL (XT, HS, or EC) 
 7 #pragma config FPLLIDIV = DIV_2         // Divide 8MHz to between 4-5MHz before PLL (now 4MHz)
 8 #pragma config FPLLMUL=MUL_20           // Multiply with PLL (now 80MHz)
 9 #pragma config FPLLODIV = DIV_1         // Sysclk = 80MHz
10 #pragma config FWDTEN = OFF             // Disable watchdog timer
11 #pragma config FPBDIV = DIV_1           // stellt den Peripheral-Clock-Divisor auf 1:1 ein      
12 
13 
14 #define SYSCLK 80000000L
15 
16 void config_timer(void)
17 {
18         T1CON = 0x00;                           // hält Timer1 an und setzt setzt alle Einstellungen zurück -> PBCLK als Taktquelle
19         T1CONbits.TCKPS = 0b11;                 // Timer1 Vorteiler auf 1:256 eingestellt
20         TMR1 = 0x00;                            // Timer1-Register auf Startwert setzen -> Überlauf nach ca. 200ms
21         PR1 = 0xFFFF;                           // Period-Register laden
22         return;
23 }
24 
25 void wait(void)
26 {
27         T1CONbits.ON = 1;                       // startet Timer1
28         while(IFS0bits.T1IF!=1);                // pollen des Timer-Interruptflags -> zeigt an wenn Timer1 überläuft
29         T1CONbits.ON = 0;                       // nach Überlauf -> Timer1 anhalten...
30         IFS0bits.T1IF = 0;                      // ...und Interruptflag zurück setzen
31         TMR1 = 0x00;                            // Startwert für den nächsten Durchlauf wieder laden
32         return;
33 }
34 
35 int main(void)
36 {
37         SYSTEMConfigPerformance(SYSCLK);        // für maximale Performence
38         TRISDbits.TRISD0 = 1;                   // RD0 -> Eingang
39 
40         SPI3CON = 0x34;                         // Betriebsmodus konfigurieren
41         SPI3BRG = 63;                           // SPI-Clock-Frequenz
42         SPI3CONbits.ON = 1;                     // SPI3 einschalten
43         unsigned char data[] = {"Hello World!"};
44         int i = 0;
45 
46         while(1)                                
47         {
48             while(PORTDbits.RD0==0);                        // warten auf Tastendruck
49             for(i=0;data[i]!='\0';i++)                      // String über SPI3 senden
50             {
51                 SPI3BUF = data[i];                          // String Zeichen für Zeichen in den Buffer schreiben
52                 while(SPI3STATbits.SPIBUSY);                // warten bis SPI bereit für nächste Übertragung ist
53             }
54             SPI3BUF = ' ';
55             wait();                                         // Taste entprellen
56         }
57         return 1;
58 }

Das Ergebnis mit einem Logikanalyser betrachtet sieht so aus:

Auch das zweite Beispiel sendet im Mastermode einen String "Hello World!" über den Datenausgang des SPI-Moduls. Diesmal ist allerdings zusätzlich der Frame-Pulse aktiviert. In diesem Modus werden auf einem Ausgangs-Pin synchron zur Übertragung kurze High-Impulse ausgegeben. Deren Abstand und Länge kann unterschiedlich konfiguriert werden. Auch Low-Impulse sind einstellbar. Über diese Frame-Impulse lassen sich ICs mit einem Latch-Pin ansteuern (z.B. Schieberegister, DAC, etc.). Dafür wurde nur der Konfigurationswerte im SPI3CON-Register geändert.

 1 /* 
 2  * File:   main.c
 3  * Author: Sven
 4  *
 5  * Created on 12. Februar 2013, 18:04
 6  */
 7 
 8 #include <p32xxxx.h>                    // include chip specific header file
 9 #include <plib.h>                       // include peripheral library functions
10 //Konfigurationseinstellungen
11 #pragma config FSOSCEN = OFF            // Disable secondary oscillator
12 #pragma config POSCMOD = HS             // High speed crystal mode
13 #pragma config FNOSC = PRIPLL           // Use Primary Oscillator with PLL (XT, HS, or EC)
14 #pragma config FPLLIDIV = DIV_2         // Divide 8MHz to between 4-5MHz before PLL (now 4MHz)
15 #pragma config FPLLMUL=MUL_20           // Multiply with PLL (now 80MHz)
16 #pragma config FPLLODIV = DIV_1         // Sysclk = 80MHz
17 #pragma config FWDTEN = OFF             // Disable watchdog timer
18 #pragma config FPBDIV = DIV_1           // stellt den Peripheral-Clock-Divisor auf 1:8 ein
19 
20 #define SYSCLK 80000000L
21 
22 void config_timer(void)
23 {
24         T1CON = 0x00;                           // hält Timer1 an und setzt setzt alle Einstellungen zurück -> PBCLK als Taktquelle
25         T1CONbits.TCKPS = 0b11;                 // Timer1 Vorteiler auf 1:256 eingestellt
26         TMR1 = 0x00;                            // Timer1-Register auf Startwert setzen -> Überlauf nach ca. 200ms
27         PR1 = 0xFFFF;                           // Period-Register laden
28         return;
29 }
30 
31 void wait(void)
32 {
33         T1CONbits.ON = 1;                       // startet Timer1
34         while(IFS0bits.T1IF!=1);                // pollen des Timer-Interruptflags -> zeigt an wenn Timer1 überläuft
35         T1CONbits.ON = 0;                       // nach Überlauf -> Timer1 anhalten...
36         IFS0bits.T1IF = 0;                      // ...und Interruptflag zurück setzen
37         TMR1 = 0x00;                            // Startwert für den nächsten Durchlauf wieder laden
38         return;
39 }
40 
41 int main(void)
42 {
43         SYSTEMConfigPerformance(SYSCLK);                // für maximale Performence
44         TRISDbits.TRISD0 = 1;                           // RD0 -> Eingang
45 
46         SPI3CON = 0x58000034;                           // Master-Mode mit Frame-Pulse
47         SPI3BRG = 63;                                   // SPI-Clock-Frequenz
48         SPI3CONbits.ON = 1;
49         unsigned char data[] = {"Hello World!"};
50         int i = 0;
51 
52         while(1)                                
53         {
54             i=0;
55             while(PORTDbits.RD0==0);                        // warten auf Tastendruck
56             for(i=0;data[i]!='\0';i++)                      // String über SPI3 senden
57             {
58                 SPI3BUF = data[i];                          // in den Buffer schreiben
59                 while(SPI3STATbits.SPIBUSY);                // warten bis SPI bereit für nächste Übertragung ist
60             }
61             SPI3BUF = ' ';
62             wait();                                         // Taste entprellen
63         }
64         return 1;
65 }

Hier wieder das Ergebnis mit einem Logikanalyser betrachtet:

I2C

Eine weitere gängige Schnittstelle für die Kommunikation von µC mit anderen Bausteinen ist der I2C-Bus. Als Beispiel für die Verwendung des I2C-Busses soll im folgenden Programm ein 4x20-LC-Display angesteuert werden. Solche Displays findet man bei diversen Online-Shops im Programm. Meist sind es gewöhnliche LCDs mit Parallelschnittstelle, die mittels eines µCs oder eines Port-Expanders I2C-tauglich gemacht wurden. Das im Beispiel verwendete Display besitzt auf der Rückseite eine kleine Adapterplatine. Auf dieser sitzt ein Port-Expander vom Typ PCF8574, dessen Adresse fest auf 0x27 eingestellt ist. Da der Port-Expander nur über acht I/O-Pins verfügt, muss das Display im 4-Bit-Modus angesteuert werden (vgl. www.sprut.de). Das Display wird mit +5V betrieben. Dies ist auch der High-Pegel, der an den SCL- und SDA-Leitungen des Port-Expanders anliegt. Dies stellt für unseren µC allerdings kein Problem dar, da die verwendeten Pins (SCL4 und SDA4) 5V-tolerant sind. Welche Pins +5V vertragen und welche nur +3,3V, lässt sich im Datenblatt nachlesen. Auch lässt sich das Display, obwohl +5V angegeben werden, problemlos mit +3,3V versorgen.

Pinbelegung zwischen dem LCD und dem Port-Expander:

PCF8574I/O 0I/O 1I/O 2I/O 3I/O 4I/O 5I/O 6I/O 7
LCDRSRWENBacklightD4D5D6D7

Die maximale Taktfrequenz, mit der der Port-Expander Daten über den I2C-Bus empfangen kann, beträgt 100kHz. Im ersten Programm wird testweise "Hello World!" auf dem Display ausgegeben.

...Code...

Als nächstes sollen einige Funktionen geschrieben werden, um die Handhabung des Displays innerhalb eines Programms zu erleichtern. Analog zu den Konsolenausgaben in C, soll es eine Funktion void printLCD() für Strings und eine Funktion void putcLCD(char) für einzelne Zeichen geben. Außerdem noch eine Funktion void init_LCD(void), um das Display zu initialisieren. Um die Programmteile zum Ansprechen des LCDs in zukünftigen Programmen besser wiederverwenden zukönnen, sollen zwei Header-Dateien erstellt werden. Eine delay.h mit Funktionen für Warteschleifen und eine LCD.h zum ansprechen von LCDs.

...Code...

PWM

...Code...
Back