The code is blocked once the additional functions are added?

Hi guys,

I would like to check why my code is constantly blocked once the additional functions are added. I am doing some simple gesture recognition on my Arduino Nano 33 BLE Sense board using two main libraries Arduino_LSM9DS1 and Arduino_TensorFlowLite. The code works perfectly for collecting data and doing inference but once some other function (even stupid print function to print some stupid sentence) is added the code is blocked and cannot be run. Can you please provide any insights? I am not sure if problem is related to software or maybe my hardware is broken.

Here is the code:

#include <Arduino_LSM9DS1.h>
#include <TensorFlowLite.h>
#include <tensorflow/lite/micro/all_ops_resolver.h>
#include <tensorflow/lite/micro/tflite_bridge/micro_error_reporter.h>
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "model.h"
#include <string.h>

// Constants
const float accelerationThreshold = 2.5;
const int numSamples = 120;
int samplesRead = numSamples;

const char* GESTURES[] = {
   "mov1",
   "mov2",
   "mov3"
 };

#define NUM_GESTURES (sizeof(GESTURES) / sizeof(GESTURES[0]))

int highestIndex = 0;
float* resultArray;
const char* finalResult;
float highestValue;

// Global variables for TensorFlow Lite (Micro)
tflite::MicroErrorReporter tflErrorReporter;
tflite::AllOpsResolver tflOpsResolver;

const tflite::Model* tflModel = nullptr;
tflite::MicroInterpreter* tflInterpreter = nullptr;
TfLiteTensor* tflInputTensor = nullptr;
TfLiteTensor* tflOutputTensor = nullptr;

constexpr int tensorArenaSize = 8 * 8192;
byte tensorArena[tensorArenaSize];

// Global variables for IMU 
float aX, aY, aZ, gX, gY, gZ;

void printResult(){
  Serial.println("This is the function that should print the final result!");
}

void collectData(){
  while (samplesRead == numSamples) {
    if (IMU.accelerationAvailable()) {
      // read the acceleration data
      //Serial.println("Read acceleration: ");
      IMU.readAcceleration(aX, aY, aZ);

      // sum up the absolutes
      float aSum = fabs(aX) + fabs(aY) + fabs(aZ);

      // check if it's above the threshold
      if (aSum >= accelerationThreshold) {
        // reset the sample read count
        samplesRead = 0;
        break;
      }
    }
  }

  // check if the all the required samples have been read since
  // the last time the significant motion was detected
  while (samplesRead < numSamples) {

    //Serial.println("Samples read is lower than num of samples. ");
    //Serial.print("Check accelerometer data: ");
    // check if new acceleration AND gyroscope data is available
    if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable()) {
      // read the acceleration and gyroscope data
      IMU.readAcceleration(aX, aY, aZ);
      IMU.readGyroscope(gX, gY, gZ);

      float minAccel = -2.0;
      float maxAccel = 2.0;
      float minGyro = -1000.0;
      float maxGyro = 1000.0;

      tflInputTensor->data.f[samplesRead * 6 + 0] = (2.0 * (aX - minAccel) / (maxAccel - minAccel)) - 1.0;
      tflInputTensor->data.f[samplesRead * 6 + 1] = (2.0 * (aY - minAccel) / (maxAccel - minAccel)) - 1.0;
      tflInputTensor->data.f[samplesRead * 6 + 2] = (2.0 * (aZ - minAccel) / (maxAccel - minAccel)) - 1.0;
      tflInputTensor->data.f[samplesRead * 6 + 3] = (2.0 * (gX - minGyro) / (maxGyro - minGyro)) - 1.0;
      tflInputTensor->data.f[samplesRead * 6 + 4] = (2.0 * (gY - minGyro) / (maxGyro - minGyro)) - 1.0;
      tflInputTensor->data.f[samplesRead * 6 + 5] = (2.0 * (gZ - minGyro) / (maxGyro - minGyro)) - 1.0;

      samplesRead++;
    }
  }
}

const char* runInference(){

  TfLiteStatus invokeStatus = tflInterpreter->Invoke();
  for (int i = 0; i < NUM_GESTURES; i++) {
    resultArray[i] = tflOutputTensor->data.f[i];
  }
  highestValue = resultArray[0];
  for (int i = 1; i < NUM_GESTURES; i++){
    if (resultArray[i] > highestValue){
      highestValue = resultArray[i];
      highestIndex = i;
    } 
  }
  return GESTURES[highestIndex];
}

