Beginners mini-cursus (deel 2)

Eerst op de PIC16F628A het volgende aansluiten:
- Een LED met 1k serieweerstand tussen pin 17 (PORTA.0) en GND
- Een LED met 1k serieweerstand tussen pin 18 (PORTA.1) en GND
- Een LED met 1k serieweerstand tussen pin  1  (PORTA.2) en GND
- Een (puls)schakelaar tussen pin 6 (PORTB.0) en GND
- Een (puls)schakelaar tussen pin 7 (PORTB.1) en GND
- Een (puls)schakelaar tussen pin 8 (PORTB.2) en GND
- En natuurlijk GND en +5V (denk aan de 100n condensator, zie deel 1)
Zie ook het schema hieronder:


IF...THEN...ELSEIF...ELSE...ENDIF

Eerst gaan we LED1 rechtstreeks met S1 aansturen, LED2 door S2 en LED3 door S3.
Als je een PIC opstart, staan alle pinnen standaard als ingangen ingesteld.
Door, zoals in deel 1, TOGGLE, HIGH en LOW te gebruiken zorgt de PIC Basic compiler ervoor dat deze automatisch uitgangen worden, maar bij spanning inschakeling zijn het dus ingangen.
Om PORTB.0 dus PORTA.0 aan te laten sturen wil je zoiets: Als PORTB.0 = laag, want de schakelaars zijn met GND verbonden, dan moet PORTA.0 hoog worden, want in tegenstelling met deel 1, zitten de LED's nu niet aan +5V, maar aan GND (zie schema).
In PIC Basic schrijf je dan IF PORTB.0 = 0 THEN HIGH PORTA.0.
Maar in deel 1 heb je geleerd om de poorten eerst een naam te geven zoals we ook hier doen in onderstaand programma:

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

SYMBOL LED1 = PORTA.0         ;Poort A.0 heeft nu de naam LED1
SYMBOL LED2 = PORTA.1         ;Poort A.1 heeft nu de naam LED2
SYMBOL LED3 = PORTA.2         ;Poort A.2 heeft nu de naam LED3
SYMBOL S1   = PORTB.0         ;Poort B.0 heeft nu de naam S1
SYMBOL S2   = PORTB.1         ;Poort B.1 heeft nu de naam S2
SYMBOL S3   = PORTB.2         ;Poort B.2 heeft nu de naam S3

PORTB_PULLUPS ON              ;On-chip pull-up weerstanden actief 
CLEAR                         ;Wis alle RAM geheugen

;Hoofdprogramma
WHILE 1 = 1                   ;Oneindige lus
  IF S1 = 0 THEN HIGH LED1    ;S1 gesloten (=laag) LED1 aan
  IF S2 = 0 THEN HIGH LED2    ;S2 gesloten (=laag) LED2 aan
  IF S3 = 0 THEN HIGH LED3    ;S3 gesloten (=laag) LED3 aan
WEND

END                           ;Einde programma

DEVICE, CONFIG, ALL_DIGITAL en CLEAR zijn bekend van deel 1.
Daaronder geven we middels SYMBOL elke poort een naam, zie deel 1, dat is makkelijker met programmeren.

Het hoofdprogramma heeft een oneindige lus door WHILE 1=1, want 1 blijft natuurlijk altijd gelijk aan 1.

Dan IF(= als) S1 = 0 THEN (= dan) HIGH LED1.
Dit betekent zoiets: Als (IF) de schakelaar laag is (S1 = 0), want schakelaar gesloten = verbonden met GND, dan (THEN) HIGH LED1 (PORTA.0 dus) hoog maken.
Het lijkt verkeerd om, als de schakelaar geopend is, dan is ingang PORTB.0 hoog en als de schakelaar gesloten is dan is PORTB.0 laag.
Dit komt omdat de schakelaar niet met +5V is verbonden maar met GND.
Het is overigens wel mogelijk om de schakelaars met +5V te verbinden maar dan moet er ook een weerstand (10k) worden aangesloten tussen de poort en GND (dat heet pull-down).
Als je de schakelaars (zoals hier) verbind aan GND zijn deze weerstanden niet nodig omdat de PIC16F628 op alle PORTB ingangen pull-up weerstanden in de chip zelf heeft.
Die kun je activeren met PORTB_PULLUPS ON, zoals hier ook is gedaan.
Om het eenvoudiger te maken geven we zodadelijk de constanten 0 en 1 een naam, we schrijven dan SYMBOL AAN = 0 en SYMBOL UIT = 1.

