diff options
Diffstat (limited to 'cores/arduino/HardwareSerial.cpp')
-rw-r--r-- | cores/arduino/HardwareSerial.cpp | 63 |
1 files changed, 47 insertions, 16 deletions
diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index 5cd89e5..e99d503 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -26,6 +26,7 @@ #include <stdio.h> #include <string.h> #include <inttypes.h> +#include <util/atomic.h> #include "Arduino.h" #include "HardwareSerial.h" @@ -76,6 +77,13 @@ void serialEventRun(void) #endif } +// macro to guard critical sections when needed for large TX buffer sizes +#if (SERIAL_TX_BUFFER_SIZE>256) +#define TX_BUFFER_ATOMIC ATOMIC_BLOCK(ATOMIC_RESTORESTATE) +#else +#define TX_BUFFER_ATOMIC +#endif + // Actual interrupt handlers ////////////////////////////////////////////////////////////// void HardwareSerial::_tx_udr_empty_irq(void) @@ -89,8 +97,14 @@ void HardwareSerial::_tx_udr_empty_irq(void) // 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); + // 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 @@ -177,15 +191,13 @@ int HardwareSerial::read(void) int HardwareSerial::availableForWrite(void) { -#if (SERIAL_TX_BUFFER_SIZE>256) - uint8_t oldSREG = SREG; - cli(); -#endif - tx_buffer_index_t head = _tx_buffer_head; - tx_buffer_index_t tail = _tx_buffer_tail; -#if (SERIAL_TX_BUFFER_SIZE>256) - SREG = oldSREG; -#endif + tx_buffer_index_t head; + tx_buffer_index_t tail; + + TX_BUFFER_ATOMIC { + head = _tx_buffer_head; + tail = _tx_buffer_tail; + } if (head >= tail) return SERIAL_TX_BUFFER_SIZE - 1 - head + tail; return tail - head - 1; } @@ -218,8 +230,22 @@ size_t HardwareSerial::write(uint8_t c) // significantly improve the effective datarate at high (> // 500kbit/s) bitrates, where interrupt overhead becomes a slowdown. if (_tx_buffer_head == _tx_buffer_tail && bit_is_set(*_ucsra, UDRE0)) { - *_udr = c; - sbi(*_ucsra, TXC0); + // If TXC is cleared before writing UDR and the previous byte + // completes before writing to UDR, TXC will be set but a byte + // is still being transmitted causing flush() to return too soon. + // So writing UDR must happen first. + // Writing UDR and clearing TC must be done atomically, otherwise + // interrupts might delay the TXC clear so the byte written to UDR + // is transmitted (setting TXC) before clearing TXC. Then TXC will + // be cleared when no bytes are left, causing flush() to hang + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + *_udr = c; +#ifdef MPCM0 + *_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << MPCM0))) | (1 << TXC0); +#else + *_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << TXC0))); +#endif + } return 1; } tx_buffer_index_t i = (_tx_buffer_head + 1) % SERIAL_TX_BUFFER_SIZE; @@ -240,9 +266,14 @@ size_t HardwareSerial::write(uint8_t c) } _tx_buffer[_tx_buffer_head] = c; - _tx_buffer_head = i; - - sbi(*_ucsrb, UDRIE0); + + // make atomic to prevent execution of ISR between setting the + // head pointer and setting the interrupt flag resulting in buffer + // retransmission + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + _tx_buffer_head = i; + sbi(*_ucsrb, UDRIE0); + } return 1; } |