Alternative zu sprintf

OP #7382471
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.

#7382509
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

#7382529
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.

(Firma: egal) #7382536
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?

#7382538
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!).

OP #7382545
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.

Moderator Persönliche Seite #7382576
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.

#7382586
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.

#7382596
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.

#7382597
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

#7382635
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.

#7382636
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

#7382651
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?

#7382662
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
}
Moderator Persönliche Seite #7382752
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.

#7382819
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.

#7383076
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.

Moderator Persönliche Seite #7383277
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.

#7383286
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.

#7383358
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

#7383372
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren