Forum: Projekte & Code Zahlenausgabe


von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Die übliche Methode ist printf(). Leider wird dabei eine Menge
Programmspeicher und Rechenzeit benötigt. Auch kann printf()
Kommazahlen nicht in der technischen Schreibweise ausgeben.
Deshalb habe ich mir meine eigenen Routinen geschrieben.


Für die Ausgabe von Ganzzahlen ist auf CPUs ohne Divisionsbefehl die
Subtraktionsmethode am schnellsten und Code sparendsten. Die
Subtraktionswerte für die einzelnen Digits werden in einer Tabelle
definiert. Damit ist es ein leichtes, die Routine auf long zu
erweitern.


Die Kommazahlen werden in der technischen Schreibweise (Zehnerpotenzen
durch 3 teilbar) dargestellt. In meiner Anwendung werden 10^-3 (mA) und
10^3 (kV) benötigt.

Die Ausgabe erfolgt über putchar(), d.h. standardmäßig über die UART.
Mit einem eigenen putchar() kann man z.B. auch auf ein LCD ausgeben.
Man kann aber genauso gut in den RAM schreiben.


Die Routinen wurden auf einem 8051 implementiert.
Für die Verwendung auf dem AVR sind 2 Dinge zu beachten:

Beim Keil C51 gibt es die genialen Generic Pointer. D.h. man kann
definieren in welchen Speicherbereich Variablen abgelegt werden und der
Compiler kümmert sich automatisch darum, welche Zugriffsbefehle richtig
sind.
Beim AVR-GCC muß man dagegen für den Flash spezielle Zugriffsmacros
bzw. extra Funktionen verwenden.


Beim Keil kann man alle Bibliotheksfunktionen durch eigene ersetzen.
D.h. der Linker löst die Referenzen zuerst anhand des eigenen Codes auf
und erst danach über die Bibliotheken.
Beim AVR-GCC muß man dagegen für putchar() einen anderen Namen
verwenden, wenn die Ausgabe über eigene Funktionen erfolgen soll.


Peter

von Dirk (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe mal die guten Funktionen auf AVRGCC potiert. Ich hoffe
jemanden hilft es weiter.

Es sollten keine Fehler versteckt sein. Wuerde mich aber trotzdem
freuen wenn eine erfahrene Person drueberschaun wuerde.

Gruß,

Dirk

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Hi Leutz,

bin mal so frei und poste den Link mit meinem 180Byte / 4Byte printf 
hier ;-)

Beitrag "Re: smartes printf"

Peter war so nett und hat mich drauf hingewiesen.

Ein paar stellen sind böse (zeigervergawaltigung), dafür ists klein und 
schnell ;-)


Greetz,
th.

von Peter D. (peda)


Lesenswert?

Thorsten Db wrote:
> Ein paar stellen sind böse (zeigervergawaltigung), dafür ists klein und
> schnell ;-)

Meine ist aber noch kleiner und schneller, wenn Du die float Ausgabe 
wegläßt.

Du mußt ja noch die Divisionsfunktion bei Dir mit dazu zählen.
Und wie schon oben gesagt, Subtraktion ist schneller als Division.


Und Zeigeroperanden sind auf dem AVR grundsätzlich langsamer und 
aufwendiger als Registeroperanden, da der AVR nicht im SRAM rechnen 
kann. Er muß also immer erst den Wert aus dem SRAM in Register holen, 
rechnen und wieder zurück speichern.


Peter

von bst (Gast)


Lesenswert?

@Peter Dannegger:
Deine main3.c scheint ja wirklich interessant zu sein, aber teilweise 
ist mir der Code zu hoch um ihn zu verstehen.
Könntest du ihn z.B. ausführlicher kommentieren?
Beispielsweise verstehe ich schon einmal nicht, was bei deinen 
Konstanten PROGMEM bedeutet. Die Shift und Verknüpfungsoperationen sind 
ja auch nicht ohne...

von Aufmerksamer (Gast)


