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.
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.
Wir schreiben uns jetzt also eine Funktion void wait(void). Doch zuerst muss der Timer konfiguriert werden, weshalb wir auch noch eine Funktion
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.
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.
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 } |
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:
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:
PCF8574 | I/O 0 | I/O 1 | I/O 2 | I/O 3 | I/O 4 | I/O 5 | I/O 6 | I/O 7 |
LCD | RS | RW | EN | Backlight | D4 | D5 | D6 | D7 |
...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... |
...Code... |