Als het programma loopt houdt hij continu de 3 ingangen in de gaten, immers, na WHILE staat er IF S1 = 0, maar die is niet 0, dus volgende regel, IF S2 = 0, maar S2 is ook niet 0, dus volgende, enz. tot aan WEND, en dan begint hij weer opnieuw S1 te bekijken.
Dit alles gaat zo snel, dat het lijkt of hij alle 3 ingangen tegelijk in de gaten houdt.
Druk je nu op S1 dan wordt de ingang laag, als het programma nu weer langs de regel IF S1 = 0 komt, dan blijkt dat S1 nu inderdaad 0 is, dus deze regel verder uitvoeren met wat erachter staat, en dat is HIGH LED1.
Maar als je de schakelaar weer loslaat, blijft LED1 branden!
Er staat namelijk nergens een opdracht die LED1 weer uit moet zetten, de regel die hem aan heeft gezet wordt niet meer uitgevoerd, maar de uitgang is nu eenmaal al aangezet.
Om er voor te zorgen dat de LED weer uitgaat als je de schakelaar loslaat, kun je ELSE (= anders) toevoegen.
Je krijgt dan de zin IF S1 = 0 THEN HIGH LED1 ELSE LOW LED1, wat zoiets betekent: Als S1 = 0 dan hoog LED1 anders laag LED1.


Wijzig het programma in het onderstaande:

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

SYMBOL AAN  = 0               ;Schakelaar aan = laag (0)
SYMBOL UIT  = 1               ;Schakelaar uit = hoog (1)

SYMBOL LED1 = PORTA.0         ;Poort A.0 heeft nu de naam LED1
SYMBOL LED2 = PORTA.1         ;Poort A.1 heeft nu de naam LED2
SYMBOL LED3 = PORTA.2         ;Poort A.2 heeft nu de naam LED3
SYMBOL S1   = PORTB.0         ;Poort B.0 heeft nu de naam S1
SYMBOL S2   = PORTB.1         ;Poort B.1 heeft nu de naam S2
SYMBOL S3   = PORTB.2         ;Poort B.2 heeft nu de naam S3

PORTB_PULLUPS ON              ;On-chip pull-up weerstanden actief 
CLEAR                         ;Wis alle RAM geheugen

;Hoofdprogramma
WHILE 1 = 1                   ;Oneindige lus
  IF S1 = AAN THEN            ;Als S1 aan is dan...
    HIGH LED1                 ;LED1 aan
    LOW  LED2                 ;LED2 uit
  ELSE                        ;... anders ...
    LOW  LED1                 ;LED1 uit
  ENDIF                       ;Einde IF...THEN...ELSE blok van S1

  IF S2 = AAN THEN HIGH LED2  ;Als S2=AAN dan LED2 aanzetten

  IF S3 = AAN THEN            ;Als S3=AAN dan...
    HIGH LED3                 ;LED3 aanzetten
    DELAYMS 500               ;Tijd van LED3 aan
    LOW LED3                  ;LED3 uitzetten
    WHILE S3 = AAN            ;Zolang S3 is ingedrukt...
    WEND                      ;...in dit lusje blijven wachten
  ENDIF                       ;Einde IF...THEN blok van S3
WEND

END                           ;Einde programma

Om het verwarrende van schakelaar aan = 0 en uit = 1 uit de wereld te helpen geven we de constante 0 en de constante 1 eerst een naam, dus SYMBOL AAN = 0 en SYMBOL UIT = 1.
AAN is nu dus hetzelde als '0' en UIT heeft nu de waarde '1'.
Nu kun je gewoon schrijven voor "als S1 aan is dan" : IF S1 = AAN THEN, wat dus hetzelfde is als IF PORTB.0 = 0 THEN, alleen veel duidelijker nu.

Wat nu doet het bovenstaande programma?