void setup(){

  Serial.begin(9600);
  while(!Serial);

  IMU.begin();
  tflModel = tflite::GetModel(model);

  if (tflModel->version() != TFLITE_SCHEMA_VERSION) {
    Serial.println("Model schema mismatch!");
    while (1);
  } 
  else {
    Serial.println("Model schema matches!");
  }

  tflInterpreter = new tflite::MicroInterpreter(tflModel, tflOpsResolver, tensorArena, tensorArenaSize);
  tflInterpreter->AllocateTensors();
  Serial.print("Arena Used Bytes of the deployed model: ");
  Serial.println(tflInterpreter->arena_used_bytes());

  tflInputTensor = tflInterpreter->input(0);
  tflOutputTensor = tflInterpreter->output(0);

  Serial.println("Setup done!");
}

void loop(){

  Serial.println("Welcome!");
  collectData();
  
  if (samplesRead == numSamples){
    finalResult = runInference();
  }
  Serial.println("Final result is: ");
  Serial.println(finalResult);
// when printResult function is commented it works without issues!
  printResult();
}
  

did you read this ?

No, I did not, but I read it now. I did not find too much information that can be useful in my case to be honest (or any solution they provided). Based on my insights, seems that any function that does not contain components from TensorFlowLite library somehow blocks the code execution. Is there maybe some bug in tensorflow lite library flow or something like that?

it seems one indication was

The problem always occurs when the tenserflow invoke() is called, but i'm not sure why. Also make sure that you are not blocking the loop function because otherwise the bluetooth will not work.

do you see the
Serial.println("Setup done!");

which is at the end of the setup?

Initially you block in collectData, may be having a bool that would let you know when the buffer is full would be better than a blocking call?

It is not blocking the collectData when there is no other function that does not include tensorflow variables. In my case, I have one additional function printResults that blocks the code every time it is not commented and there is an initiative to call it once inference part is done. That's weird no :confused:

This may be a comprehension of Serial usage problem, but I don't have time to investigate your code, so you'll have to. Please read this carefully.

  • You have initialized Serial at 9600 baud. That means at most, 960 characters can be sent per second.
  • You have verbose print commands in your code, which I would applaud for debugging purposes, but...
  • Serial has a limited output buffer(can be 64 or 128 chars, Arduino type dependent). If you print characters faster than that buffer is being emptied, when it fills, your code will be blocked while space is freed in the transmit buffer, painfully, one character at a time.
  • For example, if you try to send 20 characters, but there's only room for 5, you will be blocked for 15 character times before the Serial Print routine returns to your code. The next call will likely block immediately, for almost the entire character time of the message you send. And, it will only improve from there on if your code goes silent until the buffer empties.

So, to test if this is in fact occurring in your code, increase the Serial baud rate to 115200, set Serial Monitor to match, and observe your system performance. If the problem goes away, that was it. If it takes much longer to appear, it's related to the problem, but you'll still have to reduce verbosity.
If there's no change, look elsewhere.

actually upon reading your code more carefully, you are using resultArray as an array but you never allocated memory for it.

➜ you are writing in random places in memory, probably making a mess


I edited your code to have more of a state machine approach and not block the code. I typed that here so mind typos

#include <Arduino_LSM9DS1.h>
#include <TensorFlowLite.h>
#include <tensorflow/lite/micro/all_ops_resolver.h>
#include <tensorflow/lite/micro/tflite_bridge/micro_error_reporter.h>
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "model.h"
#include <string.h>

// Constants and variables
const float accelerationThreshold = 2.5;
const uint8_t numSamples = 120;
const char* gestureNames[] = {
  "mov1",
  "mov2",
  "mov3"
};

const uint8_t NUM_gestureNames = sizeof gestureNames  / sizeof * gestureNames;

uint8_t samplesRead = 0;


// Global variables for TensorFlow Lite (Micro)
tflite::MicroErrorReporter tflErrorReporter;
tflite::AllOpsResolver tflOpsResolver;

const tflite::Model* tflModel = nullptr;
tflite::MicroInterpreter* tflInterpreter = nullptr;
TfLiteTensor* tflInputTensor = nullptr;
TfLiteTensor* tflOutputTensor = nullptr;

constexpr size_t tensorArenaSize = 8 * 8192;
byte tensorArena[tensorArenaSize];

// Global variables for IMU
float aX, aY, aZ, gX, gY, gZ;

enum {WAITING_STAGE, COLLECTING_STAGE, INFERENCE_STAGE} stage = WAITING_STAGE;

