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.
(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.)
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.
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
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...
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
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
@ 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.
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
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.
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