if-Schleife schneller machen?

Hallo liebe Gemeinde,

Ich programmiere im Rahmen eines Studienprojektes ein Arduino Mega2560 so, dass er für einen Asynchronmotor 6 Phasen als PWM ausgibt.

Dafür soll eine Sinus-Dreieck-Modulation für die PWM verwendet werden.
Diese Realisiere ich indem ich einen Timer im Phase Correct PWM-Mode betreibe und bis 500 zählen lasse mit einem Presclaer von 8.
Nun wollte ich alle 20 µs mit einem 2ten Timer ein Interrupt erzeugen in der von 6 Sinus-Tabellen, verglichen wird ob der Akutelle Zäler Wert vom 1ten Timer größer oder kleiner als der Sinus Wert ist, woraufhin ich meine Ausgänge auf LOW oder HIGH schalte.

Hier mein Code

//---- init all variables
const float pi = 3.14159;
volatile float sinus_1[100];
volatile float sinus_2[100];
volatile float sinus_3[100];
volatile float sinus_4[100];
volatile float sinus_5[100];
volatile float sinus_6[100];
volatile  int n=0;

//---- define Pins for Output of the PWM
#define sinus_pin_1 6
#define sinus_pin_2 7
#define sinus_pin_3 8
#define sinus_pin_4 10
#define sinus_pin_5 11
#define sinus_pin_6 12

//---- setup-code only runs once to the beginnning of the programm
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(sinus_pin_1,OUTPUT);
  noInterrupts();//Disable Interrupts
  set_Timer3(); // Timer 3 für 20 micro sekunden
  set_Timer1(); // Timer 1 für phase Correct PWM mode
  set_sinus_arrays();// calculat the array for sine-values
  interrupts(); // Enable Interrupts
}

//---- function set_sinus_arrays
//---- calculate and saves the values for the 6 sinus-signals into arraya
void set_sinus_arrays(){
  int i=0;
  for(i;i<100;i++){
    sinus_1[i]=sin(((pi*2)/100)*i);
    sinus_2[i]=sin(((pi*2)/100)*i+((1/3)*pi));
    sinus_3[i]=sin(((pi*2)/100)*i+((2/3)*pi));
    sinus_4[i]=sin(((pi*2)/100)*i+pi);
    sinus_5[i]=sin(((pi*2)/100)*i+((4/3)*pi));
    sinus_6[i]=sin(((pi*2)/100)*i+((5/3)*pi));
    }
  }

 //---- Set the Timer to interrupt every 20 ms  
void set_Timer3(){
  TCCR3A=0; //set Register A to 0
  TCCR3B=0; // same with Register B
  TCNT3=0;  // Counter valau to 0, starts now at 0

  OCR3A =39;// set Compare match register to 40
  TCCR3B|= (1<<WGM32); //enable CTC mode
  TCCR3B|= (1<<CS31); // set prescaler to 8 
  TIMSK3 =0;
  TIMSK3 =(1<< OCIE3A); // enable INterrupt
  
  }
  
//---- set Timer 1 to generate the triangle- signal
void set_Timer1(){
  TCCR1A=0; //set Register A to 0
  TCCR1B=0; // same with Register B
  TCNT1=0;  // Counter valau to 0, starts now at 0

  OCR1A=499; // set Compare Match registr to 499
  TCCR1A |= (1<<WGM11)|(1<<WGM10);
  TCCR1B |= (1<<WGM13); // enable Phase Correct PWM
  TCCR1B |= (1<<CS11); //set Prescaler to 8 
  
  }

//---- init interrupt function that runs every 20 ms
//---- freqeuncy of interrupts will be from Timer3 
//---- set the PWM
ISR(TIMER3_COMPA_vect){
  int compare=0;
  compare=TCNT1;
  n++;
  if (n==100){
    n=0;
  }
//compare sins with triangle ad toogle the digital Pinto generate PWM
//sinus 1 0°
  if(compare<sinus_1[n]){
   PORTH|=B00001000;
  }
  else{
   PORTH = PORTH & B11110111; 
  }
  
//sinus 2 60°
  if(compare<sinus_2[n]){
    PORTH|=B00010000;
  }
  else{
    PORTH = PORTH & B11101111;
  }
//sinus 3 120°
  if(compare< sinus_3[n]){
    PORTH|=B00100000;
  }
  else{
    PORTH = PORTH & B11011111;
  }
//sinus 4 180°
  if(compare< sinus_4[n]){
    PORTB|= 00010000;
  } 
  else{
    PORTB = PORTB & 11101111;
  }
//sinus 5 240°
  if(compare<sinus_5[n]){
    PORTB|= 00100000;
  }
  else{
    PORTB = PORTB & 11011111;
  }
//sinus 6 300°
  if(compare<sinus_6[n]){
    PORTB|= 01000000; 
  }
  else{
    PORTB = PORTB & 10111111;
  }
}


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

