Forum: Mikrocontroller und Digitale Elektronik ATXMEGA32: Pins schalten viel zu langsam


von Fritz M. (widar)


Lesenswert?

Hallo zusammen

Ich bin xmega Neuling (und auch neu im Forum :-)) und komme bei einem 
Projekt nicht mehr weiter. Ich möchte ein Eingangssignal welches an 
einem Einganspin anliegt teilen und das geteilte Signal an einem 
Ausgangspin wider ausgeben. Der Teilungsfaktor soll per USART gewählt 
werden können. Grundsätzlich funktioniert mein Code wunderbar, aber eben 
leider nur sehr langsam (bis fin ca. 178KHz, danach kommt der Controller 
nicht mehr nach). Virtual Ports habe ich bereits versucht, dass hat 
nicht mehr Geschwindigkeit gebracht. Ich verstehe einfach nicht wieso er 
uC nicht nachkommt, bei 32MHz sollte das doch kein Problem sein oder?. 
Die 32MHz habe ich nachgemessen. Hat jemand eine Idee?



//Globale Variablen
int32_t iAktuellerCountCH1 = 0;    //wie viele Counts wurden auf CH1 
bereits detektiert?
int32_t iTeilerverhaeltnis = 2;    //enthält das übertragene 
Teilerverhältnis




ISR(PORTD_INT0_vect){    //PD0 = CH1_IN
  iAktuellerCountCH1 = iAktuellerCountCH1 + 1;
  if (iAktuellerCountCH1 >= iTeilerverhaeltnis){    //Ist 
Teilerverhältnis erreicht
    PORTB.OUTTGL = PORTB.OUT | 0b00000001;    //Pin wird getoggelt
    iAktuellerCountCH1 = 0;
  }
}




void interrupt_init(void){
  PMIC.CTRL = PMIC.CTRL | 0b00000111;    //High, Medium und Low-Level 
Interrupts werden aktiviert
  PORTD.INT0MASK = 0b00000001;  //PD0 wird Interrupt0 zugeordnet
  PORTD.INTCTRL = 0b00000010;    //Interrupt0 ist Medium-Level
  PORTD.PIN1CTRL = (PORTD.PIN1CTRL & 0b11111000);    //triggere auf 
steigende und fallende Flanke
}



void port_init(){
  PORTD.DIRTGL = 0b00001000;  //PD0 = CH1_IN, PD2 = RXD0, PD3 = TXD0
  PORTB.DIRTGL = 0b00000001;  //PB0 = CH1_OUT
}





int main(void){
  cli();    //Interrupts global deaktivieren
  port_init();
  clock_init();
  pll_init();
  sei();    //Interrupts global aktivieren
  interrupt_init();
  while(1){
  }
}

von fpga (Gast)


Lesenswert?

>PORTB.OUTTGL = PORTB.OUT | 0b00000001;    //Pin wird getoggelt

Das muss anders gehen, so liest du ja erst den Port wieder ein.

Gruß J

von fpga (Gast)


Lesenswert?

PORTB.OUTTGL |= PIN0_bm;

Gruß J

von Fritz M. (widar)


Lesenswert?

Vielen Dank für die rasche Antwort.

Da hast du recht. Trotzdem erklährt das nicht wieso die ISR so viel Zeit 
verpulvert. Ist mir nach wie vor ein Rätsel.

von Stefan K. (stefan64)


Lesenswert?

Welcher Compiler, welche Optimierung? Kann es sein, dass der 
UART-Interrupt Deinen INT0-Interrupt kurzfristig sperrt?

Dein INT0-Interrupt wird bei beiden Flanken aufgerufen, bei fin = 178khz 
also mit 356khz. Das macht bei 32Mhz 89 Takte. Bei fehlender Optimierung 
könnte das schon knapp werden.

Gruß, Stefan

von Jim M. (turboj)


Lesenswert?

Fritz M. schrieb:
> bis fin ca. 178KHz, danach kommt der Controller
> nicht mehr nach

Eine ISR hat immer eine Menge zusätzlichen Code zum Sichern und 
Rücksetzen der verwendeten Register. Siehe Disassembly.

