Forum: Mikrocontroller und Digitale Elektronik SAM3X8E Interruptproblem bei Uart/PMC(DMA)


von Christoph L. (lassi)


Lesenswert?

Hallo,

ich hab eine kurze Frage zum Thema DMA und Uart.

Ich Versuch gerade Daten über Uart zusenden und wollt dazu DMA benutzen.
1
void ConfigureUart(void)
2
{
3
  PIOA->PIO_IDR = PIO_PA8A_URXD | PIO_PA9A_UTXD;
4
  PIOA->PIO_PDR = PIO_PA8A_URXD | PIO_PA9A_UTXD;
5
  PIOA->PIO_ABSR &= ~(PIO_PA8A_URXD | PIO_PA9A_UTXD);
6
  PIOA->PIO_PUER = PIO_PA8A_URXD | PIO_PA9A_UTXD;
7
  PMC->PMC_PCER0 = 1 << ID_UART;
8
  UART->UART_CR = UART_CR_RSTRX | UART_CR_RSTTX | UART_CR_RXDIS |UART_CR_TXDIS;
9
10
  // Set the baudrate to 115200
11
  UART->UART_BRGR = 45; // 84000000 / 16 * x = BaudRate (write x into UART_BRGR)
12
13
  // No Parity
14
  UART->UART_MR = UART_MR_PAR_NO;
15
  //UART->UART_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS;
16
  UART->UART_IDR = 0xFFFFFFFF;
17
18
  NVIC_EnableIRQ((IRQn_Type)ID_UART);
19
  UART->UART_IER =  UART_IER_ENDTX; //<--makierte Zeile
20
21
  UART->UART_CR |=  UART_CR_TXEN;
22
23
}
24
25
void UartSend(uint8_t* value,uint16_t size)
26
{
27
  UART->UART_TPR = (uint32_t)value;
28
  UART->UART_TCR = (uint32_t)size;
29
30
  UART->UART_PTCR |= UART_PTCR_TXTEN;
31
}

Mein Problem ist folgendes: Wenn Ich den Interrupt (siehe markierte 
Zeile)
auskommentiere passt genau das was ich möchte, also sendet er seine 
Daten.

Wenn ich nun nun den Interrupt aktivieren sendet er nichts mehr.
Das kann ich mir gerade nicht Erklären und im Manual seh ich auch gerade 
kein Punkt der das Problem beschreibt.
Kann mir jemand Erklären was ich falsch mache ??

viele grüße
Lassi

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Hängt natürlich davon ab, was der ISR macht oder nicht macht - wenn der 
z.B. die Interruptquelle nicht löscht, steht der Interrupt immer wieder 
an.

Ich kenne den SAM selber nicht, aber wenn Du beim ST UART über DMA 
fahren willst, musst Du eine ganze Latte Andere Dinge im DMA Block 
konfigurieren, und der aufgerufene ISR nach Transmit Ende ist ein 
Anderer als der UART Tx ISR (nämlich einer aus der DMA Engine). Ist das 
beim SAM enger gekoppelt?

Mehr Code, den Du teilen wolltest? ;-)

von Christoph L. (lassi)


Lesenswert?

Hallo rac,

also ein anderer Interrupt sollte nicht anstehen, bzw den müsste ich 
sehen.
1
IrqState* HandleIrq(IrqState* state)
2
{
3
  IPSR_Type type;
4
  type.w = __get_IPSR();
5
6
  switch (type.b.ISR)
7
  {
8
    case 0:
9
    case 1:
10
    case 2:
11
      break;
12
    case 3:
13
    case 4:
14
    case 5:
15
    case 6:
16
      HandleFaultIrq(type.b.ISR,state);
17
      break;
18
    case 11:
19
      state = HandleSysCall(state);
20
      break;
21
    case 15:
22
      state = NextTask(state);
23
      break;
24
    case 24:
25
      PrintString("Interrupt\n");
26
      UartIRQ();
27
      break;
28
    default :
29
      HandleFaultIrq(type.b.ISR,state);
30
      break;
31
  }
32
33
  return state;
34
}

