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

19

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

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

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

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

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

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

Ответы:

19

Я получил первый метод с этой страницы, работающий на моем 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)

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

Я использовал 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);
}
cp.engr
источник
Что если я использую Makefile, а не IDE? Чего-то не хватает для работы в моем проекте Makefile ... Может кто-нибудь помочь?
Теди
@ Теди, ты используешь GCC? Это специфичный для компилятора ответ. Что вы пробовали и каковы были результаты?
cp.engr
Да, я использую GCC. Я нашел проблему. Была вызвана функция _write (). Проблема была в моем коде для отправки строки. Я неправильно использовал RTT-библиотеку Segger, но теперь работает нормально. Благодарю. Все работает сейчас.
Теди
4

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

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

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

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

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

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

Ответ @ AdamHaun - это все, что вам нужно, с помощью sprintf()которого легко создать строку и затем отправить ее. Но если вы действительно хотите printf()свою собственную функцию, то Variable Argument Functions (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 Utility. И используйте Printf через SWO viewer , вот руководство по ST-Link Utility , соответствующая часть начинается на странице 31.

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

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

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

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

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

Для тех, кто борется, добавьте следующее в 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-порту через замазку, и все будет хорошо.

Майкл Браун
источник
1

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

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
LeoDJ
источник