PWM-Ausgabe / Phasenverschiebung

Hallo Forum

Ich verwende die Bibliothek PWM.h zum Erzeugen eines in der Hoehe veraenderbaren Tons, welcher auf einem angeschlossenen Kopfhoerer ausgegeben wird.
Alles funktioniert, wie es funktionieren soll.

Jetzt aber die folgende Wunschliste - diese ist sehr kurz, sie enthaelt naemlich lediglich EINEN Wunsch:
Die PWM-Amplitude soll zeitlich verschoben werden.

Eine fixe zeitliche Verschiebung um 50% ist angestrebt, eine variable, mit einem weiteren Poti einstellbare Phasenverschiebung waere das Tuepfelchen auf dem i.

Hat jemand dazu eine Loesung parat?

Beste Gruesse kommen von
Matthias Zeller

(deleted)

Relativ wozu soll die Amplitude (der Impuls) verschoben werden?

Hi

Schau Dir Mal den PCA9685 an.
Das ist ein 16-Kanal PWM-Treiber.
Oft für (bis zu 16) Servos genutzt.
Die Frequenz ist einstellbar, in wie weit diese Einstellbarkeit für Dich reicht, müsste man schauen.
Jedem Kanal kann 'gesagt' werden, zu welchem 'Tick' Er AN geht und zu Welchem Er AUS geht - in 12 Bit (0...4095).

MfG

DrDiettrich:

Ich habe zwei PWM-Signale.
Der Einfachheit nenne ich sie Signal 1 und Signal 2.
Die Verschiebung von Signal 2 soll sich relativ auf Signal 1 beziehen.

Danke :slight_smile:

Hallo,

kann man alles mit einem Timer machen. :slight_smile:

Auf Grund der sequentiellen Abarbeitung sind zum Bsp. zeitgleiche Einschaltpunkte immer geringfügig versetzt, hier um 4,5µs. Möchte man das noch syncron haben, müßte man direkt auf Registerebene arbeiten und alle Signale auf einen gemeinsamen Port legen. Dann könnte man die Bits entsprechend positionieren woraufhin die Ausgabe automatisch syncron wird. Mehr fällt mir dazu aktuell nicht ein. Ich vermute jedoch das 4,5µs im Audiobereich nicht stören.

Die Zeitbasis pro Timer ISR Count ist aktuell eingestellt auf rechnerisch 80µs.
Resultiert auf Prescaler 64 und CompareMatch 20 was 4µs * 20 ergibt.
Daraus folgt das alle Pulseinstellungen im "Daten struct" Vielfache von 80µs sind.
PERIODENDAUER, pulsTime[ANZAHL] und pulsDelay[ANZAHL] sowie pulsRelativDelay[ANZAHL].
Wobei letzteres aus den anderen berechnet wird.
Das Ergebnis ist wiederum abhängig von der Phaseneinstellung, ob absolut oder relativ zueinander.
Das wird mittels "enum class phase" festgelegt.

bool led.update kann im weiteren Programm genutzt werden um Pulszeitenänderungen erst dann zu erlauben wenn alle Signale einen gemeinsamen Durchlauf absolviert haben. Wenn nicht wichtig werden neue Werte sofort übernommen.

Jetzt muss Matze nur sein(e) Poti(s) auslesen, entsprechend skalieren und ins gewünschte Array schreiben. Entweder led.pulsTime oder led.pulsDelay oder in beide. Wie auch immer.

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.12
  avr-gcc 9.2.0
  Arduino Mega2560
  04.03.2020
  License: GNU GPLv3
  CombiePin Library hat User combie zur Verfügung gestellt
*/

#include <util/atomic.h>
#include <CombiePin.h>
using namespace Combie::Pin;
OutputPin<41> messPin;

enum class state : byte {RELEASE, DEBUG};   // Serielle aktivieren
const state debugging = state::DEBUG;

enum class phase : byte {ABSOLUT, RELATIV}; // Phasenlage vom Delay festlegen
const phase phaseLage = phase::RELATIV;

class Task    // Interface
{
  public:
    virtual void init() = 0;
    virtual void on()   = 0;
    virtual void off()  = 0;
};

template<byte outPin>
class Led: public Task
{
  private:
    Combie::Pin::OutputPin<outPin> led;

  public:
    void init() {
      led.init();
    }
    void on()   {
      led.setHigh();
    }
    void off()  {
      led.setLow();
    }
};

byte const ANZAHL = 5;

