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

Почему Serial.print замедляет программу Arduino?

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

Serial.print() почти не отправляет байты сам: только если буфер пуст и регистр данных UART свободен, байт сразу пишется в регистр UDR, а все остальные он кладёт в кольцевой буфер, откуда их по одному отправляет обработчик прерывания UART (HardwareSerial.cpp). У ядра Arduino для AVR этот буфер — 64 байта (16 у микроконтроллеров с ОЗУ меньше 1 КБ). Пока место есть, вызов почти мгновенный. Когда буфер полон, HardwareSerial::write() крутится в цикле, пока прерывание не освободит место: дальше программа идёт со скоростью линии.

Сколько это в миллисекундах

Кадр UART 8N1 — старт-бит, 8 бит данных и стоп-бит, всего 10 бит на байт (даташит ATmega328P, разд. 20.4). Наш расчёт:

tбайта=10скорость, бод,9600 бод⇒1,04 мсt_\text{байта} = \frac{10}{\text{скорость, бод}}, \qquad 9600\ \text{бод} \Rightarrow 1{,}04\ \text{мс}
СкоростьБайт в секундуСтрока 30 байтПолный буфер 64 байта
9600 бод96031 мс67 мс
115 200 бод11 5202,6 мс5,6 мс

Если loop() печатает строку в 30 байт каждые 10 мс, программа хочет отправлять 3000 байт в секунду. На 9600 бод линия пропускает 960, буфер заполняется за несколько проходов, и каждый проход затем длится около 31 мс вместо 10. Другие скорости и форматы кадра посчитает калькулятор кадра UART, а полезную скорость с учётом пауз — калькулятор полезной скорости.

Что делать

  • Поднять скорость — например, до 115 200 бод, если монитор порта или программа на компьютере её поддерживает.
  • Печатать реже и короче — по таймеру millis(), без длинных подписей.
  • Не ждать — проверять Serial.availableForWrite() перед печатью.

Почему в кадре 10 бит, а не 8, объясняет словарная статья «Старт-бит и стоп-бит», а сам интерфейс — статья «UART».

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

Поможет ли увеличить скорость порта?

Да, если обе стороны её поддерживают: на 115 200 бод 8N1 проходит до 11 520 байт в секунду, и та же строка из 30 байт уходит за 2,6 мс вместо 31 мс на 9600 (наш расчёт). Монитор порта или программа на компьютере должны стоять на той же скорости.

Как печатать, не останавливая программу?

Проверять свободное место функцией Serial.availableForWrite() и печатать только то, что поместится, а остальное пропускать или откладывать. Ещё проще печатать реже — например, раз в 100 мс по millis(), а не в каждом проходе loop().

Можно ли вызывать Serial.print в прерывании?

Опасно: при полном буфере и запрещённых прерываниях ядро Arduino само опрашивает флаг регистра данных, чтобы не зависнуть, но обработчик прерывания будет ждать отправки байтов, и всё остальное встанет на это время. В обработчике лучше выставить флаг, а печатать в loop() (наш вывод).

Источники

  1. Arduino. ArduinoCore-avr 1.8.6: cores/arduino/HardwareSerial.h и HardwareSerial.cpp — HardwareSerial.h: SERIAL_TX_BUFFER_SIZE — 16 байт, если ОЗУ меньше 1023 байт, иначе 64; HardwareSerial::write(): если буфер и регистр данных пусты, байт сразу пишется в UDR, иначе кладётся в кольцевой буфер, а при полном буфере функция ждёт в цикле, пока обработчик прерывания освободит место; availableForWrite() — сколько байт можно записать без ожидания
  2. Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet, DS40002061B, 2020 — разд. 20.4 Frame Formats: кадр — 1 старт-бит, 5–9 бит данных, необязательный бит чётности, 1 или 2 стоп-бита

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