serialEvent3() doesn't work inside while() loop!

I have created a piece of code where I take advantage of serialEvent3() on a Mega2560 to sniff any character coming in Serial Port 3 and print them to screen on Serial Port 0.

serialEvent3() works inside the void loop(), but as soon as I create a while() loop, serialEvent3() doesn't work anymore!

In my code, whenever I send "Request Info Command\r\n" to another board, the other board sends some info back to me.

Here is the working situation:

int loop_cnt;
char myBuff[72];


void setup() {
    // put your setup code here, to run once:
    Serial.begin(57600);//Setting up Serial0
    Serial3.begin(100000);//Setting up Serial0

    DataComplete = false;//Data complete flag initialized
    loop_cnt = 0;//loop counter
    data_cnt = 0;//incoming data counter

    Serial.print(F(" - PROGRAM STARTED - \r\n"));

    Serial3.print(F("Request Info Command\r\n"));//Send me data
}

void loop() {
    if (DataComplete) {
        loop_cnt++;
        Serial.print(F("SUCCESS\r\n"));

        DataComplete = false;
                data_cnt=0;

        delay(100);
        Serial3.print(F("Request Info Command\r\n"));
    }

    if(loop_cnt == 100){
        Serial.print(F("\r\n Number of Trials: "));
        Serial.print(loop_cnt);
        Serial.print(F("\r\n\r\n - PROGRAM TERMINATED - \r\n"));
        while(1);
    }

}


/* SerialEvent3 occurs whenever a new data comes in the
   hardware serial RX3                */
void serialEvent3() {
  while (Serial3.available()) {
    uint8_t my_data = Serial3.read();
    
    myBuff[data_cnt] = my_data;
    
    Serial.print((char)my_data);
    
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (my_data == '\n') {
      DataComplete = true;
    }
    data_cnt++;
  }
}

Here is the NON-working situation:

int loop_cnt;
char myBuff[72];

/************************************************************************/
/*          Timer Values           */
/************************************************************************/
unsigned long time_begin = 0;
unsigned long time_end = 0;
unsigned long delta_time = 0;
/************************************************************************/
/*        END of Timer Values          */
/************************************************************************/

void setup() {
    // put your setup code here, to run once:
    Serial.begin(57600);
    Serial3.begin(100000);

    DataComplete = false;
    loop_cnt = 0;
    data_cnt = 0;

    Serial.print(F(" - PROGRAM STARTED - \r\n"));

    Serial3.print(F("Request Info Command\r\n"));
}

void loop() {
    do {
        time_begin = millis();
        delta_time = time_begin - time_end;

        if (delta_time > 500)
        {
            Serial3.print(F("Request Info Command\r\n"));
            time_end = time_begin;
        }

        if (DataComplete) {
            DataComplete = false;
            data_ack = true;
        }
    } while (data_ack == false);

    Serial.print(F("SUCCESS\r\n"));
    while(1);
}


/* SerialEvent3 occurs whenever a new data comes in the
   hardware serial RX3                */
void serialEvent3() {
  while (Serial3.available()) {
    uint8_t my_data = Serial3.read();
    
    myBuff[data_cnt] = my_data;
    
    Serial.print((char)my_data);
    
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (my_data == '\n') {
      DataComplete = true;
    }
    data_cnt++;
  }
}

In the non-working code, basically the serialEvent3() cannot catch any incoming data even though the other board is sending out data. I have confirmed that my board is sending out the data request command and the other board is sending out data by monitoring it in a terminal.

Does anybody know what am I doing wrong!?

Thanks

It's just the way serialEvent works. Take a look at what the Arduino AVR Boards main() is doing behind the scenes (https://github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/cores/arduino/main.cpp#L33-L51):

int main(void)
{
    init();

    initVariant();

#if defined(USBCON)
    USBDevice.attach();
#endif

    setup();
    
    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
    }
        
    return 0;
}

It's the serialEventRun() call that handles serialEvent3. Basically all it does is this:

if(Serial3.available()) {
  serialEvent3();
}

So as you can see, serialEvent3() can only be called after loop() returns. If you have blocking code then that prevents loop() from returning and serialEvent3() will not be responsive.

It was completely idiotic for Arduino to hide such a simple bit of code from the user which is really not helpful and only causes confusion and inefficiency for the 99.99% of sketches that don't use serialEvent.

You're much better to just implement something like serialEvent3 in your own code so that you will always be aware of how it works and how your other code may affect it.