struct Daten
{
  unsigned int const PERIODENDAUER = 20;              // muss größer gleich der Summe pulsTime + pulsDelay bzw. pulsRelativDelay sein
  unsigned int pulsTime[ANZAHL]  = {8, 2, 5, 4, 6};   // Pulsdauer
  unsigned int pulsDelay[ANZAHL] = {0, 3, 1, 2, 1};   // Pulsverzögerung, abhängig ob Phasenlage absolut oder relativ ist
  unsigned int pulsRelativDelay[ANZAHL] = {0};        // Platzhalter, wird berechnet
  bool done[ANZAHL] = {false};
  unsigned int longestPulsTime = 0;
  volatile bool updated = false;                      // zur freien Verwendung außerhalb der Timer ISR
  Task *pin[ANZAHL] = {
    new Led<22>(),
    new Led<23>(),
    new Led<24>(),
    new Led<25>(),
    new Led<26>(),
  };
} led;


void setup(void)
{
  if (debugging == state::DEBUG)
  {
    Serial.begin(250000);
    Serial.println("\nStart");
  }

  messPin.init();

  for (Task *i : led.pin) i->init();

  if (phaseLage == phase::ABSOLUT)
  {
    led.longestPulsTime = maxFromArray(led.pulsTime, led.pulsDelay, ANZAHL);
  }

  if (phaseLage == phase::RELATIV)
  {
    calcRelativDelay(led.pulsDelay, led.pulsRelativDelay, ANZAHL);
    led.longestPulsTime = maxFromArray(led.pulsTime, led.pulsRelativDelay, ANZAHL);
  }
  
  if (debugging == state::DEBUG)
  {
    Serial.print(F("led.longestPulsTime ")); Serial.println(led.longestPulsTime);
    Serial.print(F("led.PERIODENDAUER "));   Serial.println(led.PERIODENDAUER);
    if (led.longestPulsTime > led.PERIODENDAUER)
    {
      Serial.println(F("WARNUNG: PERIODENDAUER ist zu kurz!"));
    }  
  }

  led.updated = true;
  preSetTimer1();
  runTimer1();
}


void loop(void)
{

}


// ****** Funktionen ******* //

ISR(TIMER1_COMPB_vect)    //
{
  messPin.setHigh();

  static bool finish = false;
  static unsigned int count = 0;

  if (!finish)              // verkürzt ISR Rechenzeit wenn nichts zu tun ist
  {
    for (byte i = 0; i < ANZAHL; ++i)
    {
      // wenn Led innerhalb der Periode noch nicht eingeschalten wurde, dann nach 'pulsDelay' einschalten

      if (phaseLage == phase::ABSOLUT)
      {
        if ( (count >= led.pulsDelay[i]) && (!led.done[i]) ) led.pin[i]->on();
      }

      if (phaseLage == phase::RELATIV)
      {
        if ( (count >= led.pulsRelativDelay[i]) && (!led.done[i]) ) led.pin[i]->on();
      }

      // Led nach Summenwert ausschalten und für die Periode merken
      if (phaseLage == phase::ABSOLUT)
      {
        if (count >= led.pulsTime[i] + led.pulsDelay[i])
        {
          led.pin[i]->off();
          led.done[i] = true;
        }
      }

      if (phaseLage == phase::RELATIV)
      {
        if (count >= led.pulsTime[i] + led.pulsRelativDelay[i])
        {
          led.pin[i]->off();
          led.done[i] = true;
        }
      }
    }
  }

  // wenn die längste Zeit von allen Signale abgelaufen ist, dann obige Vergleiche ignorieren
  // längste Zeit ist die Summe aus Hightime + absolute/relative Delaytime
  if (count >= led.longestPulsTime)
  {
    finish = true;            // verkürzt ISR Rechenzeit wenn nichts zu tun ist
    led.updated = true;       // zur freien Verwendung außerhalb der Timer ISR
  }

  count++;

  // wenn Periodendauer abgelaufen alles wieder zurücksetzen
  if (count >= led.PERIODENDAUER)
  {
    finish = false;
    memset(led.done, false, ANZAHL);  // Zustände löschen
    count = 0;
  }

  messPin.setLow();
}


// ****** Funktionen ******
template <typename A, typename S>
unsigned long calcLastPulsTime(A const *puls, S const size)
{
  unsigned long sum = 0;

  for (S i = 0; i < size; ++i)
  {
    sum += puls[i];
  }

  return sum;
}


template <typename A, typename S>
void testAusgabe (A const *arr, S const size)
{
  for (S i = 0; i < size; i++)
  {
    Serial.print(arr[i]);
    Serial.print(',');
  }
  Serial.println();
}


