Jak korzystać z funkcji printf na STM32?

19

Próbuję wymyślić, jak używać funkcji printf do drukowania na porcie szeregowym.

Moja obecna konfiguracja to kod wygenerowany przez STM32CubeMX i SystemWorkbench32 z płytą wykrywającą STM32F407 .

Widzę w stdio.h, że prototyp printf jest zdefiniowany jako:

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

Co to znaczy? Gdzie jest dokładna lokalizacja tej definicji funkcji? Jaki byłby ogólny sens odkrywania, jak używać tego rodzaju funkcji do generowania wyników?

użytkownik505160
źródło
5
Myślę, że musisz napisać własne „int _write (int file, char * ptr, int len)”, aby wysłać standardowe wyjście na port szeregowy, taki jak tutaj . Wierzę, że zwykle dzieje się tak w pliku o nazwie Syscalls.c, który obsługuje „Mapowanie wywołań systemowych”. Spróbuj googling „Syscalls.c”.
Tut
1
To nie jest problem z elektroniką. Przejdź do instrukcji obsługi biblioteki kompilatora.
Olin Lathrop,
Czy chcesz debugować printf poprzez semihosting (przez debugger) czy po prostu printf w ogóle?
Daniel
Jak powiedział @Tut, _write()funkcja jest tym, co zrobiłem. Szczegóły w mojej odpowiedzi poniżej.
cp.engr
to był świetny samouczek: youtu.be/Oc58Ikj-lNI
Joseph Pighetti

Odpowiedzi:

19

Pierwsza metoda z tej strony działa na moim STM32F072.

http://www.openstm32.org/forumthread1055

Jak tam powiedziano

Sposób, w jaki działałem printf (i wszystkie inne zorientowane na konsolę funkcje stdio), polegał na tworzeniu niestandardowych implementacji funkcji We / Wy niskiego poziomu, takich jak _read()i _write().

Biblioteka GCC C wywołuje następujące funkcje w celu wykonywania operacji we / wy niskiego poziomu:

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)

Funkcje te są implementowane w bibliotece GCC C jako procedury pośredniczące z „słabym” łączeniem. Jeśli deklaracja którejkolwiek z powyższych funkcji pojawi się w twoim kodzie, twoja procedura zastępcza zastąpi deklarację w bibliotece i zostanie użyta zamiast domyślnej (niefunkcjonalnej) procedury.

Użyłem STM32CubeMX do skonfigurowania USART1 ( huart1) jako portu szeregowego. Ponieważ chciałem tylko printf(), potrzebowałem tylko wypełnić _write()funkcję, co zrobiłem w następujący sposób. Jest to konwencjonalnie zawarte w 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
źródło
Co się stanie, jeśli użyję pliku Makefile, a nie IDE? Czegoś brakuje w tym do pracy w moim projekcie Makefile ... Czy ktoś może pomóc?
Tedi
@Tedi, używasz GCC? To jest odpowiedź specyficzna dla kompilatora. Czego próbowałeś i jakie były wyniki?
cp.engr
Tak, korzystam z GCC. Znalazłem problem. Wywołano funkcję _write (). Problem dotyczył mojego kodu do wysłania ciągu. Źle wykorzystałem bibliotekę RTT Seggera, ale teraz działa dobrze. Dzięki. Wszystko działa teraz.
Tedi
4

_EXFUN jest makrem, prawdopodobnie zawierającym kilka interesujących dyrektyw, które mówią kompilatorowi, że powinien sprawdzić ciąg formatu pod kątem zgodności z printf i upewnić się, że argumenty printf są zgodne z ciągiem formatu.

Aby dowiedzieć się, jak korzystać z printf, sugeruję stronę podręcznika i trochę więcej googlowania. Napisz kilka prostych programów C korzystających z printf i uruchom je na komputerze, zanim spróbujesz przejść na korzystanie z niego na mikro.

Interesującym pytaniem będzie „gdzie idzie wydrukowany tekst?”. W systemie uniksowym idzie na „standardowe wyjście”, ale mikrokontroler nie ma czegoś takiego. Biblioteki debugowania CMSIS mogą wysyłać tekst printf do portu debugowania semihosting ramienia, tj. Do sesji gdb lub openocd, ale nie mam pojęcia, co zrobi SystemWorkbench32.

Jeśli nie pracujesz w debuggerze, bardziej sensowne może być użycie sprintf do sformatowania ciągów, które chcesz wydrukować, a następnie wysłanie tych ciągów przez port szeregowy lub na inny ekran, który mógł zostać podłączony.