Schnelles Pintoggeln erfordert z.B. einen Timer im PWM Mode.

fpga schrieb:
> PORTB.OUTTGL |= PIN0_bm;

Wieso nicht
1
 PORTB.OUTTGL = PIN0_bm;

Mit dem |= ist da ein unnötiger zusätzlicher Lesezugriff drin.

Fritz M. schrieb:
> void port_init(){
>   PORTD.DIRTGL = 0b00001000;  //PD0 = CH1_IN, PD2 = RXD0, PD3 = TXD0
>   PORTB.DIRTGL = 0b00000001;  //PB0 = CH1_OUT
> }

Die Verwendung von DIRTGL ist hier IMHO komplett falsch. Du wolltest 
vermutlich DIRSET haben. Ich hätte zusätlich einen DIRCLR für die Input 
Pins gemacht - nur zur Sicherheit.

von Crazy Harry (crazy_h)


Lesenswert?

Dreh den Takt auf 60MHz (laut Atmel erlaubt) oder 64MHz (mach ich immer) 
hoch ;-)

von Fritz M. (widar)


Lesenswert?

Vielen Dank für die Antworten.

Ist "PORTB.OUTTGL |= PIN0_bm;" nicht dasselbe wie "PORTB.OUTTGL = 
PORTB.OUTTGL | PIN0_bm;"? Dann würde genau gleich zuerst gelesen werden, 
da maskiert wird. Ich glaube nicht das dies einen Unterscheid macht.

> Welcher Compiler, welche Optimierung?
 Öhm.. Wo kann ich das anschauen? Ich programmiere in Atmelstudio 7 
(Version 7.0.1188), standartmässige Projekteinstellungen (AVR/GNU C 
Compiler).

> Kann es sein, dass der UART-Interrupt Deinen INT0-Interrupt kurzfristig
> sperrt?
Der UART-Interrupt wird zurzeit nicht ausgeführt und kann daher nichts 
blockieren, da bin ich mir sicher.



> Eine ISR hat immer eine Menge zusätzlichen Code zum Sichern und
> Rücksetzen der verwendeten Register. Siehe Disassembly.
Gemäss Datenblatt genau 6 Taktzyklen wenn ich das richtig verstehe:

The interrupt response time for all the enabled interrupts is three CPU 
clock cycles, minimum; one cycle to finish the ongoing instruction and 
two cycles to store the program counter to the stack. After the program 
counter is pushed on the stack, the program vector for the interrupt is 
executed. The jump to the interrupt handler takes three clock cycles.

> Die Verwendung von DIRTGL ist hier IMHO komplett falsch. Du wolltest
> vermutlich DIRSET haben. Ich hätte zusätlich einen DIRCLR für die Input
> Pins gemacht - nur zur Sicherheit.
Da hast du recht, vielen dank.


> Dreh den Takt auf 60MHz (laut Atmel erlaubt) oder 64MHz (mach ich immer)
> hoch ;-)

Also gemäss meinem Datenblatt kann der ATxmega32A4U @3.3V maximal 32MHz. 
Falls du ein anderes Datenblatt hast, kannst du mir den Link senden?

von Stefan K. (stefan64)


Lesenswert?

Fritz M. schrieb:
> Gemäss Datenblatt genau 6 Taktzyklen wenn ich das richtig verstehe:

Da ist aber noch kein einziges Arbeitsregister auf den Stack gesichert.
Und der Rücksprung inkl. dazugehörigem Register zurückholen darf in der 
Rechnung auch nicht vergessen werden.

Ist der eigendliche Sinn wirklich nur ein Teilen einer Eingangsfrequenz? 
Das lässt sich wahrscheinlich durch die interne Timer-Logik 
bewerkstelligen lassen, ohne einen einzigen Interrupt.

Viele Grüße, Stefan

von Fritz M. (widar)


Lesenswert?

> Ist der eigendliche Sinn wirklich nur ein Teilen einer Eingangsfrequenz?

Ja, allerdings mit zwei Kanälen gleichzeitig, welche eine unbekannte 
Phasenverschiebung (CH1->CH2: 30-70°) aufweisen. Da ich aber schon mit 
einem Kanal weit vom Ziel entfernt bin, habe ich den zweiten Kanal mal 
auskommentiert.


