# FAQ

## General

### How do I connect to a specific device?

Use serial-number-based connection when you know the adapter serial. Use HID path when you want to bind to a specific OS device path.

C++ API

    auto sdk = std::make_unique<CosmicSDK>(2000);
    bool okSerial = sdk->connectWithSerialNumber("SN12345678");
    bool okPath = sdk->connect(R"(\\?\hid#vid_2e8a&pid_...)");

C API

    CosmicSDK_Handle* h = CosmicSDK_Create(2000);
    bool ok_serial = CosmicSDK_ConnectWithSerialNumber(h, "SN12345678");
    bool ok_path = CosmicSDK_Connect(h, "\\\\?\\hid#vid_2e8a&pid_...");

## I2C

### Which I2C role is supported by the host adapters?

The host adapter works as I2C Controller (Master) only. Support for the Target (Slave) role is planned for a future release.

C++ API

    // Controller/Master initialization entry point
    uint16_t rc = sdk->i2cControllerInit(I2C_BUS_A, 400000, I2C_PULLUP_2_2kOhm);

C API

    // Controller/Master initialization entry point
    uint16_t rc = CosmicSDK_I2cControllerInit(h, I2C_BUS_A, 400000, I2C_PULLUP_2_2kOhm);

### How many I2C buses are supported on Supernova and Pulsar?

Supernova supports one I2C bus (`I2C_BUS_A`). Pulsar supports two I2C buses (`I2C_BUS_A` and `I2C_BUS_B`).

C++ API

    uint16_t rcA = sdk->i2cControllerInit(I2C_BUS_A, 400000, I2C_PULLUP_2_2kOhm);
    uint16_t rcB = sdk->i2cControllerInit(I2C_BUS_B, 400000, I2C_PULLUP_2_2kOhm); // Pulsar only

C API

    uint16_t rcA = CosmicSDK_I2cControllerInit(h, I2C_BUS_A, 400000, I2C_PULLUP_2_2kOhm);
    uint16_t rcB = CosmicSDK_I2cControllerInit(h, I2C_BUS_B, 400000, I2C_PULLUP_2_2kOhm); // Pulsar only

### How do I differentiate I2C buses in the API?

Use the `bus` argument (`I2C_BUS_A` or `I2C_BUS_B`) in all I2C methods.

C++ API

    sdk->i2cWrite(I2C_BUS_A, 0x50, {0x00}, {0xAA, 0xBB});
    sdk->i2cWrite(I2C_BUS_B, 0x50, {0x00}, {0xCC, 0xDD});

C API

    uint8_t sub[] = {0x00};
    uint8_t dataA[] = {0xAA, 0xBB};
    uint8_t dataB[] = {0xCC, 0xDD};
    CosmicSDK_I2cWrite(h, I2C_BUS_A, 0x50, sub, 1, dataA, 2, false);
    CosmicSDK_I2cWrite(h, I2C_BUS_B, 0x50, sub, 1, dataB, 2, false);

### How do I initialize the I2C interface?

Set bus voltage (if needed), then call the I2C controller init method.

C++ API

    sdk->i2cSetBusVoltage(3300);
    uint16_t rc = sdk->i2cControllerInit(I2C_BUS_A, 400000, I2C_PULLUP_2_2kOhm);

C API

    CosmicSDK_I2cSetBusVoltage(h, 3300, false);
    uint16_t rc = CosmicSDK_I2cControllerInit(h, I2C_BUS_A, 400000, I2C_PULLUP_2_2kOhm);

### How do I change I2C bus configuration after initialization?

Call the set-parameters method instead of calling init again.

C++ API

    uint16_t rc = sdk->i2cSetParameters(I2C_BUS_A, 1000000, I2C_PULLUP_1kOhm);

C API

    uint16_t rc = CosmicSDK_I2cSetParameters(h, I2C_BUS_A, 1000000, I2C_PULLUP_1kOhm);

### How do I write to and read from an I2C target (slave)?

Use `i2cWrite` / `CosmicSDK_I2cWrite` to send bytes to the target address, and `i2cRead` / `CosmicSDK_I2cRead` to read bytes from that target.

