Forum: Projekte & Code Kleiner (s)printf-Ersatz mit Q-Format


von Johann L. (gjlayde) Benutzerseite


Angehängte Dateien:

Lesenswert?

Hi,

anbei eine Implementierung einer (s)printf - ähnlichen Funktion.

Wie (s)print nimmt sie einen Format-String; danach folgt eine variable 
Argumentanzahl.

Neben Dezimal-, Hex- und Binärzahlen können auch ein paar Q-Formate 
ausgegeben werden:

signed 1.15
signed 1.31
signed 4.12

Die Funktion ist nicht so allgemein wie printf und nicht überall sind 
die %-Codes gleich, dafür können %-Formate deaktiviert werden, die nicht 
gebraucht werden. Das spart Platz.

Die Aktivierung der %-Codes geht über ein Define, das man zB per 
Kommandozeile setzen kann. Der Compiler/Linker kann das ja nicht wissen, 
weil er nicht weiß, welche %-Codes im Formatstring stehen werden.

Insgesamt verbraucht das Modul etwa 1k RAM wenn alles aktiviert ist; 
32-Bit Division oder -Multiplikation wird nicht verwendet, auch keine 
sperrigen Tabellen mit Zehnerpotenzen.

Lässt man nur %d und %u für 16-Bit Dezimalzahlen aktiv, braucht das 
ganze mit meinen Compiler-Einstellungen nur 350 Bytes an Code.

Die Ausgabe selbst geht über eine Callback-Funktion, die ausgerufen 
wird, wenn ein Zeichen auszugeben ist.

Die formatierte Ausgabe ist geschrieben für avr-gcc.

Das Beispielprogramm ist geschrieben für einen ATmega8 @ 1MHz, der die 
Zeichen per 9600 Baud 8N1 auf die serielle Schnittstelle ausgibt. Die 
Ausgaberoutine selbst ist natürlich unabhängig vom gewählten AVR.

Nach dem Programmstart kommt die Ausgabe
1
Hallo, hier ist die Demo.
2
Freier RAM-Speicher: 946 Bytes von 1024 Bytes RAM insgesamt,
3
davon statisch belegt: 58 Bytes
4
ESC -> Reset
5
M   -> RAM-Verbrauch
6
- 0 1 2 3 4 5 6 7 8 9 0 -> Zahl eingeben
7
ENTER                   -> Zahl auf 0 setzen

Und so sieht die Ausgabe aus, wenn man zB "-100" eingibt:
Bei 1.31 Q-Format wird das interpretiert als -100 / 2**31 = 0.0000000465 
etc.:
1
%d: -100
2
%u: 65436
3
%l: -100
4
%x: 0x9c
5
%X: 0xff9c
6
%Y: 0xffffff9c
7
%q: -0.00305
8
%Q: -0.0000000465
9
%v: -0.0244
10
%b: 0b11111111 11111111 11111111 10011100

Als Goodie gibt's im ZIP ne ELF und ne HEX, und wenn man 'M' drückt, 
wird der noch freie RAM-Speicher angezeigt.

J.

von internett (Gast)


Lesenswert?

(Nur als Hinweis - es gibt in der Codesammlung auch einige andere 
Ausgaberoutinen für Strings bzw. spezialisiert für Messwerte, die 
weniger Programmspeicher benötigen als printf.)

von gast (Gast)


Lesenswert?

welche denn?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

internett wrote:
> (Nur als Hinweis - es gibt in der Codesammlung auch einige andere
> Ausgaberoutinen für Strings bzw. spezialisiert für Messwerte, die
> weniger Programmspeicher benötigen als printf.)

Es geht ja nicht darum, in ne Ausgabe irgendwo ein Komma reinzufrickeln 
;-)

Ausserdem ist es zB einfacher, einen 32-Bit Q-Format auszugeben als 
einen 32-Bit Integer. Und um von dem Q zum long/short zu kommen müsste 
man erst mit einer krummen Konstanten multiplizieren. Das würde Code 
kosten und Genauigkeit verlieren.

Ich verwende Q-Format um Berechnungen zu machen und nicht nur für die 
Ausgabe.

von Peter D. (peda)


Lesenswert?

