Serial.begin() seems to block code, even uncommented - Nano 33 BLE

Hi all,
Fisrt allow me to wish you a good year all, full of arduino project and programmation learning!

I am programming only for fun and learning, with a hint of scientific experiment if possible. My project is a bluetooth-controlled motor car toy equipped with the 33BLE IMU (LSM9DS1) and an ultrasonic sensor I mounted on a servo on the nose of the car.

Please be aware I am not a professionnal programmer and my main knowledge was aquired here (Klauz, you're the king!) and with arduino reference.

So, to avoid hardware interaction (wich gave me sad issues in the past) on the board, I coded my sketch step by step and tested it a lot. I roughly have two different sketches:
the first include BLE library, servo and ultrasonic functions. works perfectly (data goes up from nano to my phone with BLE and I can pilot the servo). Debugging was made with serial (to my pc).
the second is "dedicated" to I2C protocole with LSM9DS1 and allowed me to code the data exchange with LSM9DS1. It NOT includes BLE library but exports data to serial.

Finally, as the 2 skecthes gave me the behavior I want, I merged the 2 sketches. I also put led blinking at different setup steps in order to remove any Serial.print command (the car will ultimately export data only through BLE).

I uncommented Serial.begin() command at the top of my Setup().
And the board was stucked (led blinking gave me that info without the need of serial.print) at the beginning of the Setup().
I un-uncomment Serial.begin() and everything works fine.

Finally, the "simpliest" way to have the sketch working (without the apparently blocking Serial.begin()) was to (shamefully) add a delay(400) at the beginning of the Serup().

I have read here that a while (!Serial) after the Serial.begin() is mandatory on nano 33 BLE. I also have read (and tested) that removing this blocking function allows a skecth to run on its own.

So why removing this function on my sketch didn't solve the issue? Why a delay lets the sketch run flawless?

Please note I wont join my code as I presume I missed something important about the nano itself, and the code will be very long to post in a whole but only a little part seems to be involved into trouble

Here is the hardware stuff involved in my coding (except for BLE, I used my own made library):

  • BLE library (I really fear for hardware interaction)
  • RTC2
  • Timer3
  • Timer4
  • TWIM (I2C master with easy DMA)

Add-on:

I erased any Serial command in my sketch and it didn't solve the issue.
The only way I found to run my sketch is to add delay(400) at the beginning of Setup().
I presumed a weird stuff must be managed by the BLE library (the only stuff I consider hidden in my code).

without seeing your code it's hard to comment

Help us help you.

Here is the code (I didn't want to, it is such a mess!)
Sorry for the rawdata and horloge structures I didn't transfer into a library and make the code longer.

this code give me what I want (BLE enabled, I can communicate with the board, even battery powered and disconnected from USB), but can operate only with the included "delay" function.

I2C and signal processing functions are not given here.

//  // DEFINE valeurs pour buffer I2C - module LSM9DS1

#define READ_BUFFER_SIZE (18)  // taille max 12: gyro + accel + 6: magneto
#define WRITE_BUFFER_SIZE (2)  // taille max 2: envoie registre + valeur à écrire
#define NB_RAWDATA (9)         // nombre d'éléments rawdata (3 pour gyro/Accel/magneto)

uint8_t rx_buffer_I2C[READ_BUFFER_SIZE];
uint8_t tx_buffer_I2C[WRITE_BUFFER_SIZE];

//  // DEFINE valeurs GPIOTE

#define PIN_PWM_LEFT (P1_12)    //  Pin D3
#define PIN_PWM_RIGHT (P1_11)   //  Pin D2
#define PIN_HIGH_LEFT (P1_15)   //  Pin D4
#define PIN_LOW_LEFT (P1_13)    //  Pin D5
#define PIN_HIGH_RIGHT (P1_14)  // Pin D6
#define PIN_LOW_RIGHT (P0_23)   // Pin D7
#define COUNTERTOP_MAX 5250     // durée max de la phase pour roues - aligné sur calcul _y
#define phase_servo (20000)     // durée max de la phase pour servomoteur ultrason
#define SERVO_max_left (600)    //
#define SERVO_max_right (2600)  //
#define DUTY1 0                 // PWM pour channel LEFT
#define DUTY2 0                 // PWM pour channel RIGHT

uint16_t buf[] = { 1 << 15 | DUTY1, 1 << 15 | DUTY2 };  // counter pour moteurs roues

//  // DEFINE valeurs calculs depuis module LSM9DS1

int32_t CAP = 0;  // direction relative du nez de la voiture (en degrés: 0 à 359)
int32_t magnetic_field_intensity = 0;
int32_t accel_field_intensity = 0;

int32_t time_new_mesure;   // chrono relevé I2C gyro+accel pour mesure du cap
int32_t time_last_mesure;  // chrono relevé I2C gyro+accel pour mesure du cap


//  // DEFINE valeurs calculs ultrasons

int DISTANCE_US;

//  // DEFINE valeurs calculs servomoteur

int impulse_servo = 1650;

// // DEFINE classes de données

typedef class rawdata {  // stocke une valeur du module LSM9DS1: Axe X pour acceleromètre...
private:

  int16_t scale = 0;         // ex: 4000 pour +/-4 millig pleine échelle (-32767 à +32767)
  int32_t old_valeur = 0;    // dernière valeur transférée depuis rx_buffer_I2C
  uint8_t *registre = NULL;  // adresse de départ dans rx_buffer_I2C
  int16_t offset_usine = 0;  // calibration "usine"
  int16_t treshold_low = 0;
  int16_t treshold_high = 0;
  int16_t max_valeur = 0;  // pour stat en fonctionnement
  int16_t min_valeur = 0;  // pour stat en fonctionnement
  uint16_t DEAD_ZONE = 0;

public:

  int32_t offset_position = 0;  // offset de position

  int16_t VALEUR_RAW(void) {
    // renvoi old_valeur + offset_usine
    return (old_valeur + offset_usine);
  }

  int16_t VALEUR(void) {
    // renvoi old_valeur corrigée
    return (VALEUR_RAW() - offset_position);
  }

  int16_t VALEUR_SCALED(void) {
    // renvoi la valeur old_valeur corrigée et mise à l'échelle
    float a = VALEUR() * scale;
    a = a / 32768;
    return (a);
  }

  void FIND_MAX_MIN(int16_t *_max_min) {
    // met à jour max ou min en fonction de la valeur proposée
    if (max_valeur == 0) { max_valeur = *_max_min; }
    if (min_valeur == 0) { min_valeur = *_max_min; }

    if (*_max_min > max_valeur) { max_valeur = *_max_min; }
    if (*_max_min < min_valeur) { min_valeur = *_max_min; }
  }

  byte MET_A_JOUR_SELF() {
    // transfère le registre buffer vers old_valeur et filtre min/max
    int16_t _valeur = RETURN_INT();
    FIND_MAX_MIN(&_valeur);
    old_valeur = _valeur;
    return (1);
  }

  byte MET_A_JOUR(int16_t _valeur) {
    // transfère la valeur indiquée vers old_valeur et filtre min/max
    FIND_MAX_MIN(&_valeur);
    old_valeur = _valeur;
    return (1);
  }

  int16_t RETURN_INT(void) {
    // renvoi la valeur registre
    return (int16_t(*registre | *(registre + 1) << 8));
  }

  int16_t *RETURN_MAX() {
    // renvoi la valeur max
    return (&max_valeur);
  }

  int16_t *RETURN_MIN() {
    // renvoi la valeur min
    return (&min_valeur);
  }

  byte RETURN_NOT_IN_TRESHOLD(int16_t _valeur) {
    // renvoi 1 si _valeur est différente de old_valeur +/- threshold
    if ((_valeur > old_valeur + treshold_high) | (_valeur < old_valeur - treshold_low)) {
      return 1;
    } else {
      return 0;
    }
  }

  byte RETURN_NOT_DEAD_ZONE(void) {
    // renvoi 1 si la valeur est différente de 0 (hors dead zone)
    if (VALEUR() > DEAD_ZONE | VALEUR() < -DEAD_ZONE) {
      return 1;
    } else {
      return 0;
    }
  }

  void SET_DEAD_ZONE(uint16_t _dead_zone) {
    // définit la valeur dead zone
    DEAD_ZONE = _dead_zone;
  }

  void SET_OFFSET_USINE(int16_t _offset_usine) {
    // définit l'offset spécial du capteur (non position)
    offset_usine = _offset_usine;
  }

  void SET_REGISTER(uint8_t &_registre) {
    // définit l'adresse registre dans rx_buffer
    registre = &_registre;
  }

  void SET_SCALE(int16_t _scale) {
    // définit l'échelle
    scale = _scale;
  }

  byte SET_TRESHOLD_HIGH(int16_t _valeur) {
    // met à jour trshld high ou low en fonction de la valeur proposée
    treshold_high = _valeur;
  }

  byte SET_TRESHOLD_LOW(int16_t _valeur) {
    // met à jour trshld high ou low en fonction de la valeur proposée
    treshold_low = _valeur;
  }

  void MONITOR(void) {
    // affiche les valeurs (utilisé par calibration)

    Serial.print("last raw: ");
    Serial.print(old_valeur);
    Serial.print("  offset: ");
    Serial.print(offset_position);
    Serial.print("  dead zone: ");
    Serial.print(DEAD_ZONE);
    Serial.print("  THLD+/-: ");
    Serial.print(treshold_high);
    Serial.print(" / ");
    Serial.print(treshold_low);
    Serial.print("  max/min: ");
    Serial.print(max_valeur);
    Serial.print(" / ");
    Serial.println(min_valeur);
  }
};

typedef class horloge {  // génére une horloge qui gère les timers
private:

  uint16_t horloge_overflowed = 0;  // overflow totaux de RTC2
  uint16_t old_heure = 0;           // dernier relevé dans WHAT_TIME_IT_IS
  // byte NB_timer = 0;  // additionne le nombre de timer ouverts en simultané

  void SETUP_RTC() {
    const int PRESCALER_RTC2 = 31;
    NRF_RTC2->TASKS_STOP = 1;              // arrete le compteur pour set up
    NRF_RTC2->TASKS_CLEAR = 1;             // met à 0 le compteur
    NRF_RTC2->PRESCALER = PRESCALER_RTC2;  // frequence à 1000 Hz
    NRF_RTC2->TASKS_START = 1;             // démarre le compteur
  }

public:

  struct to_time {
    // timer indépendant
    uint16_t delai_total;       // délai à écouler avant déclenchement. 0 = timer off
    uint32_t dernier_compteur;  // dernière heure relevée

    to_time() {
      // constructeur
      delai_total = 0;
    }
  };

  uint32_t WHAT_TIME_IS_IT(void) {
    // renvoi le compteur de RTC additionné du nb overflow
    uint16_t new_heure = NRF_RTC2->COUNTER;
    if (new_heure < old_heure) { horloge_overflowed++; }  // détecte overflow
    old_heure = new_heure;
    return (new_heure | horloge_overflowed << 16);  // 32bit: [heure actuelle] | [overflowed]
  }

  void begin(void) {
    // démarre RTC. à lancer à la 1ère instanciation d'horloge
    SETUP_RTC();
  }

  void START_TIMER(struct to_time *_timer, uint16_t _delai_total) {
    // démarre le timer (pour TIME_TO)
    _timer->dernier_compteur = WHAT_TIME_IS_IT();
    if (_delai_total > 1) { _timer->delai_total = _delai_total - 1; }
  }

  void STOP_TIMER(struct to_time *_timer) {
    // arrête le timer
    _timer->delai_total = 0;
  }

  byte TIME_TO(struct to_time *_timer, byte _update = 0) {
    //renvoi 1 si délai écoulé.
    if (_timer->delai_total > 0) {
      uint32_t heure_actuelle = WHAT_TIME_IS_IT();
      if ((heure_actuelle - _timer->dernier_compteur) > _timer->delai_total) {
        // update valeur si _update = 0 (laisse une autre methode reset le compteur)
        if (!_update) { _timer->dernier_compteur = heure_actuelle; }
        return 1;
      } else {
        return 0;
      }
    }
    return 0;
  }
};

// instanciation des familles de valeurs
rawdata
  GYRO_X,
  GYRO_Y, GYRO_Z,
  ACCEL_X, ACCEL_Y, ACCEL_Z,
  MAGNETO_X, MAGNETO_Y, MAGNETO_Z,
  DIST_US, CAP_ACTUEL;
rawdata *Pool_data[NB_RAWDATA] = { &GYRO_X, &GYRO_Y, &GYRO_Z,
                                   &ACCEL_X, &ACCEL_Y, &ACCEL_Z,
                                   &MAGNETO_X, &MAGNETO_Y, &MAGNETO_Z };

horloge Horloge_1;
horloge::to_time
  Timer_Mesure_I2C,
  Timer_Report, Timer_Wait,
  Timer_Manage_Led, Timer_Servo, Timer_US;
//horloge::to_time *Pool_timer[3] = { &Timer_Mesure_I2C, &Timer_Report, &Timer_Wait };

// DEBUG bytes
const byte DEBUG_BYTE_I2C = 0;
const byte DEBUG_BYTE_SETUP = 0;
const byte DEBUG_BYTE_CALIB = 1;
const byte DEBUG_BYTE_BLE = 1;

//  // DEFINE valeurs pour noyau BLE

#include <ArduinoBLE.h>
BLEService FourWheels("1819");  // Bluetooth® Low Energy Vehicle Toy
// Bluetooth® Low Energy LED Switch Characteristic - custom 128-bit UUID, read and writable by central
BLEIntCharacteristic Servoimpulse("0601", BLERead | BLEWrite); // Frequency Hertz
BLEIntCharacteristic USmesure("0602", BLERead | BLENotify); // Lenght meter
BLEIntCharacteristic CAPmesure("0603", BLERead | BLENotify); // Apparent Wind Direction
BLEStringCharacteristic stringcharacteristic("1904", BLERead | BLEWrite, 31);

BLEDescriptor Phase("2901","Phase Servo micros");
BLEDescriptor Mesure("2901","Distance US mm");
BLEDescriptor Angle("2901","CAP en degres");
BLEDescriptor Texte("2901","text log");

uint8_t _clignote = 0;  // séquence led orange quand central deconnected

void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);
  delay(400);
  Serial.println("Hello world!");

  ////  // SETUP_serial.begin: allume led verte et allume led orange
  digitalWrite(LED_BUILTIN, HIGH);
  digitalWrite(LED_PWR, HIGH);
  ////  // SETUP_début: éteint led verte et allume led orange
  digitalWrite(LED_BUILTIN, HIGH);
  digitalWrite(LED_PWR, LOW);

  // // DEBUT SETUP "module LSM9DS1"
  SETUP_LIST();         //paramètre les raw data accel, gyro et magneto
  Horloge_1.begin();    // contient setup_RTC
  SETUP_MODULES_I2C();  // Setup I2C + gyro/accel + magneto
  CALIB_ACCEL_GYRO();

  // // DEBUT SETUP "ultrason et servomoteur"
  SETUP_GPIOTE_PPI();

  // // DEBUT SETUP bluetooth
  SETUP_BLE();

  ////  // SETUP_fin: éteint led orange et allume led verte

  EXIT_BLE();
}

