Beginners mini-cursus (deel 10)

Willekeurige getallen genereren met de toevalsgenerator

RANDOM Genereert een pseudo-willekeurig getal
SEED Geeft een startwaarde aan de toevalsgenerator
Bitmanipulatie  met GETBIT, SETBIT, CLEARBIT en LOADBIT

 

Deel 10 van de cursus kan in zijn geheel worden gedaan met onderstaand schema.
De meeste PORTB poorten hebben nu een dubbelfunctie, sommige voorbeelden gebruiken alleen de LED's, de overige het HD44780 display, maar nooit gecombineerd.
Hierdoor gebeurt het dat bij voorbeelden met het display, de LED's willekeurig gaan branden, daar moet je gewoon niets van aantrekken.
Door dit schema na te bouwen op je breadboard, kun je je zelf werk besparen, omdat de voorbeelden steeds wisselen van componenten; dan weer LED's, dan weer het display.
Anders zou je steeds weer de componenten op je breadboard moeten wisselen, op deze manier niet.


De 100n ontkoppelcondensator zo dicht mogelijk bij de PIC plaatsen.
Vergeet de 10k pull-up weerstand niet.
De aangegeven LED kleuren zijn niet echt belangrijk.

 


Om met een PIC spelletjesprogramma's te kunnen maken, is het meestal nodig een onzekere factor in te bouwen, waardoor een spelletje steeds weer anders reageert.
Het handigst hiervoor is de mens zelf.

Om bijvoorbeeld een virtuele dobbelsteen te gooien (een willekeurig getal van 1 t/m 6 dus) kun je een lus in je programma bouwen die continu telt van 1 t/m 6, dus 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2 , enz.
In die lus bekijk je ook of de gebruiker een toets indrukt en als dat het geval is laat je het getal op het display zien, dat de teller op het moment had, toen de gebruiker de toets indrukte.
Hier is de mens dus de onzekere factor.

Voorbeeld:

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 LAAG         = 0       ;Laag signaal

;Algemene constanten 
SYMBOL HoogsteGetal = 6       ;BYTE: Bepaalt de maximale waarde die getrokken kan worden

;Poortnamen
SYMBOL Toets        = PORTA.1 ;Deze pulstoets laat een willekeurig getal op het display zien

;Variabelen declareren
DIM Teller          AS BYTE   ;Deze variabele bevat de tellerwaarde 

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
CLS                           ;Display wissen
PRINT "Dobbelsteen: ?"        ;Zet de tekst op het display

WHILE 1 = 1                   ;Oneindige lus
  INC Teller                  ;Verhoog 'Teller' met 1
  IF  Teller > HoogsteGetal THEN Teller = 1 ;'Teller' groter dan 6, dan 'Teller' weer 1 maken

  IF Toets = LAAG THEN        ;Als de toets wordt ingedrukt, dan...
    PRINT AT 1, 14, @Teller   ;Plaats de huidige tellerstand op het display achter de tekst
    DELAYMS 20                ;Contactontdendering bij indrukken van de toets
    WHILE Toets = LAAG : WEND ;Wacht totdat de toets wordt losgelaten
    DELAYMS 20                ;Contactontdendering bij loslaten van de toets
  ENDIF
WEND                          ;Terug naar WHILE

Het werkt simpel.
In de oneindige WHILE 1 = 1 ... WEND lus, wordt bij INC Teller de variabele 'Teller' steeds met 1 verhoogt, dus 'Teller' telt 1, 2, 3, 4, 5, 6, 7.
Is de waarde van 'Teller' echter 7, dus groter geworden dan 6 (dat is de waarde die we de constante 'HoogsteGetal' hebben opgegeven), dan wordt de variabele weer terug op 1 gezet.
Dat gebeurt in deze regel: IF Teller > HoogsteGetal THEN Teller = 1
Nu telt de variabele 'Teller' continu 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, enz.

In dezelfde oneindige lus wordt na iedere verhoging van de variabele 'Teller' met INC, bekeken of de toets (PORTA.1) wordt ingedrukt (IF Toets = LAAG THEN...)
Is de toets niet ingedrukt dan springt het programma bij de laatste WEND weer terug naar WHILE 1 = 1, waarna de lus opnieuw begint en 'Teller' dus weer met 1 wordt verhoogt.
Is de toets wél ingedrukt, dan wordt de waarde die de variabele 'Teller' op dat moment heeft, op het display geplaatst achter de tekst Dobbelsteen: dat al op het display staat.

Nu moet er alleen nog gewacht worden tot de toets weer wordt losgelaten.
Als je dit er niet bij zou zetten dan zou de teller continu de getallen op het display laten zien zolang de toets is ingedrukt.
Door deze programmaregel moet de toets eerst worden losgelaten, vóórdat er weer een nieuw getal kan worden gegooid.

Simpel, niewaar?


Het volgende voorbeeld komt op hetzelfde neer als het vorige, alleen wordt hier niet het getal, maar letterlijk het aantal ogen van de dobbelsteen op het display getoond.
Een virtuele dobbelsteen dus.
Het is alleen te bekijken als je een display hebt met minimaal twee regels.

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

;Poortnamen
SYMBOL Toets        = PORTA.1 ;Deze pulstoets laat een willekeurig getal op het display zien 

;Variabelen declareren
DIM Dobbelsteen     AS BYTE   ;Deze variabele bevat de tellerwaarde 

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 

;Dobbelsteen ogen tekenen
PRINT $FE,$40                         ;Schrijf naar HD44780 chip, vanaf eerste adres 
PRINT $07,$07,$07,$00,$00,$00,$00,$00 ;CHR(0)
PRINT $07,$07,$07,$00,$00,$07,$07,$07 ;CHR(1)
PRINT $1C,$1C,$1C,$00,$00,$1C,$1C,$1C ;CHR(2)
PRINT $1C,$1C,$1C,$00,$00,$00,$00,$00 ;CHR(3)
PRINT $00,$07,$07,$07,$00,$00,$00,$00 ;CHR(4)
PRINT $00,$1C,$1C,$1C,$00,$00,$00,$00 ;CHR(5)
PRINT $00,$00,$00,$00,$00,$0E,$0E,$0E ;CHR(6)


;Hoofdprogramma
CLS                           ;Displayscherm wissen

WHILE 1 = 1                   ;Oneindige lus
  SELECT Dobbelsteen          ;Afhankelijk van de waarde van 'Dobbelsteen' de ogen tekenen
    CASE 1
      PRINT AT 1, 1, " ", 6, " "
      PRINT AT 2, 1, "   "
      
    CASE 2
      PRINT AT 1, 1, "  ", 3
      PRINT AT 2, 1, 4, "  "
      
    CASE 3
      PRINT AT 1, 1, " ", 6, 3
      PRINT AT 2, 1, 4, "  "
      
    CASE 4
      PRINT AT 1, 1, 0, " ", 3
      PRINT AT 2, 1, 4, " ", 5
      
    CASE 5
      PRINT AT 1, 1, 0, 6, 3
      PRINT AT 2, 1, 4, " ", 5
      
    CASE ELSE
      PRINT AT 1, 1, 1, " ", 2
      PRINT AT 2, 1, 4, " ", 5
      
  END SELECT
  
  WHILE Toets = 0 : WEND      ;Wacht tot toets wordt losgelaten
  DELAYMS 5                   ;Tegen contactdender

  WHILE Toets = 1             ;Zolang poort 'Toets' hoog is, blijven "schudden"
    INC Dobbelsteen           ;Verhoog variabele 'Dobbelsteen' met 1
    IF Dobbelsteen > 6 THEN Dobbelsteen = 1 ;Na 6 weer vanaf 1 gaan tellen
  WEND
WEND                          ;Terug naar WHILE

De variabelenaam 'Teller' is hier verandert in 'Dobbelsteen', maar dat maakt voor de werking niets uit.
Zolang de toets niet wordt ingedrukt (WHILE Toets = 1), telt de variabele 'Dobbelsteen' de hele tijd 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, enz.
De PIC is dan als het ware de hele tijd bezig met de dobbelsteen in een beker te schudden.

Wordt de toets nu ingedrukt (poort 'Toets' is dan niet meer '1'), dan springt het programma uit de onderste WHILE ... WEND lus en heeft de variabele 'Dobbelsteen' een voor de gebruiker onbekende, toevallige waarde van 1 ... 6.

Om dobbelsteenogen op het display te krijgen moeten er 7 zelfgetekende karakters bij komen (CHR(0) t/m CHR(6)).

Hoe je de karakters zelf moet tekenen lees je in het subhoofdstuk van cursus deel 4.


Nu gaan we het display van bovenstaand voorbeeld vervangen door 7 LED's, die als een dobbelsteen oplichten.
Met behulp van SELECT CASE is dit eenvoudig te realiseren:

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 LAAG         = 0       ;Laag signaal

;Poortnamen
SYMBOL Toets        = PORTA.1 ;Deze pulstoets laat een willekeurig getal op het display zien

;Variabelen declareren
DIM Dobbelsteen     AS BYTE   ;Deze variabele bevat de dobbelsteen waarde (1 t/m 6)

;        76543210 
PORTB = %00000000             ;Eerst alle PORTB uitgang registers uit (laag maken)
TRISB = %00000001             ;Alle PORTB poorten zijn uitgangen, behalve PORTB.0

CLEAR                         ;Wis alle RAM geheugen


;Hoofdprogramma
WHILE 1 = 1                   ;Oneindige lus
  INC Dobbelsteen             ;Verhoog 'Dobbelsteen' met 1
  IF  Dobbelsteen > 6 THEN Dobbelsteen = 1 ;'Dobbelsteen' groter dan 6, dan weer 1 maken

  IF Toets = LAAG THEN        ;Als de toets wordt ingedrukt, dan...
    SELECT Dobbelsteen        ;We gaan de waarde van 'Dobbelsteen' testen
;                      76543210
      CASE 1: PORTB = %00010000 ;Als 'Dobbelsteen' een 1 is, dan alleen PORTB.4 hoog maken
      CASE 2: PORTB = %01000100 ;Als 'Dobbelsteen' een 2 is, dan PORTB.6 en PORTB.2 hoog
      CASE 3: PORTB = %01010100 ;enzovoort
      CASE 4: PORTB = %11000110
      CASE 5: PORTB = %11010110
      CASE 6: PORTB = %11101110
    END SELECT

    DELAYMS 20                ;Contactontdendering bij indrukken van de toets
    WHILE Toets = LAAG : WEND ;Wacht totdat de toets wordt losgelaten
    DELAYMS 20                ;Contactontdendering bij loslaten van de toets
  ENDIF
WEND                          ;Terug naar WHILE

Het programma werkt precies hetzelfde als het eerste voorbeeld, alleen het display gedeelte (PRINT) is vervangen door SELECT CASE voor aansturing van de LED's op PORTB.1 t/m PORTB.7.

Zodra de toets wordt ingedrukt, geven we bij SELECT de waarde van variabele 'Dobbelsteen' op, omdat deze variabele de toevallige, willekeurige dobbelsteenwaarde (1 ... 6) bevat.
Daarna gaan we stuk voor stuk de 6 mogelijkheden afhandelen met CASE.
Daarvoor maken we eerst een overzicht welke poort, welke LED aanstuurt.
Het cijfer in elk oog geeft aan op welke PORTB poort de betreffende LED is aangesloten:

Zo, nu kunnen we op de tekening zien welke LED's branden voor alle 6 mogelijke worpen.
Stel dat de dobbelsteen een 1 gooit.
Volgens bovenstaande tekening brandt dan alleen de LED op PORTB.4.
En als er een 2 wordt gegooid, moeten PORTB.2 en PORTB.6 hoog worden.

We sturen de hele PORTB poort in één keer aan.
En door het binair op te geven, blijft het overzichtelijk, vooral als je er een REM regel met 76543210 precies boven plaatst (zie bovenstaand programmavoorbeeld).

Bij CASE 1 geven we aan, welke uitgang hoog moet zijn als de dobbelsteen 1 oog gooit ('Dobbelsteen' = 1).
Dat is alleen PORTB.4, die maken we dus '1' (= hoog), de overige poorten maken we '0' (= laag).
In het programma zetten we achter CASE 1 : PORTB = %00010000.

