Schrittweite tone() ?

Hallo,

kann es sein, dass tone(), nicht jede ganzzahlige Frequenz ausgeben kann, sondern gewisse Schrittgrößen, bzw. Raster hat?
Ich habe eine Anwendung, die ein Messsignal als Rechteckspannung ausgeben soll und dazu benutzte ich tone() mit variablem Parameter.
Die Ausgangsfrequenz ist 3000 Hz, mein Oszilloskop zeigt mir allerdings 3010 Hz an. Kleine Schritte ändern diesen Wert nicht, erst ab etwa 2070 Hz geht die Frequenz tatsächlich runter - es scheint also kein Offset zu sein, sondern ein Raster, über das die Frequenz läuft.
Kann das jemand bestätigen?

Grüße,
Nik

Nik_Del:
Hallo,

kann es sein, dass tone(), nicht jede ganzzahlige Frequenz ausgeben kann, sondern gewisse Schrittgrößen, bzw. Raster hat?

Ja, natürlich. Du bist hier bei Digitaltechnik.
Und aus dem Systemtakt von 16 MHz läßt sich durch die wenigen verfügbaren ganzzahlige Vorteiler(Prescaler) und Teiler nicht jede beliebige andere ganzzahlige Fequenz hundertprozentig exakt herstellen.

Die einstellbaren Frequenzen liegen im unteren Frequenzbereich bis zu wenigen tausend Hertz allerdings immer recht nah an der Wunschfrequenz, mit nur sehr geringen Frequenzabweichungen. Aber oberhalb des Hörbereichs im Ultraschallbereich werden die Frequenzabweichungen dann ggf. auch mal größer ausfallen.

Die von Dir behauptete Frequenzlücke zwischen 3010 und 2070 Hz halte ich allerdings für viel zu hoch, die sollte meiner Meinung nach deutlich geringer sein.

Ich weiß zwar nicht wie groß die Frequenzdiffernz zwischen 2 nahen Frequenzen ist, aber hier https://www.arduino.cc/en/Tutorial/ToneKeyboard?from=Tutorial.Tone3 ist eine Liste der Frequenzen von den Musiktönen. Ich nehme an, daß diese schon von tone() gespielt werden können.

Grüße Uwe

Dass ich es mit diskreten Werten zu tun habe war mir schon klar, ich hatte nur blauäugig erst einmal angenommen, dass diese auf den Ganzzahlen liegen.
Ich bin die Frequenzen einmal abgelaufen und habe gemessen, ab wann sie jeweils auf die nächste Frequenz umspringen. Ich habe festgestellt, dass das Spektrum überhaupt nicht gleichmäßig abgedeckt wird.
Ich hätte erwartet, dass die Abstände größer werden, also Verdopplung der Frequenz = Verdopplung der Frequenzlücke oder etwas ähnliches.
Im Messbereich von 3000 bis 3960 wachsen die Lücken aber von 36 - 60 und von 3937 bis 4166 gehen sie dann von 15 - 17, weiter habe ich nicht gemessen.
Nach 2000 kommt 2016, nach 4000 kommt 4016 und nach 8000 kommt 8064.

Vielleicht kennt sich jemand mit der Funktion genauer aus und kann erklären, wie die Spannung generiert wird?

Im Wesentlichen möchte ich einfach nur in der Lage sein, ganzzahlige Sensorwerte von -1080 bis +1080, also gut 2000 verschiedene Werte, analog auszugeben.

Grüße,
Nik

Nik_Del:
Vielleicht kennt sich jemand mit der Funktion genauer aus und kann erklären, wie die Spannung generiert wird?

Im Wesentlichen möchte ich einfach nur in der Lage sein, ganzzahlige Sensorwerte von -1080 bis +1080, also gut 2000 verschiedene Werte, analog auszugeben.

So ganz detailliert habe ich mir die tone() Funktion nicht angesehen, als dass ich es Dir haarklein erklären könnte, aber meines Wissens nach arbeitet die tone() Funktion so:

