Beginners mini-cursus (deel 4)

Tekst en variabelen weergeven op een LCD of PLED display

Het meest voorkomende type Liquid Crystal Display (= vloeibare kristallen scherm) bij micro controllers (µC) zijn displays met een chip van het type HD44780 van Hitachi er op.
Dit is een alfanumeriek display, dus voor het weergeven van karakters zoals letters (alfa) en cijfers (numeriek).
PIC Basic heeft hier dan ook een standaard instructie voor, die het weergeven van tekst en variabelen heel eenvoudig maakt.

8$

8 LCD's met een HD44780 chip liggen vaak op tweedehands markten.
 

Het HD44780 display heeft genoeg aan 6 verbindingen met een PIC: 4 datalijnen (D4...D7), een enable (EN) en een register select (RS).
Maar geen zorg, het besturen van deze lijnen regelt PIC Basic allemaal, je hoeft alleen de tekst op te geven die op het display moet komen.

Sluit eerst het LCD aan op een PIC16F628A volgens onderstaand schema:

8

8 Als je het display volgens dit schema aansluit, hoeft er niets gedefinieerd te worden.
Bij sommige displays wil de PIC niet meer goed programmeren, dan moeten weerstanden geplaatst worden.
Ga hiervoor met de muis op het schema staan.

 

Meer informatie over het HD44780 display

Laten we snel beginnen met tekst op het schermpje af te beelden, ik ga hier uit van een 2x16 display, wat wil zeggen 2 regels met 16 karakters per regel, maar een groter formaat (bijv. 2x24 of 4x20) werkt ook met de voorbeelden van deze cursus.
PIC Basic zelf gaat in eerste instantie ook van een 2x16 uit, voor dit type hoef je niets te definiëren, dat doet PIC Basic allemaal voor je!
Verderop staat hoe je het display eventueel op andere poorten kunt aansluiten.

TIP: Lees ook de REM regels achter elke instructie van de voorbeelden, daar staat steeds nieuwe, extra informatie.


 

Met de instructie PRINT kun je gegevens zoals tekst en variabelen naar het display sturen.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

DELAYMS 500                   ;LCD stabilisering

CLS                           ;Wis scherm en zet cursor links bovenaan 
PRINT "Hello world!"          ;Plaats tekst op het display

END                           ;Einde programma

DEVICE en CONFIG zijn besproken in de eerste cursus delen.

Eerst een vertraging met DELAYMS.
Wanneer je een schakeling hebt gemaakt met een display moet je ergens bovenin je programma een vertraging van 0,2...0,5 seconde inbouwen, omdat sommige LCD's tijd nodig hebben om op te starten.
Als je de vertraging weglaat loop je de kans dat de PIC al met het LCD begint te communiceren voordat het LCD gereed is om gegevens te verwerken.
Dit verschilt van het ene en het andere display type, maar een halve seconde wachten is lang genoeg voor alle soorten.

Het display wordt eerst gewist met de instructie CLS (CLear Screen = Wis scherm).
De instructie CLS zet tevens de (onzichtbare) cursor op de eerste positie van regel 1.

Dan volgt het commando PRINT met daarachter "Hello world!" tussen aanhalingstekens.
Alles wat tussen de aanhalingstekens staat komt op het display.

Let op, aangezien het programma nu niet in een lus loopt is het nu echt belangrijk dat END vermeldt wordt omdat de PIC na de PRINT instructie moet stoppen, anders loopt de PIC verder door zijn geheugen en kan hij rare dingen gaan doen.


That's all!
Het echte werk wordt door de simpele instructie PRINT gedaan.

Als het display goed is aangesloten dan moet er nu Hello world! op het display staan.
Is dit niet het geval dan moet alles nog eens goed worden nagekeken, is het contrast goed ingesteld?
Is het een HD44780 (of compatible kloon)?
En als het een oud type display betreft: is de negatieve spanning (-5V) aanwezig? (zie aansluiten display)

OK, maar wat nu als tekst niet vooraan, maar ergens halverwege op het display moet komen?
Of op de tweede regel? (Of op regel 3 of 4, als je een display met 4 regels hebt aangesloten).
Simpel, kijk maar:

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF 

DELAYMS 500                   ;LCD stabilisering

CLS                           ;Wis eventuele oude tekst uit 
PRINT AT 1, 7, "Hallo."       ;Zet 'Hallo.' op regel 1 vanaf positie (kolom) 7
PRINT AT 2, 1, "Hoe gaat ie?" ;Zet 'Hoe gaat ie?' op regel 2 vanaf het begin (= pos.1)

END                           ;Einde programma

PRINT AT y, x, "tekst" (= zet op regel y, positie x, "tekst").
Tik na PRINT AT eerst het regelnummer in (1 ... 4), dan een komma, dan het positie nummer (kolom) waarbij positie 1 de eerste, meest linkse plaats is waar de tekst moet beginnen, dan wéér een komma en dan tussen aanhalingstekens de tekst.
Makkelijker kan het niet.


De AT optie mag vaker voorkomen in een enkele PRINT instructie.
Onderstaand voorbeeld geeft hetzelfde resultaat als het bovenstaande.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF 

DELAYMS 500                   ;LCD stabilisering

CLS                           ;Wis eventuele oude tekst uit 
PRINT AT 1, 7, "Hallo.", AT 2, 1, "Hoe gaat ie?" ;Twee regels met 1 PRINT instructie  

END                           ;Einde programma

Een voordeel is dat deze manier minder geheugen van de PIC inneemt.
Het programma kán er ook wat overzichtelijker door worden, maar soms ook juist niet, afhankelijk van de regelopbouw van dat moment.


Het volgende programmaatje laat om de 3 seconden een andere tekst zien.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

SYMBOL Tijd = 3000            ;Tekst veranderen om de 3 seconden 

DELAYMS 500                   ;LCD stabilisering

WHILE 1 = 1                   ;Oneindig blijven doorgaan 
  CLS                         ;Wis oude tekst uit 
  PRINT "Hallo."              ;Zet tekst op het scherm 
  DELAYMS Tijd                ;Tijd om tekst te laten zien 

  CLS
  PRINT "Tekst op een LCD"
  DELAYMS Tijd

  CLS
  PRINT "Met PIC-Basic!"
  DELAYMS Tijd

  CLS
  PRINT "Eenvoudig toch?"
  DELAYMS Tijd

  CLS
  DELAYMS Tijd                ;Tijd om LEEG scherm te laten zien 
WEND                          ;Terug naar WHILE

END                           ;Einde programma

Nu is ook het voordeel van SYMBOL, besproken in deel 1, te zien.
Het woordje 'Tijd' heeft een waarde van 3000, oftewel 'Tijd' staat symbool voor 3000, 'Tijd' is hetzelfde als 3000 in dit programma.
Als je nu de tekst langer of korter in het scherm wilt hebben, hoef je alleen maar 'Tijd' aan te passen en de tijden achter alle DELAYMS zijn aangepast.
Anders zou je de 3000 achter elke DELAYMS moeten aanpassen.


Het volgende programma laat ook tekst zien, maar wist de oude tekst niet, waardoor er elke seconde een woordje achter het vorige woordje bij komt.
Denk er hierbij wel aan dat je na de tekst eerst een spatie geeft en dan pas de aanhalingsteken sluit, anders staat alles aan elkaar en komt er TekstopeenLCD op het display te staan.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

SYMBOL Tijd = 1000            ;Tekst veranderen om de seconde 

DELAYMS 500                   ;LCD stabilisering 

CLS                           ;Wis eventuele oude tekst uit, cursor op 1, 1 

WHILE 1 = 1                   ;Oneindig blijven doorgaan 
  PRINT "Tekst "              ;Zet tekst + spatie op het scherm 
  DELAYMS Tijd                ;Tijd om tekst te laten zien 

  PRINT "op "
  DELAYMS Tijd

  PRINT "een "
  DELAYMS Tijd

  PRINT "LCD"                 ;Achter het laatste woord hoeft geen spatie
  DELAYMS Tijd

  CLS                         ;Wis scherm en zet cursor op 1, 1
  DELAYMS Tijd                ;Tijd om een LEEG scherm te laten zien
WEND                          ;Terug naar WHILE

END                           ;Einde programma

