Beginners mini-cursus (deel 6)

Voordat we verder gaan met het besturen van elektronica in deel 7, eerst weer een paar instructies die het programmaverloop kunnen bepalen.
Waarschijnlijk zul je je halverwege af gaan vragen wat deze instructies nu voor zin hebben voor het aansturen van elektronica, maar dat zal in de volgende cursusdelen duidelijker worden.
Het is van belang dat je dan al wat van deze instructies af weet, even doornemen dus.

 

FOR ... NEXT ... STEP Een lus met ingebouwde teller.
SELECT CASE Beslissingen nemen.
GOSUB ... RETURN Subroutines.
Array's Tabel variabelen.

 


FOR ... NEXT ... STEP

Een lus met ingebouwde teller

 

De syntaxis
FOR Variabele = Beginwaarde TO Eindwaarde {STEP Stapgrootte} 
   
{Programmacode met uit te voeren instructie(s) {BREAK}}
NEXT

De FOR ... NEXT loop (= lus) wordt gebruikt om één of meerdere instructies die binnenin deze lus staan een opgegeven aantal keer uit te voeren.
Zolang de lus-teller Variabele de Eindwaarde nog niet heeft gepasseerd worden de instructies die tussen FOR en NEXT staan herhaald.
De lus-teller wordt automatisch verhoogt (of verlaagt).

Variabele - Is de lus-teller, het is de variabele die de tellerstand bijhoudt.
De variabele zelf kan ook gebruikt worden in de programmacode die in de lus staat.
Beginwaarde - Is de waarde waarmee de lus-teller begint te tellen.
Dit hoeft geen vast getal (constante) te zijn, maar mag ook de waarde van een variabele of een berekening zijn (zie zodadelijk voorbeelden).
Eindwaarde - Is de waarde waarmee de lus-teller (en daarmee de lus) wordt beëindigt.
Dit hoeft geen vast getal (constante) te zijn, maar mag ook de waarde van een variabele of een berekening zijn (zie zodadelijk voorbeelden).
Tijdens uitvoering van de lus mag EindWaarde worden gewijzigd.
Stapgrootte - Is een optionele constante of variabele.
Hiermee kun je de stapgrootte instellen en tevens of er omhoog of omlaag (door opgave van een negatief getal) geteld moet worden.
Bij terugtellen moet Eindwaarde een kleiner getal zijn dan Beginwaarde.
Tijdens uitvoering van de lus mag Stapgrootte worden gewijzigd.


Tot nu toe hebben we steeds lussen gemaakt met WHILE ... WEND en REPEAT ... UNTIL.
Bij deze 2 instructies loopt normaal geen teller mee, die hebben we waar dat nodig was steeds zelf gemaakt door een variabele mee te laten tellen en deze te verhogen met INC of te verlagen met DEC (zie cursus deel 1).

Aangezien het regelmatig voorkomt dat er een teller mee moet lopen om een lus maar een bepaalt aantal keer uit te laten voeren, heeft de FOR ... NEXT lus een teller van zichzelf en hoef je geen INC of DEC op te geven.
Aan het begin van de lus geef je op met welke waarden de lus-teller moet beginnen en beëindigen, en welke variabele de tellerstand bij moet gaan houden.

De eerste paar voorbeelden zijn geen complete voorbeelden, alleen even goed bestuderen dus.
Achter elke programmaregel staat in de REM regel wat deze regel doet.
De werking is vrij eenvoudig te zien.
Hieronder een lus die tien keer uitgevoerd wordt.

DIM PietJanHein AS BYTE       ;Variabele aanmaken (BYTE kan tellen van 0 t/m 255)

FOR PietJanHein = 1 TO 10     ;Tel van 1 tot en met 10 met variabele 'PietJanHein'
  CLS                         ;Wis display
  PRINT @PietJanHein          ;Zet de waarde van 'PietJanHein' op het display (tellerstand)
  DELAYMS 1000                ;Een seconde wachten, anders is 1 t/m 10 in een oogwenk voorbij 
NEXT                          ;Verhoog tellervariabele 'PietJanHein' en dan terug naar FOR

PRINT AT 2, 1, "Klaar!"       ;Zet "Klaar!" op displayregel 2

Achter FOR (= voor) zet je de variabelenaam die de tellerstand bijhoudt.
Dat kan elke naam zijn, hierboven 'PietJanHein', maar straks natuurlijk logisch klinkende namen zoals bijvoorbeeld 'Teller'.

Na de variabelenaam een "is gelijk aan" teken ( = ) met daarachter de Beginwaarde, in het voorbeeld hierboven een 1 en daar weer achter keyword TO (= tot).
Nu moet achter TO nog een Eindwaarde opgegeven worden die de lus stopt als de Variabele deze waarde overschrijdt.
Aangezien we met 1 beginnen te tellen en tien keer de lus willen doorlopen moet de Eindwaarde dus op 10 worden gezet.
Keyword TO betekent tot, maar je moet het lezen als tot en met, want als de Variabele 10 is, wordt de lus ook nog uitgevoerd.

De eerste instructie in deze FOR ... NEXT lus is CLS, dus het display wissen, daarna wordt met PRINT de decimale waarde (door het @ teken, zie cursus deel 4) van tellervariabele 'PietJanHein' op het display geplaatst en dan nog 1 seconde gewacht, zodat alles goed is te volgen.
Dan komt het programma bij NEXT, daar wordt de variabele 'PietJanHein' met 1 verhoogt en vervolgens terug naar FOR gesprongen.
Daar wordt bekeken of de variabele nog in het opgegeven bereik ligt (1 t/m 10 in ons voorbeeld) en als dat het geval is, wordt de lus opnieuw uitgevoerd.
Is 'PietJanHein' na 10 keer de lus uitgevoerd te hebben bij het opnieuw verhogen 11 geworden, dan ligt de waarde van deze variabele niet meer in het opgegeven bereik en zal nu verder worden gegaan met de instructie die direct na NEXT komt, in dit voorbeeld Klaar! op de tweede displayregel plaatsen.

Let er hier ook op dat de tellervariabele, die je achter FOR zet, groot genoeg is gedeclareerd (met DIM).
Als je bijvoorbeeld een BYTE variabele opgeeft, terwijl de teller moet gaan lopen van 1 TO 400, dan kun je nagaan dat dat verkeerd af gaat lopen want een BYTE kan maar een tellerstand van maximaal 255 onthouden.
De FOR ... NEXT lus zal maar t/m 255 tellen en dan al stoppen (logisch, hij kan niet verder tellen), dus niet doorlopen tot aan 400.

Wil je verder tellen dan 255 dan zul je de tellervariabele als WORD moeten declareren.
Dit houdt wel in dat de PIC twee keer zoveel geheugen voor de tellerstand gebruikt.
Met een WORD variabele kun je tot en met 65535 tellen.
Mocht dat nóg niet genoeg zijn, dan de variabele als DWORD (= Dubbel WORD) declareren.


In onderstaand voorbeeld wordt geteld van 8 t/m 20.
Als de lus-teller 'Teller' tot aan 12 heeft geteld wordt de LED aangezet.

DIM Teller AS BYTE            ;Variabele aanmaken (BYTE kan tellen van 0 t/m 255)

FOR Teller = 8 TO 20          ;Tel van 8 tot en met 20
  CLS                         ;Wis display
  PRINT @Teller               ;Zet de waarde van 'Teller' op het display (tellerstand) 

  IF Teller = 12 THEN LED = AAN ;Bij tellerstand 12 de LED aanzetten
  DELAYMS 1000                ;Een seconde wachten, anders gaat alles zo snel
NEXT                          ;'Teller' verhogen en dan terug naar FOR

De FOR ... NEXT lus heeft een paar opties die gebruikt kunnen worden, namelijk BREAK en STEP.


STEP
Met de optie STEP (= stap grootte) kun je aangeven met welke waarde de teller verhoogt moet worden.
Laat je deze optie weg, zoals in de eerste paar voorbeelden, dan telt de teller standaard omhoog met 1 stapje per keer.
Onderstaand voorbeeld telt van 0 t/m 200, maar doet dat met stappen van 25.

DIM Teller AS BYTE            ;Variabele aanmaken (BYTE kan tellen van 0 t/m 255)

FOR Teller = 0 TO 200 STEP 25 ;Tel van 0 naar 200 met stappen van 25
  CLS                         ;Wis display
  PRINT @Teller               ;Zet de waarde van 'Teller' op het display (tellerstand) 
  DELAYMS 1000                ;Een seconde wachten, anders gaat alles zo snel
NEXT                          ;'Teller' verhogen en dan terug naar FOR

De lus zal 9 keer uitgevoerd worden en de teller op het display zal achtereenvolgens aangeven:
0, 25, 50, 75, 100, 125, 150, 175, 200.


Omlaag tellen kan ook.
Ook hiervoor heb je de optie STEP nodig.

DIM Teller AS BYTE            ;Variabele aanmaken (BYTE kan tellen van 0 t/m 255)

FOR Teller = 50 TO 20 STEP -10 ;Tel met stappen van 10 terug van 50 naar 20
  CLS                         ;Wis display
  PRINT @Teller               ;Zet de waarde van 'Teller' op het display (tellerstand) 
  DELAYMS 1000                ;Een seconde wachten, anders gaat alles zo snel
NEXT                          ;'Teller' verlagen en dan terug naar FOR

Door een negatief getal achter STEP op te geven zal de lus-teller terug tellen.
Wel even opletten dat de Eindwaarde nu kleiner moet zijn dan de Beginwaarde.
In bovenstaand voorbeeld zal de lus 4 keer doorlopen worden en het display na elkaar 50, 40, 30, 20 aangeven.
Denk er aan dat het minus streepje strak aan het getal zit, dus -10 is goed, maar - 10 niet.


Voor de Beginwaarde en de Eindwaarde mogen ook constanten, variabelen en berekeningen worden gebruikt.
In onderstaand programma worden twee 25k potmeters ingelezen en de instellingen hiervan in de variabelen Potmeter_1 en Potmeter_2 opgeslagen.
De waarde van Schaal van de functie POT is hier ingesteld op 127, eventueel zelf wijzigen (zie cursus deel 5).

