Nicla voice I2c communication with another Nicla voice

Hello,

I’m trying to deploy two models for my project. In the current prototyping phase, we are using two Nicla Voice boards and put the 2 models on them separately. We are modifying this firmware repo https://github.com/edgeimpulse/firmware-arduino-nicla-voice to test whether it works as expected. The code I wrote for the I2C sender and receiver is attached below.
It turns out that the I2C functionality of both the sender and receiver did not work. Apart from the tests with 2 other I2C devices with working sending and receiving functionalities, we also used a logic analyzer to check the output of the SDA and SCL pins of the Nicla Voice sender.
The major change for the sender is:

static void match_event(char* label)
{
    if (_on_match_enabled == true){
        if (strlen(label) > 0) {
            got_match = true;
            ei_printf("Match: %s\n", label);

            if (strcmp(label, "NN0:z_openset") == 0 && current_motion != 0) {
                ei_printf("Motion changed from %d to %d\n", current_motion, 0);
                motion_changed = true;
                current_motion = 0;
            }
.....
        }
    }
}

 added the code snippet below to start a transmission to address 96 when a new motion is detected:
        if (motion_changed) {
            motion_changed = false;
            Wire.beginTransmission(96); // transmit to device #9
            Wire.write(current_motion); // sends x
            Wire.endTransmission();     // stop transmitting
            ei_printf("motion sent: %d\n", current_motion);
        }

The major change for the receiver is:
init I2C with the address 96 and register the I2C receive event with a handler
void receiveEvent(int bytes) {
  current_motion = Wire.read();    // read one character from the I2C
  ei_printf("New motion: %d\n", current_motion);
}

...
    Wire.begin(96);
    Wire.onReceive(receiveEvent);
...

More details such as the I2C setup can be found in the cpp files but I believe the code is correct. We’ve tested a lot and the I2C is definitely not working. For example, we got the serial output from the sender:

Match: NN0:snake Motion changed from 1 to 2 motion sent: 2 Match: NN0:circular Motion changed from 2 to 1 motion sent: 1 Match: NN0:wave

All the code warping the I2C transmission is working but the I2C transmission code is not, since no signal is detected by the logic analyzer at all.

Any help/suggestions would be greatly appreciated.

** PS for some reason the arduino forum is not letting me attach my files so I am pasting the code below.

Cpp file for the sender code:

/* Edge Impulse ingestion SDK
 * Copyright (c) 2022 EdgeImpulse Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
/* Include ----------------------------------------------------------------- */
#include "ei_syntiant_ndp120.h"
#include "Nicla_System.h"
#include "NDP.h"
#include "edge-impulse-sdk/porting/ei_classifier_porting.h"
#include "ingestion-sdk-platform/nicla_syntiant/ei_at_handlers.h"
#include "ingestion-sdk-platform/nicla_syntiant/ei_device_syntiant_nicla.h"
#include "ingestion-sdk-platform/sensors/ei_inertial.h"
#include "inference/ei_run_impulse.h"
#ifdef WITH_IMU
#include "ingestion-sdk-platform/sensors/ei_inertial.h"
#include "model-parameters/model_metadata.h"
#else
#include "ingestion-sdk-platform/sensors/ei_microphone.h"
#endif

#include "rtos.h"
#include "Thread.h"
#include "EventQueue.h"
#include <Arduino.h>

#include <Wire.h>

#define TEST_READ_TANK      0

/* device class */
extern NDPClass NDP;
static ATServer *at;
static bool ndp_is_init;

#if TEST_READ_TANK == 1
static void test_ndp_extract(void);
#endif


static void error_event(void);
static void match_event(char* label);
static void irq_event(void);

static bool _on_match_enabled = false;
static volatile bool got_match = false;
static volatile bool got_event = false;

static volatile int current_motion = 0;

/* Public functions -------------------------------------------------------- */
void receiveEvent(int bytes) {
  current_motion = Wire.read();    // read one character from the I2C
  ei_printf("New motion: %d\n", current_motion);
}

/**
 * @brief
 *
 */
