Problem mit Werten von Potentiometer

#7922951
Lesenswert?

Ich habe ein seltsames Problem mit dem Wert eines Potentiometers, der die Lautstärke eines DAC steuern soll. Meine Schaltung besteht aus einem Atmega168, der den DAC (ES9028Pro) via I2C ansteuert. Das Poti bildet mit 3.3V und GND einen Spannungsteiler, dessen Augang an PC0 geht.

Der Bereich von 0 bis 1023 wird in einen Bereich von 0 bis 60 umgewandelt (siehe Code unten). Beim Debuggen sehen alle Werte korrekt aus, aber wenn ich 0 vom Potentiometer an die Funktion „setAllDACVolumes()“ übergebe, erhalte ich keine Dämpfung von 0, sondern kein Audio. Wenn ich aber der Funktion direkt eine "0" gebe, erhalte ich null Dämpfung. Oder auch wenn ich direkt eine "10" gebe, erhalte ich die korrekte Dämpung von 5dB.

1
int currentVolume = ADC_read(0);
2
if(currentVolume != lastVolume) {
3
    uint8_t att_db = ((currentVolume * 60.0f)/ 1023.0f);
4
    setAllDACVolumes(att_db*2);
5
    lastVolume = currentVolume;
6
}

Die setAllDACVolumes()-Funktion sieht so aus:

1
void setAllDACVolumes(uint8_t Value)
2
{
3
    int i;
4
    for (i=0; i<8; i++)
5
    {
6
        SendCtrlReg2(DAC_REG_VOLUME_BASE+i, Value);  // set DAC Volumes, 0x00 is default
7
    }
8
}

Die Funktion SendCtrlReg2 schreibt den Wert, der der Funktion übergeben wird, in das Register des DAC (ES9028Pro). In meinem Fall sind das die Register 16 bis 23. Der Standardwert für die Lautstärke ist 0x00. Diese Funktion läuft korrekt; sie schreibt auch in andere Register. Ich code mit Microchip Studio und debugge mit einem Atmel ICE.

: Verschoben durch Moderator
#7922967
Lesenswert?

Laurin schrieb:

uint8_t att_db = ((currentVolume * 60.0f)/ 1023.0f);

Als 0 x 60.0 und dann / 1023.0 und bei Übergabe nochmal x 2

Du verwendest float, und float ist nie genau. Allerdings sillte das Virzeichen stimmen und nicht -0.0001 zu -1 und dann 255 werden, daher kann ich den Effekt nicht erklären.

Man könnte die maximal 1023 von (klugerweuse uint16_t) currentVolume auch mit 60U multiplizieren, ergibt 61380 und passt noch in einen uint16_t und dann durch 1023U teilen zu 59 und dann x 2 macht maximal 118. Ich hätte erwartet du programmierst 0-255. Aber eigentlich hast du ein zusätzliches bit mit 18dB, also 0-291.

#7923072
Lesenswert?

Thilo R. schrieb:

Ist es so, dass die Einstellung via ADC für alle Werte funktioniert, nur speziell 0 nicht? Oder funktioniert die Einstellung über das Poti generell nicht, aber mit jedem hartkodierten Wert doch?

Die ADC funktioniert für alle Werte. Selbst wenn ich 0 nicht einstellen könnte, würde der Wert 2 oder 3 in einer sehr kleinen Attenuation resultieren, und damit ein klar hörbares Audiosignal durchkommen würde.

#7923099
Lesenswert?

Laurin schrieb:

Der Bereich von 0 bis 1023 wird in einen Bereich von 0 bis 60

In Arduino gibt es die Funktion map(). Wird sicher etwas Ähnliches in C/C++ geben. Vielleicht geht es damit. Vor allem, damit kannst du unten oder oben was weg x_en. Ich weiß nicht, ob das auch da rein spielen könnte. Lautstärkepotis sind oft nicht linear.

: Bearbeitet durch User
#7923126
Lesenswert?

Peter D. schrieb:

Also kann ADC_read() auch negative Werte liefern?

Nein, nur 0 bis 1023. Du kennst das Arduino-Framework nicht ?

Peter D. schrieb:

Was passiert dabei mit negativen Werten?

uint8_t kennt keine negativen Werte.

Und currentVolume wird, bevor gerechnet wird, von int in float gewandelt.

Aber float-Rechnungen sind nicht exakt.

#7923184
Lesenswert?

Frank O. schrieb:

