Skip to content

Arduino 2.0.0 - Question: wants to use UART Rx interrupt but still keeps HardwareSerial API to handles data from Rx FIFO #5678

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
MomePP opened this issue Sep 17, 2021 · 9 comments · Fixed by #6134
Assignees
Labels
Status: Solved Type: Feature request Feature request for Arduino ESP32
Milestone

Comments

@MomePP
Copy link

MomePP commented Sep 17, 2021

Hardware:

Board: ESP32 Dev Module
Core Installation version: 2.0.0
IDE name: PlatformIO
Flash Frequency: 40Mhz
PSRAM enabled: no
Upload Speed: 921600
Computer OS: Mac OSX

Description:

Currently, I got UART interrupt working fine. I have created new class to inherits HardwareSerial then overrides begin() to enables interrupt routine. As my understanding from ESP32 docs, when registered interrupt isr, its override default isr handler. This means the Rx HardwareSerial that relies on IDF's API (eg. read, available, etc.) will be unusable, need to handles from isr by user. So, I have override HardwareSerial functions to points to a new create buffer, queue that handles the data from isr (likes an old implementation in 1.0.6).

I have take a look at uart-event but if i want to unpack my serial packet per byte, i think isr still the way to go. correct me if i misunderstood.

Is there any other solution to workaround this if i still want to use HardwareSerial functions (some libraries that based on default HardwareSerial functions) while using UART interrupt.

Cheers,
MomePP

@mycbtec
Copy link

mycbtec commented Sep 27, 2021

Looks like PR#4656 is "on the radar" to be included officially? I was expecting it to be in the v2.0.0 already... I'm using my Hardware serial.cpp modified according to this PR.

#4656

@MomePP
Copy link
Author

MomePP commented Sep 28, 2021

Looks like PR#4656 is "on the radar" to be included officially? I was expecting it to be in the v2.0.0 already... I'm using my Hardware serial.cpp modified according to this PR.

#4656

Thanks for pointing this. It seems not including on v2.0.0 yet. This PR is exactly what I was looking for.

On PR#4656 actually the same way that I used to enable serial interrupt on v1.0.6. But as v2.0.0 underlying of HardwareSerial changes to used IDF's APIs as I mentioned. The downside of current interrupt usage on v2.0.0 is that it need to overrides available() and read() functions to point it out to user queue/buffer.

Example of my current serial interrupt usage. As you can see it's like going back to v1.0.6.

void MyHardwareSerial::begin(uint32_t baud)
{
    //? same implementation of `uartBegin()` but with my queue init
    ....
    _uart0_queue = xQueueCreate(bufferSize, sizeof(uint8_t));
    ....
    uart_t *uart = &_uart_bus_array[_uart_nr];
    ....
    
    //? register uart driver and hardware pins
    ....
    
    //? register uart interrupt
    ESP_ERROR_CHECK(uart_isr_free(_uart_nr));
    ESP_ERROR_CHECK(uart_isr_register(_uart_nr, _interruptHandler, NULL, ARDUINO_ISR_FLAG, NULL));
    ESP_ERROR_CHECK(uart_enable_rx_intr(_uart_nr));
    
    _uart = uart; //? points my init uart to HardwareSerial uart object
}

void ARDUINO_ISR_ATTR MyHardwareSerial::_interruptHandler(void *arg)
{
    uint8_t inByte;
    usb_serial_t selected_buffer;
    uart_dev_t *hw = UART_REG_ADDR(0);
    BaseType_t xHigherPriorityTaskWoken;

    //? clear interrupt status by set masks
    hw->int_clr.val = (UART_RXFIFO_FULL_INT_CLR | UART_FRM_ERR_INT_CLR | UART_RXFIFO_TOUT_INT_CLR);

    while (hw->status.rxfifo_cnt || (hw->mem_rx_status.wr_addr != hw->mem_rx_status.rd_addr))
    {
        inByte = hw->fifo.rw_byte;
        
        //? checking my packet byte if isn't my packet put it to uart queue for other purpose by access via HardwareSerial APIs
        if ( ... )
        {
            // doing my packet stuff ...
        }
        else
        {
            if (_uart0_queue != NULL)
            {
                xQueueSendFromISR(_uart0_queue, &inByte, &xHigherPriorityTaskWoken);
            }
        }
    }

    if (xHigherPriorityTaskWoken)
    {
        portYIELD_FROM_ISR();
    }
}

