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
intcurrentVolume=ADC_read(0);
2
if(currentVolume!=lastVolume){
3
uint8_tatt_db=((currentVolume*60.0f)/1023.0f);
4
setAllDACVolumes(att_db*2);
5
lastVolume=currentVolume;
6
}
Die setAllDACVolumes()-Funktion sieht so aus:
1
voidsetAllDACVolumes(uint8_tValue)
2
{
3
inti;
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.
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.
Was passiert bei currentVolume == lastVolume ?
Wenn Du das Poti auf 0 stellst, wird das nach dem ersten Mal so sein.
Bei anderen Werten dithert der ADC Wert so rum, da tritt der Fall selten
auf. Möglicherweise steckt das Problem an einer ganz anderen Stelle als
Du vermutest.
Thilo R. schrieb:> Was passiert bei currentVolume == lastVolume ?> Wenn Du das Poti auf 0 stellst, wird das nach dem ersten Mal so sein.
lastVolume ist definiert als 0xff. Die Bedingung war beim Debuggen zu
Beginn nie true.
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?
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.
Teo D. schrieb:> Laurin schrieb:>> erhalte ich keine Dämpfung von 0, sondern>> kein Audio.>> Was müsste normal gesendet werden, um das zu erreichen?
Das:
1
voidsetAllDACVolumes()
2
{
3
inti;
4
for(i=0;i<8;i++)
5
{
6
SendCtrlReg2(DAC_REG_VOLUME_BASE+i,0);// set DAC Volumes, 0x00 is default
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.
Laurin schrieb:> und debugge mit einem Atmel ICE.
Na dann laß Dir doch mal anzeigen, was als "Value" übergeben wird.
Laurin schrieb:> int currentVolume = ADC_read(0);
Also kann ADC_read() auch negative Werte liefern?
Laurin schrieb:> uint8_t att_db = ((currentVolume * 60.0f)/ 1023.0f);
Was passiert dabei mit negativen Werten?
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.
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.
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.
Rainer W. schrieb:> Früher (tm) nannte man das Dreisatz, genauer Geradengleichung in der> Zwei-Punkte-Form.
Auch heute noch.
Aber vielleicht sind solche Funktionen schon optimiert und funktionieren
besser?
Ich weiß es nicht. Oder meinst du, dass alle zu doof zum Rechnen sind,
dass man so was implementieren muss?
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 ...
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."
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.
Auf dem Bild seht Ihr den Debug-Mode. Und die Variable att_db, die an
die Funktion setAllDAC Volumes() übergeben wird ist 0. Aber ich bekomme
keine 0 Attenuation und damit auch kein Audio. Das ist für mich nicht
erklärbar..
Laurin schrieb:> Auf dem Bild seht Ihr den Debug-Mode. Und die Variable att_db, die an> die Funktion setAllDAC Volumes() übergeben wird ist 0. Aber ich bekomme> keine 0 Attenuation und damit auch kein Audio. Das ist für mich nicht> erklärbar..
Was steht in Register15, Bit0?
Ob S. schrieb:> Laurin schrieb:>>> Auf dem Bild seht Ihr den Debug-Mode. Und die Variable att_db, die an>> die Funktion setAllDAC Volumes() übergeben wird ist 0. Aber ich bekomme>> keine 0 Attenuation und damit auch kein Audio. Das ist für mich nicht>> erklärbar..>> Was steht in Register15, Bit0?
Register 15 ist auf default
Laurin schrieb:> Aber ich bekomme keine 0 Attenuation
Wenn du aber statt der att_db*2 die setAllDACVolumes mit 0
einkommentierst geht es ?
stack overflow.
Rainer W. schrieb:> Laurin schrieb:>> Register 15 ist auf default>> Zeigt der Debugger "default" oder wie kommst du auf den Wert?
Ich fasse das Register 15 aktuell nicht an. Deshalb ist es wohl auf
default.
> 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
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.
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.
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?
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).
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.
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.
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.
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.
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!
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_tatt_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.
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.
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.
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.