Simulation Drehfeld

Hallo Leute ich versuche mit einem Arduino Uno R3 und 3 H-Brücken (L298N) und drei Spulen ein Wechselfeld zu simulieren die Spulen sind um einen Kompass herum 120° Phasenverschoben angeordnet.
Jedoch habe ich das Problem das der Kompass sich nicht richtig dreht er dreht wie er will und verharrt dann an einem Punkt.

tippe oder füge den Code #include <TimerOne.h>
#include <math.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>                //Bibliotheken

LiquidCrystal_I2C lcd(0x27,20,4);

volatile int frequenz = 1.0;
float Phasenverschiebung = (M_PI * 2)/3 ;                    //360° ^= 2Pi
// Externe Interupt Variablen

  const int Taster_External_Interrupt_Pin = 2;
  const int Taster_Frequenz_Hoch   = 7;
  const int Taster_Frequenz_Runter = 8;
  const int Taster_Drehrichtungsumkehr = 12;
  volatile bool Drehrichtung = 1;     // True Rechtslauf | FALSE = Linklslauf
  volatile float Interruptauslosezeit_in_ms = 0;               
  volatile float Periodendauer_T_sek     = 0;
  volatile float Winkelgeschwindigkeit_in_rad_pro_sek = 0;


//Spule1 PinBelegung
  const int Pin_Spule1_Enable     = 3;
  const int Pin_Spule1_Rechtslauf = A5;
  const int Pin_Spule1_Linkslauf  = A4;

//Spule2 Pinbelegung
  const int Pin_Spule2_Enable     = 5;
  const int Pin_Spule2_Rechtslauf = A3;
  const int Pin_Spule2_Linkslauf  = A2;

//Spule3 PinBelegung
  const int Pin_Spule3_Enable     = 6;
  const int Pin_Spule3_Rechtslauf = A1;
  const int Pin_Spule3_Linkslauf  = A0;

  const int Anzahl_Stutzstellen_volle_Periode = 18;
  int Tabelle_Sinuswerte_L1[Anzahl_Stutzstellen_volle_Periode] = {};
  int Tabelle_Sinuswerte_L2[Anzahl_Stutzstellen_volle_Periode] = {};
  int Tabelle_Sinuswerte_L3[Anzahl_Stutzstellen_volle_Periode] = {};


void setup() 
{
  delay(500);
  Serial.begin(9600);
// PinModes für Taster Externer Interrupt

  pinMode(Taster_External_Interrupt_Pin, INPUT_PULLUP);
  pinMode(Taster_Frequenz_Hoch,       INPUT_PULLUP);
  pinMode(Taster_Frequenz_Runter,     INPUT_PULLUP);
  pinMode(Taster_Drehrichtungsumkehr, INPUT_PULLUP);

// pinModes der Spulen 1-3 

//Spule1
  pinMode(Pin_Spule1_Enable , OUTPUT);
    pinMode(Pin_Spule1_Rechtslauf,OUTPUT);
      pinMode(Pin_Spule1_Linkslauf,OUTPUT);

//Spule 2
  pinMode(Pin_Spule2_Enable , OUTPUT);
    pinMode(Pin_Spule2_Rechtslauf,OUTPUT);
      pinMode(Pin_Spule2_Linkslauf,OUTPUT);

//Spule3
  pinMode(Pin_Spule2_Enable , OUTPUT);
    pinMode(Pin_Spule2_Rechtslauf,OUTPUT);
      pinMode(Pin_Spule2_Linkslauf,OUTPUT);

  

Periodendauer_T_sek = 1.0 / frequenz;
Winkelgeschwindigkeit_in_rad_pro_sek = 2 * M_PI * frequenz;
Interruptauslosezeit_in_ms = (Periodendauer_T_sek / Anzahl_Stutzstellen_volle_Periode) * 1000.0;

for( int Aktuelle_TabellenPosition = 0 ; Aktuelle_TabellenPosition < Anzahl_Stutzstellen_volle_Periode; Aktuelle_TabellenPosition ++ )
    {
      float OmegaZeit_in_s = (Winkelgeschwindigkeit_in_rad_pro_sek * Interruptauslosezeit_in_ms * Aktuelle_TabellenPosition ) / 1000.0;
      Tabelle_Sinuswerte_L1[Aktuelle_TabellenPosition] = (int)(255 * (sin( OmegaZeit_in_s + Phasenverschiebung * 0)));
      Tabelle_Sinuswerte_L2[Aktuelle_TabellenPosition] = (int)(255 * (sin( OmegaZeit_in_s + Phasenverschiebung * 1)));
      Tabelle_Sinuswerte_L3[Aktuelle_TabellenPosition] = (int)(255 * (sin( OmegaZeit_in_s + Phasenverschiebung * 2)));
    }
// Timer Interrupt Initialisieren 
      Timer1.initialize ((Interruptauslosezeit_in_ms * 1000));     // Zeit in mykro sekunden
      Timer1.attachInterrupt (ISR_ZeitInterrupt);
//Externen Interrupt Initialisieren
      attachInterrupt(digitalPinToInterrupt(2), ISR_External_Buttons, RISING);     //ISR_TASTER 


  lcd.init();                      // initialize the lcd 
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0,0);



}