C++ API

    std::vector<uint8_t> tx = {0xAA, 0xBB, 0xCC};
    std::vector<uint8_t> rx;
    sdk->i2cWrite(I2C_BUS_A, 0x50, {0x00, 0x10}, tx, false);          // Write at subaddress 0x0010
    sdk->i2cRead(I2C_BUS_A, 0x50, {0x00, 0x10}, 8, rx, false);        // Read 8 bytes from subaddress 0x0010

C API

    uint8_t subaddr[] = {0x00, 0x10};
    uint8_t tx[] = {0xAA, 0xBB, 0xCC};
    uint8_t rx[8] = {0};
    CosmicSDK_I2cWrite(h, I2C_BUS_A, 0x50, subaddr, 2, tx, 3, false);  // Write at subaddress 0x0010
    CosmicSDK_I2cRead(h, I2C_BUS_A, 0x50, subaddr, 2, 8, rx, false);    // Read 8 bytes from subaddress 0x0010

### How can I scan the bus and discover the static address of the I2C targets connected to the USB host adapter?

Use `i2cScanBus` / `CosmicSDK_I2cScanBus`. The discovered static addresses are returned in the output vector/buffer.

Note

Depending on the bus characteristics, such as frequency, voltage, number of targets connected, and the length of the wires used in the hardware setup, the performance of the I2C SCAN BUS may be affected, and as a result, it might return a wrong list of detected addresses, an `SDK_ERROR_TIMEOUT` error, and the firmware might indeed get stuck, especially in the Pulsar. In order to avoid this scenario, consider the following recommendations to run a smooth scan bus procedure:

- Set the clock frequency to low values, such as 100KHz and 400 KHz.

- Disable the pull-up resistors, or set their value to high values - weak pull-ups.

  > - In the case of the Pulsar, suggested values are: `I2C_PULLUP_DISABLE` , `I2C_PULLUP_4kOhm`, or `I2C_PULLUP_2_2kOhm`

  > - In the case of the Supernova, suggested values are: `I2C_PULLUP_DISABLE` , `I2C_PULLUP_10kOhm`, or `I2C_PULLUP_4_7kOhm`

C++ API

    std::vector<uint8_t> addresses;
    uint8_t count7Bit = 0;
    uint8_t count10Bit = 0;
    uint16_t rc = sdk->i2cScanBus(addresses, &count7Bit, &count10Bit, I2C_BUS_A, true);

C API

    uint8_t addresses[32] = {0};
    uint8_t count7Bit = 0;
    uint8_t count10Bit = 0;
    uint16_t rc = CosmicSDK_I2cScanBus(h, addresses, 32, &count7Bit, &count10Bit, I2C_BUS_A, true);

### Does the API support I2C 10-bit addresses?

Only bus scan supports 10-bit addresses today. 10-bit transfer support already exists in firmware but is not yet exposed in the public API. We plan to add API support for 10-bit transfers in a future release.

### Which I2C pull-up resistor values are valid for Supernova and which for Pulsar?

The two devices differ on the available pull-up resistor values. The constants from 150 Ω to 2.2 kΩ are shared; above 2.2 kΩ each device provides its own set. Passing an unsupported value returns `FW_INVALID_PARAMETER`.

I2C pull-up resistor support by device
Constant | Supernova | Pulsar
--- | --- | ---
`I2C_PULLUP_150Ohm` | Yes | Yes
`I2C_PULLUP_220Ohm` | Yes | Yes
`I2C_PULLUP_330Ohm` | Yes | Yes
`I2C_PULLUP_470Ohm` | Yes | Yes
`I2C_PULLUP_680Ohm` | Yes | Yes
`I2C_PULLUP_1kOhm` | Yes | Yes
`I2C_PULLUP_1_5kOhm` | Yes | Yes
`I2C_PULLUP_2_2kOhm` | Yes | Yes
`I2C_PULLUP_3_3kOhm` | Yes | No
`I2C_PULLUP_4kOhm` | No | Yes
`I2C_PULLUP_4_7kOhm` | Yes | No
`I2C_PULLUP_10kOhm` | Yes | No
`I2C_PULLUP_DISABLE` | Yes | Yes

## SPI

### Which SPI role is supported by the host adapters?

The host adapter works as SPI Controller (Master) only.

C++ API

    uint16_t rc = sdk->spiControllerInit(
        SPI_BIT_ORDER_MSB, SPI_MODE_0, SPI_DATA_WIDTH_8_BITS,
        SPI_CHIP_SELECT_0, SPI_CS_ACTIVE_LOW, 1000000);

