Skip to content

HardwareSerial::flush hangs on high speed communications. #1742

Closed
@KurtE

Description

@KurtE

For the fun of it I started experimenting running some Dynamixel AX-12 servos connected up to an Arduino Mega (1280), with the standard Hardware Serial class, that externally I do a few tweaks to make it work at half duplex. The AX-12 interface runs at the baud rate of 1000000. I was able to get it to start working, but it would hang the processor on a pretty frequent basis. By instrumenting my code by bracketing some code fragments with digitalWrite statements and the use of my Logic Analyzer, I found the hang was in the flush function. So I took a look over the code in HardwareSerial.cpp, I found a race condition in the write function. The problem is that you clear the TXC condition, which at very high baud rates may happen after the last byte was output and the state was already set. Then when you call off to flush, the state will never be set again and you are hung in the loop.

The code also relies on every byte being output from the Interrupt hander, which for high baud rates will slow down. (Tried to add image, but would not take JPG file).

So I rewrote the write function:

size_t HardwareSerial::write(uint8_t c)
{
  // If the queue is empty and data register empty, we should simply be able to output the character
  // May only need to check for UDRE as unless interrupts are disabled the only case we should
  // have where it is empty, the interrupt handler should have handled this before we could see it
  if ((_tx_buffer_head == _tx_buffer_tail) && (*_ucsra & _BV(UDRE0))) {
    *_udr = c;
  } else {
    // Head and tail usage reversed?  Normally write to tail and read from head???
    int i = (_tx_buffer_head + 1) % SERIAL_BUFFER_SIZE;

    // If the output buffer is full, there's nothing for it other than to 
    // wait for the interrupt handler to empty it a bit
    // ???: return 0 here instead?
    while (i == _tx_buffer_tail)
      ;

    _tx_buffer[_tx_buffer_head] = c;
    _tx_buffer_head = i;

    // Now see if our UDREI interrupt is enabled.
    if (!(*_ucsrb & _BV(UDRIE0))) {
      // The interrupt is not yet enabled.  see if we need to feed it
      // the first character.
      if ((_tx_buffer_head != _tx_buffer_tail) && (*_ucsra & _BV(UDRE0))) {
        c = _tx_buffer[_tx_buffer_tail];
        _tx_buffer_tail = (_tx_buffer_tail + 1) % SERIAL_BUFFER_SIZE;
        *_udr = c;
      }
      // Then enable the interrupt
      sbi(*_ucsrb, _udrie);
    }  
  }      

  transmitting = true;
  return 1;
}

So far I have not had an issue with this updated code. Also from the logic Analyzer of the AX-12 buss, the output of a 9 byte packet went from taking about 144.5us to about 87us.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type: DuplicateAnother item already exists for this topic

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions