Forum: Mikrocontroller und Digitale Elektronik Float-Berechnung auf 8051 mit SDCC


von Tobias P. (hubertus)


Lesenswert?

Grüss euch,
Ich habe flgendes Problem:
Ich habe ein C-Programm für den 10 Bit AD-Wandler des C517A von Siemens
erstellt. Das Programm soll einfach die Spannung, die am AD-Wandler
liegt, in Volt auf einem LC-Display ausgeben.
Ich dachte mir: Nichts einfacheres, man nimmt einfach einen float und
rechnet

Spannung = (Uref / 1023) * (float)Messwert,

wobei Uref = 5V und Messwert die Zahl ist, die der AD-Wandler liefert.
Nun sollte man ja eigentlich das richtige resultat in Volt haben - weit
gefehlt.
Konvertiert man nämlich den float-Wert in einen String (mittels
sprintf), dann meldet das LC-Display nachher fröhlich

<NO FLOAT>

Und das ist ja nicht unbedingt das gewünschte Resultat.
Im SDCC-manual heisst es, float werde unterstützt, aber ich kann keine
floats ausgeben!
Kann mir jemand von euch vielleicht sagen, was ich tun muss, um

a) den AD-Wandler-Wert korrekt umzurechnen, und
b) diesen umgerechneten Wert dann auf dem LC-Display auszugeben??

Vielen dank schon im Voraus für eure hilfe.

grüsse
    tobias

von inoffizieller WM-Rahul (Gast)


Lesenswert?

>b) diesen umgerechneten Wert dann auf dem LC-Display auszugeben

Bastel dir sprintf bzw. ftoa (float to ascii) selber.
Die Konvertierungsmethode ist ja nichts weiter als eine Ansammlung von
Divisionen (für jede Stelle eine...).
Die kann man auch durch eine Reihe von Schleifen realisieren:

unsigned char Ausgabestring[8];
Tausender = 0;
while (floatzahl > 999.99)
{
  Tausender++;
  floatzahl -= 1000.0;
}
Ausgabestring[0]='0'+Tausender;

Hunderter = 0;
while (floatzahl > 99.99)
{
  Hunderter++;
  floatzahl -= 100.0;
}

.
.
.

Ausgabestring[7]='\n';

sollte so gehen...

von Pieter (Gast)


Lesenswert?

moin moin,

>Spannung = (Uref / 1023) * (float)Messwert

immer wieder der Denkfehler, 10Bit sind 1024 Zustände...

also
Spannung = (Uref / 1024) * (float)Messwert

mfg
Pieter

von Tobias P. (hubertus)


Lesenswert?

@pieter,
ich weiss, dass 2^10 = 1024 ist, aber ich habe mir überlegt, dass der
AD-wandler ja eigentlich nur 1023 verschiedene spannungen unterscheiden
kann (0 V habe ich nicht mitgezählt).

@inoffizieller WM-Rahul  (komplizierter name): hast du einen kompletten
code für diese ausgabefunktion?
scheint mir reichlich kompliziert.

eigentlich müsste sdcc solche funktionen integriert haben, aber wie
aktivier ich sie?

von Pieter (Gast)


Lesenswert?

moin moin,


Konto auf 0 ist auch ein (schlechter) Zustand!

Float ist zu aufwendig.
Besser so:

     URef  AD_Wert     5 AD_Wert
Um= --------------- = ------------- = (5 MUL AD_Wert) DIV 1024
        1024            1024

Das geht also in Integer zu rechnen, ist so einfacher und schneller.

Sieh auch mal hier:

http://www.mikrocontroller.net/forum/read-1-372794.html#new

Mit Gruß
Pieter

von Profi (Gast)


Lesenswert?

Verwende Integer-Arithmetik:

Du willst bei 1023 5.000 angezeigt bekommen *). Also multiplizierst Du
1023 mit 4,88758553274, um auf 5000 zu kommen (oder mit 48,875855327,
für 50000). Dies Zahl gibst Du aus und gibst bei der Ausgabe ein Komma
an die richtige Stelle.

Um mit 4,8875 zu multiplizieren, rechnest Du mal 4,8875855*65536 =
320312,8054 (also 320313) und streichst die letzten 16 Bits des
Ergebnisses (dividierst durch 65536 oder zur Not shift right 16).
Am effektivsten: Du greifst auf die oberen 16 Bits des
32-Bit-Ergebnisses zu.

