4.14. Nozioni di base sulla CSI

Il modulo csi è il modo in cui il codice Python pilota il sensore della camera. Ogni script che acquisisce un frame segue la stessa struttura in tre parti: gli import in cima, la configurazione una tantum nel mezzo, e un loop while True in fondo che preleva i frame dalla camera uno alla volta.

4.14.1. Il loop tipico

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. Cosa fa ogni chiamata

import csi, image, time

Importa tre moduli. csi controlla il sensore, image definisce la classe Image che snapshot() restituisce, e time fornisce l’helper time.clock() usato per misurare i frame al secondo.

csi.CSI()

Costruisce un’istanza CSI che incapsula un sensore fisico della camera. Il costruttore reclama la periferica della camera e registra la configurazione specifica del sensore. Le camere con un singolo sensore hanno un’istanza CSI; le camere con due sensori (colore più termico, colore più eventi) ne hanno due, ciascuna selezionata tramite un argomento cid passato al costruttore.

csi0.reset()

Alimenta e configura il sensore. Per impostazione predefinita invia un impulso al pin di reset del sensore, poi scrive i registri I2C del sensore in uno stato iniziale noto. Le successive chiamate di configurazione – pixformat, framesize, le manopole di controllo automatico – inviano altre scritture nei registri sullo stesso bus di controllo I2C.

csi0.pixformat(csi.RGB565)

Scrive i registri del sensore che selezionano il formato dei pixel di output. Le scelte disponibili sono i formati introdotti nella pagina formati dei pixel: RGB565, GRAYSCALE, BAYER, YUV422 e JPEG sui sensori che lo supportano.

csi0.framesize(csi.QVGA)

Scrive i registri che selezionano la risoluzione di output. QVGA è 320 × 240; le dimensioni con nome arrivano fino a WQXGA2 (2592 × 1944, circa 5 MP) sui sensori che le supportano. Funziona anche una tupla (width, height) personalizzata, purché sia compatibile con le capacità di output del sensore.

clock = time.clock()

Crea un helper clock. Ogni chiamata a clock.tick() all’interno del loop registra l’ora di inizio dell’iterazione; time.clock.fps() riporta il tasso recente del loop in frame al secondo.

img = csi0.snapshot()

Acquisisce un frame dal sensore e lo restituisce come Image. Vale la pena esaminare più da vicino il meccanismo con cui quel frame finisce in memoria.

4.14.3. Come snapshot riempie la memoria

Il sensore consegna i pixel sul bus dei dati dei pixel descritto in bus del sensore a velocità di centinaia di megabyte al secondo – troppo veloce perché la CPU li copi pixel per pixel via software.

Invece, l’MCU delega il trasferimento al Direct Memory Access (DMA) – un motore hardware separato dalla CPU che copia i byte da un punto all’altro all’interno dell’MCU senza coinvolgere affatto la CPU. La periferica di input della camera cattura ogni byte di pixel in arrivo in una piccola FIFO sul chip; gli stadi ISP eseguiti sul lato MCU elaborano i dati lungo il percorso; e il motore DMA scrive i pixel finiti in un framebuffer in RAM all’offset di pixel corrispondente. Nulla in quella catena ha bisogno della CPU una volta che il canale DMA è stato programmato.

Quando viene chiamato snapshot():

  1. Il driver CSI programma il motore DMA con l’indirizzo del framebuffer, la lunghezza del trasferimento (i pixel di un intero frame) e una callback per l’interrupt di completamento del DMA.

  2. Il driver abilita la periferica di input della camera e attende che il sensore segnali l’inizio del frame successivo.

  3. Mentre il sensore trasmette il frame, la periferica passa ogni byte di pixel attraverso l’ISP e poi al motore DMA, che scrive il risultato in RAM al successivo offset del framebuffer. La CPU è libera di eseguire altro codice durante il trasferimento.

  4. Quando arriva l’ultimo pixel del frame, il DMA genera il suo interrupt di completamento, il driver incapsula il framebuffer in un Image, e snapshot() lo restituisce al codice utente.

L”Image restituito non possiede una copia dei dati dei pixel – punta a uno dei framebuffer della camera in RAM. Quanti framebuffer la camera mantiene, e come vengono passati tra il DMA e il codice utente a ogni chiamata a snapshot(), dipende dalla modalità di buffering che l’applicazione ha selezionato tramite framebuffers().