In diesem Artikel werden jede Menge Programmbeispiele vorgestellt. Die Beispiele beziehen sich, sofern nicht anders erwähnt, auf die Hardware des StartPIC18-Entwicklungsboards. Die Programmbeispiele dienen als Anwendung der erlernten Theorie aus dem PIC18-Tutorial und dem PIC-C-Tutorial. Die Beispiele sind natürlich nicht auf die Hardware des StartPIC18 beschränkt. Sie sind zu nahezu jedem PIC-Controller kompatibel, sofern der Controller das jeweilig notwendige Interface/Modul o.ä. unterstützt.

Hier noch ein Hinweis für all diejenigen unter Euch, die mit StartPIC18-Entwicklungsboard arbeiten. Bei jedem Programmbeispiel in diesem Artikel findet Ihr einen der beiden folgenden Hinweise zur Stromversorgung der Schaltung.

 Stromversorgung durch das PICKit3 möglich

Dieser Hinweis sagt Dir, dass Du dein StartPIC18-Entwicklungsboard bei diesem Programm (ausschließlich) mit dem PICKit3 mit Strom versorgen kannst. Es spricht jedoch auch nichts dagegen einfach immer die Stromversorgung über den USB-Anschluss des Boards zu realisieren. Wenn das StartPIC18 ausschließlich über das PICKit3 versorgt werden soll, dann muss dies in den Projekt-Einstellungen auch entsprechend eingestellt werden (Haken, siehe Abbildung setzen und Spannung auf 5V einstellen).

 Stromversorgung durch das PICKit3 nicht möglich

Dieser Hinweis sagt Dir, dass Du dein StartPIC18-Entwicklungsboard bei diesem Programm nicht mit dem PICKit3 mit Strom versorgen kannst. Es muss auf jeden Fall per USB versorgt werden. In diesem Fall solltest Du die Versorgung des PICKit3 ausschalten (Haken, siehe Abbildung entfernen).

Bevor wir nun mit dem Programmieren loslegen, sollten wir uns noch kurz ansehen, mit welcher Software wir Programme für das Board bzw. für den PIC schreiben können. Ich empfehle euch hierzu die Entwicklungsumgebung MPLABX direkt von Microchip. In meinem Artikel findet ihr alle nötigen Informationen von der Installation bis hin zum Erstellen eines Projektes und der weiteren Bedienung der IDE.

Einfache Programme

Hello World

 Stromversorgung durch das PICKit3 möglich

In der Welt der Programmierung ist üblich mit dem sogenannten “Hello World” Programm zu beginnen. Bei der konventionellen PC-Programmierung wird hier schlicht und einfach der Text “Hello World” auf der Konsole des Betriebssystems ausgegeben. Bei der Mikrocontroller-Programmierung könnte man nun meinen diesen Text also z.B. auf einem LC-Display darzustellen. Nein, das wären etwa 10-20 Schritte auf einmal. Das “Hello World” der Mikrocontroller-Programmierung ist das Blinken einer LED.

Was wir in dieser Übung lernen:

  • Setzen/Schalten von Ausgängen inkl. der Konfiguration von IOs
  • Anwenden einfacher Warteschleifen (XC8-Bibliothek)

An dieser Stelle noch einmal die Erinnerung: Die Programmbeispiele in diesem Artikel beziehen sich auf die Hardware des  StartPIC18. Schauen wir uns das “Hello World” Programm, also denn Quellcode einmal an:

Dieses Programmbeispiel im GitHub-Repository betrachten.

#include <xc.h>
#include "main.h"

#pragma config FOSC = INTIO67  // Internal oscillator block
#pragma config PWRTEN = ON     // Power up timer enabled
#pragma config BOREN = OFF     // Brown-out Reset disabled
#pragma config WDTEN = OFF     // Watch dog timer is always disabled
#pragma config MCLRE = EXTMCLR // MCLR enabled
#pragma config LVP = OFF       // Single-Supply ICSP disabled

/*********************************************************************
* Diverse Einstellungen zum PIC (IO, Takt, ...)
*/

