Как вы реализуете printf в GCC из Newlib?

#c #gcc #embedded #printf #newlib

#c #gcc #встроенный #printf #newlib

Вопрос:

Я изо всех сил пытаюсь правильно реализовать printf из newlib в моем esp32, используя GCC.

Я просмотрел документацию newlib, и она дает мне общую информацию о том, как вызывается printf, но не объясняет мне внутреннюю реализацию.

Основываясь на моем текущем исследовании, я определил, что printf выводит форматированную строку в стандартный вывод. На ПК мне было проще понять это, потому что есть окно консоли, которое будет отображать форматированный вывод из printf, однако во встроенной системе я понимаю, что вы должны указать библиотеке, куда перенаправить форматированный вывод из printf, и это то, что я пытаюсь выяснить.

Опять же, основываясь на своих исследованиях, я пришел к пониманию, что для выполнения этого требуются некоторые функции, в частности функция _write .

Мне очень сложно понять, как преодолеть разрыв между printf и использованием _write функции. Я надеюсь, что кто-нибудь здесь может помочь мне понять, как правильно реализовать printf.

И если я пропустил какую-то документацию, которая четко объясняет это, то, пожалуйста, перенаправьте меня на это. Я пытался прочитать документацию newlib, а также документацию, связанную с GCC, но на самом деле нигде не упоминается, как использовать printf, но есть много документации о том, как вызвать printf и отформатировать строку, но эта часть проста. Мне нужно знать, как получить форматированную строку из стандартного вывода MCU.

Спасибо всем!

Комментарии:

1. Вы можете найти реализации printf() и его родственников большей или меньшей сложности в Интернете. Если вы обнаружите, int printf(const char * restrict fmt, ...) { va_list args; va_start(args, fmt); int rc = vfprintf(stdout, fmt, args); va_end(args); return rc; } значит, у вас есть функция ретрансляции, и вам нужно найти реализацию vfprintf() вместо этого. Но это тоже доступно…

2.@JonathanLeffler : За исключением случаев, когда используется Newlib, printf() реализация уже существует. Вопрос, я думаю, должен быть о том, как перенастроить Newlib, чтобы его printf() работало, а не как реализовать printf

Ответ №1:

В Newlib вы не реализуете printf() то, что включено в библиотеку. Вы просто реализуете минимальный набор системных вызовов для поддержки библиотеки. API sycalls для потоковых устройств состоит из open , close , read и write (или реентерабельных версий с _r суффиксом) — повторный вход в этом полезен, если вы используете многопоточность и нуждаетесь в каждом потоке errno (среди любых требований к повторному входу, специфичных для конкретной реализации).

Если все, что вы реализуете, это stdout (поток, используемый printf() , putchar() puts() и т.д.) И поддерживаете только одно устройство (обычно UART) и не беспокоитесь о возможности перенаправления или повторного входа, тогда open , close и read могут быть пустыми, и write вы можете просто вывести предоставленный буфер непосредственно в ваш низкоуровневый API последовательного ввода-вывода:

 int _write(int handle, char *data, int size ) 
{
    int count ;

    handle = handle ; // unused

    for( count = 0; count < size; count  ) 
    {
        outputByte( data[count] ) ;  // Your low-level output function here.
    }

    return count;
}
  

Обратите внимание, что handle здесь не используется. Для stdout это будет 1 ( stdin = 0 и stderr = 2). handle Аргумент можно было бы использовать, если вам нужны отдельные устройства вывода для stdout и stderr или если вы поддерживаете дополнительные устройства или файловую систему и перенаправление fopen или stdout . Он используется для идентификации steam, открытого open . Игнорируя это, весь потоковый вывод (такой, который fprintf() будет обрабатываться одинаковым образом и выводиться на одно и то же устройство); во многих случаях (где printf() это просто средство получения отладочных выходных данных или ваше приложение не имеет файловой системы, вам будет все равно.

Учитывая эту write функцию, printf() будет «просто работать» (максимально простым способом), потому что под капотом вызываются все функции вывода stdio write ). Рекомендуется использовать низкоуровневую функцию вывода, которая является буферизованной и неблокирующей (например, драйвер UART, управляемый прерываниями).

Очевидно, что если вы тоже хотите принимать ввод на stdin , вы бы реализовали аналогичную read функцию.

Если вам нужна куча ( malloc() и т.д.), вам также нужно будет реализовать sbrk / sbrk_r . Я бы посоветовал вам, по крайней мере, реализовать это, если ничего другого в системных вызовах Newlib нет.

Более сложный подход к реализации системных вызовов обсуждается Биллом Гейтлиффом в разделе «Перенос и использование Newlib во встраиваемых системах», в то время как базовая реализация обсуждается здесь, в то время как примеры минимальной реализации, аналогичные приведенным выше, приведены в самой документации Newlib.