Почему 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). Наш расчёт:
| Скорость | Байт в секунду | Строка 30 байт | Полный буфер 64 байта |
|---|---|---|---|
| 9600 бод | 960 | 31 мс | 67 мс |
| 115 200 бод | 11 520 | 2,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() (наш вывод).
Источники
- 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() — сколько байт можно записать без ожидания
- Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet, DS40002061B, 2020 — разд. 20.4 Frame Formats: кадр — 1 старт-бит, 5–9 бит данных, необязательный бит чётности, 1 или 2 стоп-бита
Нашли ошибку?
Смотрите также
- Кадр UART по битамБиты кадра, чётность, время символа, разбор и расхождение скоростей
- Полезная скорость интерфейсовБиты на байт, кадры в секунду и доля данных для UART, SPI, I²C, CAN, Modbus и USB
- UARTКадр 8N1, скорость и допуск, TX и RX, уровни, USB–UART, AVR и STM32
- Скорость передачи (бод)Бит/с и бод: стандартные скорости и допуск UART
- Старт-бит и стоп-битСлужебные биты кадра UART для синхронизации