int MyHardwareSerial::available(void)
{
    if (_uart == NULL || _uart0_queue == NULL || _uart2_queue == NULL)
        return 0;

    QueueHandle_t selected_queue = (_uart_nr == 0) ? _uart0_queue : (_uart_nr == 2) ? _uart2_queue : NULL;
    return uxQueueMessagesWaiting(selected_queue);
}

int MyHardwareSerial::read(void)
{
    if (available())
    {
        if (_uart == NULL || _uart0_queue == NULL || _uart2_queue == NULL)
            return 0;

        QueueHandle_t selected_queue = (_uart_nr == 0) ? _uart0_queue : (_uart_nr == 2) ? _uart2_queue : NULL;
        uint8_t c;
        if (xQueueReceive(selected_queue, &c, 0))
        {
            return c;
        }
        return 0;
    }
    return -1;
}

The main point of my problem is when I took data from Rx FIFO to check the packet, I cannot put it back(or it can ?) if it's not my packet. So I do as an example shown to workaround Rx FIFO access by created the queue.

Anyway it's not an issue at all, just wants to asking there is the other way round. I understand that current implementation will be fits for most users usage.

@mycbtec
Copy link

mycbtec commented Sep 28, 2021

If you want to try the PR4656, just replace the attached four files in your ESP32 core folder (make a backup of existing ones first).

For me it's working 99%. I'm just trying to understand why only sometimes, during :

Serial1.setRxInterrupt(&UART1_isr, NULL);

The ESP32 is freezing in the Setup initialization.... maybe you can try to see if that happens with you as well.

BTW, I'm still using serial.available() for UART0 and UART2.

HardwareSerial esp32_2.0.0_cores.zip

/** Tim Koers - 2021
  *
  * This sketch shows the usage of an queue, to store and read the characters that are sent to the interrupt.
  * You can safely use this in a FreeRTOS environment and use the queue from different tasks on different CPU cores.
  * This sketch assumes that when Hi is sent, the device returns an 8 byte message.
  * 
  */

#define BUFFER_SIZE 8

// This queue is here to handle the interruption of the loop when the interrupt is running.
QueueHandle_t bufferQueue;

bool messageSent = false;

// Please keep in mind, since the ESP32 is dual core, 
// the interrupt will be running on the same core as the setRxInterrupt function was called on.
static void IRAM_ATTR onSerialRX(uint8_t character, void* user_arg){

	BaseType_t xHighPriorityTaskWoken;

	if(xQueueSendFromISR(bufferQueue, &character, &xHighPriorityTaskWoken) != pdTRUE){
	  log_e("IRQ", "Failed to put character onto the queue\n");
	}
}

void setup()
{
  bufferQueue = xQueueCreate(BUFFER_SIZE * 4, sizeof(char)); // Create a queue that can hold 4 messages of 8-bytes each.

  assert(bufferQueue != NULL);

  Serial.begin(115200);
  Serial2.begin(115200);

  Serial2.setRxInterrupt(onSerialRX, NULL);
}

void loop()
{
  if(!messageSent){
	  Serial.println("Hi");
	  messageSent = true;
  }

  // Check if data in the queue and check if there is a complete message (8-bytes) in the queue
  if(!(uxQueueMessagesWaiting(bufferQueue) % BUFFER_SIZE)){
    char c;
	// Check if the queue is not empty, 0 % 8 returns 0 instead, so does 24 % 8
	while(uxQueueMessagesWaiting(bufferQueue) > 0){
	  // Get the character from the queue, but don't block if someone else is busy with the queue
	  if(xQueueReceive(bufferQueue, &c, 0) == pdTRUE)
		Serial.write(c);
	}

	// Allow the system to request another data set
	messageSent = false;
  }
}

