SoftwareSerial no baudrate change possible

SoftwareSerial does not allow a baud rate change after an initial has been set.

I have a peripheral that has a default baud rate of 115200 which works great on a real UART.

However, with SoftwareSerial it loses RX-characters at the speed. This is a known, well-documented, and fully understood limitation of SoftwareSerial. So I set the SoftwareSerial port first to 115200, sent to the peripheral the instruction to work at 38400 and reset the SoftwareSerial to 38400. That works on ALL platforms.. except UNO-R4.

It can be tested with code at the end. It basically switches the baud rate between 38400/115200/38400.

On SoftwareSerial all the text is readable at 38400. There is NO change in speed to 115200 happening. The output looks like this:

When doing the same on Serial1, the middle text running 115200 is not readable as the analyzer is set to 38400. It looks like this:

On Serial1, but now setting the analyzer to 115200 baud rate, the middle text is readable, but of course, the first text is NOT as it is at 38400.

Looking at SoftwareSerial.cpp around line 287, "// Configure the TX DMA and its trigger timer." works only the first time. Subsequent attempts will fail.

#include <SoftwareSerial.h> 
SoftwareSerial softSerial(2, 3); //RX, TX

// define the serial port to use (E.g. softSerial, Serial1 etc)
#define TestSerial softSerial

// if delays are wanted to check whether that makes a difference (it does not)
#define TestDelay 000

void setup() {
  Serial.begin(115200);
  while (!Serial); //Wait for the serial port to come online

  Serial.println(F("Serial speed 1: 38400"));
  
  TestSerial.begin(38400);
  if (TestDelay > 0) delay(TestDelay);
  TestSerial.print(38400);
  if (TestDelay > 0) delay(TestDelay);
   
  Serial.println(F("Serial speed 2: 115200"));
  
  TestSerial.begin(115200);
  if (TestDelay > 0) delay(TestDelay);
  TestSerial.print(115200);
  if (TestDelay > 0) delay(TestDelay);
  Serial.println(F("Serial speed 3: 38400"));
  
  TestSerial.begin(38400);
  if (TestDelay > 0) delay(TestDelay);
  TestSerial.print(38400);
  if (TestDelay > 0) delay(TestDelay);

  Serial.println(F("done"));

}

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

}

Any ideas how to overcome this ?

As you have posted in the Uno R4 WiFi category of the forum I assume that it is the board that you are using

If that is the case then do you realise that the board has a second hardware Serial port that you can use rather than using SoftwareSerial ?

1 Like

I'm not familiar with the Uno R4 Wifi. But I suspect that the RX and TX pins are Serial1 because the board has, to my knowledge, native USB.

You can compile a simple sketch

void setup()
{
  Serial1.begin(115200);
  Serial.println("hello");
}

void loop()
{
}

If it compiles, RX and TX are Serial1.

Some google results: uno-r4 extra serial ports - Google Search. You can finetune the query :wink:

Just to be clear, does that mean both UNO R4 Minima and UNO R4 WiFi ?

The problem is NOT related to Serial1.. that works without a problem on the UNO R4 WIFI.The problem is with SoftwareSerial not able to change the initial baudrate.

I have not tested on the minima, as I don't have one, but as it uses the same SoftwareSerial library I expect the problem to be the same.

yes, thanks. I know there is another UART available. The issue is that in some libraries SoftwareSerial is embedded and it means changing the code just for the UNO-R4 WIFI.

Did some more checking. The issue is related to obtaining a GPT-timer.

With every change in baud rate SoftwareSerial tries to get a new GPT-channel, instead of using the one obtained the first time around. There are 8 GPT channels/timers, 6 are reserved during initVariant() and assigned for PWM to pins 3, 5, 6, 9, 10 and 11. That leaves 2 GPT channels: one for SoftwareSerial TX and one for RX.

The solution can be done in SoftwareSerial.cpp with changes in begin() and fsp_tim_config().

1. Add in top of SoftwareSerial.cpp (somewhere around line 37)
int8_t channel = 0;
int8_t TX_channel = 0;
int8_t RX_channel = 0;

2. In begin() around line 293 add setting and save the TX-channel
    channel = TX_channel;
    if (fsp_tim_config(&tx_descr.tim, config.baudrate, false) != 0) {
        return 0;
    }
    TX_channel = channel;

3. In begin() around line 307 add setting and save the RX-channel
    channel = RX_channel;
    if (fsp_tim_config(&rx_descr.tim, config.baudrate, true) != 0) {
        return 0;
    }
    RX_channel = channel;

4. in fsp_tim_config(), around line 116 

change:  int8_t channel = FspTimer::get_available_timer(type);
to: if (channel == 0) channel = FspTimer::get_available_timer(type);
2 Likes

Unfortunately there does not appear to be a function to terminate a SoftwareSerial instance, similar to Serial.end(), which is what I was going to suggest, since multiple calls to begin() without destroying the previous instance could be problematic.

You might look at the library to see if there is a way to just directly change the baudrate by adding an additional function.