Bij CASE 2 geven we aan, welke twee uitgangen hoog zijn, als de dobbelsteen 2 ogen gooit.
Volgens de tekening zijn dat PORTB.2 en PORTB.6, dus die twee maken we '1', de rest '0'.
In het programma zetten we achter CASE 2 : PORTB = %01000100.

Zo gaan we de hele SELECT CASE "tabel" invullen.
Het maakt hierbij overigens niet uit, of je bij PORTB.0 een '0' of een '1' invult, PORTB.0 is namelijk als ingang ingesteld.


Rad van fortuin (1)
In het volgende programmavoorbeeld lopen acht LED's aangesloten op PORTB.0 t/m PORTB.7 in een cirkel.
Wordt er nu op een toets gedrukt dan remt het "rad van fortuin" af tot stilstand.
De LED die dán brandt, gaat snel knipperen en is de winnaar.

Door opnieuw op de toets te drukken draait het "rad" weer en begint het spel opnieuw.

Hier moet je eerst een afweging maken.
1. óf de achtste LED aansluiten op een PORTA poort en de starttoets op een PORTB poort.
Voordeel: Je kunt gebruik maken van de interne PORTB pull-up weerstand van de PIC zodat je geen extra pull-up weerstand voor de toets in je schakeling (op de print) hoeft te bouwen.

2. óf alle acht LED's op PORTB en de starttoets op een PORTA poort.
Het nadeel is dat je dan zelf een pull-up weerstandje in de schakeling moet plaatsen omdat de PIC16F628A geen ingebouwde pull-up weerstanden op PORTA heeft.
Maar het grote voordeel is dat je nu veel makkelijker kunt programmeren, omdat alle LED's op dezelfde poortregister (PORTB) zitten.
Voor deze optie heb ik dan ook gekozen:

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 FALSE        = 0       ;Niet waar
SYMBOL HOOG         = 1       ;Hoog signaal
SYMBOL LAAG         = 0       ;Laag signaal
SYMBOL TRUE         = 1       ;Waar

;Algemene constanten 
SYMBOL RadSnelheid  = 50      ;mSec: Bepaalt de snelheid van het rad (lager getal = sneller)

;Poortnamen
SYMBOL Toets        = PORTA.1 ;Met deze pulstoets start en stop je het rad van fortuin

;Variabelen declareren
;WORD
DIM Tijd            AS WORD   ;Wachttijd die steeds langer wordt als 'Toets' is ingedrukt
;BYTE
DIM Poort           AS BYTE   ;Bevat 1,2,4,8,16,32,64 of 128 afhankelijk welke poort hoog is
DIM Teller          AS BYTE   ;Teller voor de FOR...NEXT lus
;BIT
DIM Stoppen         AS BIT    ;Als dit bit '1' is, dan gaat het rad afremmen om te stoppen

;        76543210 
PORTB = %00000000             ;Eerst alle PORTB uitgang registers uit (laag maken)
TRISB = %00000000             ;Alle PORTB poorten zijn uitgangen

CLEAR                         ;Wis alle RAM geheugen


;Hoofdprogramma
WHILE 1 = 1                   ;Oneindige (hoofd)lus
  Stoppen = FALSE             ;Zet BIT variabele 'Stoppen' op 0 (FALSE) voor nieuwe ronde
  Tijd    = RadSnelheid       ;Het rad (weer) op de opgegeven hoge snelheid instellen
  WHILE 1 = 1                 ;Oneindige lus (totdat 'Tijd' boven ingestelde waarde komt)
    FOR Teller = 1 TO 8       ;We hebben acht LED's, tellen dus van 1 t/m 8
      Poort = Poort * 2       ;1, 2, 4, 8, 16, 32, 64, 128 (= steeds 1 LED verder)
      IF Poort = 0 THEN Poort = 1 ;
      PORTB = Poort           ;Laat de LED branden die in 'Poort' is aangegeven

      IF Toets = LAAG   THEN Stoppen = TRUE   ;Er moet gestopt worden als toets is ingedrukt
      IF Stoppen = TRUE THEN Tijd = Tijd + (Tijd / 8) ;Bij stoppen wordt tijd steeds langer
      IF Tijd > 1000    THEN WinnaarBekend    ;Als tijd overschrijdt, dan naar WinnaarBekend 
      DELAYMS Tijd            ;Wachttijd is variabel, wordt langer als op toets is gedrukt
    NEXT
  WEND                        ;Terug naar binnenste WHILE 1 = 1


WinnaarBekend:
  WHILE Toets = HOOG          ;Laat winnaar LED knipperen totdat op 'Toets' wordt gedrukt
    PORTB = Poort             ;Laat de LED branden die "winnaar" is
    DELAYMS 150               ;Tijd dat LED aan is
    PORTB = 0                 ;LED's uit
    DELAYMS 70                ;Tijd dat LED uit is
  WEND                        ;Terug naar binnenste WHILE (de WHILE Toets = HOOG)

  WHILE Toets = LAAG : WEND   ;Wacht eerst tot de toets weer is losgelaten
WEND                          ;Terug naar buitenste WHILE 1 = 1

TRUE en FALSE
Een BIT variabele kan alleen TRUE of FALSE zijn (waarbij TRUE ook '1' te noemen is en FALSE '0').
TRUE en FALSE worden in veel programmeertalen gebruikt om aan te geven of iets "waar" of "niet waar" is.
In de ene taal is TRUE '-1' en FALSE '0', bij anderen (zoals in deze cursus) is TRUE '1'.
We gaan TRUE en FALSE hierna vaker gebruiken, zodat je er alvast aan kunt wennen.

Als op 'Toets' wordt gedrukt, dan wordt BIT variabele 'Stoppen' TRUE (gewoon '1' dus).
'Toets' zelf kan dan weer worden losgelaten, terwijl BIT variabele 'Stoppen' op '1' blijft staan, een soort overneemcontact dus.
De regel daarna ziet dat 'Stoppen' nu TRUE is, en zal nu de wachttijd in variabele 'Tijd' steeds groter (dus langer) maken door er steeds 1/8 deel van 'Tijd' bij op te tellen: Tijd = Tijd + (Tijd / 8).
Stel dat 'Tijd' 100 is, dan wordt er (100 / 8) is ongeveer 12mSec bij opgeteld waardoor 'Tijd' nu 112mSec is.
De keer daar op is 'Tijd' (112 / 8) = 14mSec, dus dan wordt 'Tijd' 112 + 14 = 126mSec.
Op die manier wordt de tijd steeds langer en zal het rad steeds langzamer "draaien".
Duurt de tijd tussen de LED's op een gegeven moment langer dan een seconde (het rad "draait" dan langzaam), dan springt het programma naar het label 'WinnaarBekend'.

Het enige dat het programmadeel van 'WinnaarBekend' doet, is de "winnaars" LED laten knipperen, en daarbij 'Toets' in de gaten houden (met WHILE Toets = HOOG).
Zodra er dan op 'Toets' wordt gedrukt, springt het programma bij de laatste WEND terug naar het begin van de oneindige hoofdlus en begint alles weer van voren af aan.

Voordat het programma dan weer in de binnenste oneindige lus komt, wordt nog eerst het bit 'Stoppen' weer op FALSE (= '0') gezet, want het spel wordt immers opnieuw gestart.
En ook de waarde van 'Tijd' wordt weer klein gemaakt (50mSec), waardoor het "draaien" van het rad zodadelijk weer snel gaat.

  

Hoewel de mens hier weer de onzekere factor is, die op de toets drukt, wordt pas een paar seconden later bekend welke LED de winnaar is geworden.
Een nadeel hier is dat je na een paar keer spelen toch door krijgt, bij welke LED je de toets in moet drukken, om een andere LED na een paar seconden als winnaar uit de bus te laten komen.
Een oplossing om dit te voorkomen wordt verderop in dit cursusdeel gegeven.

 


RANDOM

Wat nu, als er iets willekeurigs moet gebeuren, terwijl er geen mens aan te pas komt?
Hiervoor bestaat in PIC Basic de functie RANDOM.
 

De syntaxis
 Variabele =  RANDOM 

De functie RANDOM genereert een pseudo-random (= bijna willekeurig) getal in een variabele.
 

Variabele - moet (eigenlijk) een WORD variabele zijn, omdat de functie RANDOM een (bijna) willekeurig 16 bits getal genereert.
RANDOM plaatst dit getal in Variabele.
Steeds als de functie in het programma wordt uitgevoerd, zal er weer een ander getal in Variabele worden geplaatst.
Het getal zal een willekeurige waarde zijn van 1 ... 65535.
Een 0 zal dus nooit worden gegenereerd.

Nadat je in je programma de functie RANDOM hebt uitgevoerd, staat het (bijna) willekeurige getal in de variabele.
Met die variabele kun je vervolgens allerlei dingen gaan doen.
Bijvoorbeeld een LED onregelmatig laten knipperen, door de variabele achter een DELAYMS instructie te zetten, waardoor de wachttijd steeds weer anders is.

Sluit hiervoor een LED (met serieweerstand van 1k) aan tussen PORTA.2 en GND:

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

;Poortnamen
SYMBOL LED          = PORTA.2 ;Deze LED knippert met een willekeurig ritme

;Variabele declareren
;WORD
DIM PietJanHein     AS WORD   ;In WORD variabele 'PietJanHein' komt steeds een ander getal

;        76543210 
PORTA = %00000000             ;Eerst alle PORTA uitgang registers uit (laag maken)
TRISA = %11111011             ;PORTA.2 is een uitgang voor de LED

CLEAR                         ;Wis alle RAM geheugen


;Hoofdprogramma
WHILE 1 = 1                   ;Oneindige lus
  LED = ~LED                  ;Toggle de LED (als ie aan is dan uitzetten, anders aanzetten) 
  PietJanHein = RANDOM        ;Geef een willekeurig getal aan WORD variabele 'PietJanHein'
  DELAYMS PietJanHein / 50    ;Deel willekeurige getal door 50 (anders duurt het zo lang)
WEND                          ;Terug naar WHILE

Het hoofdprogramma loopt hier in een oneindige lus, waardoor de LED altijd zal blijven knipperen.

In die lus wordt eerst aan variabele 'PietJanHein' een willekeurig getal gegeven door de RANDOM functie.
De WORD variabele 'PietJanHein' zal dus steeds een ander getal bevatten tussen 1 en 65535 (een 0 zal nooit worden gegenereerd).

De knippertijd wordt nu variabel gemaakt door 'PietJanHein' (met daarin steeds een andere waarde) achter DELAYMS te plaatsen.
Omdat 65535 de grootst mogelijke waarde is die voor kan komen, zou het betekenen dat de langste wachttijd 65535mSec is, oftewel een dikke 65 seconden.
Dit duurt wel érg lang, daarom gaan we variabele 'PietJanHein' delen door 50.
De langst mogelijke wachttijd zal nu zijn DELAYMS 1311 (= 65535 / 50) en de kortst mogelijke wachttijd zal zijn DELAYMS 0 (= 1 / 50).
De langste wachttijd die voor kan komen is door de deling door 50 nu 1311mSec, oftewel ruim 1,3 seconde.

Bovenstaand voorbeeld kan nog korter, door de variabele 'PietJanHein' helemaal weg te laten en de functie RANDOM rechtstreeks achter de instructie DELAYMS te plaatsen, dus:

DELAYMS RANDOM / 50

Zie programmavoorbeeld hieronder:

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

;Poortnamen
SYMBOL LED          = PORTA.2 ;Deze LED knippert met een willekeurig ritme

;        76543210 
PORTA = %00000000             ;Eerst alle PORTA uitgang registers uit (laag maken)
TRISA = %11111011             ;PORTA.2 is een uitgang voor de LED


;Hoofdprogramma
WHILE 1 = 1                   ;Oneindige lus
  LED = ~LED                  ;Toggle de LED (als ie aan is dan uitzetten, anders aanzetten) 
  DELAYMS RANDOM / 50         ;Deel willekeurige getal door 50 (anders duurt het zo lang)