void initPIC(void)
{
   TRISA = 0x00;
   TRISB = 0x01;  // RB0:Input (PB)
   TRISC = 0x20;  // RC6:Input (AN16)

   ANSELA = 0x00; // All digital
   ANSELB = 0x00; // ...
   ANSELC = 0x20; // AN16 on

   OSCCON2bits.MFIOSEL = 0; // 111: 16 MHz
   OSCCONbits.IRCF2 = 1;    // 110: 8 MHz
   OSCCONbits.IRCF1 = 0;    // 101: 4 MHz
   OSCCONbits.IRCF0 = 0;    // 100: 2 MHz <-- aktiv
}

/*********************************************************************
* Main Routine
*/

void main (void)
{
   initPIC();

   /*Shut OFF the LCD-Backlight*/
   LATCbits.LC2 = 1;

   /*Endlosschleife*/
   while(1)
   {
      LED1 = 0;        // turn LED on
      __delay_ms(250); // delay 250 ms
      __delay_ms(250); // ...
      LED1 = 1;        // turn LED off
      __delay_ms(250); // ...
   }
}

Beachte: In diesem und in weiteren Programmbeispielen wurde der Ausgang RC2 auf High (=5V) geschaltet. Durch diese Maßnahme wird auf dem StartPIC18-Board die Beleuchtung des LC-Displays ausgeschaltet und das PICKit3 reicht zur Stromversorgung aus.

Zu Beginn eines Programms wird stets die Main-Funktion aufgerufen. Bevor wir jedoch mit dem PIC-Controller arbeiten können, müssen wir einige Konfigurationen vornehmen, diese beginnen immer mit einem Raute-Zeichen ‘#’ gefolgt vom Kürzel “pragma”.

Wie man einen IO-Pin eines PIC zu einem Ein- oder Ausgang umschaltet erfährst Du im Abschnitt Eingänge und Ausgänge des PIC18-Tutorials. Wichtig ist auch, dass wir dem Compiler mitteilen, mit welche Taktfrequenz wir unseren Controller betreiben, damit wir entsprechende Compiler-Bibliotheken (z.B. einfache Warteschleifen) nutzen können. Diese Information übergeben wir dem Compiler mit Hilfe eines Makros, das wir in der Datei <main.h> ablegen (vollständige main.h):

#define _XTAL_FREQ 2000000 /*Clock frequency in Hz*/

So ist es uns möglich in einer Endlosschleife die LED in beliebigen Abfolgen Ein- und Auszuschalten – sie blinkt. An dieser Stelle direkt schon mal der Hinweis auf einen entscheidenden Nachteil solcher Warteschleifen: Der Controller tut während der Wartezeit nichts anderes außer warten. Logisch, dazu ist die Warteschleife ja auch gedacht. Wir lernen schon bald die überaus nützlichen Timer der PIC-Controller kennen. Diese ermöglichen es uns, dass der Controller während der Wartezeit weitere Aufgaben erledigen kann und die Wartezeit somit nicht zu verlorener Rechenzeit wird.

Ein- und Ausgänge (Tastenentprellung)

 Stromversorgung durch das PICKit3 möglich

In diesem Beispiel beschäftigen wir uns nun etwas genauer mit Ein- und Ausgängen eines PIC-Mikrocontroller und wie man diese eigentlich verwendet. Im ersten Programmbeispiel (Hello World) haben wir bereits die Ausgänge kennen gelernt. In diesem Beispiel wollen wir uns zusätzlich mit Eingängen des PIC-Controllers auseinandersetzen. Mit diesem Programm wollen wir, dass die LED1 des StartPIC18 nur dann leuchtet, wenn wir den Taster PB drücken. Im Anschluss erweitern wir das Programm, so dass wir den Status des LED1 mit dem PB umschalten. Also wenn die LED an ist und der PB gedrückt wird, dann wird die LED ausgeschaltet und andersrum.

Was wir in dieser Übung lernen:

  • Einlesen von Schaltern/Tastern
  • Entprellen von Schaltern/Tastern