Ich habe vorher probiert mit DigitalWrite() meine Ausgänge zu setzen bin allerdings bei einer kleinen selbst erstellten Laufzeitanalyse auf viel zu hohe Laufzeiten gekommen.
Deshalb setzte ich die Ports nun direkt. Allerdings Komme ich immer noch auf knapp 28 µs nur für die if-Schleifen.
Ich habe jedoch keine Methode gefunden, um die If-Schleifen schneller zu gestalten.

Hat jemand eine IDee??

Vielen Dank im Voraus.
Mit Freundlichen Güßen
Jeremy :slight_smile:

Erstens:
Du vergleichst Integer mit Float.
Die Konvertierung ist teuer und der Vergleich auch.
Versuche auf Float zu verzichten.

Zweitens: (ganz wichtig!!!)
If Schleife

Drittens:
PORTB|= 01000000; // oktale Zahl
Die Zahl passt nicht in ein Byte.
Das tut doch mit Sicherheit nicht das, was du erwartest!
Oder?

PORTB = PORTB & 11011111; // dezimale Zahl
Die Zahl passt nicht in ein Byte.
Das tut doch mit Sicherheit nicht das, was du erwartest!
Oder?

hi,

NATÜRLICH gibt es if-schleifen:

byte zaehler;

wieder_da:
  zaehler++;
  delay(10);
if ((digitalRead(5) == HIGH) && (zaehler <= 100)) {goto wieder_da;}

wartet eine sekunde, ob pin 5 LOW wird, falls er HIGH ist.

bevor jemand fragt, ICH bin nicht HIGH…

gruß stefan

Hallo,

ist bestimmt lustig gemeint, aber das ist dennoch keine if Schleife.
Denn du springst aus dem Anweisungsblock der if Abfrage raus und landest "irgendwann" wieder vor der if Abfrage.
Eine Schleife wie for bleibt in sich selbst "gefangen" bis eine Abbruchbedingung erfüllt ist.

@Eisebaer
Sach mal, bist du High?

Das ist eine Goto Schleife, wenn schon, denn schon.

Wenn hier einer ungestraft gotos einsetzt, dann bin , bitteschön, ich das!
Und kein anderer!
:o :o :o :o :o

Tipp:
Auch eine For Schleife ist keine If Schleife, trotz Bedingung.

hi,

doc, natürlich war's lustig gemeint, aber damit hast Du jetzt auch nicht recht.

  1. "rausspringen" kann ich nur aus einer schleife.

  2. der anweisungsblock der if-abfrage wird ausgeführt.

  3. ich "lande" nicht "irgendwann", sondern genau definiert.

  4. und zwar so lange, bis die abbruchbedingung erfüllt ist.

genau genommen bilde ich damit eine do - while - loop ab, und das ist eine schleife.

natürlich absolut sinnfrei, aber eine schleife.

gruß stefan

EDIT: nennen wir's eine if - goto - schleife, für combies seelenfrieden

Warum machst Du einen neuen Thread auf?

Mit if programmierst Du Abfragen, keine Schleifen.

Mit float kommst Du auf keinen grünen Zweig, das rechnet viel zu lang. Du brauchst sie ja auch garnicht, wenn das Dreieck nur bis 499 läuft. Entsprechend müssen die Werte in den Sinus-Tabellen aber auch zwischen 0 und 499 liegen, nicht zwischen -1 und +1. Noch besser wäre ein Bereich, der in ein Byte paßt.

Zum Rest sage ich erst mal nichts, so wie Du den Code gepostet hast dürfte der garnicht compilieren, zumindest nicht das machen, was er soll.

Was mir da zusätzlich auffällt, ist dass du mal locker-flockig 600 floats anlegst, mit der Aktion also 2400 Byte verbrauchst. Das ist aus meiner Sicht völlig unnütz, da die Werte der Sinustabelle ja nur um einen gewissen Anteil auf der gedachten i-Achse verschoben werden. Daher reicht einmaliges Errechnen aus, was den Speicherverbrauch auf 1/6 deiner Version reduziert. Weiter optimieren würde bedeuten, nur die obere Halbwelle zu berücksichtigen, der Rest ist ja nur gespiegelt, also mit einem Faktor -1 zu versehen, schon sind wir bei 1/12 des Speicherplatzes.
Treibt man es auf die Spitze käme man auch mit 1/24 aus, wenn man nur einmal die Hälfte der Oberwelle berechnet, dann wirds mathematisch aber schnell unübersichtlich.

Die optimale Lösung hatte ich im ursprünglichen Thread schon beschrieben: Abgespeichert werden Bytes, die direkt auf einen Port ausgegeben werden und alle 6 PWM-Signale darstellen können. Damit könnte man tatsächlich auf die angepeilte Rate von 20µs Interrupts kommen, solange keine anderen Interrupts dazwischenspucken.