template <typename A, typename S>
void calcRelativDelay (A const *dela, A *rela, S const size)
{
  if (debugging == state::DEBUG) Serial.print(F("led.puls Relativwerte "));

  rela[0] = dela[0];        // ersten Delayeintrag einfach übernehmen

  for (S i = 1; i < size; i++)
  {
    // aktuellen Wert mit dem vorherigen addieren und im aktuellen Index speichern,
    // damit wird jeder Puls relativ zum vorherigen verzögert
    rela[i] = rela[i-1] + dela[i];
  }

  if (debugging == state::DEBUG)
  {
    for (S i = 0; i < size; i++)
    {
      Serial.print(rela[i]);
      Serial.print(',');
    }
  }
  if (debugging == state::DEBUG) Serial.println();
}


template <typename A, typename S>
A maxFromArray (A const *puls, A const *dela, S const size)
{
  A maximal = 0;

  if (debugging == state::DEBUG)
  {
    Serial.print(F("led.puls Summenwerte "));
  }

  for (S i = 0; i < size; i++)
  {
    A sum = puls[i] + dela[i];
    if ( sum > maximal)
    {
      maximal = sum;
    }
    if (debugging == state::DEBUG)
    {
      Serial.print(sum);
      Serial.print(',');
    }
  }

  if (debugging == state::DEBUG) Serial.println();

  return maximal;
}


void preSetTimer1 ()     // Voreinstellungen, läuft noch nicht los
{
  cli();                  // Interrupts ausschalten
  TCCR1B = 0;             // Reset
  TCCR1A = 0;             //
  TIMSK1 = 0;             //
  TCNT1  = 0;             //
  OCR1A  = 20;            //
  TCCR1B = (1 << WGM12);  // CTC Mode
  TIMSK1 = (1 << OCIE1B); // enable Compare Match B ISR
  sei();                  // Interrupts einschalten
}


void runTimer1 ()
{
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
    TCCR1B |= (1 << CS11) | (1 << CS10);  // Prescaler 64
  }
}


void stopTimer1 ()
{
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
    TCCR1B &= ~( (1 << CS12) | (1 << CS11) | (1 << CS10) );
  }
}

Phasenlage absolut:


Phasenlage relativ:

CombiePin.zip (305 KB)

Hallo Doc_Arduino

Vielen Dank fuer diesen Loesungsvorschlag.
Ich glaube auch wirklich daran, dass dies funktionieren wird, allerdings:

Meine Arduino-Programmier-Kenntnisse reichen nicht aus um herauszufinden, wo genau ich nun die Potiwerte einzutragen haben.
Kannst du mir da bitte eine kurze Hilfe geben?

Vielen Dank :slight_smile:

Ich sollte wohl eindeutiger erklaeren, was genau dieses Programm bezwecken soll.

Auf dem Kopfhoerer soll am linken Ohr ein einstellbarer Ton von ca. 5.000 Hz ausgegeben werden.
Poti 1 veraendert die Tonlage im Bereich von plus minus 1 kHz.

Auf der rechten Ohrmuschel soll dieser Ton mit einer Phasenverschiebung wiedergegeben werden.
Dabei wird mit Poti 2 der Grad der Verschiebung eingestellt.

Der Hintergedanke ist: Ich moechte rechts einen Antischall vom linken Signal erzeugen, so dass sich diese beiden gegenseitig aufheben.

Besten Dank :slight_smile:

Wenn ich mich richtig erinnere, erfolgt die Auslöschung bei 180° Phasenverschiebung wenn die beiden Schwingungen aufeinander Treffen. Ich bezweifle, dass das durch Deinen Kopf hindurch funktioniert. Von gesundheitlichen Aspekten mal ganz abgesehen.

Gruß Tommy

Hallo Tommy56

Ich musste schmunzeln bei deinen Worten.............. :slight_smile:

Nein, der Kopfhoerer dient mir lediglich als erstes Testobjekt.
Da soll nichts durch meinen Kopf hindurch funktionieren.
Und ueberhaupt: Schall breitet sich ja eh nur ueber ein Medium aus.
Und wo nichts ist.............. :wink:

Aber danke trotzdem fuer deinen Beitrag.

Matze

Du glaubst nicht, welche Ideen hier manchmal auftauchen :wink:
Wenn es aber zum Schmunzeln gereicht hat, war es nicht vergeblich.

Gruß Tommy

Dann wollen wir doch erst einmal die physiologischen Grundlagen klären. Eine Auslöschung findet an allen Punkten im Raum statt, wo die ankommenden Töne eine Phasenverschiebung von 180° haben. Da die Ohren aber an zwei verschiedenen Punkten sitzen, kann sich so nichts aufheben.