Also Interrupts bis der Nummer 6 oder kleiner sind Faults oder ähnlichem 
und haben die höchste Priorität. 11 sind System/Supervisor Aufrufe.
15 ist ein Takt für Threading und der Interrupt 24 ist allgemein Uart.

Mit der Methode HandleFaultIRQ wird zurzeit nur eine Debug-Ausgabe 
gemacht, also wenn ein andere Interrupt als Erwartet kommt sollte ich 
das mit bekommen.

Zu dem DMA allgemein:
>At the end of a transfer, the PDC channel sends status flags to its associated
>peripheral.
>These flags are visible in the peripheral status register
>(ENDRX, ENDTX,RXBUFF, and TXBUFE).
>Refer to Section 26.4.3 and to the associated peripheral user interface.

Der PDC sind sehr eng mit der eigentlichen Peripherie verknüpft.
Es gibt aber auch noch DMAC die sind deutlich komplexer, aber stehen 
auch nicht überall zur Verfügung.

Also die Bits werden immer im Status-Register gesetzt. Die Interrupts 
sind auch auf die Peripherie bezogen und werden darüber gehandelt.

Also das Ding macht mich noch mal Wahnsinnig. Es kann verdammt viel, 
aber fürs verstehen brauch man ein weile.

viele grüße
Lassi

Hinweis: Mann muss ein bissel aufpassen es gibt den PDC den ich benutze 
für DMA und es gibt den DMAC für DMA.

von Marco H. (damarco)


Lesenswert?

Warum steht doch alles in der Referenz.

Interessant wäre wie du den PDC konfigurierst ?

Diesen müssen einige Adressen etc. übergeben werden.

Der Start des Transfers erfolgt über die entsprechende Peripherie. Damit 
auch die Steuerung wenn die nächsten Daten transferiert werden müssen.

von Christoph L. (lassi)


Lesenswert?

Hallo,

@damarco:
mehr konfiguriere ich nicht und wenn ich diese Zeile weg lasse 
funktioniert das auch alles bestens:
1
UART->UART_IER =  UART_IER_ENDTX;

Wenn ich aber die Zeile drinne lasse, um den Interrupt zu empfangen. 
Geht nichts mehr und ein senden ist nicht mehr möglich. Ich kann auch 
nicht sagen ob ein Fault oder ähnliches passiert, weil ich nichts mehr 
senden kann.

Eine Möglichkeit ist das ein Interrupt immer und immer wieder ausgeführt 
wird, aber das zu überprüfen ist halt etwas schwieriger.

von Christoph L. (lassi)


Lesenswert?

Hallo,

ich hab gerade den letzten Beitrag geschrieben, aber ich dann hab ich 
mal die Theorie verfolgt mit dem Interrupt. Und ja es wirft immer den 
Interrupt so bald er aktiviert wird und ich bin davon ausgegangen das er 
erst kommt wenn man wenigsten den PDC aktiviert.

Das muss ich wohl irgendwo überlesen haben.

So für alle, wenn jemand anderes das Problem hat.
1
void UartSend(uint8_t* value,uint16_t size)
2
{
3
  while ((UART->UART_SR & UART_SR_TXBUFE)  == 0);
4
5
  UART->UART_TNPR = (uint32_t)value;
6
  UART->UART_TNCR = (uint32_t)size;
7
8
9
  UART->UART_PTCR |= UART_PTCR_TXTEN;
10
  UART->UART_CR |=  UART_CR_TXEN;
11
  UART->UART_IER |= UART_IER_ENDTX;
12
13
}
14
15
void UartIRQ(void)
16
{
17
  if (UART->UART_SR & UART_SR_ENDTX)
18
  {
19
    UART->UART_IDR |= UART_IDR_ENDTX;
20
  }
21
}

