Forum: Mikrocontroller und Digitale Elektronik Alternative zu sprintf


von Zo R. (hsch1978)


Lesenswert?

Hallo,

ich benutze in meiner Mikrocontroller Firmware für die Textausgabe auf 
der UART die Funktion sprintf(...). Gibt es dazu eine gute Alternative?
Wenn ich meine Anwendung im Debug-Mode laufen lasse, dann verhält sich 
mein Mikrocontroller nach eine gewissen Zeit nicht so, wie es sein 
sollte. Ich muss dazu sagen dass ich für Testzwecke die sprintf Funktion 
benutze mit dieser werden zyklisch Textnachrichten über die UART 
versendet. Ich vermute das die sprintf zur Laufzeit Probleme bereitet.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

He S. schrieb:
> Ich vermute das die sprintf zur Laufzeit Probleme bereitet.

Es benötigt halt etwas Ram. Wenn es da schon eng ist, dann kann das noch 
enger werden.

Es gibt sicherlich andere Möglichkeiten, aber ob die auf dem Modell 
"Mikrocontroller" mit der Software "Firmware" auch verfügbar sind, musst 
du da schon selber rausfinden.

Oliver

von Wastl (hartundweichware)


Lesenswert?

He S. schrieb:
> Gibt es dazu eine gute Alternative?

He S. schrieb:
> Ich vermute das die sprintf zur Laufzeit Probleme bereitet.

Bitte Controller-Typ und weitere Details möglichst geheim halten.
Salami-Schneidemaschine schon mal bereitstellen.

von Steve van de Grens (roehrmond)


Lesenswert?

Wenn für sprintf kein Platz ist, benutze ich Funktionen aus der 
stdlib.h, um Zahlen in Text umzuwandeln.

von Vanye R. (vanye_rijan)


Lesenswert?

> Ich vermute das die sprintf zur Laufzeit Probleme bereitet.

Warum sollte das Probleme bereiten? Das ist einfach ein printf
und damit genauso fett.
Auf kleinen Controllern schreibe dir die Funktion einfach
selber und beschraenke den Funktionsumfang auf das was du
brauchst. Also z.B als erstes mal die Unterstuetzung von
Fliesskomma weglassen. Und schon wird es klein....

Vanye

von Steve van de Grens (roehrmond)


Lesenswert?

Vanye R. schrieb:
> Warum sollte das Probleme bereiten? Das ist einfach ein printf
> und damit genauso fett.

Sprintf benötigt einen Ausgabepuffer im RAM. Printf kann die Ausgabe 
streamen.

von Peter D. (peda)


Lesenswert?

He S. schrieb:
> Ich vermute das die sprintf zur Laufzeit Probleme bereitet.

Entweder die zu langsame UART oder der RAM, der mit konstanten Strings 
vollgestopft wird.
Wenn AVR dann schaue mal unter sprintf_P und PSTR nach.

https://cpp.hotexamples.com/examples/-/-/sprintf_P/cpp-sprintf_p-function-examples.html

von Axel S. (a-za-z0-9)


Lesenswert?

Oliver S. schrieb:
> He S. schrieb:
>> Ich vermute das die sprintf zur Laufzeit Probleme bereitet.
>
> Es benötigt halt etwas Ram. Wenn es da schon eng ist, dann kann das noch
> enger werden.

Kaffeesatzleserei! Niemand weiß, was genau der TE mit sprintf() 
anstellt. Oder welchen Controller (wieviel RAM) er verwendet. Oder was 
er so schön nebulös mit "Probleme bereitet" umschreibt.

Trivialerweise braucht sprintf() RAM in der Größe des Buffers. Wenn man 
das extra erwähnen muß, kann man sich auch gleich alle Hilfe sparen.

von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

He S. schrieb:
> für die Textausgabe auf der UART die Funktion sprintf

Axel S. schrieb:
> Niemand weiß, was genau der TE mit sprintf() anstellt?

Sehr gute Frage, sprintf gibt von sich aus erstmal überhaupt nichts 
extern aus. Das erzeugt nur ein formatiertes Char-Array im RAM und sonst 
nichts. Da stellt sich also erstmal die Frage was wird da wie 
Formatiert. Eingabedaten auf Gültigkeit geprüft, Ausgabe-Puffer immer 
ausreichend usw. (deshalb nimmt man heutzutage auch sprintf_s) Und 
danach kommt dann erst wie wird des Array auf dem UART ausgegeben?

