# arducam-evk-gui

> Fork di ArduCam_EVK_Demo che unifica gli esempi a riga di comando in un'unica GUI Tkinter sensor-agnostic e data-driven, con editor di registri da profilo JSON.

Pubblicato: 2026-05-20
Aggiornato: 2026-08-25
Ambito: elettronica
Repository: <https://github.com/stefanofante/arducam-evk-gui>

Pagina: <https://www.stline.it/wiki/arducam-evk-gui/>

---

Guida operativa a arducam-evk-gui: cos’è, come installarlo (Windows e Linux), come si usa l’interfaccia, come si lavora sui registri del sensore e come si costruiscono il file di configurazione `.cfg` e il profilo JSON. Il progetto è un fork di `ArduCam_EVK_Demo` che unifica i singoli esempi a riga di comando in un’unica GUI Tkinter, sensor-agnostic e data-driven. Contenuti ricavati da README, `GUIcamera/README.md`, `python/README.md`, le guide in `doc/` e i file reali `sensors/_template.json` e `sensors/mira220.json`; il riferimento autoritativo resta il repository GitHub.

## Cos’è e a cosa serve

Il progetto upstream `ArduCAM/ArduCam_EVK_Demo` distribuisce esempi standalone separati (open/init/start/stop, controlli, mode switching, register dump, read/write singolo registro, snapshot, salvataggio sequenze, log SDK). Questo fork li fonde in un’unica finestra Tkinter e aggiunge un editor di registri guidato dal profilo del sensore.

È nato come strumento da banco per il bring-up del sensore di immagine ams OSRAM Mira220: validare sensore e ottica, regolare esposizione/gain, ispezionare e scrivere registri, acquisire frame e sequenze per l’analisi — tutto da GUI, senza scrivere codice host. Licenza MIT per le aggiunte del fork.

## Architettura data-driven: configs/ + sensors/

La GUI è disaccoppiata dal sensore. Una cartella `configs/` contiene i file `.cfg` (una modalità del sensore per file: risoluzione, bit depth, formato, I²C); una cartella `sensors/` contiene i profili `.json` (la mappa registri descritta in modo umano). All’avvio la GUI scansiona `configs/`, raggruppa i `.cfg` per sensore e aggancia il profilo JSON confrontando (case-insensitive) il campo `match` del JSON con il `TYPE` del `.cfg` (o col nome file).

Conseguenza pratica: aggiungere o documentare un sensore non richiede modifiche al codice Python — si lavora solo su un `.cfg` e su un `.json`. `config_manager.py` fa da parser dei `.cfg` e loader dei profili `.json`.

## Installazione — Windows

Per usare la GUI servono solo il driver USB e le dipendenze Python (l’ambiente C/C++ — Visual Studio, CMake, OpenCV, variabili EVK SDK — serve solo per compilare i demo C/C++ inclusi). Scaricare il pacchetto driver <a href="https://www.arducam.com/" target="_blank" rel="noopener">Arducam</a> (`install_USB_Camera_Drivers.zip`), scompattarlo ed eseguire `install_driver.bat`.

Completata l’installazione del driver, proseguire con le dipendenze Python e l’avvio della GUI (sezione successiva).

## Installazione — Linux

Installare OpenCV e l’SDK Arducam dal PPA ufficiale: `sudo apt install libopencv-dev cmake`; quindi aggiungere la chiave e la lista APT Arducam e installare `arducam-config-parser-dev` e `arducam-evk-sdk-dev`.

Configurare le regole udev: scaricare `configure_udev_rules.sh`, renderlo eseguibile (`sudo chmod +x`) ed eseguirlo, così il device EVK è accessibile senza privilegi di root. Poi proseguire con le dipendenze Python.

## Dipendenze Python e avvio

Requisiti: Python 3.8 o superiore. Le dipendenze runtime sono in `requirements.txt`: `ArducamEvkSDK` (binding Python dell’SDK), `Pillow`, `opencv-python`, `numpy`, `arducam-rgbir-remosaic`.

Avvio: `cd GUIcamera; pip install -r requirements.txt; python launch_gui.py`. `launch_gui.py` aggiunge la cartella `GUIcamera/` al `sys.path` e chiama `gui.main()`; funziona anche `python gui.py` dall’interno della cartella.

## Le schermate dell’interfaccia