WEND                          ;Terug naar WHILE

Elke keer als het programma weer langs DELAYMS loopt, wordt er een nieuwe willekeurige waarde achter gezet door de RANDOM functie.

Om de LED sneller te laten knipperen moet je de deelfactor groter maken.
Verander de 50 bijvoorbeeld eens in 250, je krijgt dan een soort knipperlamp die ook wel eens met behulp van een TL-starter wordt gemaakt.
Echt mooi willekeurig knippert de LED nog niet, daar wordt verderop een oplossing voor gegeven.


Om de LED gemiddeld langer uit, dan aan te laten zijn moet je LED aan en LED uit elk apart behandelen:

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

;Poortnamen
SYMBOL LED          = PORTA.2 ;Deze LED knippert volgens een willekeurig ritme

;        76543210 
PORTA = %00000000             ;Eerst alle PORTA uitgang registers uit (laag maken) 
TRISA = %11111011             ;PORTA.2 is een uitgang voor de LED


;Hoofdprogramma
WHILE 1 = 1                   ;Oneindige lus
  LED = 1                     ;LED aanzetten 
  DELAYMS RANDOM / 250        ;Deel random getal door 250 en wacht (met LED aan)
  LED = 0                     ;LED uitzetten 
  DELAYMS RANDOM / 50         ;Deel random getal door 50 en wacht (met LED uit)
WEND                          ;Terug naar WHILE

RANDOM kan dus maximaal 65535 geven.
De tijd dat de LED aan is, kan nu maximaal 65535 / 250 = 262mSec zijn.
De tijd dat de LED uit is, kan hier maximaal 65535 / 50 = 1310mSec zijn.
Verderop staat een voorbeeld dat mooier knippert en waarbij ook de minimale tijd is in te stellen.


Om de acht LED's op PORTB allemaal willekeurig te laten knipperen, geef je het (bijna) willekeurige RANDOM getal aan het register PORTB.

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

;        76543210 
TRISB = %00000000             ;Alle PORTB poorten zijn uitgangen


;Hoofdprogramma
WHILE 1 = 1                   ;Oneindige lus
  PORTB = RANDOM              ;Maak de acht PORTB uitgangen willekeurig hoog of laag 
  DELAYMS 500                 ;Knipperen om de halve seconde
WEND                          ;Terug naar WHILE

Als je de acht LED's van PORTB in dezelfde volgorde als de poorten hebt opgesteld, dus LED volgorde B.0, B.1, B.2 ... B.7, dan zie je dat RANDOM gewoon een kwestie is van de bits doorschuiven en dat er alleen bij de LED van PORTB.0 willekeurig een '0' of een '1' wordt toegevoegd.
Meer hierover bij het volgende voorbeeld.


Met behulp van een HD44780 display, kunnen we deze bitverschuiving ook laten zien:

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

DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
WHILE 1 = 1                   ;Oneindige lus
  CLS                         ;Display (steeds) wissen
  PRINT BIN16 RANDOM          ;Plaats de willekeurige BINaire waarde op het display 
  DELAYMS 1000                ;Elke seconde een ander getal
WEND                          ;Terug naar WHILE

Het programma loopt zoals gebruikelijk weer in een oneindige lus, zodat steeds een nieuw getal op het display kan komen.
Door PRINT wordt de binaire random waarde getoond in 16 cijfers, vanwege keyword (= sleutelwoord) BIN16.
Degene die goed oplet ziet dat het willekeurige toch niet zo willekeurig is dan eerst gedacht.
Het opwekken gebeurt doordat PIC Basic alle 16 bitjes een plaats naar links schuift (= bitwise shift left) en er rechts willekeurig een '0' of '1' aan toevoegt.

Daarom heet de functie ook pseudo (= bijna, niet echt) random.
Je kunt er niet de getallen voor de landelijke staatsloterij mee bepalen, een slim iemand zou getallen kunnen voorspellen!
Maar er kan wel wat aan gedaan worden, zoals verderop te zien zal zijn.


SEED

De syntaxis
 SEED Waarde 

Met de instructie SEED kun je de functie RANDOM een startwaarde geven.
 

Waarde - mag een constante, variabele of een berekening zijn met een waarde van 1 ... 65535.

Als we Waarde door een andere willekeurige waarde laten bepalen, krijgen we een meer willekeurig getal met RANDOM.
Die willekeurige waarde kunnen we krijgen van bijvoorbeeld een NTC of LDR lichtgevoelige weerstand, omdat deze componenten meestal verschillende waarden geven na iedere meting.
Maar picbasic.nl heeft een andere manier gevonden om met behulp van SEED een willekeurige startwaarde voor RANDOM te verkrijgen.


Het onderstaand programma laat zien dat de opgegeven waarde aan de instructie SEED de startwaarde van RANDOM 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

DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
CLS                           ;Display wissen

SEED 65535                    ;65535 = %1111111111111111 (alle 16 bits '1')

WHILE 1 = 1                   ;Oneindige lus
  PRINT AT 1, 1, BIN16 RANDOM ;Plaats de willekeurige BINaire waarde op het display 
  DELAYMS 1000                ;Elke seconde een ander getal
WEND                          ;Terug naar WHILE

Nu zie je, dat als je de PIC opstart, dat de RANDOM functie begint met 1111111111111111, en daarna naar links schuift, terwijl er rechts steeds willekeurig een '0' of een '1' wordt bijgevoegd.
Steeds als je de PIC weer opstart, zie je dat dezelfde volgorde van nullen en enen weer langskomen, verderop gaan we daar wat aan doen.


Hoe kunnen we de randomizer (= toevalsgenerator) nu "schudden" met behulp van SEED?
Wel, door het volgende te doen:

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

;Variabelen declareren
;WORD
DIM x               AS WORD   ;In WORD variabele 'x' komen de willekeurige getallen
;BYTE
DIM Teller          AS BYTE   ;Teller voor FOR...NEXT lus

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
CLS                           ;Display wissen

WHILE 1 = 1                   ;Oneindige lus
  FOR Teller = 1 TO 11        ;Toevalsgenerator 11x schudden
    SEED x + 2221             ;Verhoog 'x' met 2221, wat een nieuwe startwaarde oplevert
    x = RANDOM                ;Geef willekeurige waarde aan variabele 'x'
  NEXT

  PRINT AT 1, 1, BIN16 x      ;Plaats de BINaire  waarde van x op regel 1 van het display
  PRINT AT 2, 1, DEC x, "    ";Plaats de DECimale waarde van x op regel 2 van het display 
  DELAYMS 1000                ;Elke seconde een ander getal
WEND                          ;Terug naar WHILE

Dit programma trekt willekeurige getallen uit 1 ... 65535.
Het is al behoorlijk willekeurig, er is geen volgorde meer te bekennen in de binaire (en dus ook decimale) weergave.

Eerst trekken we een aantal maal een willekeurig getal (11× met behulp van de FOR ... NEXT lus in dit voorbeeld).
Bij elk getrokken willekeurig getal (in variabele 'x') wordt een extra waarde opgeteld (priemgetal 2221 in dit voorbeeld) en weer teruggegeven door middel van SEED.
Hierdoor zal de RANDOM generator een nieuw willekeurig getal trekken, dat gebaseerd is op het vorige willekeurige getal (+ 2221).
Na 11× is er niets meer over van de oude, vorige waarde die 'x' bij het binnengaan van de FOR ... NEXT lus had, de randomizer is als het ware goed door elkaar "geschud".

Het getal 2221 is maar een voorbeeld, het mooist is een groot (priem)getal, maar elk getal werkt.


De getallen zijn nu onderling wel behoorlijk willekeurig, maar steeds wanneer je de PIC opnieuw opstart (door reset of spanningsonderbreking) komen weer dezelfde willekeurige getallen, in dezelfde volgorde opdraven.
Om bij een toepassing waarbij geen mens aan te pas komt, bij iedere opstart van de PIC, tóch steeds een andere startwaarde te krijgen, kunnen we het volgende doen:

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
;WORD
DIM x               AS WORD   ;In WORD variabele 'x' komen de willekeurige getallen

x = x + EREAD 0               ;'x' voor CLEAR, 'x' is dus nog niet gewist (= willekeurig!)
EWRITE 0, [x]                 ;Schrijf het willekeurige getal in 'x' naar EEPROM adres 0 en 1
CLEAR                         ;Nu pas alle RAM geheugen wissen
x = EREAD 0                   ;Lees EEPROM adres 0 (en 1, 'x' is een WORD variabele) weer uit

DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
CLS                           ;Wis displayscherm
SEED x                        ;In 'x' staat een willekeurig getal gekregen via EEPROM uit 'x'
PRINT DEC RANDOM              ;Plaats willekeurige getal op het display

END                           ;Let op, deze END is nu belangrijk

Elke PIC start op met alle duizenden RAM geheugencellen op een willekeurige waarde ('0' of '1').
Dit is ook de reden dat je bovenin een programma altijd CLEAR moet plaatsen, waardoor alle bits van het hele RAM geheugen op '0' worden gezet, zodat je altijd met een leeg, schoon geheugen begint.
Maar nu kunnen we van die willekeur mooi gebruik maken.
Overigens is ook hier de willekeur weer beperkt, omdat vaak (niet altijd) dezelfde bits '0' (of '1') worden.

Door ergens bovenin het programma, maar in ieder geval nog vóór CLEAR, de willekeurige waarde die WORD variabele 'x' heeft bij het opstarten van de PIC, op te slaan in de EEPROM (op adres 0 én 1 in dit geval, want 'x' is een WORD variabele) samen met de waarde die nog van de vorige keer op EEPROM adres 0 staat, krijg je een behoorlijk willekeurig getal.
Pas daarna ga je het geheugen wissen met CLEAR.
CLEAR wist alleen het gehele RAM geheugen, niet het EEPROM geheugen, het willekeurige getal blijft dus behouden in EEPROM.
Meteen na het wissen van het RAM kun je de EEPROM weer uitlezen, waarin nu dus nog steeds het willekeurige getal staat, en geven deze waarde terug aan variabele 'x'.

In 'x' hebben we nu dus een redelijk willekeurig getal om met SEED een startwaarde voor RANDOM in te stellen.
Hierdoor start de toevalsgenerator verderop in het programma met een willekeurige waarde, waardoor de getallen die daarna allemaal getrokken worden ook weer anders zullen zijn.


Het volgende voorbeeld laat heel snel willekeurige getallen zien, maar als er een getal onder de 1000 getrokken wordt, dan laat het display dit 1 seconde zien.
Het programma zal elke keer als je de PIC hebt opgestart, weer met geheel nieuwe getallen en volgorde komen:

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
;WORD
DIM x               AS WORD   ;In WORD variabele 'x' komen de willekeurige getallen
;BYTE
DIM Teller          AS BYTE   ;Teller voor FOR...NEXT lus

x = x + EREAD 0               ;'x' voor CLEAR, 'x' is dus nog niet gewist (= willekeurig!)
EWRITE 0, [x]                 ;Schrijf het willekeurige getal in 'x' naar EEPROM adres 0 en 1
CLEAR                         ;Nu pas alle RAM geheugen wissen
x = EREAD 0                   ;Lees EEPROM adres 0 en 1 (x is een WORD variabele) weer uit

DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
CLS                           ;Display wissen

WHILE 1 = 1                   ;Oneindige lus
  FOR Teller = 1 TO 11        ;Toevalsgenerator 11x schudden
    SEED x + 2221             ;Verhoog 'x' met 2221, wat een nieuwe startwaarde oplevert
    x = RANDOM                ;Geef willekeurige waarde aan variabele 'x'
  NEXT

  PRINT AT 1, 1, BIN16 x      ;Plaats de BINaire  waarde van x op regel 1 van het display
  PRINT AT 2, 1, DEC x, "    ";Plaats de DECimale waarde van x op regel 2 van het display
  IF x < 1000 THEN DELAYMS 1000  ;Wacht een seconde als het getal kleiner dan 1000 is 
WEND                          ;Terug naar WHILE

Het programma plaatst op displayregel 1 de binaire (16 bits) waarde van 'x' en doet dat in de decimale notatie op displayregel 2.