- https://en.cppreference.com/w/c/io/fprintf

von Bruno V. (bruno_v)


Lesenswert?

Bei relativ kleinen Controllern gibt es "eingeschränkte" (s)printfs, die 
z.B. Float-Behandlung weglassen. Man wählt dann irgendwie 
(Compiler/Linker-Flags) die passende.

Eigentlich sollte (s)printf auf jeder Plattform so effizient sein (oder 
konfigurierbar sein), dass sich ein Eigenbau in den meisten fällen nicht 
lohnt. Trotzdem macht das meist jeder selber, sei es aus Stolz, zu 
früher Optimierung oder Größenwahn (bei mir alles 3 und es war immer 
unnötig!).

: Bearbeitet durch User
von Bauform B. (bauformb)


Lesenswert?

Viele sprintf() Probleme können mit snprintf() garnicht erst entstehen. 
Trotzdem geht nichts über ein eigenes printf().

von Zo R. (hsch1978)


Lesenswert?

Ich benutzte aktuell die sprintf Funktion folgendermaßen:
1
uint8_t TxData[64];
2
st_TimeDate TimeDate;
3
4
GetDateTime(&TimeDate);
5
6
sprintf(TxData, "Time: %02d:%02d:%02d:%03d\n", TimeDate.hour, TimeDate.min, TimeDate.sec, TimeDate.ms,
7
8
SendUartData(TxData, 19);

Jede 5 Sekunden wird ein Zeitstempel ausgegeben.

von Harald K. (kirnbichler)


Lesenswert?

Von wo aus wird diese Funktion aufgerufen? Wie bestimmst Du "alle fünf 
Sekunden"?

Verwendest Du einen Timerinterrupt und rufst die Funktion /aus dem 
Interrupthandler/ auf?

von Wastl (hartundweichware)


Lesenswert?

He S. schrieb:
> Ich benutzte aktuell die sprintf Funktion folgendermaßen:

Zweite Salami-Scheibe. Wir warten ....

von J. S. (jojos)


Lesenswert?

Zwar nicht schön wegen der fixen Länge in SendUart wo sprintf doch die 
Länge als return Wert hat, aber das Problem liegt sicher woanders. Es 
sollte natürlich genug Stack vorhanden sein.

von Steve van de Grens (roehrmond)


Lesenswert?

Will jemand ein Snickers?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

J. S. schrieb:
> Es sollte natürlich genug Stack vorhanden sein.

Alternativ kann der TO das Wörtchen "static" vor die Buffer-Definition 
schreiben. Dann liegt der Buffer nicht mehr auf dem Stack. Wäre auf 
jeden Fall einen Versuch wert.

Wir wissen leider nicht, ob es sich um einen klitzekleinen ATtiny mit 40 
Bytes RAM oder um einen fetten STM32 mit einigen 100 Kilobytes handelt.

@TO: Mehr Infos bitte, sonst wird das Kaffeesatzleserei.

von Andras H. (kyrk)


Lesenswert?

Also der sprintf ist ja blockierend, schreibt aber normaler wiese in ein 
RAM Buffer. Daher das hat erstmal nix mit dem UART zu tun. Das muss man 
nachher noch aus dem Buffer Richtung UART kopieren. Also du kannst dein 
Problem aufteilen. Hast du eher Probleme mit dem sprintf? Sprich wenn du 
das mit dem UART weglässt, tut das Ding? Oder hast du schon auch mit dem 
sprintf Probleme?

sprintf Probleme, können ja aus zu kleinen Buffer kommen. Wenn man char 
temp[32] mach und dann 32 bytes reinprintet dann wird noch am Ende eine 
0 geprintet, und schon ist es passiert. Also Buffergrössen überprüfen.

Bei UART ist es halt immer Problematisch einen String zu schicken. Denn 
meinst sind diese Funktionen Blockierend. Der UART läuft aber mit max 
115kHz. Der CPU kann aber auch mit 200MHz laufen. Oder halt ältere nur 
mit 4MHz oder so. Das heißt aber, man wartet ewig dass ein Char oder der 
ganze String gesendet wird. Das frisst halt Laufzeit. Damit kommen die 
klassische Laufzeitproblemen. Lösung ist, wenn man den String asynchron 
verschickt. Also Buffer reinhauen, und interrupt arbeiten lassen. Hier 
kann man aber meist nie genug Buffer haben, denn Debug Prints kommen 
relativ oft, und der Buffer lauft voll. Dann wartet man halt (das ist 
aber genau so schlimm wie vorhin mit dem Syncronen senden), oder man 
wirft die Daten weg. Es werden teilweise halbe Strings gesendet. Und da 
man jetzt asynchron ist, ist der String was eben gesendet wird, schon 
älter. Also da ist kein Syncronität zu dem was der CPU wirklich macht.

von MaWin O. (mawin_original)


Lesenswert?

Frank M. schrieb:
> Alternativ kann der TO das Wörtchen "static" vor die Buffer-Definition
> schreiben. Dann liegt der Buffer nicht mehr auf dem Stack.

Die gleiche Menge RAM braucht er trotzdem. Und die steht dem Stack dann 
natürlich auch nicht mehr zur Verfügung. Wenn die Funktion nicht 
rekursiv aufgerufen wird, ist das Jacke wie Hose.

von Purzel H. (hacky)


Lesenswert?

Eine vernuenftige Alternative, fuer Benutzer, welche etwas mitdenken ist 
Bin2Hex. Dessen Vorteil ist, dass die Zahl immer gleich lang ist.
Was will man denn mit floating point ? Falls sich der Benutzer entgegen 
allen  Vorgaben doch eine floating point Zahl reinziehen will, 
uebertrage ich trotzdem eine Hexzahl fuer den Wert, sowie eine oder zwei 
parametrierzahlen, womit der client dann den Floatwert berechnen kann.

von Vanye R. (vanye_rijan)


Lesenswert?

> Sprintf benötigt einen Ausgabepuffer im RAM. Printf kann die Ausgabe
> streamen.

DAs hast du natuerlich recht. Aber man kann ja auch das normale
printf nehmen und puts ueberlagern falls die Umgebung das unterstuetzt.

Und wenn sie das nicht macht dann schreibt man sich doch sein eigenes
printf und gibt sich dort alles mit einer eigenen Funktion in einer FIFO
aus welche dann ihrerseits irgendwann von einem IRQ an die serielle
geschoben wird.
Das hat dann auch den charmanten Vorteil das man printf relativ
hemmungslos in seinem Programm nutzen kann ohne das es gleich
zu krassen Timingproblemen kommt.

Vanye

von Axel S. (a-za-z0-9)


Lesenswert?

MaWin O. schrieb:
> Frank M. schrieb:
>> Alternativ kann der TO das Wörtchen "static" vor die Buffer-Definition
>> schreiben. Dann liegt der Buffer nicht mehr auf dem Stack.
>
> Die gleiche Menge RAM braucht er trotzdem. Und die steht dem Stack dann
> natürlich auch nicht mehr zur Verfügung.

Wenn es darum gehen würde, RAM einzusparen, dann würde man vor allem den 
Buffer nicht erst 64 Zeichen groß machen und dann nur 20 Zeichen davon 
verwenden (19 + die abschließende 0). Man würde wohl auch SendUartData() 
so implementieren, daß es mit C-Strings klarkommt und sich die explizite 
Längenangabe sparen.

Und natürlich kann man in diesem speziellen Fall das sprintf() auch 
einfachst durch etwas handgeschriebenes ersetzen. Das wird zwar längerer 
Quellcode, aber sicher weniger Objectcode. Und schneller, wobei das nun 
wirklich kein Kriterium ist. Der UART ist sicher langsamer.

von Vanye R. (vanye_rijan)


Lesenswert?

> Und schneller, wobei das nun
> wirklich kein Kriterium ist. Der UART ist sicher langsamer.

Nicht ganz. Wenn man sich noch eine eigene Fifo implementiert
welche die Ausgabe spaeter im IRQ macht dann kann man sein
eigenen printf in gewissen grenzen natuerlich, beliebig
im einen Code verwenden ohne das man deutlich die Programmlaufzeit
aendert. Das ist schon manchmal ganz nett.

Die obercoolheit ist es im uebrigen wenn man in das eigene printf
dann noch zwei Kommandos fuer x und y, Position einbaut und sich
das dann als vt100 ausgibt. Dann kann man auf der PC-Seite
ein Terminal verwenden und so seine Testdaten gleich etwas huebsch
formatieren. Macht das Leben gleich viel einfacher.

Vanye

von Axel S. (a-za-z0-9)


Lesenswert?

Vanye R. schrieb:
>> Und schneller, wobei das nun
>> wirklich kein Kriterium ist. Der UART ist sicher langsamer.
>
> Nicht ganz. Wenn man sich noch eine eigene Fifo implementiert
> welche die Ausgabe spaeter im IRQ macht

Hahaha! Du bist ein Spaßvogel.

Wie wahrscheinlich ist das nach der Fragestellung (wie formatiert man 
einen Zeitstempel ohne auf sprintf() zurückzugreifen) und der Art der 
Fragestellung (eine Scheibe Salami, bis jetzt) und dem gezeigten 
Codeschnipsel? Null? Oder sogar negativ?

von Norbert (der_norbert)


Lesenswert?

He S. schrieb:
> uint8_t TxData[64];

Das ist ein geradezu religiös anmutender Ausdruck der Hoffnung, das auf 
dem Stack auch immer und jedes mal 64 Bytes verfügbar sind.

Wie wäre es mit:
1
void debug_time() {
2
    uint8_t slen = sizeof("Time: nn:nn:nn:nnn\n");
3
    char *TxData = alloca(slen);
4
    if(TxData) {
5
        snprintf(TxData, slen, "Time: %02d:%02d:%02d:%03d\n", 23, 59, 59, 999);
6
        SendUartData(TxData, slen - 1);
7
    }
8
}

von Harald K. (kirnbichler)


Lesenswert?

Das setzt ein funktionierendes alloca voraus; meinst Du, daß auf dem 
bislang komplett ungenannt bleibenden Microcontroller des Threadstarters 
eine saubere Stack-/Heap-Verwaltung aktiv ist?

von Norbert (der_norbert)


Lesenswert?

Harald K. schrieb:
> Das setzt ein funktionierendes alloca voraus; meinst Du, daß auf
> dem
> bislang komplett ungenannt bleibenden Microcontroller des Threadstarters
> eine saubere Stack-/Heap-Verwaltung aktiv ist?

Na wenn's schon für einen Winzling wie den tiny44 kompiliert…
Aber die Frage ist natürlich legitim.

von Harald K. (kirnbichler)


Lesenswert?

Norbert schrieb:
> Na wenn's schon für einen Winzling wie den tiny44 kompiliert…

Der gezeigte Schnipsel mag das tun, aber wer weiß, was da tatsächlich 
zum Einsatz kommt.

Der Threadstarter jedenfalls schweigt hartnäckig und beantwortet keine 
Fragen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Andras H. schrieb:
> sprintf Probleme, können ja aus zu kleinen Buffer kommen. Wenn man char
> temp[32] mach und dann 32 bytes reinprintet dann wird noch am Ende eine
> 0 geprintet, und schon ist es passiert. Also Buffergrössen überprüfen.

Sowohl die Größe des Buffers als auch der benötigte Speicherplatz sind 
bekannt, siehe Codeschnipsel aus dem Eröffnungsposting.

von J. S. (jojos)


Lesenswert?

Nur sind händisch abgezählte Konstanten immer üble Fehlerquellen, da ist 
alloca schon besser.
Oder die stdio Ausgabe auf den uart umbiegen und es dem printf 
überlassen.  Wenn man sich den Luxus schon leistet, dann gleich richtig.

von Wastl (hartundweichware)


Lesenswert?

Harald K. schrieb:
> Der Threadstarter jedenfalls schweigt hartnäckig und beantwortet keine
> Fragen.

Welche "kühnen" Vermutungen liesse uns das denken oder sagen?

(Angemeldet seit 31.12.2005 14:14, 13 Beiträge verfasst)

von Axel S. (a-za-z0-9)


Lesenswert?

Wastl schrieb:
> Harald K. schrieb:
>> Der Threadstarter jedenfalls schweigt hartnäckig und beantwortet keine
>> Fragen.
>
> Welche "kühnen" Vermutungen liesse uns das denken oder sagen?
>
> (Angemeldet seit 31.12.2005 14:14, 13 Beiträge verfasst)

Nun, er hat jetzt genau einen Post (neben dem Eröffnungspost) in diesen 
Thread abgesetzt. Und Fragen hat er genau 1 beantwortet. Wobei die 
gleich 3 (mindestens) neue aufwirft.

von Foobar (asdfasd)


Lesenswert?

Norbert schrieb:
1
void debug_time() {
2
    uint8_t slen = sizeof("Time: nn:nn:nn:nnn\n");
3
    char *TxData = alloca(slen);
4
    if(TxData) {
5
        snprintf(TxData, slen, "Time: %02d:%02d:%02d:%03d\n", 23, 59, 59, 999);
6
        SendUartData(TxData, slen - 1);
7
    }
8
}

FYI, alloca hat keinen Fehlerreturn - im Fehlerfall gibt's einfach einen 
Stackoverflow.  Und selbst wenn, würde es nicht helfen: slen mag noch 
draufgepasst haben, die folgenden Funktionsaufrufe 
(snprintf/SendUartData) aber evtl nicht mehr.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Foobar schrieb:
1
> uint8_t slen = sizeof("Time: nn:nn:nn:nnn\n");
2
> char *TxData = alloca(slen);

Für diesen Fall kann man übrigens auch schreiben:
1
  char TxDat[sizeof("Time: nn:nn:nn:nnn\n")];

alloca macht hier keinen Unterschied und ist an dieser Stelle komplett 
überflüssig.

P.S.

Man könnte nun auf die Idee kommen, dass VLAs (variable length arrays) 
alloca() komplett obsolet gemacht hätten. Es gibt jedoch einen 
klitzekleinen Unterschied zwischen VLA und alloca(), der hier erklärt 
wird:

https://stackoverflow.com/questions/3488821/is-alloca-completely-replaceable

In dem obigen Beispiel spielt dieser Unterschied jedoch keine Rolle.

: Bearbeitet durch Moderator
von Norbert (der_norbert)


Lesenswert?

Letzten Endes ist es immer eine Abwägung zwischen 
(Ausführungs)Geschwindigkeit und Sicherheit.

Um Gewissheit zu erlangen könnte man jedoch zumindest einen 
Kanarienvogel ganz ans Stack-Ende schreiben und gelegentlich überprüfen. 
Vogel tot, LED an, zurück ans Zeichenbrett. ;-)