Die Multiplikation hat 10x19=29 Bits. In Assembler brauchst Du 6 MULs
und ein paar ADD / ADC s. Wenn Du die unteren 16 Bits weglässt, bleiben
13 (0..8191) übrig, davon nutzt Du den Bereich 0..5000 .

Hier ein Beispiel:
Eingangsspannung ist 1,234V, der ADC gibt 253 aus
(1,234/5*1024=252,7232).
253*320312=81038936, 81038936/65536=1236.
Bei der Ausgabe der Integer-Zahl 1236 ein Komma eingefügt, schon steht
1,236 in der Anzeige.

Der µC kann diese Integer-Berechnungen in wenigen Taktzyklen erledigen.
Für Floats bräuchte er vermutlich 100 mal länger, vom Speicherbedarf
ganz abgesehen.

So was ähnliches (Temperaturausgabe eines DS1624) hatten wir hier:
http://www.mikrocontroller.net/forum/read-1-424385.html#426232

*)
Ob Du mit 1023 oder 1024 rechnest, hängt davon ab, ob Du bei voller
Eingangsspannung 4,995 oder 5,000 angezeigt haben willst.

von Tobias P. (hubertus)


Lesenswert?

@Pieter:
zur berechnung des AD-Wertes:
wir haben beide recht, wie ich grade aus dem post von Profi erfahren
habe.
ich habe mir bis jetzt immer überlegt, dass:

1. die grösste zahl, die der 10-Bit AD-Wandler liefert 1023 ist (
=2^10-1),
2. dass bei einer eingangsspannung von 5V = Vref das resultat des
AD-Wandlers ebenfalls 1023 ist.

folglich muss ich die 5 V durch 1023 teilen, um bei einer
multiplikation mit 1023 wieder 5 zu erreichen.

es ist jedoch naheliegender (logischer), aber das endresultat stimmt
nicht haargenau (was es zwar auf keinen fall jemals wird ;))

@Profi:
du machst deinem namen alle ehre.
ich hab nur einen kleinen teil deiner überlegungen verstanden...
gibts dazu vielleicht irgendwo noch eine detailiertere beschreibung,
wie man solche aufgaben mit integer-arithmetik löst?

grüssse
  tobias

von Pieter (Gast)


Lesenswert?

moin moin,
@Tobias
also, Du gehst davon aus, der 10Bit ADU hat eine Auflösung von
2^10-1 = 1023.
Dann nimm mal einen 1Bit ADU.
2^1-1 = 0.
Du bekommst also NIE einen Spannungswert angezeigt.

Mit Gruß
Pieter

von Tobias P. (hubertus)


Lesenswert?

@Pieter:
das mit dem 1 Bit ADU stimmt, da ahst du recht.
ABER: Wenn du dem ADU Vin=Vref einspeist, dann zeigt er seine höchste
Zahl an, die er darstellen kann, oder?

also ein 8 Bit ADU mit Vref=5V und Vin=5V würde 255 anzeigen.
einer mit 10 Bit und Vref=5V und Vin=5V würde 1023 anzeigen.

nun ist es ja so, dass wnn ich 5 Volt einspeise, und den ADU-Wert
umrechne, ich auch 5 Volt bekommen will, deshalb die division durch
2^Anzahl_Bit-1.
beim 1 Bit ADU bin ich mir nicht so sicher, aber ich würde jetzt mal
sagen, das ist ein sonderfall. (Du kannst ja direkt rechnen: Wert_ADU *
Vref; wenn der ADU 1 liefert und Vref=5V sit, ergibt das dann auch 5)
Denn wer hat schon einen 1 Bit ADU?? ;)
aber du hast natürlich recht, aus diesem gesichtspunkt wäre wieder
deine rechnung die richtige.

und mittlerweile bin ich schon ein bisschen verwirrt:
was ist richtig? -> ich konsultier gleich wikipedia ;)

gruss

von inoffizieller WM-Rahul (Gast)


Lesenswert?

>was ist richtig? -> ich konsultier gleich wikipedia ;)

Das Datenblatt zu studieren reicht auch...
Wenn man nur mit 10bit breiten Zahlen rechnen würde, könnte man gar
nicht durch 1024 teilen...(nur um die Diskussion noch mal anzufachen)