De eerste IF...THEN behandeld S1.
Als S1 AAN is dan LED1 aanzetten en LED2 uitzetten, anders (ELSE, dus als S1 niet aan is) LED1 uitzetten.
De tweede IF...THEN behandeld S2.
Als S2 AAN is dan LED2 aanzetten.
LED2 moet je dus aanzetten met S2 en uitzetten met S1.
De derde IF...THEN behandeld S3.
Als S3 AAN is dan LED3 aanzetten en na 0,5 seconde (DELAYMS 500) automatisch weer uit.
WHILE S3 = AAN : WEND is een leeg WHILE - WEND lusje.
Deze wacht zolang S3 blijft ingedrukt.
Als dit lusje er niet stond dan zal, nadat de DELAYMS tijd is afgelopen, de LED meteen weer aan gaan als de schakelaar wordt vastgehouden, nu moet de schakelaar eerst losgelaten worden, voordat het programma verder gaat.

 

Nog even over ENDIF:
Als je met een IF...THEN maar 1 opdracht uit wilt voeren dan kun je dat meteen achter IF...THEN zetten zoals hierboven met S2 is gedaan.
Wil je meer dan 1 opdracht met IF...THEN uitvoeren dan moet je de opdrachten onder IF...THEN zetten, zoals hierboven met S1 en S3 is gedaan.
Om nu de PIC Basic compiler te laten weten waar het blok eindigt moet na de laatste opdracht van dat IF...THEN blok worden afgesloten met ENDIF.


Een waarheidstabel programmeren met AND, OR en XOR
Stel dat onderstaand waarheidstabel in de PIC geprogrammeerd moet worden.

EN A B   UITGANGEN
S1 S2 S3   LED1 LED2 LED3
L L L   H L H
L H L   H H L
L L H   H H L
L H H   L L L
H X X   L L L

L = Laag als schakelaar is gesloten
X = Don't care (Maakt niet uit of schakelaar aan of uit is, resultaat blijft hetzelfde)

De waarheidstabel geeft het volgende aan:
Een uitgang kan alleen Hoog worden als de ENable schakelaar S1 aan is (Laag is).
Uitgang LED1 is alleen Hoog als schakelaar A (S2) en/of schakelaar B (S3) aan is (Laag is).
Uitgang LED2 is alleen Hoog als schakelaar A (S2) of schakelaar B (S3) aan is (Laag is).
Uitgang LED3 is alleen Hoog als schakelaar A (S2) en schakelaar B (S3) aan zijn (Laag zijn).

Wijzig het programma in het volgende:

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

SYMBOL AAN  = 0               ;Schakelaar aan = laag (0)
SYMBOL UIT  = 1               ;Schakelaar uit = hoog (1)

SYMBOL LED1 = PORTA.0         ;Poort A.0 heeft nu de naam LED1
SYMBOL LED2 = PORTA.1         ;Poort A.1 heeft nu de naam LED2
SYMBOL LED3 = PORTA.2         ;Poort A.2 heeft nu de naam LED3
SYMBOL S1   = PORTB.0         ;Poort B.0 heeft nu de naam S1
SYMBOL S2   = PORTB.1         ;Poort B.1 heeft nu de naam S2
SYMBOL S3   = PORTB.2         ;Poort B.2 heeft nu de naam S3

PORTB_PULLUPS ON              ;On-chip pull-up weerstanden actief
CLEAR                         ;Wis alle RAM geheugen

;Hoofdprogramma
WHILE 1 = 1                   ;Oneindige lus
  IF S1 = AAN THEN            ;Als S1 aan is dan...
    IF S2 = AAN OR S3 = AAN THEN ;Als S2 en/of S3 aan is dan...
      HIGH LED1               ;LED1 aan
    ELSE                      ;... anders ...
      LOW  LED1               ;LED1 uit
    ENDIF                     ;Einde IF...THEN...ELSE blok met OR

    IF S2 = AAN XOR S3 = AAN THEN ;Als S2 of S3 (niet beide) aan is... 
      HIGH LED2               ;LED2 aan
    ELSE                      ;... anders ...
      LOW  LED2               ;LED2 uit
    ENDIF                     ;Einde IF...THEN...ELSE blok met XOR

    IF S2 = AAN AND S3 = AAN THEN ;Als S2 en S3 aan zijn dan...
      HIGH LED3               ;LED3 aan
    ELSE                      ;... anders ...
      LOW  LED3               ;LED3 uit
    ENDIF                     ;Einde IF...THEN...ELSE blok met AND

  ELSE                        ;... anders ...
    LOW LED1                  ;LED1 uitzetten
    LOW LED2                  ;LED2 uitzetten
    LOW LED3                  ;LED3 uitzetten
  ENDIF                       ;Einde IF...THEN...ELSE blok van S1
WEND

END                           ;Einde programma

