Arduino Due - 4 PWM-Signale für Quadrocopter

Hallo zusammen,

Ich bin auf dem Gebiet der Timer ganz neu und bin gerade dabei, mit einem Arduino due und einem raspi eine Drohne inklusive Flugregelung zu basteln.
Hier stecke ich bereits bei dem Ansteuern der Motoren fest.

Und zwar will ich die 4 Motoren zuerst per Fernbedienung ansteuern.
Das Signal der Fernbedienung kann ich bereits einlesen (also wie lange das PWM signal auf High steht).
Die Periode für ein PWM signal für die Motoren sind 2500 µS (400Hz), das Signal befindet sich dann je nach Stellung des Hebels ca. zwischen 1500 und 2400µS auf High.

Für Jeden der 4 Motoren gibt es ein anderes PWM signal (sie stehen also alle unterschiedlich lange auf HIGH). Diese unterschiedlichen PWM signale will ich nun per Timer (mit der DueTimer bibliothek) an die einzelnen Motoren übergeben.

Hierzu ist die Frage, wie ich am besten die Timer inklusive interrupts verwende, um die PWMs zu erzeugen.

Hier mal ein Code-Ausschnitt (mit 8 Timer), damit ihr versteht wie ich das angehen wollte.

#include <DueTimer.h>
int mot4 = 9;
int mot3 = 8;
int mot2 = 7;
int mot1 = 6;
int power1;
int power2;
int power3;
int power4;
int CH3 = 12;     //CH3_THROTTLE_PIN12
unsigned long throttle = 1550;
volatile int x;

void setup(){

    Serial.begin(9600);

 
// zum einlesen der Fernbedienung
   
//  pinMode(CH3, INPUT);
//  pinMode(mot4, OUTPUT);
//  pinMode(mot3, OUTPUT);
//  pinMode(mot2, OUTPUT);
//  pinMode(mot1, OUTPUT);

 
  Timer0.start(2500); //400Hz
  Timer0.attachInterrupt(ISR_mot1);
  Timer5.start(2500);
  Timer5.attachInterrupt(ISR_mot2);
  Timer6.start(2500);
  Timer6.attachInterrupt(ISR_mot3);
  Timer7.start(2500);
  Timer7.attachInterrupt(ISR_mot4);

}


// MOTOR 1

void ISR_mot1(){
  

Timer1.start(2000); 
Timer1.attachInterrupt(ISR_low1); 
digitalWrite(mot1, HIGH);
}

void ISR_low1(){
Timer1.stop();
digitalWrite(mot1, LOW);  

}

// MOTOR 2

void ISR_mot2(){

  Timer2.start(2000);
  Timer2.attachInterrupt(ISR_low2);
  digitalWrite(mot2, HIGH);
}

void ISR_low2(){
  Timer2.stop();
  digitalWrite(mot2, LOW);
}


// MOTOR 3

void ISR_mot3(){

Timer3.start(2000); //400Hz
Timer3.attachInterrupt(ISR_low3); 
digitalWrite(mot3, HIGH); 
}

void ISR_low3(){
Timer3.stop();
digitalWrite(mot3, LOW);  
}

// MOTOR 4

void ISR_mot4(){

  Timer4.start(2000);
  Timer4.attachInterrupt(ISR_low4);
  digitalWrite(mot4, HIGH);
}

void ISR_low4(){
  Timer4.stop();
  digitalWrite(mot4, LOW);
}



void loop(){  


}

Dieses Code Beispiel funktioniert nur ohne einlesen der Fernbedienung.
Sobald die Werte für Timer 1-4 nichtmehr Konstant (2000µs auf HIGH) sind, sondern "throttle1 - throttle4" eingelesen wird, funktioniert der Code nicht mehr.
Mit weniger Timern habe ich es nicht hinbekommen und mit variablen PWMs erst recht nicht.
Hättet ihr eine Idee, wie das mit meinem Ansatz funktionieren könnte?
Gerne lass ich mich auch auf komplett neue Ansätze ein.

Danke schonmal im Voraus für die Mühe :slight_smile:

Setze Deinen Code bitte in Codetags (</>-Button oben links im Forumseditor oder [code] davor und [/code] dahinter ohne *).
Das kannst Du auch noch nachträglich ändern.

Gruß Tommy

Hi