De eerste PRINT opdracht zet Tekst en een spatie op het display.
Dit zijn bij elkaar 6 karakters, want 'Tekst' bestaat uit 5 karakters (letters in dit geval), maar een spatie telt ook als karakter.
De (onzichtbare) cursor is net daarvoor door de instructie CLS op regel 1, positie 1 gezet.
Nu er 6 karakters zijn afgebeeld, staat de cursor op (1 + 6) = de 7e positie en blijft daar staan totdat je aangeeft met PRINT AT Y, X waar je de volgende tekst neer wilt zetten.
Als je nu de instructie PRINT zonder AT Y, X geeft zoals hierboven is gedaan, dan wordt de tekst geplaatst op de plek waar de cursor op dat moment staat en dat is dus positie 7.
Hierdoor wordt "op " achter "Tekst " gezet en dan staat de cursor op positie 10, want "op " bestaat uit 3 karakters, namelijk de letters o en p en natuurlijk de spatie die daarna is opgegeven.


Variabelen
Niet alleen tekst, maar ook het afbeelden van variabelen (variabelen zijn deels behandeld in cursus deel 1) van bijvoorbeeld de uitkomst van een berekening of een tijd- of spanningsmeting stelt weinig voor.

Als voorbeeld hier een BYTE variabele met de naam 'Teller' die elke halve seconde met 1 verhoogt wordt.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

DIM Teller AS BYTE            ;Deze Teller kan tot max.255 tellen (BYTE = 255)  

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

;Hoofdprogramma
CLS                           ;Scherm allereerste keer wissen

WHILE 1 = 1                   ;Oneindige lus
  PRINT AT 1, 1, DEC Teller   ;Plaats actuele waarde van variabele 'Teller'
  DELAYMS 500                 ;Tel snelheid
  INC Teller                  ;Teller met 1 verhogen
WEND                          ;Terug naar WHILE

END

Het bovenstaande programma moet helemaal duidelijk zijn want al deze instructies zijn behandeld in de vorige 3 cursusdelen.
De variabele met de naam 'Teller' is gedefinieerd als BYTE, en kan dus tot maximaal 255 tellen.
Als je dan nóg verder gaat tellen, springt hij weer op 0, maar de karakters 2, 5 en 5 (van 255) worden niet van het display gewist, je hebt namelijk geen 'wis scherm' opdracht (CLS) gegeven.
Dus de PIC stuurt ... 253, 254, 255, 0, 1, 2, 3, 4, enz., maar op het display zie je ... 253, 254, 255, 055, 155, 255, 355, 455, enz.
Dat komt omdat het display bij de waarden 0 t/m 9 alleen de eerste positie verandert, dus de '55' van 255 blijft gewoon op het display staan, terwijl de PIC de waarde van 'Teller' toch goed verzendt.
Een oplossing hiervoor wordt zodadelijk gegeven.

Voor de duidelijkheid: Teller is maar een naam, het mag van alles heten, als het maar met een letter begint.
Dus namen als Counter_A, Counter_B, Teller1, Teller2 of PietJanHein mogen ook.

Alleen over PRINT is nog wat uit te leggen:
Tekst zet je dus op het display door deze tussen aanhalingstekens te zetten zoals we eerder hebben gezien.
Een variabele echter, heeft meerdere manieren om op het scherm te worden geplaatst, namelijk als ASCII code, decimaal, hexadecimaal of binair.
Door keyword DEC na de PRINT instructie te schrijven geef je aan dat het decimaal weergegeven moet worden.
Deze DEC is dus wat anders dan de DEC die een variabele met 1 verminderd (decrement, is behandeld in deel 1).
Je mag overigens ook het 'at' teken (@) er voor zetten in plaats van het keyword DEC, deze moet dan zonder spatie aan de variabele geplakt worden, dus b.v. PRINT @Teller (zie zodadelijk in het voorbeeld).

Om een waarde hexadecimaal af te beelden moet HEX in plaats van DEC worden gegeven.
En om de waarde van de variabele binair weer te geven moet BIN worden opgegeven.

Als je geen @, DEC, HEX of BIN er bij zet en je plaatst tóch een variabelenaam, dan wordt het karakter uit de ASCII tabel er neer gezet.
Stel dat de variabele de waarde 65 heeft, dan wordt er een A op het display gezet (65 is de hoofdletter A volgens de ASCII code).

Het volgende programmaatje geeft alle 5 mogelijkheden tegelijk weer:

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

DIM Teller AS BYTE            ;Deze Teller kan tot max.255 tellen (BYTE = 255) 

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

;Hoofdprogramma
CLS                           ;Scherm allereerste keer wissen

REPEAT
  INC Teller                  ;Teller met 1 verhogen 
  PRINT AT 1, 1, DEC Teller   ;Variabele 'Teller' decimaal weergeven
  PRINT AT 1, 5, @Teller      ;Variabele 'Teller' decimaal weergeven
  PRINT AT 1, 9, HEX Teller   ;Variabele 'Teller' hexadecimaal weergeven 
  PRINT AT 2, 1, Teller       ;Variabele 'Teller' als ASCII karakter weergeven 
  PRINT AT 2, 3, BIN Teller   ;Variabele 'Teller' binair weergeven
  DELAYMS 1000                ;Tel snelheid
UNTIL Teller = 255            ;Terug naar REPEAT totdat Teller op 255 staat

END

Let er op dat de WHILE-WEND lus is vervangen door de REPEAT-UNTIL lus.
De teller telt omhoog door INC en stopt automatisch als de teller op 255 staat.
REPEAT...UNTIL Teller = 255 betekent dus: herhaal (= repeat) alles wat tussen REPEAT en UNTIL staat totdat (= until) de teller op 255 staat (zie ook cursus deel 1).

Vooraan op regel 1 staat de decimale waarde van 'Teller', daarnaast nóg een keer de decimale waarde, maar nu in het programma aangegeven met het @ teken, op het display is het resultaat dus hetzelfde.
Halverwege regel 1 staat diezelfde teller, maar dan in hexadecimale notatie.
Op regel 2 staat vooraan een karakter uit de ASCII tabel.
Welk karakter er staat is dus afhankelijk van de waarde van 'Teller', vaak staat er zelfs niets, dan heeft het gebruikte display geen karakter die bij die ASCII waarde hoort.
En vanaf positie 3 op regel 2 staat de binaire waarde van 'Teller'.


Ook is het mogelijk om variabelen samen met tekst op het display te zetten, door de af te beelden teksten en variabelen te scheiden door komma's in de PRINT opdracht, zie het volgende voorbeeld:

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

DIM Teller AS BYTE            ;Met deze variabele gaan we tellen 

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

;Hoofdprogramma 
CLS                           ;Scherm allereerste keer wissen

REPEAT
  INC Teller                  ;Teller met 1 verhogen
  PRINT AT 1, 1, "Stand=", DEC Teller, " punten" ;Variabele en tekst in 1 commando 
  DELAYMS 500                 ;Tel snelheid 
UNTIL Teller = 255            ;Terug naar REPEAT totdat Teller op 255 staat

END

Denk eraan dat je eerst een spatie geeft en dan pas de tekst 'punten', anders staat de tellerstand direct tegen de tekst 'punten' en dat staat niet zo mooi.


Minimum en maximum aantal cijfers, rechts uitlijnen en leidende nulonderdrukking
Het is in sommige gevallen nodig dat er een minimum of een maximum aantal karakters van een getal zichtbaar moeten zijn, bijvoorbeeld de weergave van een digitale klok op het display.
Er is dan een variabele met de naam 'Uur' voor de uren en een variabele 'Minuut' voor de minuten.

Dit kun je als PRINT DEC Uur, ":", DEC Minuut in het programma zetten.
Maar stel het is 11:09 uur, dan staat er op het display 11:9.
Dit hoort dus niet zo, er moet nog een 0 voor die 9 staan.
Door nu op te geven, dat de weergave van een variabele uit een vooraf bepaalt vast aantal cijfers moet bestaan, is dit alsnog mogelijk.
Zet direct achter DEC het getal van het minimum (en tevens maximum) aantal cijfers (= digits), in het voorbeeld van de klok dus 2 cijfers: PRINT DEC Uur, ":", DEC2 Minuut.
Door DEC2 te schrijven bij de variabele minuut, staat er nu 11:09.

Voor decimale cijfers is mogelijk DEC1...DEC10, voor hexadecimale cijfers HEX1...HEX8 en voor binaire getallen BIN1...BIN32.


Hiermee worden de getallen tevens rechts uitgelijnd.
Het probleem van de teller in het voorbeeld waarbij de 55 bleef staan, is hiermee ook opgelost:

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

DIM Teller AS BYTE            ;Deze Teller kan tot max.255 tellen (BYTE = 255)  

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

;Hoofdprogramma 
CLS                           ;Scherm allereerste keer wissen