void ISR_External_Buttons()
{  
    Timer1.stop(); 
    
  //  Serial.println("Interrupt");
    
    if(digitalRead(Taster_Frequenz_Hoch) == 1) 
    {
      if(frequenz < 50.0)
      {
        frequenz = frequenz + 1.0;
      }
    }
    if(digitalRead(Taster_Frequenz_Runter) == 1)
    {
      if(frequenz > 1 ){
        frequenz --;
      }

    } 
    if(digitalRead(Taster_Drehrichtungsumkehr) == 1){
      Drehrichtung = !Drehrichtung;
    }

    Periodendauer_T_sek = 1.0 / frequenz;
    Winkelgeschwindigkeit_in_rad_pro_sek = 2 * M_PI * frequenz;
    Interruptauslosezeit_in_ms = (Periodendauer_T_sek / Anzahl_Stutzstellen_volle_Periode) * 1000.0;

  for( int Aktuelle_TabellenPosition = 0 ; Aktuelle_TabellenPosition < Anzahl_Stutzstellen_volle_Periode; Aktuelle_TabellenPosition ++ )
    {
      float OmegaZeit_in_s = (Winkelgeschwindigkeit_in_rad_pro_sek * Interruptauslosezeit_in_ms * Aktuelle_TabellenPosition ) / 1000.0;
      Tabelle_Sinuswerte_L1[Aktuelle_TabellenPosition] = (int)(255 * (sin( OmegaZeit_in_s + Phasenverschiebung * 0)));
      Tabelle_Sinuswerte_L2[Aktuelle_TabellenPosition] = (int)(255 * (sin( OmegaZeit_in_s + Phasenverschiebung * 1)));
      Tabelle_Sinuswerte_L3[Aktuelle_TabellenPosition] = (int)(255 * (sin( OmegaZeit_in_s + Phasenverschiebung * 2)));
    }   


    Timer1.setPeriod(Interruptauslosezeit_in_ms * 1000.0);
}






volatile int akt_PWM_Tastgrad_L1 = 0;
volatile int akt_PWM_Tastgrad_L2 = 0;
volatile int akt_PWM_Tastgrad_L3 = 0;

