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

Почему printf на STM32 ничего не выводит, пока нет перевода строки?

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

printf из newlib — стандартной библиотеки GNU Arm и STM32CubeIDE — не пишет в UART сам. Он форматирует текст в поток stdout, а наружу символы уходят через системный вызов _write, который реализует проект. Между ними стоит буфер потока, и его режим объясняет «пропавший» вывод.

Почему stdout буферизуется построчно

Стандарт C в разделе 7.21.3 описывает три режима потока, а для stdout оставляет одно правило: полностью буферизовать его можно, только если известно, что он не связан с интерактивным устройством. newlib решает это при первом выводе в __smakebuf_r (файл makebuf.c): если _fstat называет поток символьным устройством, а _isatty отвечает «да», поток буферизуется построчно. Минимальная isatty, которую предлагает документация newlib, всегда возвращает 1.

РежимКогда символы попадают в _writeКак получить
построчный (_IOLBF)на символе перевода строки или при заполнении буферапо умолчанию при isatty = 1
полный (_IOFBF)только при заполнении буфера или fflushесли isatty вернул 0
без буфера (_IONBF)сразу: в newlib-nano по символу, в полной newlib — одним вызовом на printfsetvbuf(stdout, NULL, _IONBF, 0)

Поэтому printf("ADC=%d", v) в цикле ничего не показывает, а printf("ADC=%d\n", v) — показывает.

Три способа исправить

printf("x=%d\n", x);                 /* 1: end the line */

printf("progress...");
fflush(stdout);                      /* 2: flush explicitly */

int main(void) {
    setvbuf(stdout, NULL, _IONBF, 0); /* 3: before the first printf */
    /* ... */
}

Третий вариант вызывают до первой операции с потоком — так требует 7.21.5.6. Как направить _write в UART, ITM или RTT и сколько стоит каждый способ, разобрано в статье «Логирование в прошивке»; вывод через отладочный вывод ядра — в статье об SWO. Где лежит syscalls.c в проекте, показано в обзоре STM32CubeIDE, а передачу без ожидания — статья о кольцевом буфере для UART.

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

Почему через SWO то же самое?

Буферизация происходит в библиотеке до вызова _write, поэтому ей всё равно, куда _write потом отправляет символы — в UART, ITM или кольцевой буфер. Перевод строки, fflush или setvbuf помогают одинаково для любого канала.

Не замедлит ли _IONBF вывод?

Зависит от варианта библиотеки. В newlib-nano vfprintf выводит символы по одному через __sfputc_r, и без буфера _write вызывается на каждый символ. Полная newlib для небуферизованного потока собирает весь результат printf во временный буфер (__sbprintf) и вызывает _write один раз. Если _write кладёт байты в кольцевой буфер, проще оставить построчный режим и заканчивать строки символом \n.

На AVR с avr-libc printf ведёт себя так же?

По заголовку stdio.h avr-libc её стандартный ввод-вывод не выполняет никакой буферизации, и fflush() там оставлена только для совместимости: функция вывода потока вызывается на каждый символ, поэтому текст уходит сразу. Эффект с переводом строки характерен для newlib.

Источники

  1. ISO/IEC 9899:201x (C11), Committee Draft N1570, April 2011 — 7.21.3p3 — небуферизованный, полностью и построчно буферизованный поток: при построчной буферизации символы передаются блоком, когда встречается перевод строки или заполнен буфер; p7 — stdout полностью буферизуется, только если известно, что он не связан с интерактивным устройством; 7.21.5.6 — setvbuf с режимами _IOFBF, _IOLBF, _IONBF вызывается до любых других операций с потоком
  2. newlib. libc/stdio/makebuf.c (sourceware.org, ветка main) — __smakebuf_r: если по fstat поток — символьное устройство и _isatty_r возвращает истину, поток получает флаг построчной буферизации __SLBF
  3. The Red Hat newlib C Library. System Calls; fflush; setvbuf (sourceware.org/newlib/libc.html) — минимальная реализация isatty, предложенная для систем с выводом только в stdout, возвращает 1; fflush — выталкивает буферизованный вывод; setvbuf — задаёт режим буферизации
  4. newlib. libc/stdio/vfprintf.c и libc/stdio/nano-vfprintf.c (sourceware.org, ветка main) — vfprintf.c: для небуферизованного потока на запись вызывается __sbprintf — вывод собирается во временный буфер BUFSIZ и передаётся одним блоком; nano-vfprintf.c: символы выводятся по одному через __sfputc_r
  5. AVR-LibC 2.3.2: include/stdio.h — fflush() оставлена только для совместимости: реализация стандартного ввода-вывода сейчас не выполняет никакой буферизации

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