void loop() {
  // put your main code here, to run repeatedly:

  BLEDevice central = BLE.central();

  if (central) {  // if a central is connected to peripheral:

    ENTER_BLE();

    while (central.connected()) {  // while the central is still connected to peripheral:

      ROUTINE_INSIDE_BLE();

      ROUTINE_ASK_BLE();

    }  // FIN de while (central.connected)

    EXIT_BLE();

  }  // FIN de if (central)

  ROUTINE_OUTSIDE_BLE();
}

Thanks for your time and help.

still missing lots of code..

it is, but not related to my issue (as far I can imagine).

let's be clear: I dont feel confortable with drowning you under code and waste your time. Maybe I was expecting something like "hey it happended to me too, just do that, it worked for me"

what you do in there is unknown

  // // DEBUT SETUP "module LSM9DS1"
  SETUP_LIST();         //paramètre les raw data accel, gyro et magneto
  Horloge_1.begin();    // contient setup_RTC
  SETUP_MODULES_I2C();  // Setup I2C + gyro/accel + magneto
  CALIB_ACCEL_GYRO();

  // // DEBUT SETUP "ultrason et servomoteur"
  SETUP_GPIOTE_PPI();

  // // DEBUT SETUP bluetooth
  SETUP_BLE();

  ////  // SETUP_fin: éteint led orange et allume led verte

  EXIT_BLE();

