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


19

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

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

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

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

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


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);
}

Что если я использую 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);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.