void ei_setup(char* fw1, char* fw2, char* fw3)
{
    uint8_t valid_synpkg = 0;
    bool board_flashed = false;
    uint8_t flashed_count = 0;
    EiDeviceSyntiantNicla *dev = static_cast<EiDeviceSyntiantNicla*>(EiDeviceInfo::get_device());
    char* ptr_fw[] = {fw1, fw2, fw3};

    ndp_is_init = false;
    Serial.begin(115200);

    nicla::begin();
    nicla::disableLDO();    // needed
    nicla::leds.begin();

    Wire.begin(96);
    Wire.onReceive(receiveEvent);

    while (!Serial) {   /* if Serial not avialable */
        nicla::leds.setColor(red);
    }

    ei_printf("Hello from Edge Impulse on Arduino Nicla Voice\r\n"
            "Compiled on %s %s\r\n",
            __DATE__,
            __TIME__);

    nicla::leds.setColor(green);
    //NDP.onError(error_event);
    NDP.onEvent(irq_event);
    NDP.onMatch(match_event);

    dev->get_ext_flash()->init_fs();
    for (int8_t i = 0; i < 3 ; i++) {
        if (ptr_fw[i] != nullptr){                  // nullptr check
            if (dev->get_file_exist(ptr_fw[i])){
                ei_printf("%s exist\n", ptr_fw[i]);
                valid_synpkg++;
            }
            else{
                ei_printf("%s not found!\n", ptr_fw[i]);
            }
        }
    }
    //dev->get_ext_flash()->deinit_fs();  // de init as NDP re init it

    if (valid_synpkg == 3){
        NDP.begin(fw1);
        NDP.load(fw2);
        NDP.load(fw3);
        NDP.getInfo();
        ndp_is_init = true;

#ifdef WITH_IMU
        NDP.configureInferenceThreshold(EI_CLASSIFIER_NN_INPUT_FRAME_SIZE);
#else
        NDP.turnOnMicrophone();
        NDP.getAudioChunkSize();    /* otherwise it is not initialized ! */
#endif
        NDP.interrupts();

        ei_syntiant_set_match();
        nicla::leds.setColor(off);
    }
    else{
        ei_printf("NDP not properly initialized\n");
        nicla::leds.setColor(red);
    }

    dev->get_ext_flash()->init_fs();    // NDP probably will de init and unmount

    /* init ar server */
    at = ei_at_init(dev);

    /* start inference */
    if (ndp_is_init == true) {
        /* sensor init */
        ei_inertial_init();
        ei_run_nn_normal();
    }

    ei_printf("Type AT+HELP to see a list of commands.\r\n");
    at->print_prompt();
}

/**
 * @brief
 *
 */
void ei_main(void)
{
    int match = -1;
    /* handle command comming from uart */
    char data = Serial.read();

    while (data != 0xFF) {
        at->handle(data);

        if (ei_run_impulse_is_active() && data == 'b') {
            ei_start_stop_run_impulse(false);
        }

        data = Serial.read();
    }

    if (ei_run_impulse_is_active() ==true) {
        if (got_match == true){
            got_match = false;
            nicla::leds.setColor(blue);
            ThisThread::sleep_for(100);
            nicla::leds.setColor(off);
        }

        if (got_event == true){
            got_event = false;
            nicla::leds.setColor(green);
            ThisThread::sleep_for(100);
            nicla::leds.setColor(off);
            match = NDP.poll();
        }

        if (match > 0) {
            ei_printf("match: %d\n", match);
            match = -1;
        }

#ifdef WITH_IMU
        // for now by default we stay in inference
        if (ei_run_impulse_is_active()) {
            ei_run_impulse();
        }
#endif
    }

}


/**
 * @brief disable interrupt from NDP class
 *
 */
void ei_syntiant_clear_match(void)
{
    _on_match_enabled = false;
    //NDP.turnOffMicrophone();
    //NDP.noInterrupts();
}

/**
 * @brief enable interrupt from NDP clas
 *
 */
void ei_syntiant_set_match(void)
{
    _on_match_enabled = true;
    //NDP.turnOnMicrophone();
    //NDP.interrupts();
}

/**
 * @brief Callback when an Error is triggered
 * @note it never exit!
 */