Im Vergleich zum Quellcode des “Hello World” Programms verändern wir lediglich die Main-Funktion. Zusätzlich fügen wir folgende Zeile in die Datei <main.h> hinzu um den Taster PB als Makro zu definieren, was die Lesbarkeit des Codes fördert.

#define PB PORTBbits.RB0

Und hier die neue Main-Funktion (vollständiger Code im GitHub-Repository):

void main (void)
{
   initPIC();
 
   /*Shut OFF the LCD-Backlight*/
   LATCbits.LC2 = 1;
 
   /*Endlosschleife*/
   while(1)
   {
      if(!PB)
      {
         LED1 = 1;
      }
      else
      {
         LED1 = 0;
      }
   }
}

Es fällt auf, dass der Taster PB auf Null und nicht auf Eins abgefragt wird. Das liegt an der Hardware-Beschaltung auf dem StartPIC18, bei dem die Taster aktiv gegen GND schalten. Der Eingang wird ansonsten mit Hilfe eines Pull-up-Widerstands gegen Vcc gezogen und das Potential bei nicht gedrücktem Taster auf einem definierten Level zu halten.

Im nächsten Schritt möchten wir das Programm so anpassen, dass wir die LED mit dem Taster umschalten können. Das mag zunächst einfach erscheinen, doch wir werden bei diesem Vorhaben auf ein Problem stoßen. Der erste Ansatz könnte wie folgt aussehen:

/*Endlosschleife*/
while(1)
{
   if(!PB)
   {
      LED1 = ~LED1;
   }
}

Ihr werdet feststellen, dass diese Methode leider nur ausgesprochen schlecht bis gar nicht funktioniert. Das liegt an der Tatsache, dass mechanische Taster Regel prellen. Das bedeutet, dass der Taster nicht sauber von einem Potential auf das andere schaltet, sondern zwischen den beiden Potentialen bzw. den Schaltkontakten mehrere male hin und her prellt/springt.

Es ist also notwendig die Taste zu entprellen. Es wird zwischen Hard- und Software-Entprellung unterschieden. Wir wenden für unser Problem eine Softwarelösung an:

void main (void)
{
   uint8_t lastKeyDown = 0;
 
   initPIC();
 
   /*Shut OFF the LCD-Backlight*/
   LATCbits.LC2 = 1;
 
   /*Endlosschleife*/
   while(1)
   {
      __delay_ms(10);
 
      switch( keyPressed(&lastKeyDown) )
      {
         case 0:
            break;
         case 1:
            LED1 = ~LED1;
      }
   }
}

Innerhalb der Endlosschleife wird bei dieser Variante alle 10 ms die Funktion keyPressed aufgerufen. Diese Funktion führt das Entprellen der Taste durch. Schauen wir uns also den Code dieser Funktion an:

uint8_t keyPressed (uint8_t *plastKeyDown)
{
   uint8_t actKeyDown;
 
   if(PB)
   {
      actKeyDown = 1;
   }
   else
   {
      actKeyDown = 0;
   }
 
   if(actKeyDown != *plastKeyDown)
   {
      *plastKeyDown = actKeyDown;
      return actKeyDown;
   }
 
   return 0;
}

Die Funktion merkt sich jeweils die zuletzt gedrückte Taste (auch “keine Taste” wird als Taste betrachtet) und vergleicht diesen Wert mit der aktuell gedrückten Taste. Wenn sich die Werte unterscheiden wurde eine Taste gedrückt.

Für dieses Programm wurde die <main.h> Datei erneut um ein Makro erweitert:

#define PB PORTBbits.RB0

Hier wurde der Eingang bzw. der IO-Pin, der mit dem Taster “PB” verbunden ist als Makro definiert. Es fällt auf, dass hier nun das Stichwort PORT und nicht mehr wie zuvor LAT verwendet wird. Das hat den Grund, dass für Eingänge immer PORT und für Ausgänge immer LAT verwendet wird, siehe auch PIC-C-Tutorial.