Er zijn nog meer willekeurige factoren te vinden die je kunt gebruiken om een willekeurige opstartwaarde te krijgen.
De mens natuurlijk, maar ook door bijvoorbeeld met een LDR de lichtsterkte van het moment dat de PIC opstart te meten met POT of RCIN (zie cursus deel 5) en de waarde die dit oplevert aan SEED aan te bieden of te verwerken (bijvoorbeeld optellen of vermenigvuldigen met 'x').
Of gebruik een NTC weerstand.
En als je een PIC hebt met een ADC aan boord, kun je een analoge zwevende spanning van een loshangend pootje van de PIC meten en de waarde die dat oplevert als willekeurige opstartwaarde voor SEED gebruiken.
PIC type 16F628(A) heeft helaas geen ADC aan boord.

Maar al deze methoden hebben het nadeel dat ze een ingangspin van de PIC gebruiken, terwijl bovenstaande SEED voorbeelden, intern een willekeurig getal oplevert dat meestal willekeurig genoeg is voor een project.


Voorbeelden

Hoe nu een getal trekken uit 0 ... 20?
De functie RANDOM geeft immers een getal uit 1 ... 65535.
Er zal dus een berekening aan vooraf moeten gaan.
Het volgende voorbeeld laat zien hoe dat 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

;Variabele declareren
;WORD
DIM x               AS WORD   ;Met WORD variabele 'x' gaan we de randomizer schudden
;BYTE
DIM RND             AS BYTE   ;In 'RND' komt steeds een willekeurig getal te staan
DIM Teller          AS BYTE   ;Teller voor de FOR...NEXT lus

x = x + EREAD 0               ;'x' voor CLEAR, 'x' is dus nog niet gewist (= willekeurig!)
EWRITE 0, [x]                 ;Schrijf het willekeurige getal in 'x' naar EEPROM adres 0 en 1
CLEAR                         ;Wis nu pas alle RAM geheugen
x = EREAD 0                   ;Lees EEPROM adres 0 en 1 (x is een WORD variabele) weer uit

DELAYMS 500                   ;LCD stabilisering 

GOTO Hoofdprogramma           ;Spring over de subroutine


;Subroutine
Load_RND:
  FOR Teller = 1 TO 11        ;Toevalsgenerator 11x schudden
    SEED x + 2221             ;Verhoog 'x' met 2221, wat een nieuwe startwaarde oplevert
    x = RANDOM                ;Geef willekeurige waarde aan variabele 'x'
  NEXT
  RND = x / (65535 / 21)      ;Waarde 0 t/m 20 (dat zijn 21 verschillende waarden)
  IF RND > 20 THEN Load_RND   ;Af en toe kan 'RND' 21 worden, dan nog een keer schudden
RETURN


Hoofdprogramma:
CLS                           ;Display wissen

WHILE 1 = 1                   ;Oneindige lus
  GOSUB Load_RND              ;Ga een nieuwe willekeurige waarde aan 'RND' geven

  PRINT AT 1, 1, @RND, " "    ;Plaats de decimale waarde van RND op het display
  DELAYMS 1000                ;Laat om de seconde een getal zien
WEND                          ;Terug naar WHILE

De variabele die het willekeurige getal bevat, hebben we hier de naam 'RND' (random) gegeven.
Het schudden van de randomizer (= toevalsgenerator) en het trekken van een willekeurig getal in 'RND' is hier in een subroutine geplaatst, waardoor het overzichtelijker blijft.
Om 'RND' een nieuwe, willekeurige waarde te geven, hoef je nu alleen maar de regel GOSUB Load_RND te plaatsen.

Je moet er wel op letten, dat je geen andere dingen met variabele 'x' gaat doen.
Het is namelijk van belang dat 'x' alleen wordt gebruikt voor het schudden van de randomizer.
Steeds als je dan weer gaat schudden, wordt het gebaseerd op de waarde die nog van de vorige keer in 'x' zit.

Nog even dit: het is belangrijk dat je subroutines boven het hoofdprogramma plaatst en niet onderaan.
(Dit in tegenstelling tot de vroegere homecomputers) zie cursus deel 6.

De regel RND = x / (65535 / 21) levert meestal een getal op van 0 ... 20 in 'RND'.
Meestal, want heel af en toe kan de uitkomst 21 zijn, terwijl we een getal uit 0 ... 20 willen hebben.
Als dit gebeurt wordt de trekking opnieuw gedaan.
 

Formule
De PIC kijkt alleen naar de cijfers vóór de komma.
Dus een getal als 99,97 is voor de PIC gewoon 99 waard.
(Getallen met cijfers achter de komma is mogelijk door de variabele te declareren als FLOAT, dit neemt echter veel geheugenruimte in van de PIC, gebruik FLOAT dus niet als het even kan).

De functie RANDOM levert dus een willekeurig getal van 1 ... 65535, in bovenstaande programma wordt die waarde aan de variabele met de naam 'x' gegeven.
Daarna volgt de berekening RND = x / (65535 / 21), waarna de uitkomst in variabele 'RND' staat.

Om te kijken wat er kan gebeuren nemen we eerst de laagst mogelijk waarde die in 'x' kan zitten, dat is dus 1.
De berekening is dan 1 / (65535 / 21).
Berekeningen die tussen haakjes ( ) staan, worden het eerst uitgerekend.
In deze berekening is dat dus 65535 / 21 met als uitkomst 3120,7143.
Maar de PIC laat de cijfers na de komma weg, dus is de uitkomst 3120.
Dan volgt nog de berekening 1 / 3120 met als uitkomst 0,0003.
De PIC laat de cijfers na de komma weer weg, dus de uitkomst is 0.
Ook als RANDOM aan 'x' een waarde levert van 2 t/m 3119, zal de uitkomst 0 zijn.
Levert RANDOM een waarde van 3120 t/m 6239, dan is de uitkomst een 1.
Bij 6240 t/m 9359 is de uitkomst een 2, enzovoort.

We nemen nu de hoogst mogelijke waarde die in 'x' kan zitten, dat is dus 65535.
De berekening is dan 65535 / (65535 / 21).
Eerst de berekening 65535 / 21 waarvan de uitkomst dus 3120 is.
Daarna 65535 / 3120 = 21,0048.
De PIC ziet dit als 21, terwijl we een getal vroegen uit 0 ... 20.
Die 21 kun je zien als een restwaarde, een deling komt immers vaak nooit op hele getallen uit.
Maar, hoe dan ook, je moet er wél voor zorgen dat 'RND' nooit de restwaarde kan bevatten als het van de subroutine terugkomt.
Nu is dat eenvoudig te doen door na de berekening te schrijven IF RND > 20 THEN Load_RND waardoor opnieuw een getal wordt getrokken.
Ook als 'x' hier van RANDOM een getal krijgt van 65520 t/m 65535, zal de uitkomst 21 zijn en wordt de trekking opnieuw gedaan.

Zoals je ziet is een deel van de berekening steeds hetzelfde, ongeacht het getrokken getal.
Hierboven is dat 65535 / 21.
Waarom zou je de PIC dat laten berekenen?
Dat kost allemaal geheugen en tijd, terwijl de uitkomst altijd 3120 is, een constante waarde.
Daarom kun je dit gedeelte beter door de compiler (oftewel je PC/laptop) laten berekenen, juist omdat het een constante waarde is.
In de volgende voorbeelden gebeurt dat dan ook, de uitkomst plaatsen we dan met SYMBOL in de constante met de naam 'DeelFactor':

SYMBOL DeelFactor = 65535 / ((HoogsteGetal + 1) - LaagsteGetal)

In de hierna volgende voorbeelden kun je de hoogste en laagste getallen invullen bij de constanten 'HoogsteGetal' en 'LaagsteGetal'.
Het programma trekt dan willekeurige waarden die lopen van het opgegeven hoogste en laagste getal.

Waarom achter 'HoogsteGetal' + 1 in de formule?
In de formule wordt 'LaagsteGetal' van 'HoogsteGetal' afgehaald, maar dan kom je er altijd één tekort.
Een paar voorbeelden (HoogsteGetal - LaagsteGetal):

5 - 0 
6 - 1 
200 - 100 
200 - 101 
= 5
= 5
= 100 
= 99
Maar moet 6 zijn, want 6 verschillende mogelijkheden.
Maar moet 6 zijn, want 6 verschillende mogelijkheden.
Maar moet 101 zijn, want 101 verschillende mogelijkheden.
Maar moet 100 zijn, want 100 verschillende mogelijkheden.

Als een getal moet worden getrokken uit 0 ... 5, dan kunnen dat 6 verschillende waarden zijn (geen 5).


Hoe nu een getal "gooien" voor bijvoorbeeld een dobbelsteen (1 ... 6)?
De 0 mag nu namelijk ook niet getrokken worden.
Dat is vrij simpel, trek een getal uit 0 en 5 en tel er bij de uitkomst 1 bij (met bijvoorbeeld INC RND).
Als er nu een 0 wordt getrokken, dan wordt deze dus 1.
Als er nu een 1 wordt getrokken, dan wordt deze dus 2.
Als er nu een 5 wordt getrokken, dan wordt deze dus 6.
In onderstaand voorbeeld is overigens nog geen veiligheid ingebouwd die de trekking opnieuw doet als het getal hoger dan 6 blijkt te zijn.
Heel af en toe kan er hier dus nog een 7 tussendoor glippen.
Het voorbeeld laat het zien:

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

;Algemene constanten 
SYMBOL HoogsteGetal = 6       ;Bepaalt de maximale waarde die getrokken kan worden
SYMBOL LaagsteGetal = 1       ;Bepaalt de minimale waarde die getrokken kan worden