In Arduino gibt es die Funktion map(). Wird sicher etwas Ähnliches in C/C++ geben.

Früher (tm) nannte man das Dreisatz, genauer Geradengleichung in der Zwei-Punkte-Form. Und natürlich lässt sich die auch in C/C++ formulieren, wenn man nicht die Arduino Quellen danach durchsuchen möchte.

: Bearbeitet durch User
Moderator Persönliche Seite #7923203
Lesenswert?

Michael B. schrieb:

Peter D. schrieb:

Also kann ADC_read() auch negative Werte liefern?

Nein, nur 0 bis 1023. Du kennst das Arduino-Framework nicht ?

Warum nutzt der TO dann für currentVolume den Typ int?

Peter D. schrieb:

Was passiert dabei mit negativen Werten?

uint8_t kennt keine negativen Werte.

Aber int für currentVolume. Nichts anderes meinte Peter.

BTW. Du plenkst.

#7923225
Lesenswert?

Frank O. schrieb:

Aber vielleicht sind solche Funktionen schon optimiert und funktionieren besser?

Da gilt es erstmal die Bewertungskriterien für "besser" in einem bestimmten Szenario zu definieren.

Wie die Arduino Funktion map() sich grob benimmt, ist in der Duko so zusammengefasst: "Die map() - Funktion verwendet Ganzzahl-Mathematik, so dass keine Brüche generiert werden, wenn die Mathematik möglicherweise darauf hinweist, dass dies der Fall ist. Bruchreste werden abgeschnitten und nicht gerundet oder gemittelt."

#7923475
Lesenswert?

Jens G. schrieb:

Laurin schrieb:

aus, aber wenn ich 0 vom Potentiometer an die Funktion „setAllDACVolumes()“ übergebe, erhalte ich keine Dämpfung von 0, sondern

Vielleicht solltest Du erstmal klären, ob wir nun über Volume (Lautstärke, Verstärkung) reden wollen, oder Dämpfung ...

In die Register schreibe ich den Wert der Dämpfung. Die lässt sich in 0.5dB-Schritten pro Wert 1 absenken. 0 dB, oder 0x00 ist volle Lautstärke.

(Firma: 1984now) #7923605
Lesenswert?

Laurin schrieb:

Also meinst du, wenn ich der funktion direkt eine 0 gebe? In diesem Fall geht es, ja

1
int currentVolume = ADC_read(0);
2
if(currentVolume != lastVolume) {
3
    uint8_t att_db = ((currentVolume * 60.0f)/ 1023.0f);
4
    setAllDACVolumes(att_db*2);
5
                     ^^^^^^^^ 
6
    lastVolume = currentVolume;
7
}

Er meinte sicher das von mir im Zitat hervorgehobene. Was passiert, wenn du an Stelle des Ausdrucks "att_db*2" einfach "0" hinschreibst?

(Firma: 1984now) #7923629
Lesenswert?

Laurin schrieb:

Er meinte sicher das von mir im Zitat hervorgehobene. Was passiert, wenn du an Stelle des Ausdrucks "att_db*2" einfach "0" hinschreibst?

Wie bereits erwähnt bekomme ich 0dB Dämpfung und damit volles Volumen, wenn ich eine "0" anstelle von "att_db*2" setze

OK, nächster Schritt: was passiert, wenn du das mit der 0 so läßt, aber zusätzlich eine Zeile höher vor die Variablendeklaration noch ein "volatile" schreibst?

Der Sinn der Sache ist, die Ausführung des Gleitkommacode-Gedöhns zu erzwingen, auch wenn das Ergebnis der Berechnung dann doch nicht benutzt wird.

#7923631
Lesenswert?

Teo D. schrieb:

Was muss an diese Funktion normalerweise übergeben werden, um kein Audio auszugeben. Also um das selbe zu erreichen, wie im Fehlerfall?

Eine hohe Dämpfung, in meinem Fall ist die maximale Dämpfung 60dB. Die wird erreicht, wenn currentVolume den Wert 1023 hat. att_db wird mit Faktor 2 multipliziert, da in 0,5dB-Schritten attenuiert wird.

#7923638
Lesenswert?

Ob S. schrieb:

uint8_t att_db = ((currentVolume * 60.0f)/ 1023.0f); setAllDACVolumes(att_db*2);