Auch sonst ergibt sich die Phasenlage im Raum aus der Laufzeit der beiden Signale und ihrer Frequenz. Bei konstanter Laufzeit muß also die Phasenverschiebung an die Frequenz angepaßt werden. Mit einem Timer ist das aber nicht so schwierig, weil seine Taktfrequenz im CTC Modus eine feste Verschiebung um N Takte erlaubt, unabhängig von der Frequenz. Mit ICR oder OCRA stellt man die Signalfrequenz ein, und mit OCRB die zeitliche Verschiebung von Kanal B. Man muß lediglich darauf achten, daß OCRB nicht höher werden darf als das mit ICR oder OCRA eingestellte TOP.

Mit zwei Ohren muß die Bediningung an zwei Punkten gleichzeitig erfüllt sein. Das geht recht einfach mit einem Lautsprecher vor und einem hinter dem Kopf, so daß die Schallwellen die gleiche Laufzeit zu beiden Ohren haben.

Ggf. ist noch die Form der Ohren zu berücksichtigen, siehe Kunstkopf-Stereophonie. Ein Kollege hat seinerzeit Naturkopf-Aufnahmen gemacht, indem er die Ohrenstöpsel als Mikrophone verwendet hat, und hat damit nach eigener Aussage ein unübertreffliches Raumerlebnis beim Abspielen erreicht.

An DrDiettrich:

Menschenskinder..............bin ich hier im Forum von Arduino oder in einer Hoerstudien-Uni?
Wahnsinn, was ihr hier fuer ein Wissen mitbringt - vielen Dank schonmal vorab fuer diese Info.

Meine Absicht ist nicht das gegenseitige Ausloeschen des Signals, ich habe das mit dem Kopfhoerer nur erwaehnt, um moeglichst simpel und verstaendlich aufzuzeigen, was ich mit diesem Programm erreichen moechte.

Jetzt werde ich nochmals etwas konkreter:
Das geht vor allem an Doc_Arduino und an DrDiettrich:
Wenn ihr mir einen ausfuehrbaren Sketch anbieten koennt, der diese besagten Kriterien erfuellt, werde ich mich finanziell erkenntlich zeigen.
Und euch im bevorstehenden Produkt dann spaeter auch gerne namentlich erwaehnen werde.

Ich heisse Matthias Zeller und komme von https://kpf-zeller.de

Wir koennen das hier ueber das Forum angehen oder direkt ueber meine Mailadresse namens service@kpf-zeller.de

Beste Gruesse!

Hallo,

also du brauchst nur 2 Signale.
Du brauchst keine zusätzliche PWM.
Die Frequenz vom 1. Signal soll 4kHz bis 6kHz sein mit 50% DutyCycle.
Das 2. Signal soll genauso sein nur eben Phasenverschoben.
Richtig verstanden?
Welche Anforderung besteht am Frequenzverhalten bei Frequenzänderung?
Darf die Frequenz bei Änderung kurzzeitig Mist machen oder nicht?
Was machst du damit wenn es fertig ist?
Welchen Arduino hast du?

Hallo Doc_Arduino

Ja, alles richtig verstanden.
Die Aenderung der Frequenz dient lediglich zum Auffinden der korrekten Frequenz. Das ist quasi ein einmaliger Akt. Ein bisschen Mist darf da gerne dabei sein, kein Problem.

Bei der Wahl des Arduino bin ich eigentlich frei, ich habe so ziemlich jeden hier.
Vorzugsweise waere mir ein Nano lieb, Leonardo, Uno und Mega stehen ebenfalls zur Verfuegung.

Zur Frage, was ich damit vorhabe:
Ich moeche mich mit meiner laienhaften Programmierkunst nicht vorzeitig blamieren und behalte es vorerst noch fuer mich.

Vielen Dank!

Hallo,

dann kann es ja losgehen.
Meine obige Softwaretakterzeugung kommt bei 5kHz nicht mehr mit.
Das der Timer bei Frequenzänderung keinen Mist macht habe ich dennoch eingebaut.
Wenn du normale Potis nimmst sollte eine Auflösung von 83 Zwischenstufen ausreichen.
Minimaler Versatz bei 5kHz sind 0,5µs.
Alles weitere hängt von der Qualität der Potis ab, wie nah man an die Bereichsgrenzen kommt.
Die Phasenverschiebung deckt einen Bereich von 0° bis 180° ab.
Aktuelle Phasenlage bleibt bei Frequenzänderung erhalten.

Du müßtest jetzt nur die Pins der Timerausgänge und der Analogeingänge anpassen.
Zeile 14, 15 und 28, 29.

Bitteschön.

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.12
  avr-gcc 9.3.0
  Arduino Mega2560
  26.03.2020
  License: GNU GPLv3

  https://forum.arduino.cc/index.php?topic=668749.0
