3.1. Microcontrollers¶
De OpenMV Cam draait op een microcontroller (MCU): een enkele chip die een CPU, werkgeheugen (RAM), programmaopslag (flashgeheugen) en een reeks randapparaten combineert – hardwareblokken voor interactie met de buitenwereld.
De randapparaten zijn het interessante deel. Elk randapparaat is een stukje silicium dat aan een taak is gewijd: een pin hoog of laag aansturen, een analoge spanning meten, bytes uitklokken over een seriële bus. De CPU configureert en leest elk randapparaat via registers – vaste geheugenadressen die de hardware in de gaten houdt en bijwerkt.
MicroPython verpakt die registers in klassen binnen de machine-module. machine.Pin(...) geeft een object terug dat een general-purpose input/output-pin (GPIO) bestuurt – een draad die de chip hoog (rond 3,3 V) of laag (rond 0 V) kan houden, of als een van die twee toestanden kan lezen wanneer iets externs hem aanstuurt. machine.ADC(...) stelt de analoog-naar-digitaalomzetter beschikbaar, die de spanning op een pin meet en als getal rapporteert. machine.UART(...) bestuurt een universal asynchronous receiver/transmitter (UART) – een randapparaat dat bytes bit voor bit verzendt en ontvangt over een paar draden, TX (transmit) en RX (receive). Andere klassen dekken de rest van de randapparaten. Het script leest en schrijft Python-objecten; MicroPython vertaalt elke toegang naar de bijbehorende registerlees- en -schrijfacties, en die bewegen bits op fysieke draden.
Een MCU verpakt CPU, geheugen en randapparaten in een enkele chip. Elk randapparaat wordt door een klasse in de machine-module aan Python beschikbaar gesteld.¶
3.1.1. De hoofdlus¶
Bijna elk microcontrollerprogramma heeft dezelfde vorm: eenmalige initialisatie bovenaan het script (modules importeren, pinnen configureren, bussen openen) en daarna een oneindige while True:-lus onderaan. Binnen de lus leest het programma invoer, neemt beslissingen en werkt uitvoer keer op keer bij. De lus is het programma; wanneer het script eindigt, doet het apparaat niets meer.
# setup, runs once
from machine import Pin
led = Pin("P0", Pin.OUT)
# main loop, runs forever
while True:
led.value(1)
# ... do work ...
led.value(0)
# ... do other work ...
Deze vorm – eenmalig initialiseren en daarna eeuwig blijven lussen – is het patroon van de hoofdlus. Alles wat volgt gaat over wat erin terechtkomt.
3.1.2. Realtime-besturing¶
Een desktopprogramma draait naast vele andere. Het besturingssysteem plant zijn werk over een of meer threads – onafhankelijke uitvoeringsstromen waartussen het milliseconde voor milliseconde wisselt. Wanneer een thread wacht op I/O (schijf, netwerk, de gebruiker die de muis beweegt), geeft het OS de CPU aan een andere. Het programma is grotendeels event-gestuurd: de window manager roept jouw code aan wanneer invoer binnenkomt, de HTTP-bibliotheek hervat jouw code wanneer er bytes op de socket aankomen. Iets groters roept jou aan.
Een microcontrollerprogramma is het tegenovergestelde. Standaard is er geen besturingssysteem, geen scheduler en geen andere thread. De zojuist getoonde hoofdlus is de enige lus. Randapparaten activeren interrupts of stellen statusvlaggen beschikbaar; de lus pollt ze of handelt de interrupts direct af. Als de lus blijft hangen in een time.sleep_ms(1000), doet het apparaat gedurende die seconde niets; er is geen andere thread om het gat op te vullen.
Er volgen twee gevolgen uit die overal van toepassing zijn:
Tijd is echt. Een pin tweemaal lezen in een strakke lus duurt microseconden; tien milliseconden slapen betekent tien milliseconden waarin niets anders gebeurt. Het patroon van niet-blokkerende timing is het antwoord.
Hardware is echt.
machine.Pin.valueop1zetten plaatst ruwweg 3,3 V op een fysieke draad; op0zetten plaatst er ruwweg 0 V. Andere delen van het circuit zien die spanning onmiddellijk – inclusief eventuele componenten die de pin kan beschadigen als hij verkeerd wordt aangestuurd.