Guten Morgen^^,

Erst einmal Vielen Dank für die vielen Antworten.
Zweitens habt ihr natürlich vollkommen recht.
Es gibt keine if-Schleife^^ Leider habe ich das von der 9.Klasse IT-Unterricht so im Kopf behalten ^^

@ Combie
Vielen Dank für den Hinweis, dass die Ports dort eine Falsche Bitmaske bekommen haben. Ich habe dies nun geändert und jeweils ein großes B davor geschrieben um dies als Binär zu deklarieren.

@sth77
Ja du hast recht es wäre deutlich kleiner. Jedoch wollte ich das erst einmal für meinen Dozenten so drinne lassen, da dieser von Programmierung nicht viel Ahnung hat und ich ihm es möglichst übersichtlich gestalten möchte.
Soweit tut es ja noch keinem Weh dass ich den Speicherplatz so verbrate.

@DrDiettrich
Erstaunlicherweise konnte ich den Code kompilieren ^^
Ich habe nun die Berechnung des Sinus am Anfang direkt auf einen Int gecastet und entsprechend dem Dreieck angepasst.
Somit vergleiche ich nun Int-Werte miteinander.
Eine kleine Laufzeitanalyse mit micros() hat ergeben dass ich nun ca. 12 µs für den gesammten Interrupt benötige.
zur Zeit weiß ich noch nicht ob mein gewünschtes PWM herrauskommt, da ich noch nicht die Möglichkeit hatte den Arduino an einen Oszi anzuschließen.

Desweiteren aheb ich viel über deine Lösung nachgedacht aber ich kann mir einfach dadrunter nichts vorstellen.
Könntest du evtl. bitte ein kleines Beispiel aufstellen? Oder mir entsprechende Denkanstöße geben??

Gruß Jerry

Hier noch den Code zum Text:

#pragma GCC optimize ("-O3")
volatile int n= 0;
volatile const float pi =3.14159;
unsigned long time1 =0;
unsigned long time2=0;
unsigned long time_end=0;
int test_array1[100];
int test_array2[100];
int test_array3[100];
int test_array4[100];
int test_array5[100];
int test_array6[100];  

#define sinus_pin_1 6
#define sinus_pin_2 7
#define sinus_pin_3 8
#define sinus_pin_4 10
#define sinus_pin_5 11
#define sinus_pin_6 12

void setup() {
  // put your setup code here, to run once:
 Serial.begin(9600);
 pinMode(sinus_pin_1, OUTPUT);
  set_Timer3();
  set_Timer1();
}

void set_Timer3(){
  TCCR3A=0; //set Register A to 0
  TCCR3B=0; // same with Register B
  TCNT3=0;  // Counter valau to 0, starts now at 0

  OCR3A =39;// set Compare match register to 40
  TCCR3B|= (1<<WGM32); //enable CTC mode
  TCCR3B|= (1<<CS31); // set prescaler to 8 
  TIMSK3 =0;
  TIMSK3 =(1<< OCIE3A); // enable INterrupt
  
  }

//---- set Timer 1 to generate the triangle- signal
void set_Timer1(){
  TCCR1A=0; //set Register A to 0
  TCCR1B=0; // same with Register B
  TCNT1=0;  // Counter valau to 0, starts now at 0

  OCR1A=499; // set Compare Match registr to 499
  TCCR1A |= (1<<WGM11)|(1<<WGM10);
  TCCR1B |= (1<<WGM13); // enable Phase Correct PWM
  TCCR1B |= (1<<CS11); //set Prescaler to 8 
  
  }

void set_sinus_arrays(){
  int i=0;
   for(i;i<100;i++){
    test_array1[i]=(int)499*sin(((pi*2)/100)*i);
    test_array2[i]=(int)499*sin(((pi*2)/100)*i+((1/3)*pi));
    test_array3[i]=(int)499*sin(((pi*2)/100)*i+((2/3)*pi));
    test_array4[i]=(int)499*sin(((pi*2)/100)*i+pi);
    test_array5[i]=(int)499*sin(((pi*2)/100)*i+((4/3)*pi));
    test_array6[i]=(int)499*sin(((pi*2)/100)*i+((5/3)*pi));
  }
}