*/

#include <util/atomic.h>

const byte pinFreqA = 11;   // Pin OCR1A (am Uno/Nano Pin  9)
const byte pinFreqB = 12;   // Pin OCR1B (am Uno/Nano Pin 10)

struct Poti
{
  const byte pin;
  unsigned int value = 0;
  unsigned int valueMapped = 0;
  float mittelwertCalc = 0;
  unsigned int mittelwert = 0;
};

const byte ANZAHL_POTI = 2;
Poti poti[ANZAHL_POTI] = {
  {A1},     // Frequenzpoti
  {A2}      // Phasenpoti
};

/*               4        5        6[kHz]
  Prescaler 1: 1999 ... 1599 ... 1332       Auflösung: 667
  Prescaler 8:  249 ...  199 ...  166       Auflösung:  83   */
const byte COMPAREvalueMIN = 166;
const byte COMPAREvalueMAX = 249;

const byte FILTERFAKTOR = 8;     // Filterfaktor zur Mittelwertbildung


void setup(void)
{
  Serial.begin(9600);

  pinMode(pinFreqA, OUTPUT);
  pinMode(pinFreqB, OUTPUT);

  preSetTimer1();
  runTimer1();
}


void loop(void)
{
  readPoti(50);             // liest zyklisch alle Potis ein
  mapPoti();                // passt den gelesenen Potiwert an den erforderlichen Bereich an, Phasenlage bleibt erhalten
  filterValueMapped();      // glättet eingelesene Potiwerte, dazu gehört obiger FILTERFAKTOR
  //seriellerMonitor(1000); // bei Bedarf oder Debugging
}


// ****** Funktionen ******* //
// Grüsse an GuntherB
void filtern(float &filtVal, const unsigned int newVal, const byte FF) {
  filtVal = ((filtVal * FF) + newVal) / (FF + 1);
}


void filterValueMapped(void)
{
  for (byte i = 0; i < ANZAHL_POTI; ++i)
  {
    // kontinuierliche Mittelwertbildung
    filtern(poti[i].mittelwertCalc, poti[i].valueMapped, FILTERFAKTOR);
    // Ergebnis korrekt runden und als Ganzzahl übergeben
    poti[i].mittelwert = static_cast<unsigned int>(poti[i].mittelwertCalc + 0.5);  
  }
}


void mapPoti(void)  // Bereichsanpassung in den gewünschten Frequenzbereich, siehe oben
{
  // Frequenzbereich beider Signale
  poti[0].valueMapped = map(poti[0].value, 0, 1023, COMPAREvalueMIN, COMPAREvalueMAX);
  // Phasenlage des 2. Signals zum 1. Signal, mittels Endwert 'poti[0].valueMapped' bleibt Phaselage erhalten
  poti[1].valueMapped = map(poti[1].value, 0, 1023, 0, poti[0].valueMapped);
}


void readPoti(const unsigned long intervall)
{
  static unsigned long lastMillis = 0;
  unsigned long ms = millis();

  if (ms - lastMillis >= intervall)
  {
    lastMillis = ms;
    for (byte i = 0; i < ANZAHL_POTI; ++i)
    {
      poti[i].value = analogRead(poti[i].pin);
    }
  }
}

ISR(TIMER1_COMPA_vect)
{
  static unsigned int oldValue = 65535;     // erzwingt erste Änderung
  if (oldValue != poti[0].mittelwert)
  {
    OCR1A = (unsigned int)poti[0].mittelwert;
    oldValue = poti[0].mittelwert;
  }
}


ISR(TIMER1_COMPB_vect)
{
  static unsigned int oldValue = 65535;
  if (oldValue != poti[1].mittelwert)
  {
    OCR1B = (unsigned int)poti[1].mittelwert;
    oldValue = poti[1].mittelwert;
  }
}


void seriellerMonitor(const unsigned long intervall)
{
  static unsigned long lastMillis = 0;
  unsigned long ms = millis();
  if (ms - lastMillis >= intervall) {
    lastMillis = ms;
    Serial.print(poti[0].value); Serial.print('\t'); Serial.print(poti[0].valueMapped); Serial.print('\t');
    Serial.print(poti[0].mittelwertCalc); Serial.print('\t'); Serial.println(poti[0].mittelwert);
    Serial.print(poti[1].value); Serial.print('\t'); Serial.print(poti[1].valueMapped); Serial.print('\t');
    Serial.print(poti[1].mittelwertCalc); Serial.print('\t'); Serial.println(poti[1].mittelwert);
    Serial.println();
  }
}