Es wird ein sehr schneller Timer-Interrupt aufgesetzt, mit einer Frequenz über 20 kHz, und in der Timer-Interruptbehandlung wird abgezählt: Aus der Häufigkeit, wie oft die Interrupt-Behandlungsroutine aufgerufen wird und der bekannten Zeitdauer zwischen zwei Timer-Interrupts wird abgezählt, nach wie vielen Interrupts wieder ein Flankenwechsel stattfinden muss, und der wird dann auf den Ausgang geschrieben. Die Frequenzlücken ergeben sich dann daraus, daß einerseits die Frequenz des Timer-Interrupts in kein ganzzahliges Vielfaches der auszugebenden Frequenz sein wird und dass anderseits beim Abzählen der Interrupts zwischen zwei Flankenwechseln immer ein Rundungsfehler von bis zu einem Interrupt-Takt im Algorithmus auftreten kann. Und das führt eben dazu, dass am Ende nicht jede ganzzahlige Frequenz bis auf die dritte Stelle nach dem Komma akkurat ausgegeben werden kann, sondern Frequenzlücken entstehen, mit der Folge, dass fast alle ganzzahligen Frequenzen nur mit einer Abweichung von der Sollvorgabe erzeugt werden können.

ich glaube das kann so nicht sein wie du es beschreibst

Nehmen wir 20Khz als Timerfrequenz an, wäre ein 20KHz Tone möglich, der nächste könnte aber nur 10KHz haben. Alles dazwischen wäre garnicht machbar. Dann 5K, dann 2,5k usw. Also halbierend. Weil du ja nur ganzzahlig teilen kannst. Selbst bei 100KHz Timerfrequenz sind dann nur sehr grobe Abstufungen möglich

100KHz/50 = 2KHz
100KHz/51 = 1960Hz

100KHz/100 = 1KHz
100KHz/101 = 990Hz

Eher wird da wohl ein Timer genau so gesetzt das aus dem Grundtakt die Frequenz entsteht und bei jedem Aufruf wird der Ausgang getoggelt. Auch das bleibt halt stufig. Wieso aber solche Abweichungen in den Stufen...wäre interessant zu erfahren.

chefin:
ich glaube das kann so nicht sein wie du es beschreibst

Nehmen wir 20Khz als Timerfrequenz an, wäre ein 20KHz Tone möglich, der nächste könnte aber nur 10KHz haben. Alles dazwischen wäre garnicht machbar. Dann 5K, dann 2,5k usw. Also halbierend. Weil du ja nur ganzzahlig teilen kannst. Selbst bei 100KHz Timerfrequenz sind dann nur sehr grobe Abstufungen möglich

100KHz/50 = 2KHz
100KHz/51 = 1960Hz

100KHz/100 = 1KHz
100KHz/101 = 990Hz

Eher wird da wohl ein Timer genau so gesetzt das aus dem Grundtakt die Frequenz entsteht und bei jedem Aufruf wird der Ausgang getoggelt. Auch das bleibt halt stufig. Wieso aber solche Abweichungen in den Stufen...wäre interessant zu erfahren.

Ja, so ungefähr hätte ich es erwartet. Es scheint so, als gäbe es verschiedene Bereiche, innerhalb derer die Abstände mit der Frequenz gleichmäßig wachsen, bis ein neuer Bereich anfängt.

Ich habe übrigens mittlerweile die Library toneAC gefunden, die tatsächlich in der Lage ist, auch um 2 kHz herum Frequenzen mit Unterschieden von 1 Hz zu erzeugen. Der Teufel steckt also offenbar nicht (nur) in der Hardware, sondern in der Library. Mein Kernproblem ist damit gelöst :slight_smile:

Wenn man tone() nur für einfache Töne braucht, sollte es reichen.

Die in der üblichen Musik verwendeten Töne

  • 12 Schritte bis die doppelte/halbe Frequenz ("Oktave") erreicht ist
  • die auch in der üblichen Notenschrift dargestellt werden können
  • für die es Namen in der Library (pitches.h) gibtliegen um ca. 5,95% auseinander. Das reicht näherungsweise mit den hier verwendeten Zählern, selbst bei einem 8-bit Timer.
    Der nächste Ton nach einem "a" (NOTE_A4) mit 440 Hz ist das "ais" oder "b" (NOTE_AS4) mit 466 Hz.

Den Unterschied zwischen 2000 und 2001 Hz bemerkt ein normaler Mensch nur, wenn beide Töne gleichzeitig zu hören sind, an den überlagerten "Schwebungen".

In der Digitaltechnik gibt es sowieso keine stufenlosen Unterschiede.
(Im echten Leben übrigens auch nicht, wenn man es quantenphysikalisch sieht)
Fragt sich immer, wie genau man was braucht.

Habs mal gerade durchgerechnet

So ab 100KHz Timerfrequenz (also alle 10µs einen Interrupt) kommt man in den bereich, das tiefe Töne bis hin zum Sprachbereich nur Abweichungen von ca 5Hz haben. Wobei manche Töne bis auf 0,0xHz genau getroffen werden, aber auch bis zu 5 Hz abweichen.