static void error_event(void)
{
    nicla::leds.begin();
    while (1) {
        nicla::leds.setColor(red);
        ThisThread::sleep_for(250);
        nicla::leds.setColor(off);
        ThisThread::sleep_for(250);
    }
    nicla::leds.end();
}

/**
 * @brief Callback when a Match is triggered
 *
 * @param label The Match label
 */
static void match_event(char* label)
{
    if (_on_match_enabled == true){
        if (strlen(label) > 0) {
            got_match = true;
            ei_printf("Match: %s\n", label);
            ei_printf("current_motion: %d\n", current_motion);
        }
    }
}

/**
 * @brief
 *
 */
static void irq_event(void)
{
    if (_on_match_enabled == true) {
        got_event = true;
    }
}


code for the receiver:

/* Edge Impulse ingestion SDK
 * Copyright (c) 2022 EdgeImpulse Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
/* Include ----------------------------------------------------------------- */
#include "ei_syntiant_ndp120.h"
#include "Nicla_System.h"
#include "NDP.h"
#include "edge-impulse-sdk/porting/ei_classifier_porting.h"
#include "ingestion-sdk-platform/nicla_syntiant/ei_at_handlers.h"
#include "ingestion-sdk-platform/nicla_syntiant/ei_device_syntiant_nicla.h"
#include "ingestion-sdk-platform/sensors/ei_inertial.h"
#include "inference/ei_run_impulse.h"
#ifdef WITH_IMU
#include "ingestion-sdk-platform/sensors/ei_inertial.h"
#include "model-parameters/model_metadata.h"
#else
#include "ingestion-sdk-platform/sensors/ei_microphone.h"
#endif

#include "rtos.h"
#include "Thread.h"
#include "EventQueue.h"
#include <Arduino.h>

#include <Wire.h>

#define TEST_READ_TANK      0

/* device class */
extern NDPClass NDP;
static ATServer *at;
static bool ndp_is_init;

#if TEST_READ_TANK == 1
static void test_ndp_extract(void);
#endif


static void error_event(void);
static void match_event(char* label);
static void irq_event(void);

static bool _on_match_enabled = false;
static volatile bool got_match = false;
static volatile bool got_event = false;

static volatile int current_motion = 0;
static volatile bool motion_changed = false;

/* Public functions -------------------------------------------------------- */
/**
 * @brief
 *
 */
void ei_setup(char* fw1, char* fw2, char* fw3)
{
    uint8_t valid_synpkg = 0;
    bool board_flashed = false;
    uint8_t flashed_count = 0;
    EiDeviceSyntiantNicla *dev = static_cast<EiDeviceSyntiantNicla*>(EiDeviceInfo::get_device());
    char* ptr_fw[] = {fw1, fw2, fw3};

    ndp_is_init = false;
    Serial.begin(115200);

    nicla::begin();
    nicla::disableLDO();    // needed
    nicla::leds.begin();

    Wire.begin();

    while (!Serial) {   /* if Serial not avialable */
        nicla::leds.setColor(red);
    }

    ei_printf("Hello from Edge Impulse on Arduino Nicla Voice\r\n"
            "Compiled on %s %s\r\n",
            __DATE__,
            __TIME__);

    nicla::leds.setColor(green);
    //NDP.onError(error_event);
    NDP.onEvent(irq_event);
    NDP.onMatch(match_event);

    dev->get_ext_flash()->init_fs();
    for (int8_t i = 0; i < 3 ; i++) {
        if (ptr_fw[i] != nullptr){                  // nullptr check
            if (dev->get_file_exist(ptr_fw[i])){
                ei_printf("%s exist\n", ptr_fw[i]);
                valid_synpkg++;
            }
            else{
                ei_printf("%s not found!\n", ptr_fw[i]);
            }
        }
    }
    //dev->get_ext_flash()->deinit_fs();  // de init as NDP re init it

    if (valid_synpkg == 3){
        NDP.begin(fw1);
        NDP.load(fw2);
        NDP.load(fw3);
        NDP.getInfo();
        ndp_is_init = true;

#ifdef WITH_IMU
        NDP.configureInferenceThreshold(EI_CLASSIFIER_NN_INPUT_FRAME_SIZE);
#else
        NDP.turnOnMicrophone();
        NDP.getAudioChunkSize();    /* otherwise it is not initialized ! */
#endif
        NDP.interrupts();

        ei_syntiant_set_match();
        nicla::leds.setColor(off);
    }
    else{
        ei_printf("NDP not properly initialized\n");
        nicla::leds.setColor(red);
    }

    dev->get_ext_flash()->init_fs();    // NDP probably will de init and unmount

    /* init ar server */
    at = ei_at_init(dev);

    /* start inference */
    if (ndp_is_init == true) {
        /* sensor init */
        ei_inertial_init();
        ei_run_nn_normal();
    }

    ei_printf("Type AT+HELP to see a list of commands.\r\n");
    at->print_prompt();
}