volatile int ListenPositionsCounter = 0;  
void ISR_ZeitInterrupt () 
{
if( Drehrichtung == 0 )
{  
  akt_PWM_Tastgrad_L1 = Tabelle_Sinuswerte_L1[ListenPositionsCounter];
  akt_PWM_Tastgrad_L2 = Tabelle_Sinuswerte_L2[ListenPositionsCounter];
  akt_PWM_Tastgrad_L3 = Tabelle_Sinuswerte_L3[ListenPositionsCounter];
}

if( Drehrichtung == 1 )
{  
  akt_PWM_Tastgrad_L1 = Tabelle_Sinuswerte_L2[ListenPositionsCounter];
  akt_PWM_Tastgrad_L2 = Tabelle_Sinuswerte_L1[ListenPositionsCounter];
  akt_PWM_Tastgrad_L3 = Tabelle_Sinuswerte_L3[ListenPositionsCounter];
}

// Spule 1
  if(akt_PWM_Tastgrad_L1 > 0)   // Rechtslauf Spule 1
  {
    digitalWrite(Pin_Spule1_Linkslauf,   LOW);
    digitalWrite(Pin_Spule1_Rechtslauf, HIGH);
  }                                                 
  if(akt_PWM_Tastgrad_L1 == 0)
  {
     digitalWrite(Pin_Spule1_Linkslauf,   LOW);
     digitalWrite(Pin_Spule1_Rechtslauf, LOW);
  }

  if(akt_PWM_Tastgrad_L1 < 0)   // Rechtslauf Spule 1
  {
    digitalWrite(Pin_Spule1_Linkslauf,   HIGH);
    digitalWrite(Pin_Spule1_Rechtslauf, LOW);
                                                   //      Serial.print("Linkslauf");
  }

// Spule 2
  if(akt_PWM_Tastgrad_L2 > 0)   // Spule 2 Rechtslauf
  {
    digitalWrite(Pin_Spule2_Linkslauf,   LOW);
    digitalWrite(Pin_Spule2_Rechtslauf, HIGH);
  }


  if(akt_PWM_Tastgrad_L2 == 0){
     digitalWrite(Pin_Spule2_Linkslauf,   LOW);
     digitalWrite(Pin_Spule2_Rechtslauf, LOW);
  }


  if(akt_PWM_Tastgrad_L2 < 0)   // Spule 2 Linkslauf
  {
    digitalWrite(Pin_Spule2_Linkslauf,   HIGH);
    digitalWrite(Pin_Spule2_Rechtslauf, LOW);
  }


// Spule 3


  if(akt_PWM_Tastgrad_L3 > 0)   //Spule 3 Rechtslauf
  {
    digitalWrite(Pin_Spule3_Linkslauf,   LOW);
    digitalWrite(Pin_Spule3_Rechtslauf, HIGH);
  }                                                  

  if(akt_PWM_Tastgrad_L3 == 0)  //Spule 3 Null Durchlauf
  {
     digitalWrite(Pin_Spule3_Linkslauf,   LOW);
     digitalWrite(Pin_Spule3_Rechtslauf,  LOW);
                                                      
  }
  if(akt_PWM_Tastgrad_L3 < 0)   //Spule 3 Linkslauf
  {
    digitalWrite(Pin_Spule3_Linkslauf,   HIGH);
    digitalWrite(Pin_Spule3_Rechtslauf, LOW);
                                                      
  }  



analogWrite(Pin_Spule1_Enable, abs(akt_PWM_Tastgrad_L1));
analogWrite(Pin_Spule2_Enable, abs(akt_PWM_Tastgrad_L2));
analogWrite(Pin_Spule3_Enable, abs(akt_PWM_Tastgrad_L3));

Serial.println(akt_PWM_Tastgrad_L1);

ListenPositionsCounter ++;

if(ListenPositionsCounter == Anzahl_Stutzstellen_volle_Periode){
  ListenPositionsCounter = 0;
}

//Serial.println(ListenPositionsCounter);
//Serial.println(akt_PWM_Tastgrad_L1);
//Serial.println(akt_PWM_Tastgrad_L2);

}

void loop() {
  // put your main code here, to run repeatedly:

  
}hier ein

hätte jemand eine Idee wo der Fehler liegen könnte bzw. wie ich am besten mit einer Fehlersuche beginne.

Vielen Dank

Der Code ist so unübersichtlich, damit will man sich nicht gerne befassen. Mit Arrays für die Pins etc. wäre alles viel kürzer und übersichtlicher.
[an dieser Stelle wurde ich unterbrochen. Muß ich übersetzen?]

No Serial output in interrupts.
Buttons should be polled in loop(), not per Interrupt.

As a first test I'd use a button to step manually through the sine table.

Da bekommt Spetticode eine neue Bedeutung. Agile programming?

Dein Hauptproblem ist, alles über Interrupts machen zu wollen. Dein loop() ist leer, aber da spielt die Musik!

