XIAO ESP32C3 Multiple Software Serial Ports

Hi there,

ROCK SOLID performance from the L76K GSS unit. seems to be a TAD faster and fresher than the Grove connected AIR530 Unit.
I was able to run each device with each method Hardware/Software Serial on either Port.
The key seems to be SoftSerial works better on pins that are already defined as a Uart channel. Hardware Serial can make changes to the interconnected PPI in the chips Peripherals to better support a Brute force or bitbanged io approach to serial comms.
Baud rates also play a role so don’t try to fast at first, test , tune and turn up as needed. :v:
I added the labels and posting current code and output. for others to duplicate.

Serial Output Monitor…

16   0.8   26.041662  -80.220924  224  02/23/2025 20:13:50 224  -11.60 134.26 0.00  SE    12348    16.02  NNE   98018 26178     0           GPS 2- L76K GSS 
16   0.8   26.041662  -80.220924  227  02/23/2025 20:13:51 227  -11.50 134.26 0.00  SE    12348    16.02  NNE   98025 26180     0           GPS 2- L76K GSS 
16   0.8   26.041662  -80.220924  224  02/23/2025 20:13:52 224  -11.50 134.26 0.00  SE    12348    16.02  NNE   98033 26182     0           GPS 2- L76K GSS 
16   0.8   26.041662  -80.220924  233  02/23/2025 20:13:53 233  -11.50 134.26 0.00  SE    12348    16.02  NNE   98040 26184     0           GPS 2- L76K GSS 
16   0.8   26.041662  -80.220924  233  02/23/2025 20:13:54 233  -11.50 134.26 0.00  SE    12348    16.02  NNE   98048 26186     0           GPS 2- L76K GSS 
16   0.8   26.041662  -80.220924  228  02/23/2025 20:13:55 228  -11.40 134.26 0.00  SE    12348    16.02  NNE   98055 26188     0           GPS 2- L76K GSS 
16   0.8   26.041662  -80.220924  231  02/23/2025 20:13:56 231  -11.40 134.26 0.00  SE    12348    16.02  NNE   98063 26190     0           GPS 2- L76K GSS 
16   0.8   26.041662  -80.220924  236  02/23/2025 20:13:57 236  -11.40 134.26 0.00  SE    12348    16.02  NNE   98071 26192     0           GPS 2- L76K GSS 
16   0.8   26.041662  -80.220924  245  02/23/2025 20:13:58 245  -11.40 134.26 0.00  SE    12348    16.02  NNE   98078 26194     0           GPS 2- L76K GSS 
16   0.8   26.041662  -80.220924  251  02/23/2025 20:13:59 251  -11.40 134.26 0.00  SE    12348    16.02  NNE   98086 26196     0           GPS 2- L76K GSS 
16   0.8   26.041662  -80.220924  254  02/23/2025 20:14:00 254  -11.40 134.26 0.00  SE    12348    16.02  NNE   98093 26198     0           GPS 2- L76K GSS 
16   0.8   26.041662  -80.220924  254  02/23/2025 20:14:01 254  -11.40 134.26 0.00  SE    12348    16.02  NNE   98101 26200     0           GPS 2- L76K GSS 
15   1.0   26.041656  -80.220879  247  02/23/2025 20:14:02 247  -5.50  308.12 2.76  NW    12348    16.02  NNE   96082 26202     0           GPS 1- Air530 
15   1.0   26.041656  -80.220879  268  02/23/2025 20:14:03 268  -5.50  308.12 0.00  NW    12348    16.02  NNE   96089 26204     0           GPS 1- Air530 
15   1.0   26.041656  -80.220879  267  02/23/2025 20:14:04 267  -5.50  308.12 0.00  NW    12348    16.02  NNE   96097 26206     0           GPS 1- Air530 
15   1.0   26.041656  -80.220879  267  02/23/2025 20:14:05 267  -5.50  308.12 0.00  NW    12348    16.02  NNE   96104 26208     0           GPS 1- Air530 
15   0.8   26.041662  -80.220924  277  02/23/2025 20:14:06 277  -11.40 134.26 0.00  SE    12348    16.02  NNE   98139 26210     0           GPS 2- L76K GSS 
15   1.0   26.041656  -80.220879  279  02/23/2025 20:14:07 279  -5.20  308.12 0.54  NW    12348    16.02  NNE   96119 26212     0           GPS 1- Air530 
15   0.8   26.041662  -80.220924  290  02/23/2025 20:14:08 290  -11.40 134.26 0.00  SE    12348    16.02  NNE   98154 26214     0           GPS 2- L76K GSS 
15   0.8   26.041662  -80.220924  295  02/23/2025 20:14:09 295  -11.40 134.26 0.00  SE    12348    16.02  NNE   98161 26216     0           GPS 2- L76K GSS 
15   0.8   26.041662  -80.220924  293  02/23/2025 20:14:10 293  -11.40 134.26 0.00  SE    12348    16.02  NNE   98169 26218     0           GPS 2- L76K GSS 
15   0.8   26.041662  -80.220924  301  02/23/2025 20:14:11 301  -11.40 134.26 0.00  SE    12348    16.02  NNE   98177 26220     0           GPS 2- L76K GSS 
15   0.8   26.041662  -80.220924  303  02/23/2025 20:14:12 303  -11.40 134.26 0.00  SE    12348    16.02  NNE   98184 26222     0           GPS 2- L76K GSS 
15   0.8  

