UART issues were reported by our customers, when using the Arduino UNO R4 with the IO Expander. When the Serial Monitor was enabled for debugging, the UART communications to the IO Expander started failing. What was going on? Surely a big company like Arduino would not release a faulty product? To help our customers we investigated the issue and discovered that the issue was with the Arduino UNO R4 UART driver and released this fix to help everyone else with these same issues.
Upon examination of the UART::write(uint8_t) function we can clearly see the while (!tx_done), waiting for tx_done to be set by the Transmit End Interrupt. This function is blocking and will not return until the transmit is complete. What a waste of CPU!
/* -------------------------------------------------------------------------- */
size_t UART::write(uint8_t c) {
/* -------------------------------------------------------------------------- */
if(init_ok) {
tx_done = false;
R_SCI_UART_Write(&uart_ctrl, &c, 1);
while (!tx_done) {}
return 1;
}
else {
return 0;
}
}
The class UART defines a txBuffer but is never used by the UART::write(uint8_t) function. What a waste of Memory!
class UART : public arduino::HardwareSerial {
...
private:
...
arduino::SafeRingBufferN<SERIAL_BUFFER_SIZE> txBuffer;
...
};
When you get a Receive Interrupt the function RingBufferN::store_char(uint8_t) is called which updates the _numElems variable. In your application when you call the Serial.read() function it calls the RingBufferN::read_char() which also updates the very same _numElems variable. This is not interrupt safe and can result in the _numElems not updating properly or even becoming corrupted.
/* -------------------------------------------------------------------------- */
void UART::WrapperCallback(uart_callback_args_t *p_args) {
/* -------------------------------------------------------------------------- */
...
case UART_EVENT_RX_CHAR:
{
if (uart_ptr->rxBuffer.availableForStore()) {
uart_ptr->rxBuffer.store_char(p_args->data);
}
break;
}
...
}
void RingBufferN::store_char( uint8_t c )
{
// 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 (!isFull())
{
_aucBuffer[_iHead] = c ;
_iHead = nextIndex(_iHead);
_numElems = _numElems + 1;
}
}
/* -------------------------------------------------------------------------- */
int UART::read() {
/* -------------------------------------------------------------------------- */
return rxBuffer.read_char();
}
int RingBufferN::read_char()
{
if (isEmpty())
return -1;
uint8_t value = _aucBuffer[_iTail];
_iTail = nextIndex(_iTail);
_numElems = _numElems - 1;
return value;
}
USB Communications has the same Interrupt Priority as UART communications, but the USB interrupt can consume too much time not allowing a UART interrupt, causing a Receive Interrupt Overrun at higher speed communications like 115k. No communication device should block others from getting their critical CPU time.
To help support the IO Expander 9-bit mode, the UART class has also been modified to support 9-bit communications as well.
Please make a backup of the files you are going to replace first, or you can reinstall the Arduino UNO R4 board package.
Download and replace the following files in the Arduino15\packages\arduino\hardware\renesas_uno\1.2.0\Cores\Arduino folder.
Windows: C:\Users\{username}\AppData\Local\Arduino15
macOS: /Users/{username}/Library/Arduino15
Linux: /home/{username}/.arduino15
serial.h
serial.cpp
IRQManager.cpp
api\HardwareSerial.h
Note: Fix is for Arduino UNO R4 v1.2.0 Board Package.
This fix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.