Почему 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 — одним вызовом на printf | setvbuf(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.
Источники
- ISO/IEC 9899:201x (C11), Committee Draft N1570, April 2011 — 7.21.3p3 — небуферизованный, полностью и построчно буферизованный поток: при построчной буферизации символы передаются блоком, когда встречается перевод строки или заполнен буфер; p7 — stdout полностью буферизуется, только если известно, что он не связан с интерактивным устройством; 7.21.5.6 — setvbuf с режимами _IOFBF, _IOLBF, _IONBF вызывается до любых других операций с потоком
- newlib. libc/stdio/makebuf.c (sourceware.org, ветка main) — __smakebuf_r: если по fstat поток — символьное устройство и _isatty_r возвращает истину, поток получает флаг построчной буферизации __SLBF
- The Red Hat newlib C Library. System Calls; fflush; setvbuf (sourceware.org/newlib/libc.html) — минимальная реализация isatty, предложенная для систем с выводом только в stdout, возвращает 1; fflush — выталкивает буферизованный вывод; setvbuf — задаёт режим буферизации
- newlib. libc/stdio/vfprintf.c и libc/stdio/nano-vfprintf.c (sourceware.org, ветка main) — vfprintf.c: для небуферизованного потока на запись вызывается __sbprintf — вывод собирается во временный буфер BUFSIZ и передаётся одним блоком; nano-vfprintf.c: символы выводятся по одному через __sfputc_r
- AVR-LibC 2.3.2: include/stdio.h — fflush() оставлена только для совместимости: реализация стандартного ввода-вывода сейчас не выполняет никакой буферизации
Нашли ошибку?
Смотрите также
- Логирование и трассировкаprintf через UART, ITM и SWO, SEGGER RTT, уровни журнала, метки времени
- SWO (вывод трассировки)Однопроводный вывод трассировки и отладочных сообщений Cortex-M
- STM32CubeIDEСреда ST для STM32: проект, сборка, отладка ST-LINK, SWV и ошибки
- Кольцевой буфер UARTБуфер SPSC с маской, ISR приёма и передачи на AVR и STM32, ORE и DOR, размер