Hallo,
ich vermute ja Deine ISR sind zu lang und die kommen sich dann letztlich mit den internen z.B die für die pwm Ausgabe ins Gehege.
lass das mit den Interrupt Verarbeitung komplett weg , das schafft der Arduino auch spielend mit einer Abfrage mittels millis() z.B alle 20ms. Dann kannst Du jedes mal die drei aktuellen Werte berechnen und mit Serial.print sogar auf dem Plotter anzeigen. Wenn Du dann drei schöne phasenverschobene Sinuswellen hast gibst Du die an die Spulen mittels analogWrite() aus.

YES!
Zum Glück grad satt. :wink:

Schade...
Es gibt AVR, und weitere, mit Timern, welche 3 (PWM) Kanäle haben.

3 Tabellen?
Reicht da nicht eine?

Eine mit Offset und Modulo ...

Wie könnten die in diesem Fall helfen?

Hallo,
ich hab mal was rumgespielt. es werden drei Sinuswellen erzeugt und analog ausgegeben.
Ich hab das mit 3 LED simuliert.
es fehlen noch deine Taster zum ändern der Frequenz, aber das kannst Du auf die gleiche Art selber machen.
Taster einlesen
wenn Taster plus gedrückt dann
alle z.B alle 100 ms Frequenz erhöhen

analog bei minus

hier mein Vorschlag das kann ma sicher noch optimieren es geht mit darum Dir zu zeigen wie man das ohne Interrupt macht.

/* erzeugt 3 Sinuswellen Ausgabe auf Plotter ist möglich
 *  Hardware Arduino UNO
 * 
 */
int L1, L2, L3;
float f = 0.5;  // Frequenz

const byte pinL1 = 3;
const byte pinL2 = 5;
const byte pinL3 = 6;
;

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

  pinMode(pinL1, OUTPUT);
  pinMode(pinL2, OUTPUT);
  pinMode(pinL3, OUTPUT);
}

void loop() {
 
  berechne();
  analogWrite(pinL1, abs(L1));
  analogWrite(pinL2, abs(L2));
  analogWrite(pinL3, abs(L3));

}

void berechne() {
  static uint32_t altzeit = 0;
  float winkel = 0;
  int periode = 1000 / f;
  uint32_t static zeit = 0;     // zeit innerhalb einer Periode
  uint32_t tdiv = 10;         // Berechnungszyklus
  const float verschiebung = 2 * PI / 3;

  if ( millis() - altzeit > tdiv) { // einmal bearbeiten 
    zeit = zeit + tdiv;
    altzeit = millis();
    winkel = 2 * PI * zeit / periode;

    L1 = 255 * sin(winkel);
    L2 = 255 * sin(winkel + verschiebung);
    L3 = 255 * sin(winkel + 2 * verschiebung);
    if (winkel > 2 * PI) zeit = 0;
    // Ausgabe auf Plotter möglich
    Serial.print(L1); Serial.print("\t");
    Serial.print(L2); Serial.print("\t");
    Serial.println(L3);
  }
}

1 Like

Auch würde ich einen Stromtreiber nehmen. Da hast Du einfach eine annähernd Sinusansteuerung.
Grüße Uwe

3 Phasen brauchen 3 unterschiedliche PWM Werte.

Die Zauberzahl ist hier die 3!

Aber 3 Tabellen braucht es nicht!
Eigentlich reicht ein 1/4 einer Vollwelle in einer Tabelle.

Und was bewirkt Dein Zauber? Mir ist kein Timer-Modus bekannt, der 3 um 120° phasenverschobene Signale erzeugen kann. Da müßten schon noch 3 Compare Interrupts dazukommen. Dafür reicht aber auch schon ein einzelner Timer Kanal.

3 unterschiedliche PWM Values machen die 3 Phasen.
Die Values kommen aus der Tabelle.

Und das bitte wie?

Für den Anfang: welcher Timer mode soll verwendet werden?

Nein!

Einer, vielleicht....
Von einem anderen Timer.

Du gibst also zu, daß 3 Kanäle an einem Timer nicht helfen, ein 3-Phasen Drehfeld zu erzeugen.

Schwachfug!
Doch tun sie.

Da lohnt sich keine Diskussion.

Hallo,