bool accelerationIsAboveThreshold() {
  if (IMU.accelerationAvailable()) {
    IMU.readAcceleration(aX, aY, aZ);
    float aSum = fabs(aX) + fabs(aY) + fabs(aZ);
    return (aSum >= accelerationThreshold);
  }
  return false;
}

bool dataCollected() {
  if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable()) {
    IMU.readAcceleration(aX, aY, aZ);
    IMU.readGyroscope(gX, gY, gZ);

    float minAccel = -2.0;
    float maxAccel = 2.0;
    float minGyro = -1000.0;
    float maxGyro = 1000.0;

    tflInputTensor->data.f[samplesRead * 6 + 0] = (2.0 * (aX - minAccel) / (maxAccel - minAccel)) - 1.0;
    tflInputTensor->data.f[samplesRead * 6 + 1] = (2.0 * (aY - minAccel) / (maxAccel - minAccel)) - 1.0;
    tflInputTensor->data.f[samplesRead * 6 + 2] = (2.0 * (aZ - minAccel) / (maxAccel - minAccel)) - 1.0;
    tflInputTensor->data.f[samplesRead * 6 + 3] = (2.0 * (gX - minGyro) / (maxGyro - minGyro)) - 1.0;
    tflInputTensor->data.f[samplesRead * 6 + 4] = (2.0 * (gY - minGyro) / (maxGyro - minGyro)) - 1.0;
    tflInputTensor->data.f[samplesRead * 6 + 5] = (2.0 * (gZ - minGyro) / (maxGyro - minGyro)) - 1.0;

    samplesRead++;
  }
  return samplesRead >= numSamples;
}

const char * runInference() {
  TfLiteStatus invokeStatus = tflInterpreter->Invoke();
  float highestValue = tflOutputTensor->data.f[0];
  uint8_t highestIndex = 0;
  for (int i = 1; i < NUM_gestureNames; i++) {
    if (tflOutputTensor->data.f[i] > highestValue) {
      highestValue = tflOutputTensor->data.f[i];
      highestIndex = i;
    }
  }
  return gestureNames[highestIndex];
}

void setup() {

  Serial.begin(115200);  while (!Serial);

  IMU.begin();
  tflModel = tflite::GetModel(model);

  if (tflModel->version() != TFLITE_SCHEMA_VERSION) {
    Serial.println("Model schema mismatch!");
    while (true);
  }
  else {
    Serial.println("Model schema matches!");
  }

  tflInterpreter = new tflite::MicroInterpreter(tflModel, tflOpsResolver, tensorArena, tensorArenaSize);
  tflInterpreter->AllocateTensors();
  Serial.print("Arena Used Bytes of the deployed model: ");
  Serial.println(tflInterpreter->arena_used_bytes());

  tflInputTensor = tflInterpreter->input(0);
  tflOutputTensor = tflInterpreter->output(0);

  Serial.println("Ready.");
}

void loop() {
  switch (stage) {
    case WAITING_STAGE:
      if (accelerationIsAboveThreshold()) {
        samplesRead = 0;
        stage = COLLECTING_STAGE;
      }
      break;

    case COLLECTING_STAGE:
      if (dataCollected()) {
        stage = INFERENCE_STAGE;
      }
      break;

    case INFERENCE_STAGE:
      Serial.print("Final result is: "); Serial.println(runInference());
      stage = WAITING_STAGE;
      break;
  }
}

I've not verified how those pointers

const tflite::Model* tflModel = nullptr;
tflite::MicroInterpreter* tflInterpreter = nullptr;
TfLiteTensor* tflInputTensor = nullptr;
TfLiteTensor* tflOutputTensor = nullptr;

get proper memory , I assume you do that right

Thanks a lot for editing the code and giving me some nice advice on how to edit it properly. I solved the issue by adding namespace:

namespace{

  tflite::MicroErrorReporter tflErrorReporter;
  tflite::AllOpsResolver tflOpsResolver;

  const tflite::Model* tflModel = nullptr;
  tflite::MicroInterpreter* tflInterpreter = nullptr;
  TfLiteTensor* tflInputTensor = nullptr;
  TfLiteTensor* tflOutputTensor = nullptr;

  constexpr int tensorArenaSize = 8 * 8192;
  byte tensorArena[tensorArenaSize];
}

After I added this part, the code behaves as expected, executing functions by an order does not matter if they contain or not tensorflow lite components.

OK - good !

if you look at mu code structure, it's not being blocked during acquisition or initial wait.

if you want to monitor something else during that time, like a button, this type of code structure (finite states machine) makes it possible

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.