Як використовувати функцію 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, char * ptr, int len)", щоб надіслати стандартний вихід на ваш послідовний порт, наприклад, тут . Я вважаю, що це зазвичай робиться у файлі під назвою Syscalls.c, який обробляє "Перезавантаження системних викликів". Спробуйте Googling "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)

Ці функції реалізовані в бібліотеці 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? Це відповідь, що стосується компілятора. Що ви пробували, і які були результати?
cp.engr

Так, я використовую GCC. Я знайшов проблему. Була викликана функція _write (). Проблема була в моєму коді для надсилання рядка. Я неправильно використовував RTT-бібліотеку Сеггера, але зараз він працює чудово. Спасибі. Усі зараз працюють.
Теді

5

@ Відповідь Адама Хауна - все, що вам потрібно, з 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 Utility. І використовуйте Printf через переглядач SWO , ось посібник з утиліти ST-Link , відповідна частина починається на сторінці 31.

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


ви не використовуєте va_arg, як ви переглядаєте аргументи змінної?
iouzzr

4

_EXFUN - це макрос, ймовірно, містить деякі цікаві директиви, які вказують компілятору, що він повинен перевірити рядок формату на відповідність printf та гарантувати, що аргументи для printf відповідають рядку формату.

Щоб дізнатися, як використовувати printf, я пропоную сторінку man і трохи більше googling. Напишіть кілька простих програм на C, які використовують printf та запустіть їх на своєму комп’ютері, перш ніж спробувати перейти до використання на мікрофоні.

Цікавим питанням буде "куди йде друкований текст?". У системі, схожій на unix, вона переходить у "стандартний режим", але мікроконтролер такого не має. Бібліотеки налагодження CMSIS можуть надсилати printf текст до порту налагодження напівхостингу, тобто у ваш gdb або openocd сеанс, але я не знаю, що робитиме SystemWorkbench32.

Якщо ви не працюєте у відладчику, може бути більше сенсу використовувати sprintf для форматування рядків, які ви хочете надрукувати, а потім надіслати ці рядки через послідовний порт або на будь-який дисплей, який ви могли б долучити.

Остерігайтеся: printf та пов'язаний з ним код дуже великі. На 32F407 це, мабуть, не має великого значення, але це справжня проблема на пристроях із невеликою кількістю спалаху.


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

1

printf () є (як правило) частиною стандартної бібліотеки С. Якщо ваша версія бібліотеки постачається з вихідним кодом, ви можете знайти там реалізацію.

Можливо, було б простіше використовувати 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.