volatile int test;
ISR(TIMER3_COMPA_vect){

  intCompareZaehler = TCNT1;
  time1=micros();
  if (n++ <= 100){
    n=0;
  }  
//compare sins with triangle ad toogle the digital Pinto generate PWM
//sinus 1 0°

  if(intCompareZaehler < test_array1[n]){
   PORTH |= B00001000;
  }
  else{
   PORTH &= B11110111; 
  }
//sinus 2 60°
  if(intCompareZaehler < test_array2[n]){
    PORTH|=B00010000;
  }
  else{
    PORTH &= B11101111;
  } 
//sinus 3 120°
  if(intCompareZaehler < test_array3[n]){
    PORTH|=B00100000;
  }
  else{
    PORTH = PORTH & B11011111;
  }
//sinus 4 180°
  if(intCompareZaehler < test_array4[n]){
    PORTB|= B00010000;
  } 
  else{
    PORTB &= B11101111;
  }
//sinus 5 240°
  if(intCompareZaehler < test_array5[n]){
    PORTB|= B00100000;
  }
  else{
    PORTB = PORTB & B11011111;
  }
//sinus 6 300°
  if(intCompareZaehler < test_array6[n]){
    PORTB|= B01000000; 
  }
  else{
    PORTB = PORTB & B10111111;
  }
  
  time2 = micros();
  time_end = time2-time1;
  
}


void loop() {
  // put your main code here, to run repeatedly:
  Serial.println(time_end);
  
}

Das ist nur ein Text-sketch welchen ich erstellt habe zur Laufzeitanalyse

Eisebaer:

... goto ...

Jehova! Er hat Jehova gesagt!

Eisebaer:
bevor jemand fragt, ICH bin nicht HIGH...

Vielleicht ist das der Fehler :smiley:

Gruß

Gregor

jerry134:
Erstaunlicherweise konnte ich den Code kompilieren ^^

... nachem Du die Leerzeichen im vorhergehenden Code rausgeworfen hast :-]

Deine Kreativität in allen Ehren, es sieht aber nicht gut aus, wenn Du die Portbehandlung für jeden Sinus anders hinschreibst.

Zudem halte ich die Arduino-spezifische Binärdarstellung mit B010101 für völlig überflüssig und nicht portabel. In C werden Binärzahlen als 0b010101 dargestellt, Hexadezimale als 0xABC.

jerry134:
Desweiteren aheb ich viel über deine Lösung nachgedacht aber ich kann mir einfach dadrunter nichts vorstellen.
Könntest du evtl. bitte ein kleines Beispiel aufstellen? Oder mir entsprechende Denkanstöße geben??

Wie sehen denn die 6 Signale an den Pins aus?

Dort wird für jede Schwingung das gleiche Muster ausgegeben. Das läßt sich also auch genau so speichern, und per Interrupt ausgeben.

Zudem halte ich die Arduino-spezifische Binärdarstellung mit B010101 für völlig überflüssig und nicht portabel. In C werden Binärzahlen als 0b010101 dargestellt,

Es wäre schön, wenn es so einfach wäre

Auch die mit 0b startende Variante ist nicht auf allen C Compilern nutzbar.
Denn: Binärliterale sind im C Standard nicht definiert.

Die voll kompatible Version sieht so aus:

#define B(x) ( \
  0##x /        01 % 010 << 0 | \
  0##x /       010 % 010 << 1 | \
  0##x /      0100 % 010 << 2 | \
  0##x /     01000 % 010 << 3 | \
  0##x /    010000 % 010 << 4 | \
  0##x /   0100000 % 010 << 5 | \
  0##x /  01000000 % 010 << 6 | \
  0##x / 010000000 % 010 << 7 )

Korrektur/Präzisierung:
Der gcc, zur Arduino 0007 Zeit, konnte 0b noch nicht, darum wurden die B Konstanten eingeführt.
0b steht ab C++14 im Standard.
Arduino ist gerade erst bei C++11 angekommen.

Es ist also eine Gnade des Kompilers, dass wir die mit 0b startenden Literale nutzen dürfen.

In C werden Binärzahlen als 0b010101 dargestellt, Hexadezimale als 0xABC.

C/C++ kennt eigentlich keine Binär-Literale. Leider. 0b ist eine gcc Erweiterung:

https://gcc.gnu.org/onlinedocs/gcc/Binary-constants.html#Binary-constants

Da gibt es ein paar nette Sachen die nur dank gcc gehen.

Binärliterale sind Teil des C++14 Standards, gehen mittlerweile auch z.B. in Visual Studio.

Guten Morgen ^^,

Vielen Dank für die ganzen Antworten und weiterführenden Diskussionen.

Ich hatte mittlerweile die Möglichkeit an einem Osziloskop zu messen und bekomme recht brauchbare PWM denke ich.
Dabei ist es mir sogar durch Variation der Frequenz vom Timer 3 die Frequenz der PWM zu verändern.

Nun ist es meine Aufgabe eine GUI für das Programm bereitzustellen und ich hatte dabei an Instrumentino gedacht.
Ich soll die Frequenz der PWM im laufende Programm verändern können.

Hat jemand bereits mit diesem Tool gearbetet oder hat jemand bessere Vorschläge?

VielenDank