Hat jetzt nicht mit dem eigentlichen Problem zu tun, aber die "*2" in dem Funktionsaufruf ist arg ungeschickt. Dadurch lassen sich die 0.5dB-Schritte nicht einstellen. Warum packst du die 2 nicht in die Float-Rechnung, bevor der Wert auf uint8_t konvertiert wird, d.h. skalierst currentVolume direkt auf 0..120?

: Bearbeitet durch User
(Firma: 1984now) #7923649
Lesenswert?

Rainer W. schrieb:

Hat jetzt nicht mit dem eigentlichen Problem zu tun, aber die "*2" in dem Funktionsaufruf ist arg ungeschickt.

Nicht unbedingt.

Wenn es beispielsweise eins der Designziele war (aus welchen Gründen auch immer), nur volle dB-Steps zu ermöglichen, dann passt das schon. Offensichtlich wird ja auch der mögliche Bereich (ganz absichtlich) nicht ausgenutzt.

Nö, wenn da was zu optimieren wäre, dann wäre die erste Maßnahme, den float-Scheiß rauszuwerfen. Das ist, was hier auf jeden Fall kein Mensch braucht (und möglicherweise sogar indirekt über den Resourcenverbrauch der störende Knackpunkt ist).

#7923734
Lesenswert?

Ob S. schrieb:

Nö, wenn da was zu optimieren wäre, dann wäre die erste Maßnahme, den float-Scheiß rauszuwerfen.

wenn ich weiß daß ich float nicht brauche wäre das meine erste Maßnahme und im µC brauche ich für ADC und DAC nie float. Float brauche ich allenfalls für eine Ausgabe zu Menschen und kurz davor kann ich zu float wandeln wenns sein muß und das sogar ohne float Library, ich rechne z.B. in CentiVolt als uint_16 und setze zur Ausgabe nur das Komma per Textersetzung in den String.

: Bearbeitet durch User
(Firma: Jasinski) #7923763
Lesenswert?

Joachim B. schrieb:

wenn ich weiß daß ich float nicht brauche wäre das meine erste Maßnahme und im µC brauche ich für ADC und DAC nie float. Float brauche ich allenfalls für eine Ausgabe zu Menschen und kurz davor kann ich zu float wandeln wenns sein muß und das sogar ohne float Library, ich rechne z.B. in CentiVolt als uint_16 und setze zur Ausgabe nur das Komma per Textersetzung in den String.

Ich persönlich brauche in fast allen µC-Fällen auch keine Fließkommazahlenberechnungen und mache das so ähnlich wie beschrieben. In dem vorliegenden Fall müsste man den Wert an der entsprechenden Stelle auch casten, es wird aber alles sehr zäh und schwierig, wenn jemand nicht einmal in der Lage ist, seine fünf Zeilen Code systematisch so durchzudebuggen, dass er die Ursache für seine falschen Werte eingrenzen und auf diese Weise finden kann. Bei so etwas gibt es bei mir kein Mitleid, denn das kann man relativ schnell lernen und Methoden zum Debuggen gibt es viele; und diese eigenen Hausaufgaben sollte man auch so schnell wie möglich erledigen. Fertigcode gibt es von mir auch nicht, denn das sollte derjenige schon selbst hier so weit korrigieren und anpassen, bis es passt, sonst wird der Lernprozess nie in Gang gesetzt und in ein paar Wochen oder Monaten wird ein neuer Thread mit ähnlicher Problematik eröffnet... und wieder darauf gewartet, dass einem ein fertiges Kotelett serviert wird.

#7924074
Lesenswert?

Joachim B. schrieb:

Float brauche ich allenfalls für eine Ausgabe zu Menschen ...

Gerade der Mensch will eine Darstellung mit Mantisse und Exponent eigentlich nicht habe, sondern kann beim Ablesen besser mit einem Komma an fester Position umgehen, falls es sich nicht um Daten mit extrem großem Dynamikbereich handelt. Dafür eignen sich dann oft logarithmische Angaben besser z.B. in dB oder nach der Richter Skala - je nach Kontext.

#7924077
Lesenswert?

Ob S. schrieb:

Wenn es beispielsweise eins der Designziele war (aus welchen Gründen auch immer), nur volle dB-Steps zu ermöglichen, dann passt das schon. Offensichtlich wird ja auch der mögliche Bereich (ganz absichtlich) nicht ausgenutzt.