void preSetTimer1 ()     // Voreinstellungen, läuft noch nicht los
{
  cli();                  // Interrupts ausschalten
  TCCR1B = 0;             // Reset
  TCCR1A = 0;             //
  TIMSK1 = 0;             //
  TCNT1  = 0;             //
  OCR1A  = 199;           // 199 = 5kHz mit Prescaler 8
  OCR1B  = 199;
  TIMSK1 = _BV(OCIE1B) | _BV(OCIE1A);   // ISR aktivieren
  TCCR1A = _BV(COM1B0) | _BV(COM1A0);   // Timer Pin Toggle aktivieren
  TCCR1B = _BV(WGM12);    // CTC Mode
  sei();                  // Interrupts einschalten
}


void runTimer1 ()
{
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
  {
    //TCCR1B |= _BV(CS10);              // Prescaler 1
    TCCR1B |= _BV(CS11);                // Prescaler 8
    //TCCR1B |= _BV(CS11) | _BV(CS10);  // Prescaler 64
  }
}


void stopTimer1 ()
{
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
  {
    TCCR1B &= ~( _BV(CS12) | _BV(CS11) | _BV(CS10) );
  }
}

Hallo,

nach diversen Tests, habe ich die ultimative Lösung für eine Phasenverschiebung für volle 360°. :slight_smile:
Als "Abfallprodukt" des dafür genutzten Effektes ist die relative Phasenlage zum 1. Signal einstellbar.
Hängt vom Endanschlag des Phasenpotis nach Reset ab.
Alles weitere ist, denke ich, alles ausreichend im Code kommentiert.
Wenn das auch bei dir funktioniert wäre das schön.

Ansonsten wie bisher.
Frequenz-Ausgangpins in Zeile 21, 22 ggf. ändern.
Poti-Eingangpins in Zeile 34, 35 ggf. ändern

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.12
  avr-gcc 9.3.0
  Arduino Mega2560
  29.03.2020
  License: GNU GPLv3

  https://forum.arduino.cc/index.php?topic=668749.0

  - Phasenlage zwischen 0° und 360° vom 2. Signal einstellbar
  - erst filtern dann mappen
  - folgenden Effekt gibt es noch. Quasi ein Feature.  :-)
    Wie das 2. Signal Phasenmäßig zum 1. liegt hängt von der Poti-Endstellung nach Reset ab.
    Poti 0° Anschlag >> zwischen 0°-180° schiebt sich negative Halbwelle unter der 1. Positiven nach hinten
    Poti 360° Anschlag >> zwischen 0°-180° schiebt sich positive Halbwelle unter der 1. Positiven nach hinten
*/

#include <util/atomic.h>

const byte pinFreqA {11};   // Pin OCR1A (am Uno/Nano Pin  9)
const byte pinFreqB {12};   // Pin OCR1B (am Uno/Nano Pin 10)

struct Poti
{
  const byte pin;
  unsigned int raw {0};               // ADC Rohwerte
  float filtered {0};                 // kontinuierlich gefilterter Mittelwert
  unsigned int calculated {0};        // gerundete Ganzzahl vom gefilterten Mittelwert
  unsigned int mapped {0};            // für den Wunschfrequenzbereich angepasste Werte
  volatile unsigned int updated {0};  // für atomaren Zugriff/Zuweisung erforderlich
};

Poti frequenz {A1};
Poti phase    {A2};

/*               4        5        6[kHz]
  Prescaler 1: 1999 ... 1599 ... 1332       Auflösung: 667
  Prescaler 8:  249 ...  199 ...  166       Auflösung:  83   */
const byte COMPAREvalueMIN {166};
const byte COMPAREvalueMAX {249};

// bei "erst filtern dann mappen" ist ein höherer Filterfaktor notwendig, weil die Schwankungsbreite größer ist,
// ein Sicherheitszuschlag 'mal 2' ist enthalten, auf Grund der schwankenden Qualität des Potischleiferkontaktes
const byte FILTERFAKTOR {80};     // Filterfaktor zur Mittelwertbildung


void setup(void)
{
  Serial.begin(9600);
  pinMode(pinFreqA, OUTPUT);
  pinMode(pinFreqB, OUTPUT);
  preSetTimer1();
  runTimer1();
}


void loop(void)
{
  readPoti(50);           // liest zyklisch alle Potis ein
  filterPoti();           // glättet eingelesene Potiwerte, dazu gehört obiger FILTERFAKTOR
  mapPoti();              // passt den gelesenen Potiwert an den erforderlichen Bereich an, Phasenlage bleibt erhalten
  //seriellerMonitor(500);  // bei Bedarf oder Debugging
}


