Achtergrond
Het is niet een van mijn moeilijkste projecten, maar het zijn wel een van de dingen die ik leuk vind aan mijn vak, vanuit een verhaal een schakeling maken. Het verhaal gaat als volgt: Op een avond wordt ik op gebeld, met de vraag “Jij ben handig he?” Nou willen wij een pub-quiz gaan houden met een groep vrienden en nu willen wij iets wat aangeeft wie het eerste het antwoord weet. Dat aangeeft wie het eerste heeft gedrukt. Je weet wel wat je ook vaak ziet op tv. Het moet vooral geluid maken en licht geven.
Ik ga in dit bericht een poging doen om uitleggen hoe ik normaal te werk gaat om tot een eind resultaat te komen zo als hieronder. Dit project is nog wel begrijpbaar als je niet een elektrotechnische achtergrond heb, ik hoop de vraag er mee te beantwoorden; wat doe jij nu eigenlijk.
Doel
Met zo een beschrijving moest ik iets maken, er ging gelijk verschillende mogelijkheden (technieken) door mijn hoofd, relais, logische poortjes, Raspberry pi of toch microcontroller. Een van de dingen waar ik ook aandacht is het moet vooral betrouwbaar zijn. Je wil geen discussie over wie toch voordeel heeft bij de ene knop ten opzichte van de ander. Het moet gewoon werken!
Ontwerp
Ik heb uiteindelijk gekozen voor de microcontroller omdat ik daarbij het beste de werking kon garanderen. Een microcontroller kan ik namelijk zo programmeren dat hij alle knoppen gelijke tijd uitleest en dan kijken wie het eerste is. Deze kan dit dan sturen naar een computer en daar kan ik gemakkelijk beeld en geluid uit toveren.
Zo als ik altijd doe maak ik dan eerst een schema. (dit is een overzicht over hoe alles elektrisch zit aangesloten) Een uitvergroting is hier te zien.
De schakeling wordt aangesloten met een micro-USB-kabel (zo een waar je je telefoon mee oplaad). Hier wordt hij ook van gevoed (de plek waar hij zijn elektriciteit vandaan haalt). Met S1 (links midden boven) kan hij aan en uitgezet worden, daarna wordt de spanning (de vertaling van het Engelse woord Voltage, Nederlandse woord bestaat niet, maar jammer genoeg wel eens gebruikt) omgezet naar een spanning
dat de microcontroller kan gebruiken. In de microcontroller kan ik zelf bepalen wat er gaat gebeuren, genaamd programmeren, maar dat doe ik achteraf (kom ik nog op terug) en het programma kan ik ook makkelijk wijzigen. Het IC1 (dat blokje links) zorgt er voor dat de microcontroller met de computer kan praten.
Aan de rechter kant van het schema zijn allemaal schakelaars te zien, van S0 t/m S5 (het gaat om het getal rechts boven). We hebben namelijk 5 spelers en een Quiz-master. In de laag er onder zijn ook schakelaars, deze zijn voor het ontwikkelen van het programma.
In het schema hier naast is een schakeling uitvergroot voor een druk knop. R1, R4 en C9 zorgen er voordat als je drukt je ook maar een keer drukt en niet 10 keer. Dit zorgt wel voor iets van vertraging. Dat berekenen ik dat met deze (voor mijn eenvoudige) formule, maar de vertraging is zo klein dat wij dat als mens niet merken. Meestal moet ik wat berekeningen doen om de juiste waarde van het component te kiezen.
![]()
Helemaal rechts onder zie je de 6 lichtjes (de gebruikte technologie is light-emitting diode of LED en worden waarom ook wel LED’s genoemd). Deze LED’s heb ik er opgeplaatst om aan te geven wie eerder heeft gedrukt. De weerstand er boven (aangeven met R3 t/m R18) is om de led niet te fel te laten branden (en de rest van de schakeling op te blazen).
Aan de rechter kan zijn de LEDs en de schakelaar (parallel) door verbonden. Zo dat ik uiteindelijk de deurbellen kan aansluiten, zo als in het eindresultaat te zien, kan aansluiten.
Helemaal links is nog een MOSFET (een soort van schakelaar dat de microcontroller kan bedienen) die was voor nog eventueel een zwaailicht aan te sluiten, dit is alleen nooit gebruikt. De overige componenten in het schema zijn nodig voor de microcontroller.
Dit schema zet ik dan met behulp van een computer programma om naar een print. Ik moet nog wel uit puzzelen hoe dat het makkelijkste gaat. De verschillende componenten (blokjes) van het schema plaats ik dan op de printplaat en verbind de sporen (draaitje op een printplaat) aan elkaar. Als je goed kijkt kan je alle componenten terug vinden van het schema. De meeste componenten van de printplaat zijn van het formaat 0805, dat komt overeen met 2.0 × 1.25 mm. De microcontroller is het middelste vierkante blokje en is 10 x 10 mm.
Voor de vorm en het formaat had ik de bekende 6 hoek gebruik van een kolonisten van catan steentje. Een zijde is 45 mm of 4.5 cm.
Realisatie
Deze tekening kan dan omgezet worden in een echte printplaat. Waar ik dan de componenten met de hand op soldeer, het is klein maar te doen. Het resultaat is hieronder te zien.