WHILE 1 = 1                   ;Oneindige lus
  PRINT AT 1, 1, DEC3 Teller  ;Plaats actuele waarde van variabele 'Teller'
  DELAYMS 500                 ;Tel snelheid
  INC Teller                  ;Teller met 1 verhogen
WEND                          ;Terug naar WHILE

END

Het voorbeeld hierboven is precies hetzelfde als het voorbeeld met het probleem waarbij de 55 bleef staan, alleen staat hier DEC3 in plaats van alleen DEC.
Nu komt er op het display: ... 253, 254, 255, 000, 001, 002, 003, 004, enz.


Moeten die leidende nullen ook weg, dan zal er iets anders ondernomen moeten worden, omdat de PRINT opdracht daar (nog) geen kant en klare oplossing voor heeft.
Hiervoor maken we gebruik van de instructie CURSOR, waarmee de (onzichtbare) cursor op een bepaalde regel en positie kan worden gezet.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

DIM Teller AS BYTE            ;Deze teller kan tot max.255 tellen (BYTE = 255) 

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

;Hoofdprogramma 
CLS                           ;Scherm allereerste keer wissen

WHILE 1 = 1                   ;Oneindige lus
  CURSOR 1, 1                 ;Zet eerst de cursor linksbovenaan het display 
  IF Teller < 100 THEN PRINT " " ;Als de waarde van 'Teller' kleiner is dan 100, dan spatie 
  IF Teller < 10  THEN PRINT " " ;Als de waarde van 'Teller' kleiner is dan 10, dan spatie 
  PRINT DEC Teller            ;Plaats actuele waarde van variabele 'Teller' 
  DELAYMS 500                 ;Tel snelheid
  INC Teller                  ;Teller met 1 verhogen
WEND                          ;Terug naar WHILE

END

Met CURSOR y, x (= regel, positie) kun je de (onzichtbare) cursor op díe plek zetten waar je hem hebben wilt.
In bovenstaande voorbeeld zetten we eerst de cursor daar waar de tellerstand moet komen, op regel 1 en eerste positie, met CURSOR 1, 1.
Als je de teller op de tweede regel, zesde positie wilt hebben dan zou er CURSOR 2, 6 moeten staan.

Daarna gaan we met IF...THEN bekijken hoeveel spaties (in plaats van nullen) er vóór het getal moeten komen.
De cursor staat dus al klaar op positie 1.
Als het getal kleiner is dan 100, dan één spatie zetten, hierdoor komt de cursor op positie 2, zonder dat er iets op het display wordt afgebeeld.
Bij de tweede IF wordt bekeken of het getal ook kleiner is dan 10 en zo ja, dan nóg één spatie zetten, waardoor de cursor op positie 3 staat.
Nu kan het getal afgebeeld worden met PRINT DEC Teller (of PRINT @Teller) en afhankelijk van de huidige positie van de cursor wordt het getal weergegeven vanaf positie 1, 2 of 3.

Als je overigens 'INC Teller' verandert in 'DEC Teller', dan telt de teller niet omhoog, maar omlaag.
Dan zie je meteen dat DEC achter een PRINT opdracht decimaal betekent en dat die andere DEC een heel andere betekenis heeft, namelijk verlagen (= decrement).


Een FLOAT variabele werkt met cijfers achter de komma, maar deze variabele vergt veel van de PIC (net als DWORD variabelen).
Maar met trucjes heb je FLOAT vaak niet eens nodig.
Om een Euro bedrag met twee cijfers achter de komma met een WORD variabele op het display te krijgen kun je het volgende doen:

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

DIM Bedrag        AS WORD     ;Deze variabele bevat het Euro bedrag

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

;Hoofdprogramma 
CLS                           ;Scherm wissen
Bedrag = 1995                 ;Voorbeeldwaarde 1995 staat voor 19,95 Euro
PRINT DEC Bedrag/100, ",", DEC2 Bedrag, " Euro" ;Getal met 2 cijfers na de komma 

END

De laatste twee cijfers worden van variabele 'Bedrag' afgehaald door 'Bedrag' door 100 te delen en op het display te zetten.
1995 / 100 = 19,95 ,de cijfers (95) na de komma vervallen, dus er blijft 19 over.
Ná de komma worden deze laatste twee cijfers door DEC2 alsnog op het display gezet, waardoor er 19,95 op het display komt te staan.
Is 'Bedrag' kleiner dan 100 dan wordt vóór de komma een 0 geplaatst.
 

Voor één cijfer na de komma geldt precies hetzelfde, bijvoorbeeld een temperatuursweergave met een BYTE variabele:

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

DIM Temperatuur   AS BYTE     ;Deze variabele bevat de temperatuurswaarde 

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

;Hoofdprogramma 
CLS                           ;Scherm wissen
Temperatuur = 205             ;Voorbeeldwaarde 205 staat voor 20,5 graden
PRINT DEC Temperatuur/10, ",", DEC1 Temperatuur, %11011111, "C" ;Met 1 cijfer na de komma 

END

Vóór de komma deel je de waarde van 'Temperatuur' door 10 waardoor het laatste cijfer van de waarde van 'Temperatuur' wegvalt.
Voorbeeldwaarde 205 wordt dan dus 20.
Daarachter zet je "," voor de decimale komma.
Ná die komma staat DEC1 dat het meest rechtse cijfer van 'Temperatuur' plaatst (van 205 is dat dus de 5).
In plaats van de komma in de PRINT regel mag je natuurlijk ook een punt plaatsen.
Omdat het een BYTE variabele is, kun je maar tot maximaal 25,5°C.
Als je een hogere temperatuur wilt weergeven, dan de variabele 'Temperatuur' als WORD declareren (= max. 6553,5°C).

De %11011111 (= 223) geeft het ASCII teken ' ° ' weer op een HD44780/A00 display, zodadelijk meer over de ASCII tabel.


Om een groot getal duidelijker leesbaar te maken wordt er normaal om de drie cijfers een punt geplaatst.
Zo is het getal honderdduizend cijfermatig 100000, maar is het beter leesbaar met een punt er tussen, dus 100.000, en tienmiljoen is 10.000.000.
Om dit op het display te krijgen kun je gebruikmaken van DEC3.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

DIM Teller AS DWORD           ;DWORD teller!

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

;Hoofdprogramma 
CLS                           ;Scherm allereerste keer wissen
Teller = 9800                 ;Startwaarde van 'Teller'
WHILE 1 = 1                   ;Oneindige lus
  PRINT AT 1, 1, DEC Teller   ;Plaats actuele waarde van variabele 'Teller' zonder punten' 
  PRINT AT 2, 1, DEC3 Teller/1000000, ".", DEC3 Teller/1000, ".", DEC3 Teller, " Hz"
  INC Teller                  ;Teller met 1 verhogen
WEND                          ;Terug naar WHILE

END

Bovenstaand programma plaatst op displayregel 1 de tellerwaarde zónder puntjes en op displayregel 2 mét puntjes.

De telsnelheid is hier overigens niet hoog, waaraan je kunt zien dat een DWORD variabele veel van de PIC vraagt.
Verander de DWORD maar eens in een WORD variabele, dan zul je zien dat de teller veel sneller loopt.
Probeer DWORD variabelen dus zoweinig mogelijk te gebruiken, en dat geldt ook voor FLOAT.
 

Moeten die leidende nullen weg dan moet je het volgende doen:

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

DIM Teller AS WORD            ;WORD teller

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

;Hoofdprogramma 
CLS                           ;Scherm allereerste keer wissen
WHILE 1 = 1                   ;Oneindige lus
  PRINT AT 1, 1, DEC Teller   ;Plaats actuele waarde van variabele 'Teller' zonder punten' 

  IF Teller > 999 THEN        ;Als de waarde van 'Teller' groter is dan 999 dan...
    PRINT AT 2, 1, DEC Teller/1000, ".", DEC3 Teller ;...waarde met punt weergeven
  ELSE
    PRINT AT 2, 1, DEC Teller ;En anders het getal "gewoon" op het display weergeven
  ENDIF

  INC Teller                  ;Teller met 1 verhogen
WEND                          ;Terug naar WHILE

END

De werking van bovenstaand voorbeeld moet voor zichzelf spreken.


ASCII tabel
De ASCII code zorgt ervoor dat alle apparaten, zoals computers, printers, maar ook displays volgens een enigzins gestandaardiseerde code werken waardoor als het ene apparaat een A verzendt, dat er aan de andere kant ook een A tevoorschijn komt.
Wanneer een PC de waarde 65 naar de printer stuurt, wordt er een hoofdletter A uitgeprint.
Zo ook bij de PIC, als deze de waarde 65 naar het display stuurt, komt er een hoofdletter A op het display.
Bij de waarde 66 een hoofdletter B, bij 67 een C, enz.
Voor de kleine letters (= onderkast) geeft ASCII 97 een a, bij 98 een b, bij 99 een c, enz.
Al deze karakters staan in de ASCII tabel.


