Skip to main content
3 of 6
added 2 characters in body; edited title

What is the logic behind Arduino inlining `HardwareSerial::_rx_complete_irq` for receiving serial data, and when is it advisable?

Q: What is the logic behind Arduino inlining HardwareSerial::_rx_complete_irq for receiving serial data, and when is it advisable?
Furthermore, what is the design logic behind putting one of the serial ISR function definitions in a header file vs in a source file? Also, when is this good design and what are the tradeoffs, and when is it illegal or not permitted by the language, compiler or anything else?

Here is the exact scenario that made me think of this question:


See here for the HardwareSerial implementation files: https://github.com/arduino/ArduinoCore-avr/tree/master/cores/arduino

Here is the main header file. https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/HardwareSerial.h

The inline ISR _rx_complete_irq:

  • Line 138 of "HardwareSerial.h" declares the inline ISR for when serial data is received:

     inline void _rx_complete_irq(void); 
    • This ISR is called whenever "there are unread data present in the receive buffer." (ATmega328 Datasheet 20.7.3 p190)

    • Line 40 and 48-50 of "HardwareSerial0.cpp" is where the ISR is set up:

       ISR(USART_RX_vect) { Serial._rx_complete_irq(); } 
    • Line 101-121 of "HardwareSerial_private.h" implements the inline function:

       void HardwareSerial::_rx_complete_irq(void) { if (bit_is_clear(*_ucsra, UPE0)) { // No Parity error, read byte and store it in the buffer if there is // room unsigned char c = *_udr; rx_buffer_index_t i = (unsigned int)(_rx_buffer_head + 1) % SERIAL_RX_BUFFER_SIZE; // if we should be storing the received character into the location // just before the tail (meaning that the head would advance to the // current location of the tail), we're about to overflow the buffer // and so we don't write the character or advance the head. if (i != _rx_buffer_tail) { _rx_buffer[_rx_buffer_head] = c; _rx_buffer_head = i; } } else { // Parity error, read byte but discard it *_udr; }; } 

The NON-inline ISR _tx_udr_empty_irq:

  • Line 139 of "HardwareSerial.h" declares the ISR for transmitting serial data: void _tx_udr_empty_irq(void);
    • This ISR is called whenever the transmit buffer has passed its value to the shift register and now is "ready to receive new data" (ATmega328 Datasheet 24.7.3 p232)

    • Its implementation is on lines 81-99 of "HardwareSerial.cpp"

       void HardwareSerial::_tx_udr_empty_irq(void) { // If interrupts are enabled, there must be more data in the output // buffer. Send the next byte unsigned char c = _tx_buffer[_tx_buffer_tail]; _tx_buffer_tail = (_tx_buffer_tail + 1) % SERIAL_TX_BUFFER_SIZE; *_udr = c; // clear the TXC bit -- "can be cleared by writing a one to its bit // location". This makes sure flush() won't return until the bytes // actually got written sbi(*_ucsra, TXC0); if (_tx_buffer_head == _tx_buffer_tail) { // Buffer empty, so disable interrupts cbi(*_ucsrb, UDRIE0); } } 

Why the difference? Why inline one ISR and not the other? Why the somewhat complicated set of 3+ files? Mainly:

  • HardwareSerial.h
  • HardwareSerial_private.h
  • HardwareSerial.cpp