C API

    uint16_t rc = CosmicSDK_SpiControllerInit(
        h, SPI_BIT_ORDER_MSB, SPI_MODE_0, SPI_DATA_WIDTH_8_BITS,
        SPI_CHIP_SELECT_0, SPI_CS_ACTIVE_LOW, 1000000);

### How do I initialize the SPI interface?

Set SPI voltage (if needed), then call SPI controller init.

C++ API

    sdk->spiSetBusVoltage(3300);
    uint16_t rc = sdk->spiControllerInit(
        SPI_BIT_ORDER_MSB, SPI_MODE_0, SPI_DATA_WIDTH_8_BITS,
        SPI_CHIP_SELECT_0, SPI_CS_ACTIVE_LOW, 10000000);

C API

    CosmicSDK_SpiSetBusVoltage(h, 3300, false);
    uint16_t rc = CosmicSDK_SpiControllerInit(
        h, SPI_BIT_ORDER_MSB, SPI_MODE_0, SPI_DATA_WIDTH_8_BITS,
        SPI_CHIP_SELECT_0, SPI_CS_ACTIVE_LOW, 10000000);

### How do I change SPI bus configuration after initialization?

Use SPI set-parameters to reconfigure without reinitializing.

C++ API

    uint16_t rc = sdk->spiSetParameters(
        SPI_BIT_ORDER_MSB, SPI_MODE_3, SPI_DATA_WIDTH_8_BITS,
        SPI_CHIP_SELECT_1, SPI_CS_ACTIVE_LOW, 5000000);

C API

    uint16_t rc = CosmicSDK_SpiSetParameters(
        h, SPI_BIT_ORDER_MSB, SPI_MODE_3, SPI_DATA_WIDTH_8_BITS,
        SPI_CHIP_SELECT_1, SPI_CS_ACTIVE_LOW, 5000000);

### How do I transmit and receive data in SPI?

Current host adapters support 4-wire SPI. In this mode, TX (MOSI) and RX (MISO) run in parallel during a transfer clock window. For this reason, the API exposes a single method, `spiTransfer` / `CosmicSDK_SpiTransfer`, for both transmit and receive. Provide TX data and transfer length, and read the received bytes from the output buffer/vector.

C++ API

    std::vector<uint8_t> tx = {0x9F};  // JEDEC ID command
    std::vector<uint8_t> rx;
    uint16_t rc = sdk->spiTransfer(tx, rx, 4);  // 1 command byte + 3 response bytes

C API

    uint8_t tx[1] = {0x9F};  // JEDEC ID command
    uint8_t rx[4] = {0};
    uint16_t rc = CosmicSDK_SpiTransfer(h, tx, 1, rx, 4);  // 1 command byte + 3 response bytes

## UART

### How do I initialize the UART interface?

Configure the UART line settings with `uartInit` / `CosmicSDK_UartInit` before sending data.

C++ API

    uint16_t rc = sdk->uartInit(
        UART_BAUD_115200, UART_HW_HANDSHAKE_DIS,
        UART_PARITY_NONE, UART_DATA_SIZE_8, UART_STOP_BIT_1);

C API

    uint16_t rc = CosmicSDK_UartInit(
        h, UART_BAUD_115200, UART_HW_HANDSHAKE_DIS,
        UART_PARITY_NONE, UART_DATA_SIZE_8, UART_STOP_BIT_1);

### How do I change UART configuration after initialization?

Use the UART set-parameters method.

C++ API

    uint16_t rc = sdk->uartSetParameters(
        UART_BAUD_9600, UART_HW_HANDSHAKE_DIS,
        UART_PARITY_NONE, UART_DATA_SIZE_8, UART_STOP_BIT_1);

C API

    uint16_t rc = CosmicSDK_UartSetParameters(
        h, UART_BAUD_9600, UART_HW_HANDSHAKE_DIS,
        UART_PARITY_NONE, UART_DATA_SIZE_8, UART_STOP_BIT_1);

### How do I transmit data in UART?

Use `uartSendMessage` / `CosmicSDK_UartSendMessage` after UART initialization.

C++ API

    std::vector<uint8_t> msg = {'H', 'e', 'l', 'l', 'o', '\n'};
    uint16_t rc = sdk->uartSendMessage(msg);

