Q: What is the logic behind Arduino inlining HardwareSerial::_rx_complete_irq()
for receiving serial data (but NOT _tx_udr_empty_irq()
), and when is it advisable?
From the bottom of the HardwareSerial
class in HardwareSerial.h:
// Interrupt handlers - Not intended to be called externally
inline void _rx_complete_irq(void); // <======= inline!
void _tx_udr_empty_irq(void); // <======= NOT inline! Why?
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) // line 40 { // line 48 Serial._rx_complete_irq(); // line 49 } // line 50
Line 101-121 of "HardwareSerial_private.h" implements the inline
_rx_complete_irq()
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 triggered by the "USART Data Register Empty" flag and is called whenever the transmit buffer has passed its value to the shift register and now is "ready to receive new data" (ATmega328 Datasheet 20.11.2 p200)
Its implementation is on lines 89-113 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. Other r/w bits are preserved, and zeroes // written to the rest. #ifdef MPCM0 *_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << MPCM0))) | (1 << TXC0); #else *_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << TXC0))); #endif 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?
Again, from the bottom of the HardwareSerial
class in HardwareSerial.h:
// Interrupt handlers - Not intended to be called externally
inline void _rx_complete_irq(void); // <======= inline!
void _tx_udr_empty_irq(void); // <======= NOT inline! Why?
Why the somewhat complicated set of 3+ files? Mainly:
- HardwareSerial.h
- HardwareSerial_private.h
- HardwareSerial.cpp
I first documented this to myself and wondered about it on 31 Jan. 2018, while studying the Arduino source code. I'd like to hear some more insight on the topic from other developers.