PWM = Timer, Du brauchst vom Timer Nichts an PWM übergeben, PWM ist eine 'Betriebsart' des Timer.
Wenn Du mehr als zwei gegensätzliche Signale haben willst, wirst Du aber wohl Hand anlegen müssen.
Da müsste man 'schauen', wie lange die Prüfung des Zählerstand und das Setzen/Rücksetzen der Ausgänge benötigt, darauf basierend den Timer tackten lassen (also langsamer, damit der Code auch hinterher kommt) - die Differenz zwischen Timer-Last und erneutem Timer-ISR-Aufruf hast Du für Dein Programm über - also den Timer wirklich nicht mit Maximum rattern lassen.

Sollte meine Antwort komplett am Geschehen vorbei gehen, hab'sch Das Da nicht gefressen gehabt.

MfG

Wenn man PWM richtig macht (also die Timer per Hand programmiert) braucht man gar keine ISR. Das ist ja gerade dass schöne an Timern. Sie können Ausgänge komplett in Hardware steuern

Ich würde Dir mal raten Dich ein weni in die Theorie und Funktion von Quadricoptern einzulesen. Du stellst Dir das alles viel zu einfach vor.

Serenifly:
Wenn man PWM richtig macht (also die Timer per Hand programmiert) braucht man gar keine ISR. Das ist ja gerade dass schöne an Timern. Sie können Ausgänge komplett in Hardware steuern

Mit "Timer per Hand programmieren" meinst du ohne die vorhandene "DueTimer-bibliothek"?
Ich habe schon versucht mich mit dem 1400-seitigen Datenblatt des Atmel SAM3X8E auseinander zu setzen und zu lernen wie man die Register richtig verwendet. Aber auch nach langer Recherche im Netz bin ich auf keine vernünftige Lösung gekommen, weshalb ich mir die schon vorhandene Bibliothek zu nutzen machen wollte.
Im Netz gibt es sehr viele Beispiele für einen Arduino Mega - nicht aber für einen Due.

uwefed:
Ich würde Dir mal raten Dich ein weni in die Theorie und Funktion von Quadricoptern einzulesen. Du stellst Dir das alles viel zu einfach vor.

Und was genau stelle ich mir daran zu einfach vor?
mir geht es ja primär bei meinem Problem darum, wie ich die Motoren mit Timern ansteuern kann. Ich will erstmals noch gar nichts regeln.

Vielleicht kannst du mir ja eine Antwort geben, welche mir bei meinem Problem hilft.
Dann weiß ich vielleicht auch was du damit meinst 'ich stelle mir das alles zu einfach vor'.

MfG

Question_lord:
Im Netz gibt es sehr viele Beispiele für einen Arduino Mega - nicht aber für einen Due.

Ja, das ist bei den ARM Prozessoren problematisch. Die sind viel komlplizierter and werden im Arduino Bereich nicht so oft verwendet. Jedenfalls der Due. Heutzutage werden 32-Bit Prozessoren mit anderen Boards beliebter

Question_lord:
Und was genau stelle ich mir daran zu einfach vor?
mir geht es ja primär bei meinem Problem darum, wie ich die Motoren mit Timern ansteuern kann. Ich will erstmals noch gar nichts regeln.

Vielleicht kannst du mir ja eine Antwort geben, welche mir bei meinem Problem hilft.
Dann weiß ich vielleicht auch was du damit meinst 'ich stelle mir das alles zu einfach vor'.

MfG

Warum willst Du eigentlich das Rad neu erfinden? Kannst Du keine bereits vorhandene Steuerung verwenden? es gibt genug open source Projekte.

Die Motoren eines Quadricopter werden nicht durch die Signale der Fernsteuerung angesteuert sondern die Fernsteuerung gibt nur die "Richtung" an und die Steuerung errechnet aus diesen und der Lagesensoren die Ansteuerung der Motoren um den Quadrikopter zu stabilisieren bzw damit dieser in die gewünschte Richtung fliegt.

Grüße Uwe

Hallo,

das Gesamtkunstwerk Quadrokopter ist komplex und zeitaufwändig. Die Motoransteuerung mittels PWM ist die Kleinste Sorge von allen. Du musst diese Werte für die PWM aber erstmal geregelt bekommen. Dir fehlen noch Höhenmessung und Orientierung "im Luftraum" damit du die Fluglage überhaupt beurteilen und damit letztlich die Motoren steuern kannst. Für all diese Sensoren gibt fertige Boards wo alles drauf ist. Auch in Bauformen die günstig für kleine Copter sind.

uwefed:
Warum willst Du eigentlich das Rad neu erfinden? Kannst Du keine bereits vorhandene Steuerung verwenden? es gibt genug open source Projekte.

