PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : 250µs-Timer für Arduino Due



HaWe
19.11.2014, 15:33
hallo,
kennt jemand Beispiel-Sketche, wie man Timer auf dem Due programmiert?
Ich habe nur die alten Codes für die IRQs mit den völlig unleserlichen und uverständlichen AVR-Codes wie



// (in setup()
noInterrupts();
TIMSK1 |= (1 << OCIE1A);
TCCR1A = 0;
TCCR1B = (1 << WGM12) | (1 << CS11);
OCR1A = 511;
interrupts();

plus zugehöriger ISR


ISR(TIMER1_COMPA_vect) { // read encoder values

ISRab [ 0] <<= 2;
ISRab [ 0] &= B00001100;
ISRab [ 0] |= (digitalRead(pinenc0A) << 1) | digitalRead(pinenc0B);
motenc[ 0] += schrittTab[ISRab[0]]; //

ISRab [ 1] <<= 2;
ISRab [ 1] &= B00001100;
ISRab [ 1] |= (digitalRead(pinenc1A) << 1) | digitalRead(pinenc1B);
motenc[ 1] += schrittTab[ISRab[1]]; //

ISRab [ 2] <<= 2;
ISRab [ 2] &= B00001100;
ISRab [ 2] |= (digitalRead(pinenc2A) << 1) | digitalRead(pinenc2B);
motenc[ 2] += schrittTab[ISRab[2]]; //

ISRab [ 3] <<= 2;
ISRab [ 3] &= B00001100;
ISRab [ 3] |= (digitalRead(pinenc3A) << 1) | digitalRead(pinenc3B);
motenc[ 3] += schrittTab[ISRab[3]]; //

ISRab [ 4] <<= 2;
ISRab [ 4] &= B00001100;
ISRab [ 4] |= (digitalRead(pinenc4A) << 1) | digitalRead(pinenc4B);
motenc[ 4] += schrittTab[ISRab[4]]; //

ISRab [ 5] <<= 2;
ISRab [ 5] &= B00001100;
ISRab [ 5] |= (digitalRead(pinenc5A) << 1) | digitalRead(pinenc5B);
motenc[ 5] += schrittTab[ISRab[5]]; //

}





Alles ziemlich verkorkst, aber funktioniert wenigstens.

Aber wie geht jetzt Auslesen von Quadratur-Encodern etc. in festen 200-250µs-Zeitabständen auf dem Due ?

HaWe
20.11.2014, 08:06
weiß evtl jemand, wie es auf dem Beaglebone Black gemacht wird?

Mxt
20.11.2014, 08:34
Hallo,

völlig unleserliche und unverständliche AVR-Codes kannst du für den Due vergessen. Da brauchst du unleserliche und unverständliche SAM3 Codes. :)

Die ARM Cortex liefern zwar das Grundgerüst, aber Timer sind bei ARM Controllern dann wieder herstellerspezifisch ausgeführt. Da hilft nur ein Blick in dieses schöne 1800 seitige Datenblatt vom Prozessor ...

Das ist ein Grund, warum mein Due meist unbenutzt rumliegt. Einfache Timergeschichten gehen mit mbed-fähigen ARM Controllern viel einfacher.
http://developer.mbed.org/handbook/Ticker
http://developer.mbed.org/handbook/RTOS#rtos-timer
Wenn das nicht reicht, wird es aber auch wieder herstellerspezifisch ...

Der Beaglebone läuft ja normalerweise unter Linux, da werden die Timer des ARM vom Betriebssystem verwendet. Es gibt allerdings auch Timereingänge z.B. zur Frequenzmessung. Für zeitkritische Hardware Sachen hat der Beaglebone die PRUs, das sind zwei eigene Kerne (kein ARM, kein Linux) auf dem Chip.

Quadraturencoder lesen ist bei solchen Prozessoren wie dem TI Sitara aber auch als Hardware verfügbar:
http://www.adafruit.com/blog/2014/07/01/beaglebone-using-ti-eqep-to-read-quadrature-encoder-in-dc-motor-beagleboneblack-txinstruments-beagleboardorg/