C API

    uint8_t msg[] = "Hello\n";
    uint16_t rc = CosmicSDK_UartSendMessage(h, msg, 6);

### How does UART reception work?

UART RX is asynchronous only. Register a notification callback and parse UART notifications with helper accessors/functions. Polling is not supported.

C++ API

    sdk->onNotification([](const Notification& n) {
      if (const UartRxNotification* rx = n.uartRx()) {
        // rx->data / rx->dataLength
      }
    });

C API

    static void cb(const Notification* n) {
      UartRxNotification rx;
      if (CosmicSDK_notificationAsUartRx(n, &rx)) {
        // rx.data / rx.dataLength
      }
    }
    CosmicSDK_onNotification(h, cb);

## GPIO

### How do I configure GPIOs?

Call GPIO configure pin with the pin number and desired mode (input or output).

C++ API

    sdk->gpioConfigurePin(GPIO_1, GPIO_DIGITAL_INPUT);
    sdk->gpioConfigurePin(GPIO_3, GPIO_DIGITAL_OUTPUT, GPIO_LOW);

C API

    CosmicSDK_GpioConfigurePin(h, GPIO_1, GPIO_DIGITAL_INPUT, false, GPIO_LOW);
    CosmicSDK_GpioConfigurePin(h, GPIO_3, GPIO_DIGITAL_OUTPUT, true, GPIO_LOW);

### How do I enable GPIO interrupts?

First configure the pin as digital input, then configure the interrupt trigger type.

C++ API

    sdk->gpioConfigurePin(GPIO_1, GPIO_DIGITAL_INPUT);
    sdk->gpioConfigureInterrupt(GPIO_1, GPIO_TRIGGER_BOTH_EDGES);

C API

    CosmicSDK_GpioConfigurePin(h, GPIO_1, GPIO_DIGITAL_INPUT, false, GPIO_LOW);
    CosmicSDK_GpioConfigureInterrupt(h, GPIO_1, GPIO_TRIGGER_BOTH_EDGES);

### How do I receive and handle asynchronous GPIO interrupt notifications?

Register a notification callback and parse GPIO notifications using helper accessors/functions.

C++ API

    sdk->onNotification([](const Notification& n) {
      if (const GpioInterruptNotification* g = n.gpio()) {
        // g->pinNumber
      }
    });

C API

    static void cb(const Notification* n) {
      GpioInterruptNotification gpio;
      if (CosmicSDK_notificationAsGpio(n, &gpio)) {
        // gpio.pinNumber
      }
    }
    CosmicSDK_onNotification(h, cb);

## I3C

### Which I3C role does Supernova support?

Supernova supports the I3C Controller role in the current API.

C++ API

    uint16_t rc = sdk->i3cControllerInit(
        PUSH_PULL_10_MHz_DC_40, OPEN_DRAIN_400_KHz, FAST_MODE, FAST);

C API

    uint16_t rc = CosmicSDK_I3cControllerInit(
        h, PUSH_PULL_10_MHz_DC_40, OPEN_DRAIN_400_KHz, FAST_MODE, FAST);

### Which I3C modes do Supernova and the I3C API support?

The supported transfer modes are:

- I3C SDR

- Legacy I2C over the I3C bus

- I3C HDR-DDR

Write/read method mapping per mode:

- I3C SDR: `i3cSdrWrite` / `i3cSdrRead`

- Legacy I2C over I3C: `i3cLegacyI2cWrite` / `i3cLegacyI2cRead`

- I3C HDR-DDR: `i3cHdrDdrWrite` / `i3cHdrDdrRead`

C++ API

    std::vector<uint8_t> out;
    std::vector<uint8_t> in = {0x11, 0x22};

    // I3C SDR
    sdk->i3cSdrWrite(0x08, {0x00}, in, I3C_XFER_NONE);
    sdk->i3cSdrRead(0x08, {0x00}, 8, out, I3C_XFER_NONE);

    // Legacy I2C over I3C
    sdk->i3cLegacyI2cWrite(0x50, {0x00}, in, I3C_XFER_NONE);
    sdk->i3cLegacyI2cRead(0x50, {0x00}, 8, out, I3C_XFER_NONE);

    // I3C HDR-DDR
    sdk->i3cHdrDdrWrite(0x08, 0x20, in);
    sdk->i3cHdrDdrRead(0x08, 0x20, 8, out);