Höher als 1Khz Tonhöhe und die Abweichungen gehen auch mal bis zu 10Hz daneben.

100KHz Timerfrequenz dürfte aber den Arduino nahezu völlig auslasten, weil ihm gerade mal 160 Takte zwischen 2 Interrupts bleiben.

Es ist sicherlich eine Möglichkeit, aber die spielt extrem falsch, so falsch das selbst unmusikalische Menschen das hören und sie kostet viel CPU-Last.

Eleganter ist es, den Timer auf die Frequenz zu setzen. Will ich 4KHz, lasse ich alle 125µsec einen Interrupt kommen. Gegenüber der Teilervariante die alle 10µsec einen Interrupt auslöst ist das deutlich schonender. Und brauche ich nur 1Khz Ton, bin ich bei 0,5msec. So bekomme ich viel genauer den Ton hin und deutlich CPU-schonender.

Und da der Threadstarter das Problem mit einer anderen Bibliothek lösen konnte, gehts hier eigentlich nur noch um theoretische Ansätze. Aber ich nehme mir trotzdem die Freiheit einen relativ untauglichen Ansatz zu bewerten. Natürlich kommt da irgendwas an Musik dabei raus, aber die Masse an Nachteilen ist so hoch, das man es besser wieder vergisst

Habs mal gerade durchgerechnet

Hast du auch mal tone.cpp angeguckt?
Deine Berechnungen sind relativ irrelevant, ausser du willst was völlig anderes erfinden.

Frequenzabweichungen als Differenz zu betrachten, ist übrigens der falsche Ansatz:
10 Hz Unterschied ist nur ganz leicht verstimmt bei 2000 Hz , aber bei tieferen Tönen schon ein ganzer Ton daneben (z.B E3 = 164,8 Hz / F3 = 174,6 Hz).

tone() ist nett, um einigermassen wiedererkennbare Melodien auf einem Piezo zu erzeugen, mehr nicht. Allein die fehlende Möglichkeit der Lautstärke-Änderung zeigt, dass tone() nicht wirklich mit Musik-Erzeugung zu tun hat.

Eine weitere Frage ist, wie lange dein Messsignal ausgewertet wird. Um 2000 von 2001Hz zu unterscheiden, musst du mindestens eine Sekunde lang messen. Je nach Anwendung ist das eine Ewigkeit.

... Oder 499,75 µs von 500,0 µs unterscheiden können. Dann kannst du zu jedem Signalwechsel sofort die Frequenz angeben. Wenn man nur 4997 µs von 5000 µs unterscheiden kann, braucht man eben 5 ms oder 10 Perioden, um 2000 Hz von 2001 Hz unterscheiden zu können. (usw.)

Wenn man nur die Anzahl Schwingungen je festem ZeitIntervall zählt, dauert es länger, da hast du recht.

Ich habe mal versucht, das nur mit Arduino Bordmitteln zu untersuchen,
deckt das sich einigermaßen mit deinen Frequenzzählerergebnissen?

const byte testPin = 13;
const byte messungen = 5;
unsigned int von = 31;
unsigned int bis = 1000;
unsigned int schritt = 1;

void setup() {
  pinMode(testPin, OUTPUT);
  Serial.begin(250000);
  for (unsigned long freq = von; freq <= bis; freq += schritt) {
    unsigned long dauer = 0;
    tone(testPin, (unsigned int) freq);
    for (byte i = 0; i < messungen; i++) {
      dauer += pulseIn(testPin, HIGH);
      dauer += pulseIn(testPin, LOW);
    }
    dauer /= messungen;
    Serial.print(F("Frequenz "));
    Serial.print(freq);
    Serial.print(F(" Dauer "));
    Serial.print(dauer);
    Serial.print(F(" berechnet "));
    unsigned long berechnet = 100000000UL / dauer;
    Serial.print(berechnet / 100);
    Serial.write('.');
    unsigned int nachkomma = berechnet % 100;
    if (nachkomma < 10) {
      Serial.write('0');
    }
    Serial.println(nachkomma);
  }
}