/**
 * @brief
 *
 */
void ei_main(void)
{
    int match = -1;
    /* handle command comming from uart */
    char data = Serial.read();

    while (data != 0xFF) {
        at->handle(data);

        if (ei_run_impulse_is_active() && data == 'b') {
            ei_start_stop_run_impulse(false);
        }

        data = Serial.read();
    }

    if (ei_run_impulse_is_active() ==true) {
        if (got_match == true){
            got_match = false;
            nicla::leds.setColor(blue);
            ThisThread::sleep_for(100);
            nicla::leds.setColor(off);
        }

        if (got_event == true){
            got_event = false;
            nicla::leds.setColor(green);
            ThisThread::sleep_for(100);
            nicla::leds.setColor(off);
            match = NDP.poll();
        }

        if (match > 0) {
            ei_printf("match: %d\n", match);
            match = -1;
        }

#ifdef WITH_IMU
        // for now by default we stay in inference
        if (ei_run_impulse_is_active()) {
            ei_run_impulse();
        }

        if (motion_changed) {
            motion_changed = false;
            Wire.beginTransmission(96); // transmit to device #9
            Wire.write(current_motion); // sends x
            Wire.endTransmission();     // stop transmitting
            ei_printf("motion sent: %d\n", current_motion);
        }
#endif
    }

}


/**
 * @brief disable interrupt from NDP class
 *
 */
void ei_syntiant_clear_match(void)
{
    _on_match_enabled = false;
    //NDP.turnOffMicrophone();
    //NDP.noInterrupts();
}

/**
 * @brief enable interrupt from NDP clas
 *
 */
void ei_syntiant_set_match(void)
{
    _on_match_enabled = true;
    //NDP.turnOnMicrophone();
    //NDP.interrupts();
}

/**
 * @brief Callback when an Error is triggered
 * @note it never exit!
 */
static void error_event(void)
{
    nicla::leds.begin();
    while (1) {
        nicla::leds.setColor(red);
        ThisThread::sleep_for(250);
        nicla::leds.setColor(off);
        ThisThread::sleep_for(250);
    }
    nicla::leds.end();
}

/**
 * @brief Callback when a Match is triggered
 *
 * @param label The Match label
 */
static void match_event(char* label)
{
    if (_on_match_enabled == true){
        if (strlen(label) > 0) {
            got_match = true;
            ei_printf("Match: %s\n", label);

            if (strcmp(label, "NN0:z_openset") == 0 && current_motion != 0) {
                ei_printf("Motion changed from %d to %d\n", current_motion, 0);
                motion_changed = true;
                current_motion = 0;
            }

            if (strcmp(label, "NN0:circular") == 0 && current_motion != 1) {
                ei_printf("Motion changed from %d to %d\n", current_motion, 1);
                motion_changed = true;
                current_motion = 1;
            }

            if (strcmp(label, "NN0:snake") == 0 && current_motion != 2) {
                ei_printf("Motion changed from %d to %d\n", current_motion, 2);
                motion_changed = true;
                current_motion = 2;
            }

            if (strcmp(label, "NN0:wave") == 0 && current_motion != 3) {
                ei_printf("Motion changed from %d to %d\n", current_motion, 3);
                motion_changed = true;
                current_motion = 3;
            }
        }
    }
}

/**
 * @brief
 *
 */
static void irq_event(void)
{
    if (_on_match_enabled == true) {
        got_event = true;
    }
}