Preview — canvas live con shifting adattivo 16→8 bit (senza stretch min-max), statistiche live, salvataggio snapshot e sequenze, istogramma in scala logaritmica (X = valore pixel, Y = conteggio) e loop software di auto-esposizione con auto-gain opzionale.

Controls — slider generati automaticamente per ogni controllo dichiarato nel `.cfg` (es. `Framerate`, `Exp(us)`); il limite di esposizione segue il framerate. Sensor regs — editor per-registro guidato dal profilo JSON attivo: barra strumenti per cambiare profilo, filtrare, raggruppare e alternare vista multi-byte combinata / per-byte; ogni riga ha un pulsante “i” che apre un help in stile datasheet.

Registers (raw) — selettore indirizzo/valore grezzo con 8 checkbox di bit e descrizione live dal profilo attivo. Advanced — dimensione del transfer USB, cambio modalità sensore, livello e file di log dell’SDK. Info — output di `dump_info()` e console di log SDK in-app.

## Flusso di lavoro tipico

1. Collegare l’EVK via USB e avviare la GUI.
2. Scegliere il sensore (dai `.cfg` trovati), poi la configurazione specifica, poi il device USB.
3. Open quindi Start: la cattura parte su un thread di background e il main loop Tk aggiorna l’anteprima senza bloccarsi sull’I/O USB.
4. Regolare esposizione/gain dai Controls, ispezionare/scrivere registri dalla tab Sensor regs (con help da datasheet), salvare snapshot o sequenze dalla Preview. Per modalità ad alta banda (12 bit a piena risoluzione) aumentare la dimensione del transfer USB nella tab Advanced fra `open()` e `start()`.

## Il file di configurazione .cfg

Il `.cfg` descrive una modalità del sensore nel formato dell’SDK Arducam. Campi principali: `CFG_MODE` (0 = parametri da UI / 1 = usa questo file come script); `TYPE` (identificativo sensore, è la chiave che lega al profilo JSON); `VID` (Vendor ID); `SIZE` (larghezza, altezza); `BIT_WIDTH` (profondità in bit).

`FORMAT` (formato immagine): 0 = RAW (sotto-codici 0=RG, 1=GR, 2=GB, 3=BG), 1 = RGB565 (0=RGB, 1=BGR), 2 = YUV422 (0=YUYV, 1=YVYU, 2=UYVY, 3=VYUY), 3 = JPG. `I2C_MODE` (ampiezza indirizzo/dato I²C): 0 = 8 bit addr/8 bit dato, 1 = 8/16, 2 = 16/8, 3 = 16/16. `I2C_ADDR` (indirizzo I²C del sensore, 8 bit incl. R/W).

Esempio (Mira220, 16-bit addr / 8-bit data, RAW): `CFG_MODE = 0` · `TYPE = Mira220` · `SIZE = 640, 480` · `BIT_WIDTH = 12` · `FORMAT = 4, 0` · `I2C_MODE = 2` · `I2C_ADDR = 0xA8`. Il nome del `.cfg` è quello mostrato nel selettore configurazione della GUI.

## Costruire il profilo JSON del sensore

Un profilo (`sensors/<nome>.json`) ha le chiavi top-level: `name`, `description`, `match` (lista di stringhe confrontate col `TYPE`/nome file del `.cfg`, case-insensitive), `guide` (testo lungo di riferimento rapido mostrato nella UI), `tabs` (suggerimenti per le singole tab) e `registers` (array dei registri). Partire sempre da `sensors/_template.json`.

Ogni registro ha: `group` (raggruppamento nella UI), `name`, `addr` (es. `"0x0100"`), `kind`, `desc` (breve), `help` (testo lungo in stile datasheet) e, dove ha senso, `lo/hi` (range). I `kind` supportati: `u8`, `bool`, `u16be`, `u16le`, `u24be`, `ro` (sola lettura), `enum`, `multi` (campo a bit distribuito su più registri, scritto in read-modify-write) e `computed` (valore calcolato con una formula su più letture, es. temperatura del die).

`enum` aggiunge `choices`: `{ "Etichetta": valore }`. `multi` aggiunge `parts`: una lista di `{ addr, reg_lsb, width, value_lsb }` che mappa i bit del valore logico sui registri fisici (LSB prima dell’MSB). Un registro può dichiarare anche `bits`: `[ { lsb, width, name, desc } ]` per documentare i singoli campi a bit. Esempio (da `_template.json`) — esposizione a 16 bit su `0x0202`/`0x0203` come `kind` `"multi"`, e stato sensore come `"enum"` con `choices` Standby/Streaming.

