# arducam-evk-gui

> Fork of ArduCam_EVK_Demo merging the command-line examples into one sensor-agnostic, data-driven Tkinter GUI, with a register editor driven by a JSON profile.

Published: 2026-05-20
Updated: 2026-08-25
Practice: elettronica
Repository: <https://github.com/stefanofante/arducam-evk-gui>

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

---

Operational guide to arducam-evk-gui: what it is, how to install it (Windows and Linux), how the interface is used, how to work on the sensor registers and how to build the `.cfg` configuration file and the JSON profile. The project is a fork of `ArduCam_EVK_Demo` that unifies the individual command-line examples into a single Tkinter GUI, sensor-agnostic and data-driven. Content derived from the README, `GUIcamera/README.md`, `python/README.md`, the `doc/` guides and the real `sensors/_template.json` and `sensors/mira220.json` files; the authoritative reference remains the GitHub repository.

## What it is and what it is for

The upstream `ArduCAM/ArduCam_EVK_Demo` project ships separate standalone examples (open/init/start/stop, controls, mode switching, register dump, single-register read/write, snapshots, sequence saving, SDK logging). This fork merges them into one Tkinter window and adds a sensor-profile-driven register editor.

It started as a bench tool for bringing up the ams OSRAM Mira220 image sensor: validating sensor and optics, tuning exposure/gain, inspecting and writing registers, capturing frames and sequences for analysis — all from a GUI, without writing host code. MIT-licensed for the fork additions.

## Data-driven architecture: configs/ + sensors/

The GUI is decoupled from the sensor. A `configs/` folder holds the `.cfg` files (one sensor mode per file: resolution, bit depth, format, I²C); a `sensors/` folder holds the `.json` profiles (the human-readable register map). At start-up the GUI scans `configs/`, groups the `.cfg` files by sensor and binds the JSON profile by comparing (case-insensitive) the JSON `match` field with the `.cfg` `TYPE` (or the file name).

Practical consequence: adding or documenting a sensor requires no Python code changes — you only work on a `.cfg` and a `.json`. `config_manager.py` acts as the `.cfg` parser and `.json` profile loader.

## Installation — Windows

To use the GUI you only need the USB driver and the Python dependencies (the C/C++ environment — Visual Studio, CMake, OpenCV, EVK SDK variables — is only needed to compile the bundled C/C++ demos). Download the <a href="https://www.arducam.com/" target="_blank" rel="noopener">Arducam</a> driver package (`install_USB_Camera_Drivers.zip`), unzip it and run `install_driver.bat`.

Once the driver is installed, continue with the Python dependencies and launching the GUI (next section).

## Installation — Linux

Install OpenCV and the Arducam SDK from the official PPA: `sudo apt install libopencv-dev cmake`; then add the Arducam APT key and list and install `arducam-config-parser-dev` and `arducam-evk-sdk-dev`.

Set up the udev rules: download `configure_udev_rules.sh`, make it executable (`sudo chmod +x`) and run it, so the EVK device is accessible without root privileges. Then continue with the Python dependencies.

## Python dependencies and launch

Requirements: Python 3.8 or newer. Runtime dependencies are in `requirements.txt`: `ArducamEvkSDK` (the SDK Python bindings), `Pillow`, `opencv-python`, `numpy`, `arducam-rgbir-remosaic`.

Launch: `cd GUIcamera; pip install -r requirements.txt; python launch_gui.py`. `launch_gui.py` adds the `GUIcamera/` folder to `sys.path` and calls `gui.main()`; running `python gui.py` from inside the folder also works.

## The interface screens

Preview — live canvas with adaptive 16→8 bit shifting (no min-max stretch), live stats, snapshot and sequence saving, log-scale histogram (X = pixel value, Y = count) and a software auto-exposure loop with optional auto-gain.

Controls — auto-generated sliders for every control declared in the `.cfg` (e.g. `Framerate`, `Exp(us)`); the exposure cap follows the framerate. Sensor regs — per-register editor driven by the active JSON profile: a toolbar to switch profile, filter, group and toggle combined multi-byte / per-byte view; each row has an "i" button that opens a datasheet-style help dialog.