Der Code ist doch schon recht umfangreich.
Du musst einfach nur die jeweilige Stelle von deiner Zahl
subtrahieren.
Das kann man dann bis zur xten Nachkommastelle treiben, wobei es da
dann immer ungenauer wird.

von Tobias P. (hubertus)


Lesenswert?

@inoffizieller...usw.:
ja, aber es ist ja nur der ADU, der 10 Bit liefert.
wenn du das ganze dann umrechnest, und du arbeitest auf einem 8 Bit
mikrocontroller, dann wirst du für das resultat sowieso eine 16 Bit
zahl nehmen müssen (oder, wie profi schon gesagt hat: 24, 32... Bit).
da kann man dann locker durch 1023 teilen. ist allerdings dann etwas
mühsamer mit nur 8 bit, als wenn man durch 1024 teilt und einfach
schieben kann.
da taucht nämlich schon die nächste frage auf:

wenn ich mit 8 Bit arbeite und habe eine 16 Bit zahl - sagen wir in den
registern R0 und R1 - wie teile ich die dann durch 1023? (in assembler!)
der div-befehl kann ja nur 8/8 Bit rechnen.
ist das etwa der grund, warum der C517A eine
multiplizier/dividier-einheit on-chip hat, um mit 32 und 16 bit rechnen
zu können? (wie man die bedient ist mir allerdings bis jetzt auch
verschlossen geblieben ;))

von Karl heinz B. (kbucheg)


Lesenswert?

> ABER: Wenn du dem ADU Vin=Vref einspeist, dann zeigt er seine
> höchste Zahl an, die er darstellen kann, oder?

Ja, aber wenn du die Spannung etwas ernidrigst, zeigt er immer
noch seine höchste Zahl an.

Du musst dich vor der Vorstellung trennen, dass der Zahlenwert
aus dem ADC ein punktuelles Ergebnis ist.
Der ADC verwendet Bereiche: Jede Spannung von 0 bis x0 wird
vom ADC mit 0 quittiert. Jede Spannung von x0 bis x1 liefert
eine 1. Jede Spannung von x(n-1) bis x(n) liefert MAX.
Wieviele solcher Bereiche gibt es? MAX verrät es uns. Es gibt
MAX + 1 solcher Bereiche.

Sagen wir mal MAX wäre 4 (dann können wir einfacher rechnen).
Weiters sei ARef gleich 5 Volt. Wie sehen dann die Bereiche aus:

Wert vom ADC       Spannungsbereich
    0              0 - 1
    1              1 - 2
    2              2 - 3
    3              3 - 4
    4              4 - 5

Wenn dir also der ADC als Ergebnis eine 4 liefert, dann liegt
die tatsächliche Spannung iregendwo im Bereich 4 - 5 Volt.

D.h. aus der Formel:    Wert = ARef * ADC / 5
kriegst du immer den unteren Grenzwert des Bereiches der Größe
ARef / ( MAX + 1) in dem die Spannung liegt.

Lass uns mal was probieren:  Rechnen wir mal ARef * ADC / 4

   0     0
   1     1.25
   2     2.5
   3     3.75
   4     5

Die Zahlen die hier rauskommen, haben nichts mehr mit den
Messbereichen zu tun. Ein Wert von 2 würde ja bedeuten, dass die
Spannung irgendwo zwischen 2.5 und 3.75 Volt gewesen war. Das
war sie aber nicht. Bei 3.2 Volt würde der ADC dir schon 3
melden! Die letztere Formel würde überhaupt nur dann einen Wert
von 4 liefern, wenn die Spannung genau 5 Volt beträgt. Das entspricht
aber nicht der Realität. Auch bei 4.6 Volt würde der ADC eine 4
zurückliefern!

von Pieter (Gast)


Lesenswert?

moin moin,

lese mal das Datenblatt zum 80C552, der hat auch 10Bit ADU

http://www.datasheetarchive.com/datasheet.php?article=630376

1Bit ADU-> Transistor
if Ube<0.6V then Uc>0
            else Uc=0.

In meinem Labornetzteil benutze ich MAX186. Lade das Datnblatt und sieh
wie der die 12Bit macht.

http://www.datasheetarchive.com/datasheet.php?article=2189149

Mit Gruß
Pieter

von Stephan H. (stephan-)


Lesenswert?

@Tobias,  Lösungsvorschlag .... die X te