Lesenswert?

Ist irgendjemanden schonmal aufgefallen das der Herr Dannegger an 
fremden codes immer was zu kritisieren hat wenn er etwas ähnliches 
programmiert hat? Mir schon.

von Karl H. (kbuchegg)


Lesenswert?

Ganz ehrlich:
Der Code ist sogar ziemlich einfach zu verstehen.

PROGMEM bedeutet einfach nur, dass besagte Variable
(eigentlich Konstante) im Flash anstatt im SRAM
abgelegt wird. Demenstrechend muss dann mittels
spezieller Funktionen auf die Werte zugegriffen werden.

Wie die Hex Ausgabe funktioniert sollte unmittelbar einsichtig
sein.

Die Int-Ausgabe funktioniert nach dem Prinzip:
Ziehe solange 10000 ab bis die Zahl kleiner als 10000
geworden ist und zähle mit wieviele Subtraktionen das
waren. Diese Anzahl ist dann unmittelbar die 10000er Ziffer.
Dann das ganze mit 1000, mit 100 und mit 10

Die Float-Ausgabe ist ebenfalls nicht schwer zu verstehen.
Nach einem Vorgeplänkel, indem festgestellt wird ob ein
Exponent extra ausgegeben werden soll oder nicht (Variable ep)
wird dir Zahl mal normiert, sodass der Vorkommaanteil
kleiner 10 aber größer 0 ist und sich alle anderen Stellen
rechts vom Komma befinden. Mittels einem (impliziten) cast
nach int, wird der Vorkommaanteil abgetrennt und ausgegeben.
Danach wird der Nachkommaanteil vom Vorkommaanteil abgetrennt
und mit einer Multiplikation mit 10 wird die nächste Stelle
vor das Komma geholt. Und so gehts in der Schleife weiter
und weiterk, bis genug Stellen ausgegeben sind.

Die Dinger sind wirklich nicht schwer zu verstehen.
Bau sie in ein Testprogramm ein und steppe sie im Debugger
durch. Danach sollte im Detail klar sein, was wann wo
passiert.

von bst (Gast)


Lesenswert?

Ach das macht PROGMEM, aber was ist dann mit defines, werden die nicht 
auch in den Flashspeicher geschrieben?

von bst (Gast)


Lesenswert?

Was macht DIGITS genau? Was müsste ich z.B. tun, wenn ich 
outfloat(123.3253) aufrufe, aber nur eine Nachkommastelle ausgeben 
möchte?

von bst (Gast)


Lesenswert?

Wo ist DIGITS definiert? Der Compiler findet es gar nicht.

von Peter D. (peda)


Lesenswert?

Aufmerksamer wrote:
> Ist irgendjemanden schonmal aufgefallen das der Herr Dannegger an
> fremden codes immer was zu kritisieren hat wenn er etwas ähnliches
> programmiert hat? Mir schon.


Es ist vollkommen natürlich, wenn sich jemand auch schon mit dem 
gleichen Thema beschäftigt hat, daß dieser einschätzen kann, welche 
Lösung welche Vorteile hat.

Und da wäre es doch grob unhöflich mit den Vorteilen hinter dem Berg zu 
halten.
Nur durch Kommunikation kann sich auch was verbessern.
Ohne Kommunikation säßen wir immer noch in Höhlen und würden Beeren 
sammeln.


Es ist also überhaupt nichts daran, was einem extra auffallen muß.

Zu kritisieren wäre es höchstens, wenn einer Kommentare abgibt, ohne was 
von der Sache zu verstehen.



Peter

von Peter D. (peda)


Lesenswert?

bst wrote:
> Ach das macht PROGMEM, aber was ist dann mit defines, werden die nicht
> auch in den Flashspeicher geschrieben?

defines sind Präprozessoranweisungen.

Mit Flash hat das überhaupt nichts zu tun.


Peter

von bst (Gast)


Lesenswert?

@DIGITS:
Hat sich erledigt schäm