Registers (raw) — raw address/value picker with 8 bit checkboxes and a live description from the active profile. Advanced — USB transfer size, sensor mode switch, SDK log level and log file. Info — `dump_info()` output and an in-app SDK log console.

## Typical workflow

1. Connect the EVK over USB and start the GUI.
2. Pick the sensor (from the discovered `.cfg` files), then the specific configuration, then the USB device.
3. Open then Start: capture runs on a background thread and the Tk main loop updates the preview without blocking on USB I/O.
4. Adjust exposure/gain from Controls, inspect/write registers from the Sensor regs tab (with datasheet help), save snapshots or sequences from Preview. For high-bandwidth modes (12-bit at full resolution) increase the USB transfer size in the Advanced tab between `open()` and `start()`.

## The .cfg configuration file

The `.cfg` describes one sensor mode in the Arducam SDK format. Main fields: `CFG_MODE` (0 = parameters from the UI / 1 = use this file as a script); `TYPE` (sensor identifier — the key that ties it to the JSON profile); `VID` (Vendor ID); `SIZE` (width, height); `BIT_WIDTH` (bit depth).

`FORMAT` (image format): 0 = RAW (sub-codes 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` (I²C address/data width): 0 = 8-bit addr/8-bit data, 1 = 8/16, 2 = 16/8, 3 = 16/16. `I2C_ADDR` (sensor I²C address, 8-bit incl. R/W).

Example (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`. The `.cfg` file name is what the GUI shows in the configuration selector.

## Building the sensor JSON profile

A profile (`sensors/<name>.json`) has the top-level keys: `name`, `description`, `match` (a list of strings compared against the `.cfg` `TYPE`/file name, case-insensitive), `guide` (long-form quick-reference text shown in the UI), `tabs` (hints for the individual tabs) and `registers` (the register array). Always start from `sensors/_template.json`.

Each register has: `group` (UI grouping), `name`, `addr` (e.g. `"0x0100"`), `kind`, `desc` (short), `help` (long datasheet-style text) and, where it makes sense, `lo/hi` (range). Supported kinds: `u8`, `bool`, `u16be`, `u16le`, `u24be`, `ro` (read-only), `enum`, `multi` (a bit-field spread across several registers, written read-modify-write) and `computed` (a value computed by a formula over multiple reads, e.g. die temperature).

`enum` adds `choices`: `{ "Label": value }`. `multi` adds `parts`: a list of `{ addr, reg_lsb, width, value_lsb }` mapping the logical value bits onto the physical registers (LSB before MSB). A register may also declare `bits`: `[ { lsb, width, name, desc } ]` to document individual bit-fields. Example (from `_template.json`) — 16-bit exposure across `0x0202`/`0x0203` as `kind` `"multi"`, and sensor state as `"enum"` with Standby/Streaming choices.

The real `sensors/mira220.json` file is the complete reference example: a register map verified against the datasheet, populated `guide` and `tabs`, rich `help` for each register. Copy it as a template when the sensor is similar.

## Adding a new sensor

1. Drop the `.cfg` file produced by the Arducam tooling into `configs/` (the `TYPE` field is the key).
2. Create the matching JSON profile in `sensors/` starting from `_template.json`, making sure `match` contains the same `TYPE` value (or a case-insensitive alias).
3. Restart the GUI: the new sensor and its configurations appear automatically in the selector. No Python code changes.

## The arithmetic of exposure

The advice “start from low exposure and gain” works, but during bring-up you need to know **how much time** a number written into a register corresponds to, and why past a certain point that number cannot be raised. These are three calculations, all coming out of the same two sensor timing registers.

Exposure is not programmed in microseconds but in **lines**, and a line lasts as long as the sensor takes to read it:

$$
t_{\text{line}} = \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` | Line time | Maximum fps |
|---|---|---|---|---|
| 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 |

With the table's first row — typical values for a 5 MP sensor — a line lasts **39.87 µs**, and from there exposure reads off directly:

| Exposure (lines) | Time | Fraction of one frame at 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 % |

The ceiling is **`frame_length_lines`**: exposure cannot exceed the frame duration, because the first line read must have finished integrating before the next frame starts. With `frame_length_lines` = 2200 the maximum is therefore around **87.6 ms**. And this is where bring-up's most common surprise hides: **to expose for longer you have to lengthen the frame, that is, give up frame rate**.

| `frame_length_lines` | Maximum fps | Maximum exposure |
|---|---|---|
| 1200 | 20.9 | 47.76 ms |
| 2200 | 11.4 | 87.62 ms |
| 4400 | 5.7 | 175.33 ms |

So “the image is too dark, I'll raise the exposure” stops being an adjustment at some point and becomes a design choice: more light **or** more fps, not both. These are the same two registers that set the blanking and therefore the bandwidth on the CSI-2 link — the connection is explained in the [MIPI CSI-2 bandwidth wiki](/en/wiki/mipi-csi2-bandwidth/): lengthening the frame to expose longer also **lowers** the link utilization.

### Exposure or gain: they are not equivalent

Faced with a dark image the GUI offers two knobs, and the temptation is to treat them as interchangeable. They are not, and the difference is measured in SNR. In the regime where the dominant noise is **photonic** (shot noise), signal grows as the number of photons collected *N* and noise as √*N*:

| Action | Signal | SNR (shot-noise regime) |
|---|---|---|
| exposure × 2 | × 2 | × 1.41 (**+3.0 dB**) |
| exposure × 4 | × 4 | × 2.00 (**+6.0 dB**) |
| exposure × 8 | × 8 | × 2.83 (**+9.0 dB**) |
| analogue gain × 2 | × 2 | × 1.00 (**0 dB**) |
| digital gain × 2 | × 2 | × 1.00 (**0 dB**) |

Doubling the **exposure** collects twice the photons and improves SNR by a real **3 dB**. Doubling the **gain** multiplies both the signal and the noise already there by two: the image is brighter and not one decibel better. Hence the right order during bring-up: **spend all the available exposure first**, then consider gain.

With one caveat that avoids the opposite mistake: **analogue** gain — applied *before* the converter — is not useless in low light. In that regime the dominant noise is no longer photonic but the read noise of the downstream chain, and amplifying before the ADC makes it less significant relative to the signal. That is why many sensors have a switchable *conversion gain*. **Digital** gain, on the other hand, never helps: it multiplies an already quantized number, and the only thing it adds is the feeling of having done something.

### The 8-bit shift, in numbers

The troubleshooting section says the 16→08 bit conversion applies no min-max stretch, “so a very dark frame stays dark”. By how much:

| 12-bit code | % of scale | After the shift, 8-bit | % of scale |
|---|---|---|---|
| 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 % |

A code of **200 out of 4095** — 4.9 % of scale, a distinctly underexposed frame — becomes **12 out of 255**, that is exactly the same 4.7 %: the shift does not change the *percentage*, so it cannot make visible what is not there. What it loses is resolution: **16 levels at 12 bits collapse into one level at 8**, and in a shadow region where the useful signal occupies a hundred 12-bit codes, six remain. That is why exposure is judged on the **12-bit** histogram, on a logarithmic scale, and not on the 8-bit preview.

## Troubleshooting

- **The EVK does not appear in the device selector.** On Windows, check that `install_driver.bat` was run; on Linux, that the udev rules are installed and the device is accessible without root. Without the driver or correct permissions the SDK does not enumerate the camera.
- **The sensor does not show up among the configurations.** The JSON profile's `match` field must match (case-insensitive) the `.cfg` `TYPE`. Run `python -m config_manager` to see what the GUI bound, without opening the interface.
- **Black or saturated preview.** Start from low exposure and gain and raise gradually; the 16→8 bit shifting applies no min-max stretch, so a very dark frame stays dark. The log-scale histogram helps you see where the energy sits.
- **Dropped frames or low throughput in high-bandwidth modes.** Increase the USB transfer size in the Advanced tab between `open()` and `start()`: at 12-bit full resolution the default may not be enough.

## Diagnostics and licence

Diagnostics: `python -m config_manager` prints the discovered configurations and profiles without opening the GUI (useful to check matches and paths); application and SDK messages are visible in the Info/Log tab, with log level and log file configurable in the Advanced tab.

Licence: the fork additions (the GUI) are released under MIT. The upstream `ArduCam_EVK_Demo` code (`c/`, `c++/`, `python/` folders) and the bundled Arducam SDK (`evk_sdk/`, version 1.0.7, redistributed as-is) retain their original Arducam licence terms.

## Vendor

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