Il file reale `sensors/mira220.json` è l’esempio completo di riferimento: register map verificata su datasheet, `guide` e `tabs` valorizzati, `help` ricchi per ogni registro. Copiarlo come modello quando il sensore è simile.

## Aggiungere un nuovo sensore

1. Rilasciare il file `.cfg` prodotto dal tooling Arducam in `configs/` (il campo `TYPE` è la chiave).
2. Creare il profilo JSON corrispondente in `sensors/` partendo da `_template.json`, assicurandosi che `match` contenga lo stesso valore `TYPE` (o un alias case-insensitive).
3. Riavviare la GUI: il nuovo sensore e le sue configurazioni compaiono automaticamente nel selettore. Nessuna modifica al codice Python.

## L'aritmetica dell'esposizione

Il consiglio «parti da esposizione e gain bassi» funziona, ma in bring-up serve sapere **a quanto tempo** corrisponde un numero scritto in un registro, e perché a un certo punto quel numero non si può più alzare. Sono tre conti che escono tutti dagli stessi due registri di temporizzazione del sensore.

L'esposizione non si programma in microsecondi ma in **righe**, e una riga dura quanto il sensore impiega a leggerla:

$$
t_{\text{riga}} = \dfrac{\texttt{line\_length\_pck}}{f_{\text{pixel}}}
\qquad\qquad
\text{fps}_{\max} = \dfrac{f_{\text{pixel}}}{\texttt{line\_length\_pck} \cdot \texttt{frame\_length\_lines}}
$$

| Pixel clock | `line_length_pck` | `frame_length_lines` | Tempo di riga | fps massimo |
|---|---|---|---|---|
| 74,25 MHz | 2960 | 2200 | 39,87 µs | 11,4 |
| 74,25 MHz | 2960 | 1200 | 39,87 µs | 20,9 |
| 160,00 MHz | 2960 | 2200 | 18,50 µs | 24,6 |
| 74,25 MHz | 1600 | 800 | 21,55 µs | 58,0 |
| 48,00 MHz | 1600 | 520 | 33,33 µs | 57,7 |

Con la prima riga della tabella — valori tipici di un sensore da 5 MP — una riga dura **39,87 µs**, e da lì l'esposizione si legge direttamente:

| Esposizione (righe) | Tempo | Frazione di un frame a 30 fps |
|---|---|---|
| 1 | 0,040 ms | 0,1 % |
| 10 | 0,399 ms | 1,2 % |
| 100 | 3,987 ms | 12,0 % |
| 500 | 19,933 ms | 59,8 % |
| 1000 | 39,865 ms | 119,6 % |
| 2190 | 87,305 ms | 261,9 % |

Il tetto è il **`frame_length_lines`**: l'esposizione non può superare la durata del frame, perché la riga letta per prima deve aver finito di integrare prima che il frame successivo cominci. Con `frame_length_lines` = 2200 il massimo è quindi attorno a **87,6 ms**. Ed è qui che si annida la sorpresa più comune del bring-up: **per esporre più a lungo bisogna allungare il frame, cioè rinunciare a frame rate**.

| `frame_length_lines` | fps massimo | Esposizione massima |
|---|---|---|
| 1200 | 20,9 | 47,76 ms |
| 2200 | 11,4 | 87,62 ms |
| 4400 | 5,7 | 175,33 ms |

Quindi «l'immagine è troppo scura, alzo l'esposizione» a un certo punto smette di essere una regolazione e diventa una scelta di progetto: più luce **o** più fps, non entrambi. Sono gli stessi due registri che determinano il blanking e quindi la banda sul link CSI-2 — il legame è spiegato nella [wiki sulla banda MIPI CSI-2](/wiki/mipi-csi2-bandwidth/): allungare il frame per esporre di più **abbassa** anche l'occupazione del link.

### Esposizione o guadagno: non sono equivalenti

Davanti a un'immagine scura la GUI offre due manopole, e la tentazione è di considerarle interscambiabili. Non lo sono, e la differenza si misura in SNR. Nel regime in cui il rumore dominante è quello **fotonico** (shot noise), il segnale cresce come il numero di fotoni raccolti *N* e il rumore come √*N*:

