Как использовать функцию printf на STM32?

Я пытаюсь понять, как использовать функцию printf для печати на последовательный порт.

Моя текущая установка — это сгенерированный код STM32CubeMX и SystemWorkbench32 с платой обнаружения STM32F407 .

Я вижу в stdio.h, что прототип printf определяется как:

int _EXFUN(printf, (const char *__restrict, ...)
               _ATTRIBUTE ((__format__ (__printf__, 1, 2))));

Что это означает? Где находится точное местоположение определения этой функции? Каков был бы общий смысл выяснения того, как использовать эту функцию для вывода?

Я думаю, вам нужно написать свой собственный «int _write(int file, char *ptr, int len)», чтобы отправить стандартный вывод на ваш последовательный порт, например здесь . Я считаю, что обычно это делается в файле Syscalls.c, который обрабатывает «Переназначение системных вызовов». Попробуйте погуглить "Syscalls.c".
Вы хотите выполнять отладку printf через полухостинг (через отладчик) или просто printf в целом?
Как сказал @Tut, _write()функция - это то, что я сделал. Подробности в моем ответе ниже.
это был отличный урок: youtu.be/Oc58Ikj-lNI

Ответы (6)

Я получил первый метод с этой страницы, работающий на моем STM32F072.

http://www.openstm32.org/forumthread1055

Как там сказано,

Я заставил работать printf (и все другие консольно-ориентированные функции stdio) путем создания пользовательских реализаций низкоуровневых функций ввода-вывода, таких как _read()и _write().

Библиотека GCC C вызывает следующие функции для выполнения низкоуровневого ввода-вывода:

int _read(int file, char *data, int len)
int _write(int file, char *data, int len)
int _close(int file)
int _lseek(int file, int ptr, int dir)
int _fstat(int file, struct stat *st)
int _isatty(int file)

Эти функции реализованы в библиотеке GCC C как подпрограммы-заглушки со "слабой" связью. Если объявление какой-либо из вышеперечисленных функций появляется в вашем собственном коде, ваша подпрограмма-заменитель переопределит объявление в библиотеке и будет использоваться вместо стандартной (нефункциональной) процедуры.

Я использовал STM32CubeMX для настройки USART1 ( huart1) в качестве последовательного порта. Поскольку я хотел только printf(), мне нужно было только заполнить _write()функцию, что я сделал следующим образом. Это условно содержится в syscalls.c.

#include  <errno.h>
#include  <sys/unistd.h> // STDOUT_FILENO, STDERR_FILENO

int _write(int file, char *data, int len)
{
   if ((file != STDOUT_FILENO) && (file != STDERR_FILENO))
   {
      errno = EBADF;
      return -1;
   }

   // arbitrary timeout 1000
   HAL_StatusTypeDef status =
      HAL_UART_Transmit(&huart1, (uint8_t*)data, len, 1000);

   // return # of bytes written - as best we can tell
   return (status == HAL_OK ? len : 0);
}
Что, если я использую Makefile, а не IDE? Чего-то не хватает для работы в моем проекте Makefile... Может ли кто-нибудь помочь?
@Tedi, ты используешь GCC? Это специфичный для компилятора ответ. Что вы пробовали, и каковы были результаты?
Да, я использую GCC. Я нашел проблему. Была вызвана функция _write(). Проблема была в моем коде для отправки строки. Я неправильно использовал библиотеку RTT Сеггера, но теперь она работает нормально. Спасибо. Все работает сейчас.

Ответ @AdamHaun - это все, что вам нужно, sprintf()так как легко создать строку, а затем отправить ее. Но если вам действительно нужна собственная printf()функция, тогда вам подойдут функции с переменными аргументами (va_list) .

С va_listпользовательской функцией печати выглядит следующим образом:

#include <stdio.h>
#include <stdarg.h>
#include <string.h>

void vprint(const char *fmt, va_list argp)
{
    char string[200];
    if(0 < vsprintf(string,fmt,argp)) // build string
    {
        HAL_UART_Transmit(&huart1, (uint8_t*)string, strlen(string), 0xffffff); // send message via UART
    }
}

void my_printf(const char *fmt, ...) // custom printf() function
{
    va_list argp;
    va_start(argp, fmt);
    vprint(fmt, argp);
    va_end(argp);
}

Пример использования:

uint16_t year = 2015;
uint8_t month = 12;
uint8_t day   = 18;
char* date = "date";

