# Configuring gain_lock_mode for ESP32-C6 in ESPectre: A Complete Guide

> Master gain_lock_mode configuration for ESP32 C6 in ESPectre. Learn about CV normalization fallback for hardware AGC lock limitations and optimize your setup.

- Repository: [Francesco Pace/espectre](https://github.com/francescopace/espectre)
- Tags: how-to-guide
- Published: 2026-06-10

---

**ESP32-C6 devices lack hardware AGC lock support, so ESPectre automatically falls back to CV normalization whenever `GAIN_LOCK_MODE` is set to `"auto"` or `"disabled"` in [`micro-espectre/src/config.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/config.py).**

The ESPectre firmware provides Wi-Fi sensing capabilities for ESP32 microcontrollers, but configuring gain_lock_mode for ESP32 C6 requires specific handling due to missing automatic gain control (AGC) hardware. When running on ESP32-C6 chips, the system cannot achieve a hardware gain lock and instead relies on coefficient of variation (CV) normalization to compute gain-invariant turbulence metrics. This guide explains the three available gain-lock modes and how the firmware automatically adapts the segmentation pipeline for ESP32-C6 hardware.

## Understanding Gain-Lock Modes

The repository defines three configurable gain-lock behaviors in [`micro-espectre/src/config.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/config.py). Because the ESP32-C6 (and original ESP32) do not implement hardware gain locking, the firmware treats these modes as follows:

- **`auto`** (default): Skips the gain-lock phase if the AGC value falls below `GAIN_LOCK_MIN_SAFE_AGC` (30). On ESP32-C6, this effectively forces CV normalization because the hardware cannot achieve a valid lock.
- **`enabled`**: Forces a gain-lock attempt on every boot. On ESP32-C6 this operation always fails, making this mode effectively a no-op that still results in CV normalization.
- **`disabled`**: Explicitly bypasses the gain-lock phase entirely, ensuring CV normalization is always used without attempting hardware calibration.

## Configuration Constants in config.py

Central configuration resides in [`micro-espectre/src/config.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/config.py), where you define the mode and safety threshold:

```python

# Gain Lock Configuration

# Modes: "auto" (skip if signal too strong), "enabled" (always lock), "disabled" (never lock)

GAIN_LOCK_MODE = "auto"       # Recommended: "auto" - skips gain lock if AGC < 30

GAIN_LOCK_MIN_SAFE_AGC = 30   # Minimum safe AGC value (below this, gain lock is skipped in auto mode)

```

The constant `GAIN_LOCK_MIN_SAFE_AGC` determines the threshold below which the firmware considers a signal too weak for reliable locking. When `GAIN_LOCK_MODE` is `"auto"` and the AGC reading sits below 30, the system immediately enables CV normalization rather than attempting a doomed lock on ESP32-C6 hardware.

## Boot-Time Detection and Pipeline Adaptation

### Chip Detection in main.py

During initialization, [`micro-espectre/src/main.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/main.py) detects the chip type and sets the normalization flag accordingly:

```python
if chip == 'ESP32-C6':
    # No hardware gain lock → must use CV normalization

    needs_cv_normalization = True

```

This logic ensures that `needs_cv_normalization` is `True` for all ESP32-C6 deployments, regardless of the configured mode, preventing the firmware from waiting on a hardware lock that will never complete.

### Segmentation Context in segmentation.py

The motion-detection pipeline consumes this flag via `SegmentationContext` in [`micro-espectre/src/segmentation.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/segmentation.py):

```python

# Default False for compatibility with origin/develop (most chips have gain lock)

# Set to True for ESP32 which doesn't have gain lock

self.use_cv_normalization = False

```

When the calibration code detects a missing lock or the user forces `disabled` mode, the context flips `use_cv_normalization` to `True`, ensuring the turbulence calculations use standard-deviation-over-mean normalization rather than AGC-locked amplitude values.

## CSI Packet Indicators and Testing

Each CSI packet carries a gain-locked bit (bit 0) defined in [`micro-espectre/tools/csi_utils.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/tools/csi_utils.py). For ESP32-C6, this bit is always `0`, and the parsing code annotates the description accordingly:

```python
description = f'HT20 {self.label}, no gain lock (ESP32 lacks AGC lock support)'

```

The test suite in [`micro-espectre/tests/test_validation_real_data.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/tests/test_validation_real_data.py) explicitly validates this behavior:

```python
def needs_cv_normalization(chip):
    return chip == 'ESP32'

```

This function ensures that unit tests verify CV normalization is applied whenever the target chip lacks hardware lock capabilities.

## Practical Configuration Examples

### Changing the Mode in Source Code

To permanently disable gain-lock attempts and force CV normalization, edit [`micro-espectre/src/config.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/config.py):

```python
GAIN_LOCK_MODE = "disabled"          # Forces CV normalization on every boot

GAIN_LOCK_MIN_SAFE_AGC = 30          # (kept for compatibility with other chips)

```

After rebuilding and flashing (`pio run`), the boot log will display:

```

⚙️  Gain lock mode: disabled → CV normalization always enabled

```

### Runtime Override via CLI

For testing purposes, you can override the configuration constant without modifying source files. The CLI in [`micro-espectre/src/main.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/main.py) accepts:

```bash
python -m src.main --gain-lock-mode disabled

```

This argument takes precedence over [`config.py`](https://github.com/francescopace/espectre/blob/main/config.py) for that execution only, useful for debugging across different chip variants.

### Verifying Configuration Programmatically

You can confirm the active configuration at runtime using the detection utilities:

```python
from src.config import GAIN_LOCK_MODE
from src.main import detect_chip_type

chip = detect_chip_type()          # Returns "ESP32-C6"

print(f"Chip: {chip}, Gain-lock mode: {GAIN_LOCK_MODE}")

# Expected output for ESP32-C6:

# Chip: ESP32-C6, Gain-lock mode: disabled

```

## Summary

- **ESP32-C6 lacks hardware AGC lock support**, making CV normalization mandatory for accurate turbulence sensing.
- **Set `GAIN_LOCK_MODE = "disabled"`** in [`micro-espectre/src/config.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/config.py) to explicitly skip the lock phase and speed up boot times.
- **The firmware automatically detects ESP32-C6** in [`main.py`](https://github.com/francescopace/espectre/blob/main/main.py) and sets `needs_cv_normalization = True` before the segmentation pipeline initializes.
- **CSI packets** indicate the lack of gain lock via bit 0, which [`csi_utils.py`](https://github.com/francescopace/espectre/blob/main/csi_utils.py) parses to annotate packet descriptions.
- **All testing** validates that CV normalization is applied when `needs_cv_normalization(chip)` returns `True` for ESP32 variants.

## Frequently Asked Questions

### What is gain_lock_mode in ESPectre?

**gain_lock_mode** is a firmware configuration constant that controls whether the system attempts to lock the automatic gain control (AGC) during boot. It supports three values: `"auto"` (skip lock if AGC < 30), `"enabled"` (always attempt lock), and `"disabled"` (never attempt lock). According to the ESPectre source code in [`micro-espectre/src/config.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/config.py), this setting determines whether the segmentation pipeline uses hardware-locked amplitude values or falls back to CV normalization.

### Why does ESP32-C6 require CV normalization instead of gain locking?

The ESP32-C6 hardware does not implement an AGC lock mechanism, unlike the ESP32-S3, C3, or C5. As implemented in [`micro-espectre/src/main.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/main.py), the firmware detects the chip type and sets `needs_cv_normalization = True` for ESP32-C6 devices. This ensures the motion-detection pipeline uses coefficient of variation (standard deviation divided by mean) rather than absolute amplitude values, providing gain-invariant metrics without hardware support.

### How do I verify that gain lock is disabled on my ESP32-C6?

Check the boot logs for the message `CV normalization always enabled` or inspect the CSI packet descriptions in [`micro-espectre/tools/csi_utils.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/tools/csi_utils.py), which will show `no gain lock (ESP32 lacks AGC lock support)` when parsing packets from an ESP32-C6. Programmatically, you can import `detect_chip_type` from [`main.py`](https://github.com/francescopace/espectre/blob/main/main.py) and verify it returns `"ESP32-C6"` while `GAIN_LOCK_MODE` is set to `"disabled"` or `"auto"`.

### Can I force gain lock on ESP32-C6 using "enabled" mode?

Setting `GAIN_LOCK_MODE = "enabled"` will force the firmware to attempt a gain lock on every boot, but on ESP32-C6 this attempt will always fail because the hardware lacks the necessary AGC lock circuitry. The mode becomes effectively a no-op, and the system still falls back to CV normalization after the failed attempt. For ESP32-C6 deployments, `"disabled"` or `"auto"` provide the same final result with faster boot times.