Go Down

Topic: Using BLE in Standalone project for Arduino Nano 33 BLE Sense (Read 178 times) previous topic - next topic

stygianblade


Hi all, I am very new to Arduino and BLE, so any help would be appreciated! 
My goal is to use the magic_wand example in the Arduino_TensorflowLite (v1.15.0-ALPHA) Library to connect to various smart home devices. I intend for the final design to be powered via a rechargeable Power Bank battery that is connected only to the Arduino Nano 33 BLE Sense board via the micro USB port. 

Issue: My code only runs when the board is connected via USB to my computer and the Serial Monitor is open. When connected to a power bank battery or the computer with the Serial Monitor closed, the board is powered but the code will not run.

Background:
I purchased an Arduino Nano 33 BLE Sense for this project. Once I got the magic_wand example to work, I used the BatteryMonitor Peripheral example in the ArduinoBLE Library to add some BLE functionality to my magic_wand. I downloaded LightBlue to my iPhone and am able to see the Peripheral show up and can see one of the values changing if I exit and reopen the Arduino Peripheral (although, it still has Battery Service and Battery Level instead of my custom service that I created, but that seems like another issue).

Looking through many forums, the only hint I have seen is to remove while(!Serial); from my code. I understand that that statement is saying continue looping until the Serial monitor is opened, but removing this line does not solve my problem-the program still doesn't run. Is there a way to have my Arduino Nano 33 BLE Sense only be connected to a battery pack and send out BLE signals, or do I need to have it connected to my computer so that I can have the Serial Monitor? Thank you in advance! 

Here is my code: 

Code: [Select]
#include <TensorFlowLite.h>
#include "main_functions.h"
#include "accelerometer_handler.h"
#include "gesture_predictor.h"
#include "magic_wand_model_data.h"
#include "output_handler.h"
#include "tensorflow/lite/experimental/micro/kernels/micro_ops.h"
#include "tensorflow/lite/experimental/micro/micro_error_reporter.h"
#include "tensorflow/lite/experimental/micro/micro_interpreter.h"
#include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
// I added this
#include <ArduinoBLE.h>

// Globals, used for compatibility with Arduino-style sketches.
namespace {
tflite::ErrorReporter* error_reporter = nullptr;
const tflite::Model* model = nullptr;
tflite::MicroInterpreter* interpreter = nullptr;
TfLiteTensor* model_input = nullptr;
int input_length;

// Create an area of memory to use for input, output, and intermediate arrays.
// The size of this will depend on the model you're using, and may need to be
// determined by experimentation.
constexpr int kTensorArenaSize = 60 * 1024;
uint8_t tensor_arena[kTensorArenaSize];

// Whether we should clear the buffer next time we fetch data
bool should_clear_buffer = false;
}  // namespace
// I added this
// BLE Magic Wand Service
BLEService magicWandService("180F");
// BLE Magic Wand Characteristic
BLEUnsignedCharCharacteristic magicWandGestureChar("2A19",  // standard 16-bit characteristic UUID
    BLERead | BLENotify); // remote clients will be able to get notifications if this characteristic changes