Sag mir einen vernünftigen Grund, warum man bei Einsatz eines Potis als Geber für die Lautstärkeeinstellung diese Lautstärke der Einstellung nicht möglichst kontinuierlich folgen soll. So, wie sich der TO mit dem Debuggen schwer tut, vermute ich erstmal keine ernste Absicht hinter dem Verhalten, sondern eher krampfhaftes Festhalten an einer 1dB-Teilung.

: Bearbeitet durch User
#7924079
Lesenswert?

Rainer W. schrieb:

So, wie sich der TO mit dem Debuggen schwer tut, vermute ich erstmal keine ernste Absicht hinter dem Verhalten, sondern eher krampfhaftes Festhalten an einer 1dB-Teilung.

Also so wie bei 98% aller Hobbyprojekten?!

Nenn mir einen vernünftigen Grund, warum dich dieser Thread über haut interessiert? Ernste Absichten scheinst du ja nicht zu haben!

#7924081
Lesenswert?

Statt der float-Herumrechnerei könnte man auch einfach mal einen Blick auf Zweierpotenzen werfen.

Den ADC-Wert, der zwischen 0 und 1023 liegt, teilt man durch 16. Dann erhält man einen Wert zwischen 0 und 63.

Teilen durch eine Zweierpotenz ist einfach nur schieben nach rechts.

Und jetzt macht man noch 'ne simple Bereichsbegrenzung, ist der Wert größer als 60, wird er auf 60 begrenzt.

Daß da potentiell ein bisschen Drehwinkel des Potis kurz vor dem Anschlag verloren geht, sollte man verkraften können, das ist eh' alles analog und Potis vor allem sind nicht irrwitzig linear und genau schon gar nicht.

1
    uint8_t att_db = currentVolume / 16;
2
    if (att_db > 60)
3
      att_db = 60;

Wenn das Ziel tatsächlich ist, auf 60 dB zu kommen, sollte man nicht durch 16, sondern nur durch 8 teilen, denn die Lautstärkenregister arbeiten mit 0.5-dB-Schritten.

Auf die Bereichsbegrenzung kann man auch verzichten, denn die Lautstärkenregister sind 8 Bit breit, können also Werte von 0..255 annehmen.

Und wenn man den vollen Wertebereich der Lautstärkenregister nutzen will, statt bei 60 (oder 63) dB aufzuhören, teilt man den ADC-Wert durch 4.

Fertig. Kein Floatgekrampfe nötig.

#7924083
Lesenswert?

Laurin schrieb:

wenn ich 0 vom Potentiometer an die Funktion „setAllDACVolumes()“ übergebe, erhalte ich keine Dämpfung von 0, sondern kein Audio. Wenn ich aber der Funktion direkt eine "0" gebe, erhalte ich null Dämpfung.

Deine Funktion erwartet einen Integer als Argument. Dort ist die 0 eindeutig definiert, egal woher sie kommt. Es kann nicht sein, dass die Funktion sich unterschiedlich verhält, je nach dem woher die 0 kommt.

Also solltest du diese Annahme nochmal doppelt und dreifach überprüfen, wahlweise mit einem Unterbrechungspunkt innerhalb der Funktion oder mit einer seriellen Text-Ausgabe. Letzteres würde ich bevorzugen.

Wenn das keine neue Erkenntnis bringt dann bedenke, dass Timing eine Rolle spielen kann. Und bedenke, dass ganz andere scheinbar intakte Teile des Programms möglicherweise Fehlfunktionen auslösen, z.B. indem sie einen Heap/Stack Überlauf auslösen, über Array-Grenzen hinaus laufen, oder mit nicht initialisierten Zeigern arbeiten.

Manchmal hilft es, das Programm schrittweise zu verkleinern (Teile auskommentieren, bzw. durch Dummies ersetzen), bus der Fehler verschwindet. Oder anders herum mit einem minimalen Programm neu anfangen und Schrittweise erweitern, bis der Fehler wieder kommt.

: Bearbeitet durch User
(Firma: Jasinski) #7924269
Lesenswert?

Harald K. schrieb:

Statt der float-Herumrechnerei könnte man auch einfach mal einen Blick auf Zweierpotenzen werfen.