In Positron is deze te vinden door te klikken op View > Plugin > ASCII Table...
 

PRINT 72, 65, 76, 76, 79 geeft dus hetzelfde resultaat als PRINT "HALLO" en PRINT 65, 66, 67, 68, 69 geeft ABCDE op het display.

Er zijn dus 256 mogelijkheden, maar er bestaan veel meer karakters dan 256.
Hoewel de ASCII codes onder de 128 behoorlijk standaard zijn, is er niet echt een standaard voor de karakters met ASCII code 128 t/m 255.
Zo is het Euro teken vrij nieuw en bestaat dus vrijwel op nog geen enkel display.
Als een fabrikant besluit om het Euro teken in te voeren, moet een ander karakter er voor wijken.
Stel dat het Euro teken ASCII code 223 krijgt, dan geeft PRINT 223 op dat nieuwe display het Euro teken, maar op een ander, ouder display een ° teken, als je een HD44780/A00 hebt tenminste, bij een /A02 krijg je het ß karakter.

Soms zie je dit ook in kranten.
Op de nieuwsredactie wordt op de PC het Euro teken in de tekst gezet, maar de machine van de drukkerij zet voor die code een heel ander, vreemd teken in de plaats.

Op het HD44780/A00 display is ASCII 255 ($FF) een blokje (alle matrixpuntjes (= dots) zijn dan geactiveerd), maar op het Europese (HD44780/02) is het een 'ij'.

Tabel 44780/A00 display


Een gedeelte wissen van het display
Met de instructie CLS (CLear Screen = wis scherm) wordt alle tekst op het display gewist.
Maar wat nu als alleen maar 1 regel of een gedeelte van een regel van het display gewist moet worden?
Je zou natuurlijk allemaal spaties op dat gedeelte kunnen zetten.
Dus stel dat alleen van regel 2, de posities 5 t/m 10 gewist moet worden, dan zou je kunnen schrijven: PRINT AT 2, 5, "      "(met 6 spaties).

Een nettere mogelijkheid is PRINT met de optie REP c\n.
REP staat voor repeat (= herhalen), c staat voor character (= karakter), dan een backslash ' ' en n staat voor number (= aantal), kortom, REP c\n = herhaal opgegeven karakter een aantal maal.
Na REP geef je tussen aanhalingstekens een spatie, dan de backslash en dan het getal voor het aantal spaties dat je wilt hebben.
Om het bovenste voorbeeld te nemen van de 6 spaties, zou je moeten schrijven: PRINT AT 2, 5, REP " "\6 , hierdoor worden er 6 spaties afgebeeld.

Maar je kunt elk karakter tussen aanhalingstekens zetten.
Stel, je wilt op regel 2 van begin tot eind een streepjeslijn op het display, dan schrijf je: PRINT AT 2, 1, REP "-"\16, hierdoor wordt er 16 maal een streepje afgebeeld, zie onderstaand voorbeeld.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF 

DELAYMS 500                   ;LCD stabilisering

;Hoofdprogramma
CLS                           ;Scherm wissen 
PRINT AT 2, 1, REP "-"\16     ;Teken een lijn van 16 streepjes

END

Als je in dit voorbeeld in plaats van het streepje bijvoorbeeld letter A tussen de aanhalingstekens zet, dan krijg je 16 maal een A op het display.



Een tijdsbalk.
 

Hiermee kunnen we ook makkelijk een tijdsbalk maken op het display, door een blokje als karakter te kiezen.
(Dit werkt alleen met het HD44780/A00 type, bij de /A02 krijg je in plaats van een blokje een 'ij').
Maar dat blokje staat niet op het toetsenbord van de PC of laptop, dus die kun je niet tussen de aanhalingstekens typen.
Het blokje heeft volgens de ASCII tabel de waarde 255.
Je kunt namelijk ook rechtstreeks het ASCII nummer in de PRINT REP instructie invullen, de aanhalingstekens moet je dan weglaten.
Voor zeven blokjes op regel 2 van het display schrijf je dan PRINT AT 2, 1, REP 255\7.
Dus voor de duidelijkheid: REP "A" \ 7 is hetzelfde als REP 65 \ 7, want 65 is het ASCII nummer van de hoofdletter A en geeft 7 maal een A op het display.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF 

DELAYMS 500                   ;LCD stabilisering

;Hoofdprogramma
CLS                           ;Scherm wissen 
PRINT AT 2, 1, REP 255\8      ;Teken een lijn van 8 blokjes

END

De 2 getallen in de REP instructie mogen ook constanten, variabelen of berekeningen zijn.
Dus om een tijdsbalk te maken kun je, in plaats van een vast getal voor het aantal blokjes in te vullen, het aantal blokjes laten afhangen van een variabele.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

SYMBOL Lengte = 16            ;Dit getal bepaalt de lengte van de lijn (display breedte) 

DIM Teller AS BYTE            ;Deze Teller kan tot max.255 tellen (BYTE = 255) 

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

;Hoofdprogramma 
CLS                           ;Scherm wissen
PRINT AT 2, 1, REP "_"\Lengte ;Teken een lijn van underscore (lage) streepjes

REPEAT
  DELAYMS 500
  INC Teller
  PRINT AT 2, 1, REP 255\Teller ;Aantal blokjes afhankelijk van 'Teller' 
UNTIL Teller = Lengte         ;Blokjes tekenen totdat het aantal gelijk is aan 'Lengte' 

PRINT AT 1, 1, "Klaar!"       ;Zet op de eerste regel 'Klaar!' als alle blokjes er staan 

END

De eerste PRINT REP tekent een lijn met underscore streepjes ' _ ', zodat de gebruiker kan zien, hoeveel blokjes er komen te staan (dat maakt het wachten voor de gebruiker aangenamer).
De lijnlengte wordt bepaalt door de constante die we de naam 'Lengte' hebben gegeven.
Een constante is een naam dat een getal voorstelt en houdt het hele programma dezelfde waarde, constant hetzelfde dus, bovendien neemt het geen extra geheugenruimte in van de PIC.
In bovenstaande voorbeeld heeft de naam 'Lengte' constant de waarde 16, dit hebben we gedaan met SYMBOL = 16 (zie cursus deel 1).
Iemand die een display heeft met 24 karakters per regel in plaats van 16, hoeft dan alleen maar dat getal aan te passen, anders zou hij het hele programma door moeten spitten om te kijken waar overal nog meer aangepast moet worden, met alle kans op vergissingen.

De tweede PRINT REP in bovenstaand programma tekent blokjes (ASCII 255), het aantal blokjes is afhankelijk van de waarde van 'Teller'.
'Teller' op zijn beurt is weer afhankelijk van 'Lengte', dus de lengte van het streepje die we eerder getekend hebben.
Als je de waarde van 'Lengte' achter SYMBOL aanpast in bijvoorbeeld 8, dan telt 'Teller' ook maar tot 8.
Dan wordt er eerst een lijn van maar 8 streepjes getekend en daarna komen er ook maar 8 blokjes, test dat maar eens uit.
De blokjes komen voor de streepjes in de plaats.
Ook het getal vóór de backslash ' \ ' mag een constante of variabele zijn, waardoor er steeds een ander karakter kan komen te staan, afhankelijk van de variabele.

Het lijkt ingewikkeld, maar als je van bovenaf het programmaverloop volgt, valt het wel mee.
Als het moeilijk te volgen is dan selecteer je het voorbeeldprogramma en print je de selectie uit op papier.
Een kwestie van een paar keer doorlezen en vooral... experimenteren door getallen te wijzigen en dan kijken wat er gebeurt en wat het display gaat doen.
Je bent op de goede weg als je dingen in het programma wijzigt en dan al vooraf kunt voorspellen wat of er zou (moeten) gaan gebeuren en gebeurt dat niet, dan onderzoeken waaróm niet.

Berekeningen mogen ook in REP, als de berekening maar niet te ingewikkeld is.
Moet het wél een ingewikkelde berekening zijn, dan eerst de uitkomst van die berekening in een variabele stoppen en dan die variabele op zijn beurt in de REP instructie zetten.
Maar simpele berekeningen mogen dus rechtstreeks zoals in onderstaand voorbeeld:

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

SYMBOL Lengte = 8             ;Dit getal bepaalt de lengte van de lijn (display breedte) 