Johann L. wrote:
> Es geht ja nicht darum, in ne Ausgabe irgendwo ein Komma reinzufrickeln
> ;-)

Um für den Benutzter z.B. 100,00°C anzuzeigen, ist das aber sehr 
nützlich.


> Ausserdem ist es zB einfacher, einen 32-Bit Q-Format auszugeben als
> einen 32-Bit Integer.

Kannst Du mal erklären, wozu man dieses Q-Format braucht?
Ich hatte bisher nicht gewußt, daß es sowas überhaupt gibt.


Peter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger wrote:
> Johann L. wrote:
>> Es geht ja nicht darum, in ne Ausgabe irgendwo ein Komma reinzufrickeln
>> ;-)
>
> Um für den Benutzter z.B. 100,00°C anzuzeigen, ist das aber sehr
> nützlich.

Ja klar, in dem Falle ist die Temeratur intern als 10000 dargestellt, 
und nicht als zB Q8.8 mit jeweils 8 Bits Vor- und Nachkommastelle. Als 
Dezimalzahl interpretiert wäre 100,00 darin 100,00 * 2**8, also 25600.

>> Ausserdem ist es zB einfacher, einen 32-Bit Q-Format auszugeben als
>> einen 32-Bit Integer.

> Kannst Du mal erklären, wozu man dieses Q-Format braucht?
> Ich hatte bisher nicht gewußt, daß es sowas überhaupt gibt.

http://en.wikipedia.org/wiki/Q_(number_format)

Ich verwende es für Arithmetik, in der ich Nachkommastellen brauche. Oft 
weiß man den Zahlenbereich, um den es geht. Bei einem signed Q 1.x hat 
man 1 Vorzeichenbit und x Nachkommastellen.

Da das Komma immer an der gleichen Stelle ist, kann man normale 
Addition, Subtraktion sowie Vergleichs- und Komplementbildung verwenden.

Bei der Multiplikation zweier Zahlen mit Komma an Stelle K1 und K2 hat 
das Produkt das Komma an Stelle K1+K2, so daß man zurechtrücken muß. 
Ausser natürlich für ganze Zahlen, wo das Komma ja an Stelle 0 steht.

Viele Architekturen übernehmen das Schieben nach der Multiplikation ohne 
Genauigkeits- oder Zeitverlust. Wenn man von Hand schieben würde, um das 
Ergebnis anzupassen, würde zwangsläufig eine 0 nachgeschoben.

Bei AVR sind das die Instruktionen der fmuls-Familie, welche Q1.7 
miteinander multiplizieren. Daraus kann man sich komplexere Sachen 
zusammenbauen wie Q1.15 oder Q1.13.

Nehmen wir mal an, wir arbeiten mit Zahlen in (-1,1). So mach ich das 
bei meiner Röhrenuhr. Um einen Vektor p um einen Winkel zu drehen muss 
man berechnen
1
rx =  px * cos(a) + py * sin(a)
2
ry = -px * sin(a) + py * cos(a)

oder in avr-gcc
1
#define frotate8(__rx,__ry,               \
2
                 __px,__py,               \
3
                 __sincos)                \
4
   asm ("fmuls %[px], %B[sc]"  "\n\t"     \
5
        "mov   %[rx], R1"      "\n\t"     \
6
        "fmuls %[py], %A[sc]"  "\n\t"     \
7
        "add   %[rx], R1"      "\n\t"     \
8
        "fmuls %[py], %B[sc]"  "\n\t"     \
9
        "mov   %[ry], R1"      "\n\t"     \
10
        "fmuls %[px], %A[sc]"  "\n\t"     \
11
        "sub   %[ry], R1"      "\n\t"     \
12
        "clr    __zero_reg__"             \
13
        : [rx] "=&r" (__rx), [ry] "=&r" (__ry) \
14
        : [px] "a"   (__px), [py] "a"   (__py) \
15
        , [sc] "a"   (__sincos))

Das brauche ich teilweise in Echtzeit ca 100000 mal pro Sekunde für ne 
Animation/Morphing. Punkte auf einem Scopeschirm warten nicht ;-) und 
zur Vorberechnen fehlt der Platz. Und float ist eh jenseits von Gut und 
Böse...