Uwaga: printf i powiązany z nim kod są bardzo duże. To prawdopodobnie nie ma większego znaczenia na 32F407, ale jest to prawdziwy problem na urządzeniach z małą ilością flasha.

William Brodie-Tyrrell
źródło
IIRC _EXFUN oznacza funkcję do wyeksportowania, tj. Do użycia w kodzie użytkownika i definiuje konwencję wywoływania. Na większości (wbudowanych) platform można zastosować domyślną konwencję wywoływania. Ale na x86 z bibliotekami dynamicznymi może być konieczne zdefiniowanie funkcji, __cdeclaby uniknąć błędów. Przynajmniej na newlib / cygwin, _EXFUN służy tylko do ustawienia __cdecl.
erebos,
4

@ AdamHaun to wszystko, czego potrzebujesz, dzięki sprintf()czemu łatwo jest utworzyć ciąg, a następnie wysłać go. Ale jeśli naprawdę chcesz mieć własną printf()funkcję, to Variable Argument Functions (va_list) jest właściwą drogą.

Z va_listniestandardową funkcją drukowania wygląda następująco:

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

Przykład użycia:

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

Zwróć uwagę, że chociaż to rozwiązanie zapewnia wygodną funkcję, ale jest wolniejsze niż wysyłanie surowych danych lub używanie parzystości sprintf(). Myślę, że w przypadku dużych danych nie będzie to wystarczające.


Inną opcją i prawdopodobnie lepszą opcją jest użycie ST-Link, debuggera SWD wraz z narzędziem ST-Link. I użyj Printf za pośrednictwem przeglądarki SWO , oto instrukcja ST-Link Utility , odpowiednia część zaczyna się na stronie 31.

Printf przez SWO Viewer wyświetla dane printf wysłane z celu przez SWO. Pozwala wyświetlić przydatne informacje o działającym oprogramowaniu układowym.

Bence Kaulics
źródło
nie używasz va_arg, jak przechodzisz przez argumenty zmiennych?
iouzzr
1

printf () jest (zwykle) częścią standardowej biblioteki C. Jeśli twoja wersja biblioteki zawiera kod źródłowy, możesz tam znaleźć implementację.

Prawdopodobnie łatwiej byłoby użyć sprintf () do wygenerowania ciągu, a następnie użyć innej funkcji do wysłania ciągu przez port szeregowy. W ten sposób całe trudne formatowanie jest zrobione dla Ciebie i nie musisz hakować standardowej biblioteki.

Adam Haun
źródło
2
printf () jest zwykle częścią biblioteki, tak, ale nie może nic zrobić , dopóki ktoś nie skonfiguruje podstawowej możliwości wyświetlania na pożądanym sprzęcie. To nie „hakowanie” biblioteki, ale wykorzystanie jej zgodnie z przeznaczeniem.
Chris Stratton
Może być wstępnie skonfigurowany do wysyłania tekstu przez połączenie debuggera, jak sugerowali Bence i William w swoich odpowiedziach. (Oczywiście nie tego chciał pytający.)
Adam Haun
Zwykle dzieje się tak tylko w wyniku kodu, który zdecydujesz się połączyć w celu obsługi twojej płyty, ale bez względu na to, zmieniając to, definiując własną funkcję wyjściową. Problem z twoją propozycją polega na tym, że nie opiera się ona na zrozumieniu, w jaki sposób biblioteka ma być używana - zamiast tego proponujesz wieloetapowy proces dla każdego wyjścia, co między innymi spowoduje bałagan w każdym istniejącym urządzeniu przenośnym kod, który działa normalnie.
Chris Stratton,
STM32 to środowisko wolnostojące. Kompilator nie musi udostępniać pliku stdio.h - jest całkowicie opcjonalny. Ale jeśli dostarcza stdio.h, musi dostarczyć całą bibliotekę. Kompilatory systemów wolnostojących, które implementują printf, zwykle robią to jako komunikacja UART.
Lundin,
1

Dla tych, którzy mają trudności, dodaj następujące informacje do 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);
}

Podłącz do portu COM za pomocą szpachli i powinieneś być dobry.

Michael Brown
źródło
1

Jeśli chcesz mieć inne podejście i nie chcesz nadpisywać funkcji ani zadeklarować własnych, możesz po prostu użyć snprintfdo osiągnięcia tego samego celu. Ale to nie jest tak eleganckie.

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
LeoDJ
źródło