Die Motoren eines Quadricopter werden nicht durch die Signale der Fernsteuerung angesteuert sondern die Fernsteuerung gibt nur die "Richtung" an und die Steuerung errechnet aus diesen und der Lagesensoren die Ansteuerung der Motoren um den Quadrikopter zu stabilisieren bzw damit dieser in die gewünschte Richtung fliegt.

Grüße Uwe

Ja das ist richtig, die Stabilisierung ist auch noch nicht das Problem. Habe genug Sensoren besorgt, sei es ein barometer, ein gyroskop und ein GPS sensor wleche ich alle im laufe des Projekts einbinden werde.
An der Fernbedienung gibt es auch einen hebel, der für die Drehzahl der Motoren verantwortlich ist, also nicht nur die Richtung vorgibt.
Und mit diesem Hebel möchte ich nun die Motoren ansteuern, je stärker geneigt der Hebel ist, desto stärker drehen die Motoren.
Automatischen regeln ist noch nicht die rede, ich möchte lediglich mit der Fernbedienung die Motoren steuern können.
Und das hatte ich vor mit Timern zu machen, habe auf dem Gebiet allerdings noch keine erfahrung.

Hallo,

wenn du unbedingt mit Timern anfangen möchtest, dann musst du jetzt von 0 auf 100 den Umgang mit diesen lernen. Auf die Gefahr hin das dich das alles erstmal erschlägt. Weil das ist abseits aller Arduino typischen Komfortfunktionen.

Kleiner Basiskurs. Allerdings ist das alles auf die 8Bit ATmegas zugeschnitten.
https://forum.arduino.cc/index.php?topic=519208.msg3538992#msg3538992
Due habe ich nicht und werde ich nie haben.
Du musst den Ablauf verstehen und dann die entsprechenden Register im Manuell des µC vom Due raussuchen. Bzw. Timer Konfigurationsbeipiele dessen µC anschauen. Das wird nicht 1:1 abgehen. Wenn du im Netz suchst, dann suche nicht nach Arduino Due sondern nach dem µC "Timer SAM..." irgendwas.

Und wenn du das alles am laufen hast, bekommste diese Vorlage. Damit kann man unter Einhaltung der 20ms Periodendauer bis zu 10 Servos mit einem Timer ansteuern. Alle Servos bekommen nacheinander ihren Puls und danach bekommen alle die restliche Pulspause. Wie bei einer RC Anlage.

Der Variablen servo[n].pulscount_New übergibts du den neuen Sollwert. Der Wert hier muss in µs * 2 - 1 vorgegeben werden. µs wegen der Auflösung und mal 2 mit -1 ist bedingt durch die Timerkonfiguration. Also Position "1ms" muss dann 1999µs sein und Position "1,5ms" entsprechend 2999µs. Wird sich sicherlich auf dem Due mit seiner Timerkonfig alles anders.

auf 4 Servos vorbereitet, auch wenn dir das 1:1 erstmal nicht hilft.

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.5
  Arduino Mega2560

  Servosteuerung nach Vorbild einer RC Anlage (Danke an Falk Brunner für die Unterstützung)
  11.04.2018
*/

#include <util/atomic.h>

// Servopins
const byte Servo_0 = 28;
const byte Servo_1 = 29;
const byte Servo_2 = 30;
const byte Servo_3 = 31;

const byte SUM_SERVOS = 4;              // unbedingt anpassen !

struct ServoDaten {                     // Datentyp ... Eigenschaften eines Servos
    byte stepWidth;                     // Schrittweite > Stellgeschwindigkeit 
    unsigned int pulscount;             // aktuelle Position (2 counts = 1µs)
    unsigned int pulscount_New;         // neue Position     
};

ServoDaten servo[SUM_SERVOS];           // alle Servos zusammengefasst

const unsigned int PULSCOUNT_MIN = 1999;        // Standard 1ms
const unsigned int PULSCOUNT_MAX = 3999;        // Standard 2ms
const unsigned int PULSPAUSENCOUNT = 39999;     // Standard 20ms
unsigned int servo_pausecount = PULSPAUSENCOUNT;     
volatile bool flag_servos_updated = false;