Statt mit Float, rechnet man mit kleinstmöglichen Integern – in diesem Fall wäre das z.B. 16-Bit ohne Vorzeichen, weil man auf knapp über 60 Tausend kommen möchte, da man die Multiplikation in der Regel immer zuerst machen muss. Wenn es nicht ausreicht oder besser mit noch anderen Integern im Konzept passt, nimmt man z.B. 32-Bit – die Integerberechnungen (Multiplikationen und Divisionen) werden auf einem AVR höchstwahrscheinlich immer noch schneller als Floatberechnungen sein. Bei Addition und Subtraktion wird es auf jeden Fall so sein – wenn es ganz schnell gehen muss, kann man manche Multiplikationen oder Divisionen auch durch Bitverschiebung und/oder Addition ersetzen (z.B. 2a+a=3a oder 4a+a=5a), diese Akrobatik ist in diesem Fall aber gar nicht nötig. Ferner kann man Wertebereiche auch an Zweierpotenzen ausrichten – z.B. wenn man Dezimationen mit ADCs machen möchte und es schnell gehen soll, braucht man dann am Ende nicht 'krumm' teilen, sondern verschiebt das Ergebnis um so viele Bitstellen wie nötig.

Schreibt man dagegen ein Programm für einen PC, sind Floatberechnungen manchmal die erste Wahl – bei Unity (schreiben eines Spiels) oder WPF-Anwendungen z.B. nimmt die grafische Oberfläche in userem Code von vorherein nur Doubles an, also sogar gleich in zweifacher Genauigkeit, weil das von der GPU der Grafikkarte eh alles schneller berechnet werden kann und beim Rendering, Skalierung oder Transformationen (z.B. Drehung von Objekten auf dem Bildschirm) die Integer nur Probleme verursachen würden bzw. völlig unbrauchbar wären. Falls man die Werte gerundet und in einfacher Form haben möchte, um sie beispielsweise als String in seiner ConfigDatei abspeichern zu können, kann man sie vorher für diesen Zweck entsprechend konvertieren – später beim Auslesen macht man es dann umgekehrt und bildet daraus wieder Doubles, damit die Objekte der Grafik es überhaupt annehmen können.


Daß da potentiell ein bisschen Drehwinkel des Potis kurz vor dem Anschlag verloren geht, sollte man verkraften können, das ist eh' alles analog und Potis vor allem sind nicht irrwitzig linear und genau schon gar nicht.

Die Randproblematik des Potis, die übrigens real ist, kann man auch ganz einfach lösen, indem man den Wertebereich etwas verschiebt, ausdehnt oder kürzt und anschließend mit einer oder zwei IF-Zeilen absichert. Dieser Feinarbeit oder Nachjustierung sollte man sich aber grundsätzlich erst danach widmen, also nachdem unsere Hauptroutinen das tun, was sie sollten, um nicht noch mehr Unbekannte ins Spiel zu bringen. Die Regel der Systematik ist ganz einfach: man macht den Code am Anfang extrem einfach und wenn dieser das tut, was er soll, baut man ihn weiter ein wenig aus und prüft wieder. Beim Debuggen von sehr merkwürdigen Fehlern so vorgehen: die verdächtigen Bereiche/Blöcke stupide Zeile für Zeile debuggen, also sich die Werte anschauen oder ausgeben lassen, denn irgendwo muss der Fehler bzw. müssen die falschen Werte ja auftauchen, manchmal ist der Fehler auch schon früher passiert, weil man einen Denkfehler gemacht hat, aber auch das sieht man bei dieser Art des Debuggens sofort. Und Leute: wir reden hier von 5-10 zeilen Code, um den Zonk dingfest zu machen! Bei ein paar Tausend Zeilen, wie bei mir oft der Fall ist, funktioniert das Prinzip des systematischen Debuggens aber genauso gut.

: Bearbeitet durch User
(Firma: 1984now) #7924363
Lesenswert?

Gregor J. schrieb:

Harald K. schrieb:

Statt der float-Herumrechnerei könnte man auch einfach mal einen Blick auf Zweierpotenzen werfen.

Statt mit Float, rechnet man mit kleinstmöglichen Integern

[...]

Jetzt ist es endgültig sicher: Gregor J. ist eine KI. Die typischen Anzeichen waren ja schon länger da (ausschweifendes Werfen von Wattebällchen).

Aber hier hat es konkret auf auf das Posting von Harald geantwortet, dabei aber offenbart, das es von dem semantischen Inhalt des Postings rein garnichts verstanden hat.

Also: entweder KI oder menschliches semmeldummes Pförtnerkind. Da letztere aber normalerweise zu faul und zu dumm sind, solch ausschweifende Postings in fast fehlerfreiem Deutsch zu verfassen, bleibt nur: KI.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren