This article shows how you can send JSON documents between two boards connected by a serial link.

In this article, I’ll assume that we use two Arduino UNOs. Because I want to keep Serial for logging, I’ll use SoftwareSerial for the communication between the two boards.

Serial communication between two Arduino UNOs

If you use a board with several hardware serial implementations (such as Arduino Mega or Arduino Due), prefer using Serial1, Serial2, or Serial3 over SoftwareSerial, because it will provide better performance.

Sender program

This program sends {"timestamp":1234,"value":687} every 5 seconds.

#include <ArduinoJson.h>
#include <SoftwareSerial.h>

// Declare the "link" serial port
// Please see SoftwareSerial library for detail
SoftwareSerial linkSerial(10, 11); // RX, TX

void setup() {
  // Initialize "debug" serial port
  // The data rate must be much higher than the "link" serial port
  Serial.begin(115200);
  while (!Serial) continue;

  // Initialize the "link" serial port
  // Use the lowest possible data rate to reduce error ratio
  linkSerial.begin(4800);
}
 
void loop() {
  // Values we want to transmit
  long timestamp = millis();
  int value = analogRead(1);

  // Print the values on the "debug" serial port
  Serial.print("timestamp = ");
  Serial.println(timestamp);
  Serial.print("value = ");
  Serial.println(value);
  Serial.println("---");

  // Create the JSON document
  StaticJsonDocument<200> doc;
  doc["timestamp"] = timestamp;
  doc["value"] = value;

  // Send the JSON document over the "link" serial port
  serializeJson(doc, linkSerial);

  // Wait
  delay(5000);
}

Receiver program

This program receive the JSON document and extracts the values.

#include <ArduinoJson.h>
#include <SoftwareSerial.h>

// Declare the "link" serial port
// Please see SoftwareSerial library for detail
SoftwareSerial linkSerial(10, 11); // RX, TX

void setup() {
  // Initialize "debug" serial port
  // The data rate must be much higher than the "link" serial port
  Serial.begin(115200);
  while (!Serial) continue;

  // Initialize the "link" serial port
  // Use the lowest possible data rate to reduce error ratio
  linkSerial.begin(4800);
}
 
void loop() {
  // Check if the other Arduino is transmitting
  if (linkSerial.available()) 
  {
    // Allocate the JSON document
    // This one must be bigger than for the sender because it must store the strings
    StaticJsonDocument<300> doc;

    // Read the JSON document from the "link" serial port
    DeserializationError err = deserializeJson(doc, linkSerial);

    if (err == DeserializationError::Ok) 
    {
      // Print the values
      // (we must use as<T>() to resolve the ambiguity)
      Serial.print("timestamp = ");
      Serial.println(doc["timestamp"].as<long>());
      Serial.print("value = ");
      Serial.println(doc["value"].as<int>());
    } 
    else 
    {
      // Print error to the "debug" serial port
      Serial.print("deserializeJson() returned ");
      Serial.println(err.c_str());
  
      // Flush all bytes in the "link" serial port buffer
      while (linkSerial.available() > 0)
        linkSerial.read();
    }
  }
}

Things used in these programs

Common problem

InvalidInput

deserializeJson() returns InvalidInput when the input JSON document is incorrect.

This problem is often due to errors in the serial communication. These errors come from:

  • clock skew
  • bad wiring / electric noise
  • receiver reading too slowly and dropping bytes
  • voltage mismatch (5V vs 3.3V)

Clock skew

Clock running at different speeds is a major problem when you use different hardware on each side of the link (for example, an AVR and an ESP8266). For example, if the sender is too fast, the receiver will occasionally receive phantom characters; and if the sender it too slow, the receiver will sometimes miss a character.

Clock accuracy is also an issue when you use SoftwareSerial; that’s why you should always prefer hardware implementations.

It’s impossible to get a serial communication without any error, but you can improve the error ratio by reducing the baud rate. For example, in the example above, we used 4800 bauds, which is very slow.

If you need a reliable serial communication, you must detect the errors (for example with a checksum) and resend the data when needed.

Voltage mismatch

Not all microcontrollers use the same voltage for the serial port. Some use 5V logic; others use 3.3V; the table below shows the values for the most common development boards:

If you need to wire two devices with different voltages, you need a logic level converter.

Troubleshooting

To troubleshoot an InvalidInput error, start by displaying the JSON input. The simplest way to do that is to use the ReadLoggingStream from the StreamUtils library:

ReadLoggingStream loggingStream(linkSerial, Serial);
deserialzeJson(doc, loggingStream);

This program will print to Serial every byte it receives from linkSerial.

It’s crucial to have Serial running much faster than linkSerial; otherwise, the program will read too slowly from linkSerial. For example, in the program above, we used 115200 bauds for Serial and 4800 bauds for linkSerial.