Serial communication with interrupt

Hello!

I want to check the Connection between my XIAO Esp32C3 and Godot Engiene 4.6.2 via a heartbeat. For the Serial Communication I have the GDSerCommPlugin in Godot. I don't know much about Interrupts and that is why I am here. I want there to be an interrupt whenever the Esp receives a message via Serial.

Hi @pr0xrl !

If you are using the Arduino IDE you can register a callback function using onReceive(). See here for explanations

https://docs.espressif.com/projects/arduino-esp32/en/latest/api/serial.html

If you use ESP-IDF there's an explanation here

https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-reference/peripherals/uart.html

and here

https://esp32.com/viewtopic.php?t=14759#p58062

Good luck!
ec2021

I am using visual studio code with PlatformIO

The onReceive() belongs to half-duplex I2C bus/protocol; whereas, serial usually refers to full-duplex UART communcation.

If you read the link I provided you'll see that the ESP32 provides a callback function for Serial

Example taken from the link:

void onReceiveCallback() {
    while (Serial1.available()) {
        char c = Serial1.read();
        Serial.print(c);
    }
}

void setup() {
    Serial1.begin(115200);
    Serial1.onReceive(onReceiveCallback);
}

Yeah! This is onReceiveCallback() and NOT onReceive()?

Wire.onReceive(receiveEvent)

void receiveEvent()
{

}

Why do you think that you need an interrupt?

I'm not familiar with the ESP32 family but serial communication is usually done using interrupts.

And the usual approach to detecting if data is available is to use Serial.available() in loop(). Your code needs to be non-blocking (so no use of delays).

My code is blocking. It has very important while loops in it which I can't get rid of for now. That is why

Incoming serial data is stored in a buffer by background interrupt service routines. The Serial.available() function provides the user with access to this buffered data.

However, it seems that the original poster is interested in reading data directly from the UART receiver hardware -- similar to accessing the USART registers (e.g., UDR0) on the Arduino Uno R3, rather than using the Arduino Serial abstraction layer.

ISR(USART_RX_vect)       //USART receive complete interrupt
{
    rx_data = UDR0;   // Read received byte
}

I have asked AI to give code to read data directly from the RX register of ESP32. It has given me the following code most of which are beyond my understandig as they are wrtten in HAL (High Level Abstraction Layer) format.

#include "driver/uart.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include <string.h>

#define UART_NUM UART_NUM_1
#define BUF_SIZE 1024

static QueueHandle_t uart_queue;

void uart_event_task(void *pvParameters)
{
    uart_event_t event;
    uint8_t data[BUF_SIZE];

    while (1)
    {
        if (xQueueReceive(uart_queue, &event, portMAX_DELAY))
        {
            if (event.type == UART_DATA)
            {
                int len = uart_read_bytes(UART_NUM, data, event.size, portMAX_DELAY);
                
                // Process received data
                for (int i = 0; i < len; i++)
                {
                    // Example: echo or store
                }
            }
        }
    }
}

void app_main(void)
{
    uart_config_t uart_config = {
        .baud_rate = 9600,
        .data_bits = UART_DATA_8_BITS,
        .parity    = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
    };

    uart_driver_install(UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 10, &uart_queue, 0);
    uart_param_config(UART_NUM, &uart_config);
    uart_set_pin(UART_NUM, 17, 16, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);

    xTaskCreate(uart_event_task, "uart_event_task", 2048, NULL, 10, NULL);
}

workflow of the above code:

  • UART hardware receives byte
  • RX FIFO gets data
  • Hardware interrupt fires
  • ESP32 UART ISR (inside driver) runs
  • Data moved to ring buffer
  • Event (UART_DATA) sent to queue
  • Your task wakes up and reads data

It is always a bad idea to write blocking code. And it is never ever really needed.
What you can do for now is checking Serial.available() in each of your while loops.

onReceive() is the function to register the callback function:

Serial1.onReceive(onReceiveCallback)

It's up to the developer how to name the callback routine. You could write

Serial1.onReceive(handleSerial)

or give it any other function name that is valid in C++

That's fine. But, if it is Arduino UNO R3/NANO/MEGA, then onReceive() is a reserved word in I2C protocoal. Could it be used in UART/SPI protocol?

No, it is not a reserved word. It is a method name in the scope of the Wire class. And of course it can be used in another scope with another meaning.

I mean it is a reserved word for I2C protocol; hence, it cannot be used as an indetifier.

Please follow the link I provided...

onReceive()

is not a globally reserved function, it is a function usually provided as a public function of a communication class. You can see this because it's not used "stand-alone" but with a prefix as Wire, Serial1 etc.

Wire onReceive()
Serial1.onReceive()

The Arduino documentation may leave the impression it was a globally reserved function but that's wrong.

The ESP32 hardware serial class also provides a similar function without any collision with the I2C class.

No one hinders you to create an onReceive() method in a communication class of your own.

As I have been using onReceive() method to register a callback function in the I2C protocol, I have thought that it only belongs to I2C protocol.

Do you have any example code that I can run for a software UART port of UNO R3 using the onReceive() method?

But does onReceive() work if the communication is via usb-c

No. onReceive() is a class method of the ESP32 hardware serial (the TO uses an ESP).

For Arduino Uno or the like you can use SerialEvent:

https://docs.arduino.cc/built-in-examples/communication/SerialEvent/

it's provided by the Arduino IDEs framework.

You could be using the Arduino framework or the espressif framework.


Platformio.ini says which is being used

I haven't tested it yet but as USB-C is also using the same communication class as Serial1 it should work.

Here's an example where it's used with Serial:

https://github.com/espressif/arduino-esp32/issues/6302#issuecomment-1046080190

Just give it a try...