Übrigens: Auch hier ist es wieder nicht optimal, dass eine 10 ms Warteschleife verwendet wird. Wesentlich besser wäre es, wenn ein Timer alle 10 ms (Zeit variabel) einen Interrupt auslöst, der wiederum den Aufruf der Funktion zum Entprellen der Taste einleitet.

Timer (Interrupt)

 Stromversorgung durch das PICKit3 möglich

In diesem Programmbeispiel möchten wir einen Timer nutzen um Wartezeiten zu generieren. Wir betrachten in diesem Beispiel den Timer0. Das Programmbeispiel ist jedoch für nahezu jeden PIC18 gültig, da der Timer0 immer vorhanden ist. Wir möchten den Timer so konfigurieren, dass er beim Überlaufen einen Interrupt auslöst. Wenn dieser Interrupt ausgelöst wird, soll die LED1 des SP18 umgeschaltet werden. Im Datenblatt des PIC18F25K22 finden wir das Kapitel zum Timer0 ab Seite 159. Wichtig für uns ist zunächst das T0CON-Register. In diesem Register wird der Timer0 entsprechend nach unseren Wünschen konfiguriert.

Dieses Programmbeispiel im GitHub-Repository betrachten.

/* Timer0 off, internal CLK, Teiler 4 */
T0CON = 0b00000001;

Mit weiteren Einstellungen konfigurieren wir den Interrupt des Timer0 und aktivieren den Timer0.

void initTMR0(void)
{
   /* Timer0 off, internal CLK, Teiler 4 */
   T0CON = 0b00000001;
 
   /* enable TMR0 Interrupt */
   INTCONbits.TMR0IE = 1;
 
   /* high priority for TMR0 Interrupt */
   INTCON2bits.TMR0IP = 1;
 
   /* turn on TMR0 */
   T0CONbits.TMR0ON = 1;
 
   /* enable priority for Interrupts */
   RCONbits.IPEN = 1;
 
   /* enable global Interrupts */
   INTCONbits.GIEH = 1;
}

Sobald der Timer überlauft, also wenn er bei 0xFFFF angekommen ist und ein weiterer Takt zum Zählen gekommen ist, setzt der Timer sein Interrupt-Flag, TMR0IF und löst somit einen Interrupt aus. Der PIC führt noch seinen aktuellen Befehl aus und springt im Anschluss in die Interrupt-Service-Routine. Dabei handelt es sich um eine Art Funktion, die jedoch nicht vom Programmierer sondern nur vom PIC, durch auftreten eines Interrupts, aufgerufen wird.

void interrupt isr_HIGH_Prio(void)
{
   if( INTCONbits.TMR0IF == 1 )
   {
      /* clear the Interrupt flag */
      INTCONbits.TMR0IF = 0;
 
      /* toggle the LED */
      LED1 = ~LED1;
   }
}

Innerhalb der ISR prüfen wir welches Ereignis den Interrupt ausgelöst hat und verfahren entsprechend. Da es in unserem Fall nur einen möglichen Verursacher gibt hätte man sich die Abfrage auch sparen können. Jedoch ist dies das gängige Schema, wenn mehr als eine Quelle in Frage kommt.

Piezosummer

 Stromversorgung durch das PICKit3 möglich

Wir wollen nun den Piezosummer der sich auf dem SP18 befindet verwenden um akustische Signale auszugeben. Dabei werden wir uns zunächst auf einfaches Ein- und Ausschalten beschränken. Der Piezosummer wird über einen Transistor geschaltet. Die Basis des Transistors befindet sich am IO-Pin RB3. Dieser IO-Pin ist im Stande ein PWM-Signal auszugeben. Somit kann die gefühlte Lautstärke des Summers beeinflusst werden. Dazu jedoch mehr im Programmbeispiel Pulsweitenmodulation. Wir legen zunächst ein neues Makro in der Datei <main.h> an um den Summer einfach ansprechen zu können.

#define SUMMER LATBbits.LB3