Scroll right to the end. :+1:

FQBN: esp32:esp32:XIAO_ESP32C3
Using board 'XIAO_ESP32C3' from platform in folder: C:\Users\Dude\AppData\Local\Arduino15\packages\esp32\hardware\esp32\3.0.7
Using core 'esp32' from platform in folder: C:\Users\Dude\AppData\Local\Arduino15\packages\esp32\hardware\esp32\3.0.7
**EDIT... for brevity...**
Using library TinyGPSPlus at version 1.0.3 in folder: D:\Arduino_projects\libraries\TinyGPSPlus 
Using library EspSoftwareSerial at version 8.1.0 in folder: D:\Arduino_projects\libraries\EspSoftwareSerial 
"C:\\Users\\Dude\\AppData\\Local\\Arduino15\\packages\\esp32\\tools\\esp-rv32\\2302/bin/riscv32-esp-elf-size" -A "C:\\Users\\Dude\\AppData\\Local\\arduino\\sketches\\C46D996DDDD904C0FBA2B540A6CCB432/sketch_feb23f_Posted_3_Serial_portsC3.ino.elf"
Sketch uses 284502 bytes (21%) of program storage space. Maximum is 1310720 bytes.
Global variables use 12700 bytes (3%) of dynamic memory, leaving 314980 bytes for local variables. Maximum is 327680 bytes.
"C:\Users\Dude\AppData\Local\Arduino15\packages\esp32\tools\esptool_py\4.6/esptool.exe" --chip esp32c3 --port "COM28" --baud 921600  --before default_reset --after hard_reset write_flash  -z --flash_mode keep --flash_freq keep --flash_size keep 0x0 "C:\Users\Dude\AppData\Local\arduino\sketches\C46D996DDDD904C0FBA2B540A6CCB432/sketch_feb23f_Posted_3_Serial_portsC3.ino.bootloader.bin" 0x8000 "C:\Users\Dude\AppData\Local\arduino\sketches\C46D996DDDD904C0FBA2B540A6CCB432/sketch_feb23f_Posted_3_Serial_portsC3.ino.partitions.bin" 0xe000 "C:\Users\Dude\AppData\Local\Arduino15\packages\esp32\hardware\esp32\3.0.7/tools/partitions/boot_app0.bin" 0x10000 "C:\Users\Dude\AppData\Local\arduino\sketches\C46D996DDDD904C0FBA2B540A6CCB432/sketch_feb23f_Posted_3_Serial_portsC3.ino.bin" 
esptool.py v4.6
Serial port COM28
Connecting...
Chip is ESP32-C3 (revision v0.3)
Features: WiFi, BLE
Crystal is 40MHz
MAC: a0:76:4e:40:08:ec
Uploading stub...
Running stub...
Stub running...

Code running as Posted…