C API

    uint8_t in[2] = {0x11, 0x22};
    uint8_t out[32];
    uint8_t out_len = 0;

    // I3C SDR
    CosmicSDK_I3cSdrWrite(h, 0x08, NULL, 0, in, 2, I3C_XFER_NONE);
    CosmicSDK_I3cSdrRead(h, 0x08, NULL, 0, 8, out, &out_len, I3C_XFER_NONE);

    // Legacy I2C over I3C
    CosmicSDK_I3cLegacyI2CWrite(h, 0x50, NULL, 0, in, 2, I3C_XFER_NONE);
    CosmicSDK_I3cLegacyI2CRead(h, 0x50, NULL, 0, 8, out, I3C_XFER_NONE);

    // I3C HDR-DDR
    CosmicSDK_I3cHdrDdrWrite(h, 0x08, 0x20, in, 2);
    CosmicSDK_I3cHdrDdrRead(h, 0x08, 0x20, 8, out);

### How do I initialize the I3C interface?

Set I3C voltage and then initialize the controller.

C++ API

    sdk->i3cSetBusVoltage(1200);
    uint16_t rc = sdk->i3cControllerInit(
        PUSH_PULL_10_MHz_DC_40, OPEN_DRAIN_400_KHz, FAST_MODE, FAST);

C API

    CosmicSDK_I3cSetBusVoltage(h, 1200);
    uint16_t rc = CosmicSDK_I3cControllerInit(
        h, PUSH_PULL_10_MHz_DC_40, OPEN_DRAIN_400_KHz, FAST_MODE, FAST);

### How do I change I3C bus configuration after initialization?

Use I3C set-parameters.

C++ API

    uint16_t rc = sdk->i3cSetParameters(
        PUSH_PULL_2_5_MHz_DC_25, OPEN_DRAIN_100_KHz, STANDARD_MODE, STANDARD);

C API

    uint16_t rc = CosmicSDK_I3cSetParameters(
        h, PUSH_PULL_2_5_MHz_DC_25, OPEN_DRAIN_100_KHz, STANDARD_MODE, STANDARD);

### How do I initialize the I3C bus with or without a target table?

Call `InitBus` with an empty table for controller-driven discovery (RSTDAA + ENTDAA). Pass a target table when you need deterministic setup (mixed I3C/I2C, predefined addressing/configuration). The table uses `I3cTargetInfo`, including `I3cBCR` and `I3cTargetConfiguration`.

C++ API

    // Discovery flow (no predefined table)
    sdk->i3cInitBus();

    // Deterministic flow with predefined targets
    I3cBCR bcrI3c{};
    bcrI3c.byte = 0x27;  // non-zero BCR
    I3cTargetConfiguration confI3c{};
    confI3c.bits.targetType = 0;      // I3C target
    confI3c.bits.daaUseENTDAA = true;
    confI3c.bits.IBIRequest = true;

    I3cBCR bcrI2c{};
    bcrI2c.byte = 0x00;
    I3cTargetConfiguration confI2c{};
    confI2c.bits.targetType = 1;      // Legacy I2C target
    confI2c.bits.daaUseENTDAA = false;

    std::vector<I3cTargetInfo> targets = {
      // Supernova can use this desired dynamic address during ENTDAA by matching PID/BCR/DCR.
      {0x69, 0x2A, {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}, bcrI3c, 0x5D, 0, 0, 0, confI3c},
      {0x50, 0x00, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, bcrI2c, 0x00, 0, 0, 0, confI2c}
    };
    sdk->i3cInitBus(targets);

C API

    // Discovery flow (no predefined table)
    CosmicSDK_I3cInitBus(h, NULL, 0);

    // Deterministic flow with predefined targets
    I3cBCR bcr_i3c = {0};
    bcr_i3c.byte = 0x27;  // non-zero BCR
    I3cTargetConfiguration conf_i3c = {0};
    conf_i3c.bits.targetType = 0;    // I3C target
    conf_i3c.bits.daaUseENTDAA = true;
    conf_i3c.bits.IBIRequest = true;

    I3cBCR bcr_i2c = {0};
    bcr_i2c.byte = 0x00;
    I3cTargetConfiguration conf_i2c = {0};
    conf_i2c.bits.targetType = 1;    // Legacy I2C target
    conf_i2c.bits.daaUseENTDAA = false;

    I3cTargetInfo targets[2] = {
      // Supernova can use this desired dynamic address during ENTDAA by matching PID/BCR/DCR.
      {0x69, 0x2A, {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}, bcr_i3c, 0x5D, 0, 0, 0, conf_i3c},
      {0x50, 0x00, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, bcr_i2c, 0x00, 0, 0, 0, conf_i2c}
    };
    CosmicSDK_I3cInitBus(h, targets, 2);