Als je goed kijkt zie je dat zowat de hele waarheidstabel met IF's alleen wordt uitgevoerd als schakelaar S1 AAN is, want anders (ELSE) zet hij alle 3 LED's uit, en dan maakt het niet uit in welke stand S2 en S3 staan.
Om LED's te laten branden moet S1 dus sowieso aan staan.
Stel dat S1 aan is, dan gaat hij dat IF...THEN blok binnen:

Daar staat meteen weer een IF...THEN blok, die gaat hij alleen uitvoeren als minimaal 1 van de 2 schakelaars (S2 en/of S3) AAN is, beide mogen dus ook AAN staan.
Dat geldt niet voor het tweede IF...THEN blok, daar staat XOR, dat betekent exclusief of, dus of alleen S2, of alleen S3, maar S2 en S3 mogen niet tegelijk aan zijn, dan gaat LED2 weer uit.
En bij het derde IF...THEN blok moeten juist S2 en (AND) S3 aan zijn om LED3 te laten branden.
Is niet aan die voorwaarde voldaan dan zet hij LED3 uit via ELSE.

Nesten
Je kunt dus een IF...THEN in een andere IF...THEN zetten, dat kun je nog veel vaker doen, dat heet nesten.
Dat nesten kan ook met WHILE - WEND en REPEAT - UNTIL, je kunt dus een WHILE - WEND lus in een andere WHILE - WEND lus plaatsen.

Nogmaals, overzicht is belangrijk
Het wordt al duidelijk waarom het programma gedeelte in een blok, 2 spaties moet inspringen, of dat nu een IF...THEN, WHILE - WEND of een REPEAT - UNTIL blok is.
Zo is makkelijker te zien welke ENDIF bij welke IF hoort en welke WEND bij welke WHILE.
Ook regels overslaan en REM (remarks) regels schrijven achter de opdrachten zijn belangrijk.
En de variabelen, constanten en poorten namen geven door middel van SYMBOL.
Doe je dat niet dan is het overzicht snel verloren, zie hieronder hetzelfde programma als hierboven, alleen zonder inspringen, REM en regels overslaan en de poorten geen naam (zoals S1 en LED1) gegeven.

DEVICE 16F628A
CONFIG INTRC_OSC_NOCLKOUT, WDT_OFF, PWRTE_ON, LVP_OFF, MCLRE_OFF 
ALL_DIGITAL TRUE
PORTB_PULLUPS ON
CLEAR
WHILE 1=1
IF PORTB.0=0 THEN
IF PORTB.1=0 OR PORTB.2=0 THEN
HIGH PORTA.0
ELSE
LOW PORTA.0
ENDIF
IF PORTB.1=0 XOR PORTB.2=0 THEN
HIGH PORTA.1
ELSE
LOW PORTA.1
ENDIF
IF PORTB.1=0 AND PORTB.2=0 THEN
HIGH PORTA.2
ELSE
LOW PORTA.2
ENDIF
ELSE
LOW PORTA.0
LOW PORTA.1
LOW PORTA.2
ENDIF
WEND
END