@PROGMEM:
Rentiert sich das aber nur bei Controllern die wenig SRAM haben, oder?
Denn Zugriffe aufs Flash sind ja langsamer? Oder gibt es da noch was, 
was ich nicht sehe?

von bst (Gast)


Lesenswert?

Nein, ganz lässt mich DIGITS doch nicht in Ruhe:
Was ist, wenn ich der Funktion outfloat eine Zahl übergebe, die sehr 
viele Nachkommastellen hat? Was passiert dann beim "Normieren"? Fallen 
die hintersten Nachkommastellen raus?

von falk (Gast)


Lesenswert?

Wie viele Nachkommastellen hat ein double eigentlich beim avr-gcc? Ist 
das irgendwo definiert?

von Karl heinz B. (kbucheg)


Lesenswert?

> Wie viele Nachkommastellen hat ein double eigentlich beim avr-gcc? Ist
> das irgendwo definiert?

Nein.

Zuallererst: Auf einem AVR sind double und float gleich
gross. Beide belegen 4 Bytes.

Mit 4 Byte Floating Point Arithmetik kannst du davon ausgehen
dass du in etwa 4 bis 5 bis 6 signifikante Stellen hast.

Achtung: signifikante Stellen sind nicht gleich Nachkommastellen,
sonder das ist quasi 'alles in allem'.

Bei einer Zahl 2345.6789
sind also vor dem Komma bereits 4 Stellen aufgebraucht worden,
sodass nur noch in etwa 1 bis 2 Nachkommastellen (wenn überhaupt)
einen sinnvollen Wert aufweisen. Alles was danach kommt ist
sowieso 'gelogen'.

von bst (Gast)


Lesenswert?

OK, aber wie soll man DIGITS im Code definieren?

von Aubacke (Gast)


Lesenswert?

Hallo zusammen,

ich lerne ja auch gerade ein wenig und habe mir die Lösung für den AVR 
aus main.c angeschaut. Ich frage mich nun, ob auf uCs bei der Division 
von Integern auch das ganzzahlige Ergebnis zurückkommt - sprich, ob man 
es auch wie folgt lösen könnte:

uint16 feld[4] = {10, 100, 1000, 10000};

for (i=3, d='0'; i>=0; i--, d='0')
{
  uint16 j = uval / feld[i];
  if (j>0)
  {
    d += (uint8)j;
    uval -= j*feld[i];
    uart_putc(d);
  }
} uart_putc((uint8)uval + '0');

Damit ich weiterlernen kann, erwarte ich natürlich Kritik/Analyse...
Vielen Dank schonmal...

von Peter D. (peda)


Lesenswert?

@Aubacke

Probier doch einfach mal selber aus im Simulator, was schneller geht.

Die Division im Binärsystem erfolgt bitweise, d.h. die Routine für eine 
16Bit-Division ist eine Schleife mit 16 Durchläufen.

Daher sollte eigentlich eine Subtraktionsschleife mit max 9 Durchläufen 
schneller sein.


Peter

von Peter D. (peda)


Lesenswert?

bst wrote:
> OK, aber wie soll man DIGITS im Code definieren?

Die Gesamtzahl der gewünschten Stellen.


Peter

von Karl heinz B. (kbucheg)


Lesenswert?

Peter Dannegger wrote:
> bst wrote:
>> OK, aber wie soll man DIGITS im Code definieren?
>
> Die Gesamtzahl der gewünschten Stellen.
>
>
> Peter

Wenn ich das richtig gesehen habe, dann sollte
DIGIT nicht kleiner als 3 gewählt werden. Ansonsten
kann es sein, dass die Schleife abgebrochen wird noch
ehe der Dezimalpunkt ausgegeben wird. 999 würde bei
DIGIT = 2 als 99 ausgegeben.

Ich würde auch noch folgende kleine Modifikation
vornehmen:

    while( val < 1 ){
      val *= 1000;
      ep -= 3;
    }

    while( val >= 1000 ){
      val *= 0.001;
      ep += 3;
    }