### How do I assign dynamic addresses?

Use `InitBus` for default assignment, or explicit CCCs: `ENTDAA` for dynamic discovery, `SETDASA` for static-to-dynamic mapping, and `SETAASA` for assigning from known static-address targets.

C++ API

    sdk->i3cInitBus();
    sdk->i3cCccENTDAA();
    sdk->i3cCccSETDASA(0x69, 0x0B);
    sdk->i3cCccSETAASA({0x69, 0x6B});

C API

    CosmicSDK_I3cInitBus(h, NULL, 0);
    CosmicSDK_I3cCccENTDAA(h);
    CosmicSDK_I3cCccSETDASA(h, 0x69, 0x0B);
    uint8_t static_addresses[] = {0x69, 0x6B};
    CosmicSDK_I3cCccSETAASA(h, static_addresses, 2);

### Which CCCs are supported by the API?

The API supports common controller-side CCC operations, including:

- Address assignment and discovery: `RSTDAA`, `ENTDAA`, `SETDASA`, `SETAASA`, `SETNEWDA`

- Target identification and capabilities: `GETPID`, `GETBCR`, `GETDCR`, `GETSTATUS`, `GETCAPS`

- Event and reset control: `ENEC` (broadcast/direct), `DISEC` (broadcast/direct), `RSTACT` (broadcast/direct)

- Transfer limits: `GETMWL`, `SETMWL` (broadcast/direct), `GETMRL`, `SETMRL` (broadcast/direct)

C++ API

    sdk->i3cCccRSTDAA();
    sdk->i3cCccENTDAA();
    sdk->i3cCccSETDASA(0x69, 0x0B);
    sdk->i3cCccSETAASA({0x69, 0x6B});
    sdk->i3cCccSETNEWDA(0x08, 0x09);

    std::vector<uint8_t> pid, status, caps;
    uint8_t bcr = 0, dcr = 0;
    uint16_t mwl = 0, mrl = 0;
    std::optional<uint8_t> ibiLen;

    sdk->i3cCccGETPID(0x08, pid);
    sdk->i3cCccGETBCR(0x08, bcr);
    sdk->i3cCccGETDCR(0x08, dcr);
    sdk->i3cCccGETSTATUS(0x08, status, ibiLen);
    sdk->i3cCccGETCAPS(0x08, caps, ibiLen);

    sdk->i3cCccENECBroadcast(IBI);
    sdk->i3cCccENECDirect(0x08, IBI);
    sdk->i3cCccDISECBroadcast(IBI);
    sdk->i3cCccDISECDirect(0x08, IBI);

    sdk->i3cCccRSTACTBroadcast(RESET_I3C_PERIPHERAL);
    uint8_t resetTime = 0;
    sdk->i3cCccRSTACTDirect(0x08, RETURN_TIME_RESET_PERIPHERAL, resetTime);

    sdk->i3cCccGETMWL(0x08, mwl);
    sdk->i3cCccSETMWLBroadcast(256);
    sdk->i3cCccSETMWLDirect(0x08, 256);
    sdk->i3cCccGETMRL(0x08, mrl, ibiLen);
    sdk->i3cCccSETMRLBroadcast(256);
    sdk->i3cCccSETMRLDirect(0x08, 256);