// ****** Funktionen ******* //
void readPoti(const unsigned long intervall)
{
  static unsigned long lastMillis {0};
  unsigned long ms { millis() };

  if (ms - lastMillis >= intervall)
  {
    lastMillis = ms;
    frequenz.raw = analogRead(frequenz.pin);
    phase.raw = analogRead(phase.pin);
  }
}


void filterPoti(void)
{
  // kontinuierliche Mittelwertbildung
  filtern(frequenz.filtered, frequenz.raw, FILTERFAKTOR);
  filtern(phase.filtered, phase.raw, FILTERFAKTOR);

  // Ergebnis korrekt runden und als Ganzzahl übergeben
  frequenz.calculated = static_cast<unsigned int>(frequenz.filtered + 0.5);
  phase.calculated = static_cast<unsigned int>(phase.filtered + 0.5);
}


void mapPoti(void)  // Bereichsanpassung in den erforderlichen Wertebereich
{
  // Frequenzbereich
  frequenz.mapped = map(frequenz.calculated, 0, 1023, COMPAREvalueMIN, COMPAREvalueMAX);
  // Phasenlage des 2. Signals zum 1. Signal, mittels frequenz.mapped als Endwert bleibt Phasenlage bei Frequenzänderung erhalten
  // Endwert "frequenz.mapped mal 2" deswegen um 2x 180° Abdeckung zu ermöglichen. Wird anschließend wieder korrigiert.
  phase.mapped = map(phase.calculated, 0, 1023, 0, (frequenz.mapped * 2));

  unsigned int tempPhase {0};

  /* Korrekturerkennung ob Phasendrehung für 180-360° erfolgt oder nicht
   * Der ausgenutzte Effekt zur benötigten Phasendrehung für > 180° ist folgender.
   * Der Pin wird ja nur im Compare Interrupt getoggelt. 
   * Das heißt auch ein Update erfolgt nur im Compare Interrupt wenn der Pin getoggelt wird.
   * Wenn die Phasenlage 180° überschreitet erfolgt noch ein Toggle mit dem letzten Wert fvon <= 180°.
   * Während des Toggle erfolgt das Update mit der sprunghaften Änderung auf einen kleineren Compare Wert.
   * Das hat zur Folge das ein Toggle unmittelbar erneut ausgeführt wird.
   * Dadurch entsteht der Effekt der "unsichtbaren" Phasendrehung womit man im gültigen Wertebereich die Phase weiterschieben kann.
   */
  if (phase.mapped > frequenz.mapped)
  {
    tempPhase = phase.mapped - frequenz.mapped;   // korrigiert Compare Wert
  }
  else
  {
    tempPhase = phase.mapped;
  }

  // zusätzliche Sicherheitsabfrage und Limitierung, sollte eigentlich nie vorkommen,
  // könnte jedoch durch größere Schwankungen im Filter vielleicht notwendig sein
  if (tempPhase > frequenz.mapped) tempPhase = frequenz.mapped;

  // Interrupt sichere und vorallendingen gleichzeitige Aktualisierung
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
  {
    frequenz.updated = frequenz.mapped;
    phase.updated = tempPhase;
  }
}


void seriellerMonitor(const unsigned long intervall)
{
  static unsigned long lastMillis {0};
  unsigned long ms { millis() };
  
  if (ms - lastMillis >= intervall)
  {
    lastMillis = ms;

    Serial.print(frequenz.raw); Serial.print('\t'); Serial.print(frequenz.filtered); Serial.print('\t');
    Serial.print(frequenz.calculated); Serial.print('\t'); Serial.print(frequenz.mapped); Serial.print('\t');
    Serial.println(frequenz.updated);
    Serial.print(phase.raw); Serial.print('\t'); Serial.print(phase.filtered); Serial.print('\t');
    Serial.print(phase.calculated); Serial.print('\t'); Serial.print(phase.mapped); Serial.print('\t');
    Serial.println(phase.updated);
  }
}


// Grüsse an GuntherB
void filtern(float &filtVal, const unsigned int newVal, const byte FF)
{
  filtVal = ((filtVal * FF) + newVal) / (FF + 1);
}


ISR(TIMER1_COMPA_vect)
{
  OCR1A = frequenz.updated;
}


ISR(TIMER1_COMPB_vect)
{
  OCR1B = phase.updated;
}


void preSetTimer1 (void)  // Voreinstellungen, läuft noch nicht los
{
  cli();                  // Interrupts ausschalten
  TCCR1B = 0;             // Resets
  TCCR1A = 0;             //
  TIMSK1 = 0;             //
  TCNT1  = 0;             //
  OCR1A  = COMPAREvalueMIN; 
  OCR1B  = COMPAREvalueMIN;
  TIMSK1 = _BV(OCIE1B) | _BV(OCIE1A);   // ISR aktivieren
  TCCR1A = _BV(COM1B0) | _BV(COM1A0);   // Timer Pin Toggle aktivieren
  TCCR1B = _BV(WGM12);    // CTC Mode
  sei();                  // Interrupts einschalten
}