OK. Auf einem µC hat man meist keine wirklich großen
Zahlen. Wenn aber doch, dann ist man so auf der sicheren
Seite. Und der while anstelle des if kostet ja auch nicht
die Welt.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Hi Leutz,

um mal wieder etwas Bewegung in diesen Thread zu bringen ... ich suche 
immer noch nach einer besseren Lösung für dieses Codefragment:
1
case 'i':
2
{
3
  int_tmp = va_arg(tag, int);
4
      
5
  if(int_tmp <0)
6
  {
7
    fputc('-', 0);
8
    int_tmp=-int_tmp;
9
  }
10
          
11
  pointer = (char*)int_tmp;
12
  for(i=MAX_NUM_DEC; i; i/=10)
13
  {                        if((int)pointer/i)
14
    {           
15
      fputc((int_tmp/i)+48, 0);
16
      int_tmp%=i;
17
    }
18
    else if(i<10) fputc(0+48, 0);
19
  }        
20
} break;


Mir fällt allerdings so recht keine Variante ein, die die Stellen eines 
int von "links nach rechts" abrechnen könnte, ohne diesen begrenzenden 
10^irgendwas einzufügen und abzudividieren, oder die max. Stellenzahl 
über eine vorgesetzte Schleife zu ermitteln.



btw.: Gibtz eigendlich in C ne Möglichkeit, um diese beiden Ausdrücke 
zusammenzuführen?
1
#define MAX_NUM_LEN  6  ///<  max. chars for a number
2
#define MAX_NUM_DEC  10E06  ///< Put the same Number here as in MAX_NUM_LEN

a'la
#define MAX_NUM_DEC  10E0MAX_NUM_LEN o.ä.?


Greetz,
/th.




von Karl H. (kbuchegg)


Lesenswert?

Thorsten De buhr wrote:
> Mir fällt allerdings so recht keine Variante ein, die die Stellen eines
> int von "links nach rechts" abrechnen könnte, ohne diesen begrenzenden
> 10^irgendwas einzufügen und abzudividieren, oder die max. Stellenzahl
> über eine vorgesetzte Schleife zu ermitteln.

Du könntest so wie hier
http://www.mikrocontroller.net/articles/FAQ#Eigene_Umwandlungsfunktionen
die Generierung von rechts nach links machen lassen und den
entstehenden String danach spiegeln. Spart ein paar Divisionen
ein.

von Karl H. (kbuchegg)


Lesenswert?

Thorsten De buhr wrote:
> btw.: Gibtz eigendlich in C ne Möglichkeit, um diese beiden Ausdrücke
> zusammenzuführen?
>
>
1
> #define MAX_NUM_LEN  6  ///<  max. chars for a number
2
> #define MAX_NUM_DEC  10E06  ///< Put the same Number here as in
3
> MAX_NUM_LEN
4
> 
5
>
>
> a'la
> #define MAX_NUM_DEC  10E0MAX_NUM_LEN o.ä.?
>

Ich hab eine Lösung. Schön ist sie aber nicht
1
#include <stdio.h>
2
3
#define MAX_NUM_LEN  6
4
#define TOKEN(X) X
5
#define FORM(a,b) TOKEN(a)##TOKEN(b)
6
7
const double MaxNum = FORM(10E, MAX_NUM_LEN);
8
9
int main()
10
{
11
   printf( "%d\n", MAX_NUM_LEN );
12
   printf( "%lf\n", MaxNum );
13
}

  

von Random .. (thorstendb) Benutzerseite


Lesenswert?

danke für die idee, probier ich gleich mal aus :-)

Es geht auch nicht um schön, sondern nur um klein g (Wenns schön sein 
soll, sähe es anders aus und wäre nicht voll mit so fiesen 
verpointerungen)
Sieht irgendwie aus, als wenn du den parser veräppelst :)
## war irgendwas mit string zusammenbauen über den parser, richtig?


