4.14. Noțiuni de bază despre CSI

Modulul csi este modul în care codul Python comandă senzorul camerei. Fiecare script care capturează un cadru urmează aceeași structură în trei părți: importurile la început, configurarea unică la mijloc și o buclă while True la final care preia cadre de la cameră unul câte unul.

4.14.1. Bucla tipică

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. Ce face fiecare apel

import csi, image, time

Importă trei module. csi controlează senzorul, image definește clasa Image pe care o returnează snapshot(), iar time oferă funcția ajutătoare time.clock() folosită pentru a măsura cadrele pe secundă.

csi.CSI()

Construiește o instanță CSI care încapsulează un senzor fizic al camerei. Constructorul revendică periferia camerei și înregistrează configurația per senzor. Camerele cu un singur senzor au o instanță CSI; camerele cu doi senzori (color plus termic, color plus eveniment) au două, fiecare selectată printr-un argument cid al constructorului.

csi0.reset()

Alimentează și configurează senzorul. În mod implicit, pulsează pinul de resetare al senzorului, apoi scrie registrele I2C ale senzorului într-o stare inițială cunoscută. Apelurile de configurare ulterioare – pixformat, framesize, comenzile de control automat – trimit mai multe scrieri de registre pe aceeași magistrală de control I2C.

csi0.pixformat(csi.RGB565)

Scrie registrele senzorului care selectează formatul pixelilor de ieșire. Opțiunile disponibile sunt formatele prezentate de pagina formate de pixeli: RGB565, GRAYSCALE, BAYER, YUV422 și JPEG pe senzorii care îl suportă.

csi0.framesize(csi.QVGA)

Scrie registrele care selectează rezoluția de ieșire. QVGA este 320 × 240; dimensiunile cu nume merg până la WQXGA2 (2592 × 1944, aproximativ 5 MP) pe senzorii care le suportă. Un tuplu personalizat (width, height) funcționează de asemenea, atâta timp cât se aliniază cu capacitățile de ieșire ale senzorului.

clock = time.clock()

Creează o funcție ajutătoare de tip clock. Fiecare apel la clock.tick() din interiorul buclei înregistrează momentul de început al iterației; time.clock.fps() raportează rata recentă a buclei în cadre pe secundă.

img = csi0.snapshot()

Capturează un cadru de la senzor și îl returnează ca Image. Mecanica modului în care acel cadru ajunge în memorie merită o privire mai atentă.

4.14.3. Cum umple snapshot memoria

Senzorul livrează pixeli pe magistrala de date a pixelilor descrisă în magistralele senzorului la rate de sute de megaocteți pe secundă – mult prea rapid pentru ca CPU-ul să copieze pixel cu pixel în software.

În schimb, MCU-ul transferă sarcina către Direct Memory Access (DMA) – un motor hardware separat de CPU care copiază octeți dintr-un loc în altul în interiorul MCU-ului fără a implica deloc CPU-ul. Periferia de intrare a camerei captează fiecare octet de pixel care sosește într-un mic FIFO pe cip; oricare etape ISP rulează pe partea MCU procesează datele în trecere; iar motorul DMA scrie pixelii finalizați într-un framebuffer din RAM la decalajul de pixel corespunzător. Nimic din acel lanț nu are nevoie de CPU odată ce canalul DMA a fost programat.

Când se apelează snapshot():

  1. Driverul CSI programează motorul DMA cu adresa framebuffer-ului, lungimea transferului (numărul de pixeli dintr-un cadru) și o funcție de retroapelare (callback) pentru întreruperea de finalizare a DMA.

  2. Driverul activează periferia de intrare a camerei și așteaptă ca senzorul să semnaleze începutul cadrului următor.

  3. Pe măsură ce senzorul transmite cadrul, periferia predă fiecare octet de pixel prin ISP și mai departe către motorul DMA, care scrie rezultatul în RAM la următorul decalaj al framebuffer-ului. CPU-ul este liber să ruleze alt cod în timpul transferului.

  4. Când sosește ultimul pixel al cadrului, DMA declanșează întreruperea sa de finalizare, driverul încapsulează framebuffer-ul într-o Image, iar snapshot() îl returnează codului utilizatorului.

Image returnată nu deține o copie a datelor pixelilor – ea indică spre unul dintre framebuffer-ele camerei din RAM. Câte framebuffer-e păstrează camera și cum sunt transferate între DMA și codul utilizatorului la fiecare apel la snapshot() depinde de modul de tamponare pe care l-a selectat aplicația prin framebuffers().