Перейти к содержанию

Почему Serial.readString() ждёт целую секунду?

Обновлено Редакция GAW

Serial на Arduino — объект класса HardwareSerial, который наследует Stream. Все «удобные» функции чтения — readString(), readStringUntil(), readBytes(), parseInt(), parseFloat() — живут в Stream и ждут каждый следующий байт не дольше таймаута. В конструкторе Stream() ядра 1.8.8 он равен 1000 мс.

Когда возвращаются функции чтения

ФункцияКогда возвращает результат
readString()только когда новый байт не пришёл за таймаут — всегда не раньше чем через 1000 мс после последнего символа
readStringUntil('\n')сразу по символу конца строки; если его нет — по таймауту
readBytes(buf, n)когда принято n байт или вышел таймаут
parseInt(), parseFloat()когда после числа пришёл нецифровой символ или вышел таймаут
Serial.read()сразу: байт из буфера или −1

Отсюда секундная задержка: у readString() нет признака конца, и она всегда дожидается тишины. Монитор порта Arduino IDE умеет дописывать перевод строки, поэтому чаще всего достаточно заменить вызов на readStringUntil('\n') и включить этот режим.

Приём строки без ожидания

char line[32];
uint8_t len = 0;

void loop() {
  while (Serial.available()) {          // take what is already in the buffer
    char c = Serial.read();
    if (c == '\n') {                    // end of line: handle it
      line[len] = '\0';
      handleCommand(line);
      len = 0;
    } else if (c != '\r' && len < sizeof(line) - 1) {
      line[len++] = c;                  // drop characters beyond the buffer
    }
  }
  // the rest of loop() keeps running between characters
}

Такой разбор не блокирует loop() и не создаёт объектов String, которые на ATmega328P с его 2 КБ ОЗУ фрагментируют кучу. Как байты попадают в 64-байтный кольцевой буфер и почему он теряет данные, разобрано в уроке «Serial изнутри» и статье «Кольцевой буфер для UART». Обратная сторона — почему тормозит передача — в ответе «Serial.print замедляет программу».

Похожие вопросы

Какой таймаут ставить вместо 1000 мс?

Чуть больше паузы между символами одной посылки. На 9600 бит/с байт с одним стоп-битом передаётся около 1 мс, на 115 200 — около 87 мкс, так что при непрерывной посылке хватает 10–50 мс. Если отправитель делает паузы внутри строки — например, человек печатает в мониторе порта с отправкой по символу, — маленький таймаут разрежет строку.

Почему parseInt() возвращает 0?

parseInt() ждёт цифру тем же таймаутом и, не дождавшись, возвращает 0. Так же выглядит и честный ноль в данных, поэтому отличать их приходится по Serial.available() до вызова или переходить на собственный разбор.

Может ли readString() потерять данные?

Приёмный буфер она вычитывает сама, так что переполнить его ей сложно. Риск в другом: каждый символ дописывается в объект String, и при этом буфер строки перевыделяется через realloc ровно под новую длину. У ATmega328P всего 2 КБ ОЗУ; если памяти не хватит, concat() по исходнику WString.cpp вернёт 0, и символ молча пропадёт. Для длинных посылок надёжнее свой массив фиксированного размера.

Источники

  1. Arduino. ArduinoCore-avr 1.8.8: cores/arduino/Stream.h, Stream.cpp — Stream.h: конструктор Stream() { _timeout = 1000; }, комментарий — число миллисекунд ожидания следующего символа; Stream.cpp: timedRead() ждёт байт, пока millis() − _startMillis < _timeout, и возвращает −1 по истечении; readString() читает timedRead() до −1; readStringUntil(terminator) — до −1 или до символа-терминатора, сам терминатор не включает; parseInt() и parseFloat() пропускают символы и ждут через тот же timedPeek(); setTimeout(timeout) меняет _timeout
  2. Arduino. ArduinoCore-avr 1.8.8: cores/arduino/WString.cpp — concat(cstr, length) вызывает reserve(newlen); reserve → changeBuffer: realloc(buffer, maxStrLen + 1) ровно под новую длину; если realloc не удался, reserve и concat возвращают 0, и строка не меняется; concat(char c) дописывает один символ через тот же путь
  3. Arduino. ArduinoCore-avr 1.8.8: cores/arduino/HardwareSerial.h — HardwareSerial наследует Stream; SERIAL_RX_BUFFER_SIZE — 64 байта при ОЗУ не меньше 1 КБ
  4. Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet (DS40002061B) — Features: 512/1K/1K/2K байт встроенной SRAM для ATmega48/88/168/328 — у ATmega328P 2 КБ

Нашли ошибку?