Nano 33 BLE Sense serial workaround

I'm testing out a Nano 33 BLE Sense for possible use in a computer organization and assembly language class that I teach (transitioning from an NXP LPC1768 on MBED), and after looking through the many posts about issues with the Serial monitor, I thought I would post what I have come up with. I'm running Arduino 1.8.15 on MacOS 11.4 (Big Sur) and testing on both Intel and M1 machines. As has been noted, the native USB in the Nano Sense causes some inconsistent behavior for communicating with the serial monitor, and various posts have already mentioned how upload can cause the port to change on Windows, with a variety of workarounds. This is for MacOS, and may also be relevant to Linux.

If I run the following sketch, and open the serial monitor while compiling:

void setup() {
Serial.begin(9600);
Serial.println("This is a test.");
}

void loop() { }

It frequently, but not always, will print the message. But pressing the reset button on the board usually doesn't cause the message to be redisplayed (it has done it a few times, but never after a second reset). However, with the following adjustment:

void setup() {
delay(1000);
Serial.begin(9600);
delay(1000);
Serial.println("This is a test.");
}

void loop() { }

It consistently shows the message after uploading, and on each press of the reset button (if it is held for a second or so). I was particularly interested in being able to get consistent behavior from setup() because several of my assembly assignments involve C++ calling an assembly routine (with parameter passing), displaying some memory before and after the call, to show that it has worked correctly, and I don't want that to run in the loop() section.

But I also ran some experiments with loop(). The following sketch runs reliably on the Intel machine, displaying both output lines repeatedly after upload and pressing reset:

void setup() {
Serial.begin(9600);
}

void loop() {
Serial.println("This is a first test.");
Serial.println("This is a second test.");
delay(1000);
}

But on the M1 machine, I had a situation where it would skip the first line on the first iteration. Inserting a delay after the call to begin fixed that, but also unplugging the USB cable and reconnecting seemed to solve it.

I also tried the approach of following the code in the loop() section with a while(1); to get the singe-execution effect. But that caused no output on upload or after resets, which I'm guessing is because the code that repeatedly calls loop is doing some additional management or buffer flushing that gets prevented when loop doesn't return.

At this point, I'm satisfied that I have a fix I can share with students. I also have a sketch successfully calling a routine in a .S file (after sorting out differences between the Arm and gcc assembler directives, to be able to access the full M4 instruction set). Next on my list is working out the locations and values for the GPIO ports, so I can show the students how to write a low level driver for the RGB LED. I have the Nordic documentation and the schematics for the BLE Sense, so hopefully it won't be too much work to get that going.

Is there a problem with this approach?

void setup() {
Serial.begin(9600);
while (!Serial); //wait for serial monitor to connect
Serial.println("This is a test.");
}

void loop() { }

In my testing, it was less reliable. About 30% of the time, it did not respond to the reset button press. And as had been noted in some of the related posts, if there is an issue with the serial port, it doesn't get past it to the loop section.

ARM processors have been designed from the beginning to be used with compilers and not assembler. Cortex-M processors can be programmed without any assembly code. Directly after reset the processor has a stack pointer and the interrupt controller saves the core registers according the software calling standard onto the stack.
While it may be useful for developers to know the assembly language for low-level debugging, I would recommend to your students to avoid assembly programming as much as possible. Assembly is not portable even from ARMCC to GCC and most compilers will on average generate smaller and faster code then programmers.
There are a few special instructions like WFI and NOP that are not needed by the compiler and there are compiler intrinsics available to use them.

If you want to teach your student how to make use of a Cortex-M4 with high performance code have a look at the CMSIS DSP library. It is open source by ARM.

https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/DSP

You need to close the Serial Monitor window. Windows will create a new instance of a COM port every time you reset the Arduino and you need to open a new Serial Monitor window to connect to it. The new COM3 does is not the same as COM3 a minute ago.
That is an easy price to pay for not having to know anything about the device you are talking to. Windows provides a COM port and hides everything from the application programmer.

That is the whole point of the while. It waits until you connect with the Serial Monitor or Plotter.

The solution jremington posted is 100% reliable.

This is a class about computer organization and architecture. The whole point is to understand what happens underneath all of the abstraction layers. We start with semiconductors and work our way up through a data path design in a logic simulator, then transition to assembly language to understand the relationship of the memory hierarchy to registers, the foundations of operating systems, interrupt handling, and compiler code generation, then they code some device drivers, and once they understand how the libraries work, then we build on top of them to do interfacing. Computer science majors benefit greatly from knowing how the hardware actually works.

As I noted, this is for MacOS, and I have seen the discussions of the issues with how Windows handles the COM ports. But I need to ensure that the board can be made to work reliably on all of the operating systems my students use.

And in my experimentation, the version of the sketch that was posted did not print the message with about 30% of the reset presses. I've done some more experimenting, and it appears that if I insert a delay before the call to begin, then it becomes reliable:

void setup() {
delay(1000);
Serial.begin(9600);
while(!Serial)
Serial.println("This is a test.");
}

void loop() {
}

So it appears that there is a timing issue with connecting to the serial port on MacOS. It should also be noted that MacOS doesn't need to have a new monitor window opened. Unlike Windows, it preserves the port assignment across resets of the board.

It is hard to image how a call to delay() before a call to Serial.begin() could be a fix, unless the compiler rearranges the resultant machine code in some unexpected fashion.

If you manage to figure out the technical explanation for how that works, and your students actually buy it, please post it here.

On the other hand, I've always considered Macs to be quirky beasts, and this does not reassure me.

Sounds good to me. You students are lucky you chose a modern but still simple processor to learn the basics. Many students in the forum use Arduino Uno which seems a bit outdated as a base line for someone who will start a career in a few years from now.

It's a good question, since it only helps when printing from the setup section, and doesn't seem to be needed when the print calls are in the loop section. If it had something to do with the M4 being too fast and beating the Mac to executing begin before the virtual serial port is re-established after the reset, then I would expect it to affect both cases.

MacOS is just another flavor of Unix. Over the years of using the LPC1768 in my classes, the students with the Windows laptops have been encountering increasing challenges in connecting to its virtual serial port (for example, I had to number all of the boards because Windows started requiring drivers to be reinstalled if a student tried to connect to a different one). The students with Linux have always had it fairly easy, and the ones running MacOS had issues in the beginning that seemed to get resolved in the most recent years.

Unfortunately, the 1768 is approaching obsolescence for MBED OS. So it's time to find a new platform. I need something that can plug directly into a prototyping board, and that provides a good range of sensors and I/O pins. We tried a Micro:Bit last year, but the M0 instruction set is missing some features that I want the students to see.