// This is a 3 serial port demo using GPS units from Seeed, 2 GPS setup with ESP32C3
// Hardware Serial & Software Serial are implimented.
// Compiled with BSP 3.0.7 
// added a column at the end for which GPS unit is sending the data
// tested with AIR530 V1.0 & L76K GNSS Seeed Xiao GPS unit.
// Either unit can be used in either Port position.
// See the Seeed Support form for pictures and errata.
#include <TinyGPSPlus.h>
#include <SoftwareSerial.h>
#include <HardwareSerial.h>
// ------------------ GPS Configurations ------------------
// For GPS 1 (HardwareSerial on UART1)
const int gps1RXPin = 9; // Connect GPS1 TX to ESP32 RX (pin 7) AIR530 Grove GPS
const int gps1TXPin = 10; // Connect GPS1 RX to ESP32 TX (pin 6)
const uint32_t gps1Baud = 9600;
// For GPS 2 (SoftwareSerial)
const int gps2RXPin = D7; // Connect GPS2 TX to ESP32 RX (pin 9) L76K GNSS SEEED Studio
const int gps2TXPin = D6; // Connect GPS2 RX to ESP32 TX (pin 10)
const uint32_t gps2Baud = 9600;
// ------------------ Objects ------------------
TinyGPSPlus gps1; // For HardwareSerial GPS
TinyGPSPlus gps2; // For SoftwareSerial GPS
// Use UART1 for GPS 1:
HardwareSerial SerialGPS1(1);
// Use SoftwareSerial for GPS 2:
SoftwareSerial SerialGPS2(gps2RXPin, gps2TXPin); // (RX, TX)

// ------------------ Function Prototypes ------------------
static void smartDelay(unsigned long ms);
static void printFloat(float val, bool valid, int len, int prec);
static void printInt(unsigned long val, bool valid, int len);
static void printDateTime(TinyGPSDate &d, TinyGPSTime &t);
static void printStr(const char *str, int len);
static void printGPSSource();

void setup() {
  Serial.begin(9600);
  delay(2000);
  Serial.println("\nFullExample.ino");
  Serial.println("An extensive example of many interesting TinyGPSPlus features");
  Serial.print("Testing TinyGPSPlus library v. "); 
  Serial.println(TinyGPSPlus::libraryVersion());
  Serial.println("by Mikal Hart, tweaked for 2 GPS by PJG");
  Serial.println();
  Serial.println("Sats HDOP  Latitude   Longitude   Fix  Date       Time     Date Alt    Course Speed Card  Distance Course Card  Chars Sentences Checksum   GPS");
  Serial.println("           (deg)      (deg)       Age                      Age  (m)    --- from GPS ----  ---- to London  ----  RX    RX        Fail");
  Serial.println("----------------------------------------------------------------------------------------------------------------------------------------");
  
  // Initialize GPS 1 (HardwareSerial) on UART1 with assigned pins.
  SerialGPS1.begin(gps1Baud, SERIAL_8N1, gps1RXPin, gps1TXPin);
  Serial.println("GPS-1 HardwareSerial initialized on pins 6 (RX) and 7 (TX).");
  
  // Initialize GPS 2 (SoftwareSerial) on pins 9 & 10.
  SerialGPS2.begin(gps2Baud);
  Serial.println("GPS-2 SoftwareSerial initialized on pins 9 (RX) and 10 (TX).");
  Serial.println();
}

