работа Serial.flush()

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Коллеги.

Всегда считал, что Serial.flush() работает некорректно. Но закрались сомнения в собственной компетентности. Я не эксперт в AVR, однако вот мои соображения.

Текст Serial.flush() (выглядит слегка обфусцированным почему-то):

void HardwareSerial::flush()
{
  // If we have never written a byte, no need to flush. This special
  // case is needed since there is no way to force the TXC (transmit
  // complete) bit to 1 during initialization
  if (!_written)
    return;

  while (bit_is_set(*_ucsrb, UDRIE0) || bit_is_clear(*_ucsra, TXC0)) {
    if (bit_is_clear(SREG, SREG_I) && bit_is_set(*_ucsrb, UDRIE0))
	// Interrupts are globally disabled, but the DR empty
	// interrupt should be enabled, so poll the DR empty flag to
	// prevent deadlock
	if (bit_is_set(*_ucsra, UDRE0))
	  _tx_udr_empty_irq();
  }
  // If we get here, nothing is queued anymore (DRIE is disabled) and
  // the hardware finished tranmission (TXC is set).
}

Текст прерывания по опустошению UDR (другие прерывания по концу передачи в HardwareSerial не используются):

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);
  }
}

Я вижу так, что Serial.flush() выйдет тогда, когда уйдёт последний байт из UDR, но не когда уйдёт последний байт из сдвигового регистра (по TXC == 1). То есть Serial.flush() работает некорректно. Разубедите, если это не так.

asam
asam аватар
Offline
Зарегистрирован: 12.12.2018

Schwarz78 пишет:

Всегда считал, что Serial.flush() работает некорректно. Но закрались сомнения в собственной компетентности. Я не эксперт в AVR, однако вот мои соображения.

Текст Serial.flush() (выглядит слегка обфусцированным почему-то):

