UART Serial Write appears to be blocking, arduino-mbed, Rasberry Pi Pico

Hi,

Newbie here, so please forgive me if this is the wrong place to post.

I am using a Raspberry Pi Pico, RP2040 on Platform IO. I am using the default framework and board options. I realise that I am not using an Arduino, but the same platform and framework are used for the Arduino Nano RP2040 Connect so I hoped the documentation would be pertinent.

Basically I have a query regarding the behaviour of Serial.write() described in the documentation, " Serial transmission is asynchronous. If there is enough empty space in the transmit buffer, Serial.write() will return before any characters are transmitted over serial." I have run some tests and this does not seem to be the case for the Pico with this setup. Serial.write() - Arduino Reference

My platformio.ini looks like this

[env:pico]
platform = raspberrypi
board = pico
framework = arduino

I am using Serial which is USB, Serial1 and Serial2. Serial1 worked out of the box, Serial 2 required instantiating as described in this post:
serial - How to use Serial2 on Raspberry Pi Pico - Arduino Stack Exchange

UART Serial2(4, 5, 0, 0);

I wrote some simple code to see if sending was buffered.

Serial.begin(9600);
Serial1.begin(9600);
Serial2.begin(9600);

    for (int i = 0; i < 10; i++) {
    Serial1.print(micros());   // this will be about 9 characters soon after starting
    Serial1.write(',');
  }

Here Serial is class UART defined in Serial.h - wrapper over mbed RawSerial, Part of Arduino - http://www.arduino.cc/ - hence I am posting here.

For Serial1 on 9600 baud this gave,
10201203,10210573,10219947,10229323,10238698...
roughly 9000 microseconds for each 9 bytes, about 1 ms per byte, which is roughly correct for 9600 baud (about 960 bytes per second). Therefore if my test code is reasonable the serial.print() call is blocking.

Performing the same code for Serial (USB) @ 9600 baud gave:
20854472,20854640,20854806,20854941,20855214...
about 150 microseconds. This still seems like quite a bit of time for a 25MHz processor, but is way faster than 9600 baud.

I have looked at the code in:
.platformio\packages\framework-arduino-mbed\cores\arduino\api
to try to understand this.

The write() method is defined in Serial.cpp

size_t UART::write(uint8_t c) {
#if defined(SERIAL_CDC)
	if (is_usb) {
		return _SerialUSB.write(c);
	}
#endif
	while (!_serial->obj->writeable()) {}
	int ret = _serial->obj->write(&c, 1);
	return ret == -1 ? 0 : 1;
}

Even if I use a buffer and write one byte at a time in the the main loop in my main.cpp each byte written will block for 1ms (I am stuck with hardware on 9600 baud).

The way I see it I can

  • Try to find a fix (or correction of my code)
  • Try to get the project to work with the limitation of the blocking write
  • Move to using /earlephilhower/arduino-pico (should work on platform IO)
  • Give up on Platform IO and use the pico sdk www.raspberrypi.com/documentation/pico-sdk/

Can anyone let me know if this makes sense and what the solution might be?

show us the whole code - Don't post snippets (Snippets R Us!)

I note that the timing is not at the very start of the code but more 10 sec or 20 sec into the execution of the code.

Was the for loop inside the loop and you kept printing? if so, what was written the very first time you executed the code?

if you waited until the output buffer is full, then you are throttled by the baud rate as explained in the doc

meaning if there is no space in the transmit buffer, the code will block until a byte has been transmitted and removed from the buffer so that you can add a new byte ➜ hence the throttling.

@J-M-L Thank you for taking the time to reply.

I appreciate what you are saying so I started a new project in Platform IO with the basically the same setup, but just using Serial1 to keep it as simple as possible. The (new) full code:

#include <Arduino.h>

void setup() {
  Serial1.begin(9600);

  delay(10000);  // so that print(micros()) will be about 9 characters long

  for (int i = 0; i < 10; i++) {
    Serial1.print(micros());   // this will be about 9 characters soon after starting
    Serial1.write(',');
  }
}

void loop() {
}

The output:
10030119,10037413,10046784,10056157,10065532,10074907,10084282,10093657,10103032,10112407,

The first delay is 7300 microseconds followed by roughly 9 or 10 ms each time. Again I think this is about right for 9600 baud to send 9 bytes, so blocking?

Any insight would be appreciated.

Thanks for the clarification

It seems indeed to be the case .

Have you tried to see if there is any difference with the arduino ide and build?

I don’t have that board so can’t test

@J-M-L Thank you again.

No I have not tried Arduino IDE, I have no experience with it and don't know if it supports the Pi Pico.

I did a bit of digging into the code after checking availableForWrite() always returned 0, and states that "default to zero, meaning "a single write may block, should be overridden by subclasses with buffering".

UART1 is of type UART. The implementation in Serial.cpp seems to use mbed::UnbufferedSerial, which then sets blocking to true.

I decided to try using BufferedSerial directly which does write, but I can only ever get the first two characters. Code below. Perhaps there is a reason for the default to Unbuffered. I don't have enough knowledge of the platform to go much further down this rabbit hole!

#include <Arduino.h>
#include "BufferedSerial.h"
#include "mbed.h"

static mbed::BufferedSerial bSerial2(digitalPinToPinName(4), digitalPinToPinName(5));

void setup() {
  Serial.begin(9600);
  delay(3000);  // wait for usb console
  Serial.println("Started setup");

  bSerial2.set_baud(9600);
  bSerial2.set_format(8, mbed::BufferedSerial::None, 1);
  
  // Only the first two characters are written "He"
  ssize_t written = bSerial2.write("Hello, World!\n", 14); // returns 14 as expected
  bSerial2.sync();    // tried this to flush - didn't work

  Serial.print("Wrote :");
  Serial.println(written); // 14

  Serial.println("Setup complete");   // this is written as expected
}

void loop() {
}

Man, I'm really getting to hate MBED. Not only does it have these sorts of "gotchas", but it does a really good job of hiding what's going on.
(I can't even find the source for Arduino's version of MBED. It's certainly not included in arduinocore-mbed; just a massive .a file and a bunch of .h files.)

Switch to the Philhower core.

(Note that this probably means that NONE of the Arduino MBED platforms have buffered serial output!)

Well - I never invested much time on mbed and won’t start now that they announced an EOL date.

https://os.mbed.com/blog/entry/Important-Update-on-Mbed/

@westfw I have now switched to earlephilhower/arduino-pico (still in Platfrom IO).

@J-M-L I had no idea that MBED was end of life, when you start with Platfrom IO for Pico you only have one choice. Hopefully the arduino-pico PlatformIO integration will be merged soon. The documentation is great and it allows access to the Pico SDK which is actually where I started with the Pico. The Pico SDK may not have all the features of Platform IO, but it is much less confusing (to me at least). Pico SDK documentation is also very good.

Just for completeness I ran the same test (code in second post) using Philhower and got the following output:
10003443,10003498,10003508,10003517,10006505,10015880,10025254,10034629,10044004,10053379

This makes sense, the Pico has a 32 byte hardware FIFO for the UART transmit. The first four timestamps increase by only a few microseconds. The fourth timestamp is taken before the FIFO is full then and then the timestamps increase by around 9ms, commensurate with 9600 baud.

Thank you again for the input.

1 Like

I've submitted a bug: MBED SerialN UART drivers are unbuffered in transmit path · Issue #935 · arduino/ArduinoCore-mbed · GitHub

Apparently similar issues have shown up before (and not been addressed):

1 Like

Thanks for coming back with the answer.