In diesem Artikel möchte ich nach und nach Programmbeispiele vorstellen, die Problemstellungen verschiedenster Art betrachten. 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, jedoch direkt auf ihr anwendbar 🙂 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 setzen – siehe Markierung in der 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 (integrierte Entwicklungsumgebung / integrated development environment).

Einfache Programme

Wir wollen zunächst damit beginnen einige einfache Programme zu schreiben um zum einen die neue Hardware etwas besser kennen zu lernen und zum Anderen um uns mit den Ersten wichtigen Dingen im PIC-Mikrocontroller auseinanderzusetzen. Es ist ganz besonders wichtig gerade die Basics sicher zu beherrschen, bevor man sich an schwierigere Aufgaben wagt.

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 womit das PICKit3 zur Stromversorgung ausreicht 😉

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*/

Der Compiler weiß somit genau mit welcher Frequenz wir unseren Mikrocontroller betreiben werden. Diese Information ist zwingend erforderlich, damit (wie schon erwähnt) Warteschleifen mit der richtigen Zeit realisiert werden: Einfacher ausgedrückt: Zum Ausführen eines Befehls braucht der Controller eine gewisse Anzahl an Takten (wenn du mehr dazu wissen möchtest, dann lies hier mal weiter). Wenn wir eine ganz einfache Warteschleife einsetzen möchten, dann wird der Controller in dieser Zeit die sogenannte Nop-Anweisung ausführen. Nop steht hierbei für “no operation” also “nichts”. Also, um einmal “nichts” zu machen (einmal Nop ausführen) benötigt der Controller ebenfalls einen Befehlstakt. So und wenn du jetzt eine Warteschleife von z.B. 1 Sekunde ausführen willst, muss der Compiler / Controller wissen wie lange ein Befehlstakt dauert, damit er wiederum weiß wie oft er Nop ausführen muss, damit 1 Sekunde lang nichts passiert 🙂

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

Anstatt des relativ unleserlichen Ausrucks “PORTBbits.RB0” können wir jetzt einfach “PB” schreiben, ohne dass sich die Funktion des Programmcodes verändert. Der Sinn solcher Defines/Makros ist es Programmcode unter anderem leserlicher zu gestalten. Wenn das Programm dann irgendwann einmal kompiliert wird, werden alle “PB” durch den Ausdruck im Define ausgetauscht, so dass wieder der korrekte Quelltext zustande kommt.

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 (im Ruhezustand) mit Hilfe eines Pull-up-Widerstands gegen Vcc gezogen um das Potential bei nicht gedrücktem Taster auf einem definierten Level zu halten. Würde man den Pull-up Widerstand weglassen, so würde sich kein definierter Spannungspegel am Eingang einstellen, wenn der Taster nicht gedrückt ist. Wir hätten einen offenen Eingang (auch floating input genannt), der zu Fehlverhalten führen würde! Merke: Ein als Eingang konfigurierter I/O muss zu jeder Zeit auf einem definierten Pegel liegen (high / low).

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, siehe auch die nachfolgende Darstellung des Signalverlaufes, ausgehend von einem einfachten Taster:



Warum ist das ein Problem? Nun euer Mikrocontroller arbeitet in der Regel so schnell, dass er die kurzen Signalhübe zu Beginn (key pressed) und zum Ende (key released) eines Tastendrucks als mehrfaches Drücken der Taste interpretieren würde. Somit würde eine zum Taster hinterlegte Aktion auch entsprechend mehrfach ausgeführt werden und genau das möchten wir nicht. 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 (eine Entprellung wird selten eingesetzt):

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) )
      {
         // push button pressed
         case 1: LED1 = ~LED1;
         
         default: break;
      }
   }
}

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. Spielen wir es doch einmal durch:

Zeit [ms] |  Pegel (PB) |  (1) actKeyDown  | (2) *plastKeyDown  | (3) return |  Bemerkung
----------+-------------+------------------+--------------------+------------+------------------------
       0  |  low        |               0  |            0       |          0 | Taste nicht gedrückt
      10  |  low        |               0  |            0       |          0 | ..
      20  |  high       |          0 -> 1  |            0 -> 1  |          1 | Taste wurde gedrückt
      30  |  high       |               1  |            1       |          0 | Taste wird gehalten
      40  |  high       |               1  |            1       |          0 | ..
      50  |  low        |          1 -> 0  |            1 -> 0  |          0 | Taste wurde losgelassen

In dem gezeigten Ablaufbeispiel sieht man zu Beginn, dass die Taste nicht gedrückt wird und der logische Zustand des Eingangs ist somit low ist (Erinnerung: Low aktiver Taster). Die Arbeitsvariablen der Entprellfunktion sind beide “0”. Auch nach 10 ms ist nichts passiert, der Taster wurde nicht angerührt. Zum Zeitpunkt 20 ms liegt ein high Pegel am Eingang an, der Taster wurde nun gedrückt. Die Funktion erkennt dies und setzt die loakale Variable actKeyDown auf “1”. Hinweis: Die “1” bedeutet hier nicht etwa high, sondern steht als Code für den gedrückten Taster PB. An dieser Stelle kann die Funktion beliebig erweitert werden um weitere Taster zu entprellen, die dann zum Beispiel die Werte “2”, “4” .. bekommen würden, sofern sie gedrückt werden (Hinweis: Je Taster ein Bit verwenden). Aber zurück zum Beispiel.. Die Funktion hat den low Pegel über die if-Abfrage am Eingang erkannt und die lokale Variable actKeyDown mit dem Tastencode “1” belegt.