Unser erster Ansatz gleicht dem Schalten der LED. Wir möchten durch Drücken des Tasters PB den Summer ein- und ausschalten können. Da sich der Programmcode nun kaum von dem Beispiel der geschalteten LED samt Entprellung unterscheidet, verzichte ich an dieser Stelle auf eine weitere Analyse. Der vollständige Programmcode ist wie immer im GitHub-Repository zu finden.

Fortgeschrittene Programme

LC-Display

 Stromversorgung durch das PICKit3 nicht möglich

In diesem Beispiel wollen wir das LC-Display auf dem StartPIC18 in Betrieb nehmen. Bei dem Display handelt es sich um ein zweizeiliges LCD mit je 8 Zeichen. Die Adressen sind 0..7 in der ersten und 40…47 in der zweiten Zeile. Das Display ist mit einem HD44780 kompatiblem Kontroller ausgestattet. In meinem Artikel zu diesem Display sind die Routinen gesondert zu finden.

Dieses Programmbeispiel im GitHub-Repository betrachten.

void main (void)
{ 
   initPIC();
   initLCD();
 
   /*Shut ON the LCD-Backlight*/
   LATCbits.LC2 = 0;
 
   charChainLCD(0, "Hallo");
   charChainLCD(40, "Welt");
 
   /*Endlosschleife*/
   while(1)
   {
   }
}

Nach dem Starten des Programms wird auf dem Display der Text “Hallo Welt” angezeigt. Falls Ihr keinen Text auf dem Display sehen könnt, dann ist es wahrscheinlich notwendig den Kontrast des einzustellen. Hierfür dient der Trimmer R4. Dreht diesen solange, bis das Display gut ablesbar ist.

DS1820 Temperatursensor

 Stromversorgung durch das PICKit3 möglich

Mit dem DS18S20 der sich auf dem StartPIC18-Board befindet möchten wir die Umgebungstemperatur messen. Dieses Programmbeispiel zeigt lediglich den Einstieg in die Ansprache und das Auslesen des Sensor. Das Programm ist lediglich in der Lage positive Temperaturen ohne Nachkommastelle zu messen. Das Programm kann vom Anwender auf Nachkommastellen, sowie negative Temperaturen erweitert werden.

Dieses Programmbeispiel im GitHub-Repository betrachten.

void main (void)
{ 
   uint8_t k = 0, tValue[9];
   char buf[9];
 
   initPIC();
   initLCD();
 
   /*Shut OFF the LCD-Backlight*/
   LATCbits.LC2 = 1;
 
   charChainLCD(0, "Temp [C]");
 
 
   /*Endlosschleife*/
   while(1)
   {
      OW_MRI();
      OW_S_Rom();
      OW_W_Byte(0x44);
      __delay_ms(200);
      __delay_ms(200);
      __delay_ms(200);
      __delay_ms(200);
      OW_MRI();
      OW_S_Rom();
      OW_W_Byte(0xBE); 
 
      for(k=0; k<9; k++)
      {
         tValue[k] = OW_R_Byte();
      }
 
      tValue[0] = tValue[0] >> 1;
 
      itoa(buf,tValue[0],10);
      charChainLCD(0x40," ");
      charChainLCD(0x40, buf);
   }
}

Die genauen Hintergrundinformationen zur Ansteuerung des Sensors findest Du in meinem eigenen Artikel zum DS18S20-Temperatursensor. Ich möchte an dieser Stelle noch einmal erwähnen, dass dieses Programm nur in der Lage ist positive Temperaturen ohne Nachkommastelle zu messen/anzuzeigen. Das Programm kann erweitert und in Funktionen aufgegliedert werden. Es macht ja auch keinen Spaß mehr, wenn bereits alles fix und fertig ist 🙂

Analogmessung

 Stromversorgung durch das PICKit3 möglich

Wir möchten nun den Analog-Digital-Umsetzer des PIC-Mikrocontroller beschäftigen. Das StartPIC18 verfügt neben einem Potentiometer auch über einen photosensitven Widerstand, einen sogenannten LDR. Ein LDR ändert seinen Widerstandswert je nach Intensität des einfallenden Lichtes. Die Oberfläche eines solchen LDR sieht wie folgt aus:

