4.14. CSI-basis

De csi-module is de manier waarop Python-code de camerasensor aanstuurt. Elk script dat een frame vastlegt volgt dezelfde driedelige vorm: imports bovenaan, eenmalige configuratie in het midden, en een while True-lus onderaan die frames één voor één van de camera ophaalt.

4.14.1. De typische lus

import csi, image, time

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()
    # process img here
    print(clock.fps())

4.14.2. Wat elke aanroep doet

import csi, image, time

Brengt drie modules binnen. csi bestuurt de sensor, image definieert de Image-klasse die snapshot() retourneert, en time levert de time.clock()-helper die wordt gebruikt om frames per seconde te meten.

csi.CSI()

Construeert een CSI-instantie die één fysieke camerasensor omhult. De constructor claimt het camerarandapparaat en registreert de configuratie per sensor. Camera’s met een enkele sensor hebben één CSI-instantie; camera’s met twee sensoren (kleur plus thermisch, kleur plus event) hebben er twee, elk geselecteerd door een cid-argument aan de constructor.

csi0.reset()

Voorziet de sensor van stroom en configureert deze. Standaard pulseert het de reset-pin van de sensor en schrijft het vervolgens de I2C-registers van de sensor naar een bekende beginstaat. Daaropvolgende configuratieaanroepen – pixformat, framesize, de automatische-controleknoppen – sturen meer registerschrijfacties over dezelfde I2C-controlebus.

csi0.pixformat(csi.RGB565)

Schrijft de sensorregisters die het uitvoerpixelformaat kiezen. De beschikbare keuzes zijn de formaten die de pagina pixelformaten introduceerde: RGB565, GRAYSCALE, BAYER, YUV422, en JPEG op de sensoren die het ondersteunen.

csi0.framesize(csi.QVGA)

Schrijft de registers die de uitvoerresolutie kiezen. QVGA is 320 × 240; de benoemde groottes lopen op tot WQXGA2 (2592 × 1944, ongeveer 5 MP) op sensoren die ze ondersteunen. Een aangepaste (width, height)-tuple werkt ook, zolang die overeenkomt met de uitvoermogelijkheden van de sensor.

clock = time.clock()

Maakt een klokhelper. Elke aanroep van clock.tick() binnen de lus registreert de starttijd van de iteratie; time.clock.fps() rapporteert de recente lussnelheid in frames per seconde.

img = csi0.snapshot()

Legt één frame van de sensor vast en retourneert het als een Image. De mechaniek van hoe dat frame in het geheugen terechtkomt is een nadere blik waard.

4.14.3. Hoe snapshot het geheugen vult

De sensor levert pixels op de pixeldatabus die wordt beschreven in sensorbussen met snelheden van honderden megabytes per seconde – veel te snel voor de CPU om pixel voor pixel in software te kopiëren.

In plaats daarvan draagt de MCU de overdracht over aan Direct Memory Access (DMA) – een hardware-engine los van de CPU die bytes van de ene plaats naar de andere kopieert binnen de MCU zonder de CPU er ook maar bij te betrekken. Het camera-invoerrandapparaat vangt elke binnenkomende pixelbyte op in een kleine FIFO op de chip; de ISP-fasen die aan de MCU-zijde draaien verwerken de gegevens onderweg; en de DMA-engine schrijft de voltooide pixels naar een framebuffer in RAM op de bijbehorende pixeloffset. Niets in die keten heeft de CPU nodig zodra het DMA-kanaal is geprogrammeerd.

Wanneer snapshot() wordt aangeroepen:

  1. De CSI-driver programmeert de DMA-engine met het adres van de framebuffer, de overdrachtslengte (de pixels van één frame), en een callback voor de DMA-klaar-interrupt.

  2. De driver schakelt het camera-invoerrandapparaat in en wacht tot de sensor het begin van het volgende frame aangeeft.

  3. Terwijl de sensor het frame uitstroomt, geeft het randapparaat elke pixelbyte door de ISP en verder naar de DMA-engine, die het resultaat naar RAM schrijft op de volgende framebuffer-offset. De CPU is vrij om tijdens de overdracht andere code uit te voeren.

  4. Wanneer de laatste pixel van het frame aankomt, vuurt de DMA zijn klaar-interrupt af, omhult de driver de framebuffer in een Image, en retourneert snapshot() deze aan gebruikerscode.

De geretourneerde Image bezit geen kopie van de pixelgegevens – het wijst naar een van de framebuffers van de camera in RAM. Hoeveel framebuffers de camera bijhoudt, en hoe ze bij elke aanroep van snapshot() tussen de DMA en gebruikerscode worden doorgegeven, hangt af van de buffermodus die de toepassing heeft geselecteerd via framebuffers().