von Peter D. (peda)


Lesenswert?

Johann L. wrote:
> Ja klar, in dem Falle ist die Temeratur intern als 10000 dargestellt,
> und nicht als zB Q8.8 mit jeweils 8 Bits Vor- und Nachkommastelle. Als
> Dezimalzahl interpretiert wäre 100,00 darin 100,00 * 2**8, also 25600.

Nur verstehe ich nicht, wozu das nützen soll. Die Wandlung wird dadurch 
noch komplizierter, ich muß Vor- und Nachkommateil getrennt wandeln. 
Außerdem verliere ich einen Teil des Wertebereichs für vorhergehende 
Berechnungen.


> http://en.wikipedia.org/wiki/Q_(number_format)

Dieser Link führt zu nichts:
"Wikipedia does not have an article with this exact name."


> Bei der Multiplikation zweier Zahlen mit Komma an Stelle K1 und K2 hat
> das Produkt das Komma an Stelle K1+K2, so daß man zurechtrücken muß.

Kann man meistens schon zur Compilezeit in die Konstanten reinrechnen.


> Bei AVR sind das die Instruktionen der fmuls-Familie, welche Q1.7
> miteinander multiplizieren.

Hab mir schon den Kopf zerbrochen, was man damit anfangen soll.


> Nehmen wir mal an, wir arbeiten mit Zahlen in (-1,1). So mach ich das
> bei meiner Röhrenuhr. Um einen Vektor p um einen Winkel zu drehen muss
> man berechnen

Ich hab mich bisher noch nicht mit Grafikanimationen beschäftigt, komme 
mehr aus der Hardwareentwicklung (Steuerungen, Regelungen).

Muß mal sehen, ob ich Deinen Assemblercode irgendwann verstanden kriege. 
Sieht aber nach nem verdammt harten Brocken aus.


Wenn ich das richtig sehe, ist das Q-Format also nur für Dich, um 
Debugausgaben der Röhrenuhr darzustellen.
In einer Ausgabefunktion für den Benutzer kann man es aber nicht 
gebrauchen. Und daher kennt es C (printf, scanf) auch nicht.


Peter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger wrote:
> Johann L. wrote:
>> Ja klar, in dem Falle ist die Temeratur intern als 10000 dargestellt,
>> und nicht als zB Q8.8 mit jeweils 8 Bits Vor- und Nachkommastelle. Als
>> Dezimalzahl interpretiert wäre 100,00 darin 100,00 * 2**8, also 25600.
>
> Nur verstehe ich nicht, wozu das nützen soll. Die Wandlung wird dadurch
> noch komplizierter, ich muß Vor- und Nachkommateil getrennt wandeln.

Ja, die Wandlung wird komplizierter.

Aber Zahlen sind nicht nur zum Anzeigen da, sondern auch zum Rechnen. 
Wenn es nur darum geht, einen ADC-Wert anzuzeigen, wird man kein 
Q-Format wählen.

Aber wenn du viel rechnen musst und dir den Luxus von float nicht 
leisten kannst aus Platz- oder Zeitgründen, dann ist Fixpunktarithmetik 
aka. Q-Format das einzige, was bleibt.

> Außerdem verliere ich einen Teil des Wertebereichs für vorhergehende
> Berechnungen.

Du verlierst Wertebereich, aber darin liegen die Werte dichter. Man 
gewinnt also Genauigkeit. Das ist der Grund, float oder fix herzunehmen.

>> http://en.wikipedia.org/wiki/Q_(number_format)
>
> Dieser Link führt zu nichts:
> "Wikipedia does not have an article with this exact name."

Das ist Huddel hier von der Seite, weil sie nicht gepeilt kriegt, die ) 
in die URL zu nehmen:

http://en.wikipedia.org/wiki/Q_(number_format%29

>> Bei der Multiplikation zweier Zahlen mit Komma an Stelle K1 und K2 hat
>> das Produkt das Komma an Stelle K1+K2, so daß man zurechtrücken muß.

> Kann man meistens schon zur Compilezeit in die Konstanten reinrechnen.