;Compiler berekeningen
SYMBOL DeelFactor   = 65535 / ((HoogsteGetal + 1) - LaagsteGetal

DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
CLS                           ;Display wissen

WHILE 1 = 1                   ;Oneindige lus
  PRINT AT 1, 1, DEC (RANDOM / DeelFactor) + LaagsteGetal, "    "
  DELAYMS 1000                ;Laat om de seconde een ander getal zien
WEND                          ;Terug naar WHILE

En getallen uit 100 t/m 200?
Hetzelfde verhaal.
Trek eerst een getal uit 0 t/m 100.
En dan net als het bovenste voorbeeld, alleen dan geen 1, maar 100 erbij optellen.

Door de constanten 'LaagsteGetal' en 'HoogsteGetal' blijft het overzichtelijk en is het makkelijk te wijzigen in andere waarden.
Nu is bovenstaand voorbeeldje een uitgeklede versie om je het gebruik van 'LaagsteGetal' en 'HoogsteGetal' te laten zien, echt mooi willekeurig zijn de getallen niet, omdat de schudroutine hier niet in zit.


Hè hè, vanaf nu beginnen we ergens te komen, in onderstaand voorbeeld zit alles wat we tot nu toe besproken hebben.

Het programma trekt een getal dat ligt tussen 'LaagsteGetal' en 'HoogsteGetal'.
Wordt het getal (heel af en toe) hoger dan 'HoogsteGetal', dan wordt de trekking opnieuw gedaan.
De getallen zijn behoorlijk willekeurig, omdat hier de boel weer flink geschud wordt.
En ook de voorziening die er voor zorgt dat er weer met nieuwe getallen wordt begonnen als de PIC uitgeschakeld is geweest (met behulp van de interne EEPROM) is aanwezig.
Bovendien kun je bij de constanten 'LaagsteGetal' en 'HoogsteGetal' zelf waarden naar wens invullen:

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

;Algemene constanten 
SYMBOL HoogsteGetal = 6       ;Bepaalt de maximale waarde die getrokken kan worden
SYMBOL LaagsteGetal = 1       ;Bepaalt de minimale waarde die getrokken kan worden 

;Compiler berekeningen
SYMBOL DeelFactor   = 65535 / ((HoogsteGetal + 1) - LaagsteGetal) ;Bereken waarde voor deling

;Variabele declareren
;WORD
DIM x               AS WORD   ;Met WORD variabele 'x' gaan we de randomizer schudden
;BYTE
DIM RND             AS BYTE   ;In 'RND' komt steeds een willekeurig getal
DIM Teller          AS BYTE   ;Teller voor de FOR...NEXT lus

x = x + EREAD 0               ;'x' voor CLEAR, 'x' is dus nog niet gewist (= willekeurig!)
EWRITE 0, [x]                 ;Schrijf het willekeurige getal in 'x' naar EEPROM adres 0 en 1
CLEAR                         ;Wis nu pas alle RAM geheugen
x = EREAD 0                   ;Lees EEPROM adres 0 en 1 (x is een WORD variabele) weer uit

DELAYMS 500                   ;LCD stabilisering 

GOTO Hoofdprogramma           ;Spring over de subroutine(s)


;Subroutine
Load_RND:
  FOR Teller = 1 TO 11        ;Toevalsgenerator 11x schudden
    SEED x + 2221             ;Verhoog 'x' met 2221, wat een nieuwe startwaarde oplevert
    x = RANDOM                ;Geef willekeurige waarde aan variabele 'x'
  NEXT
  RND = x / Deelfactor
  RND = RND + LaagsteGetal    ;Verhogen met laagste getal
  IF RND > HoogsteGetal THEN Load_RND ;Af en toe kan 'RND' hoger worden, dan nog een keer
RETURN


Hoofdprogramma:
CLS                           ;Display wissen

WHILE 1 = 1                   ;Oneindige lus
  GOSUB Load_RND              ;Laad 'RND' met een nieuwe willekeurige waarde

  PRINT AT 1, 1, @RND, " "    ;Plaats de decimale waarde van RND op het display
  DELAYMS 1000                ;Laat om de seconde een getal zien
WEND                          ;Terug naar WHILE

Soms lijkt het dat een getal langer dan anders blijft staan.
De oorzaak is simpel, er is dan hetzelfde getal getrokken als het vorige.


Nu even controleren of het wel eerlijk verloopt.
Het nu volgende programma trekt weer heel snel getallen uit 0 t/m 5.
Hierbij wordt op het display afgebeeld, hoe vaak ieder getal wordt getrokken.
Als één van de getallen 250 maal is getrokken, stopt het programma.
Op die manier is te bekijken of de PIC wel eerlijk en willekeurig de getallen trekt.
Wanneer steeds of heel vaak hetzelfde getal als eerste de 250 bereikt, of als er een getal ver achter blijft bij de rest, dan zit er iets niet goed in het 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

;Algemene constanten 
SYMBOL HoogsteGetal = 5       ;Bepaalt de maximale waarde die getrokken kan worden
SYMBOL LaagsteGetal = 0       ;Bepaalt de minimale waarde die getrokken kan worden 

;Compiler berekeningen
SYMBOL DeelFactor   = 65535 / ((HoogsteGetal + 1) - LaagsteGetal) ;Bereken delingswaarde

;Variabele declareren
;BYTE ARRAY
DIM Getal[6]        AS BYTE   ;Array bestaande uit 6 elementen
;WORD
DIM x               AS WORD   ;Met WORD variabele 'x' gaan we de randomizer schudden
;BYTE
DIM RND             AS BYTE   ;In 'RND' komt steeds een willekeurig getal
DIM Teller          AS BYTE   ;Teller voor de FOR...NEXT lus

x = x + EREAD 0               ;'x' voor CLEAR, 'x' is dus nog niet gewist (= willekeurig!)
EWRITE 0, [x]                 ;Schrijf het willekeurige getal in 'x' naar EEPROM adres 0 en 1
CLEAR                         ;Wis alle RAM geheugen
x = EREAD 0                   ;Lees EEPROM adres 0 en 1 (x is een WORD variabele) weer uit

DELAYMS 500                   ;LCD stabilisering 

GOTO Hoofdprogramma           ;Spring over de subroutine(s)


;Subroutine
Load_RND:
  FOR Teller = 1 TO 11        ;Toevalsgenerator 11x schudden
    SEED x + 2221             ;Verhoog 'x' met 2221, wat een nieuwe startwaarde oplevert
    x = RANDOM                ;Geef willekeurige waarde aan variabele 'x'
  NEXT
  RND = (x / Deelfactor) + LaagsteGetal ;Bereken waarde tussen Laagste- en 'HoogsteGetal'
  IF RND > HoogsteGetal THEN Load_RND   ;Af en toe kan 'RND' hoger worden, dan nog een keer
RETURN


Hoofdprogramma:
CLS                           ;Display wissen

REPEAT                        ;Herhaal...
  GOSUB Load_RND              ;Laad 'RND' met een nieuwe willekeurige waarde

  INC Getal[RND]              ;Verhoog de teller van het getrokken getal (0...5) met 1

  PRINT AT 1, 1,  @Getal[0]   ;Geef op display weer hoe vaak er een 0 is getrokken
  PRINT AT 1, 6,  @Getal[1]   ;Geef op display weer hoe vaak er een 1 is getrokken
  PRINT AT 1, 11, @Getal[2]   ;Geef op display weer hoe vaak er een 2 is getrokken
  PRINT AT 2, 1,  @Getal[3]   ;Geef op display weer hoe vaak er een 3 is getrokken
  PRINT AT 2, 6,  @Getal[4]   ;Geef op display weer hoe vaak er een 4 is getrokken
  PRINT AT 2, 11, @Getal[5]   ;Geef op display weer hoe vaak er een 5 is getrokken
UNTIL Getal[RND] = 250        ;Totdat een zojuist getrokken getal 250 maal is getrokken
END                           ;Belangrijk dat deze END er nu staat

Dit doen we met behulp van een array met 6 elementen met de naam 'Getal' (zie cursus deel 6).
In de subroutine 'Load_RND' wordt een willekeurig getal uit 0 t/m 5 getrokken en aan de variabele 'RND' gegeven.
Twee regels van het vorige voorbeeld: RND = x / DeelFactor en RND = RND + LaagsteGetal
zijn hier samengevoegd tot één regel: RND = (x / DeelFactor) + LaagsteGetal

In de regel INC Getal[RND] wordt de waarde van de arrayvariabele die net getrokken is, met 1 verhoogt.
Dus stel dat het zojuist getrokken getal (dat steeds in de variabele 'RND' staat) een 4 is, dan wordt arrayvariabele 'Getal[4]' met 1 verhoogt.
In 'Getal[4]' komt dus te staan, hoe vaak er een 4 wordt getrokken.
Zo staat straks in 'Getal[0]' hoe vaak er een 0 is getrokken, in 'Getal[1]' hoe vaak er een 1 is getrokken, enz.

Bij PRINT AT... wordt de actuele waarde van elke arrayvariabele op het display gezet.
Op displayregel 1 staat hoe vaak achtereenvolgens de getallen 0, 1 en 2 zijn getrokken en op displayregel 2 hoe vaak 3, 4 en 5 zijn getrokken.

243   218   250
241   233   241

De REPEAT ... UNTIL lus voert de lus net zolang uit totdat één van de getallen de 250 heeft bereikt.
Steeds als er een getal is getrokken, wordt bij UNTIL Getal[RND] bekeken of het zojuist getrokken getal de 250 heeft bereikt.
In bovenstaand displayvoorbeeld is 'Getal[0]' 243 maal getrokken, 'Getal[1]' is 218 maal getrokken, enz.
Omdat 'Getal[2]' als eerste de 250 heeft gehaald, wordt de lus beëindigt en wordt verder gegaan met de eerstvolgende instructie.

Die eerstvolgende instructie is END, waardoor het PIC programma beëindigt wordt (de PIC gaat in low-power mode).
Denk eraan dat het nu belangrijk is dat END ook wordt geplaatst omdat het programma uit de REPEAT ... UNTIL lus springt zodra één van de getallen 250 maal is getrokken (of gegooid, of hoe je het ook noemt).
Je kunt het testprogramma steeds opnieuw starten door de spanning even van de PIC te halen, je zult dan ook zien, dat steeds een ander willekeurig getal als eerste de 250 bereikt.


Rad van fortuin (2)
Kon je in de vorige versie van Rad van fortuin nog inschatten bij welke LED je de toets in moest drukken om even later een andere LED als "winnaar" uit de bus te laten komen, dat zal bij het volgende voorbeeld niet zo makkelijk meer gaan, omdat door RANDOM de remsnelheid steeds een beetje anders is.
De ene keer zal het rad wat sneller afremmen dan een andere keer, waardoor niet meer in te schatten is bij welke LED je 'Toets' in moet drukken:

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 FALSE        = 0       ;Niet waar
SYMBOL HOOG         = 1       ;Hoog signaal
SYMBOL LAAG         = 0       ;Laag signaal
SYMBOL TRUE         = 1       ;Waar

;Algemene constanten 
SYMBOL RadSnelheid  = 50      ;mSec: Bepaalt de snelheid van het rad (lager getal = sneller)

;Poortnamen
SYMBOL Toets        = PORTA.1 ;Met deze pulstoets start en stop je het rad van fortuin

;Variabelen declareren
;WORD
DIM Tijd            AS WORD   ;Wachttijd die steeds langer wordt als 'Toets' is ingedrukt
;BYTE
DIM Poort           AS BYTE   ;Bevat 1,2,4,8,16,32,64 of 128 afhankelijk welke poort hoog is
DIM RND             AS BYTE   ;Bevat de uitkomst van de berekening met RANDOM
DIM Teller          AS BYTE   ;Teller voor de FOR...NEXT lus
;BIT
DIM Stoppen         AS BIT    ;Als dit bit '1' is, dan gaat het rad afremmen om te stoppen

;        76543210 
PORTB = %00000000             ;Eerst alle PORTB uitgang registers uit (laag maken)
TRISB = %00000000             ;Alle PORTB poorten zijn uitgangen

CLEAR                         ;Wis alle RAM geheugen


;Hoofdprogramma
SEED 2221                     ;Geef random generator een startwaarde

WHILE 1 = 1                   ;Oneindige (hoofd)lus
  Stoppen = FALSE             ;Reset BIT variabele 'Stoppen' (FALSE) voor nieuwe ronde
  Tijd    = RadSnelheid       ;Het rad (weer) op de opgegeven hoge snelheid instellen
  RND = (RANDOM / 3276)       ;Trek een getal uit 0 en 19
  WHILE 1 = 1                 ;Oneindige lus (totdat 'Tijd' boven ingestelde waarde komt)
    FOR Teller = 1 TO 8       ;We hebben acht LED's, tellen dus van 1 t/m 8
      Poort = Poort * 2       ;1, 2, 4, 8, 16, 32, 64, 128 (= steeds 1 LED verder)
      IF Poort = 0 THEN Poort = 1 ;Als laatste poort is geweest, dan weer met eerste verder
      PORTB = Poort           ;Laat de LED branden die in 'Poort' is aangegeven

      IF Toets = LAAG   THEN Stoppen = TRUE   ;Er moet gestopt worden als toets is ingedrukt
      IF Stoppen = TRUE THEN Tijd = Tijd + (Tijd / 10) + RND ;Bij stop, tijd steeds langer
      IF Tijd > 1000    THEN WinnaarBekend    ;Als tijd overschrijdt, dan naar WinnaarBekend
      DELAYMS Tijd            ;Wachttijd is variabel, wordt langer als op toets is gedrukt
    NEXT
  WEND                        ;Terug naar binnenste WHILE 1 = 1


WinnaarBekend:
  WHILE Toets = HOOG          ;Laat winnaar LED knipperen totdat op 'Toets' wordt gedrukt
    PORTB = Poort             ;Laat de LED branden die "winnaar" is
    DELAYMS 150               ;Tijd dat LED aan is
    PORTB = 0                 ;LED's uit
    DELAYMS 70                ;Tijd dat LED uit is
  WEND                        ;Terug naar binnenste WHILE (de WHILE Toets = HOOG)

  WHILE Toets = LAAG : WEND   ;Wacht eerst tot de toets weer is losgelaten
WEND                          ;Terug naar buitenste WHILE 1 = 1

 

Door een willekeurig getal te trekken met RANDOM en dit bij de afremtijd op te tellen zal het rad de ene keer iets sneller afremmen dan een andere keer, waardoor je niet meer in kunt schatten bij welke LED je de toets in moet drukken om een andere LED als winnaar uit de bus te laten komen.
Het verschil is maar miniem tussen het afremmen, maar net voldoende om het vooraf voorspellen onmogelijk te maken.
Het programma is verder precies hetzelfde als het eerste Rad van fortuin voorbeeld.
  

 


Schemerlamp met willekeurig knipperende 12V lampjes

Het schemerlampproject van deze site maakt ook gebruik van de functie RANDOM om af en toe eens (maximaal) één lampje uit te zetten (voor de gein).
Meestal branden alle lampjes gewoon.

Het programma werkt simpel.
Door RANDOM wordt een getal gekozen uit 1 ... 25.
Als het getrokken getal een 1 is, dan zal lampje 1 uitgezet worden, is het getal een 2, dan wordt lampje 2 uitgezet, enzovoort tot aan getal 6 voor lampje 6.
Elk ander getal dat getrokken wordt (7 ... 25) zal geen lampje uitzetten waardoor alle lampjes blijven branden.

Daarna wacht het programma een aantal minuten en zet dan alle lampjes weer aan (als er een lampje uit was, zal die dus weer aan gaan).
Hierna wacht het programma weer een aantal minuten waardoor het zeker is dat alle lampjes even aan zijn.
Door dit alles krijg je het effect dat er maar af en toe een lampje een paar minuten uit is.

De voorziening met de EEPROM is hier ook toegepast, zodat steeds als je de lamp inschakelt, het weer een verrassing is welk lampje er als eerste uit zal gaan.
 

Zowel het Basicprogramma als het HEX bestand is te downloaden bij het artikel van de "Lampicon" schemerlamp.

Meer info zelfbouw staande schemerlamp: De"Lampicon"



Onthouden welke getallen zijn geweest

De ingebouwde MP3 speler van het aloude Napster (op de PC) heeft een random functie die je kunt aanvinken, maar het programma Napster onthoud niet welke muzieknummers al zijn geweest.
Zo kan het gebeuren dat uit een lijst met bijvoorbeeld 20 verschillende muzieknummers eerst Madonna wordt afgespeeld, daarna Queen en daarop weer hetzelfde nummer van Madonna.

Shuffletoets Een CD-speler met een shuffle toets, speelt van een CD de tracks in willekeurige volgorde (in random order).
Als de CD-speler goed is ontworpen, dan onthoud het welke tracks er zijn geweest om te voorkomen dat na een aantal nummers hetzelfde nummer opnieuw wordt afgespeeld.

Om lotto of bingo getallen te trekken betekent het dat een eenmaal getrokken getal, daarna niet nog een keer getrokken mag worden.
De PIC moet de getallen die zijn getrokken dus onthouden.
Steeds als RANDOM dan een getal trekt, moet eerst worden bekeken of dit getal al eerder is getrokken en als dat het geval is dan zal nog een keer een getal met RANDOM moeten worden getrokken, net zolang totdat een getal wordt getrokken dat nog niet geweest is.
Hiervoor moeten we gebruikmaken van een paar PIC Basic instructies, die dit mogelijk maken.


Lotto / Bingo
Het onderstaand voorbeeld trekt elk getal van 1 t/m 8 maar één keer.

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 HOOG         = 1       ;Hoog signaal
SYMBOL LAAG         = 0       ;Laag signaal

;Algemene constanten 
SYMBOL HoogsteGetal = 7       ;BYTE: Bepaalt de maximale waarde die getrokken kan worden
SYMBOL LaagsteGetal = 0       ;BYTE: Bepaalt de minimale waarde die getrokken kan worden

;Compiler berekeningen
SYMBOL DeelFactor   = 65535 / ((HoogsteGetal + 1) - LaagsteGetal) ;Bereken de deelfactor

;Poortnamen
SYMBOL Toets        = PORTA.1 ;Deze pulstoets laat een willekeurig getal op het display zien

;Variabelen declareren
;WORD
DIM x               AS WORD   ;Met WORD variabele 'x' wordt de randomizer geschud
;BYTE
DIM ReedsGeweest    AS BYTE   ;Bevat de 8 bits die bijhouden welke getallen al zijn geweest
DIM RND             AS BYTE   ;Bevat een willekeurig getal (0...7)
DIM Teller          AS BYTE   ;Teller voor de FOR...NEXT lus
;BIT
DIM ID1             AS BIT    ;bIt Dummy 1 (Hulpbitje)

x = x + EREAD 0               ;'x' voor CLEAR, 'x' is dus nog niet gewist (= willekeurig!)
EWRITE 0, [x]                 ;Schrijf het willekeurige getal in 'x' naar EEPROM adres 0 en 1
CLEAR                         ;Nu pas het RAM geheugen wissen
x = EREAD 0                   ;Lees EEPROM adres 0 en 1 (x is een WORD variabele) weer uit

DELAYMS 500                   ;LCD stabilisering 

GOTO Hoofdprogramma           ;Spring over de subroutine(s)


;Subroutine
Load_RND:
  FOR Teller = 1 TO 11        ;Toevalsgenerator 11x schudden
    SEED x + 2221             ;Verhoog 'x' met 2221, wat een nieuwe startwaarde oplevert
    x = RANDOM                ;Geef willekeurige waarde aan variabele 'x'
  NEXT
  RND = (x / Deelfactor) + LaagsteGetal ;Bereken waarde tussen Laagste- en 'HoogsteGetal'
  IF RND > HoogsteGetal THEN Load_RND   ;Af en toe kan 'RND' hoger worden, dan nog een keer
RETURN


Hoofdprogramma:
CLS                           ;Display wissen
PRINT "Druk op de toets"      ;Plaats eerste bericht op het display

WHILE 1 = 1                   ;Oneindige lus
  WHILE Toets = HOOG : WEND   ;Wacht op een toetsindruk
' DELAYMS 10                  ;Contactontdendering bij indrukken van de toets
  CLS                         ;Wis display, tevens tijd tegen contactdender toets (10mSec)

  REPEAT                      ;Herhaal onderstaande lus
    GOSUB Load_RND            ;Geef een willekeurig getal (0...7) aan 'RND'
    ID1 = GETBIT ReedsGeweest, RND ;Geef een '0' of een '1' aan dummy variabele 'ID1'
  UNTIL ID1 = 0               ;Is 'ID1' niet '0' dan opnieuw getal trekken (herhaal lus)

  SETBIT ReedsGeweest, RND    ;Maak bit van getrokken getal '1'

  PRINT AT 1, 1, DEC RND + 1  ;Plaats decimale waarde van 'RND' + 1(!) 0...7 wordt dan 1...8

  IF ReedsGeweest = %11111111 THEN   ;Zijn alle getallen geweest (= alle bits aan), dan...
    PRINT AT 2, 1, "Allen getrokken" ;Plaats tekst op display regel 2
    DELAYMS 4000                     ;Wacht 4 seconden, en dan...
    PRINT AT 1, 1, "Druk toets voor" ;Plaats tekst op display regel 1
    PRINT AT 2, 1, "nog een ronde! " ;Plaats tekst op display regel 2
    CLEAR ReedsGeweest               ;Zet alle bits weer op 0 (%00000000)
  ENDIF

  WHILE Toets = LAAG : WEND   ;Wacht totdat de toets wordt losgelaten
  DELAYMS 20                  ;Contactontdendering bij loslaten van de toets
WEND                          ;Terug naar WHILE

Onthouden van getallen die al geweest zijn doen we door bitjes van een extra variabele op '1' te zetten.
Omdat we maar van acht getallen hoeven te onthouden welke al eens zijn getrokken kunnen deze waarden onthouden worden in een BYTE (= 8 bits) variabele, die we hier 'ReedsGeweest' (al geweest) noemen.

Het idee is zo:
BYTE variabele 'ReedsGeweest' is 0 (oftewel, binair %00000000), alle acht bitjes op 0.
We laten RANDOM een getal trekken die (na terugrekenen) een getal oplevert van 0 t/m 7, laten we hier zeggen dat het eerste getrokken getal een 5 is.
Dan gaan we eerst kijken of bit 5 van 'ReedsGeweest' een '0' is, zo ja, dan zetten we bit 5 van 'ReedsGeweest' op '1' en plaatsen we het getal 5 op het display.
Variabele 'ReedsGeweest' is nu dus %00100000 (bitnummering = 76543210).

Stel dat het volgende getal dat door RANDOM is getrokken toevallig weer een 5 is.
Dan gaan we eerst weer kijken of bit 5 van 'ReedsGeweest' een '0' is.
Als dat bit al '1' blijkt te zijn, laten we RANDOM opnieuw een getal trekken, net zolang tot een getal wordt getrokken waarvan het bit van 'ReedsGeweest' nog op '0' staat, waardoor we weten dat dat getal nog niet eerder is getrokken.

Om te bekijken of een bit een '0' of een '1' is, gebruiken we de functie GETBIT (= haal bit).
En het op '1' zetten (= set) van een bit in een variabele kan met de instructie SETBIT.

Je kunt van dit voorbeeld niet de waarde 7 van 'HoogsteGetal' en de 0 van 'LaagsteGetal' zomaar wijzigen in een ander getal.
Dit voorbeeld kan namelijk maximaal 8 getallen onthouden (een BYTE = 8 bits), dus een groter getal kan niet.
Een lager getal gaat ook niet, want het programma wil nu eenmaal alle acht bitjes van 'ReedsGeweest' op '1' hebben.
Stel dat je een lager getal invult, bijvoorbeeld 7, dan zal het programma maar blijven wachten tot nummer 8 een keer verschijnt, maar die komt dus nooit.
Het lijkt er dan op dat de PIC is gecrasht (= vastgelopen), omdat deze ogenschijnlijk "niets" meer doet, maar de PIC wacht (oneindig) op nummer 8.

De in grijs weergegeven tijd voor de contactontdendering kan vervallen, er zit namelijk al een wachttijd van 10mSec in de CLS instructie ingebouwd.


Lotto / Bingo met 40 cijfers
Bovenstaande manier kan maximaal 32 getallen trekken en onthouden (bij gebruik van een DWORD variabele).
Wil je meer getallen onthouden, dan moet het onthouden met behulp van bits over meerdere variabelen worden verdeeld.

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 FALSE        = 0       ;Niet waar
SYMBOL HOOG         = 1       ;Hoog signaal
SYMBOL LAAG         = 0       ;Laag signaal
SYMBOL TRUE         = 1       ;Waar

;Compiler berekeningen
SYMBOL DeelFactor   = 65535 / 40 ;40 getallen trekken (niet in een andere waarde wijzigen)

;Poortnamen
SYMBOL Toets        = PORTA.1 ;Deze pulstoets laat een willekeurig getal op het display zien

;Variabelen declareren
;DWORD
DIM ReedsGeweest1   AS DWORD  ;Houd van de eerste 32 getallen bij welke al zijn geweest
;BYTE
DIM ReedsGeweest2   AS BYTE   ;Houd van de laatste 8 getallen bij welke al zijn geweest
DIM RND             AS BYTE   ;Bevat een willekeurig getal (0...39)
;BIT
DIM NieuwGetal      AS BIT    ;TRUE als er een niet eerder getrokken getal gevonden is

CLEAR                         ;Wis alle RAM geheugen
DELAYMS 500                   ;LCD stabilisering 


;Hoofdprogramma
CLS                           ;Display wissen
PRINT "Druk op de toets"      ;Plaats eerste bericht op het display

WHILE 1 = 1                   ;Oneindige lus
  WHILE Toets = HOOG: WEND    ;Wacht op een toetsindruk
' DELAYMS 10                  ;Contactontdendering bij indrukken van de toets
  CLS                         ;Wis display, tevens tijd tegen contactdender toets (10mSec)

  REPEAT                      ;Herhaal lus (tot een niet eerder getrokken getal gevonden is)
    RND = (RANDOM / DeelFactor) ;Geef een willekeurig getal (0...39) aan 'RND'
    NieuwGetal = FALSE        ;Maak 'NieuwGetal' FALSE voor een nieuwe zoekronde
    SELECT RND                ;Vervolgens 'RND' waarde testen
      CASE < 32               ;Is de waarde van 'RND' kleiner dan 32, dan...
        IF GETBIT ReedsGeweest1, RND = 0 THEN ;Als het getrokken bit op '0' staat, dan...
           SETBIT ReedsGeweest1, RND          ;...dat bit op '1' zetten,
           NieuwGetal = TRUE  ;'NieuwGetal' TRUE, hierdoor beeindigt de REPEAT...UNTIL lus
        ENDIF

      CASE ELSE
        IF GETBIT ReedsGeweest2, RND - 32 = 0 THEN ;Als het bit op '0' staat, dan...
           SETBIT ReedsGeweest2, RND - 32          ;...dat bit op '1' zetten,
           NieuwGetal = TRUE  ;'NieuwGetal' TRUE, hierdoor beeindigt de REPEAT...UNTIL lus
        ENDIF
    END SELECT
  UNTIL NieuwGetal = TRUE     ;'NieuwGetal' TRUE, een niet eerder getrokken getal is gevonden

  PRINT AT 1, 1, DEC RND + 1, "  "   ;Plaats de decimale waarde van 'RND' + 1(!)

  IF ReedsGeweest1 = $FFFFFFFF AND ReedsGeweest2 = $FF THEN ;Alle bits '1', dan...
    PRINT AT 2, 1, "Allen getrokken" ;Plaats tekst op display regel 2
    DELAYMS 4000                     ;Wacht 4 seconden, en dan...
    PRINT AT 1, 1, "Druk toets voor" ;Plaats tekst op display regel 1
    PRINT AT 2, 1, "nog een ronde! " ;Plaats tekst op display regel 2
    CLEAR ReedsGeweest1              ;Zet alle 32 bits weer op '0'
    CLEAR ReedsGeweest2              ;Zet alle  8 bits weer op '0'
  ENDIF

  WHILE Toets = LAAG : WEND   ;Wacht totdat de toets wordt losgelaten
  DELAYMS 20                  ;Contactontdendering bij loslaten van de toets
WEND                          ;Terug naar WHILE

Ook hier kun je niet klakkeloos de waarde 40 in dit programma wijzigen in een ander getal.
Dit voorbeeld kan namelijk maximaal 40 getallen onthouden (door een DWORD (= 32 bits) + een BYTE (= 8 bits).
Een lager getal zal ook niet gaan, want het programma wacht tot alle 40 getallen zijn getrokken, maar de hogere getallen zullen dan nooit komen.
Je moet dit programmaatje dan ook zien als voorbeeld.

Het programma bevat geen schudroutine want dan wordt het te groot voor de PIC Basic LITE versie.
De resultaten in dit voorbeeld zullen daarom voorspelbaar zijn en steeds weer met dezelfde getallen beginnen als de PIC uitgeschakeld is geweest, juist omdat de schudroutine en EEPROM handeling er niet bij in zitten.
Mensen die de FULL versie hebben, moeten de schudroutine en EEPROM handeling er zelf bij inschrijven om mooie resultaten te krijgen, eventueel met behulp van het vorig voorbeeld.

In het programma staat IF ReedsGeweest1 = $FFFFFFFF THEN...
Dit staat in hexadecimale notatie (door het $ teken) om het overzichtelijk te houden.
Er had ook kunnen staan IF ReedsGeweest1 = %11111111111111111111111111111111 THEN... (met 32× '1') maar dat maakt het er niet duidelijker op, daarom dus hexadecimaal.

Met een array kan het mooier, maar de GETBIT en SETBIT instructies ondersteunen (nog) geen array's.


Het stokkenspel
Bij het stokkenspel is het de bedoeling om stokken te vangen.
Aan een balk hangen 7 of 8 stokken aan bijvoorbeeld elektromagneten.
Als de presentator op een toets drukt laat de PIC eerst één seconde een toeter horen, zodat de kandidaat weet dat er tussen 2 en 6 seconden vanaf nu (= random tijd) een willekeurige stok (= random uitgang) zal vallen.
Niemand weet wanneer en welke stok er gaat vallen.
Voor elke stok krijgt de kandidaat een prijs (of punten), de kandidaat moet dus proberen zoveel mogelijk stokken te vangen.
Er valt steeds maar één stok na een toetsindruk.

De PIC stuurt het geheel aan.
Bij de start zijn alle uitgangen hoog, alle elektromagneten zijn dus geactiveerd.
Elke keer als er op de toets is gedrukt, wordt er na een willekeurige tijd één willekeurige uitgang laag gemaakt.
De PIC moet dus onthouden welke stokken al zijn gevallen.
Dat gaat in dit geval simpel door eerst te kijken of de willekeurig getrokken uitgang al laag is.
Is dat het geval, dan moet er opnieuw getrokken worden, net zolang tot er een uitgang wordt getrokken, dat nog hoog is.
Is er een uitgang gevonden dat nog hoog is, dan wordt deze uitgang laag gemaakt met CLEARBIT (de stok valt).

Met GETBIT kunnen we bekijken of de uitgang al laag is (= elektromagneet uit, dus stok al weg).
Het programmavoorbeeld gaat uit van zeven stokken (PORTB.1 t/m PORTB.7).
Op PORTA.1 zit de starttoets, als hier op wordt gedrukt, maakt de PIC weer een willekeurige uitgang laag.
Als de allerlaatste stok gevallen is, worden na één seconde alle zeven uitgangen weer hoog gemaakt, zodat de stokken weer opgehangen kunnen worden voor een nieuwe ronde.

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 HOOG         = 1       ;Hoog signaal
SYMBOL LAAG         = 0       ;Laag signaal

;Algemene constanten 
SYMBOL ClaxonTijd   = 1000    ;mSec: Bepaalt de tijd dat de toeter gaat na een toetsindruk 
SYMBOL HoogsteGetal = 7       ;Bepaalt de maximale waarde die getrokken kan worden
SYMBOL LaagsteGetal = 1       ;Bepaalt de minimale waarde die getrokken kan worden 

;Compiler berekeningen
SYMBOL DeelFactor   = 65535 / ((HoogsteGetal + 1) - LaagsteGetal) ;Bereken deel waarde

;Poortnamen
SYMBOL Toets        = PORTA.1 ;Deze pulstoets maakt een willekeurige PORTB uitgang laag
SYMBOL Claxon       = PORTA.2 ;Deze uitgang stuurt een claxon (= toeter) aan

;Variabele declareren
;WORD
DIM x               AS WORD   ;Met WORD variabele 'x' gaan we de randomizer schudden
;BYTE
DIM RND             AS BYTE   ;In 'RND' komt steeds een willekeurig getal
DIM Teller          AS BYTE   ;Teller voor de FOR...NEXT lus
;BIT
DIM ID1             AS BIT    ;ID1 = bIt Dummy 1 (een hulpbitje)

x = x + EREAD 0               ;'x' voor CLEAR, 'x' is dus nog niet gewist (= willekeurig!)
EWRITE 0, [x]                 ;Schrijf het willekeurige getal in 'x' naar EEPROM adres 0 en 1
CLEAR                         ;Wis alle RAM geheugen
x = EREAD 0                   ;Lees EEPROM adres 0 en 1 (x is een WORD variabele) weer uit

;        76543210 
PORTA = %00000000             ;Alle PORTA uitgangen laag bij opstart
PORTB = %11111110             ;Bij opstart 7 PORTB uitgangen (van de stokken) hoog maken
TRISA = %11111011             ;PORTA.2 is uitgang voor de claxon (toeter)
TRISB = %00000001             ;Alle PORTB poorten zijn uitgangen, behalve PORTB.0

GOTO Hoofdprogramma           ;Spring over de subroutine(s)


;Subroutine
Load_RND:
  FOR Teller = 1 TO 11        ;Toevalsgenerator 11x schudden
    SEED x + 2221             ;Verhoog 'x' met 2221, wat een nieuwe startwaarde oplevert
    x = RANDOM                ;Geef willekeurige waarde aan variabele 'x'
  NEXT
  RND = (x / Deelfactor) + LaagsteGetal ;Bereken waarde tussen Laagste- en 'HoogsteGetal'
  IF RND > HoogsteGetal THEN Load_RND   ;Af en toe kan 'RND' hoger worden, dan nog een keer
RETURN


Hoofdprogramma:
WHILE 1 = 1                   ;Oneindige lus
  WHILE Toets = HOOG : WEND   ;Wacht op een toetsindruk

  Claxon = HOOG               ;Toeter aanzetten (Uitgang 'Claxon' hoog maken)
  DELAYMS ClaxonTijd          ;Effen de opgegeven tijd wachten
  Claxon = LAAG               ;Toeter weer uitzetten (Uitgang 'Claxon' laag maken)

  DELAYMS 2000 + (RANDOM / 20) ;Minimaal 2 seconden, maximaal ongeveer 6 seconden

  REPEAT                      ;Herhaal onderstaande lus
    GOSUB Load_RND            ;Laad 'RND' met een nieuwe willekeurige waarde
    ID1 = GETBIT PORTB, RND   ;'ID1' wordt '0' of '1' afhankelijk of PORTB laag of hoog is
  UNTIL ID1 = 1               ;Herhaal lus totdat de gekozen uitgang een hoge uitgang is

  CLEARBIT PORTB, RND         ;Maak getrokken bit van PORTB laag (= '0' oftewel CLEAR BIT)

  IF PORTB & %11111110 = 0 THEN ;Alle 7 stokken gevallen (alle 7 uitgangen laag), dan...
    DELAYMS 1000              ;Wacht 1 seconde, en dan...
    PORTB = %11111110         ;Alle 7 uitgangen weer hoog maken (stokken kunnen weer hangen)
  ENDIF

  WHILE Toets = LAAG : WEND   ;Wacht totdat de toets wordt losgelaten
  DELAYMS 20                  ;Contactontdendering bij loslaten van de toets
WEND                          ;Terug naar WHILE

Bij TRISB worden PORTB.1 t/m PORTB.7 uitgang gemaakt, die meteen al een hoog niveau hebben.
Dat komt omdat twee regels eerder met PORTB = %11111110 deze zeven bits van het PORTB register een hoog niveau hebben gekregen.

Dan het hoofdprogramma.
Eerst wacht het op de bekende manier op een toetsindruk.

Wordt de toets ingedrukt, dan loopt het programma verder en zet dan meteen de claxon (= toeter) aan.
Dan wacht het programma één seconde, en zet dan de claxon weer uit.

Nu wordt opnieuw gewacht (maar nu dus met de claxon uit).
Deze tijd is steeds anders, maar wel tussen 2 en 6 seconden.
Met RANDOM wordt een waarde uit 1 ... 65535 getrokken, dat weer wordt gedeeld door 16, dat is maximaal ongeveer 4000 (mSec), oftewel 4 seconden.
Bij de wachttijd wordt nog 2000mSec opgeteld waardoor de minimale tijd altijd twee seconden is en de maximale tijd (4 + 2) nu zes seconden is.
Vergeet niet de haakjes ( ) om (RANDOM / 20).

Je zou kunnen denken, waarom bitdummy 'ID1' nodig is.
Theoretisch zou onderstaande (zonder 'ID1') moeten werken, maar in de praktijk werkt het niet 100%.
Daarom gebeurt het via een dummy.

  REPEAT                      ;Herhaal onderstaande lus
    GOSUB Load_RND            ;Laad 'RND' met een nieuwe willekeurige waarde
  UNTIL (GETBIT PORTB, RND)   ;Herhaal lus totdat de gekozen uitgang een hoge uitgang is 

Meer info dummy's


Acht LED's op PORTB die willekeurig knipperen.

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

;Algemene constanten 
SYMBOL HoogsteGetal = 255     ;Bepaalt de maximale waarde die getrokken kan worden
SYMBOL LaagsteGetal = 1       ;Bepaalt de minimale waarde die getrokken kan worden 

;Compiler berekeningen
SYMBOL DeelFactor   = 65535 / ((HoogsteGetal + 1) - LaagsteGetal) ;Bereken waarde voor deling

;Variabele declareren
;WORD
DIM x               AS WORD   ;Met WORD variabele 'x' gaan we de randomizer schudden
;BYTE
DIM RND             AS BYTE   ;In 'RND' komt steeds een willekeurig getal
DIM Teller          AS BYTE   ;Teller voor de FOR...NEXT lus

x = x + EREAD 0               ;'x' voor CLEAR, 'x' is dus nog niet gewist (= willekeurig!)
EWRITE 0, [x]                 ;Schrijf het willekeurige getal in 'x' naar EEPROM adres 0 en 1
CLEAR                         ;Wis alle RAM geheugen
x = EREAD 0                   ;Lees EEPROM adres 0 en 1 (x is een WORD variabele) weer uit

;        76543210 
TRISB = %00000000             ;Alle PORTB poorten zijn uitgangen

GOTO Hoofdprogramma           ;Spring over de subroutine(s)


;Subroutine
Load_RND:
  FOR Teller = 1 TO 11        ;Toevalsgenerator 11x schudden
    SEED x + 2221             ;Verhoog 'x' met 2221, wat een nieuwe startwaarde oplevert
    x = RANDOM                ;Geef willekeurige waarde aan variabele 'x'
  NEXT
  RND = (x / Deelfactor) + LaagsteGetal
  IF RND > HoogsteGetal THEN Load_RND ;Af en toe kan 'RND' hoger worden, dan nog een keer
RETURN


Hoofdprogramma:
WHILE 1 = 1                   ;Oneindige lus
  GOSUB Load_RND              ;Laad 'RND' met een nieuwe willekeurige waarde

  PORTB = RND                 ;Plaats de waarde van RND op de 8 PORTB uitgangen
  DELAYMS 100                 ;Knippertijd
WEND                          ;Terug naar WHILE

Er wordt hier een getal uit 1 ... 255 getrokken, waarna deze waarde rechtstreeks aan PORTB wordt gegeven.
Het resultaat ziet eruit als de vele knipperende lampjes in een ruimteschip.
Als je DELAYMS op 60000 (= 60 seconden, dus 1 minuut) zet, dan zou je op een modelbaan de verlichting in huisjes willekeurig aan en uit kunnen laten gaan.


Moeten er vaker getallen getrokken worden, maar tussen verschillende waarden?
Dus, de ene keer een getal trekken uit bijvoorbeeld 10 en 30, en in een ander deel van het programma uit 100 en 200?
Dan moet je 'HoogsteGetal' en 'LaagsteGetal' als variabele declareren (met DIM) en niet opgeven als een constante.

Onderstaande programma laat weer een LED knipperen.
De tijd dat de LED aan is zal steeds een willekeurige tijd zijn tussen 10 en 30mSec, en de tijd dat de LED uit is tussen 100 en 200mSec.
Je kunt die tijden natuurlijk aanpassen naar eigen wens.
Let er op, als een tijd groter moet zijn dan 255mSec, dat je 'HoogsteGetal' (en misschien ook 'LaagsteGetal') dan als een WORD variabele declareert.

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 ON           = 1
SYMBOL OFF          = 0

;Poortnamen
SYMBOL LED          = PORTA.2 ;De LED met serieweerstand aansluiten tussen PORTA.2 en GND

;Variabelen declareren
;WORD
DIM x               AS WORD   ;Met WORD variabele 'x' gaan we de randomizer schudden
;BYTE
DIM HoogsteGetal    AS BYTE   ;Hierin kun je IN het programma de langste tijd opgeven
DIM LaagsteGetal    AS BYTE   ;Hierin kun je IN het programma de kortste tijd opgeven
DIM RND             AS BYTE   ;In 'RND' komt steeds een willekeurig getal
DIM Teller          AS BYTE   ;Teller voor de FOR...NEXT lus

x = x + EREAD 0               ;'x' voor CLEAR, 'x' is dus nog niet gewist (= willekeurig!)
EWRITE 0, [x]                 ;Schrijf het willekeurige getal in 'x' naar EEPROM adres 0 en 1
CLEAR                         ;Wis alle RAM geheugen
x = EREAD 0                   ;Lees EEPROM adres 0 en 1 (x is een WORD variabele) weer uit

;        76543210 
TRISA = %11111011             ;PORTA.2 is uitgang voor de LED

GOTO Hoofdprogramma           ;Spring over de subroutine


;Subroutine
Load_RND:
  FOR Teller = 1 TO 11        ;Toevalsgenerator 11x schudden
    SEED x + 2221             ;Verhoog 'x' met 2221, wat een nieuwe startwaarde oplevert
    x = RANDOM                ;Geef willekeurige waarde aan variabele 'x'
  NEXT
  RND = (x / (65535 / ((HoogsteGetal + 1) - LaagsteGetal))) + LaagsteGetal
  IF RND > HoogsteGetal THEN Load_RND ;Af en toe kan 'RND' hoger worden, dan nog een keer
RETURN


Hoofdprogramma:
WHILE 1 = 1                   ;Oneindige lus
  LED = ON                    ;Eerst LED aanzetten
  HoogsteGetal = 30           ;Maximum tijd in milliseconden dat de LED aan mag zijn
  LaagsteGetal = 10           ;Minimum tijd in milliseconden dat de LED aan mag zijn
  GOSUB Load_RND              ;Laad 'RND' met een nieuwe willekeurige waarde
  DELAYMS RND                 ;Wacht nu tussen 10 en 30mSec (waarde van RND)

  LED = OFF                   ;Eerst LED uitzetten
  HoogsteGetal = 200          ;Maximum tijd in milliseconden dat de LED uit mag zijn
  LaagsteGetal = 100          ;Minimum tijd in milliseconden dat de LED uit mag zijn
  GOSUB Load_RND              ;Laad 'RND' met een nieuwe willekeurige waarde
  DELAYMS RND                 ;Wacht nu tussen 100 en 200mSec (waarde van RND)
WEND

De PIC moet nu zelf de deelfactor uitrekenen (in de subroutine), omdat 'HoogsteGetal' en 'LaagsteGetal' nu variabel zijn.

Het resultaat van bovenstaande is een onregelmatige knipperfrequentie die je mooi kunt toepassen door twee lampjes in de ogen van een doodskop of griezelmasker te plaatsen.


Dubbelsteen
Onderstaand voorbeeld toont op een HD44780 display (minimaal 2 x 16) een dubbele dobbelsteen.
Zolang je de toets ingedrukt houdt, zullen op het display de twee dobbelstenen rollen.
Laat je de toets los, dan staan de twee dobbelstenen stil met elk een willekeurige waarde.

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

;Poortnamen
SYMBOL Toets        = PORTA.1 ;Bij het loslaten van deze toets staan de 2 dobbelstenen stil

;Variabelen declareren
DIM Dobbelsteen     AS BYTE   ;Deze variabele bevat de waarde voor beide dobbelstenen
DIM X_Positie       AS BYTE   ;Positie van de beide dobbelstenen op het display (X-as)

DELAYMS 500                   ;LCD stabilisering 

;Dobbelsteen ogen tekenen
PRINT $FE,$40                         ;Schrijf naar HD44780 chip, vanaf eerste adres
PRINT $07,$07,$07,$00,$00,$00,$00,$00 ;CHR(0)
PRINT $07,$07,$07,$00,$00,$07,$07,$07 ;CHR(1)
PRINT $1C,$1C,$1C,$00,$00,$1C,$1C,$1C ;CHR(2)
PRINT $1C,$1C,$1C,$00,$00,$00,$00,$00 ;CHR(3)
PRINT $00,$07,$07,$07,$00,$00,$00,$00 ;CHR(4)
PRINT $00,$1C,$1C,$1C,$00,$00,$00,$00 ;CHR(5)
PRINT $00,$00,$00,$00,$00,$0E,$0E,$0E ;CHR(6)

CLS                           ;Displayscherm wissen
PRINT "Druk toets"

GOTO Hoofdprogramma           ;Spring over de subroutine


;Subroutine
TekenDobbelsteen:
  SELECT Dobbelsteen          ;Afhankelijk van de waarde van 'Dobbelsteen' de ogen tekenen
    CASE 1
      PRINT AT 1, X_Positie, " ", 6, "   "
      PRINT AT 2, X_Positie, "   "
      
    CASE 2
      PRINT AT 1, X_Positie, "  ", 3, "  "
      PRINT AT 2, X_Positie, 4, "  "
      
    CASE 3
      PRINT AT 1, X_Positie, " ", 6, 3, "  "
      PRINT AT 2, X_Positie, 4, "  "
      
    CASE 4
      PRINT AT 1, X_Positie, 0, " ", 3, "  "
      PRINT AT 2, X_Positie, 4, " ", 5
      
    CASE 5
      PRINT AT 1, X_Positie, 0, 6, 3, "  "
      PRINT AT 2, X_Positie, 4, " ", 5
      
    CASE ELSE
      PRINT AT 1, X_Positie, 1, " ", 2, "  "
      PRINT AT 2, X_Positie, 4, " ", 5
      
  END SELECT
RETURN


Hoofdprogramma:
  Dobbelsteen = (RANDOM / (65535 / 6)) ;continu schudden, getal uit 1...6 (dobbelsteen 1)
  IF Toets = 0 THEN           ;Als de toets wordt ingedrukt, dan...
    X_Positie = 1             ;Eerste dobbelsteen vanaf display positie 1 tekenen
    GOSUB TekenDobbelsteen    ;Dobbelsteen 1 tekenen op het display

    Dobbelsteen = (RANDOM / (65535 / 6)) ;Trek getal uit 1...6 voor dobbelsteen 2
    X_Positie = 6             ;Tweede dobbelsteen vanaf display positie 6 tekenen
    GOSUB TekenDobbelsteen    ;Dobbelsteen 2 tekenen op het display
  ENDIF

  DELAYMS 150                 ;Dobbelstenen langzamer laten rollen
GOTO Hoofdprogramma           ;Oneindige lus

De randomizer wordt continu geschud doordat er continu een getal (1 ... 6) wordt getrokken met RANDOM dat in variabele 'Dobbelsteen' geplaatst wordt.
Maar zolang de toets niet is ingedrukt, wordt er niets met dat getal in 'Dobbelsteen' gedaan.

Wordt nu de toets ingedrukt dan wordt eerst 'X_Positie' op 1 gezet en daarna naar de subroutine gesprongen.
Met variabele 'X_Positie' wordt opgegeven waar de subroutine de dobbelsteen op het display moet tekenen.
Omdat 'X_Positie' op 1 is gezet, zal de willekeurige waarde die 'Dobbelsteen' op dat moment in zich heeft, vanaf de eerste positie op het display worden getekend.
Hierna keert het programma terug naar het hoofdprogramma (door RETURN).

Nu wordt er opnieuw met RANDOM een getal getrokken (1 ... 6) voor de tweede dobbelsteen.
Dit keer wordt 'X_Positie' op 6 gezet en opnieuw naar de subroutine gesprongen om de tweede dobbelsteen te tekenen.
Omdat 'X_Positie' nu op 6 staat, zal de subroutine de dobbelsteen dit keer tekenen vanaf display positie 6.

Zolang de toets ingedrukt is, zal dit continu worden uitgevoerd, waardoor je heel snel verschillende waarden van de dobbelstenen op het display ziet.
Wordt de toets nu losgelaten, dan wordt er niet meer naar het display geschreven en zullen de twee dobbelstenen die het laatst naar het display waren gestuurd nu blijven staan.

De DELAYMS is er om het effect iets mooier te maken, anders "rollen" de dobbelstenen wel erg snel.
Wil je dat er ook andere waarden komen als je maar heel kort op de toets drukt, dan moet je de DELAYMS weghalen.


Volgens de HELP files van PIC Basic IDE moet RANDOM PietJanHein ook mogelijk zijn.
Het blijkt echter dat deze manier niet werkt, 'PietJanHein' blijft gewoon zijn oude waarde houden.
Daarom gewoon, zoals in deze cursus: PietJanHein = RANDOM.

Onthoud, ook al zijn de getallen nu behoorlijk willekeurig, het blijft pseudo-random.
Laat er dus geen grote geldbedragen van afhangen (bijv. bij een landelijke lotto).


Deel 11 verschijnt misschien nog wel eens een keer.
Veel plezier met PIC's programmeren.

Frits Kieftenbelt.