may be there is blocking code if something goes wrong... we don't know

what you could try is wait for the Serial connection to be established if you need it

change
Serial.begin(9600);

into

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

and get rid of the delay()
(and modify the Serial monitor baud rate)

Thanks for your answer.
I keep in mind your idea of blocking code. I will slice my setup with led blinking in order to identify the blocking part without the need of Serial.begin. I suspect my instanciation of horloge structure (wich plays with RTC2) to interferate with BLE library, as the final sketch is their first meet.

I know the library makers do such a great job, but I spent a lot of time to work on my own in order to avoid hardaware interference (insert baby cringe here) and there I get trapped on myself (insert loud baby cringe here).

And change that to;

Serial.println("Hello world!");
Serial.flush();

To see is Serial. is actually working .................

I had trouble with one of the new Nanos too - Serial wouldn't work without a delay. Some Arduinos need a loop to check that the Serial object has been created (e.g. Leonardo) but I don't recall whether that was my fix or just leaving the delay in place.

thanks @wildbill and @srnet , I will try the flush command.
But I can see my sketch is working as long as I keep delay in the code, whether or not Serial.begin() is in.

I wonder wich command request a pause to finish its tasks.

Finally: even if the intellectual issue remains, my code is working in a satisfying way.

When an Arduino has native USB support, meaning that the USB interface is part of the processor itself instead of a separate chip, then it takes a small amount of time after Serial.begin() for the USB connection to be established. The usual code to wait for this is the "while (!Serial);", but this will block the code completely if the Arduino is not connected to the computer with a USB cable. The delay() will generally work when you make it long enough, and still allow the code to operate without a USB connection. Commenting out Serial.begin() is not a good idea if you do not also remove all the Serial references in the code, because using Serial without first initializing it with begin() can cause unexpected behavior.

If does no good to use Serial.flush() if there is no "while (!Serial);" or delay(), anything sent to Serial after Serial.begin() but before the USB connection is established will never be seen on the computer.

hmmm. That is a good recall.

I tested my sketch after removing ANY command related to Serial (like the final version is supposed to be) but results are the same: stucked in setup().

So the delay solution is working, even if intellectually disappointing.

Starting at the bottom of setup(), put the delay() before the last function call, then test the code. If it does not work, move the delay() up one function call and retest. Keep doing this until you find which function call in setup() needs the delay in order to operate properly.

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