bei 5 Volt Ref. hast Du ja 4,88 mV pro LSB.
Also bringt der A/D bei 1 Volt CCh.
Jetzt könntest du zB. vom Ergebnis so oft CCh subtrahieren und eine
Variable dabei hochzählen bis Du einen OV hast.
Dann CCh addieren und Du hast die Ganzen Volt in der Variablen die
hochgezählt wurde.
Für den Rest der kleiner als 1 Volt ist,einfach ne Tabelle nehmen und
die gerundeten Werte da ablegen.

Hat den Nachteil das dieser Weg bei Änderung von Vref angepasst werden
muß.( aus CCh wird bei Vref 4,096V dann  FAh ).

von Tobias P. (hubertus)


Lesenswert?

@karl heinz:
nun bin ich überzeugt. ich muss also durch 1024 teilen und nicht durch
1023.

von Tobias P. (hubertus)


Lesenswert?

@stephan:
das ist auch eine gute idee.
nun habe ich ca. 3 verschiedene lösungswege, das zu machen :D
und ich frage mich, was am besten ist:

a) integer-arithmetik (z.b. 16 bit)
b) tabelle (nicht so elegant)
c) dein lösungsvorschlag mit dem subtrahieren

nur mal so als anmerkung:
ich will keine besonders schnellen berechnungen, sie müssen auch nicht
100%ig genau sein.
aber mein codespeicher ist begrenzt, deshalb kann ich nicht beliebig
grosse funktionen basteln. schliesslich soll ja noch ein haufen anderer
rkam reinpassen ;)
welches ist jetzt die eleganteste methode?
wie rechnet eigentlich ein digitales multimeter? ich hab zwar keine
ahnung davon, aber ich denk mal, da ist auch ein ADC drinn, dessen wert
umgerechnet werden soll?

von Stephan H. (stephan-)


Lesenswert?

Das hängt von Dir und Deiner Programmierkunst ab.

Tabelle geht am schnellsten macht aber bei der erstellung Arbeit.
Und braucht Speicher

Integer hat zur Folge das Du eben etwas mehr rechnen mußt.
Da ohne Komma gerechnet wird sind die Zahlen etwas größer, da ja das
Komma nachher nur gesetzt wird. Aus 1558 wird 15,58.
Ist allerdings auch die sauberste Lösung die auch mit anderen zB. 12
oder 14 Bit Wandlern gehen würde, wenn  sie 16 Bit rechnet !!

Meine Methode ist ne Zwischenlösung. Etwas Rechnen und den Rest aus der
Tabelle. Damit bleibt die Tabelle wesentlich kleiner und der
Rechenaufwand ist wesentlich geringer.

Deinen Weg mußt Du selbst finden. Wenn nicht gehe alle 3 Wege und lerne
dabei und wähle für Später deinen aus.

von Markus_8051 (Gast)


Lesenswert?

@Pieter:
> Dann nimm mal einen 1Bit ADU.
> 2^1-1 = 0.   ???

Also 2^1-1 ist bei mir immer noch 1 und nicht 0!

Markus_8051

von Tobias P. (hubertus)


Lesenswert?

@Markus:

uhps, stimmt :D da bin ich ebenfalls in pieters fettnäpfchen getreten
;)

@stephan:
ich hab das ganze jetzt mal mit integer gemacht. klappt ganz schön,
ausser dass ich bei printf() keine möglichkeit gefunden habe, irgendwo
mitten in die int-zahl ein komma zu setzen.
gibts da vl. schon was vorgefertigtes? sonst muss ich eine solche
funktion selber basteln. ist zwar kein grosses problem, aber es ist
mühsam und braucht viel zusätzliche rechenzeit. etwas vorgefertigtes
wäre da sicher idealer.
gruss

von Stephan H. (stephan-)


Lesenswert?

Sorry ich nix verstehen von "C " !
Aber letztendlich ist es doch nur eine Frage der ASCI darstellung.
Du mußt doch eh alles in ASCI wandeln.Dann steht auf dem Display 1588
und auf Position XY ungelößt schreibst Du ein Komma. Dann steht da
15,88. Die Zahl 1588 ( zB. Dein Ergebnis) mußt Du doch eh zerlegen.

von Pieter (Gast)


Lesenswert?

moin moin,

sollte doch in der Hilfe stehen, wie

%W.De  Scientific notation x.xxxx e nnn. float, double

%W.Df  Fixed format xx.xxxx. float, double