DIM Teller AS BYTE            ;Deze Teller kan tot max.255 tellen (BYTE = 255) 

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering


;Hoofdprogramma 
CLS                           ;Scherm wissen
PRINT "Met 2 tegelijk"        ;Plaats tekst
PRINT AT 2, 1, REP "_"\Lengte * 2   ;Teken een lijn van underscore (lage) streepjes

REPEAT
  DELAYMS 700
  INC Teller
  PRINT AT 2, 1, REP 255\Teller * 2 ;Aantal blokjes afhankelijk van 'Teller' 
UNTIL Teller = Lengte         ;Blokjes tekenen totdat het aantal gelijk is aan 'Lengte' 

PRINT AT 1, 1, REP " "\14     ;Wis oude tekst van regel 1 uit 
PRINT AT 1, 1, "Klaar!"       ;Zet op de eerste regel 'Klaar!' als alle blokjes er staan 

END

In dit voorbeeld heeft de constante 'Lengte' de waarde 8, maar er worden toch 16 streepjes op het display getekend, omdat bij de PRINT opdracht Lengte x 2 staat.
Ook loopt de balk niet met 1, maar met 2 blokjes tegelijk op, door de berekening Teller * 2, vermenigvuldigen met 2 dus.

Regel 1 met de tekst "Met 2 tegelijk" wordt eerst uitgewist door er 14 spaties neer te zetten met REP "  " \ 14, voordat "Klaar!" er wordt neergezet.
Als dat niet wordt gedaan dan komt er Klaar!tegelijk op het display te staan, "Met 2 tegelijk" wordt door het korte woordje "Klaar!" namelijk niet helemaal overschreven, waardoor "tegelijk" blijft staan, daarom eerst wissen.
Een andere mogelijkheid zou "Klaar!         " (met 8 spaties erachter) zijn, waardoor het woordje "tegelijk" wordt uitgewist door de spaties.
In dat geval kan de wisregel vervallen.


Zo zijn er nog eindeloos veel mogelijkheden met de REP optie van PRINT.
Nog één voorbeeld met REP dan:

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

SYMBOL Lengte = 16            ;Dit getal bepaalt de lengte van de lijn (display breedte)
SYMBOL Minuut = 60            ;Een minuut heeft 60 seconden

DIM Teller AS BYTE            ;Deze Teller kan tot max.255 tellen (BYTE = 255) 

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering


;Hoofdprogramma 
CLS                           ;Scherm wissen
PRINT AT 2, 1, REP "_"\Lengte ;Teken een lijn van underscore (lage) streepjes

REPEAT
  INC Teller
  PRINT AT 1, 1, DEC Teller   ;Zet actuele stand van 'Teller' op de eerste regel 
  PRINT AT 2, 1, REP 255\(Teller * Lengte) / Minuut ;Aantal blokjes afhankelijk van 'Teller' 
  DELAYMS 1000                ;1 seconde
UNTIL Teller = Minuut         ;Blokjes tekenen totdat de minuut voorbij is

END

Bovenstaand voorbeeld laat op regel 1 een teller zien die in 1 minuut van 1 t/m 60 telt.
Op regel 2 wordt eerst een dunne lijn getekend zodat een gebruiker kan schatten hoe lang die tijd gaat duren.
Wannéér er een blokje bij moet komen wordt berekent door het aantal verlopen seconden (in de variabele 'Teller') te vermenigvuldigen met de constante 'Lengte' en de uitkomst daarvan weer te delen door de constante 'Minuut'.
Dit zorgt ervoor dat als de teller bij 60 is aangekomen, de tijdsbalk ook precies vol met blokjes staat.
Hierdoor komen er sneller nieuwe blokjes bij een display met meer karakters dan één met maar 16 karakters, er moeten bij een groter display in die minuut immers meer blokjes geplaatst worden.
De waarde van de constante 'Lengte' moet dan wel gelijk zijn met het maximaal mogelijke aantal karakters dat op één regel past van het gebruikte display, anders klopt de berekening natuurlijk niet, tenzij je expres een korter balkje wilt hebben.

Met de blokjes kun je niet alleen een tijdsbalk, maar bijvoorbeeld ook een VU-meter van het display maken.
Hier wordt overigens weer duidelijk dat het toch echt wel belangrijk is om voor de duidelijkheid SYMBOL zo veel mogelijk te gebruiken in plaats van getallen rechtstreeks achter de instructies te zetten.


Het  "  teken op het display zetten
Hoe krijg je nu een aanhalingsteken op het display?
Een aanhalingsteken kun je namelijk niet tussen aanhalingstekens zetten, omdat de compiler denkt dat jouw aanhalingsteken de aanhalingsteken sluiten is.
Er is maar één manier voor en dat is rechtstreeks het ASCII nummer in te toetsen.
Voor de aanhalingsteken is dat ASCII 34.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF

DELAYMS 500                   ;LCD stabilisering

CLS                           ;Wis scherm en zet cursor links bovenaan 
PRINT "Het ", 34, "LC", 34, " display" ;Plaats LC tussen aanhalingstekens op het display 

END                           ;Einde programma

Bovenstaand programma zet Het "LC" display op het display, het ASCII 34 plaatst een aanhalingsteken op het display.


Zelf ontworpen karakters op het display zetten
De eerste 64 geheugenadressen (= geheugenplaatsen) (op de adressen 64 t/m 127) van het HD44780 display zijn vrij om 8 eigen ontworpen karakters in op te slaan (van 8 bytes per karakter, dus 8x8=64 adressen).
Je kunt bijvoorbeeld een Smiley ontwerpen en dat op het display laten zien.

Dit gedeelte is wat moeilijker, wil je dat (nog) niet dan kun je het overslaan.
Klik op de knop als je er wel mee bezig wilt.


Het display op andere poorten aansluiten
Als je niets definieert, dus helemaal niets aan de PIC Basic compiler vertelt over wat voor soort display, hoeveel regels en karakters, welke poorten, enz., dan gaat de PIC Basic compiler van de volgende instellingen uit, default setting (= standaard instelling) wordt dat genoemd:

HD44780(of compatible) alfanumeriek display
Data aansturing met 4 datalijnen
De enable (EN) pin aan PORTB.2
De register select (RS) pin aan PORTB.3
De datalijnen D4 t/m D7 aan PORTB.4 t/m PORTB.7
Twee display regels ( 2x16 )

Als je display voldoet en het aansluit volgens bovenstaande opsomming, dan hoef je verder niets te doen, de PIC Basic compiler regelt dan alles voor je.

Maar soms komt het, bijvoorbeeld bij het ontwerpen van de printplaat, beter uit als bijvoorbeeld de EN en RS lijnen van het display aan dezelfde kant van de PIC komen te zitten als waar ook de 4 datalijnen zitten.
Het is dus mogelijk om één (of meer) standaard instellingen te wijzigen.


Er is nog veel meer mogelijk met het display, zoals scrollen van de tekst, de cursor zichtbaar maken en eventueel laten knipperen en negatieve getallen.
Later als we verder zijn komt er misschien een tweede cursusdeel specifiek over het display.
Maar ook in de komende cursusdelen wordt het display veel gebruikt, omdat zo makkelijk is te zien, wat de waarden van variabelen en dergelijke zijn.


Tot slot nog 3 voorbeeld programma's met het display.


Teksten en variabelen afbeelden afhankelijk van gebeurtenissen
Sluit eerst 2 druktoetsen (puls) en een rode LED en een groene LED met serieweerstanden aan op de PIC volgens het schema.


De 2 schakelaars moeten pulsschakelaars zijn.
 


Voorbeeld 1
We gaan een programma maken dat de reactiesnelheid van een persoon meet en de uitkomst op het display zet.

TIP: Door het programmavoorbeeld te selecteren (linker muisknop ingedrukt houden en dan van onderaf 'slepen' naar boven) en met CTRL + C te kopiëren naar het klembord, kun je het in de PIC Basic editor met CTRL + A, en dan CTRL + V weer plakken, dat scheelt een hoop typewerk en voorkomt vergissingen.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF
ALL_DIGITAL TRUE              ;Alle ingangen digitaal (voor de toetsen)

;Logische constanten
SYMBOL AAN          = 0       ;Geinverteerd AAN 
SYMBOL OFF          = 0       ;UIT 
SYMBOL ON           = 1       ;AAN 
SYMBOL UIT          = 1       ;Geinverteerd UIT

;Algemene constanten 
SYMBOL MaxTijd      = 2000    ;Maximaal 2 seconden de tijd om de reactietoets in te drukken 