// The name of this function is important for Arduino compatibility.
void setup() {
  // Set up logging. Google style is to avoid globals or statics because of
  // lifetime uncertainty, but since this has a trivial destructor it's okay.
  static tflite::MicroErrorReporter micro_error_reporter;  // NOLINT
  error_reporter = &micro_error_reporter;

  // Map the model into a usable data structure. This doesn't involve any
  // copying or parsing, it's a very lightweight operation.
  model = tflite::GetModel(g_magic_wand_model_data);
  if (model->version() != TFLITE_SCHEMA_VERSION) {
    error_reporter->Report(
      "Model provided is schema version % d not equal "
      "to supported version % d.",
      model->version(), TFLITE_SCHEMA_VERSION);
    return;
  }

  // Pull in only the operation implementations we need.
  // This relies on a complete list of all the ops needed by this graph.
  // An easier approach is to just use the AllOpsResolver, but this will
  // incur some penalty in code space for op implementations that are not
  // needed by this graph.
  static tflite::MicroMutableOpResolver micro_mutable_op_resolver;  // NOLINT
  micro_mutable_op_resolver.AddBuiltin(
    tflite::BuiltinOperator_DEPTHWISE_CONV_2D,
    tflite::ops::micro::Register_DEPTHWISE_CONV_2D());
  micro_mutable_op_resolver.AddBuiltin(
    tflite::BuiltinOperator_MAX_POOL_2D,
    tflite::ops::micro::Register_MAX_POOL_2D());
  micro_mutable_op_resolver.AddBuiltin(tflite::BuiltinOperator_CONV_2D,
                                       tflite::ops::micro::Register_CONV_2D());
  micro_mutable_op_resolver.AddBuiltin(
    tflite::BuiltinOperator_FULLY_CONNECTED,
    tflite::ops::micro::Register_FULLY_CONNECTED());
  micro_mutable_op_resolver.AddBuiltin(tflite::BuiltinOperator_SOFTMAX,
                                       tflite::ops::micro::Register_SOFTMAX());

  // Build an interpreter to run the model with
  static tflite::MicroInterpreter static_interpreter(
    model, micro_mutable_op_resolver, tensor_arena, kTensorArenaSize,
    error_reporter);
  interpreter = &static_interpreter;

  // Allocate memory from the tensor_arena for the model's tensors
  interpreter->AllocateTensors();

  // Obtain pointer to the model's input tensor
  model_input = interpreter->input(0);
  if ((model_input->dims->size != 4) || (model_input->dims->data[0] != 1) ||
      (model_input->dims->data[1] != 128) ||
      (model_input->dims->data[2] != kChannelNumber) ||
      (model_input->type != kTfLiteFloat32)) {
    error_reporter->Report("Bad input tensor parameters in model");
    return;
  }

  input_length = model_input->bytes / sizeof(float);

  TfLiteStatus setup_status = SetupAccelerometer(error_reporter);
  if (setup_status != kTfLiteOk) {
    error_reporter->Report("Set up failed\n");
  }
// I added this
  // Setup for BLE
  Serial.begin(9600);    // initialize serial communication
//  while (!Serial);
  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");
    while (1);
  }
  /* Set a local name for the BLE device
     This name will appear in advertising packets
     and can be used by remote devices to identify this BLE device
     The name can be changed but maybe be truncated based on space left in advertisement packet
  */
  BLE.setLocalName("MagicWand");
  BLE.setAdvertisedService(magicWandService); // add the service UUID
  magicWandService.addCharacteristic(magicWandGestureChar); // add the magic wand gesture characteristic
  BLE.addService(magicWandService); // Add the magic wand service
  /* Start advertising BLE.  It will start continuously transmitting BLE
     advertising packets and will be visible to remote BLE central devices
     until it receives a new connection */
  // start advertising
  BLE.advertise();
  Serial.println("Bluetooth device active, waiting for connections...");
}

void loop() {
  // Attempt to read new data from the accelerometer
  bool got_data = ReadAccelerometer(error_reporter, model_input->data.f,
                                    input_length, should_clear_buffer);
  // Don't try to clear the buffer again
  should_clear_buffer = false;
  // If there was no new data, wait until next time
  if (!got_data) return;
  // Run inference, and report any error
  TfLiteStatus invoke_status = interpreter->Invoke();
  if (invoke_status != kTfLiteOk) {
    error_reporter->Report("Invoke failed on index : % d\n", begin_index);
    return;
  }
  // Analyze the results to obtain a prediction
  int gesture_index = PredictGesture(interpreter->output(0)->data.f);
  // Clear the buffer next time we read data
  should_clear_buffer = gesture_index < 3;
// I added this
  //  wait for a BLE central
  BLEDevice central = BLE.central();
  // if a central is connected to the peripheral:
  if (central && should_clear_buffer) {
    Serial.print("Connected to central : ");
    // print the central's BT address:
    Serial.println(central.address());
    Serial.print("Magic Wand Gesture is now : "); // print it
    Serial.println(gesture_index);
    magicWandGestureChar.writeValue(gesture_index);  // and update the magic wand gesture characteristic
    if (!central.connected()) {
      Serial.print("Disconnected from central : ");
      Serial.println(central.address());
    }
  }
  // Produce an output
  HandleOutput(error_reporter, gesture_index);
}

Please let me know if you need any of the header files. I have not touched any files except the one shown here for magic_wand.ino. 

Klaus_K

(although, it still has Battery Service and Battery Level instead of my custom service that I created, but that seems like another issue).
The reason you are still seeing the Battery Service is that you use the Battery Service UUID (0x180F). You need to create your own UUIDs. They must not be 16-bit UUIDs because these are reserved for the Bluetooth SIG defined services and characteristics. You need to create random 128-bit UUIDs for your characteristic and service. You can use a UUID generator online or write yourself a script.

https://www.uuidgenerator.net/

Is there a way to have my Arduino Nano 33 BLE Sense only be connected to a battery pack and send out BLE signals, or do I need to have it connected to my computer so that I can have the Serial Monitor?
Yes, when you remove the while(!Serial) your code should run. Unless there is a similar construct in the tensorflow code.

stygianblade

Thank you @Klaus_K! I went looking through the Tensorflow code and found
Code: [Select]
while (!Serial) {
  }

in arduino_accelerometer_handler.cpp. I commented out both while(!Serial) loops. The program now runs as soon as it is connected to power without the need for the Serial Monitor to be open.




I also created my own UUIDs and passed them to BLEUnsignedCharCharacteristic and BLEService, which fixed that issue I was seeing.  

Go Up