C API

    CosmicSDK_I3cCccRSTDAA(h);
    CosmicSDK_I3cCccENTDAA(h);
    CosmicSDK_I3cCccSETDASA(h, 0x69, 0x0B);
    uint8_t static_addresses[] = {0x69, 0x6B};
    CosmicSDK_I3cCccSETAASA(h, static_addresses, 2);
    CosmicSDK_I3cCccSETNEWDA(h, 0x08, 0x09);

    uint8_t pid[6] = {0};
    uint8_t bcr = 0, dcr = 0;
    uint8_t status[8] = {0}, caps[8] = {0};
    uint8_t status_len = 0, caps_len = 0;
    uint16_t mwl = 0, mrl = 0;
    bool has_ibi_len = false;
    uint8_t ibi_len = 0;

    CosmicSDK_I3cCccGETPID(h, 0x08, pid);
    CosmicSDK_I3cCccGETBCR(h, 0x08, &bcr);
    CosmicSDK_I3cCccGETDCR(h, 0x08, &dcr);
    CosmicSDK_I3cCccGETSTATUS(h, 0x08, status, sizeof(status), &status_len, &has_ibi_len, &ibi_len);
    CosmicSDK_I3cCccGETCAPS(h, 0x08, caps, sizeof(caps), &caps_len, &has_ibi_len, &ibi_len);

    CosmicSDK_I3cCccENECBroadcast(h, IBI);
    CosmicSDK_I3cCccENECDirect(h, 0x08, IBI);
    CosmicSDK_I3cCccDISECBroadcast(h, IBI);
    CosmicSDK_I3cCccDISECDirect(h, 0x08, IBI);

    CosmicSDK_I3cCccRSTACTBroadcast(h, RESET_I3C_PERIPHERAL);
    uint8_t reset_time = 0;
    CosmicSDK_I3cCccRSTACTDirect(h, 0x08, RETURN_TIME_RESET_PERIPHERAL, &reset_time);

    CosmicSDK_I3cCccGETMWL(h, 0x08, &mwl);
    CosmicSDK_I3cCccSETMWLBroadcast(h, 256);
    CosmicSDK_I3cCccSETMWLDirect(h, 0x08, 256);
    CosmicSDK_I3cCccGETMRL(h, 0x08, &mrl, &has_ibi_len, &ibi_len);
    CosmicSDK_I3cCccSETMRLBroadcast(h, 256, false, 0);
    CosmicSDK_I3cCccSETMRLDirect(h, 0x08, 256, false, 0);

### How can I know the actual transfer length in an I3C SDR read?

An I3C target can terminate a read early using End-Of-Data (EOD, T-bit = 0), so the actual returned length may be shorter than requested.

- In C++, check `outData.size()` after `i3cSdrRead`.

- In C, check `outDataLength` from `CosmicSDK_I3cSdrRead`.

C++ API

    std::vector<uint8_t> outData;
    uint16_t rc = sdk->i3cSdrRead(0x08, {0x00}, 32, outData, I3C_XFER_NONE);
    if (rc == SUCCESS) {
      size_t actualLen = outData.size();  // May be < 32 when target signals EOD
    }

C API

    uint8_t out[32];
    uint8_t actual_len = 0;
    uint16_t rc = CosmicSDK_I3cSdrRead(h, 0x08, NULL, 0, 32, out, &actual_len, I3C_XFER_NONE);
    if (rc == SUCCESS) {
      // actual_len may be < 32 when target signals EOD
    }

### How do I handle I3C IBIs?

IBIs are asynchronous notifications. Register a callback and decode notifications using the helper accessors/functions.

C++ API

    sdk->onNotification([](const Notification& n) {
      if (const I3cIbiNotification* ibi = n.i3cIbi()) {
        // ibi->address, ibi->payload, ibi->payloadLength
      }
    });

C API

    static void cb(const Notification* n) {
      I3cIbiNotification ibi;
      if (CosmicSDK_notificationAsI3cIbi(n, &ibi)) {
        // ibi.address, ibi.payload, ibi.payloadLength
      }
    }
    CosmicSDK_onNotification(h, cb);

### How do I handle I3C Hot-Join notifications?

Hot-Join is also asynchronous. Register a callback and parse the notification using the helper accessors/functions.

C++ API

    sdk->onNotification([](const Notification& n) {
      if (const I3cHotJoinNotification* hj = n.i3cHotJoin()) {
        // hj->dynamicAddress, hj->bcr, hj->dcr
        // PID bytes: hj->pid[0] ... hj->pid[5]
      }
    });

C API

    static void cb(const Notification* n) {
      I3cHotJoinNotification hj;
      if (CosmicSDK_notificationAsI3cHotJoin(n, &hj)) {
        // hj.dynamicAddress, hj.bcr, hj.dcr
        // PID bytes: hj.pid[0] ... hj.pid[5]
      }
    }
    CosmicSDK_onNotification(h, cb);