> Das lässt sich wahrscheinlich durch die interne Timer-Logik
> bewerkstelligen lassen, ohne einen einzigen Interrupt.

Hmm... Hast du mir da einen Tipp? Code-technisch meine ich. Examples 
oder so..

von Code Lesa (Gast)


Lesenswert?

Fritz M. schrieb:
> int32_t iAktuellerCountCH1 = 0;
> int32_t iTeilerverhaeltnis = 2;

Die Variablen-Breite ist womöglich etwas zu gross, je nachdem
wie dein zu erwartender Teilerbereich ist. Das kostet ein
paar wertvolle Zyklen bei Vergleichen und Inkrementierungen.

Vielleicht reicht sogar uint8_t ?

von Sascha W. (sascha-w)


Lesenswert?

Fritz M. schrieb im Beitrag #5025
>> Das lässt sich wahrscheinlich durch die interne Timer-Logik
>> bewerkstelligen lassen, ohne einen einzigen Interrupt.
>
> Hmm... Hast du mir da einen Tipp? Code-technisch meine ich. Examples
> oder so..

Die oder einige Timer kannst du extern takten. Dann stellst du im 
Timer-OCR deinen Teilerfaktor ein und lässt den zugehörigen Ausgangspin 
bei erreichen des OCR-Wertes toggeln.
Geht völlig ohne Interrupts. Die Timer haben aber nur 16Bit.

Sascha

von Bastian W. (jackfrost)


Lesenswert?

Fritz M. schrieb:
> Ist "PORTB.OUTTGL |= PIN0_bm;" nicht dasselbe wie "PORTB.OUTTGL =
> PORTB.OUTTGL | PIN0_bm;"? Dann würde genau gleich zuerst gelesen werden,
> da maskiert wird. Ich glaube nicht das dies einen Unterscheid macht.

Bei Portx.OUTTGL werden die Bits mit 1 gewechselt. Beim lesen hat das 
Register 0 da das nicht den Inhalt von Portx.OUT hat.

Es reicht also
1
PORTB.OUTTGL = Pin1_bm;

Was für eine Frequenz hast du am Eingang.
Ggf kannst du das Eventsystem nutzen. Um den Takt zuerfassen.

Gruß JackFrost

: Bearbeitet durch User
von Crazy Harry (crazy_h)


Lesenswert?

Fritz M. schrieb:
> Dreh den Takt auf 60MHz (laut Atmel erlaubt) oder 64MHz (mach ich immer)
> hoch ;-)
>
> Also gemäss meinem Datenblatt kann der ATxmega32A4U @3.3V maximal 32MHz.
> Falls du ein anderes Datenblatt hast, kannst du mir den Link senden?

Es gibt eine Appnote von Atmel (die ich natürlich grad nicht finde) in 
der beschrieben wird, wie und daß man auf 60MHz gehen kann. Ich mach 
dann immer gleich 64MHz (XMega256A3U).

von Dieter F. (Gast)


Lesenswert?

Fritz M. schrieb:
> //triggere auf
> steigende und fallende Flanke

Warum?

Damit hast Du bei jeder Flanke einen Interrupt mit Verarbeitung, Prolog 
und Epilog und Deinen 32-Bit-Operationen. Wenn Du Dein .lss-File zeigst 
kann man schauen, wieviele Zyklen da jeweils verbraten werden. Ungefähr 
80 Zyklen scheinen mir da nicht ganz so ungewöhnlich zu sein.

von Dieter F. (Gast)


Lesenswert?

Fritz M. schrieb:
> //Globale Variablen
> int32_t iAktuellerCountCH1 = 0;    //wie viele Counts wurden auf CH1
> bereits detektiert?
> int32_t iTeilerverhaeltnis = 2;    //enthält das übertragene
> Teilerverhältnis

Praktisch wäre auch, wenn Du diese Variablen als "volatile" deklarieren 
und ein compilierbares Beispiel Deines Problems anhängen würdest (mit 
genauer Angabe des MC - XMega32 gibt es in mehreren Versionen ...)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.