Skip to content

Commit b7e9bb4

Browse files
Fix HardwareSerial::flush() when interrupts are kept disabled for a while
It turns out there is an additional corner case. The analysis in the previous commit wrt to flush() assumes that the data register is always kept filled by the interrupt handler, so the TXC bit won't get set until all the queued bytes have been transmitted. But, when interrupts are disabled for a longer period (for example when an interrupt handler for another device is running for longer than 1-2 byte times), it could happen that the UART stops transmitting while there are still more bytes queued (but these are in the buffer, not in the UDR register, so the UART can't know about them). In this case, the TXC bit would get set, but the transmission is not complete yet. We can easily detect this case by looking at the head and tail pointers, but it seems easier to instead look at the UDRIE bit (the TX interrupt is enabled if and only if there are bytes in the queue). To fix this corner case, this commit: - Checks the UDRIE bit and only if it is unset, looks at the TXC bit. - Moves the clearing of TXC from write() to the tx interrupt handler. This (still) causes the TXC bit to be cleared whenever a byte is queued when the buffer is empty (in this case the tx interrupt will trigger directly after write() is called). It also causes the TXC bit to be cleared whenever transmission is resumed after it halted because interrupts have been disabled for too long. As a side effect, another race condition is prevented. This could occur at very high bitrates, where the transmission would be completed before the code got time to clear the TXC0 register, making the clear happen _after_ the transmission was already complete. With the new code, the clearing of TXC happens directly after writing to the UDR register, while interrupts are disabled, and we can be certain the data transmission needs more time than one instruction to complete. This fixes arduino#1463 and replaces arduino#1456.
1 parent 59c6467 commit b7e9bb4

File tree

1 file changed

+8
-7
lines changed

1 file changed

+8
-7
lines changed

hardware/arduino/avr/cores/arduino/HardwareSerial.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ void HardwareSerial::_tx_udr_empty_irq(void)
233233
_tx_buffer_tail = (_tx_buffer_tail + 1) % SERIAL_BUFFER_SIZE;
234234

235235
*_udr = c;
236+
237+
// clear the TXC bit -- "can be cleared by writing a one to its bit
238+
// location". This makes sure flush() won't return until the bytes
239+
// actually got written
240+
sbi(*_ucsra, TXC0);
236241
}
237242
}
238243

@@ -344,9 +349,9 @@ void HardwareSerial::flush()
344349
if (!_written)
345350
return;
346351

347-
// UDR is kept full while the buffer is not empty, so TXC triggers
348-
// when EMPTY && SENT
349-
while (bit_is_clear(*_ucsra, TXC0));
352+
while (bit_is_set(*_ucsrb, UDRIE0) || bit_is_clear(*_ucsra, TXC0));
353+
// If we get here, nothing is queued anymore (DRIE is disabled) and
354+
// the hardware finished tranmission (TXC is set).
350355
}
351356

352357
size_t HardwareSerial::write(uint8_t c)
@@ -364,10 +369,6 @@ size_t HardwareSerial::write(uint8_t c)
364369

365370
sbi(*_ucsrb, UDRIE0);
366371
_written = true;
367-
// clear the TXC bit -- "can be cleared by writing a one to its bit
368-
// location". This makes sure flush() won't return until the bytes
369-
// actually got written
370-
sbi(*_ucsra, TXC0);
371372

372373
return 1;
373374
}

0 commit comments

Comments
 (0)