vielen Dank an alle und bis bald
Lassi

von Marco H. (damarco)


Lesenswert?

1
// Konfiguration für den PDC
2
UART->UART_TNPR = (uint32_t)value;
3
UART->UART_TNCR = (uint32_t)size;
4
UART->UART_PTCR |= UART_PTCR_TXTEN;
5
6
// startet den Transfer ! 
7
UART->UART_CR |=  UART_CR_TXEN;
8
9
//setzt aktiviert den Interrupt obwohl der Transfer schon läuft
10
UART->UART_IER |= UART_IER_ENDTX;

Vielleicht solltest du die Reihenfolge nochmal überdenken

Ich würde den Interrupt in der ISR auch wieder deaktivieren.

Wird das UART_SR nicht gelöscht wenn man es ausließt ?

von Eric B. (beric)


Lesenswert?

Christoph L. schrieb:
> case 24:
>       PrintString("Interrupt\n");
>       UartIRQ();
>       break;
>     default :
>       HandleFaultIrq(type.b.ISR,state);
>       break;
...
>Interrupt 24 ist allgemein Uart.
...
> Mit der Methode HandleFaultIRQ wird zurzeit nur eine Debug-Ausgabe
> gemacht,

Und wo werden die Ausgaben von PrintString und HandleFaultIrq 
hingeleitet? An den UART? :-)

von Christoph L. (lassi)


Lesenswert?

Hallo,

@damarco
Ja die Reihenfolge kann sein das die bei kleinen Datenmengen nicht 
passt.
Ich hab das jetzt mit 6 Byte und Baudrate von 115200 getestet hab.

Wichtig ist nur das TNCR bzw. TCR etwas ungleich 0 drinnen steht.
Der Interrupt wird eignetlich UartIRQ wieder deaktiviert.

Die IER (Interrupt Enable Register) aktivieren den Interrupt und zum 
deaktivieren muss man in das IDR (Interrupt Disable Register) schreiben.
Find ich schöner, weil man dann nicht so sehr mit Bitmanipulationen 
arbeiten muss, aber das ist wohl Geschmackssache.

Ach und soweit ich weiß werden die Statusregister nicht gelöscht beim 
lesen, aber ich lasse mich gerne korrigieren. Das 1400 Seiten Manual hab 
ich noch nicht ganz durch :)

@Beric
Ja die werden direkt in das UART_THR-Register geschrieben dort geht es 
noch nicht über dem PDC, weil ich das Problem hab das der PDC nicht aus 
dem Flash Daten lesen kann und da müssen die Ausgaben erst in den RAM. 
Ich bin deshalb gerade beim Umbau der ganzen Architektur, weil ich 
möchte nicht das ganze im Interrupt machen sondern in eigenen Task ...

Das ganze hat wohl mit dem NVIC zutun, wenn der aktiviert wird geht's 
wohl los ob man will oder nicht.

von Eric B. (beric)


Lesenswert?

Christoph L. schrieb:
> @Beric
> Ja die werden direkt in das UART_THR-Register geschrieben

...und dannn wird, sobald das erste Byte von PrintString 
("Interrupt!") verschickt wurde, ein Interrupt ausgelöst. Die 
Interruptroutine wird aufgerufen und macht PrintString ("Interrupt!"). 
Sobald davon das 'I' verschickt worden ist, kommt ein Interrupt usw usw?

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Eric B. schrieb:
> Christoph L. schrieb:
>> @Beric
>> Ja die werden direkt in das UART_THR-Register geschrieben
>
> ...und dannn wird, sobald das erste Byte von /PrintString/
> ("Interrupt!") verschickt wurde, ein Interrupt ausgelöst. Die
> Interruptroutine wird aufgerufen und macht PrintString ("Interrupt!").
> Sobald davon das 'I' verschickt worden ist, kommt ein Interrupt usw usw?

