4.14. CSI:n perusteet

csi-moduuli on tapa, jolla Python-koodi ohjaa kamerasensoria. Jokainen kehyksen kaappaava skripti noudattaa samaa kolmiosaista rakennetta: tuonnit ylhäällä, kertaluonteinen konfigurointi keskellä, ja while True -silmukka alhaalla, joka hakee kehyksiä kamerasta yksi kerrallaan.

4.14.1. Tyypillinen silmukka

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. Mitä kukin kutsu tekee

import csi, image, time

Tuo kolme moduulia. csi ohjaa sensoria, image määrittelee Image -luokan, jonka snapshot() palauttaa, ja time tarjoaa time.clock() -apurin, jota käytetään kehysten sekuntinopeuden mittaamiseen.

csi.CSI()

Rakentaa CSI -instanssin, joka kääräisee yhden fyysisen kamerasensorin. Konstruktori varaa kameran oheislaitteen ja tallentaa sensorikohtaisen konfiguraation. Yhden sensorin kameroilla on yksi CSI -instanssi; kahden sensorin kameroilla (väri plus lämpö, väri plus tapahtuma) on kaksi, joista kukin valitaan konstruktorin cid -argumentilla.

csi0.reset()

Käynnistää ja konfiguroi sensorin. Oletuksena se pulssittaa sensorin reset-nastaa, sitten kirjoittaa sensorin I2C-rekisterit tunnettuun aloitustilaan. Seuraavat konfigurointikutsut – pixformat, framesize, automaattisäätönupit – työntävät lisää rekisterikirjoituksia saman I2C-ohjausväylän kautta.

csi0.pixformat(csi.RGB565)

Kirjoittaa sensorirekisterit, jotka valitsevat ulostulon pikselimuodon. Saatavilla olevat vaihtoehdot ovat ne muodot, jotka pikselimuodot -sivu esitteli: RGB565, GRAYSCALE, BAYER, YUV422 ja JPEG niillä sensoreilla, jotka sitä tukevat.

csi0.framesize(csi.QVGA)

Kirjoittaa rekisterit, jotka valitsevat ulostuloresoluution. QVGA on 320 × 240; nimetyt koot ulottuvat aina WQXGA2 -kokoon (2592 × 1944, noin 5 MP) niillä sensoreilla, jotka niitä tukevat. Mukautettu (width, height) -monikko toimii myös, kunhan se vastaa sensorin ulostulokyvykkyyksiä.

clock = time.clock()

Luo kelloapurin. Jokainen clock.tick() -kutsu silmukan sisällä tallentaa iteraation aloitusajan; time.clock.fps() raportoi viimeaikaisen silmukkanopeuden kehyksinä sekunnissa.

img = csi0.snapshot()

Kaappaa yhden kehyksen sensorista ja palauttaa sen Image -oliona. Sen mekaniikka, miten kyseinen kehys päätyy muistiin, ansaitsee lähemmän tarkastelun.

4.14.3. Miten snapshot täyttää muistin

Sensori toimittaa pikselit pikselidataväylällä, joka kuvataan kohdassa sensoriväylät, satojen megatavujen sekuntinopeudella – aivan liian nopeasti, jotta CPU ehtisi kopioida pikseli pikseliltä ohjelmistossa.

Sen sijaan MCU siirtää siirron suoramuistinkäytölle (DMA) – CPU:sta erilliselle laitteistomoottorille, joka kopioi tavuja paikasta toiseen MCU:n sisällä ilman CPU:n osallistumista lainkaan. Kameran syöttöoheislaite nappaa kunkin saapuvan pikselitavun pieneen sirun sisäiseen FIFO:on; mitkä tahansa MCU:n puolella suoritettavat ISP-vaiheet käsittelevät datan matkan varrella; ja DMA-moottori kirjoittaa valmiit pikselit RAM:issa olevaan kehyspuskuriin vastaavaan pikselisiirtymään. Mikään tuossa ketjussa ei tarvitse CPU:ta sen jälkeen kun DMA-kanava on ohjelmoitu.

Kun snapshot() kutsutaan:

  1. CSI-ajuri ohjelmoi DMA-moottorin kehyspuskurin osoitteella, siirron pituudella (yhden kehyksen verran pikseleitä) ja takaisinkutsulla DMA-valmis-keskeytystä varten.

  2. Ajuri ottaa kameran syöttöoheislaitteen käyttöön ja odottaa, että sensori signaloi seuraavan kehyksen alun.

  3. Kun sensori virtauttaa kehystä ulos, oheislaite luovuttaa kunkin pikselitavun ISP:n läpi ja edelleen DMA-moottorille, joka kirjoittaa tuloksen RAM:iin seuraavaan kehyspuskurisiirtymään. CPU on vapaa suorittamaan muuta koodia siirron aikana.

  4. Kun kehyksen viimeinen pikseli saapuu, DMA laukaisee valmis-keskeytyksensä, ajuri kääräisee kehyspuskurin Image -olioon, ja snapshot() palauttaa sen käyttäjäkoodille.

Palautettu Image ei omista kopiota pikselidatasta – se osoittaa yhteen kameran kehyspuskureista RAM:issa. Kuinka monta kehyspuskuria kamera pitää, ja miten ne luovutetaan DMA:n ja käyttäjäkoodin välillä jokaisella snapshot() -kutsulla, riippuu puskurointitilasta, jonka sovellus on valinnut framebuffers() -metodin kautta.