(Vanaf hier zijn de programma's weer volledig weergegeven).

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

;Variabelen declareren
DIM Potmeter_1    AS BYTE     ;Potmeter van maximum motorsnelheid
DIM Potmeter_2    AS BYTE     ;Potmeter van stijging van de motorsnelheid
DIM Snelheid      AS BYTE     ;'Snelheid' staat voor de snelheid van de motor

Potmeter_1 = POT PORTA.0, 127 ;Lees potmeter 1 in en zet waarde in Potmeter_1

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
CLS                           ;Wis eventuele tekst op het display uit

FOR Snelheid = 0 TO Potmeter_1 STEP Potmeter_2 ;Tel met 'Snelheid'
  PRINT AT 1, 1, "Snelheid = ", DEC3 Snelheid

  ;Hier komt later de motorbesturing (cursus deel 7)

  DELAYMS 500                 ;Motorsnelheid langzaam opbouwen
  Potmeter_2 = (POT PORTA.1, 127) / 10 ;Lees potmeter 2 in en zet waarde in Potmeter_2 
NEXT                          ;'Snelheid' verhogen en dan terug naar FOR

END                           ;Einde programma

In dit programma is de Eindwaarde (dus tot hoever de lus gaat tellen) afhankelijk van de instelling van de potmeter die is aangesloten op PORTA.0.
De instellingswaarde van die potmeter wordt alleen tijdens het opstarten van de PIC ingelezen (in de variabele 'Potmeter_1'), daarna niet meer, want de potmeter staat vóór de lus.
Dat betekent dat als de waarde eenmaal ingelezen is, de potmeter daarna nooit meer wordt bekeken.
Je kunt dan ook aan de potmeter draaien wat je wilt, de waarde van de variabele Potmeter_1 wijzigt niet.

Dat geldt niet voor die andere potmeter die de Stapgrootte van STEP bepaalt.
Het uitlezen van deze potmeter gebeurt in de lus, dus iedere keer als de lus wordt uitgevoerd, zal de actuele potmeterwaarde weer worden ingelezen.
De eerste keer wordt er nog niet naar de Eindwaarde en de Stapgrootte gekeken.
Deze hoeven dus nog geen juiste waarde te hebben als de lus wordt gestart, maar mogen ook pas binnenin de lus hun waarde krijgen.
De waarde van 'Potmeter_2' wordt ook nog eens door 10 gedeeld.

In het volgende deel van deze cursus gaan we motoren aansturen met variabele snelheid.
Het bovenstaande voorbeeld zal dan opnieuw worden behandeld, maar dan met de PWM instructie in de FOR ... NEXT lus.
De bedoeling zal zijn dat als er spanning op de PIC wordt gezet, dat de motor vanaf stilstand (hier de 0 van Beginwaarde van de FOR ... NEXT lus) langzaam steeds sneller begint te draaien tot het maximum afhankelijk van de waarde van Eindwaarde.
Hóe snel de motor optrekt, is afhankelijk van de waarde achter STEP, de instelling van Potmeter_2 dus.
(In cursus deel 7 zal het beter uitgelegd worden).


De STEP waarde mag een constante of variabele, maar geen berekening zijn.

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

;Constanten
SYMBOL BeginWaarde = 0        ;De beginwaarde van de lus-teller van de FOR-NEXT lus
SYMBOL EindWaarde  = 50       ;De eindwaarde  van de lus-teller van de FOR-NEXT lus

;Variabelen
DIM StapGrootte   AS BYTE     ;De stapgrootte van de lus-teller van de FOR-NEXT lus
DIM Teller        AS BYTE     ;Variabele aanmaken (BYTE kan tellen van 0 t/m 255)

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
StapGrootte = 4               ;Startwaarde van de stapgrootte van de FOR-NEXT lus
FOR Teller = BeginWaarde TO EindWaarde STEP StapGrootte ;Tel volgens opgegeven waarden
  CLS                         ;Wis display
  PRINT @Teller               ;Zet de waarde van 'Teller' op het display (tellerstand) 

  INC StapGrootte             ;Verhoog de variabele StapGrootte met 1

  DELAYMS 1000                ;Een seconde wachten, anders gaat alles zo snel
NEXT                          ;'Teller' verhogen en dan terug naar FOR

END                           ;Einde programma

De Beginwaarde en de Eindwaarde hebben hier de symbolische namen 'BeginWaarde' en 'EindWaarde' gekregen.
De Stapgrootte met de naam 'StapGrootte' is hier een variabele, omdat we in de lus, de stapgrootte steeds groter maken met INC.
Deze lus zal 7 keer uitgevoerd worden, starten bij 0 (de Beginwaarde) en stoppen als de lus-teller groter wordt dan 50 (de Eindwaarde).
Het display zal achtereenvolgens aangeven 0, 5, 11, 18, 26, 35, 45, omdat de stapgrootte steeds groter wordt (+5, +6, +7, +8, +9 en +10).


BREAK
Je kunt een FOR ... NEXT lus net als bij de WHILE ... WEND en de REPEAT ... UNTIL lussen, voortijdig verlaten met de opdracht BREAK.

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

;Logische constanten
SYMBOL LAAG = 0

;Poortnamen
SYMBOL S1   = PORTB.0         ;Met S1 is de FOR-NEXT lus voortijdig af te breken

;Variabele declareren
DIM Teller AS BYTE            ;Variabele aanmaken (BYTE kan tellen van 0 t/m 255)

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


;Hoofdprogramma
FOR Teller = 0 TO 50          ;Tel van 0 naar 50
  CLS                         ;Wis display
  PRINT @Teller               ;Zet de waarde van 'Teller' op het display (tellerstand)

  IF S1 = LAAG THEN BREAK     ;Als S1 (PORTB.0) laag is dan uit de FOR-NEXT lus springen 
  DELAYMS 1000                ;Een seconde wachten, anders gaat alles zo snel
NEXT                          ;'Teller' verhogen en dan terug naar FOR

PRINT AT 2, 1, "Gestopt op ", @Teller ;Zet stand toen PORTB laag werd op regel 2

END                           ;Einde programma

Deze lus telt van 0 naar 50 en doet daar zo'n 51 seconden over (vanwege die seconde wachttijd die in de lus staat).
Daarna staat er Gestopt op 51, niet 50, want NEXT verhoogt eerst de lus-teller, springt terug naar FOR en kijkt dán pas of de lus-teller groter is dan Eindwaarde.
Als de lus-teller bij NEXT naar 50 is verhoogt, wordt de lus dus nog één keer doorlopen.
Daarna verhoogt NEXT de lus-teller weer (nu is hij dus 51 geworden).
Nu blijkt dat de lus-teller groter is dan de Eindwaarde en wordt met de eerste instructie na NEXT verder gegaan.

Echter, als PORTB.0 laag wordt gemaakt, wordt ongeacht de waarde van de lus-teller, uit de lus gesprongen door de instructie BREAK en verder gegaan met de eerste instructie na NEXT, en dat is PRINT AT 2, 1, "Gestopt op ", @Teller.

Let wel even op dat S1 even wordt vastgehouden, omdat het programma de meeste tijd in de DELAYMS tijd zit te wachten en op dat moment dus niet naar de PORTB.0 kijkt waar S1 op zit aangesloten.
Het direct uit de lus springen als er ook maar heel kort op S1 wordt gedrukt komt later in deze cursus aan bod.
Verderop staat een manier waarop het bijvoorbeeld zou kunnen.


Nesten
Alle lussen kun je nesten.
Nesten betekent dat er één (of meer) lussen in een andere lus kan staan.
Dit is eerder al gedaan met de WHILE ... WEND lus en de REPEAT ... UNTIL lus.
Zo ook mogelijk bij de FOR ... NEXT lus:

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

SYMBOL Vertraging = 300       ;Vertraging van de binnenste lussen

DIM Teller1       AS BYTE     ;'Teller1' is de lus-teller van de buitenste lus
DIM Teller2       AS BYTE     ;'Teller2' is de lus-teller van de binnenste lus

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
CLS                           ;Wis display
FOR Teller1 = 1 TO 5          ;Tel met 'Teller1' van 1 naar 5
  FOR Teller2 = 0 TO 15       ;Tel met 'Teller2' van 0 naar 15
    PRINT AT 1, 1, DEC2 Teller1 ;Zet de waarde van 'Teller1' op het display (tellerstand)
    PRINT AT 2, 1, DEC2 Teller2 ;Zet de waarde van 'Teller2' op het display (tellerstand)
    DELAYMS Vertraging
  NEXT                          ;'Teller2' verhogen en dan terug naar bijbehorende FOR

  FOR Teller2 = 15 TO 0 STEP -1 ;Tel met 'Teller2' terug van 15 naar 0
    PRINT AT 1, 1, DEC2 Teller1 ;Zet de waarde van 'Teller1' op het display (tellerstand)
    PRINT AT 2, 1, DEC2 Teller2 ;Zet de waarde van 'Teller2' op het display (tellerstand) 
    DELAYMS Vertraging
  NEXT                        ;'Teller2' verlagen en dan terug naar bijbehorende FOR
NEXT                          ;'Teller1' verhogen en dan terug naar bijbehorende FOR 

PRINT AT 1, 1, "Teller1 = ", @Teller1 ;Zet eindstand van 'Teller1' op displayregel 1
PRINT AT 2, 1, "Teller2 = ", @Teller2 ;Zet eindstand van 'Teller2' op displayregel 2

END                           ;Einde programma

Bovenstaand programma telt met 'Teller2' van 0 naar 15 en dan weer terug naar 0, en dat doet het programma 5 keer.
Als je goed kijkt kun je al zien hoe dat werkt.

De buitenste FOR ... NEXT lus telt met 'Teller1' van 1 t/m 5.
In deze lus staan twee andere FOR ... NEXT lussen, die allebei tellen met lus-teller 'Teller2'.
Dat het mogelijk is dat beide binnenste lussen allebei met dezelfde variabele tellen ('Teller2'), komt omdat deze twee lussen nooit tegelijk aan het tellen zijn.
Als de eerste (binnenste) lus klaar is met tellen, dan is variabele 'Teller2' weer vrij voor gebruik.
Dat doen we dan ook in de volgende lus (de lus die van 15 naar 0 telt).
De buitenste lus kan niet met deze variabele tellen want deze houdt bij hoe vaak de binnenste lussen uitgevoerd zijn.
Als de binnenste lussen met 'Teller2' aan het tellen zijn, houdt 'Teller1' de stand van de buitenste lus bij.
Het is dus niet mogelijk om dezelfde variabele te gebruiken om meerdere lussen te tellen die binnen elkaar liggen.
Bij lussen die na elkaar liggen kan dat wel.

Als een FOR ... NEXT lus is uitgeteld dan is de huidige waarde van de lus-teller altijd 1 hoger dan de Eindwaarde (bij omhoog tellen).
Dit kun je zien aan de weergegeven eindstand op het display, nadat beide tellers zijn uitgeteld:
Teller1 = 6
Teller2 = 255

'Teller1' is bij het verlaten van de lus dus 6 want de lus moest stoppen als 'Teller1' groter was geworden dan 5.
'Teller2' is 255 omdat terug moest worden geteld tot 0, en 0 - 1 = -1.
Aangezien we hier met een BYTE variabele tellen en deze alleen een waarde kan bevatten van 0 t/m 255, is 0 - 1 = 255.
Zo is 5 - 4 = 1, 5 - 5 = 0, 5 - 6 = 255 en 5 - 7 = 254, enzovoort.

Hier zie je nu ook waarom dat inspringen van de programmatekst met 2 spaties (of TAB) zo belangrijk is.
Als je dat niet doet dan is niet snel te zien waar het begin en het eind van een lus is.
Ook zie je zo makkelijker welke NEXT bij welke FOR hoort.

 


SELECT ... CASE ... ENDSELECT

Beslissingen nemen.

Een aanverwant commando van IF...THEN...ELSEIF...ELSE...ENDIF is SELECT ... CASE ... ENDSELECT.
Dit commando doet bijna hetzelfde als ELSEIF, maar werkt overzichtelijker.
De SELECT CASE instructie is een soort multiple-choise (= meer keuze).

SELECT bekijkt wat de waarde is van de constante, variabele of berekening die er achter staat.
Daarna gaat hij deze waarde vergelijken met de testwaarden in de navolgende lijst met CASE's.
Zodra er een CASE testwaarde wordt tegengekomen die waar (= TRUE) is, wordt het instructieblok uitgevoerd die bij de desbetreffende CASE hoort.
De overige CASE blokken worden niet meer bekeken.
Wanneer geen enkele CASE waar is, wordt CASE ELSE uitgevoerd.
Als er geen CASE ELSE is opgegeven dan wordt verder gegaan met de instructie die onder ENDSELECT staat.

De volgende paar (onvolledige) voorbeelden zullen overeenkomsten en verschillen tussen SELECT CASE en IF ... THEN ... ELSE laten zien.
Als er aan de potmeter wordt gedraaid levert 'Weerstand' een waarde op van 0 ... 9 (omdat voor Schaal in de functie POT de waarde 5 is opgegeven, zie cursus deel 5).

Eerst hoe dit gaat met IF ... THEN ... ELSE:

Weerstand = POT PORTA.1, 5    ;Geef gemeten potmeterinstelling aan 'Weerstand' 

IF Weerstand = 4 THEN         ;Als 'Weerstand' gelijk is aan 4 dan...
  PRINT "Vier"                ;..."Vier" op het display zetten
ELSEIF Weerstand = 5 THEN     ;anders, als 'Weerstand gelijk is aan 5 dan...
  PRINT "Vijf"                ;..."Vijf" op het display zetten
ELSEIF Weerstand = 6 THEN     ;anders, als 'Weerstand gelijk is aan 6 dan...
  PRINT "Zes"                 ;..."Zes" op het display zetten
ELSE                          ;anders (in alle andere gevallen)...
  PRINT "Andere waarde"       ;..."Andere waarde" op het display zetten
ENDIF                         ;Einde IF...THEN...ELSE blok

PRINT AT 2, 1, "Klaar!"       ;Zet "Klaar!" op displayregel 2

Als de potmeterinstelling een waarde oplevert van 4, 5 of 6 dan wordt dit op displayregel 1 in tekstvorm weergegeven (Vier, Vijf of Zes).
Heeft de potmeter een andere waarde (0 ... 3 of 7 ... 9) dan komt er Andere waarde op het display te staan.
Op regel 2 van het display staat Klaar!.

Bij ELSEIF moet steeds opnieuw de variabele 'Weerstand' worden opgegeven, bij SELECT CASE geef je maar één maal de variabele 'Weerstand' op.
Hieronder is het programma herschreven maar nu met de SELECT CASE instructie, het resultaat is precies hetzelfde.

Weerstand = POT PORTA.1, 5    ;Geef gemeten potmeterinstelling aan 'Weerstand' 

SELECT Weerstand              ;We gaan de variabele 'Weerstand' testen
  CASE 4                      ;Als 'Weerstand' is 4 dan...
    PRINT "Vier"              ;..."Vier" op het display zetten
  CASE 5                      ;Als 'Weerstand' is 5 dan...
    PRINT "Vijf"              ;..."Vijf" op het display zetten
  CASE 6                      ;Als 'Weerstand' is 6 dan...
    PRINT "Zes"               ;..."Zes"  op het display zetten 
  CASE ELSE                   ;Anders...
    PRINT "Andere waarde"     ;..."Andere waarde" op het display zetten
ENDSELECT                     ;Einde SELECT CASE blok

PRINT AT 2, 1, "Klaar!"       ;Zet "Klaar!" op displayregel 2

Eerst selecteer je de variabele die vergeleken (= getest) moet worden.
Dat doe je door achter SELECT de variabelenaam op te geven.

Achter elke CASE (= in het geval) worden nu steeds vergelijkingen uitgevoerd.
CASE 4 betekent dus, in het geval dat de opgegeven variabele 'Weerstand' gelijk is aan 4 dan de daarop volgende instructie(s) uitvoeren, in het voorbeeld dus PRINT "Vier".
Het is duidelijk dat de instructie(s) onder CASE 5 worden uitgevoerd als de variabele 'Weerstand' de waarde 5 heeft en voor CASE 6 als die waarde 6 is.

Instructies tussen CASE ELSE en ENDSELECT worden alleen uitgevoerd als geen van de andere CASE selecties een positief resultaat heeft opgeleverd.
Een CASE ELSE moet daarom altijd als laatste CASE in het blok staan.

Het is overigens niet verplicht om een CASE ELSE te plaatsen.
Stel dat in bovenstaand voorbeeld géén CASE ELSE is geplaatst en 'Weerstand' de waarde 2 heeft, dan wordt er niets van het blok uitgevoerd.
Het programma zal meteen verdergaan met de eerste instructie die na ENDSELECT komt.
In bovenstaand voorbeeld is dat PRINT AT 2, 1, "Klaar!".
Het resultaat is dat dan alleen op displayregel 2 Klaar! komt te staan, regel 1 blijft leeg.


Niet alle beslissingen kunnen hiermee uitgevoerd worden.
Het volgende bijvoorbeeld kan niet met SELECT CASE worden uitgevoerd.

NTC       = POT PORTA.0, 255  ;Geef gemeten temperatuur aan variabele 'NTC'
Weerstand = POT PORTA.1, 127  ;Geef gemeten potmeterinstelling aan 'Weerstand' 

IF Weerstand = 0 AND NTC = 0 THEN
  PRINT "Beide zijn nul"
ELSEIF Weerstand = 0 THEN
  PRINT "Weerstand is nul"
ELSEIF NTC = 0 THEN
  PRINT "NTC is nul"
ELSE
  PRINT "Geen van beide nul"
ENDIF

PRINT AT 2, 1, "Klaar!"

Hier worden twee variabelen getest, namelijk 'Weerstand' en 'NTC'.
Aangezien SELECT CASE maar één variabele kan testen moet hier wel gebruik worden gemaakt van de IF ... THEN ... ELSEIF ... ELSE instructie.


Om op meerdere waarden te kunnen testen moet je deze waarden scheiden door komma's.
In onderstaand programmavoorbeeld zal als 'Teller' de waarde 4, 6, 8 of 10 heeft, Even waarde op het display worden weergegeven.
Schreef je eerst: IF Teller = 4 OR Teller = 6 OR Teller = 8 OR Teller = 10 THEN...
dan schrijf je nu simpel CASE 4, 6, 8, 10.
We gebruiken de zojuist beschreven FOR ... NEXT lus als teller.

(Vanaf hier zijn de programma's weer volledig weergegeven).

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

DIM Teller AS BYTE            ;Deze variabele wordt gebruikt als teller in de FOR-NEXT lus

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
FOR Teller = 0 TO 15          ;Tel met de variabele 'Teller' van 0 t/m 15

  CLS                         ;Wis scherm
  PRINT DEC Teller            ;Zet de stand van 'Teller' op het display
  CURSOR 2, 1                 ;Zet cursor alvast vooraan op displayregel 2

  SELECT Teller               ;We gaan de variabele 'Teller' testen
    CASE 3, 5, 7, 9           ;Als 'Teller' 1 van deze waarden heeft dan...
      PRINT "Oneven waarde"   ;...tekst op het display zetten

    CASE 4, 6, 8, 10          ;Als 'Teller' 1 van deze waarden heeft dan...
      PRINT "Even waarde"     ;...tekst op het display zetten

    CASE < 3                  ;Als 'Teller' kleiner is dan 3, dan...
      PRINT "Kleiner dan drie";...tekst op het display zetten

    CASE > 10                 ;Als 'Teller' groter is dan 10, dan...
      PRINT "Groter dan tien" ;...tekst op het display zetten

  ENDSELECT

  DELAYMS 1000                ;Om de seconde tellen, anders gaat het zo snel
NEXT                          ;'Teller' verhogen en dan terug naar FOR

CLS
PRINT AT 2, 1, "Klaar!"       ;Zet "Klaar!" op displayregel 2

END                           ;Einde programma

Er bestaan een paar mogelijkheden om een heel bereik te selecteren.
Om een bereik van 0 ... 20 op te geven kun je schrijven CASE <= 20 of CASE < 21.
< ' betekent kleiner dan en ' <= ' betekent kleiner dan of gelijk aan het getal dat er achter staat.
Let op, <= is goed, maar =< geeft een foutmelding.

Om een bereik van 5 t/m 33 op te geven kun je schrijven CASE 5 TO 33.
Wel altijd het laagste getal vooraan zetten, CASE 33 TO 5 werkt dus niet.
In deze vorm kan er maar één bereik per CASE getest worden.
CASE < 10, 12, 14, > 20 is dus (gescheiden door komma's) toegestaan.
Maar CASE < 10, 12 TO 15, > 20 is niet toegestaan en CASE 5 TO 8, 10 TO 15 is ook niet toegestaan.


Stel je hebt voor één of ander project een puls die steeds 3 uitgangssignalen moet opleveren die bij elke nieuwe puls een nieuwe onlogische, maar wel vastgestelde uitgangsituatie moet opleveren en na 16 pulsen moet dezelfde combinatievolgorde weer van voren af aan beginnen.
Dit alles volgens onderstaande tabel:

Teller 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
LED1 L H H H H H H H L H L L H L H H
LED2 L L L L L H L H H L H H H L H H
LED3 L H L H H H L H L L L L H H L H

H = Hoog (LED brandt)

 

Met S1 geven we een puls op PORTB.0 en de LED's laten het niveau (hoog of laag) van de uitgangen zien.
Op het display staat de variabele 'Teller' die steeds met 1 wordt verhoogt als je op S1 drukt.
Neem onderstaand programma over in de PIC Basic editor (eventueel met behulp van Copy en Paste).

DEVICE 16F628A                ;We gebruiken 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

;Poortnamen
SYMBOL LED1 = PORTA.0         ;Op poort A.0 zit LED1 aangesloten
SYMBOL LED2 = PORTA.1         ;Op poort A.1 zit LED2 aangesloten
SYMBOL LED3 = PORTA.2         ;Op poort A.2 zit LED3 aangesloten
SYMBOL S1   = PORTB.0         ;S1 verhoogt de teller

;Variabele declareren
DIM Teller  AS BYTE           ;Byte variabele met naam 'Teller'

;        76543210
PORTA = %00000000             ;Alle PORTA uitgangen uitzetten (laag maken)
TRISA = %11111000             ;PORTA.2, A.1 en A.0 omschakelen als uitgang voor de LED's 

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


;Hoofdprogramma
CLS                           ;Wis display
PRINT "Druk op S1"            ;Plaats tekst op display

WHILE 1 = 1                   ;Oneindige lus
  WHILE S1 = UIT : WEND       ;Wacht tot S1 wordt ingedrukt

  ;Eerst vorige stand van 3 LED's uitzetten
  LED1 = OFF                  ;LED1 uit
  LED2 = OFF                  ;LED2 uit
  LED3 = OFF                  ;LED3 uit

  SELECT Teller               ;Variabele 'Teller' wordt getest
    CASE > 15                 ;Als Teller boven de 15 komt dan...
      CLEAR Teller            ;Reset 'Teller' (Weer bij 0 beginnen)

    CASE 2, 6, 9              ;Als Teller 2, 6 of 9 is dan...
      LED1 = ON               ;LED1 aan

    CASE 8 TO 11              ;Als Teller 8 t/m 11 is dan...
      LED2 = ON               ;LED2 aan

    CASE 13                   ;Als Teller = 13 dan...
      LED3 = ON               ;LED3 aan

    CASE 14                   ;Als Teller = 14 dan...
      LED1 = ON               ;LED1 aan
      LED2 = ON               ;LED2 aan

    CASE < 5                  ;Als Teller kleiner is dan 5 dan...
      LED1 = ON               ;LED1 aan
      LED3 = ON               ;LED3 aan

    CASE ELSE                 ;In alle overige gevallen...
      LED1 = ON               ;LED1 aan
      LED2 = ON               ;LED2 aan
      LED3 = ON               ;LED3 aan

  ENDSELECT                   ;Einde blok SELECT...CASE

  CLS                         ;Wis display en zet cursor op 1, 1
  PRINT "Teller: ", DEC Teller;Zet tellerstand op het display

  WHILE S1 = AAN : WEND       ;Wacht tot toets is losgelaten
  DELAYMS 10                  ;Vertraging tegen contactdender

  INC Teller                  ;Verhoog 'Teller' met 1

WEND

Ter controle moeten de volgende LED's branden:

Teller LED1 LED2 LED3 CASE
0   L L L >15
1   H L H <5
2   H L L 2, 6, 9
3   H L H <5
4   H L H <5
5   H H H ELSE
6   H L L 2, 6, 9
7   H H H ELSE
8   L H L 8 TO 11
9   H L L 2, 6, 9
10   L H L 8 TO 11
11   L H L 8 TO 11
12   H H H ELSE
13   L L H 13
14   H H L 14
15   H H H ELSE

H = LED brandt

De meest rechtse kolom geeft aan door welke CASE de uitgangen zijn geactiveerd.
SELECT CASE wordt van boven naar beneden uitgevoerd, als hij een CASE tegenkomt die aan de voorwaarde voldoet, dan worden de opdrachten die in dat blok staan uitgevoerd.
Daarna wordt verder gegaan met de instructie die na ENDSELECT komt.
Er wordt dus altijd maar 1 blok uitgevoerd, ook als er meer CASE gevallen zijn die aan de voorwaarde voldoen, zoals in bovenstaand voorbeeld; als 'Teller' = 2 dan is zowel CASE 2, 6, 9 waar (= TRUE) als ook CASE < 5.
Maar omdat CASE 2, 6, 9 hoger in de lijst staat, heeft deze een hogere prioriteit en worden de opdrachten van die CASE uitgevoerd en niet die van CASE < 5, ook al is 2 kleiner dan 5.

Datzelfde geldt als 'Teller' de waarde 9 heeft.
CASE 2, 6, 9 staat hoger in de lijst dan CASE 8 TO 11, daarom worden de instructies die onder CASE 2, 6, 9 staan wel uitgevoerd, maar de instructies van CASE 8 TO 11 niet.


Hieronder het voorbeeld uit cursus deel 5, meerdere pulsschakelaars op 1 ingang inlezen met RCIN.
Alleen is ELSEIF nu vervangen door SELECT CASE, het resultaat blijft precies hetzelfde, maar het Basic programmaoverzicht is beter.

Alle toetsen worden via 4k7 weerstanden (SIL-netwerk of SMD?) aangesloten op maar één poort (PORTA.1, met de naam 'Toets' in dit geval).
Met de functie RCIN is nu te meten welke toets is ingedrukt omdat elke toets zijn eigen weerstandswaarde heeft.
Het onderstaand voorbeeldprogramma doet het volgende:

Toets 1: Beide LED's uitzetten.
Toets 2: Toggle de groene LED.
Toets 3: Toggle de rode LED.
Toets 4: Beide LED's aanzetten.
Toets 5: De groene LED aanzetten.
Toets 6: De rode LED aanzetten.
SIL weerstands netwerk

Het uitfilteren van de waarde van de variabele 'Weerstand' gebeurt door steeds de waarde die bij elke CASE is opgegeven te vergelijken met de waarde van 'Weerstand' en zo de lijst van boven naar beneden af te werken totdat een bewering wordt gevonden die waar is.

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

;Poortnamen
SYMBOL Toets        = PORTA.1 ;Toetsen zitten aan PORTA.1 via een weerstandsnetwerk
SYMBOL LED_Groen    = PORTA.2 ;De groene LED zit op PORTA.2 aangesloten
SYMBOL LED_Rood     = PORTA.3 ;De rode LED zit op PORTA.3 aangesloten

;Variabele declareren
DIM Weerstand AS WORD         ;WORD variabele, bevat straks waarden van de toetsindrukken 

;        76543210
TRISA = %11110011             ;PORTA.3 en PORTA.2 omschakelen als uitgang voor de LED's

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering


;Hoofdprogramma
CLS                           ;LCD scherm wissen

WHILE 1 = 1                   ;Oneindig door blijven meten
  IF Toets = AAN THEN         ;Als er op een toets wordt gedrukt dan...
    PRINT AT 1, 1, "Toets "   ;Zet het woordje 'Toets' alvast op de eerste display regel

    HIGH Toets                ;Condensator ontladen
    DELAYMS 1                 ;Even wachten zodat condensator helemaal leeg is
    Weerstand = RCIN Toets, HIGH ;Geef RC oplaadtijd aan WORD variabele 'Weerstand'

    SELECT Weerstand          ;We gaan de waarde van de variabele 'Weerstand' testen
      CASE < 31               ;Toets 1 ingedrukt dan...
        LED_Groen = OFF       ;...beide LED's uit
        LED_Rood  = OFF
        PRINT "1"             ;Zet het toetsnummer achter 'Toets' op het display

      CASE < 94               ;Toets 2 ingedrukt dan...
        LED_Groen = LED_Groen ^ 1;...toggle de groene LED
        PRINT "2"             ;Zet het toetsnummer achter 'Toets' op het display

      CASE < 159              ;Toets 3 ingedrukt dan...
        LED_Rood  = LED_Rood ^ 1 ;...toggle de rode LED
        PRINT "3"             ;Zet het toetsnummer achter 'Toets' op het display

      CASE < 228              ;Toets 4 ingedrukt dan...
        LED_Groen = ON        ;...beide LED's aan
        LED_Rood  = ON
        PRINT "4"             ;Zet het toetsnummer achter 'Toets' op het display

      CASE < 300              ;Toets 5 ingedrukt dan...
        LED_Groen = ON        ;...de groene LED aanzetten
        PRINT "5"             ;Zet het toetsnummer achter 'Toets' op het display

      CASE < 999              ;Toets 6 ingedrukt dan...
        LED_Rood  = ON        ;...de rode LED aanzetten
        PRINT "6"             ;Zet het toetsnummer achter 'Toets' op het display

    ENDSELECT

    PRINT AT 2, 1, DEC5 Weerstand ;Zet waarde van 'Weerstand' op regel 2 van het display

    WHILE Toets = AAN : WEND  ;Wacht hier tot toets(en) wordt losgelaten
  ENDIF
WEND                          ;Terug naar WHILE

Is de opgemeten weerstand kleiner dan 31 (CASE < 31) ?
Zo ja, dan is het toets 1 die is ingedrukt en dan moeten de bijbehorende instructies uitgevoerd worden (LED_Groen en LED_Rood uitzetten en een 1 (achter Toets) op het display zetten).

Zoniet, dan op naar de volgende CASE.
Dit betekent dat 'Weerstand' in ieder geval 31 of groter is, want anders zou CASE < 31 zijn uitgevoerd.
Is de opgemeten weerstand misschien kleiner dan 94 (CASE < 94) ?
Zo ja, dan is het toets 2 die is ingedrukt en worden de bijbehorende instructies van deze toets uitgevoerd.

Zoniet, dan op naar de volgende CASE, enzovoort.

Meer info over het inlezen van meerdere pulsschakelaars op 1 ingangspoort (zie cursus deel 5).


Het is ook mogelijk om constanten, variabelen en zelfs berekeningen toe te passen.
In het voorbeeld zijn 2 constanten aangemaakt, namelijk 'Maximum', die constant de waarde 50 heeft, en 'Minimum' die symbool staat voor de waarde 10.

SYMBOL Maximum = 50           ;'Maximum' staat symbool voor 50
SYMBOL Minimum = 10           ;'Minimum' staat symbool voor 10

SELECT Weerstand              ;De variabele 'Weerstand' wordt hier getest
  CASE <= Minimum             ;Als 'Weerstand' kleiner of gelijk is aan 10 (Minimum) dan... 
    LED_Rood  = ON            ;...de rode LED aanzetten

  CASE (3 * Minimum)          ;Als 'Weerstand' = 30 (3 x 'Minimum') dan...
    LED_Geel  = ON            ;...de gele LED aanzetten

  CASE 75                     ;Als 'Weerstand' = 75 dan
    LED_Blauw = ON            ;...de blauwe LED aanzetten

  CASE >= Maximum             ;Als 'Weerstand' groter of gelijk is aan 50 (Maximum) dan...
    LED_Groen = ON            ;...de groene LED aanzetten

  CASE ELSE                   ;Bij de overige waarden van 'Weerstand'...
    LED_Wit   = ON            ;...de witte LED aanzetten en...
    DELAYMS 1000              ;...na 1 seconde...
    LED_Wit   = OFF           ;...de witte LED weer uitzetten

ENDSELECT                     ;Einde SELECT CASE blok

PRINT AT 2, 1, "Klaar!"       ;Zet "Klaar!" op displayregel 2

LED_Rood wordt hier aangezet als 'Weerstand' kleiner of gelijk is aan (<=) 10 (de waarde van 'Minimum') door CASE <= Minimum.

Het tweede geval is een CASE met een berekening erachter, namelijk CASE (3 * Minimum), LED_Geel wordt alleen aangezet als 'Weerstand' de waarde 30 heeft, want 'Minimum' staat symbool voor 10 en er staat dus eigenlijk de berekening 3 × 10, oftewel CASE 30.

Bij de derde CASE in het voorbeeld gaat LED_Blauw aan als 'Weerstand' de waarde 75 heeft.
De waarde 75 is weliswaar groter dan 'Maximum', maar omdat CASE 75 hoger in het rijtje staat dan CASE >= Maximum, wordt het daardoor eerder getest.
Er wordt immers maar één CASE per SELECT CASE blok uitgevoerd, en dat is de hoogst geplaatste CASE die "waar" (= TRUE) is, ook al staan er meerdere CASE gevallen in die waar zijn.
Stel dat CASE >= Maximum boven CASE 75 stond, dan wordt LED_Groen aangezet in plaats van LED_Blauw.
De volgorde van het rijtje CASE opsommingen kan dus in sommige gevallen wel degelijk verschil uitmaken!
Instructies met de hoogste prioriteit moeten dus bovenaan gezet worden.

LED_Groen wordt aangezet als 'Weerstand' groter of gelijk is aan (>=) 'Maximum' (CASE >= Maximum).
Als 'Weerstand' de waarde 50 (de opgegeven waarde van 'Maximum') of hoger heeft, dan brandt LED_Groen, behalve als deze waarde 75 is, want dan wordt het programma één CASE eerder al uitgevoerd, namelijk bij CASE 75, en daar wordt dus LED_Blauw aangezet.

De programmaregel(s) onder CASE ELSE worden alleen afgehandeld als geen van de andere CASE gevallen "waar" (= TRUE) zijn.
In bovenstaand voorbeeld zal LED_Wit alleen een seconde oplichten als 'Weerstand' een waarde heeft van 11 t/m 49 (met uitzondering van 30), want elke andere waarde wordt afgehandeld in één van de eerdere CASE selecties.


Nesten
Ook SELECT CASE kun je nesten.
Achter een CASE kan dus een andere SELECT CASE instructie staan.

SYMBOL Maximum = 50           ;'Maximum' staat symbool voor 50
SYMBOL Minimum = 10           ;'Minimum' staat symbool voor 10

SELECT Weerstand              ;De variabele 'Weerstand' wordt hier getest
  CASE < Minimum              ;Als 'Weerstand' kleiner is dan 10 (Minimum) dan... 
    LED_Rood = ON             ;...de rode LED aanzetten

  CASE (3 * Minimum)          ;Als 'Weerstand' = 30 (3 x 'Minimum') dan...
    SELECT PietJanHein        ;De variabele 'PietJanHein' wordt hier getest
      CASE < 5                ;(Als 'Weerstand' = 30 en) 'PietJanHein' kleiner 5 dan... 
        LED_Rood = ON         ;...de rode LED aanzetten

      CASE > 10               ;(Als 'Weerstand' = 30 en) 'PietJanHein' groter 10 dan...
        LED_Geel = ON         ;...de gele LED aanzetten

      CASE ELSE               ;(Als 'Weerstand' = 30) en overige waarden van 'PietJanHein'...
        LED_Wit  = ON         ;...de witte LED aanzetten en...
        DELAYMS 1000          ;...na 1 seconde...
        LED_Wit  = OFF        ;...de witte LED weer uitzetten

    ENDSELECT                 ;Einde binnenste SELECT CASE blok

  CASE >= Maximum             ;Als 'Weerstand' groter of gelijk is aan 50 (Maximum) dan...
    LED_Groen = ON            ;...de groene LED aanzetten

ENDSELECT                     ;Einde buitenste SELECT CASE blok

PRINT AT 2, 1, "Klaar!"       ;Zet "Klaar!" op displayregel 2

Wanneer in dit voorbeeld 'Weerstand' de waarde 30 heeft (3 × 'Minimum'), dan wordt een andere SELECT CASE uitgevoerd, die de variabele 'PietJanHein' gaat testen.

Om van de vele mogelijke situaties die nu mogelijk zijn er één als voorbeeld te nemen:
De rode LED gaat hier branden als 'Weerstand' kleiner is dan 'Minimum'.
Maar de rode LED gaat ook branden als 'Weerstand' de waarde 30 heeft en de waarde van 'PietJanHein' kleiner is dan 5.


Een functie mag rechtstreeks achter SELECT worden gezet.
Hieronder is de functie POT rechtstreeks achter SELECT gezet:

SELECT POT PORTA.1, Schaal    ;De functie POT staat rechtstreeks in de SELECT instructie 
  CASE <= 100                 ;Als de ingelezen waarde kleiner of gelijk is aan 100 dan... 
    LED_Rood  = ON            ;...de rode LED aanzetten
  .
  .
  .
ENDSELECT                     ;Einde SELECT CASE blok

Het is echter niet aan te bevelen om dit zo te doen.
Dit neemt meer programmageheugen en RAM geheugen van de PIC in dan via een dummy variabele.
In onderstaand voorbeeld wordt de waarde van de functie POT eerst in de (dummy) variabele 'BD1' gelezen en daarna wordt op zijn beurt de dummy variabele getest in SELECT:

BD1 = POT PORTA.1, Schaal     ;De ingelezen waarde wordt eerst in de variabele 'BD1' gezet
SELECT BD1                    ;Daarna wordt de (dummy) variabele 'BD1' getest
  CASE <= 100                 ;Als de ingelezen waarde kleiner of gelijk is aan 100 dan... 
    LED_Rood  = ON            ;...de rode LED aanzetten
  .
  .
  .
ENDSELECT                     ;Einde SELECT CASE blok

Het resultaat is hetzelfde maar neemt minder programma- en RAM geheugen van de PIC in, al zul je zo op het oog zeggen dat het juist meer geheugen in zou nemen.


Een volledige (8-bits) poort kun je ook testen.
Ook hier is het aan te bevelen om de poort eerst in een dummy variabele in te lezen en dan op zijn beurt de dummy variabele te testen, omdat dit ook in dit geval programma- en RAM geheugen scheelt:

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

;Variabele declareren
DIM BD1 AS BYTE               ;Byte Dummy 1

PORTB_PULLUPS ON              ;On chip pull-up weerstanden actief
CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering


;Hoofdprogramma
CLS                           ;LCD scherm wissen

WHILE 1 = 1                   ;Oneindig door blijven meten
  CURSOR 1, 1                 ;Zet cursor alvast links op regel 1
  BD1 = PORTB & %00000011     ;Lees PORTB.1 en PORTB.0 in dummy 'BD1'
  SELECT BD1                  ;Daarna de (dummy) variabele 'BD1' testen
    CASE %00000010, %00000001 ;Als alleen PORTB.1 hoog is OF alleen PORTB.0 hoog is dan... 
      PRINT "EEN "            ;...EEN op het display zetten

    CASE %00000011            ;Als PORTB.1 EN PORTB.0 hoog zijn dan... 
      PRINT "TWEE"            ;...TWEE op het display zetten

    CASE %00000000            ;Als PORTB.1 en PORTB.0 beide laag zijn dan... 
      PRINT "DRIE"            ;...DRIE op het display zetten

  ENDSELECT                   ;Einde SELECT CASE blok

WEND

END

Hier willen we alleen PORTB.0 en PORTB.1 inlezen.
Dus gaan we eerst de andere 6 poorten er uit filteren met behulp van de bitwise AND, aangegeven met '&'.
Door enen op de plaats van PORTB.0 en PORTB.1 te zetten, en nullen op de plaats van PORTB.2 t/m PORTB.7 te zetten (dus %00000011) wordt een filter gemaakt die de andere 6 poorten van PORTB eruit filtert.

Stel dat de signalen op PORTB.7, PORTB.5 en PORTB.0 hoog zijn dan is dit de situatie:

%10100001 << signalen op de poorten.
%00000011 << ons opgegeven filter waarde.
-----------------&
%00000001 << dit is het resultaat, je ziet dat PORTB.7 en B.5 er uitgefilterd zijn.

Omdat we dit met de bitwise AND (= EN) functie doen worden alleen die bits hoog die EN hoog is in ons opgegeven filter, EN een hoog signaal heeft op zijn poort.
En in het voorbeeld voldoet alleen PORTB.0 aan die voorwaarde.
Alleen PORTB.1 heeft ook de mogelijkheid om er door te komen als deze een hoog signaal op zijn ingang krijgt, de andere 6 poorten kunnen een hoog of laag signaal hebben, ze kunnen het resultaat niet beïnvloeden, kortom, die zijn er uit gefilterd.

Beetje ingewikkeld?
Mwah, maakt niet uit, dan gebruik je deze manier gewoon niet.


Er is nog meer met SELECT CASE mogelijk, met name het selecteren van tekst, maar dit is niet mogelijk bij 14-bit PIC's, en dus ook niet bij de PIC16F628(A).
Misschien wordt dat later nog behandeld.

Mocht de SELECT CASE instructie nog niet helemaal duidelijk zijn dan komt dat vanzelf in de volgende cursus delen.
En je kunt natuurlijk ook gewoon IF ... THEN ... ELSE blijven gebruiken.

 


GOSUB ... RETURN

Programmeerwerk en geheugen besparen met subroutines

Een subroutine is een afgerond stukje programma.
Wanneer in een programma dezelfde handelingen meerdere malen moeten worden uitgevoerd, dan kunnen we deze handelingen in een subroutine zetten.
In het hoofdprogramma springen we dan naar de subroutine door middel van de instructie GOSUB.
Nadat de handelingen die in de subroutine staan zijn uitgevoerd, wordt weer terug gesprongen naar het hoofdprogramma door middel van de instructie RETURN (= terugkeren), en wel naar de eerste instructie die ná de GOSUB instructie komt.

Met GOSUB kun je programmadelen die op meerdere plekken in het programma voor komen en (vrijwel) identiek (= gelijk, hetzelfde) zijn aan elkaar dus vervangen door dat programmadeel maar één maal op te schrijven en daar dan "even langs" te lopen als het nodig is.

Een voorbeeld zal het duidelijker maken:
In het voorbeeld moet steeds op een pulstoets worden gedrukt die is aangesloten op PORTB.0.
De toets wordt door DELAYMS AntiDender ontdendert.
Er wordt steeds gewacht op een toetsindruk en na elke toetsindruk verschijnt er nieuwe tekst op het display.
Dan wordt er gewacht tot de gebruiker de toets weer loslaat, want anders zou alle tekst snel na elkaar verschijnen als de gebruiker de toets ingedrukt houdt.
Nu moet de gebruiker de toets wel eerst loslaten, anders loopt het programma niet verder.

Eerst zoals dit zonder GOSUB gaat

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 UIT          = 1       ;Geinverteerd UIT

;Algemene constante
SYMBOL AntiDender   = 10      ;(mSec): Tijd voor ontdendering van toetsen

;Poortnamen
SYMBOL Toets        = PORTB.0 ;Pulstoets zit op PORTB.0 aangesloten

PORTB_PULLUPS ON              ;On chip pull-up weerstanden actief
CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering


;Hoofdprogramma
CLS                           ;Wis scherm van het display
PRINT "Druk op toets"         ;Begintekst er neer zetten

WHILE 1 = 1
  DELAYMS AntiDender          ;Tegen contactdender bij het indrukken van de toets
  WHILE Toets = AAN : WEND    ;Wacht tot toets wordt losgelaten
  DELAYMS AntiDender          ;Tegen contactdender bij het loslaten van de toets
  WHILE Toets = UIT : WEND    ;Wacht op een toetsindruk  
  CLS                         ;Wis scherm van het display
  PRINT "Met GOSUB is er"     ;Plaats tekst op het display

  DELAYMS AntiDender          ;Tegen contactdender bij het indrukken van de toets 
  WHILE Toets = AAN : WEND    ;Wacht tot toets wordt losgelaten
  DELAYMS AntiDender          ;Tegen contactdender bij het loslaten van de toets
  WHILE Toets = UIT : WEND    ;Wacht op een toetsindruk  
  CLS                         ;Wis scherm van het display
  PRINT "meer structuur"      ;Plaats andere tekst op het display

  DELAYMS AntiDender          ;...enzovoort
  WHILE Toets = AAN : WEND
  DELAYMS AntiDender
  WHILE Toets = UIT : WEND
  CLS
  PRINT "in het"

  DELAYMS AntiDender
  WHILE Toets = AAN : WEND
  DELAYMS AntiDender
  WHILE Toets = UIT : WEND
  CLS
  PRINT "Basic programma"

  DELAYMS AntiDender
  WHILE Toets = AAN : WEND
  DELAYMS AntiDender
  WHILE Toets = UIT : WEND
  CLS
  PRINT "te krijgen."

  DELAYMS AntiDender
  WHILE Toets = AAN : WEND
  DELAYMS AntiDender
  WHILE Toets = UIT : WEND
  CLS
  PRINT "Zie je wel?"

WEND

Je ziet dat hier heel vaak hetzelfde wordt opgeschreven.
Dit is allemaal extra werk voor de programmeur (= jij dus) en kost onnodig veel geheugenruimte van de PIC.

In een subroutine schrijf je nu het gedeelte dat steeds hetzelfde is en dan spring je vanuit het hoofdprogramma steeds met de instructie GOSUB naar dat stukje programma.
Wanneer dat stukje programma is afgehandeld, spring je met RETURN terug in het hoofdprogramma naar de plek waar je gebleven was.

Het volgende programma geeft hetzelfde resultaat als bovenstaande programma, maar nu met behulp van een subroutine:

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 UIT          = 1       ;Geinverteerd UIT

;Algemene constante
SYMBOL AntiDender   = 10      ;(mSec): Tijd voor ontdendering van de toets

;Poortnamen
SYMBOL Toets        = PORTB.0 ;Pulstoets zit op PORTB.0 aangesloten

PORTB_PULLUPS ON              ;On chip pull-up weerstanden actief
CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering

GOTO HoofdProgramma           ;Spring over de subroutine


;Subroutine
WachtOpToets:                 ;Label van subroutine met de naam 'WachtOpToets'
 DELAYMS AntiDender           ;Tegen contactdender bij het indrukken van de toets
 WHILE Toets = AAN : WEND     ;Wacht tot toets wordt losgelaten
 DELAYMS AntiDender           ;Tegen contactdender bij het loslaten van de toets
 WHILE Toets = UIT : WEND     ;Wacht op een toetsindruk
 CLS                          ;Wis scherm van het display
RETURN                        ;Ga terug naar waar je vandaan kwam


HoofdProgramma:               ;Label van het hoofdprogramma met de naam 'HoofdProgramma' 
CLS                           ;Wis scherm van het display
PRINT "Druk op toets"         ;Begintekst er neer zetten

WHILE 1 = 1                   ;Oneindige lus
  GOSUB WachtOpToets          ;Ga even langs het programma in de subroutine
  PRINT "Met GOSUB is er"     ;Plaats tekst op het display

  GOSUB WachtOpToets          ;Ga even langs het programma in de subroutine
  PRINT "meer structuur"      ;Plaats andere tekst op het display

  GOSUB WachtOpToets          ;...enzovoort
  PRINT "in het"

  GOSUB WachtOpToets
  PRINT "Basic programma"

  GOSUB WachtOpToets
  PRINT "te krijgen."

  GOSUB WachtOpToets
  PRINT "Zie je wel?"

WEND

Nu is dit maar een eenvoudig voorbeeld, maar het gaat erom dat je nu wat weet van GOSUB en RETURN, omdat in de volgende cursusdelen de subroutine soms gebruikt gaat worden.

GOSUB zegt de PIC dat hij "even langs" een stuk programma moet lopen (= subroutine).
De RETURN aan het eind van de subroutine zegt dat hij weer terug moet gaan naar de plaats waar hij vandaan kwam.

Zowel voor de subroutines als voor het hoofdprogramma wordt het begin aangeduid met een label (= benaming).
Een label moet altijd met een letter beginnen en eindigen met een dubbele punt ( : ).
Kies duidelijke namen zodat je meteen weet wat die subroutine ook alweer doet.
Let op dat het label "HoofdProgramma:" wat anders is dan de REM regel ";Hoofdprogramma" die regelmatig als aanwijzing in de programmavoorbeelden stond.
 

Nesten
Ook een GOSUB mag je nesten.
Met nesten van GOSUB's wordt bedoeld dat in een subroutine ook weer een GOSUB staat die naar een andere subroutine springt.
En ook in die subroutine kan weer een verwijzing naar wéér een andere subroutine staan, enzovoort.
Nesten van GOSUB's kan dus, alleen niet onbeperkt diep (wat bij een lus wel mag).
Hóe diep je mag nesten is afhankelijk van het PIC type.
De PIC moet namelijk al die terugkeeradressen onthouden om zo later weer terug te kunnen keren naar de plaats waar hij vandaan kwam.
Zodra de PIC weer is teruggekeerd, "vergeet" hij dit adres en is die plek weer vrij voor een nieuwe GOSUB.
Spring daarom ook nooit met een GOTO uit een subroutine, maar doe dat altijd met een RETURN.
Als je met een GOTO uit de subroutine springt, dan blijft de PIC het terugkeeradres onthouden terwijl hij al lang niet meer in de subroutine zit, waardoor op een moment het stapelgeheugen volloopt (= stack overflow).

Een 14-bit PIC (wat de PIC16F628A óók is) kan maar 8 adressen onthouden.
Bovendien gebruikt de compiler daarvan al (maximaal) 4 niveaus voor zijn library (= bibliotheek) subroutines, dus ga nooit dieper nesten bij een PIC16F628(A) dan 4 niveaus.
Bij een 16-bit PIC kan tot 28 niveaus diep genest worden.
Als je dieper gaat nesten dan mogelijk is dan "vergeet" de PIC zijn weg terug naar de allereerste GOSUB's.

Bedenk wel dat de beperking alleen voor het nesten geldt.
Je kunt dus gerust tientallen subroutines in een PIC16F628A programmeren.
 

GOTO Hoofdprogramma?
In tegenstelling tot de vroegere homecomputers, waar de subroutines meestal onderin het programma werden geschreven, wordt bij een PIC aanbevolen de subroutines juist zo hoog mogelijk in het programma te plaatsen.
Dit heeft te maken met hoe het geheugengebied van de PIC is opgebouwd.
Bij een klein programma zoals deze voorbeelden maakt het nog niet veel uit, maar wel bij grotere programma's.
Daarom worden na de noodzakelijke instellingen met SYMBOL, DIM, CLEAR, enzovoort eerst de subroutines geschreven en daarna pas het hoofdprogramma.
Het is daarom nodig om met de GOTO instructie over die subroutines naar het hoofdprogramma te springen omdat het programma anders meteen de subroutines induikt en de PIC een RETURN tegenkomt, terwijl er nog geen GOSUB opdracht is geweest.
Het resultaat kan zijn dat de PIC rare, onverwachte dingen gaat doen.

De instructie GOTO moet natuurlijk wel een punt, oftewel een adres, hebben waar hij naartoe kan springen.
Deze maak je door, net als bij de subroutine, een label te plaatsen op het punt waar het programma verder moet gaan.
In het voorbeeld heeft de label de naam "HoofdProgramma:" gekregen, maar dat had net zo goed "PietJanHein:" kunnen zijn.

Zodra er in de volgende cursusdelen sprake is van een GOSUB dan zal er terplekke meer over de werking vertelt worden.


Onderstaand programma is een voorbeeld die is gebruikt bij de beschrijving van het nesten van FOR ... NEXT lussen (zie hier).
Aangezien in dat voorbeeld de inhoud van de beide binnenste lussen precies hetzelfde zijn, kunnen deze ook worden vervangen door GOSUB.

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

SYMBOL Vertraging = 300       ;Vertraging van de binnenste lussen

DIM Teller1       AS BYTE     ;'Teller1' is de lus-teller van de buitenste lus
DIM Teller2       AS BYTE     ;'Teller2' is de lus-teller van de binnenste lus

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

GOTO HoofdProgramma           ;Spring over de subroutine


;Subroutine
PrintTellers:                 ;Deze subroutine zet 'Teller1' en 'Teller2' op het display 
 PRINT AT 1, 1, DEC2 Teller1  ;Zet de waarde van 'Teller1' op het display (tellerstand)
 PRINT AT 2, 1, DEC2 Teller2  ;Zet de waarde van 'Teller2' op het display (tellerstand)
 DELAYMS Vertraging
RETURN                        ;Ga terug naar waar je vandaan kwam


HoofdProgramma:               ;Hier begint het hoofdprogramma
CLS                           ;Wis display
FOR Teller1 = 1 TO 5          ;Tel met 'Teller1' van 1 naar 5
  FOR Teller2 = 0 TO 15       ;Tel met 'Teller2' van 0 naar 15
    GOSUB PrintTellers        ;Loop even langs het programmadeel in PrintTellers
  NEXT                        ;'Teller2' verhogen en dan terug naar bijbehorende FOR

  FOR Teller2 = 15 TO 0 STEP -1 ;Tel met 'Teller2' terug van 15 naar 0
    GOSUB PrintTellers        ;Loop even langs het programmadeel in PrintTellers
  NEXT                        ;'Teller2' verlagen en dan terug naar bijbehorende FOR
NEXT                          ;'Teller1' verhogen en dan terug naar bijbehorende FOR 

PRINT AT 1, 1, "Teller1 = ", @Teller1 ;Zet eindstand van 'Teller1' op displayregel 1
PRINT AT 2, 1, "Teller2 = ", @Teller2 ;Zet eindstand van 'Teller2' op displayregel 2

END                           ;Einde programma

 


Ook als vaak de instructie RCIN in je programma voorkomt (zie cursus deel 5) loont het om deze in een subroutine te zetten.
Bij RCIN moet je immers eerst de condensator ontladen met HIGH, even wachten met DELAYMS en dan nog de instructie RCIN zelf.

Meten:                        ;Potmeterinstelling meten
  HIGH PORTA.1                ;Condensator ontladen
  DELAYMS 1                   ;Even wachten zodat condensator helemaal leeg is
  Weerstand = RCIN PORTA.1, HIGH    ;Geef RC oplaadtijd aan WORD variabele 'Weerstand' 
RETURN                        ;Ga terug naar waar je vandaan kwam

Zet deze subroutine bovenin je programma en je hoeft om de potmeterinstelling te meten alleen maar GOSUB Meten op die plaatsen neer te zetten, waar anders steeds 3 programmaregels zouden moeten staan.
Als het programma dan is teruggekeerd vanuit de subroutine staat de actuele potmeterinstelling in de variabele 'Weerstand'.


Het volgende (onvolledige) voorbeeld leest een 24-uurs RTC (= Real Time Clock) IC in.
Zo'n klok IC (bijvoorbeeld type DS1302) heeft o.a. tijd, dag van de week en datum in zich.
Om de dagnaam met dagdeel op het display te zetten is gebruik gemaakt van 2 subroutines.
Elke subroutine maakt gebruik van de zojuist besproken SELECT ... CASE.

Het voorbeeld is onvolledig, dus alleen even bestuderen hoe het ongeveer werkt.

.
.
.
GOTO HoofdProgramma           ;Spring over de subroutines


PrintWeekDag:                 ;Deze subroutine zet de dagnaam vooraan op displayregel 2
PRINT AT 2, 1, REP " "\16     ;Wis displayregel 2
CURSOR 2, 1                   ;Plaats cursor waar straks de eerste letter van de dag komt

SELECT Dag                    ;Het RTC klok IC geeft een weekdagnummer van 1 (ma) t/m 7 (zo) 
  CASE 1: PRINT "Maan"
  CASE 2: PRINT "Dins"
  CASE 3: PRINT "Woens"
  CASE 4: PRINT "Donder"
  CASE 5: PRINT "Vrij"
  CASE 6: PRINT "Zater"
  CASE 7: PRINT "Zon"
ENDSELECT
PRINT "dag"                   ;Zet "dag" achter de dagnaam

RETURN


PrintDagdeel:                 ;Deze subroutine zet het dagdeel op de huidige cursorpositie
SELECT Uur                    ;Het RTC klok IC geeft een Uurwaarde van 0 t/m 23 (= 24 uur)
  CASE <  5: PRINT "nacht"    ;Als het vroeger is dan 05:00 dan is het nacht
  CASE < 12: PRINT "ochtend"  ;Als het vroeger is dan 12:00 dan is het ochtend
  CASE < 18: PRINT "middag"   ;Als het vroeger is dan 18:00 dan is het middag
  CASE ELSE: PRINT "avond"    ;En anders is het avond
ENDSELECT

RETURN


HoofdProgramma:               ;Hier begint het hoofdprogramma
WHILE 1 = 1
  ;Lees hier een klok in      ;Hier wordt steeds een klok IC ingelezen, bijv. DS1302

  PRINT AT 1, 7, DEC2 Uur, ":", DEC2 Minuut ;Zet tijd in het midden van displayregel 1
  IF Uur <> UurOud THEN       ;Als het uur verandert is dan...
    GOSUB PrintWeekdag        ;De dagnaam op het display zetten en...
    GOSUB PrintDagdeel        ;...strak daarachter welk dagdeel het is
  ENDIF
WEND

Omdat bij het instellen van de dag in het klok IC (hier niet weergegeven in het programmavoorbeeld) de dagnaam zonder dagdeel op het display moet worden weergegeven, zijn het plaatsen van de dagnaam en het dagdeel in twee afzonderlijke subroutines geplaatst.
In het gedeelte waar de klok (en dus ook de dag) wordt ingesteld, loopt het programma dan alleen langs subroutine 'PrintWeekDag'.


Het laatste voorbeeld is een subroutine die 1 minuut wacht.
Er wordt gebruik gemaakt van de zojuist besproken FOR ... NEXT lus.

DelayMinuut:
FOR Tijd = 1 TO 60000         ;60.000 x 1 milliseconde = 1 minuut
  IF PORTB.0 = LAAG THEN BREAK;Als er een lage puls op PORTB.0 komt dan uit de FOR-NEXT lus 
  DELAYMS 1 
NEXT                          ;'Teller' verhogen en dan terug naar FOR

RETURN

Wordt hier echter op de toets gedrukt die is aangesloten op PORTB.0, dan wordt de FOR ... NEXT lus onmiddellijk verlaten met BREAK.
Aangezien RETURN de eerstvolgende instructie is na NEXT wordt er dus tevens uit de subroutine teruggekeerd.
Overal waar in het hoofdprogramma een minuut wachttijd nodig is die alleen door PORTB.0 te onderbreken moet zijn hoef je alleen op die plekken GOSUB DelayMinuut neer te zetten.

 


Array's

Een array (= reeks) is een groep variabelen van dezelfde grootte (BYTE of WORD) die allen dezelfde naam hebben, maar onderling worden onderscheiden door een getal (als index) dat er tussen blokhaken [  ] achter staat.
Het leuke is dat dat getal zelf ook weer een variabele mag zijn (indexvariabele genoemd), waardoor je afhankelijk van de waarde van deze indexvariabele kunt bepalen met welke array variabele gewerkt gaat worden.

Ook array's moet je eerst declareren (dat heet bij array's dimensioneren).
Je geeft dat op met DIM VariabeleNaam[Aantal elementen].

 
Voorbeeld:
Stel jezelf een dobbelsteen voor die je 50 keer laat gooien en alle resultaten wilt bewaren.
Deze 50 getallen berg je niet op in 50 variabelen die allen een andere naam hebben, dat zou een boel programmeerwerk zijn.
Nee, hiervoor gebruik je nu een array.
Een array heeft een variabelenaam en een index tussen blokhaken.
Het index mag een vast getal zijn, maar ook een constantenaam, variabele of een berekening.

DIM Getal[50]  AS BYTE       ;Declareer een array van 50 elementen

Hierdoor weet PIC Basic dat er 50 variabelen van het type BYTE bestaan en dat deze aangeduid worden met de variabelenaam 'Getal'.

Een dobbelsteen gooit een waarde van 1 t/m 6.
Alle waarden worden bewaard in de array 'Getal'.
De eerste worp wordt bewaard in Getal[0].
De tweede worp in Getal[1] en zo verder tot aan de vijftigste worp, die bewaard wordt in Getal[49].

Worp Teller Getal [Teller] Waarde
1
2
3
4
5
6
7
8
9
10
11
12
13
.
.
49
50
0
1
2
3
4
5
6
7
8
9
10
11
12
.
.
48
49
  Getal [0]
Getal [1]
Getal [2]
Getal [3]
Getal [4]
Getal [5]
Getal [6]
Getal [7]
Getal [8]
Getal [9]
Getal [10]
Getal [11]
Getal [12]
       .
       .
Getal [48]
Getal [49]
2
6
3
4
2
5
6
1
1
3
2
4
1
.
.

6
5

Let op, er is in dit voorbeeld een array van 50 elementen gedimensioneerd, maar de index loopt nu niet van 1 t/m 50, maar van 0 t/m 49, de index begint namelijk altijd vanaf 0 te tellen.
Vindt je dat lastig dan zou je 51 elementen kunnen dimensioneren (met DIM) en sla je Getal[0] gewoon over.
In dat geval is de eerste worp Getal[1], de tweede Getal[2], enz.

By the way, het declareren van een array wordt dimensioneren genoemd, het keyword DIM is hiervan afgeleid.

(Het dobbelsteenspel wordt in cursus deel 10 behandeld)


Ander voorbeeld:
Stel dat je een temperatuurmeter hebt gemaakt die van elke dag van juli de hoogste temperatuur bewaard.
Dan kun je natuurlijk 31 variabelen aanmaken met namen als Juli_1, Juli_2, Juli_3 ... Juli_31.
Hoewel deze groep variabelen bij elkaar horen hebben ze toch ieder een eigen naam.
Dit werkt onhandig want je zult iedere variabele apart moeten behandelen:

SELECT DagDatum
  CASE 1 : Temperatuur = Juli_1
  CASE 2 : Temperatuur = Juli_2
  CASE 3 : Temperatuur = Juli_3
  CASE 4 : Temperatuur = Juli_4
  CASE 5 : Temperatuur = Juli_5
  CASE 6 : Temperatuur = Juli_6
  CASE 7 : Temperatuur = Juli_7
  CASE 8 : Temperatuur = Juli_8
  CASE 9 : Temperatuur = Juli_9
  CASE 10: Temperatuur = Juli_10
  CASE 11: Temperatuur = Juli_11
  ...... enzovoort ......
  CASE 31: Temperatuur = Juli_31
END SELECT

PRINT DEC Temperatuur

Met een array heb je voldoende aan één programmaregel.
Onderstaande doet precies hetzelfde als bovenstaande SELECT ... CASE routine:

Temperatuur = Juli[DagDatum]    

PRINT DEC Temperatuur

De variabele 'DagDatum' bevat een waarde van 1 ... 31, afhankelijk van welke dagdatum geselecteerd is.
Hierdoor wordt van de juiste array-variabele, de waarde aan 'Temperatuur' gegeven.
Het zou nu zelfs zonder de hulpvariabele 'Temperatuur' kunnen, simpel:

PRINT DEC Juli[DagDatum]        

 
Omdat de maand hier telt van 1 t/m 31 moet een array worden gedeclareerd (= gedimensioneerd) van 32 elementen, omdat de index altijd bij 0 begint en 0 juli bestaat nu eenmaal niet, dus Juli[0] zal nooit worden gebruikt:

DIM Juli[32] AS BYTE            

Een andere mogelijkheid is om de temperatuurwaarde van 1 juli in Juli[0] te stoppen, 2 juli in Juli[1] t/m 31 juli in Juli[30].
In dat geval kun je de array 'Juli' wel met 31 elementen declareren.
Je moet er dan echter steeds op letten dat je in het hele programma overal de datum steeds met één moet verminderen, dus 'DagDatum' - 1 :

Temperatuur = Juli[DagDatum - 1]

PRINT DEC Temperatuur

Het is in dit geval maar wat je het belangrijkst vindt.
Een variabele ongebruikt laten (Juli[0] dus) of alle variabelen gebruiken, maar met de kans op vergissingen.
Persoonlijk zou ik het eerste kiezen.

Je kúnt Juli[0] natuurlijk ook gewoon gebruiken, bijvoorbeeld om de hoogste temperatuur van die maand in op te slaan.


Je moet een array zien als een rij vakjes waarin je in elk vakje een waarde kunt opslaan.
De hele rij heeft één naam, bijvoorbeeld 'Juli'.
Met het getal dat er tussen blokhaken achter staat geef je aan met welk vakje je aan het werk wilt (een waarde er in stoppen of er uit halen).

Juli 
 
[0]
0
[1]
18
[2]
22
[3]
23
[4]
22
[5]
25
[6]
25
[7]
28
[8]
25
[9]
26

In bovenstaande tabel zie je dat we de temperaturen van de maand juli hebben opgeslagen (de eerste 9 dagen).
Op 1 juli is het 18°C geweest, de temperatuurwaarde staat in Juli[1].
En 7 juli was het wel heel warm, 28°C maarliefst.
Ook is te zien dat hier Juli[0] niet is gebruikt, deze is nog 0.


Experimenteer voorbeeld:
Onderstaand een voorbeeld van een array van 5 elementen met de variabelenaam 'PietJanHein'.
Van de 5 waarden wordt de grootste waarde opgezocht en op het display weergegeven:

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

;ARRAY
DIM PietJanHein[5]   AS WORD  ;Declareer een array met 5 elementen
;WORD
DIM GrootsteWaarde   AS WORD  ;Bevat de grootste waarde na onderzoek
;BYTE
DIM Teller           AS BYTE  ;'Teller' voor FOR...NEXT en indexvariabele voor de array

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
PietJanHein[0] = 5432         ;De eerste van de 5 bevat het getal 5432
PietJanHein[1] = 1000         ;De tweede van de 5 bevat het getal 1000
PietJanHein[2] = 20001        ;De derde  van de 5 bevat het getal 20001
PietJanHein[3] = 9            ;De vierde van de 5 bevat het getal 9
PietJanHein[4] = 365          ;De laatste bevat het getal 365

;Hier worden de 5 waarden na elkaar op het display weergegeven
FOR Teller = 0 TO 4           ;Loop alle 5 elementen langs (0 t/m 4)
  CLS                         ;Wis display
  PRINT @PietJanHein[Teller]  ;Zet de waarde om beurt op het display 
  DELAYMS 3000                ;Om de 3 seconden, anders gaat het zo snel
NEXT

;Hier wordt de grootste waarde opgezocht
FOR Teller = 0 TO 4           ;Loop alle 5 elementen langs (0 t/m 4)
  IF PietJanHein[Teller] > GrootsteWaarde THEN ;Als de array groter is dan 'GrootsteWaarde' 
    GrootsteWaarde = PietJanHein[Teller]       ;...'GrootsteWaarde' gelijk maken aan array 
  ENDIF
NEXT

CLS
PRINT "Grootste: ", @GrootsteWaarde            ;Zet de grootste waarde op het display 

END                           ;Einde programma (Stop PIC)

Hierboven is een WORD array variabele gedeclareerd (= gedimensioneerd) met de naam 'PietJanHein', bestaande uit 5 elementen.
De array 'PietJanHein' kan in dit voorbeeld dus 5 verschillende WORD waarden (0 ... 65535) onthouden.
Even opletten, de nummering van de array elementen begint altijd met 0 en loopt hier daarom niet van 1 t/m 5, maar van 0 t/m 4.

In de eerste FOR ... NEXT lus geeft het display na elkaar 5432, 1000, 20001, 9 en tot slot 365 aan.

Als het programma de eerste keer de FOR ... NEXT lus in loopt heeft de variabele 'Teller' de waarde 0.
Na het wissen van het display staat er PRINT @PietJanHein[Teller].
En omdat 'Teller' 0 is, staat er eigenlijk PRINT @PietJanHein[0].
De waarde van 'PietJanHein[0]' is 5432 en die wordt dus op het display weergegeven.
De FOR ... NEXT lus telt met 'Teller' van 0 t/m 4, dus komen de overige vier ook allemaal aan de beurt.


In de tweede FOR ... NEXT lus wordt gezocht naar de grootste waarde die in de array (= tabel) staat.
Met behulp van een IF ... THEN wordt elke waarde in de array bekeken en steeds vergeleken met de variabele 'GrootsteWaarde'.
Wanneer de PIC opstart dan is 'GrootsteWaarde' nog 0.
Als hij de FOR ... NEXT lus binnengaat is 'Teller' ook (weer) 0.

Dan de vergelijking:
IF
 PietJanHein[Teller] > GrootsteWaarde THEN GrootsteWaarde = PietJanHein[Teller]

Als 'Teller' 0 is dan staat er eigenlijk:
IF
 PietJanHein[0] > GrootsteWaarde THEN GrootsteWaarde = PietJanHein[0]

Oftewel, bekeken met de waarden die beide variabelen hebben:
IF
 5432 > 0 THEN GrootsteWaarde = 5432

'PietJanHein[0]' heeft de waarde 5432 en 'GrootsteWaarde' de waarde 0.
'PietJanHein[0]' is inderdaad groter dan 'GrootsteWaarde' dus de bewering is waar (= TRUE) en wordt uitgevoerd.
'GrootsteWaarde' krijgt nu ook de waarde 5432 ('GrootsteWaarde' = 'PietJanHein[0]').

Dan wordt bij NEXT 'Teller' verhoogt en wordt de lus opnieuw uitgevoerd.
Nu wordt 'PietJanHein[1]' bekeken, die heeft de waarde 1000.
Bij de vergelijking blijkt nu dat 'PietJanHein[1]' niet groter is dan 'GrootsteWaarde', want die heeft daarvoor waarde 5432 gekregen, dus de bewering is niet waar (= FALSE) en de instructie na THEN wordt niet uitgevoerd.

Echter, als voor de derde keer de lus in wordt gegaan, blijkt 'PietJanHein[2]' met waarde 20001 wél weer groter dan 'GrootsteWaarde' te zijn.
20001 is immers groter dan 5432 dus wordt de instructie na THEN nu wel weer uitgevoerd.
'GrootsteWaarde' krijgt nu de nieuwe, grootste waarde tot nu toe en dat is de waarde 20001 in 'PietJanHein[2]'.

Deze waarde zal hij blijven houden want 'PietJanHein[3]' en 'PietJanHein[4]' zijn allebei kleiner dan 20001.

 
Door de IF ... THEN meteen in de eerste FOR ... NEXT lus te plaatsen kan de tweede FOR ... NEXT zelfs vervallen:

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

;ARRAY
DIM PietJanHein[5]   AS WORD  ;Declareer een array met 5 elementen
;WORD
DIM GrootsteWaarde   AS WORD  ;Bevat de grootste waarde na onderzoek
;BYTE
DIM Teller           AS BYTE  ;'Teller' voor FOR...NEXT en indexvariabele voor de array

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
PietJanHein[0] = 5432         ;De eerste van de 5 bevat het getal 5432
PietJanHein[1] = 1000         ;De tweede van de 5 bevat het getal 1000
PietJanHein[2] = 20001        ;De derde  van de 5 bevat het getal 20001
PietJanHein[3] = 9            ;De vierde van de 5 bevat het getal 9
PietJanHein[4] = 365          ;De laatste bevat het getal 365

FOR Teller = 0 TO 4           ;Loop alle 5 elementen langs (0 t/m 4)
  CLS                         ;Wis display
  PRINT @PietJanHein[Teller]  ;Zet de waarde om beurt op het display 
  DELAYMS 3000                ;Om de 3 seconden, anders gaat het zo snel

  IF PietJanHein[Teller] > GrootsteWaarde THEN ;Als de array groter is dan...
    GrootsteWaarde = PietJanHein[Teller]       ;...'GrootsteWaarde' gelijk maken aan array  
  ENDIF
NEXT

CLS
PRINT "Grootste: ", @GrootsteWaarde            ;Zet de grootste waarde op het display 

END                           ;Einde programma (Stop PIC)

De grootste waarde opzoeken kan van pas komen om bijvoorbeeld de hoogste temperatuur van juli te bekijken.


Berekeningen zijn (nog) niet toegestaan in een element van een array.
PietJanHein[2 + 3] mag dus niet.
Door de uitkomst van de berekening eerst in een dummy te plaatsen kan het wel:

BD1 = 2 + 3      ;Eerst de uitkomst aan dummy variabele 'BD1' geven
PietJanHein[BD1] ;Vervolgens de dummy 'BD1' in het element plaatsen 

 


Eén bit wijzigen in een array
Om bijvoorbeeld bit 6 van PietJanHein[3] op '0' te zetten kun je niet schrijven:

PietJanHein[3].6 = 0  ;Dit werkt zo niet                 

Maar moet je dit zo schrijven:

PietJanHein#3.6 = 0   ;Dit werkt (denk aan het hekje # ) 

 


Theoretisch kun je een BYTE array tot maximaal 256 elementen declareren en een WORD array tot maximaal 128.
Maar werk voorlopig nog niet met zulke grote aantallen elementen, aangezien je dan wat meer over het geheugen van de PIC zelf moet weten zoals bijvoorbeeld geheugenbanken en dergelijke (later misschien meer hierover).
De PIC16F628A is een 14-bit PIC en is niet zo geschikt voor veel en/of grote array's.
Degenen die echt met veel en/of grote array's willen gaan werken kunnen beter een 16-bit PIC (= 18Fxxx) nemen (helaas niet te programmeren met de PIC Basic LITE (demo) versie).

In verband met het geheugen nog even over de volgorde van het declareren van variabelen met DIM.
Aanbevolen wordt om altijd eerst de BYTE array's, dan WORD array's, dan DWORD, dan WORD, dan BYTE en tot slot de BIT variabelen in die volgorde te declareren.

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

;BYTE-ARRAY
DIM PietJanHein[8]    AS BYTE ;Een BYTE variabele met  8 elementen (0...7)  voor 0...255
;WORD-ARRAY
DIM VariabeleNaam[50] AS WORD ;Een WORD variabele met 50 elementen (0...49) voor 0...65535
;DWORD
DIM MijnVariabele     AS DWORD;Een DWORD variabele voor -2147483648...+2147483647
;WORD
DIM WordVariabele     AS WORD ;Een WORD variabele voor een waarde van 0...65535
;BYTE
DIM ByteVariabele     AS BYTE ;Een BYTE variabele voor een waarde van 0...255
;BIT
DIM BitVariabele      AS BIT  ;Een BIT variabele voor een waarde van 0...1

Merk op dat bij de array declarering de BYTE hoger in de lijst staat dan WORD.

 
En heb je meerdere declaraties, zorg er dan voor dat je de array's met het minst aantal elementen bovenaan hebt staan, en zo de lijst uitbreidt naar de array's die meer elementen hebben.
In dit geval kun je dus (helaas) niet altijd de array declaratie lijst op alfabetische volgorde zetten.

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

;BYTE-ARRAY
DIM PietJanHein[8]    AS BYTE ;Een BYTE variabele met  8 elementen (0...7)  voor 0...255
DIM AanUit[20]        AS BYTE ;Een BYTE variabele met 20 elementen (0...19) voor 0...255
DIM KlaasJan[21]      AS BYTE ;Een BYTE variabele met 21 elementen (0...20) voor 0...255
;WORD-ARRAY
DIM VariabeleNaam[5]  AS WORD ;Een WORD variabele met  5 elementen (0...4)  voor 0...65535
DIM Relais[11]        AS WORD ;Een WORD variabele met 11 elementen (0...10) voor 0...65535
DIM Dobbelsteen[50]   AS WORD ;Een WORD variabele met 50 elementen (0...49) voor 0...65535
;WORD
DIM WordVariabele     AS WORD ;Een WORD variabele voor een waarde van 0...65535
;BYTE
DIM ByteVariabele     AS BYTE ;Een BYTE variabele voor een waarde van 0...255
;BIT
DIM BitVariabele      AS BIT  ;Een BIT variabele voor een waarde van 0...1

(Over FLOAT en STRING later meer)

Als je heel veel array's tegelijk gebruikt kunnen er problemen ontstaan met het (beperkte) geheugen van de PIC16F628A (je moet dan eigenlijk een groter PIC type uitzoeken).
Wil je nog meer weten over array's, kijk dan in de PIC Basic IDE editor bij Help en vul bij de zoekfunctie "creating and using arrays" in.


Zodra er in de volgende cursusdelen sprake is van array's, een FOR ... NEXT lus, SELECT CASE of GOSUB, zal er terplekke meer over de werking vertelt worden.

 


Na deze droge kost zal het in cursus deel 7 weer leuker worden met veel volledige programmavoorbeelden die zwakstroom motoren op variabele snelheden laat draaien, bestuurd door de PIC.