;Poortnamen
SYMBOL ActieLED     = PORTA.2 ;LED die aangeeft dat je op de schakelaar moet drukken 
SYMBOL ErrorLED     = PORTA.3 ;De LED die aangeeft dat je iets fout deed heet ErrorLED 
SYMBOL StartToets   = PORTB.0 ;De pulsschakelaar om het spel te starten heet StartToets 
SYMBOL ReactieToets = PORTB.1 ;De toets die de reactiesnelheid meet heet ReactieToets

;Variabele declareren
DIM Teller AS WORD            ;Deze variabele telt de tijd heel snel op (max.65535)

;        76543210
TRISA = %11110011             ;PORTA.2 en A.3 zijn uitgangen voor de LED's
TRISB = %11111111             ;Deze regel mag eventueel weggelaten worden
 
PORTB_PULLUPS ON              ;On-chip pull-up weerstanden actief (voor de schakelaars)
CLEAR                         ;Wis alle RAM geheugen 
DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
CLS                           ;Eerste keer scherm wissen

WHILE 1 = 1                   ;Oneindige lus (het spel is hierdoor steeds opnieuw te starten) 
  WHILE StartToets = UIT : WEND ;Zolang de StartToets uit is, hier wachten

  CLS                         ;Scherm wissen
  PRINT "Opgelet!"            ;Zet tekst neer 
  ActieLED = OFF              ;De LED's uitzetten
  ErrorLED = OFF

  DELAYMS 5000                ;Nu eerst 5 seconden wachten 

  IF ReactieToets = AAN THEN  ;Als na de 5 seconden de toets meteen al aan is dan...
    ErrorLED = ON             ;...wordt er vals gespeeld! De rode LED aanzetten
    PRINT AT 1, 1, "Niet vals spelen"
  ELSE                        ;...en anders... (wordt er dus eerlijk gespeeld)
    ActieLED = ON             ;Zet de groene LED aan 

    CLEAR Teller              ;Teller resetten (vanaf 0 beginnen te tellen)
    WHILE Teller < MaxTijd    ;Zolang de tijd nog niet voorbij is kan er gedrukt worden
      IF ReactieToets = AAN THEN BREAK ;Zodra de toets wordt ingedrukt uit deze lus springen
      DELAYMS 1               ;We meten de tijd per milliseconde
      INC Teller              ;Teller met 1 verhogen
    WEND

    IF Teller < MaxTijd THEN  ;Als er binnen de tijd was gedrukt dan...
      PRINT AT 1, 1, "Reactie: ", DEC Teller, "mS" ;...de reactietijd weergeven 
      IF Teller < 300 THEN PRINT AT 2, 1, "Zeer snel, prima!" ;Tekst op regel 2 bij zeer snel
    ELSE                      ;...anders... (dus te lang gewacht)
      ActieLED = OFF          ;groene LED weer uitzetten en de... 
      ErrorLED = ON           ;...rode LED aan want te lang gewacht
      PRINT AT 1, 1, "Te laat gedrukt" ;Tekst 'Te laat gedrukt' plaatsen
    ENDIF
  ENDIF
WEND

END

Dit is een compleet werkend spel.
Het spel wordt gestart met een druk op de starttoets, aangesloten op PORTB.0
Het display geeft dan aan: Opgelet! en er begint een tijd van 5 seconden te lopen.
Na die 5 seconden gaat de groene actie LED branden en is het de bedoeling om dan zo snel mogelijk de reactietoets (op PORTB.1) binnen 2 seconden in te drukken (Actie = Reactie).
Als er eerlijk wordt gespeeld en er op de reactietoets wordt gedrukt nadat de groene LED is gaan branden, dan wordt de tijd in milliseconden gemeten en op het display afgebeeld, bijv.: Reactie: 126mS
Alleen als je echt heel snel bent, komt er op de tweede regel te staan: Zeer snel, prima!, anders niet.

Wordt er echter vals gespeeld door de toets al in te drukken voordat de groene LED brandt, dan gaat niet de groene, maar de rode LED branden en meldt het display: Niet valsspelen!
De rode LED gaat ook branden als er langer dan 2 seconden wordt gewacht en dan meldt het display: Te laat gedrukt.

Het spel is steeds opnieuw te starten met een druk op de starttoets.


De werking van het programma van het spel
De eerste regels moeten nu toch duidelijk bekend zijn van de eerste cursusdelen.
Er wordt een waarde van 2000 aan de naam 'MaxTijd' gegeven, dus 2000mSec = 2 seconden de tijd om de knop in te drukken.

Daarna worden namen aan de poorten gegeven, de LED's zitten op PORTA en de 2 toetsen op de 2 nog vrije poorten van PORTB, de andere 6 poorten worden door het display gebruikt.
PORTB is nu dus helemaal bezet.
Die 2 toetsen zijn niet voor niets op PORTB gezet, alleen PORTB heeft inwendige pull-up weerstanden, PORTA heeft deze niet en zo sparen we ons dus 2 losse pull-up weerstanden uit.
De inwendige pull-up weerstanden moeten dan wel worden geactiveerd, dat gebeurt verderop met de instructie PORTB_PULLUPS ON.

TRISA = %11110011 geeft aan dat PORTA.3 en PORTA.2 uitgangen moeten zijn, want hierop zijn de LED's aangesloten.
De poorten waarop het display is aangesloten definieert de PIC Basic compiler zelf als uitgang zodra het de instructie PRINT, CLS of CURSOR tegenkomt, daar hebben we geen omkijken naar, zelfs als we die poorten als ingang hebben opgegeven.

Er wordt maar 1 variabele in dit programma gebruikt en die hebben we hier (weer) 'Teller' genoemd omdat hij de milliseconden telt zolang er nog niet op de reactietoets is gedrukt, maar een andere naam mag natuurlijk ook.
Let op dat we deze variabele nu als WORD hebben gedefinieerd en niet als BYTE.
Als we 'Teller' als BYTE zouden definiëren dan konden we maar tot 255 milliseconden tellen, en dat is wel erg kort.
Met WORD kunnen we t/m 65535 milliseconden tellen (ruim 65 seconden).

Dan het hoofdprogramma:
WHILE 1=1 betekent dat het spel oneindig vaak is op te starten.

Eerst wordt gewacht tot er op de starttoets wordt gedrukt.
Hiervoor is een leeg WHILE - WEND wachtlusje ingebouwd.
Met leeg bedoel ik, dat er niets staat tussen WHILE en WEND.
Dus 'WHILE StartToets = Uit : WEND' doet niets, zolang (= while) de StartToets uit is.
Let erop dat je tussen WEND en de puntkomma (;) van de rem-regel een spatie hebt.
Dus niet de puntkomma strak achter WEND want dan krijg je een foutmelding als je gaat compileren.
Wordt er nu op de StartToets gedrukt, dan gaat het programma verder en begint het spel.

Eerst een CLS zodat een eventuele stand van de vorige keer op het display wordt gewist.
Meteen daarna wordt er "Opgelet!" op het display gezet.
Ook worden meteen de 2 LED's uitgezet, mochten deze nog aan staan van de vorige keer.

Dan 5 seconden wachten.

Wanneer de 5 seconden voorbij zijn wordt eerst gekeken of de ReactieToets al is ingedrukt (IF ReactieToets = AAN THEN...) want als dat zo is dan wordt er vals gespeeld!
De rode LED wordt dan aangezet en op het display "Niet vals spelen" gezet.

Als de ReactieToets niet is ingedrukt (...ELSE...) dan wordt de groene LED aangezet en de variabele 'Teller' op 0 gezet.
Daarna komt er weer een WHILE - WEND lus.
In deze lus gebeuren de volgende dingen, zolang de maximum tijd (constante MaxTijd = 2 seconden) nog niet is bereikt (WHILE Teller < MaxTijd (zolang de variabele 'Teller' kleiner is dan 2000, deze lus doen)):

- Kijk of de ReactieToets is ingedrukt en zo ja, dan uit deze WHILE - WEND lus springen (...THEN BREAK).
- Wacht 1 milliseconde.
- Verhoog de variabele 'Teller' met 1 (INC Teller).
- Ga via WEND de lus opnieuw uitvoeren, dus weer de ReactieToets meten, 1 milliseconde wachten, Teller verhogen, enzovoort.

De lus kan nu door 2 oorzaken beëindigd worden.
óf doordat de 'Teller' groter is geworden dan MaxTijd (die staat op 2000 (= 2 seconden)) en als dat het geval is, dan heb je te laat gedrukt.
óf omdat de ReactieToets is ingedrukt.
Als dat het geval is wordt de lus onmiddellijk verlaten door de instructie BREAK.