@MomePP
Copy link
Author

MomePP commented Sep 29, 2021

@mycbtec your issue may relate to this one RX line has traffic at boot, this has been solved by using core v2.0.0 (UART IDF refactoring). I tried the PR#4656 and stuck at esp_intr_alloc() cause my custom board has data traffic before ESP boot up. Without start traffic issue, It works fine as expected.

BTW, I don't want to mesh up with core itself. As my example MyHardwareSerial just inherits from HardwareSerial without changing the arduino core.

@SuGlider SuGlider self-assigned this Dec 13, 2021
@SuGlider
Copy link
Collaborator

@MomePP @mycbtec

A potential solution is being implemented.
It's the Serial.onReceive(function)

As soon as any data arrives UART RX, function() will called.

Example:

void UART_RX_IRQ(){
  uint16_t size = Serial.available();
  Serial.printf("Got %d bytes on Serial to read\n", size);
  while(Serial.available()) {
    Serial.write(Serial.read());
  }
  Serial.printf("\nSerial data processed!\n");
}

void setup() {
  Serial.begin(115200);
  Serial.onReceive(UART_RX_IRQ);
  Serial.println("Send data to UART0 in order to activate the RX callback");
}

void loop() {
  Serial.println("Sleeping for 10 seconds...");
  delay(10000);
}

Would that work for you?

@SuGlider SuGlider added Status: In Progress ⚠️ Issue is in progress Type: Feature request Feature request for Arduino ESP32 Status: To be implemented Selected for Development labels Jan 12, 2022
@SuGlider SuGlider added this to the 2.0.3 milestone Jan 12, 2022
@MomePP
Copy link
Author

MomePP commented Jan 12, 2022

@SuGlider

Sure, This can be solved current 'Arduino Things' that need to be handles from user side when using serial interrupt.

Looking forward to trying out on 2.0.3, Thanks !

@mycbtec
Copy link

mycbtec commented Jul 5, 2022

@SuGlider

Sure, This can be solved current 'Arduino Things' that need to be handles from user side when using serial interrupt.

Looking forward to trying out on 2.0.3, Thanks !

@MomePP did you try Serial.onReceive(function) on 2.0.3 ?

I still have PR#4656 implemented in my code and was planning to update it... any feedback would be appreciated!!

@MomePP
Copy link
Author

MomePP commented Jul 6, 2022

@mycbtec Same here, I did not try onReceive() yet. Currently I still used my inherit method to enabled uart isr.

One thing, I notice when I'm trying to use uart event is initial baudrate issue (note that it's not the onReceive()). For my custom board, I have inter-communication between chips at high baudrate (~4 mbps - 3686400). I need to start uart driver at normal baudrate(115200) then update it to the high one. So far, as I said still stick with isr by now.

In future, if I had a chance to use this in my new project implementation, I will update here. 😀

@mycbtec
Copy link

mycbtec commented Jul 9, 2022

@MomePP @mycbtec

A potential solution is being implemented. It's the Serial.onReceive(function)

As soon as any data arrives UART RX, function() will called.

Example:

void UART_RX_IRQ(){
  uint16_t size = Serial.available();
  Serial.printf("Got %d bytes on Serial to read\n", size);
  while(Serial.available()) {
    Serial.write(Serial.read());
  }
  Serial.printf("\nSerial data processed!\n");
}

void setup() {
  Serial.begin(115200);
  Serial.onReceive(UART_RX_IRQ);
  Serial.println("Send data to UART0 in order to activate the RX callback");
}

void loop() {
  Serial.println("Sleeping for 10 seconds...");
  delay(10000);
}

Would that work for you?

@SuGlider

I'm testing your example above... Is it possible to trigger ISR every single byte UART RX receives?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Solved Type: Feature request Feature request for Arduino ESP32
Projects
Development

Successfully merging a pull request may close this issue.

4 participants