| Azione | Segnale | SNR (regime shot-noise) |
|---|---|---|
| esposizione × 2 | × 2 | × 1,41 (**+3,0 dB**) |
| esposizione × 4 | × 4 | × 2,00 (**+6,0 dB**) |
| esposizione × 8 | × 8 | × 2,83 (**+9,0 dB**) |
| guadagno analogico × 2 | × 2 | × 1,00 (**0 dB**) |
| guadagno digitale × 2 | × 2 | × 1,00 (**0 dB**) |

Raddoppiare l'**esposizione** raccoglie il doppio dei fotoni e migliora l'SNR di **3 dB** veri. Raddoppiare il **guadagno** moltiplica per due sia il segnale sia il rumore che già c'è: l'immagine è più chiara e non un decibel migliore. Da cui l'ordine giusto in bring-up: **prima si spende tutta l'esposizione disponibile**, poi si valuta il guadagno.

Con una precisazione che evita l'errore opposto: il guadagno **analogico** — applicato *prima* del convertitore — non è inutile a bassa luce. In quel regime il rumore dominante non è più quello fotonico ma quello di lettura della catena a valle, e amplificare prima dell'ADC lo rende meno rilevante rispetto al segnale. È il motivo per cui molti sensori hanno una *conversion gain* commutabile. Il guadagno **digitale**, invece, non aiuta mai: moltiplica un numero già quantizzato, e l'unica cosa che aggiunge è la sensazione di aver fatto qualcosa.

### Lo shift a 8 bit, in numeri

La sezione sui problemi dice che il passaggio 16→08 bit non applica stretch min-max, «quindi un frame molto scuro resta scuro». In cifre:

| Codice a 12 bit | % della scala | Dopo lo shift, a 8 bit | % della scala |
|---|---|---|---|
| 50 | 1,2 % | 3 | 1,2 % |
| **200** | 4,9 % | **12** | 4,7 % |
| 512 | 12,5 % | 32 | 12,5 % |
| 1024 | 25,0 % | 64 | 25,1 % |
| 2048 | 50,0 % | 128 | 50,2 % |
| 4095 | 100,0 % | 255 | 100,0 % |

Un codice **200 su 4095** — il 4,9 % della scala, un frame decisamente sottoesposto — diventa **12 su 255**, cioè esattamente lo stesso 4,7 %: lo shift non cambia la *percentuale*, quindi non può far apparire ciò che non c'è. Quello che perde è la risoluzione: **16 livelli a 12 bit collassano in un solo livello a 8**, e in una zona d'ombra dove il segnale utile occupa cento codici a 12 bit ne restano sei. È il motivo per cui la valutazione dell'esposizione si fa sull'istogramma **a 12 bit** e in scala logaritmica, non sull'anteprima a 8 bit.

## Risoluzione dei problemi

- **L'EVK non compare nel selettore device.** Su Windows verifica di aver eseguito `install_driver.bat`; su Linux che le regole udev siano installate e il device sia accessibile senza root. Senza driver o permessi corretti l'SDK non enumera la camera.
- **Il sensore non compare fra le configurazioni.** Il campo `match` del profilo JSON deve corrispondere (case-insensitive) al `TYPE` del `.cfg`. Lancia `python -m config_manager` per vedere cosa la GUI ha agganciato, senza aprire l'interfaccia.
- **Anteprima nera o satura.** Parti da esposizione e gain bassi e alza gradualmente; lo shifting 16→08 bit non applica stretch min-max, quindi un frame molto scuro resta scuro. L'istogramma in scala logaritmica aiuta a capire dove si concentra l'energia.
- **Frame persi o throughput basso in modalità ad alta banda.** Aumenta la dimensione del transfer USB nella tab Advanced fra `open()` e `start()`: a 12 bit a piena risoluzione il valore di default può non bastare.

## Diagnostica e licenza

Diagnostica: `python -m config_manager` stampa configurazioni e profili individuati senza aprire la GUI (utile per verificare `match` e percorsi); i messaggi dell’applicazione e dell’SDK sono visibili nella tab Info/Log, con livello e file di log impostabili nella tab Advanced.

Licenza: le aggiunte del fork (la GUI) sono rilasciate sotto MIT. Il codice upstream `ArduCam_EVK_Demo` (cartelle `c/`, `c++/`, `python/`) e l’SDK Arducam incluso (`evk_sdk/`, versione 1.0.7, ridistribuito as-is) mantengono i termini di licenza Arducam originali.

## Produttore

<a href="https://www.arducam.com/" target="_blank" rel="noopener">Arducam</a> — Simplifying embedded vision for all.