Have a look at the examples in Serial Input Basics - simple reliable ways to receive data.

...R

It's one of those things on the "to do" list. It's been on the list for longer than I've been using Arduinos. The reference says that serialEvent() and its brothers work like interrupts. But they don't.

At this point, I don't see this ever changing in the Arduino core. Insert the code below inside any while() loops which may need to update from serial, or at least clear the small serial buffer during a long-running while() loop.

if(Serial3.available()) {
  serialEvent3();
}

Thank you guys for your replies.

I tried following this Atmel Tutorial to setup ISR on Serial3, but I am facing some problems.

I tried Enabling UART3 receiver and transmitter by placing the following line in my Setup():

UCSR3B = ((1<<RXEN3) | (1<<TXEN3) | (1<<RXCIE3));

Then, tried setting up ISR(USART3_RX_vect), but compiler said it has already been defined. So, I digged into Arduino files and commented the one defined by Arduino and managed to define my own.

#define UART_RX_BUFFER_MASK (SERIAL_RX_BUFFER_SIZE - 1)
static unsigned char UART_RxBuf[SERIAL_RX_BUFFER_SIZE];

ISR(USART3_RX_vect){
	unsigned char data;
	unsigned char tmphead;
	
	/* Read the received data */
	data = UDR3;                 
	/* Calculate buffer index */
	tmphead = (_rx_buffer_head + 1) & UART_RX_BUFFER_MASK;
	/* Store new index */
	_rx_buffer_head = tmphead;      

	if (tmphead == _rx_buffer_tail) {
		/* ERROR! Receive buffer overflow */
	}
	/* Store received data in buffer */
	UART_RxBuf[tmphead] = data;
}

However, it still doesn’t work for me.

Can anybody help me fix my ISR loop?

Thanks

Why are you making this so complicated?

If SerialEvent() would work then so will the examples in the link I gave you in Reply #2

…R

This piece of code definitely works, but it defeats the purpose of setting up an interrupt on a uart port.

if(Serial3.available()) {
  serialEvent3();
}

That is why I am trying to pursue the more complicated way.

markwright041: This piece of code definitely works, but it defeats the purpose of setting up an interrupt on a uart port.

That does not make sense to me. There was no indication in your Original Post that you needed to use an interrupt. Where has that idea come from?

serialEvent3() is just a perfectly ordinary function that just happens to be called in the background from the normally invisible main() function.

...R

Oh I see. my bad then. I thought SerialEvent() is supposed to work like ISR!

Thanks for the clarification. I guess I have to figure out how to setup ISR then.

If you wanted to throw out all of the functionality that Arduino provides for Serial3, then go ahead. In most real programs, you don't actually need to know to the microsecond when the character arrived. Just deal with Serial3.available() in the normal flow of your program.

serialEvent() is a leftover stub of an idea which turned out to be unnecessary.

I guess I have to figure out how to setup ISR then.

Why?

Just poll for Serial3.available inside loop, like everyone has been telling you. There is only one possible reason you need to handle characters during the RX interrupt, and you have not described that yet. Why do you think you need the RX char ISR?

If you really do need that, I have a solution, but I'm reluctant to throw it out there as a solution to a problem you probably don't have. :-/

Cheers, /dev


P.S. serialEvent used to be an interrupt function. :(

-dev:
P.S. serialEvent used to be an interrupt function. :frowning:
[/quote]
I see! It must be about time to update the documentation then. It was only 6 years ago.

@-dev good to know! That makes it more clear to me why Arduino has this thing.

MorganS: The reference says that serialEvent() and its brothers work like interrupts. But they don't.

Would you mind pointing out where it says that? I'll put it on my list of things to fix as soon as it becomes more clear what's happening with the reference-en repository.

It looks like it's fixed now. I'm sure it was there a year ago.

Thanks for everyone's response.

MorganS: if(Serial3.available()) {   serialEvent3(); }

I am following what you guys recommended and it is working fine. Thanks for the solution and detailed explanation.

-dev: Why?

I am controlling a charging system where I am using all 4 UART ports of my ATmega2560 board, where one is used for debugging, one is used for UI notification, one is used for communication with another MCU, and the last one is connected to an XBee for wireless communication. So, for fast response to changes in charging cycle, I need to receive and process received data as fast as possible to jump to the next stage of charging cycle. So far the suggested method is working fine though. Thanks.