De print volledig gesoldeerd, (ik had drie poolige connectoren nodig maar ik had de verkeerde besteld daarom zit er nog een zilver blokje naast)
Programmeren
In het programma stuur ik de LED’s aan zo dat ze uitdagend knipperen (te zien in het filmpjes). Eerst knipperen alle LEDjes van alle speller, daarna gaat hij ze een voor een af. Ik zal een beetje oppervlakkig proberen uit te leggen hoe mijn programma (en daar bij programmeren) werkt. Programmeren is niet moeilijk, je moet het alleen zien. Het kan soms helpen om het hard op voor te lezen, met een beetje nadenken en kleine stukjes per keer bekijken kom je er wel uit.
Als een microcontroller een programma uitvoert gaat dat heel snel in dit geval 7372800 keer per seconden, dat hij een instructie uitvoert. (getal is gekozen vanwege de communicatie met de computer). De microcontroller voert een hele lijst met instructies achter elkaar uit, met programmeren stel de deze instructies in en als de microcontroller aanstaan gaat hij deze instructies achter elkaar uitvoeren. Hieronder is de code dat ik hier voor heb geschreven.
while(1){
//knipperen van de leds
if(time++ > 250){
state++;
if(state == 1){
PORTC = 0x00;
}else if (state == 2) {
PORTC = 0xFE;
}else if (state == 3) {
PORTC = 0x00;
}else if (state == 4) {
PORTC = 0xFE;
}else if (state == 5) {
PORTC = 0x02;
}else if (state < 10) {
PORTC=(PORTC<<1);
}else if (state < 14) {
PORTC=(PORTC>>1);
}else{
state = 0;
}
time = 0;
}
//uitlezen van de knoppen
port_temp = (~PIND&0xFC)>>2;
if(port_temp){
switch (port_temp) {
case _BV(0):
case _BV(1):
case _BV(2):
case _BV(3):
case _BV(4):
case _BV(5):
PORTC = port_temp;
printf("player %d\r\n", binary_depth(port_temp)-1);
break;
default:
printf("Gelijk %x\r\n", port_temp);
PORTC = ~port_temp;
break;
}
while(uart_getc() != UART_NO_DATA);
while((~PIND&0xFC)>>2 != _BV(0) && uart_getc() == UART_NO_DATA);
_delay_ms(500);
state = 1;
}
//wachten
_delay_ms(1);
}
Boven aan begin ik met “while” dat betekent blijf dit lijstje met instructies uitvoeren. Omdat de microcontroller zo snel is, laat ik aan het einde van de lus wachten. 1 ms (miliseconden = 0.0001 seconden). Tijdens “_delay_ms(1)” is de microcontroller aan het tellen tot dat hij verder mag gaan, net zo als bij verstoppertje.
Het bovenste stuk is voor het knipperen van de leds, eens in de 250 keer dat de microcontroller de lus af gaat, verandert hij de uitgang. Dat komt dus neer op ongeveer 250 ms of een kwart seconden. Dat gebeurd door middel van tellen, iedere keer dat hij “If(time++ > 250){” uitvoert, telt hij er één op bij het register “time” op en kijkt hij of time groter is dan 250, als dat zo is gaat hij het stukje(de stukjes beginnen met “{” tot “}”, om het makkelijk te maken is de code bij iedere “{” ingesprongen) er onder uitvoeren, als dat niet zo is gaat hij verder (na de “}”). Onder aan het uit te voeren stukje staat “time = 0” dat betekent dat time weer op nul wordt gezet (alleen af het stukje wordt uitgevoerd), zo dat hij weer opnieuw kan beginnen met tellen. Op deze manier wordt dat stukje van state eens in de 250 keer uitgevoerd.
De “state” teller staat in het uit te voeren stukje . Iedere keer bij het uitvoeren wordt hier ook 1 bij opgeteld bij “state” (gebeurd met “state++;”). Aan de had van de waarde van “state” wordt “PORTC” ingesteld, aan “PORTC” zijn de ledjes aangesloten, dus hiermee worden deze ledjes aan of uit-gezet. Welke leds dat zijn is aangegeven in hexadecimaal (begint hier met “0x”), 0x00 is voor geen led’s aan, 0x01 is de eerste led aan, 0x02 is de tweede led, 0x80 is de laatste led (is alleen niet aangesloten). Dat kan dan bij elkaar op geteld worden en kan je uit komen op 0x03 voor de eerste twee en 0xFF voor alle leds.
if(state == 1){
PORTC = 0x00;
}else if (state == 2) {
PORTC = 0xFE;
}else if (state == 3) {
PORTC = 0x00;
}else if (state == 4) {
PORTC = 0xFE;
}else if (state == 5) {
PORTC = 0x02;
}else if (state < 10) {
PORTC=(PORTC<<1);
}else if (state < 14) {
PORTC=(PORTC>>1);
}else{
state = 0;
}
Dat gebeurt in het uitgebreide “if” stukje. Als “state” gelijk is aan 1 voert hij alleen het eerste stukje uit (dit geval “PORTC = 0x00;”). Alleen als dat niet zo is kijkt hij of “state” twee is, alleen als dat waar is voert hij zijn eigen stukje uit (in dit geval “PORTC = 0xFE;”). Als dat ook niet waar is gaat hij kijken of”state” 3 is. (je moet het gewoon hard oplezen, dan hoor je het vanzelf)
Zo worden in state 1 t/m 5 alle leds aan of uitgezet. Zo lang “state” niet 1 t/m 5 is en kleiner is dan 10 ( 6 t/m 9 dus). Zet hij het volgende ledje aan door middel van “PORTC=(PORTC<<1);”. Hier staat heel simpel gezegd; lees wat er in “PORTC” staat (de “PORTC tussen in de haakjes), schuif er een op ( het “<<“) en zet dat weer in “PORTC”. De 0x02 die er in stond wordt nu 0x04 en gaat het volgende ledje branden. Als “state” groter (of gelijk) is dan 10 maar kleiner dan 14, schuift de microcontroller dat weer terug. Als hij groter of gelijk is dan 14 (in dit geval 15) wordt “state” weer nul en begint hij de volgende keer als time weer 250 is bij 0. bij “state++” dan gelijk weer op 1 gezet.
//uitlezen van de knoppen port_temp = (~PIND&0xFC)>>2;
Het stukje dat begint met “if(time++….” wordt wegens de teller eens in de 250 ms uitgevoerd. Het uitlezen van de knoppen zit buiten deze “if” voorwaarde en wordt dus wel iedere keer in de lus uitgevoerd. Het komt er in het kort op neer dat de knoppen iedere 1 ms (plus een beetje) wordt uitgevoerd. Het uitlezen van de knoppen gebeurd als volgt; “PIND” wordt uitgelezen (hierop zitten de knoppen aangesloten), dat draaien we om met “~” (programmeren kent net als rekenen ook een volgorde van uitvoeren). Dat is nodig omdat als er een knop ingedrukt wordt, hij niet 1 maar 0 wordt, dus met om draaien worden alle 1-tjes 0-etjes. We zijn maar geïnteresseerd in 6 knoppen dus maken we de rest 0 (met “&”). 0xFC is gelijk aan 0b11111100 in binair en daarmee kan je zien dat we niet geïnteresseerd zijn in de laatste twee. Maar de laagste twee cijfers zijn wel 00, dat willen we niet dus schuiven we alles twee keer op (met “>>2”). Uiteindelijk wordt dat in “port_temp” neergezet.
Een heel ingewikkeld verhaal, daarom een voorbeeld; S1 (zo als ik bij het schema had uitgelegd) is aangesloten op PD3, dat betekent dat hij op PIND zit aangesloten als 4de (we beginnen in de digitale wereld te tellen bij 0). Als deze knop dan is ingedrukt is de waarde van “PIND” gelijk aan 0b1111 0111. Dat draaien we om dan hebben we 0b00001000. Dan kijken we alleen naar de knopjes, dat betekent dat als het 0b00001010 of 0b00001001 toch 0b00001000 wordt. (op die overige pinnen is de communicatie aangesloten en dat wordt op een andere manier uitgelezen) Dat schuiven we dan nog twee plekken op en komen we uit op 0b00000010. Dat wordt dan in “port_temp” neergezet en kunnen we er na gaan gebruiken.
if(port_temp){
switch (port_temp) {
case _BV(0):
case _BV(1):
case _BV(2):
case _BV(3):
case _BV(4):
case _BV(5):
PORTC = port_temp;
printf("player %d\r\n", binary_depth(port_temp)-1);
break;
default:
printf("Gelijk %x\r\n", port_temp);
PORTC = ~port_temp;
break;
}
Alleen als “port_temp” een waarde is (eigenlijk als “port_temp” niet gelijk is aan 0b00000000) gaan we kijken, wie de knop heeft in gedrukt. (“if(port_temp){“) Dat kijken gebeurt in het “switch” stukje. (in het stukje beginnen we dus gelijk weer een nieuw stukje). In het geval (case) dat “port_temp” 0b00000001 (aangegeven met “_BV(0)”) maken we PORTC weer hoog, maar dat gebeurt ook als het 0b00000010 t/m 0b00100000 is. Dat komt omdat er geen “break;” staat, hij begint bij zijn geval en gaat dan door tot dat hij “break;” tegen komt. Na “break;” gaat hij weer verder na het switch stukje, afgesloten met een “}”.
Nadat we de led van de desbetreffende speler hebben laten branden ( “PORTC = port_temp;” ) zeggen we dit tegen de computer. Dat gebeurt met “printf”, die stuurt de tekst “player %d\r\n” naar de computer. Alleen de “%d” wordt vervangen door “binary_depth(port_temp)-1”, dat kleine stukje code zorgt ervoor dat 0b00000010 in een leesbare “1” verandert (tellen beginnen we bij 0 en “speler 0” is de quiz-master). De computer krijgt dan bij het indrukken van speler 1 “player 1”.
Als niet één speler (bij het uitlezen van PIND) maar twee of nog meer gedrukt hebben. Dan is “port_temp” bijvoorbeeld 0b00000110 en komt de switch uit bij “default”. Hij begint dan met het printen van “Gelijk %x\r\n”, dit keer wordt %x vervangen door de hexadecimale van “port_temp”, in dit geval 0x06. De computer krijgt dan bij het gelijk indrukken van speler 1 en 2 “Gelijk 6” (0x en de 0 ervoor wordt weg gehaald). Dan zet hij het omgekeerde van “port_temp” in “PORTC”, in dit geval 0b11111001. Dat betekent dat alle LEDs gaan branden, behalve de spelers die gelijk hebben ingedrukt. Dit was heel moeilijk om te testen, in het filmpje was het ook niet voor gekomen, maar ik wilde hem er wel in omdat ik wilde dat geen speler voordeel zou hebben ten opzichte van de ander. Gelijkspel kan dan opgelost worden met een shootout vraag.
while(uart_getc() != UART_NO_DATA); while((~PIND&0xFC)>>2 != _BV(0) && uart_getc() == UART_NO_DATA); _delay_ms(500); state = 1;
In beide gevallen (van gelijk spel en een speler) wordt “while(uart_getc() != UART_NO_DATA);” uitgevoerd, dit is een manier om te zeggen dat alles wat je al hebt ontvangen, gooi dat weg.(hij onthoud standaard wat hij heeft ontvangen) (dit is inderdaad een while, omdat hij geen “{ }” maar ; zit er geen sub stukje/lus aan vast) De volgende while blijft hij uitvoeren tot dat er aan een van de twee voorwaarden niet zijn voldaan. Dat zijn “(~PIND&0xFC)>>2 != _BV(0)” en (“en” is in dit geval “&&”) “uart_getc() == UART_NO_DATA”. Als je het vorige stuk een beetje heb begrepen, kan je dit ook begrijpen. “(~PIND&0xFC)>>2” werden de knoppen uitgelezen alleen gaan we nu kijken of het niet gelijk (“!=”) is aan _BV(0) (0b00000001 of het indrukken van knop 0). Dat betekent namelijk dat de quiz master de knop heeft ingedrukt en kan er verder gegaan worden met de volgende vraag. Dat kan ook gebeuren als er vanuit de computer iets wordt gestuurd. (dat is dat andere uart stuk).
Omdat de microcontroller heel snel is kan hij binnen 1 ms weer kijken welke knop is ingedrukt, dus daarom wachten we eerst 500 ms voordat we weer verder gaan met de hoofd lus.
Ik hoop dat ik duidelijk genoeg ben geweest en dat je nu ook kan berijpen wat de laatste “state = 1;” voor gevolgen heeft.
Slot
Het is iets meer geworden dan gedacht, maar ik hoop dat je het hebt begrepen. Ik was begonnen met schrijven over het ontwerpen van de hardware. De software is iets uitgebreider geworden, maar omdat ik vind dat een basisschool kind ook kan programmeren heb ik het heel uitgebreid uitgelegd. De code is geschreven in C dit is niet de makkelijkste taal, maar wel een van de meest gebruikte (ook bij de computer). Het is niet altijd even makkelijk, vooral niet het uitlezen van en aansturen van de poorten maar als je het stapje voor stapje leest kom je er wel uit.
Als mensen het interessant vinden kan ik nog wel een deel twee doen over het computer programma, dat is ook niet zo moeilijk, maar maakt gebruik van heleboel andere onderdelen van de computer en wordt daarom gebruik gemaakt van meer verschillende instructies.