void HardwareSerial::flush()
{
...

  while (bit_is_set(*_ucsrb, UDRIE0) || bit_is_clear(*_ucsra, TXC0)) {
....
}

 

Я вижу так, что Serial.flush() выйдет тогда, когда уйдёт последний байт из UDR, но не когда уйдёт последний байт из сдвигового регистра (по TXC == 1). То есть Serial.flush() работает некорректно. Разубедите, если это не так.

 

Насколько я понял именно когда уйдет последний бит из сдвигового регистра.

Из даташита

TXCn: USART Transmit Complete This flag bit is set when the entire frame in the Transmit Shift Register has been shifted out and there are no new data currently present in the transmit buffer (UDRn).

 

А в чем некорректность работы serial.flash()?

rkit
Offline
Зарегистрирован: 23.11.2016

flush выйдет тогда, когда освободится буфер отправки, (_tx_buffer приравняется _tx_buffer_tail), что вполне понятно из приведенного кода.

Schwarz78
Offline
Зарегистрирован: 19.01.2019

rkit пишет:

flush выйдет тогда, когда освободится буфер отправки, (_tx_buffer приравняется _tx_buffer_tail), что вполне понятно из приведенного кода.

Это не так. Точнее - именно так. И это неправильно, если не понимаете, почему - не ко мне.

Schwarz78
Offline
Зарегистрирован: 19.01.2019

asam пишет:

Насколько я понял именно когда уйдет последний бит из сдвигового регистра.

Из даташита

TXCn: USART Transmit Complete This flag bit is set when the entire frame in the Transmit Shift Register has been shifted out and there are no new data currently present in the transmit buffer (UDRn).

 

А в чем некорректность работы serial.flash()?

В том, что flush выйдет, когда сработает последнее прерывание по UDR. Дело не в даташите, а в логике тех, кто писал HardwareSerial. Я долго на это смотрел, но вот я не вижу выхода flush по окончании передачи, а вижу выход по последнему прерыванию по опустошению UDR.

asam
asam аватар
Offline
Зарегистрирован: 12.12.2018

Schwarz78 пишет:

В том, что flush выйдет, когда сработает последнее прерывание по UDR. Дело не в даташите, а в логике тех, кто писал HardwareSerial. Я долго на это смотрел, но вот я не вижу выхода flush по окончании передачи, а вижу выход по последнему прерыванию по опустошению UDR.

Выход из flush происходит когда выставляетс флаг TXC0. А происходит это когда the entire frame in the Transmit Shift Register has been shifted out and there are no new data currently present in the transmit buffer (UDRn).

 

Schwarz78
Offline
Зарегистрирован: 19.01.2019

asam пишет:

Выход из flush происходит когда выставляетс флаг TXC0. А происходит это когда the entire frame in the Transmit Shift Register has been shifted out and there are no new data currently present in the transmit buffer (UDRn).

Вы посмотрите, какое условие там первое по или. Ну и что произойдёт раньше? UDRIE сбросится или TXC встанет? Ага?

asam
asam аватар
Offline
Зарегистрирован: 12.12.2018

Schwarz78 пишет:

Вы посмотрите, какое условие там первое по или. Ну и что произойдёт раньше? UDRIE сбросится или TXC встанет? Ага?

UDRIE это просто разрешение прерыванией по опустошении.Ты путаешь с UDRE

UDRIEn: USART Data Register Empty Interrupt Enable 

UDREn: USART Data Register Empty

 

Там же в комментарии сказано

// interrupt should be enabled, so poll the DR empty flag to

  prevent deadlock

 

 

Schwarz78
Offline
Зарегистрирован: 19.01.2019

asam пишет:

UDRIE это просто разрешение прерыванией по опустошении.Ты путаешь с UDRE

UDRIEn: USART Data Register Empty Interrupt Enable 

UDREn: USART Data Register Empty

Вот это не смущает? Я же не зря привёл текст прерывания.

if (_tx_buffer_head == _tx_buffer_tail) {
  // Buffer empty, so disable interrupts
  cbi(*_ucsrb, UDRIE0);

И, конечно я читал комментарии. Я не просто так сказал, что давно смотрю на эту шнягу.

Schwarz78
Offline
Зарегистрирован: 19.01.2019

asam пишет:

UDRIE это просто разрешение прерыванией по опустошении.Ты путаешь с UDRE

UDRIEn: USART Data Register Empty Interrupt Enable 

UDREn: USART Data Register Empty

Вам сидеть и смотреть пару дней, пытаясь понять логику работы этой библиотеки. Когда точно сможете сказать в чём я неправ - тогда пишите. Не забудьте логику библиотеки увязать с работой контроллера, которая описана чуть более, чем конкретно производителем.

asam
asam аватар
Offline
Зарегистрирован: 12.12.2018

Ну да, согласен. Выйдет по опустошению data register. Для меня это вообще загадочная функция. По названию должна буфер очищать, а не ждать пока все передастся. 

Schwarz78
Offline
Зарегистрирован: 19.01.2019

asam пишет:

Ну да, согласен. Выйдет по опустошению data register. Для меня это вообще загадочная функция. По названию должна буфер очищать, а не ждать пока все передастся. 

Так я именно так и думал, но во многих библиотеках она используется для переключения RS-485 на приём. А это рано.

asam
asam аватар
Offline
Зарегистрирован: 12.12.2018

Факт. Было бы логичнее добавить для этих случаев функцию вроде WaitForEndOfTransmission и ждать пока освободится буфер и  выставится TXCn

sadman41
Offline
Зарегистрирован: 19.10.2016

flush() в библиотеке для эзернета тоже используется для выталкивания в сетевой чип всего, что еще болтается в памяти МК. Так что это, видать, устоявшаяся практика - считать, что буфер "почищен", когда избавились от данных на своей стороне.

rkit
Offline
Зарегистрирован: 23.11.2016

Schwarz78 пишет:

Это не так. Точнее - именно так. И это неправильно, если не понимаете, почему - не ко мне.

Пришел арбитр правильности и решил, что общепринятое поведение теперь неправильно, и идите все нафиг если не согласны.

Schwarz78
Offline
Зарегистрирован: 19.01.2019

rkit пишет:

Пришел арбитр правильности и решил, что общепринятое поведение теперь неправильно, и идите все нафиг если не согласны.

Если бы не эти строчки в HardwareSerial.cpp я бы ничего не имел против такого поведения:

  // 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.

Ну и описание с оф. сайта:

Waits for the transmission of outgoing serial data to complete.

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Собирался спросить "а что не так?", но увидел

Schwarz78 пишет:

И это неправильно, если не понимаете, почему - не ко мне.

и понял, что это не к Вам.

rkit
Offline
Зарегистрирован: 23.11.2016

Schwarz78 пишет:

rkit пишет:

Пришел арбитр правильности и решил, что общепринятое поведение теперь неправильно, и идите все нафиг если не согласны.

Если бы не эти строчки в HardwareSerial.cpp я бы ничего не имел против такого поведения:

  // 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.

Ну и описание с оф. сайта:

Waits for the transmission of outgoing serial data to complete.

 

Всё верно. Комментарий подходит к строчке, которую он комментирует, и поведение процедуры соответствует описанному.

Schwarz78
Offline
Зарегистрирован: 19.01.2019

rkit пишет:

Всё верно. Комментарий подходит к строчке, которую он комментирует, и поведение процедуры соответствует описанному.

Не соответствует. Одному вроде уже объяснил, тут второй нарисовался.

Schwarz78
Offline
Зарегистрирован: 19.01.2019

.

Schwarz78
Offline
Зарегистрирован: 19.01.2019

ЕвгенийП пишет:

Собирался спросить "а что не так?", но увидел

Schwarz78 пишет:

И это неправильно, если не понимаете, почему - не ко мне.

и понял, что это не к Вам.

Не ко мне. Вам это просто не нужно.

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Короче говоря, товарищи. Если кто хочет доказать, что Serial.flush() корректно выходит по окончании передачи, как написано в описании на сайте ардуино.цц и в комментариях в библиотеке - добро пожаловать. Кто хочет пивным авторитетом пугать неокрепших юношей - проходите мимо.

А кто хочет сказать, что от Serial.flush() нечего было ожидать иного, кроме ожидания очистки буфера передачи - те уже сказали, и я в том числе.

SoftFelix
Offline
Зарегистрирован: 12.01.2020

"Serial.flush(); // ждем окончания передачи"

Тоже не работает, как описано!

Программа (Ардуино) отсылала на скорости 115200 другому устройству пакеты по 256 байт (большой массив, поделённый на такой размер). После отсылки пакета ( читаем с SD в буфер и далее из него Serial.write(); ) ставилось Serial.flush(); На втором пакете сбивалась синхронизация на той стороне. Заработало только после того, как Serial.flush(); заменил на банальную паузу delay(200); между пакетами.

Ворота
Ворота аватар
Offline
Зарегистрирован: 10.01.2016

SoftFelix пишет:

"Serial.flush(); // ждем окончания передачи"

Тоже не работает, как описано!

Кашмар!

И куда смотрит правительство?