void setup()  { 
  pinMode(Servo_0, OUTPUT);
  pinMode(Servo_1, OUTPUT);
  pinMode(Servo_2, OUTPUT);
  pinMode(Servo_3, OUTPUT);
  
  // Servos default settings
  for (byte i=0; i<SUM_SERVOS; i++) { 
    servo[i].pulscount = 2999;            // alle Servos auf Mitte (2 count = 1µs) 
    servo[i].pulscount_New = 2999;        // alle Servos auf Mitte  
    servo[i].stepWidth = 255;             // max. Stellgeschwindigkeit pauschal für alle     
  }
   
  preSet_Timer1();
  run_Timer1();                           // entweder das oder turn_on_off_servos()

  // Bsp. individuelle Stellgeschwindigkeit
  servo[0].stepWidth = 20;                
  servo[1].stepWidth = 30;                 
}


void loop() {
  
  if (flag_servos_updated == true) {
    calc_ServoPositions();
    flag_servos_updated = false;
  }

  change_Servoposition();               // Demo
    
} // loop Ende


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

ISR(TIMER1_COMPB_vect)    // wird aller >= 1ms aufgerufen bzw. aller minimalen Pulszeit
{  
   static byte servoNr = 0;
   static unsigned int tcount = 0;
    
   switch(servoNr) {    
      case 0:                             digitalWrite(Servo_0, HIGH); tcount = servo[servoNr].pulscount; break;
      case 1: digitalWrite(Servo_0, LOW); digitalWrite(Servo_1, HIGH); tcount = servo[servoNr].pulscount; break;  
      case 2: digitalWrite(Servo_1, LOW); digitalWrite(Servo_2, HIGH); tcount = servo[servoNr].pulscount; break; 
      case 3: digitalWrite(Servo_2, LOW); digitalWrite(Servo_3, HIGH); tcount = servo[servoNr].pulscount; break;   
      case 4: digitalWrite(Servo_3, LOW);                              tcount = servo_pausecount;         break;  // restliche Pulspausenzeit                        
   }
   
   OCR1B += tcount;  
   servoNr++;
   if (servoNr > SUM_SERVOS) {
    flag_servos_updated = true;
    servoNr = 0;                  
   }
}


void calc_ServoPositions ()      
{          
   // *** alle Pulszeiten aktualisieren ***
   for (byte i=0; i<SUM_SERVOS; i++) {
      if (PULSCOUNT_MIN <= servo[i].pulscount_New && servo[i].pulscount_New <= PULSCOUNT_MAX) {
         
         if (servo[i].pulscount_New > servo[i].pulscount) {           // Bsp.: 3800 > 3700
            servo[i].pulscount += servo[i].stepWidth;                 // Bsp.: 3800 + 250 = 4050
            if (servo[i].pulscount > servo[i].pulscount_New) {        // Bsp.: 4050 > 3800
               servo[i].pulscount = servo[i].pulscount_New;           // Maximalwertbegrenzung = 3800   
            }
         }
         else if (servo[i].pulscount_New < servo[i].pulscount) {
            servo[i].pulscount -= servo[i].stepWidth;
            if (servo[i].pulscount < servo[i].pulscount_New) {
               servo[i].pulscount = servo[i].pulscount_New;
            }
         }
      }      
   }
   
   // *** restliche Pulspausenzeit berechnen nach Abzug aller Pulszeiten ***
   servo_pausecount = PULSPAUSENCOUNT;        // entspricht 20ms
   for (byte i=0; i<SUM_SERVOS; i++) {
      servo_pausecount = servo_pausecount - servo[i].pulscount;
   }
}


void preSet_Timer1 ()     // Normal Mode, Servo Pulserzeugung
{
  cli();                  // Interrupts ausschalten
  TCCR1A = 0;             // Reset 
  TCCR1B = 0;             // 
  TIMSK1 = 0;             // 
  TCNT1  = 0;             // 
  OCR1B  = 0;             // 
  TIMSK1 = (1<<OCIE1B);   // enable Compare Match B ISR
  sei();                  // Interrupts einschalten
}  


void run_Timer1 ()        // Servo Pulserzeugung      
{
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
    TCCR1B |= (1 << CS11);  // Prescaler 8
  }
}


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

 
void change_Servoposition()                     // Demo
{
  static unsigned long last_ms = 0;
  const unsigned int intervall = 5000;          // aller 5s bewegen
  
  if ( millis() - last_ms < intervall)  return;
  last_ms += intervall;

  for (byte i=0; i<SUM_SERVOS; i++) { 
    servo[i].pulscount_New += 2000;             // etwas bewegen
    if (servo[i].pulscount_New > PULSCOUNT_MAX) {
      servo[i].pulscount_New = 1999;    
    }
  }
}

Jetzt acker dich da mal durch .... :wink: Das ist das was ich dir von meiner Servospielerei beisteuern kann.

