Hi,
habe ein kleines Problem:
Ich ermittle über die ADC's verschiedene Spannungen, diese werden dann
als 10bit variable gespeichert. Nun will ich diese 10bit in 0-100%
umformen.
Wenn ich jetzt einfach den wert solange um 10 subtrahiere, bis das
carrybit nen wert enthält habe ich Abweichungen um bis zu 2,4%. Diese
würde ich gerne vermeiden! Hat da jmd. ne Lösung parat? Bin bestimmt
nicht der Erste der das Problem hat.
Vielen Dank im Vorraus
Prozent = Wert / 1024 * 100
um die Division zu vermeiden kannst du erstmal
x = Wert * 100
rechnen und dann das Ergebnis (16 Bit) um 10 Bit nach recht's schieben
was einer Division durch 1024 entspricht.
allerdings könnte die division durch 1024 probleme machen: der maximale
wert, den der ad-wandler liefert ist ja 1023, d.h. wenn man den
prozentwert mit
prozentwert = ad_wert * 100 / 1024
berechnet, kommen maximal etwa 99.90% raus, was bei integer-rechnungen
zu 99% abgeschnitten würde. um die 0..1023 des ad-wandlers wirklich auf
die 0..100% abzubilden müsstest du also durch 1023 dividieren. das
könnte dann wieder ein bisschen aufwändiger werden.
Zu oben genannter Rechnung: Geht noch etwas einfacher (Diesmal mit dem
Hardware-Multiplizierer):
x = HIGH(ADCH * 100)
Damit multiplizierst du erstmal das High-Byte des ADCs (Ergebnis
unbedingt linksbündig ausrichten lassen) mit 100 und nimmst davon
wiederum nur das High-Byte des Ergebnisses. Dort steht dann dein Wert
von 0 bis 99% drin. Um jetzt auf deine 100% zu kommen, kannst du den
Wert noch entsprechend runden lassen:
Wenn das MSB von ADCL gesetzt ist, dann addiere zum Endergebnis 1
dazu.
Das ganze in AVR ASM (ungetestet):
1
in r16, ADCH
2
ldi r17, 100
3
mul r16, r17
4
;Ergebnis ist nun in r1:r0, du brauchst aber nur r1
5
in r16, ADCL
6
andi r16, 0x7F
7
breq PC + 2
8
inc r0
9
;Nun hast du dein gerundetes Ergebnis von 0 - 100% in r0
Funktion ohne Gewähr, müsste aber so ungefähr funzen. Bin mir nicht
mehr ganz sicher, wie die Registernamen vom ADC wirklich heissen.
also ich bezweifle das das geht... habe es auch mal im simulator
durchlaufen lassen und wenn ich für ADCH 0b00000011 nehme und für ADCL
0b11111111 müssten ja 100 rauskommen. Stattdessen liegt der wert unter
50 und bei anderen werten wo normalerweise 70 % rauskommen müsste
erhalt ich zahlen über 200
Setze mal das ADLAR Bit, dann erhälst Du als max Wert
ADCH = 0b11111111 und ADCL = 0b11000000.
Das ist es, was Phillip mit "linksbündig" meinte.
Sonst musst Du eine vollständige 16x8bit Multiplikation
durchführen, und ein Ergebnis von 24bit verwursten.
Ich schrieb ja, dass du das Wandler-Ergebnis linksbündig ausrichten
musst (ADLAR = 1). Dann hast du in ADCH einen Wert von 0 bis 255, nicht
von 0 bis 3.
@Hagen:
??? Es geht doch darum, dass er 100% erreichen kann. Dieses +1 ist
eigentlich nur die "Rundung".
Beispiel:
ADCH = 127; ADCL = 0
x = (127 * 100) >> 8 = 49
ADCH = 127; ADCL = 255
x = (127 * 100) >> 8 + 1 = 50
ADCH = 255; ADCL = 0
x = (255 * 100) >> 8 = 99
ADCH = 255; ADCL = 255
x = (255 * 100) >> 8 + 1 = 100
Wo ist das Problem?
So sorry habe das Linksbündig doch glatt überlesen...
Also jetzt geht es fast...
wie Hagen schon gesagt hat ist das Problem, dass ein ADC Wert von 1
nach deiner Rechnung 1 % ergibt, obwohl es nur 0,09 % sind und deshalb
nicht aufgerundet werden dürfte. Ist ansich nicht schlimm, kommt aber
nen bisschen blöd, wenn auf dem display 1% angezeigt wird, obwohl der
Poti am Anschlag ist.
Das "Problem" ist ein anderes. Die Korrektur um einen Wert von +1
erzeugt ja in jedem Falle einen "Fehler". Wird der ADC Wert vor der
Berechnung um +1 "aufgerundet" so beträgt dieser Fehler exakt
1/1024'tel. Wird nach der Rechnung der Prozentwert um +1 erhöht dann
beträgt der Fehler aber schon 1024/100'tel = 10.24 statt nur +1.
Gruß Hagen
Das "Problem" ist tatsächlich folgendes:
Die +1 für die Rundung einer Ganzzahldivision müssen vom Divisor
abgeleitet werden, und nicht vom Dividenden.
Als Formel ausgedrückt:
x = (a + b/2) / b
Das heißt hier, sorry Philipp, die Rundung direkt vom ADC-Wert
abzuleiten, funktioniert nicht in allen Fällen zuverlässig.
Ich kuck mir die Sache nochmal an und poste über den Abend noch einen
konkreten Code. Sofern es nicht jemand anders tut ;-)
Grüße
Johannes
Naja, es geht ja nicht um "Rundung" eines Wertes, noch um eine
Scalierung eines Wertes, sondern um eine Transposition um +1, einfach
um als Resultat bei 1023/1024*100 nicht 99% zu bekommen, sondern 100%.
Korrekterweise müsste man nämlich durch 1023 statt 1024 dividieren,
aber aus Effizienzgründen nimmt man 1024. Das verursacht quasi einen
Fehler von exakt +1 und den gleichen wir einfach aus. Da das Resultat
in seiner Auflösung als Prozentzahl eh einen Fehler von 1024/100 =
10.24 hat stört also die Addition von +1 auf den ADC Wert garnicht.
Gruß Hagen
Hagen, mit der Scalierung hast Du im Prinzip Recht, bloß sind die 100%
normal gar nicht das Hauptproblem, sondern die 0%. Die 100% kannst Du
zur Not noch "zurechtlügen", das nimmt Dir keiner krumm. Nur wenn
eine Anzeige bei 0% irgendwas anderes anzeigt, mault Dir gleich jeder
die Ohren voll. Und das wird schon bei nur 3 Kunden echt nervig ;-)
Gruß Johannes
Genau das habe ich letzte Woche gemacht, und zwar für 3,29678, 3,3000,
4,99512 5,000V 99,902 und 100%.
Und zwar folgendermaßen:
zuerst den ADCmit 0x20 multiplizieren (also um 5 Bits nach links
schieben), dann ein paar Bytes 0x00 anhängen.
Davon zuerst den ADC-Wert für 100% abziehen (mit
Floating-Nachkommastellen), sooft, bis das Ergebnis negativ wird, dann
den letzten Schritt mit einer Addition rückgängig gemacht und das selbe
mit dem ADC-Wert für 10% , 1%, usf.
Inspiriert hat mich die Routine für die Integer -> LCD-Ausgabe.
Nur dass ich nicht 1000, 100, 10 oder 1 subtrahiere, sondern den
ADC-Wert für die jeweiligen Stellen.
Code hab ich gerade nicht auf diesem Rechner, kann ich in den nächsten
Tagen posten.
Nur soviel, falls Du es selbst versuchen willst:
10 Bits sind max. 1023=0x3ff Hier die Version für 99,901%:
mal 0x20 = 0x7fe0, daran beliebig viel Nullen angehängt:
7fe00000 davon den Wert für 10% abziehen:
0ccccccd sooft, bis das Ergebnis negativ wird, das geht 10 Mal
... von 10 ziehst Du 1 ab und erhälts die erste Ziffer 9
ffdffffe negatives Ergebnis, dazu einmal addieren:
caccccb
und nun geht es von vorne los: den ADC-Wert für 1% subtrahieren
147ae14 (0x80000000 / 100)
.... geht wieder 10 mal, also 2. Ziffer wieder 9, dann Komma einfügen
ffe00003 wieder 147ae14 addieren
=127ae17 (0x80000000 / 1000)
- 20c49b subtrahieren, geht wieder 10 Mal -> 3. Ziffer auch 9
ffe00009
+ 20c49b
= c4a4
- 346DC 0x80000000 / 10000)
fffd7dc8 wird gleich beim erstem Mal negativ -> 4. Ziffer 0
falls mehr Stellen gewünscht, weitermachen
wenn Du bei 0x3ff 100,0 anzeigen willst, musst Du 0x7fe00000 / 10 ...
rechnen.
10 Bit genau messen und auf unter 7 Bit wandeln ?
Zunächst reichen 8 statt 10 Bit für den gelesenen Wert, also Wandler
auf 8 Bit stellen oder per 16 Bit 'schieben' auf 8 Bit vermindern.
Also haben wir 0...255 als mögliche Werte, welche man einmal mit 100
per Hardware multipliziert.
In R0/R1 haben wir dann ein high-Byte zwischen 0...99 und ein low Byte.
Ist Bit 7 im low Byte = high wird das High-Byte per INC um +1 erhöht.
Der Bereich im High-Byte liegt dann bei 0...100%.
---
Will man 10 Bit / 0,0% bis 100,0% muss man die Analgogwert (0...1023)
mit (binär) 1001 multiplizieren.
Das Ergebis dann /1024 = shiftr 12 bzw. shiftr 4 und Ergebnisregister
neu bezeichen.
Ergebnis dann 0 bis 100,0%
"Ist Bit 7 im low Byte = high wird das High-Byte per INC um +1
erhöht."
Ich würde diesen 16 Bit Wert immer mit 0x0080 addieren, spart den
begingten Sprung.
Gruß Hagen
Ich hatte einen konkreten Code versprochen, hier ist er nun.
Er ist ziemlich genau, wahrscheinlich genauer als gebraucht,
aber sei es drum. Vielleicht hilft es ja noch jemandem.
Also erstmal ADLAR wieder auf 0 und nachdem der ADC fertig ist,
das Ergebnis einlesen:
in r16,ADCL
in r17,ADCH
Danach 1 addieren bzw. -1 (=0xFFFF) subtrahieren, um die 100%
sicher zu erreichen:
subi r16,0xFF
sbci r17,0xFF
Dann mit 100 multiplizieren, wobei ein Wert >16bit
herauskommen kann (R2-R4):
ldi r18,100
mul r16, r18 ; adcl * 100
movw r2, r0
clr r4
mul r17, r18 ; adch * 100
add r3, r0
adc r4, r1
Nun teile ich durch Verschieben durch 256...
mov r16,r3 ; r4:r3:r2 / 256
mov r17,r4
...addiere den entsprechenden Wert für die Rundung...
subi r16,0xFE ; + (512/256) entspr. -2
sbci r17,0xFF
...und teile per Rechtschieben noch einmal durch 4:
lsr r17 ; / (1024/256)
ror r16 ; entspr. r17:r16>>2
lsr r17
ror r16
Ist doch eigentlich gar nicht so kompliziert. Oder?
Beste Grüße
Johannes
So, hier meine ultra-genaue Version zum Konvertieren einer n-bittigen in
eine beliebige Zahl. Beispiele für 3,3, 5,0 100% sind dabei.
Die Routine macht beide Aufgaben auf einen Streich:
- Umwandeln in den passende Zahlenbereich und
- Aufbereiten der Ausgabe in einzelne Ziffern.
Viel Spaß und Erfolg
Großes Entschuldigung. war doch tatsächlich fähig das High und low byte
für nen test zu vertauschen! Kopf gegen die Wand hau
Dein Code funzt echt super!!!
Vielen Vielen Dank!
@Fabian
Na prima!
@Andreas
Nichts für ungut, aber float und int sind doch zwei verschiedene
Welten. Auch wenn man sich in beiden über dasselbe Thema unterhält.
Und dann noch an diesen namenlosen Witzbold
...
Beleive in C
And you will find,
That you must beleive,
That world is disc,
Too.
...
Grüße an alle,
hat Spaß gemacht.
Johannes
Ähm, nagut, ich habe das Teil nur vom Betreff
"Float-Berechnung auf 8051 mit SDCC"
her weder mit int-Rundung noch mit Asm und AVRs
in Zusammenhang gebracht.
Und immer erst genauer hinschauen, fällt mir in
einem Massenforum zugegeben etwas schwer ;-)
Gruß Johannes
Gestern noch ein wenig optimiert.
Und neue Ideen eingebaut, aber noch große Baustelle.
;there are 3 versions, v.1 has the constants within the linear code
(without loop, optimised for speed) and uses 32 Bits,
;V.2 and V.3 use an external table in flash and use a loop for the
digits (optimised for space).
;The result is stored in R6..R15 (for debugging), but could be output
(e.g. to a LCD) instead.
;Then R6 and R7 could be used for full 64-Bit resolution.
;As the result is absolute precise, it can be used for converting any
n-bit-number, not only 10 Bits of the ADC.
;V.3 was optimised by using LPM Rd,x+
;V.4 subtracts 7, 4, 2, 1 times the digit-value, so a maximum of 3
subtracts is necessary (instead of 10 inv.3).
; V.4 uses more table space, but is much faster.
;v.5 is a new begin of a try with constants within the program. It is
very tricky, because it first subtracts 6,
; and then makes a binary search, which guarantees to be finished
within 4 loops for 0..5 and within 3 loops for 6..9.
; But loading all the values eats more cycles than the idea.
; V.5 is not finished, only the first digit is coded. It uses much
space, but should be very fast.
;new ideas: store only the value of 8*digitvalue and calculate 4, 2 and
1* by shifting, which should be much faster
; than loading them from flash
Grüße