%W.Dg  Variable `W' digits wide,`D' decimals  xxxx.xx

Mit Gruß
Pieter

von Tobias P. (hubertus)


Lesenswert?

@pieter:
danke. woher hast du das?

übrigens: sry dass ich mich so lange nicht gemeldet habe, ich war
leider kurz abwesend.

grüsse

von toto (Gast)


Angehängte Dateien:

Lesenswert?

ALSO

Wenn du Gleitkommas im printf haben willst.
Dann musst du beim sdcc die compiler option large-model benutzten.
Wenn es immernoch nicht klappt, dann schaue mal im printf.c source
code.
Ich glaube das man dort ein #define ändern muss, und neu compilieren.
Auf jedem fall muss du den source dann code ändern.

Am besten ich gebe dir meins

von Tobias P. (hubertus)


Lesenswert?

hallo toto,
danke für den tipp mit --model-large.
aber komischerweise funktioniert mein programm überhaupt nicht mehr, 
wenn ich model-large einsetze. ein teil des programmes wird zwar 
ausgeführt, aber dann passiert einfach nichts mehr.
komische sache das, model-large...


gruss

von Joe (Gast)


Lesenswert?

Model Large unterstellt ja externen RAM Bereich etc, die Frage ist, wie 
sieht deine Hardware aus ??

von Tobias P. (hubertus)


Lesenswert?

meine hardware sieht folgendermassen aus:
Adresse
0000h - 07FFh: EPROM mit monitprorogramm
8000h - FFFFh: RAM 32k zum speichern von programm + daten

internes RAM ist 256 Byte gross + 2k erweitertes internes RAM (kann über 
SFR-Bits eingeschaltet werden).

reichen die angaben??
gruss

von Joe (Gast)


Lesenswert?

Ja, jetzt noch einen Blick ins SDCC Manual, Seite 32. Und da schaust du 
dir mal die Compiler Optionen an, die da z.B. wären:

--xdata-loc
--xram-size
--code-loc
--code-size

Setze da mal deine Angaben rein und neuer Versuch.

von Tobias P. (hubertus)


Lesenswert?

meinst du das klappt, wenn ich die angaben änbdere?
das einzige, was ich jeweils angebe, ist --code-loc 0x8000 und 
--xram-size 0x8000. das reicht also nicht?

gruss

von Joe (Gast)


Lesenswert?

xdata location fehlt doch. Bitte lese dir das Kapitel mal durch. RAM 
Größe kennt er nun, nur wo es zu finden ist ...? Und was meinst du wo er 
deine Variablen ablegt?

von Tobias P. (hubertus)


Lesenswert?

@joe:
stimmt, du hast recht.
ich probier das bei gelegenheit nochmal, mit den richtigen angaben dazu.

grüsse & schönen abend

von Joe (Gast)


Lesenswert?

Lass von dir hören, ist auch für andere von Interesse. Am besten du 
liest dir die Optionen mal gesamt durch, da läßt sich einiges 
optimieren.

von Tobias P. (hubertus)


Lesenswert?

ja mach ich,
ich meld mich morgen abend wieder.
bis dann,

gruss

von M. G. (looking)


Lesenswert?

toto schrieb:
> ALSO
>
> Wenn du Gleitkommas im printf haben willst.
> Dann musst du beim sdcc die compiler option large-model benutzten.
> Wenn es immernoch nicht klappt, dann schaue mal im printf.c source
> code.
> Ich glaube das man dort ein #define ändern muss, und neu compilieren.
> Auf jedem fall muss du den source dann code ändern.


Sorry, dass ich den alten Thread nochmal aufwärme, aber meine Frage 
passt hier ganz gut rein:
Im SDCC-Manual steht folgendes:
> The default printf() implementation in printf_large.c does not support
> float (except on ds390), only <NO FLOAT> will be printed instead of the
> value. To enable floating point output, recompile it
> with the option -DUSE_FLOATS=1 on the command line. Use --model-large
> for the mcs51 port, since this uses a lot of memory.

Wie ist das genau gemeint mit dem neu kompilieren? Muss dann da eine Lib 
erzeugt werden (wie geht das?) oder wie weiß dann der SDCC, dass die neu 
kompilierte printf verwendet werden soll? Oder muss ich die Sourcedatei 
von printf ändern und in mein SDCC-Projekt mit aufnehmen?

Danke.

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.