void runTimer1 (void)
{
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
  {
    // TCCR1B |= _BV(CS10);     // Prescaler 1
    TCCR1B |= _BV(CS11);        // Prescaler 8
  }
}


void stopTimer1 ()
{
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
  {
    TCCR1B &= ~( _BV(CS12) | _BV(CS11) | _BV(CS10) );
  }
}

Hallo Doc_Arduino

Ich bin absolut sprachlos.

Allerdings erhalte ich beim Kompilieren eine Fehlermeldung und kann nicht erkennen, woran das liegt.

Ich habe die aktuelle IDE 1.8.12

Die Fehlermeldung lautet:
Arduino: 1.8.12 (Windows 10), Board: "Arduino Uno"

Frequenzverschiebung:35:18: error: no matching function for call to 'Poti::Poti()'

Poti frequenz {A1};

^

C:\Users\Matze\Desktop\Frequenzverschiebung\Frequenzverschiebung.ino:25:8: note: candidate: constexpr Poti::Poti(const Poti&)

struct Poti

^~~~

C:\Users\Matze\Desktop\Frequenzverschiebung\Frequenzverschiebung.ino:25:8: note: no known conversion for argument 1 from 'const uint8_t {aka const unsigned char}' to 'const Poti&'

C:\Users\Matze\Desktop\Frequenzverschiebung\Frequenzverschiebung.ino:25:8: note: candidate: constexpr Poti::Poti(Poti&&)

C:\Users\Matze\Desktop\Frequenzverschiebung\Frequenzverschiebung.ino:25:8: note: no known conversion for argument 1 from 'const uint8_t {aka const unsigned char}' to 'Poti&&'

Frequenzverschiebung:36:18: error: no matching function for call to 'Poti::Poti()'

Poti phase {A2};

^

C:\Users\Matze\Desktop\Frequenzverschiebung\Frequenzverschiebung.ino:25:8: note: candidate: constexpr Poti::Poti(const Poti&)

struct Poti

^~~~

C:\Users\Matze\Desktop\Frequenzverschiebung\Frequenzverschiebung.ino:25:8: note: no known conversion for argument 1 from 'const uint8_t {aka const unsigned char}' to 'const Poti&'

C:\Users\Matze\Desktop\Frequenzverschiebung\Frequenzverschiebung.ino:25:8: note: candidate: constexpr Poti::Poti(Poti&&)

C:\Users\Matze\Desktop\Frequenzverschiebung\Frequenzverschiebung.ino:25:8: note: no known conversion for argument 1 from 'const uint8_t {aka const unsigned char}' to 'Poti&&'

exit status 1
no matching function for call to 'Poti::Poti()'

Dieser Bericht wäre detaillierter, wenn die Option
"Ausführliche Ausgabe während der Kompilierung"
in Datei -> Voreinstellungen aktiviert wäre.

Hallo,

ging beim Sketch kopieren etwas verloren?
Fehlt eine geschweifte Klammer?

Ansonsten kommt die IDE vermutlich mit dem modernen Syntax der Initialisierung nicht klar, was mich wundert. Der gcc 7.x sollte das können. Ist aber kein Problem, nimmste die alte noch gebräuchliche Syntax.

Bsp. statt

const byte pinFreqA {11};

abändern in

const byte pinFreqA = 11;

Wenn das auch nicht klappt, werde ich bestimmt noch einen Konstruktor schreiben müssen, dann bekommt das der gcc 7.x nicht aufgelöst. Vermute ich jetzt derzeit. Ich kann das heute Abend mit einem älteren gcc mal quer testen.

Hallo :slight_smile:

Ich habe die Syntax geaendert.
Nun erscheint nur noch folgende Fehlermeldung:

Arduino: 1.8.12 (Windows 10), Board: "Arduino Mega or Mega 2560, ATmega2560 (Mega 2560)"

Frequenzverschiebung:35:16: error: conversion from 'const uint8_t {aka const unsigned char}' to non-scalar type 'Poti' requested

Poti frequenz =A1;

^~

Frequenzverschiebung:36:16: error: conversion from 'const uint8_t {aka const unsigned char}' to non-scalar type 'Poti' requested

Poti phase =A2;

^~

exit status 1
conversion from 'const uint8_t {aka const unsigned char}' to non-scalar type 'Poti' requested