How to establish UART connection between ESP32 and Arduino Uno

I have been given a University assignment where I need to be able to send data via UART between an ESP32 (specifically LilyGo T-Display) and an Arduino Uno.

I connect both boards using a level shifter, and the ESP RX is using pin 21, and TX 22.

When I use the below code for each board, there are no compilation issues. If I disconnect the ESP32 from power supply, the Arduino sends it's serial output to my serial monitor correctly, and when I connect the ESP to power, it stops (I assume it's trying to send it to the ESP now). The ESP then outputs to my serial monitor the output messages with the initial 0 values constantly.

I don't have much experience with Arduino programming, and I am confused as to why this isn't working. Any help is appreciated.

packet_data.h:

#ifndef PACKET_DATA_H
#define PACKET_DATA_H

typedef struct Data{
  String ph;
  String temp;
  String rpm;
} Data;

#endif

ESP32 code:

#include "packet_data.h"

#define MONITOR_BAUD_RATE 115200
#define UART_BAUD_RATE 9600
#define RX 21 
#define TX 22

Data current_bioreactor_data;
Data current_target_data;

void start_serial()
{
  Serial.end();
  Serial.begin(MONITOR_BAUD_RATE);
  // Waits for the serial connection to be secured.
  while (!Serial);
  // Adds initial delay so the serial monitor will receive any messages (change as needed).
  delay(1000);
}

void uart_setup()
{
  Serial1.begin(UART_BAUD_RATE, SERIAL_8N1, TX, RX);
  current_target_data.ph = String(0);
  current_target_data.temp = String(0);
  current_target_data.rpm = String(0);
  current_bioreactor_data.ph = String(0);
  current_bioreactor_data.temp = String(0);
  current_bioreactor_data.rpm = String(0);
  Serial.println("UART serial setup successful.");
}

void send_uart_data()
{
  String message = current_target_data.ph + "," + current_target_data.temp + "," + current_target_data.rpm;
  Serial1.println(message);
}

void read_uart_data()
{
  if (Serial1.available() > 0) 
  {
    current_bioreactor_data.ph = Serial1.readStringUntil(',');
    current_bioreactor_data.temp = Serial1.readStringUntil(',');
    current_bioreactor_data.rpm = Serial1.readStringUntil('\n');
  }
}

void setup() 
{
  start_serial();
  uart_setup();

  Serial.println("Setup successful.");
  delay(3000);
}

void loop() 
{
  read_uart_data();
  send_uart_data();

  // Testing.
  Serial.println("pH: " + current_bioreactor_data.ph);
  Serial.println("Temp: " + current_bioreactor_data.temp);
  Serial.println("RPM: " + current_bioreactor_data.rpm);
  delay(10);
}

Arduino Uno code:

#include "packet_data.h"

#define UART_BAUD_RATE 9600
#define RX 0
#define TX 1

Data current_bioreactor_data;
Data current_target_data;

void uart_setup()
{
  Serial.begin(UART_BAUD_RATE, SERIAL_8N1);
  current_target_data.ph = String(0);
  current_target_data.temp = String(0);
  current_target_data.rpm = String(0);
  current_bioreactor_data.ph = String(0);
  current_bioreactor_data.temp = String(0);
  current_bioreactor_data.rpm = String(0);
}

void send_uart_data()
{
  String message = current_bioreactor_data.ph + "," + current_bioreactor_data.temp + "," + current_bioreactor_data.rpm;
  message = "5.5,23.5,690"; // Testing.
  Serial.println(message);
}

void read_uart_data()
{
  if (Serial.available() > 0) 
  {
    current_target_data.ph = Serial.readStringUntil(',');
    current_target_data.temp = Serial.readStringUntil(',');
    current_target_data.rpm = Serial.readStringUntil('\n');
  }
}

float get_target_ph()
{
  return current_target_data.ph.toFloat();
}

float get_target_temp()
{
  return current_target_data.temp.toFloat();
}

int get_target_rpm()
{
  return current_target_data.rpm.toInt();
}

void setup() 
{
  uart_setup();
}

void loop() 
{
  read_uart_data();
  send_uart_data();
}

both the ESP and the USB chip on the Arduino are connected in parallel the Arduino TX/RX pins. you can't have both

consider using Software Serial on the Arduino or use a Mega which has 4 serial interfaces

make sure they share the same GND too.

I am restricted to only being able to use an Uno.

Could you please recommend a source on how to do this with SoftwareSerial please?

They do

arduino softwaer serial

I would suggest to study Serial Input Basics for the communication

Regarding the wiring

ESP32 Tx <---> UNO Rx (or through voltage adapter if you want to)
ESP32 Rx <---> voltage adaptor <---> UNO Tx
ESP32 GND <---> UNO GND

On the ESP32 you can use an alternate UART to keep Serial for debug

const byte  RXD2 =  16;
const byte  TXD2  = 17;

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
}

void loop() {
  if (Serial.available()) Serial2.write(Serial.read());
  if (Serial2.available()) Serial.write(Serial2.read());
}

On the UNO there is only one UART used by Serial, so if you want to keep that for debug, then you will indeed use Software Serial. You should not that it's flaky if you go at more than 9600 bauds and if you send / receive lots of data (and it can't receive and send at the same time).

#include <SoftwareSerial.h>
const byte  RX =  2;
const byte  TX  = 3;
SoftwareSerial altSerial(RX, TX);

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

void loop() {
  if (Serial.available()) altSerial.write(Serial.read());
  if (altSerial.available()) Serial.write(altSerial.read());
}

Now whatever you type in the Serial monitor of one board is sent to the other board and printed in its serial monitor. Make sure both Serial monitors are opened at 115200 bauds.

(code typed here so mind typos)

Thank you, I will give this a go when I next have access to the microcontrollers

Since I don't need to monitor the Arduino's output, how would the esp code need to be adjusted to use on the arduino?

how would you know there is successful communication between the 2? toggle an LED?

If the data is received and output by the esp's serial0

at the moment use that code to ensure the communication works. if it does then we can take it to next step.

Okay, so an update on this after having access to the kits.

I scrapped UART, I used the I2C protocol https://docs.arduino.cc/learn/communication/wire/ to send the data between the two, it works much better this way.

It has its own quirks…

What might these be?

I2C is more challenging than UART because it has stricter wire length limits due to signal degradation over shared lines, processes data in ISR context which complicates multitasking, has smaller buffers like the Wire library’s 32-byte limit, is more sensitive to noise as both clock and data share the same lines, and relies on the Wire library which is not as solid compared to UART.

I've connected the SDA to SDA and the SCL to SCL, surely they wouldn't share the same lines?

Other devices could use the same bus