Na de WHILE - WEND lus wordt bekeken of er binnen de tijd was gedrukt (IF Teller < MaxTijd THEN...).

Zo ja:
Zet op het display "Reactie: " met daarachter de huidige waarde van de variabele 'Teller', want deze bevat nog steeds de ReactieToets druktijd, en daar weer achter "mS" (milliSeconden).
Als er zeer snel op de ReactieToets was gedrukt (binnen de 300 milliseconden) dan wordt er op regel 2 van het display ook nog "Zeer snel, prima!" neergezet (IF Teller < 300 THEN PRINT...).

Zo nee: er is dus niet binnen de tijd gedrukt (via ELSE)
De groene LED uit- en de rode LED aanzetten en op het display "Te laat gedrukt" neerzetten.
Let op, er wordt geen oude tekst gewist, de nieuwe tekst wordt gewoon over de oude tekst heen geschreven, dit kan zolang je er maar voor zorgt, dat de nieuwe tekst minstens even lang is als de oude tekst, anders blijft er een deel van de oude tekst zichtbaar.

De laatste WEND stuurt het programma weer naar WHILE 1=1 waardoor het spel oneindig vaak opnieuw is te starten.
Het eerste wat het programma doet na die WHILE 1=1, is dus wachten tot er opnieuw op de StartToets wordt gedrukt, enzovoort.


Voorbeeld 2
Het volgende programma telt automatisch omhoog of omlaag van 0 t/m 100, afhankelijk van welke toets is ingedrukt, bovendien geven de LED's ook nog wat aan.
Het elektrisch schema is precies hetzelfde als van het programma hierboven met de 2 toetsen en 2 LED's, dus daar hoef je niets aan te veranderen, alleen zijn nu wel de namen van de poorten in het programma veranderd.

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF
ALL_DIGITAL TRUE              ;Alle ingangen digitaal

;Logische constanten
SYMBOL AAN          = 0       ;Geinverteerd AAN 
SYMBOL OFF          = 0       ;UIT
SYMBOL ON           = 1       ;AAN
SYMBOL UIT          = 1       ;Geinverteerd UIT

;Algemene constanten 
SYMBOL MaxSnelheid  = 20      ;Hoe lager het getal, hoe sneller de teller kan

;Poortnamen
SYMBOL LED_Groen    = PORTA.2 ;LED brandt als waarde te laag is
SYMBOL LED_Rood     = PORTA.3 ;LED brandt als waarde te hoog is
SYMBOL ToetsHoger   = PORTB.0 ;Deze pulstoets laat de teller omhoog lopen
SYMBOL ToetsLager   = PORTB.1 ;Deze pulstoets laat de teller omlaag lopen

;Variabelen declareren
DIM Teller          AS BYTE   ;Deze variabele bevat de tellerwaarde 
DIM TelSnelheid     AS BYTE   ;Deze variabele heeft de telsnelheid
DIM Tijd            AS WORD   ;Houdt de tijd bij als een toets is ingedrukt

;        76543210 
PORTA = %00000000             ;Alle PORTA uitgangen uit (laag maken)
TRISA = %11110011             ;PORTA.2 en A.3 zijn uitgangen voor de LED's
TRISB = %11111111             ;Deze regel mag eventueel weggelaten worden

PORTB_PULLUPS ON              ;On-chip pull-up weerstanden actief (voor de schakelaars) 
CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
CLS                           ;Display allereerste keer wissen
PRINT "Snelheid: ???"         ;Zet tekst op het display

WHILE 1 = 1                   ;Oneindige lus
  IF Teller < 20 THEN         ;Als 'Teller' waarde lager dan 20 is, dan...
    LED_Groen = ON            ;...de groene LED aanzetten
  ELSEIF Teller > 80 THEN     ;...anders, als 'Teller' waarde hoger dan 80 is, dan...
    LED_Rood  = ON            ;...de rode LED aanzetten
  ELSE                        ;...anders...
    LED_Groen = OFF           ;...beide LED's uitzetten
    LED_Rood  = OFF
  ENDIF

  WHILE (PORTB & 3) = 3       ;Wacht in deze lus tot een toets wordt ingedrukt
    TelSnelheid = 100         ;Beide toetsen niet bedient dan TelSnelheid weer langzaam
  WEND 

  IF ToetsHoger = AAN AND Teller < 100 THEN INC Teller ;...de teller met 1 verhogen
  IF ToetsLager = AAN AND Teller > 0   THEN DEC Teller ;...de teller met 1 verlagen

  PRINT AT 1, 11, DEC3 Teller ;De waarde in 3 cijfers op het display zetten 

  Tijd = TelSnelheid
  WHILE (PORTB & 3) < 3       ;Zolang 1 van de toetsen is ingedrukt in de lus blijven... 
    IF Tijd = 0 THEN BREAK    ;...maar als tijd voorbij is ook uit de lus springen
    DELAYMS 5
    DEC Tijd
  WEND

  IF TelSnelheid >= MaxSnelheid THEN TelSnelheid = TelSnelheid - 3 ;Steeds sneller tellen 
WEND                          ;Terug naar WHILE

END 

De ene toets laat de teller omhoog tellen, de ander naar omlaag en als je de toets blijft vasthouden, dan gaat het tellen automatisch en steeds sneller tot de tellerstand op 0 of 100 staat.
Bovendien gaat de groene LED branden als de tellerstand onder de 20 is en de rode LED als de tellerstand boven de 80 is.
Later in deze cursus gaan we hier de instructie PWM aan toevoegen en daarmee de snelheid van een motortje regelen en aflezen op het display.

De tel snelheid is dit keer geen constante (met SYMBOL), maar een variabele (met DIM) omdat de tel snelheid dus geen constante, maar een variabele snelheid is.
Als je namelijk één van beide toetsen ingedrukt blijft houden dan gaat de teller steeds sneller lopen omdat na iedere tel de variabele 'TelSnelheid' met 3 wordt verminderd (TelSnelheid = TelSnelheid - 3).
Hoe lager de waarde van 'TelSnelheid', des te korter wacht hij bij DELAYMS.
Anders gezegd, hoe lager de waarde, hoe sneller de teller loopt.

Steeds als je de toets loslaat, wordt de tel snelheid weer op z'n langzaamst (= 100) gezet.

Wanneer ToetsOmhoog is ingedrukt en (= AND) de teller nog niet op 100 staat, wordt 'Teller' met 1 verhoogt.
Als 'Teller' dus op 100 staat, wordt 'Teller' niet meer verhoogt.
Hetzelfde geldt voor ToetsOmlaag en Teller > (= groter dan) 0.

Hierna wordt de actuele waarde van 'Teller' achter de tekst "Snelheid: " geplaatst.

Dan het laatste blokje programma:
Eerst wordt de huidige waarde van 'TelSnelheid' door een andere variabele genaamd 'Tijd' overgenomen.
Beide variabelen hebben nu dus dezelfde waarde, alleen gaan we zodadelijk de variabele 'Tijd' tot 0 laten tellen.
Hierdoor blijft de variabele 'TelSnelheid' z'n huidige waarde behouden voor de volgende keer.

Het programma blijft in de binnenste WHILE - WEND lus zolang één van de twee knoppen is ingedrukt.
Met andere woorden, het programma springt uit de lus als beide toetsen zijn losgelaten.
Maar omdat de 'Teller' ook automatisch omhoog of omlaag kan tellen, juist bij het ingedrukt houden van een toets, moet er nóg een manier zijn om uit de lus te springen, ook als er een toets is ingedrukt.
En die is afhankelijk van de variabele 'Tijd', die net voor het ingaan van de lus een waarde van 'TelSnelheid' heeft gekregen (overgenomen).
Om de 5 milliseconden (DELAYMS 5 die ook in de lus staat) wordt de variabele met 1 verminderd (DEC Tijd) totdat deze op 0 staat (IF Tijd = 0 THEN BREAK).

En als laatste wordt de 'TelSnelheid' steeds verder opgevoerd door de wachttijd steeds korter te maken, tenminste, als de TelSnelheid nog niet aan de maximum snelheid is, opgegeven met SYMBOL MaxSnelheid.

Je begrijpt dat mijn uitleg over bekende programmadelen en instructies steeds minder wordt, omdat je zelf al behoorlijk moet kunnen nagaan hoe één en ander in een programma verloopt.
Ik kan me voorstellen dat alles niet meteen duidelijk is.
Lees de cursus daarom een paar keer vaker door, ook nog eens na een nachtje slapen.
Schrijf programma's niet in 1 keer maar stukje voor stukje en tussendoor steeds in de PIC programmeren en uittesten of datgene werkt.

 