HaWe
20.11.2014, 08:51
haha, "Da brauchst du unleserliche und unverständliche SAM3 Codes."
und der ist auch gut: "dieses schöne 1800 seitige Datenblatt vom Prozessor ..."

na wunderbar :-/

rtos sieht zwar auf den ersten Blick gar nicht sooo schwierig aus -
(edit - ok - auf den zweiten allerdings schon... :( )
ich hatte mir aber auch schon überlegt, die Scheduler-Lib zu benutzen, wenn das nicht geht.

z.B per
void loop1() { // neue Endlosschleife //
// Encoderstellungen auslesen //
// dann irgendwie berechnen //
// dann ein Yield() fürs koop. Multitasking //
// dann vllt ein paar µs warten //
}

Hast du Erfahrung mit der Scheduler-Lib?

Zeitscheibenverhalten (Echtzeit ist es sicher nicht) ist dann aber nur die eine Sache,
aber die andere ist dann Encoder auslesen und berechnen - das muss dann ja auch wieder von Grund auf neu programmiert werden

Fangen wir beim 1. Problem mal an:
Was hältst du / haltet ihr von der Scheduler-Lib ?

Mxt
20.11.2014, 08:57
Na dann antworte ich mal für mich.

Der Due ist ganz nützlich, wenn ich mal einen Controller mit vielen IOs und vielen UARTs brauche. Ansonsten halte ich vom Due und der zugehörigen Arduino Software nicht viel. Der Due ist nur eine Durchgangsstation, die echten Fans ignorieren ihn. Wer sich darauf einlässt, erkennt schnell, dass es bessere Alternativen gibt.

Arduino ist primär 8-Bit. Ob der Arduino Zero daran etwas ändern wird, bleibt abzuwarten.

HaWe
20.11.2014, 09:08
ja, ich brauchte ihn, weil ich
- mindestens annähernd so viele I/Os brauche wie beim Mega für meine 6-8 Encodermotore mit pwm (5 digit. pins pro Motor)
- multitaskingfähig
- 2 unabhängige Hardware-i2c-Ports, beide als Slave benutzbar
- mehr Speicher für anspruchsvollere Programme (mindestens ~100k RAM, am liebsten noch mehr)
- und mehr Rechenpower, mindestens 10x so schnell wie die AVRs (Mega), was der Due ja kann.
- eine einfache IDE wie Sketch war auch Vorraussetzung, Eclipse oder VS sind für mich der Horror.

nu hab ich ihn als Hardware - und hoffte eigentlich, die Arduino-Entwickler haben auch an den Soft-Teil gedacht, um ihn auch nutzen zu können...

aber zurück zum 1. Fragenkomplex -

kennst du die Scheduler-Lib, und falls ja:
- was hälst du davon
- und denkst du, man kann extra-loops für Encoder Reading verwenden ?

Mxt
20.11.2014, 09:13
Nachtrag:

Die mbed-Beispiele für Encoder verwenden alle Interrupts:
http://developer.mbed.org/cookbook/QEI
http://developer.mbed.org/teams/OpenMoCo/code/QEIx4/

Das sollte man mit dem Due und Standard-Arduino Code eigentlich nachbauen können.

The Arduino Due board has powerful interrupt capabilities that allows you to attach an interrupt function on all available pins. You can directly specify the pin number in attachInterrupt().

- - - Aktualisiert - - -



kennst du die Scheduler-Lib, und falls ja:

Nur mal kurz angesehen, nie in einem Projekt benutzt. Ich verwende mbed-RTOS.

Für Encoder würde ich eher zu Interrupts tendieren. S.o.

HaWe
20.11.2014, 09:18
zu den Links:
http://developer.mbed.org/cookbook/QEI
http://developer.mbed.org/teams/OpenMoCo/code/QEIx4/

...was man da jetzt für die Encoder als libs (Ordner) in den Arduino-Sketch-Ordner kopieren muss, und wie das dann im Sketch-Programm später aussieht, erkenne ich da ehrlich gesagt nicht so recht...

mbed-RTOS ist mir zu schwierig, denke ich.

Mxt
20.11.2014, 09:30
Kopieren soll man da nichts.

Man muss das in Sketch nachprogrammieren. Dazu schaut man sich den Source an, z.B.
http://developer.mbed.org/users/aberk/code/QEI/file/5c2ad81551aa/QEI.cpp

Da tauchen Sachen auf wie


QEI::QEI(PinName channelA,
PinName channelB,

und


channelA_.rise(this, &QEI::encode);
channelA_.fall(this, &QEI::encode);

//If we're using X4 encoding, then attach interrupts to channel B too.
if (encoding == X4_ENCODING) {
channelB_.rise(this, &QEI::encode);
channelB_.fall(this, &QEI::encode);
}

D.h. es gibt da Pins für Channel A und B. Steigenden und fallenden Flanken daran werden Funktionen zugeordnet, hier überall die gleiche.

So was ähnliches muss man sich in Sketch auch schreiben.

HaWe
20.11.2014, 09:42
ach so, ganz neu schreiben - das hatte ich nicht verstanden, ich dachte bis jetzt, das wäre schon für Sketch.
Das kann ich allerdings leider nicht, dazu verstehe ich zuwenig davon, und C++ kann ich überhaupt nicht - nur nacktes C (oder eben extrem abgespeckte Objekte wie z.B. Serial und entsprechend automatisch included und aufbereitet wie in Sketch).

HaWe
21.11.2014, 08:23
heißt das etwa, es gibt keine fertigen Sketch-Beispielcodes für Due-Timer ???

Mxt
21.11.2014, 15:52
Was ist mit der Lib, die Google gleich als erstes findet ?
https://github.com/ivanseidel/DueTimer

Wenn man mal in die DueTimer.cpp schaut, sieht man, dass sich das ganze doch etwas vom AVR unterscheidet.
Die Registerzugriffe scheinen aber auch beim Due schon durch Funktionen gekapselt zu sein. Was da an Libraries
in Arduino 1.5.x alles steckt, scheint aber nirgends richtig dokumentiert zu sein.

HaWe
21.11.2014, 16:42
ah - jetzt verstehe ich...
es ist ganz anders als die AVR-Variante. ich hatte nicht an so eine komplette lib gedacht.
Das Programm sieht ja ausgesprochen simpel aus, viel einfacher als das oben für den AVR:



#include <DueTimer.h>

int myLed = 13;

bool ledOn = false;

void myHandler(){
ledOn = !ledOn;

digitalWrite(myLed, ledOn); // Led on, off, on, off...
}

void setup(){
pinMode(myLed, OUTPUT);

Timer3.attachInterrupt(myHandler);
Timer3.start(500000); // Calls every 500ms
}

void loop(){

while(1){
// I'm stuck in here! help me...
}

}


Hier wird dann ja wohl Timer 3 verwendet, bei mir oben war es glaube ich Timer 1 und alle 250µs, dann also
Timer1.attachInterrupt(myHandler);
Timer1.start(250); // Calls every 250µs

in myHandler dann meine ISR arrays


volatile long motenc[MAXMOTORS] = {0, 0, 0, 0, 0, 0},
oldenc[MAXMOTORS] = {0, 0, 0, 0, 0, 0};
int8_t schrittTab[16] = {0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1, 0};

volatile int8_t ISRab[MAXMOTORS] = {0, 0, 0, 0, 0, 0};

void myHandler() { // read encoder values

ISRab [ 0] <<= 2;
ISRab [ 0] &= B00001100;
ISRab [ 0] |= (digitalRead(pinenc0A) << 1) | digitalRead(pinenc0B);
motenc[ 0] += schrittTab[ISRab[0]]; //

ISRab [ 1] <<= 2;
ISRab [ 1] &= B00001100;
ISRab [ 1] |= (digitalRead(pinenc1A) << 1) | digitalRead(pinenc1B);
motenc[ 1] += schrittTab[ISRab[1]]; //

ISRab [ 2] <<= 2;
ISRab [ 2] &= B00001100;
ISRab [ 2] |= (digitalRead(pinenc2A) << 1) | digitalRead(pinenc2B);
motenc[ 2] += schrittTab[ISRab[2]]; //

ISRab [ 3] <<= 2;
ISRab [ 3] &= B00001100;
ISRab [ 3] |= (digitalRead(pinenc3A) << 1) | digitalRead(pinenc3B);
motenc[ 3] += schrittTab[ISRab[3]]; //

ISRab [ 4] <<= 2;
ISRab [ 4] &= B00001100;
ISRab [ 4] |= (digitalRead(pinenc4A) << 1) | digitalRead(pinenc4B);
motenc[ 4] += schrittTab[ISRab[4]]; //

ISRab [ 5] <<= 2;
ISRab [ 5] &= B00001100;
ISRab [ 5] |= (digitalRead(pinenc5A) << 1) | digitalRead(pinenc5B);
motenc[ 5] += schrittTab[ISRab[5]]; //

}
- müsste dann doch so funktionieren...? Werde ich gleich am Wochenende austesten!

Ein ganz herzliches Dankeschön! 8-)

Mxt
21.11.2014, 18:04
Ein Problem bei deinem Code dürfte das digitalRead() sein. Das ist bei Arduinos als ziemlich langsam verrufen.

Bei mbed gibt es da die Möglichkeit mit PortIn alle 32 Pins eines Ports auf einmal zu lesen, das wäre für die Abfrage mehrerer Encoder sicher viel besser.

Eventuell gibt es in den Untiefen der Arduino Software dafür auch Funktionen ...

Sisor
21.11.2014, 20:24
Das SFR GPIOx_ODR (data output register) hat laut Manual 32Bit, von denen aber die oberen 16 unbenutzt sind. D.h. man kann (und sollte) ein 16Bit Port (z.B. PortA_L + PortA_H) direkt in einem Zug auslesen, statt mehrere einzelne nacheinander.

HaWe
22.11.2014, 11:01
danke für die Tipps, falls mein Code nicht funktioniert, werde ich dran denken!
digitalRead hat allerdings bisher beim Mega mit Sketch perfekt funktioniert, und selbst da war noch Luft drin (theoretisch reichen bei 100° pro 100ms sogar 500-1000µs IRQ Takt).

HaWe
22.11.2014, 15:17
es scheint zu stimmen, ich habe es gegen den Mega getestet:
Beim mega mit dem ursprünglichen Code immer alle Encoder ok in Echtzeit, volle Umdrehungen aller Motoren werden immer als 360° angezeigt,
beim Due mit dem angepassten Code werden aber etliche Werte verschluckt, meist wird pro volle Umdrehung irgendwas zwischen 180 und 360° angezeigt.

Relativ gut war immer der erste Motor, alle anderen deutlich schlechter.

Selbst nach Timer-Intervall von 250 auf 100µs kaum eine Verbesserung.
Umpolen und Vertauschen der Motoren brachte keine Änderung: der erste in der Reihe war immer der beste, der Rest sehr schlecht.


Wie kann ich das jetzt mit den 32 bit probieren, um das zu verbessern? Wo muss da was geändert werden?

Der Vollständigkeit halber: hier ist der komplette Code für 6 Motoren:



/************************************************** **********
* Programm zur Auswertung eines händisch betriebenen
* Drehencoders (Quadraturencoder) mit dem Arduino Due
* per Due-Timer mit einer Abfragefrequenz von rd. 4-10kHz
* Entlehnt an http ://www.meinDUINO.de
************************************************** **********/
#include <DueTimer.h>

char sbuf[100];

#define MAXMOTORS 6 // maximum number of encoder motors supported by Arduino Uno == 2 // Mega == 8



// motor 0
#define pinenc0A 22 // enc0A yellow
#define pinenc0B 23 // enc0B blue
#define pinmot0d1 24 // dir0-1 <<
#define pinmot0d2 25 // dir0-2
#define pinmot0pwm 8 // pwm enable0 << %timer

// motor 1
#define pinenc1A 26 // enc1A yellow
#define pinenc1B 27 // enc1B blue
#define pinmot1d1 28 // dir1-1 <<
#define pinmot1d2 29 // dir1-2
#define pinmot1pwm 9 // pwm enable1 << %timer


// motor 2
#define pinenc2A 30 // enc2A yellow
#define pinenc2B 31 // enc2B blue
#define pinmot2d1 32 // dir2-1 <<
#define pinmot2d2 33 // dir2-2
#define pinmot2pwm 10 // pwm enable2 << %timer

// motor 3
#define pinenc3A 34 // enc3A yellow
#define pinenc3B 35 // enc3B blue
#define pinmot3d1 36 // dir3-1 <<
#define pinmot3d2 37 // dir3-2
#define pinmot3pwm 11 // pwm enable3 << %timer

// motor 4
#define pinenc4A 38 // enc4A yellow
#define pinenc4B 39 // enc4B blue
#define pinmot4d1 40 // dir4-1 <<
#define pinmot4d2 41 // dir4-2
#define pinmot4pwm 12 // pwm enable4 << %timer

// motor 5
#define pinenc5A 42 // enc5A yellow
#define pinenc5B 43 // enc5B blue
#define pinmot5d1 44 // dir5-1 <<
#define pinmot5d2 45 // dir5-2
#define pinmot5pwm 13 // pwm enable5 << %timer



volatile long motenc[MAXMOTORS] = {0, 0, 0, 0, 0, 0},
oldenc[MAXMOTORS] = {0, 0, 0, 0, 0, 0};

byte pinmotdir[MAXMOTORS][ 2] = {
{pinmot0d1, pinmot0d2}, // motor direction pin array
{pinmot1d1, pinmot1d2},
{pinmot2d1, pinmot2d2},
{pinmot3d1, pinmot3d2},
{pinmot4d1, pinmot4d2},
{pinmot5d1, pinmot5d2},
};

int pinmotpwm[MAXMOTORS] = {pinmot0pwm, pinmot1pwm, pinmot2pwm, // motor pwm pin array
pinmot3pwm, pinmot4pwm, pinmot5pwm,
};

volatile int8_t ISRab[MAXMOTORS] = {0, 0, 0, 0, 0, 0};

// 1/2 Auflösung
int8_t schrittTab[16] = {0, 0,0,0,1,0,0,-1, 0,0,0,1,0,0,-1,0};




/************************************************** ***********
* Interrupt Handler Routine
************************************************** ***********/

void myHandler() {

ISRab [ 0] <<= 2;
ISRab [ 0] &= B00001100;
ISRab [ 0] |= (digitalRead(pinenc0A) << 1) | digitalRead(pinenc0B);
motenc[ 0] += schrittTab[ISRab[0]]; //

ISRab [ 1] <<= 2;
ISRab [ 1] &= B00001100;
ISRab [ 1] |= (digitalRead(pinenc1A) << 1) | digitalRead(pinenc1B);
motenc[ 1] += schrittTab[ISRab[1]]; //

ISRab [ 2] <<= 2;
ISRab [ 2] &= B00001100;
ISRab [ 2] |= (digitalRead(pinenc2A) << 1) | digitalRead(pinenc2B);
motenc[ 2] += schrittTab[ISRab[2]]; //

ISRab [ 3] <<= 2;
ISRab [ 3] &= B00001100;
ISRab [ 3] |= (digitalRead(pinenc3A) << 1) | digitalRead(pinenc3B);
motenc[ 3] += schrittTab[ISRab[3]]; //

ISRab [ 4] <<= 2;
ISRab [ 4] &= B00001100;
ISRab [ 4] |= (digitalRead(pinenc4A) << 1) | digitalRead(pinenc4B);
motenc[ 4] += schrittTab[ISRab[4]]; //

ISRab [ 5] <<= 2;
ISRab [ 5] &= B00001100;
ISRab [ 5] |= (digitalRead(pinenc5A) << 1) | digitalRead(pinenc5B);
motenc[ 5] += schrittTab[ISRab[5]]; //


}


void setup() {
// motor pin settings
// setup for L293D motor driver

// motor 0
pinMode(pinenc0A, INPUT_PULLUP); // enc0A yellow
pinMode(pinenc0B, INPUT_PULLUP); // enc0B blue
pinMode(pinmot0d1, OUTPUT); // dir0-1
pinMode(pinmot0d2, OUTPUT); // dir0-2
pinMode(pinmot0pwm ,OUTPUT); // enable0

// motor 1
pinMode(pinenc1A, INPUT_PULLUP); // enc1A yellow
pinMode(pinenc1B, INPUT_PULLUP); // enc1B blue
pinMode(pinmot1d1, OUTPUT); // dir1-1
pinMode(pinmot1d2, OUTPUT); // dir1-2
pinMode(pinmot1pwm, OUTPUT); // enable1

// motor 2
pinMode(pinenc2A, INPUT_PULLUP); // enc2A yellow
pinMode(pinenc2B, INPUT_PULLUP); // enc2B blue
pinMode(pinmot2d1, OUTPUT); // dir2-1
pinMode(pinmot2d2, OUTPUT); // dir2-2
pinMode(pinmot2pwm, OUTPUT); // enable2

// motor 3
pinMode(pinenc3A, INPUT_PULLUP); // enc3A yellow
pinMode(pinenc3B, INPUT_PULLUP); // enc3B blue
pinMode(pinmot3d1, OUTPUT); // dir3-1
pinMode(pinmot3d2, OUTPUT); // dir3-2
pinMode(pinmot3pwm, OUTPUT); // enable3

// motor 4
pinMode(pinenc4A, INPUT_PULLUP); // enc4A yellow
pinMode(pinenc4B, INPUT_PULLUP); // enc4B blue
pinMode(pinmot4d1, OUTPUT); // dir4-1
pinMode(pinmot4d2, OUTPUT); // dir4-2
pinMode(pinmot4pwm, OUTPUT); // enable4

// motor 5
pinMode(pinenc5A, INPUT_PULLUP); // encA5 yellow
pinMode(pinenc5B, INPUT_PULLUP); // encB5 blue
pinMode(pinmot5d1, OUTPUT); // dir5-1
pinMode(pinmot5d2, OUTPUT); // dir5-2
pinMode(pinmot5pwm, OUTPUT); // enable5



Timer1.attachInterrupt(myHandler);
Timer1.start(100); // Calls every ...µs

Serial.begin(9600);
}


void loop() {

while(true) {
sprintf(sbuf, " 0=%5d, 1=%5d, 2=%5d, 3=%5d, 4=%5d, 5=%5d",
motenc[ 0],motenc[ 1], motenc[ 2], motenc[ 3], motenc[ 4], motenc[ 5]);
Serial.println(sbuf);
delay(100);
}
}

HaWe
22.11.2014, 18:17
bzw,
gibt es noch eine völlig andere, insgesamt viel effizientere Methode fürs Encoder-lesen anstelle der obigen nach

ISRab [ 0] <<= 2;
ISRab [ 0] &= B00001100;
ISRab [ 0] |= (digitalRead(pinenc0A) << 1) | digitalRead(pinenc0B);
motenc[ 0] += schrittTab[ISRab[0]];

Sisor
22.11.2014, 19:09
Hi HaWe,

ich hab gerade mal etwas in den Tiefen des Arduino-Systems gegraben.

In folgender Datei sind die Pins definiert:
C:\Program Files (x86)\Arduino\hardware\arduino\sam\variants\arduin o_due_x.cpp

Die Pins des Arduino-Due sind ganz anders strukturiert als die Pins der Ports. Ein ganzen Port als Wort auszulesen (statt einzelner Pins) macht imho keinen Sinn, weil dann die Verkabelung und der Code sehr unübersichtlich würden. Daher würde ich dir dazu raten, den Code so zu lassen.

HaWe
22.11.2014, 21:19
hi,
danke für den Post!
habe jetzt noch mal alles auseinandergenommen und neu zusammengesteckt - möglicherweise war in den Verbindungsteckern zur Spannungsversorgung ein Wackelkontakt.
Es lief dann anschließend schon deutlich besser.
Dann habe ich den 100µs- Timer wieder in einen 250er und sogar in einen 500er Takt gebracht - läuft jetzt offenbar doch fehlerfrei mit dem Due-Timer, zumindest bei Handantrieb. :)

Ich werde dann sicher nochmal unter Motorantrieb und schneller PID-Steuerung testen müssen, aber ich bin eigentlich optimistisch.
Danke an alle für die netten Tipps! 8-)