void loop() {}
Frequenz 31 Dauer 32046 berechnet 31.20
Frequenz 32 Dauer 31028 berechnet 32.22
Frequenz 33 Dauer 30010 berechnet 33.32
Frequenz 34 Dauer 29118 berechnet 34.34
Frequenz 35 Dauer 28356 berechnet 35.26
Frequenz 36 Dauer 27594 berechnet 36.23
Frequenz 37 Dauer 26830 berechnet 37.27
Frequenz 38 Dauer 26067 berechnet 38.36
Frequenz 39 Dauer 25431 berechnet 39.32
Frequenz 40 Dauer 24795 berechnet 40.33
Frequenz 41 Dauer 24160 berechnet 41.39
Frequenz 42 Dauer 23652 berechnet 42.27
Frequenz 43 Dauer 23012 berechnet 43.45
Frequenz 44 Dauer 22505 berechnet 44.43
Frequenz 45 Dauer 21996 berechnet 45.46
Frequenz 46 Dauer 21486 berechnet 46.54
Frequenz 47 Dauer 21105 berechnet 47.38
Frequenz 48 Dauer 20597 berechnet 48.55
Frequenz 49 Dauer 20215 berechnet 49.46
Frequenz 50 Dauer 19835 berechnet 50.41
Frequenz 51 Dauer 19453 berechnet 51.40
Frequenz 52 Dauer 19071 berechnet 52.43
Frequenz 53 Dauer 18688 berechnet 53.51
Frequenz 54 Dauer 18307 berechnet 54.62
Frequenz 55 Dauer 18052 berechnet 55.39
Frequenz 56 Dauer 17670 berechnet 56.59
Frequenz 57 Dauer 17417 berechnet 57.41
Frequenz 58 Dauer 17035 berechnet 58.70
Frequenz 59 Dauer 16780 berechnet 59.59
Frequenz 60 Dauer 16526 berechnet 60.51
Frequenz 61 Dauer 16272 berechnet 61.45
Frequenz 62 Dauer 16017 berechnet 62.43
Frequenz 63 Dauer 15762 berechnet 63.44
Frequenz 64 Dauer 15507 berechnet 64.48
Frequenz 65 Dauer 15254 berechnet 65.55
Frequenz 66 Dauer 15000 berechnet 66.66
Frequenz 67 Dauer 14744 berechnet 67.82
Frequenz 68 Dauer 14490 berechnet 69.01
Frequenz 69 Dauer 14363 berechnet 69.62
Frequenz 70 Dauer 14109 berechnet 70.87
Frequenz 71 Dauer 13981 berechnet 71.52
Frequenz 72 Dauer 13726 berechnet 72.85
Frequenz 73 Dauer 13602 berechnet 73.51
Frequenz 74 Dauer 13346 berechnet 74.92
Frequenz 75 Dauer 13218 berechnet 75.65
Frequenz 76 Dauer 12962 berechnet 77.14
Frequenz 77 Dauer 12835 berechnet 77.91
Frequenz 78 Dauer 12708 berechnet 78.69
Frequenz 79 Dauer 12455 berechnet 80.28
Frequenz 80 Dauer 12327 berechnet 81.12
Frequenz 81 Dauer 12200 berechnet 81.96
Frequenz 82 Dauer 12073 berechnet 82.82
Frequenz 83 Dauer 11946 berechnet 83.71
Frequenz 84 Dauer 11818 berechnet 84.61
Frequenz 85 Dauer 11567 berechnet 86.45
Frequenz 86 Dauer 11437 berechnet 87.43
Frequenz 87 Dauer 11309 berechnet 88.42
Frequenz 88 Dauer 11182 berechnet 89.42
Frequenz 89 Dauer 11055 berechnet 90.45
Frequenz 90 Dauer 10927 berechnet 91.51
Frequenz 91 Dauer 10802 berechnet 92.57
Frequenz 92 Dauer 10673 berechnet 93.69
Frequenz 93 Dauer 10674 berechnet 93.68
Frequenz 94 Dauer 10545 berechnet 94.83
Frequenz 95 Dauer 10419 berechnet 95.97
Frequenz 96 Dauer 10292 berechnet 97.16
Frequenz 97 Dauer 10165 berechnet 98.37
Frequenz 98 Dauer 10038 berechnet 99.62
Frequenz 99 Dauer 9910 berechnet 100.90
Frequenz 100 Dauer 9910 berechnet 100.90

michael_x:
... Oder 499,75 µs von 500,0 µs unterscheiden können. Dann kannst du zu jedem Signalwechsel sofort die Frequenz angeben.

Das setzt aber auch voraus, dass das Timing der Signalerzeugung perfekt gleichmäßig ist und kein Jitter im Signal ist. Der Unterschied von 0,25µs sind nur 4 Prozessortakte bei 16Mhz.

Der Unterschied von 0,25µs sind nur 4 Prozessortakte bei 16Mhz.

Schon richtig. Wollte nur darauf hinweisen, dass man nicht unbedingt eine ganze Sekunde braucht, um den Unterschied zwischen 2000 Hz und 2001 Hz zu erkennen :slight_smile: