> For the complete documentation index, see [llms.txt](https://docs.anthriq.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.anthriq.com/bxi-studio/developer/overview/features/channels.md).

# Configure channels

> **Applies to:** [Instinct](/bxi-studio/developer/overview/devices/instinct.md) and [xSys](/bxi-studio/developer/overview/devices/xsys.md). Not available on [xBud](/bxi-studio/developer/overview/devices/xbud.md).

Channel configuration lives in device registers: gain, filtering, routing, and which ADC each electrode feeds. Configure these before starting acquisition.

Registers are available on `anthriq-instinct`. See [Device support](/bxi-studio/developer/overview/concepts/device-support.md).

## Address a register

Each register type has its own identifier field, and is reachable either through the `registers` feature with a `type` field or with the type as the feature name. Both reach the same handler.

| Type         | Identifier      | Controls                               |
| ------------ | --------------- | -------------------------------------- |
| `synap`      | `synap_id`      | Per-channel configuration and gain     |
| `nerv`       | `nerv_id`       | ADC configuration and oversampling     |
| `motor`      | `motor_id`      | Motor configuration                    |
| `cms_drl`    | `cms_drl_id`    | Common mode sense and driven right leg |
| `features`   | `features_id`   | Acquisition start and stop             |
| `definition` | `definition_id` | System-wide definition and lockdown    |
| `routing`    | `routing_id`    | Signal routing                         |
| `bucket`     | `bucket_id`     | Data bucket configuration              |

## Read a register

{% tabs %}
{% tab title="Node.js" %}

```typescript
const response = await client.features.executeOperation("registers", "read", {
  type: "synap",
  synap_id: 0,
});

const fields = response.data.fields;
console.log(fields.enabled, fields.gain_stage_1, fields.lpf_setting);
```

{% endtab %}

{% tab title="Python" %}

```python
response = await client.execute_operation(
    "registers", "read", {"type": "synap", "synap_id": 0}
)

fields = response.data["fields"]
print(fields["enabled"], fields["gain_stage_1"], fields["lpf_setting"])
```

{% endtab %}
{% endtabs %}

Read that as: channel enabled, normal (not REF), on ADC 0 channel 0, 20G then 50G of gain, impedance monitoring on, 300 Hz low-pass, driven by motor 0.

The response carries parsed named fields rather than a raw register word.

## Write a register

Writes are partial: send only the fields to change.

{% tabs %}
{% tab title="Node.js" %}

```typescript
import { GainStage1, GainStage2, LpfSetting } from "@anthriq_dev/bxi-interface";

await client.features.executeOperation("registers", "write", {
  type: "synap",
  synap_id: 0,
  fields: {
    enabled: 1,
    gain_stage_1: GainStage1.Gain20G,
    gain_stage_2: GainStage2.Gain50G,
    lpf_setting: LpfSetting.Hz300,
  },
});
```

{% endtab %}

{% tab title="Python" %}

```python
from anthriq_bxi_interface import GainStage1, GainStage2, LpfSetting

await client.execute_operation("registers", "write", {
    "type": "synap",
    "synap_id": 0,
    "fields": {
        "enabled": 1,
        "gain_stage_1": GainStage1.GAIN_20G,
        "gain_stage_2": GainStage2.GAIN_50G,
        "lpf_setting": LpfSetting.HZ_300,
    },
})
```

{% endtab %}
{% endtabs %}

> **Note:** A write returns once the device accepts it, not once the value has propagated. Allow about 200 ms before reading back to verify.

Read-only fields are rejected: `synap_id`, `nerv_id`, `motor_id`, `cms_drl_id`, `current_position`, and `status`.

## Set gain and filtering

The two gain stages sit in different parts of the signal chain and **count in opposite directions**. Use the enums rather than bare integers.

| Value | `gain_stage_1` (Synap) | `gain_stage_2` (Nerv) |
| ----- | ---------------------- | --------------------- |
| `0`   | 20G                    | 5G                    |
| `1`   | 10G                    | 10G                   |
| `2`   | 5G                     | 50G                   |
| `3`   | 2G                     | 100G                  |

Low-pass filtering is set per channel on the same register:

| `lpf_setting` | Cutoff |
| ------------- | ------ |
| `0`           | 700 Hz |
| `1`           | 300 Hz |
| `2`           | 100 Hz |
| `3`           | 40 Hz  |

Converters translate either way, which is useful when a value comes from a user interface:

{% tabs %}
{% tab title="Node.js" %}

```typescript
import { gainStage1ToString, stringToLpfSetting } from "@anthriq_dev/bxi-interface";

gainStage1ToString(0);          // "20G"
stringToLpfSetting("300Hz");    // 1
```

{% endtab %}

{% tab title="Python" %}

```python
from anthriq_bxi_interface import gain_stage_1_to_string, string_to_lpf_setting

gain_stage_1_to_string(0)        # "20G"
string_to_lpf_setting("300Hz")   # LpfSetting.HZ_300
```

{% endtab %}
{% endtabs %}

The `stringTo*` converters fall back to the first value on an unrecognised input rather than raising, so validate input from a user before converting.

## Configure the Synap register

| Field                          | Range     | Meaning                    |
| ------------------------------ | --------- | -------------------------- |
| `synap_id`                     | Read-only | Register index             |
| `enabled`                      | `0`, `1`  | Disabled, enabled          |
| `synap_type`                   | `0`, `1`  | Normal, REF                |
| `ref_synap_id`                 | 8-bit     | Reference channel          |
| `connected_nerv_id`            | 4-bit     | Attached ADC               |
| `connected_nerv_channel_id`    | 3-bit     | Channel on that ADC        |
| `gain_stage_1`                 | `0`–`3`   | Gain in the Synap          |
| `gain_stage_2`                 | `0`–`3`   | Gain in the Nerv           |
| `impedance_monitoring_enabled` | `0`, `1`  | Disable, enable            |
| `lpf_setting`                  | `0`–`3`   | Low-pass cutoff            |
| `cms_drl_id`                   | 4-bit     | Attached CMS/DRL block     |
| `cms_contribution_ch`          | 5-bit     | CMS contribution channel   |
| `motor_associated`             | `0`, `1`  | Not associated, associated |
| `associated_motor_id`          | 8-bit     | Attached motor             |

`connected_nerv_id` and `connected_nerv_channel_id` together determine the absolute channel that appears in stream frames, as `(adc_id << 3) | channel_local`. See [Stream lifecycle](/bxi-studio/developer/overview/concepts/streams.md).

## Configure the ADC

Each Nerv is an ADC serving up to eight channels. Oversampling is set separately for EXG and impedance, because the two run at different rates and want different noise trade-offs: a higher OSR buys resolution and costs bandwidth.

| Field               | Range     | Meaning                    |
| ------------------- | --------- | -------------------------- |
| `nerv_id`           | Read-only | Register index             |
| `enabled`           | `0`, `1`  | Disabled, enabled          |
| `osr_for_exg`       | `0`–`31`  | Oversampling for EXG       |
| `osr_for_impedance` | `0`–`31`  | Oversampling for impedance |

Oversampling values span four filter families. `osrValueToString` renders one readably:

{% tabs %}
{% tab title="Node.js" %}

```typescript
import { osrValueToString } from "@anthriq_dev/bxi-interface";

osrValueToString(11);   // "Sinc4: OSR = 32"
```

{% endtab %}

{% tab title="Python" %}

```python
from anthriq_bxi_interface import osr_value_to_string

osr_value_to_string(11)   # "Sinc4: OSR = 32"
```

{% endtab %}
{% endtabs %}

## Configure common mode sense

Common mode sense and driven right leg suppress interference picked up equally by every electrode, mains hum above all. CMS measures the body's common potential; DRL feeds an inverted copy back, holding the subject near the amplifier's reference.

Each block serves a set of channels, and a Synap register points at its block through `cms_drl_id`. A channel whose `cms_drl_id` names a disabled block keeps working but loses common-mode rejection, which usually shows as 50 or 60 Hz riding on every trace.

| Field                              | Range     | Meaning             |
| ---------------------------------- | --------- | ------------------- |
| `cms_drl_id`                       | Read-only | Register index      |
| `enabled`                          | `0`, `1`  | Disabled, enabled   |
| `connected_nerv`                   | 4-bit     | Attached ADC        |
| `cms_mon_ch`                       | 3-bit     | CMS monitor channel |
| `drl_mon_ch`                       | 3-bit     | DRL monitor channel |
| `drl_impedance_monitoring_enabled` | `0`, `1`  | Disable, enable     |
| `cms_input_map`                    | 24-bit    | One bit per input   |
| `cms_pga`                          | `0`–`6`   | -1G through -64G    |

## Start and stop acquisition

The `features` register carries the acquisition switch and the automatic control flags.

| Field                    | Range      | Meaning                 |
| ------------------------ | ---------- | ----------------------- |
| `features_id`            | Always `0` | Register index          |
| `start_stop_acq`         | `0`, `1`   | Stop, start acquisition |
| `auto_impedance_control` | `0`, `1`   | Disable, enable         |
| `auto_cms_pga_control`   | `0`, `1`   | Disable, enable         |

## Lock the configuration

The `definition` register holds system-wide settings.

| Field                    | Range      | Meaning                                    |
| ------------------------ | ---------- | ------------------------------------------ |
| `definition_id`          | Always `0` | Register index                             |
| `lockdown_system_config` | `0`, `1`   | Writing `1` locks all system configuration |
| `eeg_stream_enabled`     | 2-bit      | EXG stream enable mask                     |
| `z_stream_enabled`       | 8-bit      | Impedance stream enable mask               |
| `notch_enabled`          | 4-bit      | Notch filter mask                          |
| `bucket_output_ports`    | 4-bit      | Bucket output port mask                    |
| `exporter_ports`         | 4-bit      | Exporter port mask                         |
| `exporter_interface`     | 2-bit      | Exporter interface selection               |

> **Warning:** Writing `1` to `lockdown_system_config` locks every system configuration register. Recovering the device requires a power cycle or factory service.

## Configure many channels at once

A `commands` array covers every entry in one round trip, which matters when configuring sixteen channels at session start.

{% tabs %}
{% tab title="Node.js" %}

```typescript
const response = await client.features.executeOperation("registers", "read", {
  commands: [
    { type: "synap", synap_id: 0 },
    { type: "synap", synap_id: 1 },
    { type: "nerv", nerv_id: 0 },
  ],
});

for (const result of response.data.results) {
  console.log(result.type, result.id, result.fields);
}
```

{% endtab %}

{% tab title="Python" %}

```python
response = await client.execute_operation("registers", "read", {
    "commands": [
        {"type": "synap", "synap_id": 0},
        {"type": "synap", "synap_id": 1},
        {"type": "nerv", "nerv_id": 0},
    ]
})

for result in response.data["results"]:
    print(result["type"], result["id"], result["fields"])
```

{% endtab %}
{% endtabs %}

Batch writes take the same shape with a `fields` object per command:

{% tabs %}
{% tab title="Node.js" %}

```typescript
await client.features.executeOperation("registers", "write", {
  commands: [
    { type: "synap", synap_id: 0, fields: { enabled: 1, gain_stage_1: 0 } },
    { type: "synap", synap_id: 1, fields: { enabled: 1, gain_stage_1: 0 } },
  ],
});
```

{% endtab %}

{% tab title="Python" %}

```python
await client.execute_operation("registers", "write", {
    "commands": [
        {"type": "synap", "synap_id": 0, "fields": {"enabled": 1, "gain_stage_1": 0}},
        {"type": "synap", "synap_id": 1, "fields": {"enabled": 1, "gain_stage_1": 0}},
    ]
})
```

{% endtab %}
{% endtabs %}

## Verify a write

Read back rather than assuming, since acceptance is not propagation.

{% tabs %}
{% tab title="Node.js" %}

```typescript
await client.features.executeOperation("registers", "write", {
  type: "synap",
  synap_id: 0,
  fields: { enabled: 1 },
});

await new Promise((resolve) => setTimeout(resolve, 200));

const check = await client.features.executeOperation("registers", "read", {
  type: "synap",
  synap_id: 0,
});

if (check.data.fields.enabled !== 1) {
  throw new Error("Register write did not take effect");
}
```

{% endtab %}

{% tab title="Python" %}

```python
import asyncio

await client.execute_operation("registers", "write", {
    "type": "synap",
    "synap_id": 0,
    "fields": {"enabled": 1},
})

await asyncio.sleep(0.2)

check = await client.execute_operation(
    "registers", "read", {"type": "synap", "synap_id": 0}
)

if check.data["fields"]["enabled"] != 1:
    raise RuntimeError("Register write did not take effect")
```

{% endtab %}
{% endtabs %}

## Next steps

* [Position the headset](/bxi-studio/developer/overview/devices/instinct/motors.md)
* [Measure contact impedance](/bxi-studio/developer/overview/features/impedance.md)
* [Stream EXG](/bxi-studio/developer/overview/features/exg.md)