void loop() {
  // Process incoming GPS data for both GPS units.
  // For GPS1 via HardwareSerial:
  while (SerialGPS1.available() > 0) {
    char c = SerialGPS1.read();
    gps1.encode(c);
  }
  // For GPS2 via SoftwareSerial:
  while (SerialGPS2.available() > 0) {
    char c = SerialGPS2.read();
    gps2.encode(c);
  }
  
  // Define a reference location (London in this example).
  //static const double LONDON_LAT = 51.508131, LONDON_LON = -0.128002;
  static const double PITTSBURGH_LAT = 40.4406, PITTSBURGH_LON = 79.9959;
  // We'll decide which GPS to display.
  // Priority: If one has valid location and the other doesn't, choose the valid one.
  // If both are valid, choose the one with a lower "age" (i.e. more recent fix).
  bool valid1 = gps1.location.isValid();
  bool valid2 = gps2.location.isValid();
  TinyGPSPlus *activeGps = nullptr;
  if(valid1 && !valid2) {
    activeGps = &gps1;
  } else if(valid2 && !valid1) {
    activeGps = &gps2;
  } else if(valid1 && valid2) {
    activeGps = (gps1.location.age() <= gps2.location.age()) ? &gps1 : &gps2;
  } else {
    activeGps = &gps1; // default to gps1 if none valid
  }
  
  // Print output using the active GPS data:
  printInt(activeGps->satellites.value(), activeGps->satellites.isValid(), 5);
  printFloat(activeGps->hdop.hdop(), activeGps->hdop.isValid(), 6, 1);
  printFloat(activeGps->location.lat(), activeGps->location.isValid(), 11, 6);
  printFloat(activeGps->location.lng(), activeGps->location.isValid(), 12, 6);
  printInt(activeGps->location.age(), activeGps->location.isValid(), 5);
  printDateTime(activeGps->date, activeGps->time);
  printFloat(activeGps->altitude.meters(), activeGps->altitude.isValid(), 7, 2);
  printFloat(activeGps->course.deg(), activeGps->course.isValid(), 7, 2);
  printFloat(activeGps->speed.kmph(), activeGps->speed.isValid(), 6, 2);
  printStr(activeGps->course.isValid() ? TinyGPSPlus::cardinal(activeGps->course.deg()) : "*** ", 6);
  
  unsigned long distanceKmToLondon = (unsigned long)TinyGPSPlus::distanceBetween(
      activeGps->location.lat(),
      activeGps->location.lng(),
      //LONDON_LAT, LONDON_LON) / 1000;
      PITTSBURGH_LAT, PITTSBURGH_LON) / 1000 ;
  printInt(distanceKmToLondon, activeGps->location.isValid(), 9);
  
  double courseToLondon = TinyGPSPlus::courseTo(
      activeGps->location.lat(),
      activeGps->location.lng(),
      //LONDON_LAT, LONDON_LON);
      PITTSBURGH_LAT, PITTSBURGH_LON);
  printFloat(courseToLondon, activeGps->location.isValid(), 7, 2);
  
  const char *cardinalToLondon = TinyGPSPlus::cardinal(courseToLondon);
  printStr(activeGps->location.isValid() ? cardinalToLondon : "*** ", 6);
  
  printInt(activeGps->charsProcessed(), true, 6);
  printInt(activeGps->sentencesWithFix(), true, 10);
  printInt(activeGps->failedChecksum(), true, 9);
  
  // Print additional column indicating source: "GPS 1" or "GPS 2".
  Serial.print("   ");
  if(activeGps == &gps1) {
    Serial.print("GPS 1- Air530 ");
  } else {
    Serial.print("GPS 2- L76K GSS ");
  }
  
  Serial.println();
  
  smartDelay(1000);
  
  if (millis() > 5000 && activeGps->charsProcessed() < 10)
    Serial.println(F("No GPS data received: check wiring"));
}

// Modified smartDelay() that feeds both GPS streams.
static void smartDelay(unsigned long ms) {
  unsigned long start = millis();
  do {
    while (SerialGPS1.available() > 0)
      gps1.encode(SerialGPS1.read());
    while (SerialGPS2.available() > 0)
      gps2.encode(SerialGPS2.read());
  } while (millis() - start < ms);
}

static void printFloat(float val, bool valid, int len, int prec) {
  if (!valid) {
    while (len-- > 1)
      Serial.print('*');
    Serial.print(' ');
  } else {
    Serial.print(val, prec);
    int vi = abs((int)val);
    int flen = prec + (val < 0.0 ? 2 : 1); // . and -
    flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1;
    for (int i = flen; i < len; ++i)
      Serial.print(' ');
  }
  smartDelay(0);
}

static void printInt(unsigned long val, bool valid, int len) {
  char sz[32] = "*****************";
  if (valid)
    sprintf(sz, "%ld", val);
  sz[len] = 0;
  for (int i = strlen(sz); i < len; ++i)
    sz[i] = ' ';
  if (len > 0)
    sz[len - 1] = ' ';
  Serial.print(sz);
  smartDelay(0);
}

static void printDateTime(TinyGPSDate &d, TinyGPSTime &t) {
  if (!d.isValid()) {
    Serial.print(F("********** "));
  } else {
    char sz[32];
    sprintf(sz, "%02d/%02d/%02d ", d.month(), d.day(), d.year());
    Serial.print(sz);
  }
  
  if (!t.isValid()) {
    Serial.print(F("******** "));
  } else {
    char sz[32];
    sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second());
    Serial.print(sz);
  }
  
  printInt(d.age(), d.isValid(), 5);
  smartDelay(0);
}

static void printStr(const char *str, int len) {
  int slen = strlen(str);
  for (int i = 0; i < len; ++i)
    Serial.print(i < slen ? str[i] : ' ');
  smartDelay(0);
}

HTH
GL :slight_smile: PJ :v:

Xiao ESP32C3 is All you neeed :stuck_out_tongue:

1 Like