hmmm welche Konstanten? Wie rechnest du x*y aus? Beides sind nicht zur 
Compilezeit bekannte Variablen mit Vor- und Nachkommastellen.

>> Bei AVR sind das die Instruktionen der fmuls-Familie, welche Q1.7
>> miteinander multiplizieren.
>
> Hab mir schon den Kopf zerbrochen, was man damit anfangen soll.
>
>
>> Nehmen wir mal an, wir arbeiten mit Zahlen in (-1,1). So mach ich das
>> bei meiner Röhrenuhr. Um einen Vektor p um einen Winkel zu drehen muss
>> man berechnen
>
> Ich hab mich bisher noch nicht mit Grafikanimationen beschäftigt, komme
> mehr aus der Hardwareentwicklung (Steuerungen, Regelungen).
>
> Muß mal sehen, ob ich Deinen Assemblercode irgendwann verstanden kriege.
> Sieht aber nach nem verdammt harten Brocken aus.

> Wenn ich das richtig sehe, ist das Q-Format also nur für Dich, um
> Debugausgaben der Röhrenuhr darzustellen.

Nein, ich verwende das Format auch für die Berechnungen. Die Vektoren 
sind intern in diesem Format dargestellt.

Hier wurde aber schon des öfteren Fixpunkt-Arithmetik diskutiert. 
Meistens handelt es sich dabei um 8.8 oder 16.16, weil sich die 
resultierenden Shifts um 8 bzw 16 besonders leicht umsetzen lassen.

In einfacheren Berechnungen ist das dann implizit auch ein Q-Format, 
bleibt aber durch geschicktes Rummultiplizieren mit Konstanten unter der 
Oberfläche.

Wenn jedoch viel zu rechnen ist, dann lohnen sich eigene 
Arithmetik-Routinen dafür, um nicht immer das Geschiebe, das sich ja aus 
komplexeren Rechnungen nicht mehr rauskürzen lässt, nicht jedesmal 
hinschreiben zu müssen bzw. geschickterer Instruktionen auszugeben wie 
fmuls.

> In einer Ausgabefunktion für den Benutzer kann man es aber nicht
> gebrauchen.

Daher kann man es deaktivieren. Es braucht dann weder Platz noch Zeit 
wenn keine Fixpunkt-Arithmetik betrieben wird. Und Q8.8 oder Q16.16 oder 
was man immer gern hätte lässt sich leicht hinzufügen.

Johann

von Benedikt K. (benedikt)


Lesenswert?

@ Peter Dannegger
Das Q Format ist bei Fixed Point DSPs weit verbreitet und hat da seine 
Vorteile. Mit einem auf Binärebene festgelegten "Dezimalpunkt" rechnet 
es sich nämlich für eine CPU einfacher als im Dezimalsystem.
Auch du hast dieses Format schon verwendet, z.B. der DS18B20 gibt die 
Zahl in diesem Format aus, nämlich als Q12.4: 12 Vorkommastellen und 4 
Nachkommastellen.
Der Vorteil davon ist, dass man die Vorkommastellen direkt ablesen kann, 
und die Umrechnung bei der Anzeige unabhängig von der eigentlichn 
Auflösung ist, da das ganze meist am MSB ausgerichtet ist, also die LSBs 
einfach mit 0en aufgefüllt werden, wenn weniger Auflösung als Platz in 
der Variablen vorhanden ist.

von Peter D. (peda)


Lesenswert?

Benedikt K. wrote:
> Das Q Format ist bei Fixed Point DSPs weit verbreitet und hat da seine
> Vorteile.

Das mag ja sein, aber hier ging es ja um die Zahlenausgabe.

So wie das verstanden habe, kann C auch nicht damit rechnen, sondern man 
muß sich irgendwelche Inline-Assemblerkrücken selber basteln, was 
natürlich nicht mehr portabel ist.


> Auch du hast dieses Format schon verwendet, z.B. der DS18B20 gibt die
> Zahl in diesem Format aus, nämlich als Q12.4: 12 Vorkommastellen und 4
> Nachkommastellen.

Und ich hab mich darüber geärgert, daß man damit keine vernünftige 
Dezimaldarstellung hinkriegt.
Es entstehen Lücken bei 0,01° Ausgabe bzw. Werte sind unterschiedlich 
häufig bei 0,1° Ausgabe.

