Frage zur Umwandlung von Integerzahlen in einen String

  • Hallo Kai


    Ja, ich sehe, Du willst immer tiefer vorstossen und profitierst von deiner Neugier. Die Lib deines Texas Prozessors hat die beiden Funktionen divs32 divu32 getrennt, weil vermutlich das Aufgliedern in viele kleine Funktionen, die möglichst ohne Referenzierung anderer Funktionen, effizienter ist.


    Ob bei den Atmel Prozessoren ähnlich vorgegangen wurde, weiss ich leider nicht und es ist im Endeffekt auch gar nicht so entscheidend, wie es realisiert wurde. Weil, letztendlich sind die Gedankengänge der Benutzer (hier Entwickler) nicht im vornhinein zu bestimmen. Wichtig ist aber zu wissen, dass die Entwickler der Lib’s sich Gedanken gemacht haben, um am Ende möglichst gute Ergebnisse zu bekommen, aber letztendlich bleibt es so was wie Zufall. Du kennst doch sicher den Spruch von Murphy’s Law:


    Es ist unmöglich ein Programm idiotensicher zu schreiben, weil Idioten so erfindungsreich sind.

    oder

    Jedes Programm lässt sich um eine Zeile kürzen! Auch wenn nur noch eine Zeile vorhanden ist.


    Wird ein guter und erfahrener Assemblerprogrammierer mit einer Lösung beauftragt, dann wird diese Lösung kaum mehr zu verbessern sein (und es wird teuer). Aber der grösste Nachteil dabei ist der Umstand, dass diese Lösung genau auf ein einziges Problem passt und sich nur eingeschränkt wiederverwenden lässt und dass das Programm sich kaum warten lässt.

    Heute versuchen wir meist Lösungen zu finden die bei mehreren Aufgaben behilflich sein sollen und damit die Entwicklungszeit verkürzen sollten.


    Es sind wenige, die es dann genau wissen wollen, solche die dann vielleicht anecken, wenn sie ihren Kommentar zu einer Lösung abgeben, weil die Mehrheit glaubt, dass diese Hinweise nicht relevant oder nicht zielführend sind. Und irgendwie liegen Beide ein wenig richtig.


    Uns als „normale“ Hobbyprogrammierer bleibt der Versuch unsere Lösung im Kopf dann möglichst gut umzusetzen, basierend auf der Hoffnung, dass die Lib Entwickler ihren Job gut gemacht haben.
    (und das machen sie meist)


    Nun hast du einen stromsparenden Prozessor, ein tieferes Verständnis des Aufbaus (mit den ASM Grundlagen) und musst unweigerlich feststellen, dass wir hier nichts dazu beitragen können? Nö, das wusstest du schon zuvor!^^


    Echt, ich selber bin beeindruckt, dass es noch Leute gibt, die es einfach nur wissen wollen.


    wünsche einen Guten Rutsch und bin gespannt was du uns nächstes Jahr alles servierst.

    Pius

  • Es geht jetzt nicht mehr um das eigentliche "itoa" Problem. Vielmehr möchte ich beschreiben, was ich bei diesem kleinen Projekt gelernt habe. Um es vorweg zu nehmen, vermeintliche Kleinigkeiten können ordentlich Speicherplatz sparen.


    Zuerst nochmal der Quelltext mit dem ich probiert habe:


    Wenn man den Code der diese Funktion enthält compiliert, wird das Programm 678 Bytes groß. Ich hatte in meinem Programm eine Konstante BASE definiert, welche der Zahl 10 (U)nsigned entspricht. Geht man nun hin und macht aus der Definition #define BASE 10U ein #define BASE 10UL, so wird es nach einer erneuten Compilierung nur noch 614 Byte groß sein. Also nur, weil ein "unsigned" Wert (16 Bit Datentyp) zu einem unsigned long Wert (32 Bit Datentyp) gemacht wurde, wird das Programm kleiner. Das erscheint doch erst einmal widersinnig!


    Wenn man nun vergleicht, was der Compiler aus den beiden Versionen macht, wird aber klar, warum das so ist. Im Folgenden ist ein Auszug über die Speichernutzung dargestellt. Die Zahlen geben den Speicherplatzverbrauch in Byte an:


    Die 10U Variante:


    678 Bytes gesamt


    Offenbar werden hier zwei Unterprogramme für die Division verwendet div32u.asm.obj und div32s.asm.obj. Schaut man sich Speicheraufteilung der 10UL Variante an, sieht das so aus:


    614 Bytes gesamt


    Sofort fällt auf, dass nur noch eine Routine für die Division (div32u.asm.obj) verwendet wird. Dadurch, das ein Unterprogramm wegfällt, werden 64Byte eingespart. Nur durch einen einzigen zusätzlichen Buchstaben im Quellcode.


    Es ist in C/C++ also entscheidend auf Datentypen zu achten. Auch wenn man Konstanten verwendet. Eine sorgfältige Programmierung und die Kenntnis der "Feinheiten" einer Programmiersprache helfen effektiveren Code zu schreiben, was ja gerade bei µControllern mit ihren i.d.R. nicht gerade üppigen Speicherressourcen, viel ausmacht. Man sollte also nicht nur darauf achten, ob sich ein Programm fehlerfrei compilieren lässt, sondern auch noch andere Aspekte betrachten.


    Die itoa32 Funktion ist ja in einem Mainprogramm eingebettet. Es wurde aber nur an dieser Funktion eine Änderung vorgenommen. Betrachtet man jetzt die 28 Byte für die itoa32 Routine zuzüglich der 152 Byte für die benötigten Divisionsfunktionen (erste Version = 180 Byte gesamt) und vergleicht das mit 28 Byte + 88 Byte = 116 Byte (zweite Version), so wurde eine Ersparnis von immerhin gut 35%, nur durch die Anpassung eines Datentyps, erreicht.


    Und man kann auch gut erkennen wieviel Ressourcen Divisionen "fressen". Also wenn es geht -> vermeiden oder zumindest reduzieren.


    Pius hat also durchaus recht mit seiner Aussage, dass die Compiler schon sehr effektiv sind. Jedenfalls ist mein Programm, welches ich weiter unten in Beitrag #9 als Assembler Variante geschrieben habe, auch nicht mehr kleiner als diese C-Version mit dem angepassten Datentyp. Und das liegt daran, dass genau diese Umstellung auf eine Divisionsroutine in meinem Assemblercode die Ersparnis ausgemacht hat. Alles andere drum herum lässt sich (zumindest bei diesem Beispiel) auch in Assembler nicht, oder nur unwesentlich verkleinern.

  • Ja Kai, ich denke da liegt nicht mehr drin.
    Aber es gibt immer jemand, der noch ein Byte rausholt, die Frage ist nur, ob sich der Aufwand lohnt. Früher, sorry ist eben bei den Alten so;(, habe ich meist den Code auf ein Minimum an Grösse optimiert, weil beim 8085 wollte ich meist nur ein 2k EPROM einsetzen. Diese waren damals wesentlich günstiger, als ein 4k EPROM. Wenn man sich dann minimale Size und maximale Ausführgeschwindigkeit auf den Hut schreibt, dann kommt man unter Druck, weil sich Beides nicht verträgt.

    Es lohnt sich meist auch nicht, weil man sich auf eine CPU beschränkt und damit die Arbeit nur auf dieser CPU eingesetzt werden kann. Grundsätzlich habe ich die Erfahrung gemacht, dass die von den Herstellern gelieferten Lib’s sehr effizient sind und daher habe ich die frühere Praxis, mir eigene optimierte Funktionen anzulegen, längst abgelegt. Erst kürzlich entsorgte ich einen Ordner mit den dokumentierten Funktionen (Z80). Ich nahm mir die Mühe, jede Funktion in ASM sauber dokumentiert zu drucken, um dann für mich wie ein Nachschlagwerk benutzen zu können. Pustekuchen, ich denke ich habe es nicht oft benutzt. Als dann PC Software meine Aufgabe wurde (lange vor Windows), schrieb ich eine Lib zur Text-Ausgabe auf einem ASCII Schirm (24Zeilen a 80 Zeichen), die es erlaubte so was wie Fenstertechnik (überlappend) mit Mauseunterstützung zu realisieren. Die ganze Lib war in 8086 ASM geschrieben und belegte am Ende lediglich knappe 10k Code. Bereits damals benutzte ich eine Metadatei, die die Ausgaben als Text beschrieb und dann bei der Ausgabe selbst umgesetzt wurde. Beispiel „Cxy,bhct“ erstellte ein Fenster an der Startposition XY mit der Grösse von bh und t bestimmte den Typ des Rahmens, wobei dann c für die Farbe des Fensters stand. Ein Mitarbeiter erstellte darauf einen Editor, mit dem man WYSIWYG die Metadatei erstellen konnte. Damit konnte ich etliche Konkurrenten jeweils überbieten, als mein Kunde sich an Vorgaben eines IBM Standards (Mainframes) für die Ausgaben halten wollte und bestehende SW auf den Standard angepasst werden mussten.


    Übrigens, den folgenden Vortrag zur Speedoptimierung (keine Microkontroller) finde ich spannend:


    Gruss

    Pius

  • Ich habe gerade noch einmal etwas probiert und eine Variante ohne Konstanten-Array ausprobiert. Der Divisor wird bei jeder Ziffer neu ermittelt.

    Das erhöht der Rechenaufwand, erspart aber das Array mit immerhin 10 konstanten 32 Bit Werten.



    Nun hat das Gesamtprogramm (inkl. UART Konfiguration) mit der Funktion wie sie in #11 gepostet wurde 588 Bytes, mit dieser Variante hier hat es 566Bytes. Wirklich wichtig ist es, die "divisor" Variable als unsigned long zu definieren! Definiert man sie als (signed) long, wird das Programm 646Bytes groß =O! Diese Variante hier, dürfte auch um einiges langsamer sein. Beim Testen ist das aber nicht merklich gewesen. Sie ist bis jetzt, speicherplatzmäßig, die sparsamste.


    Viel mehr ist da wohl nicht mehr drin... nehme ich an.

  • Alles klar, Kai


    aber ich schaue dann nochmals in das Dokument, weil wie immer eine beantwortete Frage auch gleich neue Fragen erzeugen muss.

    Und, selbstverständlich schaut man mit dem Disassembler ab, wie es gemacht wird, bevor man hunterte von Seiten Text durcharbeitet ist es logisch viel einfacher, wenn man den Werkzeugen auf die Finger, ehhm Bits schaut.



    Beim anderen Thema, deine Zeile:


    static unsigned long divisor[10] = {1,10,100,1000,10000,100000,1000000UL,10000000UL,100000000UL,1000000000UL};


    Beim Atmel würde ich die Tabelle (und dies ist es ja) im FLASH positionieren. Dann müsste der Compiler die Tabelle nicht ins RAM befördern. Dein Texas Prozessor ist ja bekanntlich nach Von-Neumann aufgebaut und trennt damit nicht den Daten- vom Programm-Bus. Aber ich würde die Tabelle ganz sicher zusätzlich als const Deklarieren, weil du diese Tabelle nie verändern wirst. Bei 10 Eintragungen a 32 Bit, belegt die Tabelle bereits 40 Byte, was ja nicht ganz ist. Vielleicht gibt es eine Mischform zwischen if() und Tabellenwerten? Aber ich hätte im Augenblick auch keine Idee dazu.

    Dann hast Du ja noch die Modulo Operation im Code. Belegt diese Operation viel Code Platz oder bleibt es da verkaftbar? Weil ...

    wenn Du Dir eine Tabelle für die Division anlegst könnte es unter Umständen nahe liegen, diese Tabelle auch für andere Operationen zu benutzen, in diesem Fall dürfte die Tabelle dann nicht mehr static innerhalb der Funktion liegen. Aber das weisst Du ja längst.

    Oder, wenn Du eine itoa16 brauchst, dann könnte man ja diese vielleicht auch bei der itoa32 mit benutzen (aber wie immer habe ich dies nicht fertig gedacht).

    schönen Abend

    Pius

  • Unsere Antworten haben sich überschnitten. Die Fragen zum Assembler: Die Parameter einer C/C++ Funktion werden immer ab dem Register R12 bis R15 übernommen. Reicht das nicht aus, wird zusätzlich der Stack mitbenutzt. Zumindest ist das in der CCS Umgebung so.


    Am Ende einer ASM-Funktion kann man Rückgabewerte ab R12 schreiben. Das "landet" dann wieder in einer C-Variable, um es mal platt auszudrücken.

    Bei itoa32 und uitoa32 habe ich so den Stringpufferwert zurückgegeben, damit man Konstruktionen wie *pPuffer = itoa32(char* puffer, uint32_t zahl) schreiben kann.


    Ist aber gut das Du fragst. Dadurch ist mir aufgefallen, dass ich vergessen habe jenes Dokument zu verlinken, in dem genau das beschrieben ist (SLAA140A).


    Ich habe die übergebenen Werte, je nach Funktion, in R6-R9 kopiert, um später wieder darauf zurückgreifen zu können, weil die Registerwerte zwischenzeitlich verändert und an anderer Stelle aber wieder benötigt werden.


    Dein Einwand bezüglich *.B(yte) und *.W(ord) ist berechtigt. Das war etwas inkonsequent von mir. Weil es sich um einen 16Bit Controller handelt ist

    *.W eigentlich Standard und müsste gar nicht benutzt werden. Darum funktioniert PUSH und PUSH.W genau gleich. Wie du richtig vermutet hast, steht *.B für byteweise Operationen. Ich habe das W (fast immer) drangehangen weil ich dachte es ist dann offensichtlicher, dass es sich um eine Wordoperation handelt. Bei den PUSH Befehlen habe ich es schlicht vergessen. Ich bin da noch nicht stilsicher ;)


    Ich habe den Code zugegebener Maßen nicht von der Pike auf gebaut. Der Disassembler hat mir da schon viel geholfen. Ohne, hätte ich das nie hinbekommen. Und auch da sind die Ausdrücke nicht konsequent mit Erweiterung aufgeführt. Ich wäre spätestens bei der Division gescheitert. Aber durch die Namen der disassembelten Sprungadressen habe ich die Literatur zum EABI gefunden. Die hat mir weiter geholfen. Außerdem hätte ich wesentlich mehr Zeit benötigt.

  • Hallo Pius,


    das macht richtig Freude mit Dir Gedanken auszutauschen. Da lerne ich doch immer wieder dazu. An die Variante, den Funktionszeiger der Ausgabefunktion mit an die "Umwandlungsroutine" zu geben, habe ich gar nicht gedacht. Wunderbar und ziemlich elegant. Super :):thumbup:.


    Ich habe deinen Code in einer 32 Bit Variante ausprobiert und es funktioniert sehr gut. Allerdings geht der Aufwand für die Divisor-Bestimmung sehr nach oben. Darum habe ich mir eine Variante mit einem Array ausgedacht. Das erspart auch eine Division, weil man den Array-Index als "Schalter" für die nächstkleinere 10er Potenz verwenden kann:

    Ich wüsste momentan aber nicht, wie man um den Speicherplatzverbrauch für die Konstanten herumkommt. Es sei denn, man rechnet wieder...


    Auch die gezeigte Division mit dem Bit-Shiften ist ja extrem gewitzt. Sowas hätte ich mir nie ausdenken können. Aber ich glaube, solche Tricks sind nicht notwendig. Dennoch sehr interessant.

  • Hallo Kai


    Das ist wirklich beeindruckend, dass Du es in so kurzer Zeit schaffst, eine Funktion in Assembler zu implementieren. Hut ziehe


    Ich hatte leider nur ganz wenig Zeit, mich in den Dokumenten, die Du referenziert hast, umzusehen. Bei der Sichtung der Funktion itoa32.asm ist mir aufgefallen, dass die zwei 16 Bit Parameter scheinbar in R6/R7 und in R8/R9 übergeben werden. Ist das die Standard Zuordnung des Compilers?


    Im Dokument slau132y.pdf, Seite 128 fand ich die Definition eines Funktionsaufrufs und war dann unsicher, zum einen, wegen der Beschreibung, dass die Aufrufende Funktion, der Parent, Stack anlegen soll. Diesen Teil muss ich nochmals nachlesen, weil ich zwar die Internas des Compilers nicht verstehen will, davon aber profitiere zu lernen, wie es andere Compiler intern handhaben.


    Weiter, habe ich es richtig verstanden, dass mnemonic.W als Beispiel den Befehl auf 16 Bit bezieht und mnemonic.B dann auf eine Byte Grösse? Wenn dem so ist, weshalb muss Du am Anfang der Funktion dann nur z.B: PUSH R6 aufrufen, beim Aufräumen dann aber POP.W?


    Ich bin nun gespannt, wie weit Du Deine Forschung da weiter treibst. Auf jeden Fall willst Du es wissen und nichts hindert Dich daran, Dir die Details anzusehen. Das war in früheren Jahren genau meine Vorgehensweise, nur ohne Internet und lediglich mit ein paar Büchern ausgerüstet.


    Übrigens, wenn nur mehr Leute ihr Hobby nur so aus Interesse fundiert festigen würden ... Du zeigst es ihnen, wie man es auch machen kann.

    Mein Respekt

    Pius

  • @Pius: ich hätte ja nicht gedacht, dass ich damit nochmal anfange, aber ich habe das Ganze mal in Assembler umgesetzt.

    Hab wieder viel gelernt :). Es wundert mich, dass ich das überhaupt in der recht kurzen Zeit hinbekommen habe.

    Es gilt ja in "Tonnen" von Dokumentation die richtigen Abschnitte zu finden. Demnächst nehme ich Dir deine letzte Variante mal vor. Momentan qualmt mir aber noch der Kopf.


    Ich habe drei Assembler Routinen gebaut, die auch aus C/C++ heraus aufrufbar sind.

    Die Funktionen sind:


    char* itoa32(char*, int32_t val);

    char* uitoa32(char*, uint32_t);

    void strrev(char*, uint8_t);


    Der itoa Befehl ist zwar auch in der MSP430 Toolchain vorhanden, allerdings

    - kann er nur bis 16 Bit verwendet werden

    - Die Funktion ist nur für signed int geeignet. Ein Wertebereich außerhalb von +32767/-32768 ist nicht darstellbar.

    - Die Funktion erzeugt Buchstabensalat wenn ein Zahlenüberlauf bei negativen Zahlen auftritt.


    Darum habe ich zwei Funktionen itoa32() und uitoa32() gebaut mit denen größere Wertebereiche dargestellt werden können. Einschränkung: Es funktioniert nur das dezimale Zahlensystem als Ausgabe. Die Funktion strrev() wird von beiden Funktionen benötigt, kann aber auch unabhängig davon verwendet werden. Die Funktion erledigt das schon in einem anderen Thread diskutierte Vertauschen einer Zeichenkette.


    Beim Assemblercode handelt sich um eine Umsetzung der itoa() Funktion zu dem in diesem Thread vom Knisterbein ein Teil des C-Codes gepostet wurde. Der Assemblercode spart noch ein paar Byte im Vergleich zur C-Variante ein (62 Byte). Das klingt nicht viel aber bei 2kb Flash, 128 Byte RAM kann es auf jedes Byte ankommen;). Die unsigned Variante uitoa32() ist wegen der nicht notwendigen Vorzeichenbehandlung noch etwas kleiner.


    Das itoa32 Objektfile ist 146 Byte groß, strrev braucht 26Byte und jetzt kommt es ... die 32 Bit Division braucht 88 Byte :/. Die Maindatei mit dem UART "Gedöhns" ist 224 Byte groß.


    Wen es interessiert:

    Hier ist der Code auf Github mit ein bisschen Beschreibung: MSP430-itoa32


    Es ist ein Beispielprogramm dabei, das Zeichen von einer seriellen Konsole entgegen nimmt und wenn ein "a" eingegeben wurde, wird eine Integerzahl in einen String umgewandelt und auf der Konsole angezeigt.


    Es wurde alles mit dem frei erhältlichen CCS (Code Composer Studio) von Texas Instruments umgesetzt. Doku zur Umsetzung ist in der README.md verlinkt.

  • Hallo Pius,


    vielen Dank für Deine Gedanken und Unterstützung. Ich werde mir den Code nach Weihnachten zu Gemüte führen.


    Bis dahin wünsche ich ebenfalls ein schönes Weihnachtsfest.

  • Hallo Kai


    anbei nun ein Beispiel, wie man mittels einer direkten Ausgabe der Zeichen sich den Zwischenspeicher einsparen kann. Damit ich ihn testen konnte (Simulation) benutzte ich in der Funktion myPut() aber trotzdem einen Buffer, aber dies hast Du sicher schnell verstanden.


    Mit dem Makro SUPREES_LEADZERO gesetzt, wird die Funktion alle führenden 0en unterdrücken, was auch für mich die schönere Weise darstellt.
    Zusätzlich wird im Falle SUPREES_LEADZERO gesetzt der Divisor nur so hoch angesetzt wie notwendig. Dies erspart vor allem Rechenaufwand, den man sich mit etwas mehr Code erkauft.

    Wichtig ist noch der Hinweis, dass ich in der Funktion void itos( fP fZeiger, int val) für die Division /10 einen Versuch mit einer Annäherung der Division versucht habe. Zu meinen früheren Zeiten (8080,8085 und Z80) waren solche Konstrukte oft benutzt, vor allem wenn man in Assembler keine Division schreiben wollte. Ich war erstaunt, als ich mich im Netz danach auf die Suche machte, dass diese Lösungen doch nicht ganz vergessen gingen. Ich würde Dir empfehlen, dies mit deinem Compiler auszuprobieren, um heraus zu finden, ob es in deinem Fall möglicherweise weniger Code erzeugt.

    Da der Lösungsvorschlag mit dem Funktionszeiger nur dann effektiv funktioniert, wenn die Zeichen in der Folge von links nach rechts produziert werden, bin ich beim ersten Ansatz geblieben.


    Wenn Du die Lösung mit gespiegelter Zahl bevorzugst, dann empfehle ich anstelle des Umdrehens des Strings, eine direkt Ausgabe des Strings von Hinten nach Vorne , da du die Länge des Strings bekanntlich kennst, ist dies sehr leicht zu bewerkstelligen.

    Weil das Beispiel klein bleibt, habe ich auf auf separate *.h und *.c Dateien verzichtet. Wie ich gesehen habe, ist dir die Benutzung von C in C++ geläufig, weshalb ich mir dies schenkte.


    so, nun wünsche ich Dir,Deiner Familie und selbstverständlich allen anderen Mitlesern schöne, geruhsame Feiertage und bleibt gesund.

    Pius



  • Ja, die reverse Funktion kommt mir bekannt vor.

    Die einfachste Lösung ist wirklich den tmp Buffer auf dem Stack anzulegen, dann Ausführen, was man immer will und fertig.


    {

    char buffer[BUFFER_SIZE_L]; // lokaler Speicher auf dem Stack anlegen

    itos(val, buffer); // konvertieren

    UART_put(buffer); // nun den Inhalt des Buffers ausgeben

    } // an dieser Stelle wird buffer wieder den Stack zurück gegeben.


    Oder dann gleich eine Funktion für die Uart:

    void sendInteger(int32_t val)

    {

    char buffer[BUFFER_SIZE_L]; // lokaler Speicher auf dem Stack anlegen

    itos(val, buffer); // konvertieren

    UART_put(buffer); // nun den Inhalt des Buffers ausgeben

    }


    Malloc() ist nicht ideal, weil es viel mehr erledigen muss, als nur Speicher zu liefern. Damit der Speicher wieder zurückgegeben werden kann, und dies meist nicht in der Reihenfolge wie der Speicher angefordert wurde, muss malloc() eine verkettete Lise mit Zeigen aufbauen, die wiederum Speicher benötigt und Zeit zur Verwaltung kostet. Auf grossen Controllern kann es Sinn machen (was ich bisher kaum angetroffen habe).

    Ich schicke dir morgen noch ein Beispiel mit einem Funktionspointer für die Ausgabe. ABER, die funktioniert nicht mit einer Ausgabe die gedreht werden müsste. Und deinen Code muss ich mir dann nochmals genau ansehen, als ich es jetzt tun konnte.


    schönen Abend
    Pius

  • @Pius,


    ich habe Deine Vorschläge weitestgehend umgesetzt. Mit dem Puffer muss ich nochmal schauen. Es sind jetzt zwei Hilfsfunktionen herausgekommen. Eine für signed long int und eine für unsigned long int. Beide Funktionen sind nach dem von dir geposteten itoa Beispiel umgesetzt.


    Die Funktion, welche den String umdreht, dürfte Dir auch bekannt vorkommen ;).


    Das Ganze sieh jetzt so aus:



  • Hallo Pius,


    vielen vielen Dank für deine Ausführungen und die Anmerkungen in den zwei Dateien. Da habe ich erst mal etwas damit zu tun mir das anzuschauen. :thumbup:


    Ich habe zwischenzeitlich auf Github gesucht und mir ein paar Quellcodes angeschaut. Dabei musste ich feststellen, dass doch viele im Wesentlichen die itoa Variante benutzten. Ich habe es in meinem Code versucht anders herum zu machen, damit am Ende der "String" nicht umgedreht werden muss. Aber bei unterschiedlichen Wortlängen ist das umständlicher, weil der Divisor jedes mal angepasst werden muss (int, long int usw.).


    Darum werde ich auf die von Dir vorgeschlagene Version umsteigen. Wie man effektiv ein Array umdreht, hatten wir hier ja schon in einem anderen Thread ;).


    Was den Speicher betrifft, erschien mir eine "dynamische" Variante zu aufwändig. Letztendlich muss der Speicher verfügbar sein, sei es statisch oder dynamisch. Ich vermute Du meinst mit "Buffer auf dem Stack anlegen" die "malloc" Funktion. Jedenfalls muss man sich hier auch wieder um die Freigabe des Speichers kümmern. Ausprobiert habe ich das aber noch nicht.

    Ob der Buffer jetzt lokal oder global ist, sehe ich jetzt recht leidenschaftslos. Ich habe es bei meinem Versuch lokal gemacht, weil ich sonst keinen Buffer brauchte. Außerdem ist es in der Funktion gekapselt und man muss sich "außerhalb" keine Gedanken drum machen.


    Meine Intension ist tatsächlich nur eine oder mehrere Hilfsfunktionen für die serielle Ausgabe. Ich denke das funktioniert ohne Zwischenspeicher, wenn man die UART Ausgabe direkt bei der Zahlenkonvertierung durchführt. An einem Beispiel bin ich natürlich interessiert.


    Aber ich werde mir jetzt erst mal Deine Dateien anschauen.


    Danke noch einmal :).

  • Hallo Kai


    Deine Frage, wenn ich sie an mich gerichtet sehe, ist natürlich nicht ganz fair, da du weisst, dass ich meist nur mit dem Microchip Studio arbeite. Dein Code funktioniert ja grundsätzlich und viel lässt sich da auch nicht optimieren, das die „guten“ Compiler nicht selbst erledigen können. Da kommst du nicht drumherum, selber auszuprobieren, welche Version dir den kleinsten Code erzeugt. Aber sicher ist, dass du mit einer 16-Bit CPU (und das ist bei den MSP430 ja der Fall) mit Zeigern kürzeren Code erhalten müsstest. Was manchmal auch ein Vorteil sein könnte ist in diesem Fall die Von- Neumann Architektur, die, so vermute ich, mit Konstanten die in ein Register geladen werden müssen auch besser umgehen kann.


    Richtig ist Deine Vermutung, dass das Einbinden von stdlib Code manchmal sehr viel Drumherum mit verlinken muss, aber dies hängt wirklich von der Implementierung ab. Als erstes würde ich mir die Details der itoa() Funktion ansehen und ob Diese nicht auch einzeln aufrufbar ist, auch wenn die Funktion nicht im K&R Standard definiert wurde, wird sie doch oft benutzt. Meist in etwas wie folgt:


    Wenn ich im Datenblatt dann das folgende sehe (unter den Operand Addressing Modes Seite 48):

    11/- Indirect autoincrement @Rn+ Rn
    is used as a pointer to the operand. Rn is incremented afterwards by 1 for .B instructions and by 2 for .W instructions.

    dann sind Statements wie *pDest++ = x;

    für den Compiler sehr effizient umsetzbar.

    Was, wie beim Atmel, nicht effizient gelöst werden kann, ist die Division, da beide CPU’s keine Befehl für die Division implementieren.


    Wie gesagt, ich habe mir den Code nur mit dem Studio angeschaut, und jeweils die Veränderung in einer eigenen (auskommentiert) Funktion angeschaut. Diese Vorgehensweise kannst du auch mit Deinem Compiler anwenden und die Resultate untereinander vergleichen. Achtung, dies sind nur Vergleichswerte und so sollte man sie auch betrachten.

    Nach dem RAM Bedarf hast du zwar nicht gefragt, dieser aber wird dir, vor allem mit der UART dann auch irgendeinmal ins Gewicht fallen. In deinem Code benutzt du einen isolierten Buffer mit


    static char buffer[BUFFER_SIZE];


    angelegt, der nur innerhalb deiner Funktion char* itos(int val) benutzt werden kann. Diese 7 Byte sind somit fix vergeben und werden NUR von itos() benutzt. Als Alternativen kann man einen globalen Buffer benutzen (ich weiss, man sollte es so wenig wie möglich benutzen) oder man übergibt jeweils den Zeiger auf einen Buffer, der dann auch lediglich auf dem Stack angelegt werden kann und damit nach der Benutzung wieder frei gemacht wird.


    Wird die Funktion itos() tatsächlich nur für die Ausgabe der int’s auf die UART benutzt, dann gäbe es eine weitere Möglichkeit, die ohne irgendwelchen Zwischenspeicher umzusetzen. Bei Interesse schicke ich gerne ein Beispiel dazu.


    Der Vollständigkeitshalber lege ich meine Testdatei bei.
    Jede Funktionsversion ist mit Kommentar "Debug Mode F, R" versehen. Relative Werte F für Codegrösse und R für Ram.


    Vielleicht hilft Dir mein Gewaschel etwas weiter
    Gruss

    Pius

    Dateien

    • Itos.c

      (7,09 kB, 1 Mal heruntergeladen, zuletzt: )
    • Itos.h

      (814 Byte, 1 Mal heruntergeladen, zuletzt: )
  • Hallo zusammen,


    da µController oft sehr wenig Speicher haben muss man doch so manches selber erledigen, weil fertige Bibliotheksfunktionen zu viel Speicher benötigen.

    Bisher habe ich es mir bei der seriellen Übertagung recht einfach gemacht, indem ich mir einen String mit der sprintf Funktion zusammen gebaut habe, wenn ich Integer-Variablen auf der seriellen Konsole ausgeben wollte. Oder ich habe das allseits bekannte Serial.print() vom Arduino Framework verwendet.


    Nun habe ich aber einen µController der sehr wenig Speicher hat und deshalb keine Systembibliotheken verwendet werden können. Um dennoch Werte seriell ausgeben zu können, muss ich mir selber aus Integer-Variablen einen String bauen.


    Beispiel:


    Diesen Code angewandt, kann ich beispielsweiser mit:

    eine Integer-Variable über eine serielle Schnittstelle ausgeben.


    Meine Frage bezieht sich auf obigen Quellcode. Geht das noch besser, schneller, kürzer? Hat jemand einen Tipp für mich?