Was aber trotzdem bleibt, ist der sparsamst mögliche Umgang mit den arg 
begrenzten Resourcen des Stacks.
Wenn anstelle von 64Bytes nur 20Bytes alloziert werden, verbleibt eben 
eine riesige Menge an weiter nutzbaren Bytes. Diese könnte womöglich 
(sprich mit einiger Wahrscheinlichkeit) alle Folgeprobleme lösen.

von Vanye R. (vanye_rijan)


Lesenswert?

> Um Gewissheit zu erlangen könnte man jedoch zumindest einen
> Kanarienvogel ganz ans Stack-Ende schreiben und gelegentlich überprüfen.

Ich fuelle in meinem StartUp Code den Stack komplett mit 0xdead.
Dann habe ich in meinem Programm eine Funktion die von unten
nach oben laeuft und prueft wann das erstemal was anderes
im Stack steht. So kann ich die Stacknutzung ueberpruefen.

Das ist in der Regel bei heutigen Microcontrollern mit 20-128k
internem Ram aber kein Problem mehr. Braucht man nur noch in seltenen
Ausnahmefaellen.

Vanye

von Bauform B. (bauformb)


Lesenswert?

Vanye R. schrieb:
> So kann ich die Stacknutzung ueberpruefen.
>
> Das ist in der Regel bei heutigen Microcontrollern mit 20-128k
> internem Ram aber kein Problem mehr. Braucht man nur noch in seltenen
> Ausnahmefaellen.

Man ist ja neugierig ;) Aber uC mit 128k RAM sollten auch eine MMU/MPU 
o.ä. haben. Dann knallt es im Zweifelsfall gleich richtig, aber vor 
allem an exakt der richtigen Stelle. Ich möchte nicht mehr drauf 
verzichten.

Mit -Wstack-usage=42 gibt der gcc eine Warnung aus, wenn eine Funktion 
mehr Stack braucht als geplant. Das funktioniert nicht bei jeder Art von 
Stack-Nutzung, aber meistens.

von Wastl (hartundweichware)


Lesenswert?

Bauform B. schrieb:
> Ich möchte nicht mehr drauf verzichten.

Wenn man zu doof ist mit seinen Resourcen bewusst umzugehen
dann braucht man so etwas eben.

von Bauform B. (bauformb)


Lesenswert?

Wenn man sich die Maschinenbefehle nicht merken kann, braucht man eben 
einen Compiler...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.