// "Today's date: 2015-12-18"
my_printf("Today's %s: %d-%d-%d\r\n", date, year, month, day);

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


Другой вариант, и, вероятно, лучший вариант — использовать отладчик ST-Link, SWD вместе с утилитой ST-Link. И используйте Printf через программу просмотра SWO , вот руководство по утилите ST-Link , соответствующая часть начинается на странице 31.

Printf через SWO Viewer отображает данные printf, отправленные от цели через SWO. Он позволяет отображать некоторую полезную информацию о работающей прошивке.

вы не используете va_arg, как вы проходите через переменные аргументы?

_EXFUN — это макрос, вероятно, содержащий некоторые интересные директивы, которые сообщают компилятору, что он должен проверить строку формата на совместимость с printf и убедиться, что аргументы для printf соответствуют строке формата.

Чтобы узнать, как использовать printf, я предлагаю справочную страницу и еще немного поиска в Google. Напишите несколько простых программ на языке C, использующих printf, и запустите их на своем компьютере, прежде чем пытаться перейти на их использование на микропроцессоре.

Интересным будет вопрос «куда девается печатный текст?». В unix-подобной системе он переходит в «стандартный выход», но в микроконтроллере такого нет. Библиотеки отладки CMSIS могут отправлять текст printf в порт отладки полухостинга arm, то есть в ваш сеанс gdb или openocd, но я понятия не имею, что будет делать SystemWorkbench32.

Если вы не работаете в отладчике, может иметь смысл использовать sprintf для форматирования строк, которые вы хотите напечатать, а затем отправить эти строки через последовательный порт или на любой другой дисплей, который вы могли подключить.

Осторожно: printf и связанный с ним код очень велики. Это, вероятно, не имеет большого значения для 32F407, но это реальная проблема для устройств с небольшой флэш-памятью.

IIRC _EXFUN помечает функцию для экспорта, т. е. для использования в пользовательском коде, и определяет соглашение о вызовах. На большинстве (встроенных) платформ можно использовать соглашение о вызовах по умолчанию. Но на x86 с динамическими библиотеками вам может понадобиться определить функцию, __cdeclчтобы избежать ошибок. По крайней мере, в newlib/cygwin _EXFUN используется только для установки __cdecl.

Если вам нужен другой подход и вы не хотите перезаписывать функции или объявлять свои собственные, вы можете просто использовать snprintfдля достижения той же цели. Но не так элегантно.

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
Это очень просто и позволяет повторно использовать буфер для tx dma.

printf() (обычно) является частью стандартной библиотеки C. Если ваша версия библиотеки поставляется с исходным кодом, вы можете найти там реализацию.

Вероятно, было бы проще использовать sprintf() для генерации строки, а затем использовать другую функцию для отправки строки через последовательный порт. Таким образом, все сложное форматирование выполняется за вас, и вам не нужно ломать стандартную библиотеку.

printf() обычно является частью библиотеки, да, но она ничего не может сделать , пока кто-то не настроит базовую возможность вывода на нужное оборудование. Это не «взлом» библиотеки, а использование ее по назначению.
Он может быть предварительно настроен для отправки текста через соединение отладчика, как предложили Бенс и Уильям в своих ответах. (Это, конечно, не то, чего хотел вопрошающий.)
Обычно это происходит только в результате кода, который вы решили связать для поддержки вашей платы, но независимо от того, вы измените это, определив свою собственную функцию вывода. Проблема с вашим предложением заключается в том, что оно не основано на понимании того, как должна использоваться библиотека, а не на том, что вы предлагаете многоэтапный процесс для каждого вывода, который, среди прочего, испортит любой существующий портативный код, который делает все как обычно.
STM32 — это автономная среда. Компилятору не нужно предоставлять stdio.h — это совершенно необязательно. Но если он предоставляет stdio.h, он должен предоставить всю библиотеку. Автономные системные компиляторы, которые реализуют printf, как правило, делают это как связь UART.

Для тех, кто испытывает затруднения, добавьте в syscalls.c следующее:

extern UART_HandleTypeDef huart1; // access huart1 instance
//extern int __io_putchar(int ch) __attribute__((weak)); // comment this out

__attribute__((weak)) int __io_putchar(int ch)
{
    HAL_StatusTypeDef status = HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
    return (status == HAL_OK ? ch : 0);
}

Подключитесь к вашему COM-порту через шпатлевку, и все будет хорошо.