Der Kunde würde mir ein Gerät mit 1/16° Ausgabe um die Ohren hauen und 
das Konkurrenzprodukt kaufen. Rechenvorteile interessieren ihn nicht.

Ist eine gute Nachkommadarstellung gefordert, setze ich deshalb den 
SMT160-30 ein, der erlaubt eine lineare Anzeige.

Temperaturregelungen sind auch recht gemächlich, da reicht die normale 
C-Rechenleistung (float) vollkommen aus.


Peter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger wrote:
> Benedikt K. wrote:
>> Das Q Format ist bei Fixed Point DSPs weit verbreitet und hat da seine
>> Vorteile.
>
> Das mag ja sein, aber hier ging es ja um die Zahlenausgabe.

Wenn man das Format nicht nutzt, wird man auch keine Zahlen in dem 
Format ausgeben wollen.

> So wie das verstanden habe, kann C auch nicht damit rechnen, sondern man
> muß sich irgendwelche Inline-Assemblerkrücken selber basteln, was
> natürlich nicht mehr portabel ist.

Doch es kann. Allerding nicht als Basistyp; man muss die Arithmetik 
schon selber schreiben oder eine Bibliothek oder entsprechende Builtins 
verwenden falls vorhanden.

Inline Assembler ist nicht notwendig. Alles was darin steht, kann man 
auch auf C-Ebene hinschreiben. Last not least kann man, wenn man 
einerseits Protierbarkeit haben will und andererseits beste Performance 
für AVR, hinschreiben.
1
#if defined (__AVR__)
2
// inline asm
3
#else
4
// Standard C
5
#endif

>> Auch du hast dieses Format schon verwendet, z.B. der DS18B20 gibt die
>> Zahl in diesem Format aus, nämlich als Q12.4: 12 Vorkommastellen und 4
>> Nachkommastellen.
>
> Und ich hab mich darüber geärgert, daß man damit keine vernünftige
> Dezimaldarstellung hinkriegt.
> Es entstehen Lücken bei 0,01° Ausgabe bzw. Werte sind unterschiedlich
> häufig bei 0,1° Ausgabe.
>
> Der Kunde würde mir ein Gerät mit 1/16° Ausgabe um die Ohren hauen und
> das Konkurrenzprodukt kaufen. Rechenvorteile interessieren ihn nicht.

Das am besten geeignete Datenformat zu wählen obliegt dem Entwickler. 
Q-Format ist ein Format, das die Auswahlmöglichkeit erweitert.

Weil das Komma an einer Binärstelle steht, ruckelt es natürlich wenn man 
mit Dezimalzahlen hantiert. Bei deinen Zahlen hast du ja gerne das Komma 
an 2ter Dezimalstelle, und nicht an einer Binärstelle. Wenn Binärstelle, 
dann brauchst du mindestens 7 Nachkomma-Bits, um 1/100 auflösen zu 
können. Und mindestens 7 Bits vor dem Komma, um die 100 auflösen zu 
können. Aber für deine Anwendung ist Q eh nicht adäquat, wie ober 
bereits erklärt.

> Temperaturregelungen sind auch recht gemächlich, da reicht die normale
> C-Rechenleistung (float) vollkommen aus.

Ja, wenn man auch noch den Platz dafür hat.

von Peter D. (peda)


Lesenswert?

Johann L. wrote:
> Peter Dannegger wrote:
>> Temperaturregelungen sind auch recht gemächlich, da reicht die normale
>> C-Rechenleistung (float) vollkommen aus.
>
> Ja, wenn man auch noch den Platz dafür hat.


Ja, hat man.
Atmel hat doch sogar die 8-Pinner bis 8kB aufgepimt (ATtiny85).
Nur aufm ATtiny2313 wird float ziemlich eng.


Peter

von 900ss (900ss)


Lesenswert?

Hier noch ein Artikel, der eine Implementierung von fixed-point in 'C' 
ganz gut erklärt finde ich. Habe die Codebeispiele aber auf einem AVR 
nicht ausprobiert.

http://www.embedded.com/columns/15201575?_requestid=247327

900ss

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.