Voorbeeld 3
Het laatste voorbeeld heeft 2 tellers voor een wijnflessen inpakmachine.
Eén telt het aantal flessen per doos, de ander telt het aantal dozen.
Ook dit voorbeeld werkt met hetzelfde schema als de vorige 2 voorbeelden:

DEVICE 16F628A                ;Gebruik een 16F628A type
CONFIG INTRC_OSC_NOCLKOUT,_   ;Interne oscillator, geen clocksignaal naar buiten
       WDT_OFF,_              ;WatchDog Timer uitgeschakeld
       PWRTE_ON,_             ;Power-up Timer Enable ingeschakeld
       LVP_OFF,_              ;Low Voltage Programming uitgeschakeld
       MCLRE_OFF              ;Externe Master Reset Enable uitgeschakeld
ALL_DIGITAL TRUE              ;Alle ingangen digitaal
 
;Logische constanten 
SYMBOL AAN          = 0       ;Geinverteerd AAN 
SYMBOL OFF          = 0       ;UIT 
SYMBOL ON           = 1       ;AAN 
SYMBOL UIT          = 1       ;Geinverteerd UIT

;Algemene constanten 
SYMBOL AantalDozen  = 5       ;Aantal dozen dat geproduceerd moet worden 
SYMBOL AantalStuks  = 12      ;Aantal stuks producten dat in een doos moet(VPE) 

;Poortnamen 
SYMBOL Gereed       = PORTA.2 ;Hoog als productie klaar is 
SYMBOL DoosVol      = PORTA.3 ;Geeft puls als een doos vol is 
SYMBOL ResetToets   = PORTB.0 ;Deze pulstoets stelt de startwaarde weer in 
SYMBOL TelPuls      = PORTB.1 ;Impuls van de machine als er weer een fles klaar is

;Variabelen declareren
DIM DozenTeller     AS BYTE   ;Telt het aantal dozen
DIM StuksTeller     AS BYTE   ;Telt aantal stuks per doos 

;        76543210 
PORTA = %00000000             ;Alle PORTA uitgangen uit (laag maken)
TRISA = %11110011             ;PORTA.2 en A.3 zijn uitgangen voor de LED's 
TRISB = %11111111             ;Deze regel mag eventueel weggelaten worden 

PORTB_PULLUPS ON              ;On-chip pull-up weerstanden actief (voor de schakelaars) 
CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering


;Hoofdprogramma 
CLS                           ;Eerste keer scherm wissen 

WHILE 1 = 1                   ;Oneindige lus
  PRINT AT 1, 1, "Stuks: ", @StuksTeller, " van ", @AantalStuks, " "
  PRINT AT 2, 1, "Dozen: ", @DozenTeller, " van ", @AantalDozen, " "

  WHILE (PORTB & 3) < 3 : WEND ;Wacht tot resettoets of puls van de machine weg is
  DELAYMS 20                   ;Anti contactdender
  WHILE (PORTB & 3) = 3 : WEND ;Wacht op resettoets of puls van de machine

  IF DozenTeller < AantalDozen AND StuksTeller < AantalStuks THEN ;Order nog niet klaar? 
    IF TelPuls = AAN THEN     ;Telpuls van de machine bij ieder afgeleverd product
      INC StuksTeller         ;Weer 1 klaar, dus teller met 1 verhogen
        IF StuksTeller = AantalStuks THEN ;Als doos vol is dan...
        DoosVol = ON          ;Puls signaal voor machine: moet nieuwe doos invoeren
        DELAYMS 200           ;Pulstijd
        DoosVol = OFF         ;Signaal weer uitzetten
        INC DozenTeller       ;Nieuwe doos erbij gekomen
        CLEAR StuksTeller     ;Reset aantal stuks per doos want nieuwe doos
      ENDIF
     ENDIF
  ELSE
    IF ResetToets = AAN THEN  ;Als er op de resettoets wordt gedrukt dan...
      CLEAR DozenTeller       ;Reset de DozenTeller (op 0 zetten)
      CLEAR StuksTeller       ;Reset de StuksTeller (op 0 zetten)
      Gereed = OFF            ;Gereed signaal weer laag maken
    ENDIF
  ENDIF

  IF DozenTeller = AantalDozen THEN Gereed = ON ;Aantal dozen is bereikt
WEND                          ;Terug naar WHILE

END

Ik ga bij dit laatste voorbeeldprogramma alleen uitleggen wat het doet en voorstelt, de werking moet je nu zelf kunnen uitpluizen.

Het programma stelt een teller van een machine voor die wijnflessen in een doos pakt.
De machine zelf geeft steeds een lage puls op ingang PORTB.1 (genaamd TelPuls) dat er weer een fles gevuld is en in de doos gepakt moet worden (simuleer dit (= doe dit zelf) met de druktoets).
Er gaan 12 wijnflessen in een doos en na 5 dozen moet de machine stoppen.
Als de doos vol is verschijnt er een kort pulsje op uitgang PORTA.3 (genaamd DoosVol) die de inpakmachine te kennen geeft dat de doos vol is en er een nieuwe doos gepakt moet worden (puls wordt aangegeven met de rode LED).
Wanneer de vijfde doos ook vol is wordt PORTA.2 hoog (genaamd Gereed, aangegeven met de groene LED) waardoor de machine automatisch stopt.
Door een lage puls met de druktoets op PORTB.0 (genaamd ResetToets) worden de beide tellers weer op 0 gezet en wordt PORTA.2 weer laag (groene LED dooft) waardoor de machine weer kan gaan inpakken.
Het programma is zo gemaakt dat de ResetToets alleen werkt als alle dozen vol zijn.
Op het display zijn de beide tellers af te lezen.

Extra aandacht voor de CONFIG instructie.
Deze is hier met underscores ( _ ) (= laag streepje) in stukken gehakt zodat elk deel van commentaar kan worden voorzien.
Meer info underscore


Ook een grafisch display met teken instructies zoals LINE, BOX, CIRCLE en PLOT kan met PIC Basic worden aangestuurd, echter zijn deze instructies alleen beschikbaar in het gekochte pakket en uitgeschakeld in PIC Basic LITE (demo versie).


Tot slot
Het is van groot belang dat bij een actie van een gebruiker eerst via het display wordt vermeldt dat de PIC de actie heeft begrepen.
Zelfs grote firma's vergeten dat wel eens.
Ik ben een enorme Philips fan, maar als ik de DVD-harddisk recorder met de afstandsbediening stand-by zet, duurt het zo'n 4 seconden voordat het apparaat daadwerkelijk stand-by gaat.
En dat is op zich niet erg, er zullen waarschijnlijk eerst wat gegevens moeten worden opgeslagen, maar als ik op de stand-by toets van de afstandsbediening druk, geeft het apparaat eerst geen enkele reactie.
Er wordt aan mij (de gebruiker) niet kenbaar gemaakt dat het bericht is overgekomen met een bericht als STAND BY op het display of iets dergelijks.
En ik maar steeds drukken op die afstandsbediening.

Bij de digitale ontvanger (van een Japans merk overigens), gaat het stappen door het menu van het apparaat ontzettend traag.
Dus ik druk op 'pijltje naar beneden' en dan duurt het bijna 2 seconden voordat hij daadwerkelijk een stap naar beneden gaat, maar intussen heb ik hierom nog eens gedrukt met als resultaat dat er dus 2 stappen naar beneden wordt gegaan.
Ergenis alom, een rot apparaat dus.
Als de vertraging niet is te voorkomen, zet dan eerst Moment... of zoiets op het scherm, dan weet ik dat mijn druk op de afstandsbediening is overgekomen.

Terwijl een apparaat op zich een goed apparaat kan zijn, kan slechte besturingssoftware er voor zorgen dat het apparaat door gebruikers een klote apparaat wordt gevonden.
Dus de eerste instructie die de PIC na een actie van de gebruiker moet doen is de gebruiker informeren (via LED's en/of display), en dan pas het commando uitvoeren dat is gevraagd.
Als ik dus bijvoorbeeld op PLAY druk, dan moet het programma eerst op het display PLAY zetten, en dan pas het apparaat aan het werk zetten, niet andersom.
Het is in sommige gevallen dus wel degelijk belangrijk in welke volgorde je de instructies zet.

 
Voti verkoopt HD44780 compatible LC-displays
Download HD44780 datasheet

 


In cursus deel 5 lezen we variabele weerstanden in, ondanks dat de PIC16F628A geen A/D Converter heeft.