Als je dit programma compileert (Basic omzet naar .HEX bestand voor PIC's) dan werkt het net zo goed, maar als je na een paar maanden iets wilt veranderen is niet snel te zien hoe het programma ook alweer werkt, terwijl het programma in de PIC zelf hierdoor echt niets kleiner is geworden, daarvoor hoef je het dus ook niet te doen.


En dan heeft IF...THEN ook nog het ELSEIF commando.
Stel voor, een PIC-programma die maar 1 LED tegelijk mag laten branden, ook al staan er meer schakelaars aan.
Wijzig het programma hiervoor in het onderstaande:

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

SYMBOL AAN  = 0               ;Schakelaar aan = laag (0)
SYMBOL UIT  = 1               ;Schakelaar uit = hoog (1)

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         ;Op poort B.0 zit S1 aangesloten
SYMBOL S2   = PORTB.1         ;Op poort B.1 zit S2 aangesloten
SYMBOL S3   = PORTB.2         ;Op poort B.2 zit S3 aangesloten

PORTB_PULLUPS ON              ;On-chip pull-up weerstanden actief
CLEAR                         ;Wis alle RAM geheugen

;Hoofdprogramma
WHILE 1 = 1                   ;Oneindige lus
  IF S1 = AAN THEN            ;Als S1 = AAN dan...
    HIGH LED1                 ;LED1 aan
    LOW  LED2                 ;LED2 uit
    LOW  LED3                 ;LED3 uit
  ELSEIF S2 = AAN THEN        ;... anders, als S2 = AAN dan ...
    LOW  LED1                 ;LED1 uit
    HIGH LED2                 ;LED2 aan
    LOW  LED3                 ;LED3 uit
  ELSEIF S3 = AAN THEN        ;... anders, als S3 = AAN dan ...
    LOW  LED1                 ;LED1 uit
    LOW  LED2                 ;LED2 uit
    HIGH LED3                 ;LED3 aan
  ELSE                        ;... anders ... (Zijn S1, S2 en S3 uit) 
    LOW  LED1                 ;LED1 uitzetten
    LOW  LED2                 ;LED2 uitzetten
    LOW  LED3                 ;LED3 uitzetten
  ENDIF
WEND

END

Als bovenstaand programma wordt uitgevoerd met gewoon IF, dan gebeuren er rare dingen als meer dan 1 schakelaar tegelijk aan wordt gezet.
LED's gaan dan op verschillende helderheid branden omdat ze afhankelijk van de schakelaars heel snel in de ene IF aan, en in de andere IF weer uit worden gezet, ELSEIF voorkomt dat hier.

Het programma moet het volgende doen:
S1 zet LED1 aan, S2 zet LED2 aan en S3 zet LED3 aan, maar er mag in dit geval maar 1 LED tegelijk branden.
S2 heeft voorrang boven S3, en S1 heeft weer voorrang boven S2.
S1 heeft de hoogste prioriteit, zo wordt dat genoemd.
En S2 heeft een hogere prioriteit dan S3, maar een lagere prioriteit dan S1.
Dat betekent dat als S2 aan staat, dat LED2 brandt.
Als S3 ook aan wordt gezet gebeurt er niets zolang S2 aan blijft staan.
Wordt echter S1 aangezet dan gaat LED2 uit en LED1 aan omdat S1 dus een hogere prioriteit heeft.
ELSEIF voert dus maar 1 blokje van het hele IF...THEN blok tegelijk uit.


Nu IF...THEN behandeld is kan er nog een ontbrekende instructie behandeld worden voor lussen zoals WHILE-WEND en REPEAT-UNTIL dat eigenlijk bij deel 1 hoorde en dat is het commando BREAK.
Met BREAK kun je voortijdig een lus verlaten:

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 AAN        = 0         ;Schakelaar aan = laag (0)
SYMBOL AantalKnip = 5         ;Het aantal keer dat LED knippert

SYMBOL LedTijd    = 250       ;mSec, Knipper snelheid LED

SYMBOL LED1       = PORTA.0   ;Op poort A.0 zit LED1 aangesloten
SYMBOL LED2       = PORTA.1   ;Op poort A.1 zit LED2 aangesloten
SYMBOL S1         = PORTB.0   ;Op poort B.0 zit S1 aangesloten

DIM LedTeller     AS BYTE     ;Maak variabele LedTeller aan

PORTB_PULLUPS ON              ;On-chip pull-up weerstanden actief
CLEAR                         ;Wis alle RAM geheugen

;Hoofdprogramma
LedTeller = AantalKnip * 2    ;Stel eerst de teller in

REPEAT                        ;Herhaal onderstaande lus...
  TOGGLE LED1                 ;Knipper LED
  DELAYMS LedTijd             ;Knippersnelheid LED
  IF S1 = AAN THEN BREAK      ;Ook uit de lus springen als S1 = aan 
  DEC LedTeller               ;Teller met 1 verlagen
UNTIL LedTeller = 0           ;...totdat LedTeller op 0 staat

HIGH LED2                     ;LED2 aan

END                           ;Einde programma

Om LED1 5× te laten knipperen moet de lus 10× worden doorlopen, want 5× de LED aan en 5× de LED uit, dus: LedTeller = AantalKnip × 2.
LED1 knippert maximaal 5 keer en springt dan uit de REPEAT - UNTIL lus.
Echter, als nu op S1 wordt gedrukt springt het programma meteen uit de lus (...THEN BREAK) ook al is LedTeller nog niet 0.
Het programma gaat verder bij HIGH LED2, dus LED2 aan zetten.

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.


In deel 3 worden ingangen en uitgangen op een betere manier gedefinieerd en wordt wat over het kristal uitgelegd.