Für PWM können Sie sich Folgendes ansehen:

Bibliotheken:

PWM_lib:

TC_lib:

oder direkte Programmierung:
https://forum.arduino.cc/index.php?topic=420674.0
reply 1

https://forum.arduino.cc/index.php?topic=600811.msg4086829#msg4086829
reply 17

https://forum.arduino.cc/index.php?topic=600811.msg4084378#msg4084378
reply 8

Moderne ESC benutzen gar kein pwm mehr, sondern andere Protokolle.

Was hast du für welche?

Moderne ESC benutzen gar kein pwm mehr, sondern andere Protokolle.

Mein BLHeli-32 ESCs koennen fast alle Protokolle akzeptieren, ob es 490Hz PWM, Oneshot, Multishot oder DShot ist.

Diese Code implementiert DShot600 auf die Arduino Due mit dem PDC DMA. Es gibt 4 PWM Ausgaenge auf D34, D36, D38 und D40:

// Enable DShot600 for 4 PWM channels on D34, D36, D38 and D40
#define NO_CHANNELS 4               // 4 PWM output channels
#define DSHOT_PACKET_SIZE 18        // 16-bit DShot packet size plus additional header and footer 0 duty-cycle values

// Array to store the DShot PWM pulse widths for each of the 4 channels
uint16_t dShotData[NO_CHANNELS * DSHOT_PACKET_SIZE]; 

void setup() {
  // Set-up 4 PWM outputs on pins D34, D36, D38 and D40 for channels 0 through to 3 respectively
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                                               // Enable PWM   
  PIOC->PIO_ABSR |= PIO_ABSR_P8 | PIO_ABSR_P6 | PIO_ABSR_P4 | PIO_ABSR_P2;         // Set the port C PWM pins to peripheral type B                
  PIOC->PIO_PDR |= PIO_PDR_P8 | PIO_PDR_P6 | PIO_PDR_P4 | PIO_PDR_P2;              // Set the port C PWM pins to outputs                 
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(1);                                // Set the PWM clock A rate to 84MHz (84MHz/1)
  PWM->PWM_SCM |= PWM_SCM_UPDM_MODE2 |                                             // Automatically load the duty-cycle register with the PDC each timer cycle                    
                  PWM_SCM_SYNC3 | PWM_SCM_SYNC2 | PWM_SCM_SYNC1 | PWM_SCM_SYNC0;   // Set the PWM channels as synchronous                  
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_CLKA;       // Enable single slope PWM and set the clock source as CLKA for all synchronous channels
  PWM->PWM_CH_NUM[0].PWM_CPRD = 139;                    // Set the PWM frequency 84MHz/(139 + 1) = 600kHz for all synchronous channels
  PWM->PWM_ENA = PWM_ENA_CHID0;                         // Enable all synchronous PWM channels
}

void loop() 
{
   delay(1);                                            // Wait 1 millisecond to simulate flight controller calculations
   for (uint8_t i = 0; i < 4; i++)                      // Iterate through each PWM channel
   {
      DShotWrite(i, 48, 0);                             // Generate the DShot600 pulses: 48 = standby state
   }
   PWM->PWM_TPR = (uint32_t)dShotData;                  // Set the address of the transmit data pointer
   PWM->PWM_TCR = NO_CHANNELS * DSHOT_PACKET_SIZE;      // Set the length of the transmit data
   PWM->PWM_PTCR |= PWM_PTCR_TXTEN;                     // Enable the Peripheral DMA Controller to send DShot binary coded PWM
}

void DShotWrite(uint8_t dmaChannel, uint16_t data, boolean telemetry)
{
  data = (data << 1) | (telemetry ? 1 : 0);   // Shift 11 bits of data 1 space to the right and append the telemetry bit
  
  uint16_t checksum = 0;                // Compute CRC checksum
  uint16_t checksumData = data;
  for (uint8_t i = 0; i < 3; i++)       // XOR data by nibbles
  {
    checksum ^=  checksumData;   
    checksumData >>= 4;
  }
  checksum &= 0x000f;                   // Mask the checksum
  data = (data << 4) | checksum;        // Append the checksum

  uint16_t bitMask = 0x8000;            // Generate the timer duty cycles for each bit of data 
  for (uint8_t i = 0; i < 16; i++)
  {
    dShotData[i * 4 + dmaChannel + 4] = (data & bitMask) ? 104 : 51;    // Calculate the duty cycles for DSHOT600
    bitMask >>= 1;
  }
}