Printf ist in der Standard C Library <stdio.h> enthalten.
Damit sie Funktioniert, musst du die folgende C Funktion implementieren.
Das folgende Beispiel leitet die Ausgaben auf den USB Port. Für serielle
Ports musst du sie entsprechend umschreiben.
Stefanus F. schrieb:> Damit sie Funktioniert, musst du die folgende C Funktion implementieren.
Das ist aber jetzt schon ein sehr konkretes Beispiel. Der TO hat
überhaupt nicht geschrieben, wo der Output (stdout) hingehen soll.
Außerdem muss man dazusagen, dass nicht nur _write, sonder auch andere
Syscalls benötigt werden. Zum Beispiel _sbrk für den Heap. Das braucht
dann wiederum den notwendigen Speicherbereich, ggf. Im Linkerskript
anlegen...
Kenne mich mit CubeMX nicht aus. Bei den einzigen beiden Projekten, die
ich damit gemacht, äh versucht, habe, musste ich im generierten Code von
CubeMX Fehler beseitigen. Hätte jetzt gedacht, dass CubeMX eine
Einstellung hat, wie es den Standardoutput legen soll und dir das dann
alles richtig hinbastelt.
M. H. schrieb:> Das ist aber jetzt schon ein sehr konkretes Beispiel
Darauf habe ich hingewiesen. Unkonkrete Beispiele sind hier unbeliebt.
Entscheidend ist, dass der TO einen kleinen Schubser in die richtige
Richtung bekommt. Wer AVR kennt, weiß man es dort ganz anders machen
muss.
> Außerdem muss man dazusagen, dass nicht nur _write, sonder auch andere> Syscalls benötigt werden. Zum Beispiel _sbrk für den Heap. Das braucht> dann wiederum den notwendigen Speicherbereich, ggf. Im Linkerskript> anlegen...
Die werden üblicherweise vom Projektassistenten der IDE angelegt, bzw
von Cube MX.
> Kenne mich mit CubeMX nicht aus.
Dann laber doch nicht dumm herum. Ich habe das vorher ausprobiert, es
funktioniert genau so, wie ich es geschrieben habe.
> Hätte jetzt gedacht, dass CubeMX eine Einstellung hat,> wie es den Standardoutput legen soll und dir das dann> alles richtig hinbastelt.
Leider nicht.
Mit sprintf kann man alles schön formatieren und dann an eine beliebige
Schnittstelle senden.
z.B. HAL_I2C_Master_Transmit_IT();
oder an cdc, spi, can ....
pegel schrieb:> Ist mir noch nicht passiert.
sprintf ist eine geradezu sprichwörtliche Sicherheitslücke :-) Es gibt
bestimmt genug IoT-Geräte, welche sich darüber übernehmen lassen...
Wenn ich sprintf benutze, dass weiß ich meistens vorher, wie lang der
String maximal werden kann. Entsprechend groß lege ich den Buffer aus.
Wenn die Größe nicht vorher festlegen kann, benutze ich snprintf.
Das Einzige Risiko besteht in schlampiger Programmierung.
Stefanus F. schrieb:> Das Einzige Risiko besteht in schlampiger Programmierung.
Leider besteht hier ein großes Fehlerpotenzial. Man schätzt die maximale
Größe schnell mal falsch ein. Da ist es doch viel einfacher, immer
snprintf+sizeof zu nutzen. Das funktioniert dann auch nach Änderung der
Puffer-Größe, Format-String oder Eingaben noch. Als schlampige
Programmierung würde ich es sehen, wenn die Sicherheit der Anwendung
davon abhängt, ob 3 Faktoren (Array-Größe, Format-String, Eingabe-Daten)
genau richtig zueinander passen.
Niklas G. schrieb:> Man schätzt die maximale Größe schnell mal falsch ein.
Du vielleicht.
Ich schätze nicht, ich weiß genau, wie viel Puffer ich benötige. Wenn
nicht, benutze ich snprintf und riskiere unvollständige Ergebnisse. Ist
auch suboptimal.
Niklas G. schrieb:> Als schlampige Programmierung würde ich es sehen,> wenn die Sicherheit der Anwendung> davon abhängt, ob ... Faktoren> genau richtig zueinander passen.
Ich unterscheide hier zwischen Programmen, die ich voll im Griff habe,
und andere. Für die anderen Programme ist C ohnehin die falsche Sprache.
Stefanus F. schrieb:> Ich schätze nicht, ich weiß genau, wie viel Puffer ich benötige.
Ah, wie viel braucht man denn hierfür?
1
sprintf(buffer,"Hallo %s, deine Note ist %f. Du hast %zu Bytes Speicher verbraucht.\n",userName,mark,memsize);
Wenn das Ergebnis immer vollständig sein muss, nimmt man std::string
o.ä. sprintf ist hier absolut keine Lösung.
snprintf hat keinerlei Nachteil, ich verstehe nicht warum man auf
sprintf bestehen würde, selbst wenn es in speziellen Sonderfällen
korrekt wäre...
> Ah, wie viel braucht man denn hierfür?
Dein Code ist unvollständig, daher kann ich Dir diese Frage nicht
beantworten. Das ist so ein Fall von "wo ich den Code nicht voll im
Griff habe". Da würde im Zweifelsfall snprintf verwenden.
Strings in String einzufügen ist allerdings ohnehin fragwürdig.
Wesentlich effizienter wäre die Ausgabe der Teilstrings, insbesondere
wenn ich extrem lange userNamen erwarte.
"Hallo "
userName
", deine Note ist %f. Du hast %zu Bytes Speicher verbraucht.\n"
> snprintf hat keinerlei Nachteil
Doch, es ist langsamer.
Müssen wir überhaupt über so eine Pillepalle diskutieren? Ich komme mir
langsam blöd vor, ich habe wichtigere Sorgen.
Stefanus F. schrieb:> Dein Code ist unvollständig, daher kann ich Dir diese Frage nicht> beantworten.
Okay, die maximale Länge von userName sei MaxUsernameLength. Mehr muss
man nicht wissen.
Stefanus F. schrieb:> Doch, es ist langsamer.
Um wieviel, dass das Risiko akzeptabel ist? Welche printf-Ausgabe ist so
zeitkritisch, dass es auf das Bisschen ankommt?
Niklas G. schrieb:> Um wieviel, dass das Risiko akzeptabel ist?
Es gibt kein Risiko, wenn ich genau weiß, wie lang der String maximal
werden kann. Ansonsten nutze ich wie gesagt snprintf oder gleich Java.
Du verbeißt Dich da in ein "Problem" das gar nicht existiert, wenn man
konzentriert arbeitet. Und wenn nicht, dann kann Dich die
Programmiersprache und solche Konstrukte ohnehin nicht retten. Früher
oder später baust du irgendwo einen Buffer, Stack oder Heap Überlauf -
auch mit snprintf.
Ja, so stürzten früher ständig die Programme ab weil die Programmierer
alles im Griff hatten und Schuld war Windows... MS hat extra eine
Warnung in den Compiler eingebaut um darauf hinzuweisen das diese
Funktionen unsicher sind. Und Fehler aus überschriebenen Stackvariablen
sind die schönsten.
Stefanus F. schrieb:> Es gibt kein Risiko, wenn ich genau weiß, wie lang der String maximal> werden kann.
Wie lang ist er denn jetzt? :-) Steht doch da, ist doch leicht
erkennbar.
Stefanus F. schrieb:> Früher> oder später baust du irgendwo einen Buffer, Stack oder Heap Überlauf -> auch mit snprintf.
Mit der Einstellung braucht man sich dann auch nicht mehr über
Sicherheitslücken und instabile Software beschweren.
Mit deinem snprintf tauschst du nur einen potentiellen Fehler gegen
einen anderen aus. Jetzt bekommst du zwar garantiert an dieser Stelle
keinen Pufferüberlauf, dafür bekommst du eine unvollständige Ausgabe.
Auch das ist ein Fehler, der nicht vorkommen darf.
Wenn schon, dann begrenzt du bei der Ausgabe die Länge des Namens, dann
hast du wieder eine berechenbare Puffergröße. Oder du splittest es wie
bereits empfohlen auf drei Teilstrings auf.
Oder du machst den Puffer einfach groß genug:
char buffer[n+MaxUsernameLength];
n darfst Du Dir selber ausrechnen.
Stefanus F. schrieb:> n darfst Du Dir selber ausrechnen.
Du gibst also auf. War wohl doch nicht so einfach. Noch eine
Einschränkung habe ich tatsächlich vergessen: mark liege zwischen 1.0
und 5.0.
Stefanus F. schrieb:> der nicht vorkommen darf.
Dieser Fehler kann nur verärgerte Nutzer, aber kein übernommenes und für
illegale Aktivitäten genutztes System zur Folge haben.
Stefanus F. schrieb:> Jetzt mach ich> wirklich Schluss.
Ok. Dann gebe ich für die Zuschauer zuhause selbst die Lösung:
Die Länge des Puffers muss man korrekterweise so berechnen:
sprintf(buffer,"Hallo %s, deine Note ist %f. Du hast %zu Bytes Speicher verbraucht.\n",userName,mark,memsize);
Das ist allerdings aufgerundet, in Randfällen ist es ggf. ein Byte zu
viel. Exakt muss man es mit dem Taschenrechner machen.
Wem das zu umständlich ist, nimmt snprintf:
1
charbuffer[100];
2
snprintf(buffer,sizeof(buffer),"Hallo %s, deine Note ist %f. Du hast %zu Bytes Speicher verbraucht.\n",userName,mark,memsize);
Hier kann zwar Text abgeschnitten werden, das ist aber keine
Sicherheitslücke und daher deutlich weniger schlimm.
Falsch hingegen ist:
1
charbuffer[80+MaxUsernameLength];
2
sprintf(buffer,"Hallo %s, deine Note ist %f. Du hast %zu Bytes Speicher verbraucht.\n",userName,mark,memsize);
Dies wird spätestens bei der Portierung auf eine 64bit-Plattform zu
Problemen führen.
M. H. schrieb:> Kenne mich mit CubeMX nicht aus. Bei den einzigen beiden Projekten, die> ich damit gemacht, äh versucht, habe, musste ich im generierten Code von> CubeMX Fehler beseitigen.
War das eine Aspach-Uralt Version?
Ich bekam von CubeMX bis jetzt immer lauffähigen Code, sogar mit
umfangreicheren Sachen wie FreeRTOS und USB.
Man muss darauf achten, dass man eine möglichst aktuelle Version
verwendet und wenn möglich eine direkt unterstützte Entwicklungsumgebung
wie z.B. TrueSTUDIO, SW4STM32, IAR, ...
> Hätte jetzt gedacht, dass CubeMX eine> Einstellung hat, wie es den Standardoutput legen soll und dir das dann> alles richtig hinbastelt.
Bei der riesigen Anzahl an Kommunikationsmöglichkeiten, die ein STM32
hat, wäre das dann vielleicht doch ein wenig zuviel verlangt.
Johnny B. schrieb:> War das eine Aspach-Uralt Version?> Ich bekam von CubeMX bis jetzt immer lauffähigen Code, sogar mit> umfangreicheren Sachen wie FreeRTOS und USB.
War vor ca. einem Jahr. Habe versucht etwas mit I2S und USB zu basteln.
CubeMX hat in der Clock Initialisierung ein ODER (|) in der Zuweisung
vergessen. Also statt clock->register |= mask nur ein = gehabt. Habe ca.
ne Stunde den Code gedebuggt, um das zu finden. Habe den Fehler
gemeldet, genauso wie die fehlerhaften Linkerskripte. Kann sein, dass
sie es repariert haben. Habe seitdem nichts mehr versucht. Die
Linkerskripte sind glaube ich immernoch fehlerhaft und funktionieren
nur, weil der STM auch Speicher hat, der im Datenblatt als "reserved
area" gelistet ist.
Das ist der Thread: Beitrag "SRAM Lücke STM32Fxxx, Linkerskripte falsch"
Vorallem schön, wenn man das produktiv für sichere Sachen nutzt :D
Johnny B. schrieb:> Bei der riesigen Anzahl an Kommunikationsmöglichkeiten, die ein STM32> hat, wäre das dann vielleicht doch ein wenig zuviel verlangt.
Keineswegs. Das Tool kann mir nen Heap automatisch basteln und tut auch
sonst so als wäre es die ultimative Lösung, dann wird es mir auch
generieren können, dass ich stdout auf eine aktivierte streamingfähige
Schnittstelle lege.
Stefanus F. schrieb:> Dann laber doch nicht dumm herum. Ich habe das vorher ausprobiert, es> funktioniert genau so, wie ich es geschrieben habe.
Ich wollte nur darauf hinweisen, dass das einfügen der von dir
geposteten Funktion nicht magisch dazu führt, dass es geht.
Hatte die printf funktion erfolgreich zum laufen gebracht, jedoch bei
einem neuen Projekt werden Bytes Verschluckt.
Uart1 auf printf:
int _write(int file, char *ptr, int len)
{
HAL_UART_Transmit_IT(&huart1, (uint8_t*) ptr, len);
return len;
}
Hauptprogramm:
while (1)
{
printf("STM32 Glump Test mit UART \r\n");
printf("ABCDEF1234567890 \r\n");
HAL_Delay(50);
}
Kommt nur noch das raus am Terminal:
SBCDEF1234567890
t UART
SBCDEF1234567890
t UART
STCDEF1234567890
t UART
STCDEF1234567890
t UART
Beim vorherrigen Projekt war es kein Problem zweimal direckt
hintereinander Print zu machen. Was kann das sein?
Schorsch schrieb:> Was kann das sein?
Ein Hardware Abstraction Layer, programmiert von der Marketing Abteilung
von ST? HAL_UART_Transmit_IT() funktioniert offenbar nicht (na gut:
nicht so, wie man es erwartet).
Das erste printf() kopiert den Text in einen Puffer und ruft HAL... auf.
Das kehrt zurück während das UART das erste 'S' ausgibt. Das erste
printf() hat nichts weiter zu tun und kehrt auch zurück. Jetzt kopiert
das zweite printf() seinen Text in den gleichen Puffer und überschreibt
damit den ersten Text von 'S' bis zum 'i' vom "mit".
Irgendwann wird das UART mit dem 'S' fertig und gibt das nächste Zeichen
aus. Das ist inzwischen nicht mehr das 'T' vom ersten Text sondern das
'B' vom zweiten.
Normal wäre jetzt "man HAL_UART_Transmit_IT" angesagt...
Vor dem Senden eines neuen Strings musst du daher warten, bis der
vorherige Sendevorgang abgeschlossen ist. Ausserdem kannst du keine
Strings senden, die länger sind, als der Puffer.
Manchmal ist es doch besser, solche Dinge selbst zu programmieren. In
diesem Fall mit einem Ringpuffer und ggf. einer Warteschleife, falls der
Puffer nicht ausreicht (so wie Arduino es macht).
So in der art hab ich mir das Problem auch vorgestellt.
Allerdings hätte ich gedacht/erwartet dass erst nach dem fertig
Raussenden wieder ins Programm zurückgekert wird.
Zum Lösungsansatz:
Wie mache ich dass am einfachsten mit einem Ringpuffer?
Schorsch schrieb:> Allerdings hätte ich gedacht/erwartet dass erst nach dem fertig> Raussenden wieder ins Programm zurückgekert wird.
Du hast aber die Interrupt-gesteuerte Variante (mit "IT") verwendet.
Wenn diese Funktion den Programmablauf blockieren würde, wäre sie im
Verglich zu Version ohne "IT" sinnlos.
> Wie mache ich dass am einfachsten mit einem Ringpuffer?
Schau erst einmal in der HAL Doku ob die etwas dazu hergibt.
Der Punkt ist, dass der Ringpuffer in der ISR entsprechend programmiert
werden muss. Wenn die HAL das nicht drin hat, musst du zumindest an
dieser Stelle auf die HAL verzichten. Willst du das? Wohl kaum.
Wenn du warten willst, solltest du am besten einfach die andere Variante
ohne "IT" verwenden.
Wenn du nicht warten willst, kopiere den String in einen Puffer und
beauftrage die HAL, diesen zu senden. So ungefähr stelle ich mir das
vor:
// copy the string into the buffer (with size limit)
18
if(len>sizeof(buffer)){
19
len=sizeof(buffer)
20
}
21
memcpy(buffer,ptr,len);
22
23
// send the buffer
24
HAL_UART_Transmit_IT(&huart1,buffer,len);
25
26
returnlen;
27
}
Bei diesem Code dürfen die Strings nicht grösser sein, als der Puffer.
Du könntest zu grosse Strings in mehrere Teilstücke zerlegen.
Mit ein bisschen Nachdenken kannst du diesen Ansatz auf zwei Puffer
ausbauen, die wechselweise verwendet werden. Dadurch reduzieren sich die
Wartezeiten weiter so dass es einem Ringpuffer kaum noch nach steht.
Schorsch schrieb:> Hatte die printf funktion erfolgreich zum laufen gebracht, jedoch bei> einem neuen Projekt werden Bytes Verschluckt.
Jaja.
Auf dem PC kann man sich dank riesigen RAM's so ziemlich alles erlauben,
auch printf und Konsorten.
Aber auf einem µC, wo man nur einen recht begrenzten RAM hat, ebenso
einen im Vergleich zum PC recht kleinen Stack und Heap, da sind
eigentlich andere Methoden als printf gefragt.
Der ewige Denkfehler beim Benutzen von printf ist, daß man eben immer zu
allererst RAM für das Aufbauen eines Ausgabe-Blockes benötigt und dann
einen fetten und oftmals in seiner Größe schwer abschätzbaren Block hat,
der dann ausgegeben werden soll. Entweder man macht das dann blockierend
per Polling, oder man braucht nochmals eine noch größere Portion an RAM
als Zwischenpuffer für den Lowlevel-Treiber.
Das Ganze ist eben sehr RAM-hungrig.. und den RAM hat man hier nicht im
Überfluß.
Die Lösung wäre, einfach auf printf und all die daran hängenden Dinge zu
verzichten und die Ausgaben ohne Aufbauen eines Zwischen-Strings zu
erledigen. Aber das wäre natürlich eine Umgewöhnung beim Programmierer
und das scheint hier immerzu nur schlecht anzukommen.
Naja - und was man von ST's HAL zu halten hat, da ist meine Ansicht:
garnix.
W.S.
Stefanus F. schrieb:> Mit ein bisschen Nachdenken kannst du diesen Ansatz auf zwei Puffer> ausbauen
Hmm.. noch mehr RAM reservieren? Wo du ja in jedem Falle ohnehin warten
mußt?
W.S.
W.S. schrieb:> Hmm.. noch mehr RAM reservieren? Wo du ja in jedem Falle ohnehin warten> mußt?
Der erste String wird gesendet.
Den zweiten String kann man in den zweiten Puffer legen, ohne zu warten.
Bis der dritte String gesendet werden muss, ist der erste Puffer
hoffentlich wieder frei.
Das war zumindest die Idee dahinter. Kann man beliebig weiter spinnen -
bis zum Out of Memory.
Ganz mutige legen unendlich viele Puffer on-demand mit malloc an - ich
rate davon ab.
Oder man programmiert es mit Ringpuffer, so sparsam wie üblich. Aber die
HAL hat ihre Prioritäten woanders, wie wir beide wissen.
W.S. schrieb:> Der ewige Denkfehler beim Benutzen von printf ist, daß man eben immer zu> allererst RAM für das Aufbauen eines Ausgabe-Blockes benötigt und dann> einen fetten und oftmals in seiner Größe schwer abschätzbaren Block hat,> der dann ausgegeben werden soll. Entweder man macht das dann blockierend> per Polling, oder man braucht nochmals eine noch größere Portion an RAM> als Zwischenpuffer für den Lowlevel-Treiber.
Ob per Interrupt oder per Polling ausgegeben wird, hat doch nichts mit
printf zu tun. Für den Interrupt-Luxus muss ich halt RAM spendieren,
aber printf selbst braucht wenig. Mein printf (von ChibiOS geklaut)
verwendet einen 12 Byte Puffer auf dem Stack und wahrscheinlich könnte
man den auch noch sparen.
> Die Lösung wäre, einfach auf printf und all die daran hängenden Dinge zu> verzichten und die Ausgaben ohne Aufbauen eines Zwischen-Strings zu> erledigen.
Ich hab es lange Zeit so gemacht. Jetzt mit printf finde ich es
wesentlich angenehmer. Deine Lösung würde aber auf jeden Fall
blockieren? Wobei mich das nicht stört, mein printf blockiert auch.
> Aber das wäre natürlich eine Umgewöhnung beim Programmierer> und das scheint hier immerzu nur schlecht anzukommen.
Natürlich, weil es nur bei extremem Speichermangel nötig ist. Außerdem
brauchst du wahrscheinlich mehr Flash für viele kleine Funktionsaufrufe.
Stefanus F. schrieb:> Bauform B. schrieb:>> aber printf selbst braucht wenig>> Die printf() Funktion aus der newlib-nano Library belegt 1468 Bytes RAM.
Nichts ist so unnütz, dass es nicht als schlechtes Beispiel dienen
könnte ;)