Ihr findet diesen unter dem LC-Display und rechts neben dem Temperatursensor. Der LDR hat die Bezeichnung PH1. In diesem Programmbeispiel wird der analoge Wert des LDR und der analoge Wert des Potentiometers digitalisiert und auf dem LC-Display angezeigt. Dieses Programmbeispiel kann ausnahmsweise mit dem PICKit3 versorgt werden, da die Hintergrundbeleuchtung in diesem Beispiel deaktiviert ist.

Dieses Programmbeispiel im GitHub-Repository betrachten.

uint16_t readADC(uint8_t channel)
{
   uint16_t aValue = 0;

   ADCON0bits.ADON = 1;
   switch(channel)
   {
      case AN17:
         ADCON0bits.CHS0=1;
         ADCON0bits.CHS1=0;
         ADCON0bits.CHS2=0;
         ADCON0bits.CHS3=0;
         ADCON0bits.CHS4=1;
         break;
 
      case LDR:
         ADCON0bits.CHS0=1;
         ADCON0bits.CHS1=1;
         ADCON0bits.CHS2=0;
         ADCON0bits.CHS3=1;
         ADCON0bits.CHS4=0;
         break;

      default:
         break;
 }

   ADCON0bits.GO = 1;
   while(ADCON0bits.NOT_DONE);
   aValue=ADRESH;
   aValue=aValue<<8;
   aValue=aValue+ADRESL;
   ADCON0bits.ADON = 0;

   return aValue;
}

Der oben gezeigte Ausschnitt des Programmcodes zeigt die Ansprache der ANx Kanäle und die Umsetzung des Analogwertes zu einem 10 Bit Digitalwert. Je nachdem welcher Übergabewert der Funktion übergeben wird, wird ein ANx Kanal ausgewählt und die Umsetzung gestartet. Im GitHub-Repository ist der gesamte Code verfügbar. Die genaue Arbeitsweise, sowie Informationen zur Konfigurieren des ADC findet Ihr im PIC18-Tutorial.

Serielle Schnittstelle

Bis ich dieses Programmbeispiel fertig habe, kannst Du ja schon mal das passende Kapitel im PIC18-Tutorial lesen.

OLED-Display (l2C-Erweiterung)

 Stromversorgung durch das PICKit3 möglich

In diesem Programmbeispiel wollen wir ein OLED-Display über die I2C-Erweiterung des StartPIC18 in Betrieb nehmen. Ich verwende hierzu ein knapp 1 Zoll kleines OLED-Display mit weißer Darstellung. In meinem separaten Artikel zu diesem Display findet Ihr weitere Informationen zu diesem Display. Dieses Programmbeispiel zeigt Euch, wie Ihr den I2C-Bus konfigurieren müsst um ihn entsprechend zu verwenden. Beachtet, dass die notwendigen Pull-up-Widerstände für die Signale SDL und SCL nicht auf dem Board vorhanden sind. Ihr müsst diese extern beschalten. Das hat den einfachen Grund, dass die beiden IOs nicht nur für den I2C-Bus dienen und somit “nackt” am Board zur Verfügung stehen um keine Einschränkungen in Kauf zu nehmen.

In diesem Beispiel wird ein kurzer Text bestehend aus drei Zeilen auf dem Display ausgegeben. Somit seid Ihr gewappnet um weitere Aufgaben mit diesem wunderbaren Display zu bewältigen. Hier eine kurze Kostprobe des Programms:

Dieses Programmbeispiel im GitHub-Repository betrachten.

void main (void)
{ 
   initPIC();
   initI2C();
   lcd_init();

   /*Shut OFF the LCD-Backlight*/
   LATCbits.LC2 = 1;

   /*Etwas Text in den Framebuffer schreiben*/
   fb_draw_string(0,0,"SSD1306");
   fb_draw_string(0,1,"pic-projekte.de");
   fb_draw_string(0,2,"StartPIC18");

   /*Den Framebuffer zur Anzeige bringen*/
   fb_show();
 
   /*Endlosschleife*/
   while(1)
   {
   }
}

2 Responses

Leave a Comment