> For the complete documentation index, see [llms.txt](https://dev.solid-run.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dev.solid-run.com/solidsense-aiot/solidsense-aiot-tips.md).

# SolidSense AIOT — Tips

Practical notes for the SolidRun SolidSense AIOT carrier: peripherals, how to verify they're up, and how to use them from Linux.

Most of the I²C sensors live on the **Flash Card (J6)** add-on and are enabled by the `*-solidsense-aiot-addon-flash-card` device-tree overlay — apply it (see below) when the card is fitted.

The audio subsystem, by contrast, is on the carrier itself: a TAC5111 codec (I²C2 @ 0x50, U11) with two PDM microphones (U13/U14) and a mono speaker path through a PAM8302 amplifier on connector J5 — see the Audio & Microphone section for record/playback examples.

{% hint style="info" %}
**Find the carrier I²C bus**

```sh
i2cdetect -l        # list adapters
i2cdetect -y 1      # scan it (RZ/V2N carrier = bus 1)
```

In `i2cdetect`: **`UU`** = address claimed by a kernel driver, a **number** = device present but unclaimed, **`--`** = nothing.
{% endhint %}

### Flash Card (J6) Overlay

The J6 add-on sensors (HDC3022, OPT4001, LM3645) live in a device-tree overlay applied at boot only when the card is fitted. On RZ/V2N the flash-card overlay is per-connector (it also wires the camera lane), so pick the one matching where the card is fitted:

| SoM          | Base DTB                           | Flash-Card Overlay                                    |
| ------------ | ---------------------------------- | ----------------------------------------------------- |
| RZ/V2N (J8)  | `r9a09g056n48-solidsense-aiot.dtb` | `rzv2n-solidsense-aiot-addon-flash-card-cam-j8.dtbo`  |
| RZ/V2N (J17) | `r9a09g056n48-solidsense-aiot.dtb` | `rzv2n-solidsense-aiot-addon-flash-card-cam-j17.dtbo` |
| i.MX8MP      | `imx8mp-solidsense-aiot.dtb`       | `imx8mp-solidsense-aiot-addon-flash-card.dtbo`        |

Related CSI camera (IMX678) overlays for RZ/V2N:

* `rzv2n-solidsense-aiot-csi-camera-imx678-j8.dtbo`
* `rzv2n-solidsense-aiot-csi-camera-imx678-j17.dtbo`

***

### Enable via extlinux.conf — updated examples

Copy the `.dtbo` to the boot partition next to the base `.dtb`, then add an `FDTOVERLAYS` line to your boot entry in `/boot/extlinux/extlinux.conf`.

**RZ/V2N (flash card on J8):**

```
LABEL Linux
    KERNEL /Image
    FDT    ../renesas/r9a09g056n48-solidsense-aiot.dtb
    FDTOVERLAYS ../renesas/rzv2n-solidsense-aiot-addon-flash-card-cam-j8.dtbo
    APPEND root=PARTUUID=... rootwait console=ttySC0,115200
```

**i.MX8MP (flash card):**

```
LABEL Linux
    KERNEL /Image
    FDT    ../freescale/imx8mp-solidsense-aiot.dtb
    FDTOVERLAYS ../freescale/imx8mp-solidsense-aiot-addon-flash-card.dtbo
    APPEND root=PARTUUID=... rootwait console=ttymxc1,115200
```

Multiple overlays = space-separated, applied in order (e.g. flash card on J8 plus the IMX678 camera on J17):

```
FDTOVERLAYS /rzv2n-solidsense-aiot-addon-flash-card-cam-j8.dtbo /rzv2n-solidsense-aiot-csi-camera-imx678-j17.dtbo
```

> If U-Boot prints `failed on fdt_overlay_apply … '__symbols__'`, the base dtb wasn't built with `-@` — rebuild it with `make dtbs`.

#### Requirements

* **Base DTB built with symbols (`-@`)** so U-Boot can resolve the overlay's references (`&i2c1`, `&pinctrl`, `&v_3_3`). In-tree this needs `DTC_FLAGS_<base> := -@` **and a full `make dtbs`** — a single `make <vendor>/<board>.dtb` does *not* emit symbols. Verify:

  ```sh
  fdtget <base>.dtb /__symbols__ >/dev/null 2>&1 && echo "has symbols" || echo "rebuild with: make dtbs"
  ```
* **U-Boot with overlay support** (`CONFIG_OF_LIBFDT_OVERLAY=y`), using the extlinux/distro-boot path.

#### Confirm it applied (after boot)

```sh
ls /proc/device-tree/soc/i2c@14400800/   # J6 nodes appear: light-sensor@44, humidity-sensor@46, flash-led@65
i2cdetect -y 1                           # 0x44 / 0x46 / 0x65 now present
```

{% hint style="warning" %}
Without the overlay, none of the J6 sensors show up (the base dtb has no nodes for them). If U-Boot prints `failed on fdt_overlay_apply … '__symbols__'`, the base dtb wasn't built with `-@` — rebuild it with `make dtbs`.
{% endhint %}

### HDC3022 — Temperature & Humidity Sensor

TI **HDC3022** on carrier **I²C1**, address **`0x46`**. Handled by the `hdc3020` IIO driver (`CONFIG_HDC3020`) and read through the **IIO** subsystem — not raw I²C.

#### Verify it is detected

```sh
i2cdetect -y 1
# 40: -- -- -- -- -- -- UU --   <- 0x46 = UU : driver bound

for d in /sys/bus/iio/devices/iio:device*; do echo "$d -> $(cat $d/name)"; done
# /sys/bus/iio/devices/iio:device3 -> hdc3020
```

If `0x46` shows `46` instead of `UU`, the chip is present but the driver didn't bind — check `dmesg | grep hdc3020`. A raw `i2cdump 0x46` of all zeros is normal (command/response chip; always read via IIO).

#### Read temperature and humidity

Values are raw; convert with `value = (raw + offset) × scale` (milli-units, ÷1000 → °C / %RH):

```sh
D=$(grep -l hdc3020 /sys/bus/iio/devices/iio:device*/name | xargs dirname)

awk -v tr=$(cat $D/in_temp_raw) -v ts=$(cat $D/in_temp_scale) -v to=$(cat $D/in_temp_offset) \
    -v hr=$(cat $D/in_humidityrelative_raw) -v hs=$(cat $D/in_humidityrelative_scale) \
    'BEGIN{ printf "Temperature: %.2f C\nHumidity:    %.2f %%RH\n", (tr+to)*ts/1000, hr*hs/1000 }'
# Temperature: 26.10 C
# Humidity:    50.76 %RH
```

#### Continuous read loop

```sh
D=$(grep -l hdc3020 /sys/bus/iio/devices/iio:device*/name | xargs dirname)
while true; do
  awk -v tr=$(cat $D/in_temp_raw) -v ts=$(cat $D/in_temp_scale) -v to=$(cat $D/in_temp_offset) \
      -v hr=$(cat $D/in_humidityrelative_raw) -v hs=$(cat $D/in_humidityrelative_scale) \
      'BEGIN{ printf "%.2f C   %.2f %%RH\n", (tr+to)*ts/1000, hr*hs/1000 }'
  sleep 1
done
```

Breathe on the sensor — humidity jumps, temperature drifts up, then both settle. `in_*_peak_raw` hold the max since power-on; `iio_info` (libiio) dumps everything with units.

### OPT4001 — Ambient Light Sensor

TI **OPT4001** on carrier **I²C1**, address **`0x44`**. Driver `opt4001` (`CONFIG_OPT4001`). The device-tree `compatible` **must match the package**: `ti,opt4001-sot-5x3` (SOT-5X3) or `ti,opt4001-picostar` (PicoStar).

#### Verify it is detected

```sh
i2cdetect -y 1                       # 0x44 = UU
for d in /sys/bus/iio/devices/iio:device*; do echo "$d -> $(cat $d/name)"; done
# ... -> opt4001
```

#### Read illuminance

The driver reports **processed lux** directly:

```sh
D=$(grep -l opt4001 /sys/bus/iio/devices/iio:device*/name | xargs dirname)
cat $D/in_illuminance_input          # lux           e.g. 203.167562500
cat $D/integration_time              # current integration time (s)
cat $D/integration_time_available    # selectable values (s)
```

**Example on RZ/V2N:**

```shell
root@rzv2n-sr-som:~# D=$(grep -l opt4001 /sys/bus/iio/devices/iio:device*/name | xargs dirname)
root@rzv2n-sr-som:~# echo $D
/sys/bus/iio/devices/iio:device3
root@rzv2n-sr-som:~# cat $D/in_illuminance_input
211.546125000
root@rzv2n-sr-som:~# cat $D/integration_time_available
0.000600 0.001000 0.001800 0.003400 0.006500 0.012700 0.025000 0.050000 0.100000 0.200000 0.400000 0.800000
```

Read loop:

```sh
D=$(grep -l opt4001 /sys/bus/iio/devices/iio:device*/name | xargs dirname)
while true; do echo "$(cat $D/in_illuminance_input) lux"; sleep 1; done
```

Cover the sensor → lux drops toward 0; shine a light → it rises.

{% hint style="warning" %}
**Not detected (`0x44` absent)?** The chip isn't ACKing. Confirm it's populated on the card, then check the **SDA/VDD solder joints** — with power off, verify continuity from the OPT4001 SDA pin to a known-good bus SDA point (e.g. the HDC3022 SDA). An open/cold joint (SDA floating near 0 V while the bus works for other devices) is a board/assembly fault, not software.
{% endhint %}

### LM3645 — Camera Flash LED

TI **LM3645** flash/torch LED driver on carrier **I²C1**, address **`0x65`**.

#### Verify it is detected

```sh
i2cdetect -y 1
# 60: -- -- -- -- -- 65 --        <- 0x65 present (a number, not UU: no kernel driver)

# read a register to confirm it responds (-f since no driver claims it):
i2cget -y -f 1 0x65 0x00
```

The Flash Card (J6) carries a **TI LM3645** flash driver powering **4× SFH4722 IR emitters**. It is exposed through the Linux **LED flash class** at:

```
/sys/class/leds/ir:flash
```

#### Prerequisites

* Flash Card fitted on connector **J6**.
* Flash Card device-tree overlay applied: `rzv2n-solidsense-aiot-addon-flash-card-cam-j8.dtbo` (or `…-j17.dtbo` for J17; `imx8mp-solidsense-aiot-addon-flash-card.dtbo` on i.MX8MP).
* Driver loaded (`leds-lm3645`).

Verify the device is present and bound:

```sh
lsmod | grep lm3645              # leds_lm3645 loaded
i2cdetect -y 1                   # 'UU' at 0x65 = driver bound
ls /sys/class/leds/              # shows: ir:flash
```

{% hint style="info" %}
If the module is built as a module and not auto-loaded, run `modprobe leds-lm3645`.
{% endhint %}

#### Torch — continuous IR

```sh
cd /sys/class/leds/ir:flash
cat max_brightness        # torch steps available (e.g. 255)
echo 128 > brightness     # IR torch ON (mid level)
echo 0   > brightness     # OFF
```

#### Flash — pulsed IR

```sh
cd /sys/class/leds/ir:flash
cat max_flash_brightness          # max flash current (µA)
echo 1000000 > flash_brightness   # 1 A per output (µA)
echo 100000  > flash_timeout      # 100 ms pulse (µs)
echo 1       > flash_strobe       # fire one pulse (auto-clears)
```

#### Current & timing reference (per output)

| Mode    | Formula                         | Range             |
| ------- | ------------------------------- | ----------------- |
| Flash   | `I = 7.8 mA × code + 7.325 mA`  | \~7 mA … 2 A      |
| Torch   | `I ≈ 1.4 mA × level + 0.525 mA` | \~0.5 mA … 360 mA |
| Timeout | tabulated                       | 10 ms … 400 ms    |

All four outputs are driven identically, so total emission ≈ **4×** the per-output current. Written values are clamped to the device-tree limits (`flash-max-microamp`, `led-max-microamp`, `flash-max-timeout-us`).

#### Confirming it works

{% hint style="info" %}
IR light is **invisible to the eye**. Point a **phone or USB camera** at the four emitters while running torch/flash — IR appears as a faint **violet-white glow** on the camera sensor. Phone *front* cameras usually lack an IR-cut filter and show it best.
{% endhint %}

#### Notes

* Strobe is **software/I2C** triggered; the hardware STR1/STR2 pins are not used.
* HWEN is handled automatically by the driver (`enable-gpios`, pin **P72**).
* Camera-synchronized IR (V4L2 flash) is not wired in this driver version.

{% hint style="danger" %}
**Eye safety:** IR is invisible but can be intense at high current. Do not stare into the emitters, and keep the flash/torch current within the rated limit.
{% endhint %}

### LTE SIM Card Detection (GC02S1) — Polling-Mode

The SolidSense AIOT uses the Sequans **GC02S1** LTE modem with **SIM detection in polling mode**: the modem detects the USIM electrically, polling at a fixed interval, **without** using the SIM card-detect (CD) signal. This is the standard SIM configuration for the board.

{% hint style="success" %}
**This is the default on production units.** SolidRun provisions the GC02S1 with SIM polling mode during manufacturing, so boards ship with this as the default SIM-detection mode (no card-detect line required). The steps below are only needed to (re)apply it on a module that isn't already configured.
{% endhint %}

#### Configuration

To set the modem to SIM polling mode, send these AT commands in order:

```
AT+CFUN=5
AT+SQNHWCFG="sim0","enable","10000"
AT^RESET
AT+CFUN=5
AT+SQNFACTORYSAVE="OEM"
```

* `AT+CFUN=5` — enter manufacturing mode (required to change hardware config).
* `AT+SQNHWCFG="sim0","enable","10000"` — enable SIM polling; the 3rd parameter is the **poll interval in milliseconds** (10000 = 10 s).
* `AT^RESET` — reboot the modem to apply.
* `AT+SQNFACTORYSAVE="OEM"` — store the setting as the module default.

#### Applying from Linux

The modem's AT port enumerates as `/dev/ttyACM0`. Open it, start a background reader, then send each command with `printf`:

```sh
# open the AT port and read responses
PORT=/dev/ttyACM0
stty -F $PORT 115200 raw -echo -onlcr
cat $PORT & CATPID=$!

# enable SIM polling mode
cmd='AT+CFUN=5'                          ; printf '%s\r' "$cmd" > $PORT ; sleep 1
cmd='AT+SQNHWCFG="sim0","enable","10000"'; printf '%s\r' "$cmd" > $PORT ; sleep 1
cmd='AT^RESET'                           ; printf '%s\r' "$cmd" > $PORT ; sleep 20
```

`AT^RESET` re-enumerates the modem over USB, so re-open the port before persisting and verifying:

```sh
# re-open the port after the modem reboots
ls /dev/ttyACM*
PORT=/dev/ttyACM0
stty -F $PORT 115200 raw -echo -onlcr
cat $PORT & CATPID=$!

# persist the setting as the module default
cmd='AT+CFUN=5'                ; printf '%s\r' "$cmd" > $PORT ; sleep 1
cmd='AT+SQNFACTORYSAVE="OEM"'  ; printf '%s\r' "$cmd" > $PORT ; sleep 1
```

#### Verify

Allow \~10 s after boot (one poll interval), then:

```sh
cmd='AT+CPIN?'    ; printf '%s\r' "$cmd" > $PORT ; sleep 1   # -> +CPIN: READY
cmd='AT+SQNCCID?' ; printf '%s\r' "$cmd" > $PORT ; sleep 1   # -> valid ICCID
cmd='AT+CIMI'     ; printf '%s\r' "$cmd" > $PORT ; sleep 1   # -> valid IMSI
```

A `+CPIN: READY` response with a valid ICCID and IMSI confirms the SIM is detected and working.

{% hint style="info" %}
**One-time setting.** `AT+SQNHWCFG` is stored in non-volatile memory and `AT+SQNFACTORYSAVE="OEM"` makes it the module default, so it **persists across reset and power-cycle**. Apply it once per modem; it does not need to be re-run at each boot.
{% endhint %}

### Audio & Microphone

The SolidSense AIOT carries a **Texas Instruments TAC5111IRGER** audio codec (**U11**, on **I²C2 address 0x50**) that provides both a stereo PDM microphone input and a mono speaker output path.

**Hardware**

| Function    | Part                     | Designator | Notes                                                                 |
| ----------- | ------------------------ | ---------- | --------------------------------------------------------------------- |
| Audio codec | TAC5111IRGER             | U11        | I²C2 @ 0x50, I²S on SAI3, **S16\_LE only**                            |
| Microphones | MMICT5837-00-012 ×2      | U13, U14   | PDM digital mics → codec IN1 / IN2                                    |
| Speaker amp | PAM8302AASCR             | —          | Single-channel class-D, driven from codec **OUT1**; **OUT2 grounded** |
| Speaker out | J5 (2×1, 1.27 mm header) | J5         | Amplifier output                                                      |

The codec's **OUT1** feeds the PAM8302 amplifier; **OUT2 is grounded** and unused. Because the PAM8302 is a single-ended-input amp wired with OUT1M as the reference, the OUT1 channel must be driven in **pseudo-differential mode with OUTxM as VCOM** — plain differential mode produces loud noise. The amplifier `/SD` (shutdown) enable is on **GPIO1\_IO12** (active-high) and must be asserted before playback.

> ⚠️ Only **S16\_LE** is supported (S32\_LE is buggy in the driver).

#### Recording (microphone)

The two MMICT5837 PDM mics map to codec channels IN1/IN2. Set the input mux to PDM, enable the two ASI capture channels, set a sane PDM gain and clock, then record:

```sh
# Route both mics through the PDM input path
amixer -c 0 sset 'IN1 Source Mux' 'PDM'
amixer -c 0 sset 'IN2 Source Mux' 'PDM'

# Enable both capture channels on the audio serial interface
amixer sset 'ASI_TX_CH1_EN' cap
amixer sset 'ASI_TX_CH2_EN' cap

# PDM digital gain — try 160–200 (NOT 255); raise until just below clipping
amixer sset 'PDM1 Digital' 180

# PDM clock — 1.4112 MHz or 1.536 MHz
amixer sset 'PDM Clk Divider' '1.4112 MHz'

# Record 60 s, stereo, 48 kHz, 16-bit
arecord -D hw:0,0 -c 2 -r 48000 -f S16_LE -d 60 rec.wav

# Trim the first 0.5 s of startup noise
sox rec.wav rec_trim.wav trim 0.5
```

#### Playback (speaker)

Enable the DAC receive channel and route OUT1 through the DAC into the amplifier using the VCOM pseudo-differential configuration:

```sh
amixer -c 0 sset 'ASI_RX_CH1_EN' on
amixer -c 0 sset 'OUT1x Source' 'DAC Input'
amixer -c 0 sset 'OUT1x Config' 'Pseudo differential with OUTxM as VCOM'
amixer -c 0 sset 'OUT1x Driver' 'Line-out'

aplay -D hw:0,0 rec_trim.wav
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://dev.solid-run.com/solidsense-aiot/solidsense-aiot-tips.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