wenn ich diesen ausdruck FORM(10E, MAX_NUM_LEN) in der for-schleife 
einsetze, müsste doch nach dem parsen da 1000000 rauskommen, oder wird 
da doch (weil ich was übersehen habe) was komplizierteres raus?

Sinn der sache ist, dass ich da nicht MAX_NUM_DEC reinschreiben kann, 
sondern eine Stellenzahl als Ziffer angeben kann.

---
zu string spiegeln:
ein paar divisionen sind nicht weiter wild. Ich möchte keine 12Bytes 
ausgeben, um eine 4G-Zahl incl. '\0' als string zu speichern. In dem 
Fall wärs ja einfach, in jedem durchlauf %10 und /10, das spart auch die 
sache mit dem MAX_NUM_DEC und den "verschwendeten" Divionen auf die 
führenden Nullen.

Man könnte das auch rekursiv lösen, was aber mit jeder rekursion 
speicher im heap belegt.

Sinn der sache ist, (auch aus spass an der sache) möglichst kleinen 
C-code zu schreiben, der ein minimales, aber ausreichendes printf 
darstellt.
Ist ausserdem auch interessant, was man alles machen kann, und zu 
schauen, was der Compiler draus macht.

So hab ich bei meinen Spielereien heraisgefunden, dass (Keil µVision, 
ARM RealView Compiler, -O3)
1
while(fputc(*pointer++, 0));

den Code 2 Bytes länger macht als:
1
do
2
{
3
  fputc(*pointer, 0);          
4
} while(*pointer++);

(auch wenn man davon absieht, dass der Einzeiler den String nicht 
korrekt ausgibt :-) (Spielerei, Compilertest)


Anm.: Eigentlich darf man dem fputc keine 0 als zweiten Parameter 
übergeben, M$ VC z.B. schimpft dann (Nullpointer).
Da ich die fputc aber selbst geschrieben habe und den FP ignoriere 
(retarget auf uart) ist das egal, muss halt nur stdio-konform sein mit 
der parameterliste.
Sauberer ist's über die retarget.c von Keil µVision.

Greetz,
/th.

von Random .. (thorstendb) Benutzerseite


Angehängte Dateien:

Lesenswert?

bin ich eigendlich zu do*f nen anhang anzuhängen?
...irgendwie krieg ichs nciht hin, ne simple .c mit anzufügen... geht 
net!


korr: Nu ist das ding dran ... versteh das einer ... oder gehts nur beim 
bearbeiten nciht ?!?

von Random .. (thorstendb) Benutzerseite


Lesenswert?

mit den ## und TOKEN ...

Keil µVision und ARM RV Compiler schimpft:

src\thprintf.c(65): error:  #166: invalid floating constant

bei:
const double MaxNum = FORM(10E, MAX_NUM_LEN);

:(


---
ähnliche Geschichte hier:
1
else if(*string == '\\') 
2
{          
3
  switch(*(++string))
4
  {
5
    case 'n': fputc('\n', 0);
6
    break;
7
          
8
    case 't': fputc('\t', 0);
9
    break;
10
  }  
11
} // end if '\'

Gibt es eine Möglichkeit, ein konstantes Zeichen '\' und eine Variable 
(z.B. t für tab oder n für newline) zu einem (hier sonder-)zeichen 
zusammenzuführen? Oder ist das vom code her aufwänsdiger als ein 
stumpfes switch?


Greetz,
/th.

von Karl H. (kbuchegg)


Lesenswert?

Thorsten De buhr wrote:
> mit den ## und TOKEN ...
>
> Keil µVision und ARM RV Compiler schimpft:
>
> src\thprintf.c(65): error:  #166: invalid floating constant
>
> bei:
> const double MaxNum = FORM(10E, MAX_NUM_LEN);
>
> :(

Hmm. Ich habs mit MS VC++ probiert und solange rumprobiert,
bis ich s hin gekriegt habe.

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.