ganz so einfach wie gedacht ist es wirklich nicht. Das was bspw. Rentner zeigt, sieht schön aus, hat aber nichts mit 50Hz zu tun. Das Raster der Zeitskala beträgt 10ms. Genau genommen ein 11ms Raster wegen > statt >= :wink: Die Zeitskala ist ein fortlaufender Zähler der Ausgaben nacheinander. Sieht schön aus, man kann das Prinzip erkennen. Zu sehen ist die serielle Ausgabe. Die Timer können keine negativen Werte ausgeben.

Im vereinfachten Verfahren, denke ich, ist es mit einem Timer der 3 Kanäle hat schon möglich. Der Timer muss nur um Faktor x schneller takten wie die Zielfrequenz. Will man bspw. im 1ms Raster die Sinusform nachbilden, muss er mit 1kHz takten statt nur 50Hz. Weil der Timer seine Compare Updates bestmöglich mit Buffer machen sollte, welches bei Überlauf der Periode stattfindet, ist ein passender Modus erforderlich. Fast PWM auf keinen Fall, erzeugt mit Duty 0 bei alten ATmegas Nadelimpulse.

Jetzt kann man im 1ms Update Raster bei 20ms Periode nicht viel machen. Wäre viel zu grob. Wir unterteilen die 20ms Periode einmal in 360 Teile. Dann müßte der Timer mit 18kHz laufen. 20ms/360=0,055ms. Jetzt könnte man für jedes Grad der Phase einen Amplitudenwert in einer Tabelle hinterlegen. Den 3 Timerkanälen weist man immer einen versetzen Tabellenindex von 120 zu.
Kanal 1 beginnt mit Index 0, Kanal 2 mit Index 120, Kanal 3 mit Index 240. Die Timer Ausgänge geben bis dahin immer noch PWM aus. Da müßten noch RC Filter ran. Je höher die Ansprüche sind, umso vielfach höher muss der Takt sein. Da stößt man dann schnell an die Grenzen. Unendlich kurz kann man das Update Raster nicht machen. Zwischen den Update muss der nächste Wert "berechnet" werden. Da sind dann auch plötzlich 16MHz für die MCU nicht viel. :thinking: Praktisch kann man den 18kHz Timer nicht sauber bedienen. Man hat grob 900 Takte Zeit zwischen Updates. Die sind schnell "verballert". :joy:

Mit DDS gibt es hier ein Bsp. variabler Sinus Leider hatte sich der TO nie wieder gemeldet.

Könnte man sicherlich auf 3 Phasen erweitern. Hängt nur alles von der Rechenzeit ab die zum nächsten Update verfügbar ist.

Wie das andere Leute machen weiß ich nicht, jedenfalls scheint noch genügend Rechenzeit übrig zu sein, wenn die anderen Leute BLDC Motoren ansteuern. Da passiert noch viel viel mehr Zwischendurch. Irgendwo gibt es noch paar Geheimnisse. :wink: Von Atmel/Microchip muss es auch Appnotes zum Thema 3 Phasen geben.

Mit DDS kann man jedenfalls noch viel machen, weil durch die schnelle ständige Wiederholung man in Summe mit einem groben Raster trotzdem über alles gesehen alle Zeitpunkte erwischt. DDS funktioniert etwas anders als sturr im obigen 0,055ms Raster immer Werte rauszuhauen. Man springt in einem Indexraster durch die Tabelle. Dadurch hat man mit jeden Tabellendurchlauf andere Indexe und damit andere Werte. So ganz grob. Am Ende muss wieder ein RC Filter ran.

Nachtrag:
Ich wusste doch das es darüber Artikel gibt.
3 Phasen PWM - Verständnisfrage
µC articles/DDS

Dann sollte der Duty Faktor immer größer als 0 sein, das läßt sich ja machen. Ebenso muß die Länge der Sinustabelle nicht 360 sein, alle Vielfache von 3 sind möglich.

Dabei werden AFAIR nur 3 Anschlüsse (Stern oder Dreieck) mit je 3 Zuständen ausgegeben, nämlich +, -
und OFF. Den Rest macht die Trägheit des Rotors. Es
bleibt auszuprobieren, wie träge der Kompass ist und wieviele Mikroschritte demnach notwendig sind.