Die zweite if-Abfrage prüft nun ob sich die bei diesem Durchgang erkannte Taste von der zuletzt erkannten (mit Hilfe des Wertes auf den der Pointer plastKeyDown zeigt) unterscheidet. Und genau das ist nun der Fall: Zuletzt hat die Funktion keinen Tastendruck erkannt  und die Variable auf die der Pointer zeigt hat somit den Wert Tastencode “0”. Die aktuell erkannte Taste hat den Tastencode “1”, sprich die Werte sind unterschiedlich. Die if Anweisung wird also aktiv. Innerhalb der if Anweisung speichern wir nun die aktuell erkannte Taste (Tastencode “1”) als zuletzt erkannte Taste, damit wir diese bei späteren Funktionsaufrufen nicht wieder als neuen Tastendruck identifizieren und gibt zusätzlich den Tastencode der erkannten Taste zurück (siehe rote “1” im Beispiel oben).

Bei 30 ms wird die Funktion zum Tastenentprellen nun wieder aufgerufen. Die Taste wird nun in der aktiven Position gehalten, so dass der Pegel am Eingang immer noch low ist. Da sich die Funktion jedoch diese Taste bereits beim letzten Mal als zuletzt gedrückte Taste gemerkt hat (Pointer  plastKeyDown), wird diese aktive Taste nicht erneut als Event erkannt. Die Funktion gibt den Code “0” zurück (kein neues Tastenevent). Das selbe passiert beim Aufruf der Funktion bei 40 ms. Es gibt keine Änderung zum vorherigen Status und somit erneut kein neues Event als Rückgabewert.

Zum Zeitpunkt 50 ms gibt es nun noch einmal eine Änderung, denn der Anwender hat die Taste nun wieder losgelassen, so dass der Pegel am Eingang nun nicht mehr low (wie zuvor), sondern wieder high (Ruhezustand) ist. Beim Aufruf der Funktion bei 50 ms ist der zuletzt gemerkte Tastencode noch die “1” und wir detektieren nun keine aktive Taste mehr, so dass actKeyDown den Wert “0” bekommt. Somit unterscheiden sich diese beiden Werte was erneut die zweite if-Abfrage aktiviert. Diese nimmt nun die “0” als zuletzt detektierten Tastencode auf und gibt diesen auch zurück.

Aufgrund der zeitlichen Begrenzung des Prellens von Tastern (Zeit TB in der Darstellung oben), kann mit dieser Methode das Problem beseitigt werden und es können auf elegante Art und Weise beliebig viele (nahezu beliebig viele) Taster in einer Funktion verwaltet werden.

In dem Beispiel wurde der Eingang, 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. Die folgende Abbildung soll den Ablauf eines Interrupts etwas verdeutlichen:



Und hier seht ihr den Programmcode der Interrupt-Service-Routine (ISR):

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.

Wenn wir die Quelle des Interrupts bestimmt und als unseren Timer0 identifiziert haben und quittieren wir den Interrupt indem wir das auslösende Bit wieder löschen (andernfalls würde nach Verlassen der ISR sofort wieder ein Interrupt ausgelöst werden). Danach führen wir das simple Umschalten eines Ausgangs durch. In diesem Fall ist die LED1 an diesem Ausgang (StartPIC18) angeschlossen, welches wir zuvor mit Hilfe eines Makros definiert haben. Hinweis: Eine Interrupt-Service-Routine sollte nach Möglichkeit immer so kurz wie möglich gehalten werden! Im Idealfall wird lediglich ein Flag gesetzt, was den normalen Algorithmus dazu veranlasst eine bestimmte Aufgabe anzuarbeiten.

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

Nachdem wir nun bereits ein paar einfachere Programme ausprobiert und dabei grundlegende Techniken kennengelernt haben, soll es nun an etwas anspruchsvollere Programme gehen, bei denen der Anspruch darin besteht komplexere externe Komponenten anzusprechen bzw. mehrere Hardware-Module des Mikrocontroller auf einmal zu nutzen.

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. Warum sind die Adressen für die zweite Zeile nicht 8 … 15? Das hat den einfachen Grund, dass der eingesetzte Display-Controller auch für Displays mit mehreren Zeilen eingesetzt wird und hier der vorhandene Adressraum genutzt wird. In der nachfolgenden Tabelle sehr ihr die Adressen bei verschiedenen Displays:

Displaytyp Zeile 1 Zeile 2 Zeile 3 Zeile 4 Bemerkung
1×8 00-07 / / /
1×16 00-0F / / /
1×16 (8+8) 00-07 / / / linke Hälfte
40-47 rechte Hälfte
1×20 00-13 / / /
1×40 00-27 / / /
2×8 00-07 40-47 / /
2×12 00-0B 40-4B / /
2×16 00-0F 40-4F / /
2×20 00-13 40-53 / /
2×24 00-17 40-57 / /
2×40 00-27 40-67 / /
4×16 00-0F 40-4F 10-1F 50-5F
4×20 00-13 40-53 14-27 54-67
4×40 00-27 40-67 / / Controller #1
/ / 00-27 40-67 Controller #2

Das Display des StartPIC18 ist mit einem HD44780 kompatiblem Display-Controller ausgestattet. In meinem Artikel zu diesem Display sind die Routinen gesondert zu finden. Mit Hilfe des nachfolgenden Programmbeispiels könnt ihr das 2×8 Zeilen LC-Display ganz einfach in Betrieb nehmen, die Funktionen des Displays kennen lernen und auf Basis des Beispiels eigene Entwicklungen vorantreiben.

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 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