Das ist die "theory of operation." Deswegen muß der Tx Interrupt Handler 
darüber Buch führen, wieviele Zeichen noch ausstehen, und den Sonderfall 
"letztes Zeichen gesendet" anders behandeln. In der Regel wird hier (bei 
synchronem Transmit) eine Sempahore signalisiert, auf die die Task, die 
den Output macht, wartet. Hier gibt es jede Menge Möglichkeiten, Dinge 
(falsch oder richtig) Anders zu machen; z.B. kann es Fälle geben (Full 
Duplex Protokoll), in denen die sendende task nicht wartet, bis der 
Output durch ist und in der Zwischenzeit weitere Zeichen hinten anhängt, 
das muß dann sauber synchronisiert werden.

Beim printf() wird aber in der Regel die __write() callback Funktion auf 
den UART umgleitet, und die ist synchron.

Es gibt auch eine Variante dieser Implementation, bei der die task, die 
den printf initiiert, nicht selber das erste Zeichen sendet, sondern nur 
einen Tx Interrupt forciert, und der sendet eben das erste Zeichen statt 
dem Zweiten.

Aber der Thread ging ja ursprünglich über Tx und DMA, und dort ist es ja 
so, daß der DMA Engine ein Zeiger auf den String und die Länge übergeben 
wird, und DMA macht das Ganze dann völlig autark und generiert erst dann 
einen Interrupt, wenn der Transmit komplett durch ist.

von Eric B. (beric)


Lesenswert?

Ruediger A. schrieb:

> Das ist die "theory of operation." Deswegen muß der Tx Interrupt Handler
> darüber Buch führen, wieviele Zeichen noch ausstehen, und den Sonderfall
> "letztes Zeichen gesendet" anders behandeln.

Das hilft dir aber nichts wenn der Interrupthandler selber immer wieder 
neue Zeichen zum verschicken hinzufügt!

von Christoph L. (lassi)


Lesenswert?

Hallo:

@Beric:
>Die Interruptroutine wird aufgerufen und macht PrintString ("Interrupt!").
>Sobald davon das 'I' verschickt worden ist, kommt ein Interrupt usw usw?

Ja und nein. Ja es könnte ein Interrupt kommen, aber nein es kommt 
keiner, weil ich den Interrupt deaktiviert habe.

@Rac:
Aber ich muss zugeben das ich gerade das Problem der Synchronisierung 
habe. Weil ich brauch wirklich ein Speicherbereich im Ram und der muss 
irgendwann mal freigegeben werden. Das heißt gerade das ich ein Task 
anhalten muss bis der Sendevorgang von dem Task fertig ist.

viele grüße
Lassi

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Eric B. schrieb:
> Ruediger A. schrieb:
>
>> Das ist die "theory of operation." Deswegen muß der Tx Interrupt Handler
>> darüber Buch führen, wieviele Zeichen noch ausstehen, und den Sonderfall
>> "letztes Zeichen gesendet" anders behandeln.
>
> Das hilft dir aber nichts wenn der Interrupthandler selber immer wieder
> neue Zeichen zum verschicken hinzufügt!

Hä? Verstehe ich nicht, deine Anmerkung.

Der Pseudocode ist (variante 2):

Applikation:

{
   Hinterlege String und Stringlänge in Struktur;
   Triggere Tx Interrupt;
   <optional: Warte auf ISR Signal, s.o.>
}

Tx ISR:
{
    wenn Stringlänge = 0
    {
        // nichts tun, weil keine Zeichen mehr zu senden sind
        <optional: Signalisiere Applikation End Of Transmission>
    }
    else
    {
        <sende nächstes Zeichen> // das triggert den nächsten ISR
        Stringlänge -= 1;
    }
}

Was soll daran nicht funktionieren oder mir nichts helfen? Das ist 
Standard 0815 Code in Transmittern, der in Hunderten von Treibern seit 
Jahrzehnten funktioniert.

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.