Forum: Mikrocontroller und Digitale Elektronik SPI Slave mit DMA auf XMEGA128A1


von Stefan B. (stb_haag)


Lesenswert?

Hallo Leute,

ich möchte einen Xmega128A1 als SPI Slave betreiben. Es sollen 16 Byte 
in Folge in den Controller geschoben werden. Wenn ich den SPI Port per 
Interrupt betreibe, funktioniert es, d.h. ich bekomme 16 Interrupts und 
verarbeite die Bytes einzeln.

Nun möchte ich DMA dafür benutzen und ich hadere mit dem für meinen 
Geschmack sehr knappen Manual. Ich nehme an, der SPI Port ist wie zuvor 
zu initialisieren, nur der Interrupt ist disabeled. Die Einstellungen 
des DMA Controllers kann ich nur intuitiv vornehmen. Leider gelingt es 
mir nicht, eine Beziehung zwischen Manual und meiner Anwendung 
herzustellen. Ich hätte gerne nach Empfang der 16 Bytes einen Interrupt, 
um diese abzuholen. In dieser ISR lasse ich erst mal ein Portbit 
toggeln, um auf dem Oszi das Timing relativ zum SPI Signal zu prüfen. 
Ergebnis: die ISR wird alle 1.5µs aufgerufen.

Hat jemand eine Idee?

Viele Grüße
Stefan


Hier der Code:

//---------------------------------------------------------------------- 
------------------------------------------
  SPIC.CTRL = (0<<SPI_CLK2X_bp)
        | (1<<SPI_ENABLE_bp)
        | (1<<SPI_DORD_bp)
        | (0<<SPI_MASTER_bp)
        | SPI_MODE_0_gc;

  SPIC.INTCTRL = SPI_INTLVL_OFF_gc;



  //---------------------------------------------------------------------- 
------------------------------------------
  // init dma controller

  DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_SPIC_gc;

  DMA.CH0.TRFCNT = 16;     // Transfer Count Register
  DMA.CH0.REPCNT = 0;      // Repeat Count Register

  DMA.CH0.SRCADDR0 = ((uint16_t) &SPIC.DATA)&0xff;    // 24 Bit Source 
Adress
  DMA.CH0.SRCADDR1 = (((uint16_t) &SPIC.DATA)>>8)&0xff;  // 24 Bit 
Source Adress
  DMA.CH0.SRCADDR2 = 0;                  // 24 Bit Source Adress

  DMA.CH0.DESTADDR0 = ((uint16_t) data)&0xff;        // 24 Bit Source 
Adress
  DMA.CH0.DESTADDR1 = (((uint16_t) data)>>8)&0xff;    // 24 Bit Source 
Adress
  DMA.CH0.DESTADDR2 = 0;                  // 24 Bit Source Adress


  DMA.CH0.ADDRCTRL = DMA_CH_SRCRELOAD_NONE_gc      // Channel Source 
Address Reload
           | DMA_CH_SRCDIR_FIXED_gc      // Channel Source Address Mode
           | DMA_CH_DESTRELOAD_BLOCK_gc    // Channel Destination 
Address Reload
           | DMA_CH_DESTDIR_INC_gc;      // Channel Destination Address 
Mode

  DMA.INTFLAGS = (1<<DMA_CH3ERRIF_bp)      // Channel 3 Block Transfer 
Error Interrupt Flag
         | (1<<DMA_CH2ERRIF_bp)       // Channel 2 Block Transfer Error 
Interrupt Flag
         | (1<<DMA_CH1ERRIF_bp)      // Channel 1 Block Transfer Error 
Interrupt Flag
         | (1<<DMA_CH0ERRIF_bp)      // Channel 0 Block Transfer Error 
Interrupt Flag
         | (1<<DMA_CH3TRNIF_bp)      // Channel 3 Transaction Complete 
Interrupt Flag
         | (1<<DMA_CH2TRNIF_bp)      // Channel 2 Transaction Complete 
Interrupt Flag
         | (1<<DMA_CH1TRNIF_bp)      // Channel 1 Transaction Complete 
Interrupt Flag
         | (1<<DMA_CH0TRNIF_bp);    // Channel 0 Transaction Complete 
Interrupt Flag

  DMA.CH0.CTRLB = (0<<DMA_CH_CHBUSY_bp)    // Block Transfer Busy
          | (0<<DMA_CH_CHPEND_bp)      // Block Transfer Pending
          | (0<<DMA_CH_ERRIF_bp)      // Block Transfer Error Interrupt 
Flag
          | (0<<DMA_CH_TRNIF_bp)      // Transaction Complete Interrup 
Flag
          | DMA_CH_ERRINTLVL_OFF_gc    // Transfer Error Interrupt Level
          | DMA_CH_TRNINTLVL_MED_gc;  // Transaction Complete Interrupt 
Level

  DMA.CH0.CTRLA = (1<<DMA_CH_ENABLE_bp)    // Channel Enable bit
           | (0<<DMA_CH_RESET_bp)    // Channel Software
           | (1<<DMA_CH_REPEAT_bp)    // Channel Repeat Mode
            | (0<<DMA_CH_TRFREQ_bp)    // Channel Transfer Request
            | (0<<DMA_CH_SINGLE_bp)    // Channel Single Shot Data 
Transfer
            | DMA_CH_BURSTLEN_1BYTE_gc;  // Channel Transfer Mode

  DMA.CH1.CTRLA = 0;
  DMA.CH2.CTRLA = 0;
  DMA.CH3.CTRLA = 0;

  DMA.CTRL = (1<<DMA_CH_ENABLE_bp)      // 1 = dma enable
       | (0<<DMA_CH_RESET_bp)        // 1 = reset, disable first, then 
reset
       | (0<<DMA_DBUFMODE_gp)        // double buffer
       | (0<<DMA_PRIMODE_gp);        // priority mode


//---------------------------------------------------------------------- 
-----------------------------------------------
ISR( DMA_CH0_vect )
{
  PORTC.OUTTGL = 0x01;
}

von Heiko V. (xmegaman)


Lesenswert?

Hallo Stefan,

versuchs mal mit 1<<DMA_CH_SINGLE_bp !

Das Problem liegt darin, dass der DMA-Controller normalerweise nach 
jedem BLOCK-Transfer auf einen neuen Trigger wartet (Stichwort 
Handshake). Das heißt, er versucht TRFCNT(=16) mal SO SCHNELL WIE 
MÖGLICH (getaktet mit CLKper) Daten zu übertragen:
Bei angenommenen 10MHz CLKper ergibt das bei 16 Zugriffen deine 1,5us.

Da aber quasi jede Peripherie-Einheit langsamer arbeitet (und erst recht 
die seriellen Schnittstellen), muss der DMA-Controller dazu gezwungen 
werden, jedes Mal auf den Trigger zu warten. Und das geht paradoxerweise 
mit dem Singleshot-Bit. Jetzt wird nach jedem BURST gehandshaked. Da die 
SPI nur ein Byte zur Zeit verarbeiten kann, muss 1-Byte-Burst 
eingestellt sein (ist ja auch schon der Fall). Ohne dieses 
Singleshot-Verfahren wäre der DMA-Contoller übrigens so gut wie nutzlos.

Wenn man es einmal hinbekommen hat, findet man auch die Stelle im 
Datenblatt, wo das beschrieben ist. Ich gebe dir aber auf jeden Fall 
recht, dass dieses Setup viel deutlicher (am besten mit einem Beispiel) 
im Datenblatt herausgestellt werden müsste. Ich habe auch recht lange 
daran geknobelt, bis es geklappt hat.

Gruß

Xmegaman

von Heiko V. (xmegaman)


Lesenswert?

Bei weiterem Datenblattstudium bin ich auf folgende Zeile gestoßen:

When repeat mode is enabled,
/the start of transfer of the next block does not require a transfer 
trigger/.
It will start as soon as the previous block is done.

(Off-topic: Wer gibt mir Nachhilfe im Kursiv-Schreiben ?)

Ich befürchte, du musst dir den Repeat-Mode abschminken :-(.
Aber da du ja sowieso nach der Transaction einen Interrupt bekommst, 
kannst du den DMA-Controller ja auch dort wieder scharf machen...

von Stefan B. (stb_haag)


Lesenswert?

Hallo Heiko,

besten Dank für deine Hilfe.

In der Zwischenzeit hatte ich noch herausgefunden, dass ich in der ISR 
die DMA.INTFLAGS löschen muss. Interessanterweise genügt es, die Flags 
zu lesen. Mit dieser Massnahme bekommen ich einige Interrupts parallel 
zu den SPI-Daten.

Deine Vorschläge hab ich eingebaut und jetzt bekomme ich einen Interrupt 
direkt nach Ende der SPI-Übertragung, so wie es sein sollte.

Leider ist das noch nicht alles. Die Daten werden nicht richtig in den 
Vektor data übernommen. Die 16 Byte werden von meiner Software als 8 
Worte interpretiert. In den ersten 4 Worten stehen falsche Werte, die 
letzten 4 Werte sind 0.

Mir ist der Zusammenhang der folgenden Werte nicht richtig klar, vermute 
aber, dass hier das Problem liegt.

DMA.CH0.TRFCNT = 16;
DMA.CH0.REPCNT = 0;
DMA.CH0.CTRLA |= DMA_CH_BURSTLEN_1BYTE_gc;
DMA.CH0.ADDRCTRL |= DMA_CH_SRCRELOAD_NONE_gc
                 | DMA_CH_SRCDIR_FIXED_gc
                 | DMA_CH_DESTRELOAD_BLOCK_gc
                 | DMA_CH_DESTDIR_INC_gc;

Viele Grüße
Stefan

von Heiko V. (xmegaman)


Lesenswert?

Hallo Stefan,

evtl. liegts am DMA_CH_DESTRELOAD_BLOCK_gc. Stell den mal auf 
DMA_CH_DESTRELOAD_TRANSACTION_gc um.
Source müsste richtig eingestellt sein.

Außerdem solltest du es erstmal ohne REPEAT probieren (s. meinen 2. 
Post).
-> 0<<DMA_CH_REPEAT_bp!

Wenns ohne REPEAT läuft, kannst du ja weier probieren.

Gruß

von Stefan B. (stb_haag)


Lesenswert?

Hallo Heiko,

DMA_CH_DESTRELOAD_TRANSACTION_gc hatte ich bereits probiert. Wie gesagt, 
auf dem Oszi sah es gut aus (ISR wird richtig aufgerufen), aber die 
Daten waren korrupt.

Es dauerte noch ein wenig, bis ich den Fehler fand. Ich suchte nur in 
der DMA-Funktion, und übersah den Fehler in der Definition der 
Variablen. Beim DMA werden Bytes transportiert und ich hatte einen 
UINT16 Vektor benutzt.

Kurzum, deine Lösung funktioniert jetzt. Der Vollständigkeit halber ist 
der Code unten angehängt (die Datenverarbeitung in der ISR fehlt noch).

Wie ist eigentlich die Synchronisierung sichergestellt? Landet das erste 
Byte des SPI-Protokolls immer in data[0]? Oder könnten sich die Daten 
verschieben? Das SPI-Enable bleibt während er 16*8 Bit auf low, aber ich 
befürchte, davon weiß der DMA-Controller nichts.

Viele Grüße
Stefan



====================================================================
int main( void )
{
  UINT8 n;
  UINT16 m = 0;

  InitPorts();
  InitClock();
  InitUartRs232();

  // init SPI
  PORTC.DIR = 0x41;
  SPIC.CTRL = (0<<SPI_CLK2X_bp)
      | (1<<SPI_ENABLE_bp)
      | (1<<SPI_DORD_bp)
      | (0<<SPI_MASTER_bp)
      | SPI_MODE_0_gc;

  SPIC.INTCTRL = SPI_INTLVL_OFF_gc;

  //------------------------------------------------------------------
  // init dma controller
  DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_SPIC_gc;

  DMA.CH0.TRFCNT = 16;    // Transfer Count Register
  DMA.CH0.REPCNT = 0;      // Repeat Count Register

  DMA.CH0.SRCADDR0 = ( (uint16_t) &SPIC.DATA) & 0xff;
  DMA.CH0.SRCADDR1 = (((uint16_t) &SPIC.DATA)>>8) & 0xff;
  DMA.CH0.SRCADDR2 = 0;

  DMA.CH0.DESTADDR0 = ( (uint16_t) data) & 0xff;
  DMA.CH0.DESTADDR1 = (((uint16_t) data)>>8) & 0xff;
  DMA.CH0.DESTADDR2 = 0;

  DMA.CH0.ADDRCTRL = DMA_CH_SRCRELOAD_NONE_gc
       | DMA_CH_SRCDIR_FIXED_gc
       | DMA_CH_DESTRELOAD_TRANSACTION_gc
       | DMA_CH_DESTDIR_INC_gc;

  DMA.CH0.CTRLB = DMA_CH_ERRINTLVL_OFF_gc
          | DMA_CH_TRNINTLVL_MED_gc;

  DMA.CH0.CTRLA = (1<<DMA_CH_ENABLE_bp)
                | (0<<DMA_CH_RESET_bp)
                | (0<<DMA_CH_REPEAT_bp)
                | (0<<DMA_CH_TRFREQ_bp)
                | (1<<DMA_CH_SINGLE_bp)
                | DMA_CH_BURSTLEN_1BYTE_gc;

  DMA.CTRL = (1<<DMA_CH_ENABLE_bp)
     | (0<<DMA_CH_RESET_bp)
     | (0<<DMA_DBUFMODE_gp)
     | (0<<DMA_PRIMODE_gp);

  //---------------------------------------------------------------------- 
-
  // enable interrupts
  PMIC.CTRL = (0<<PMIC_RREN_bp)
      | (0<<PMIC_IVSEL_bp)
      | (0<<PMIC_HILVLEN_bp)
      | (1<<PMIC_MEDLVLEN_bp)
      | (1<<PMIC_LOLVLEN_bp);
  sei();


  //---------------------------------------------------------------------
  // endless loop
  while( 1 )
  {
        p = (UINT16 *) data;
        for( n = 0; n < 8; n++ )
  {
    sprintf(str, "%5u  ", p[n] );
    UartRs232Print( str );
  }
  sprintf( str, "\r");
  UartRs232Print( str );
  PORTQ.OUTTGL = 0x01;
  _delay_ms(250);
  }

}



//---------------------------------------------------------------------- 
-
ISR( DMA_CH0_vect )
{
  PORTC.OUTTGL = 0x01;

  DMA.INTFLAGS |= (1<<DMA_CH0TRNIF_bp);
  DMA.CH0.CTRLA |= (1<<DMA_CH_ENABLE_bp);
}

von Heiko V. (xmegaman)


Lesenswert?

Freut mich, dass es jetzt läuft. DMA ist schon eine feine Sache ;-).

Stefan Brandhoff schrieb:

> Wie ist eigentlich die Synchronisierung sichergestellt? Landet das erste
> Byte des SPI-Protokolls immer in data[0]? Oder könnten sich die Daten
> verschieben? Das SPI-Enable bleibt während er 16*8 Bit auf low, aber ich
> befürchte, davon weiß der DMA-Controller nichts.

Meinst du mit SPI-Enable das SS-Signal (slave select) ? Von dem weiß der 
DMA-Controller in der Tat nichts. Du solltest deshalb evtl. vor dem 1. 
Start sichergehen, dass SS nicht aktiv ist, und falls doch, einfach 
warten (d. h. SS pollen), bis der Master fertig ist. Anderenfalls kann 
es passieren, dass der Vorgang mitten im Daten-Frame startet und sich 
nie wieder richtig aufsynchronisiert.

Gruß

Xmegaman

von Stefan B. (stb_haag)


Lesenswert?

Ja genau, ich meine das Slave Select.

Ich habe mir eine while Schleife eingebaut, die auf wartet bis SS auf 
high geht. Einmal direkt vor der Initialisierung und dann prüfe ich in 
der ISR das SS. Falls dort ein low anliegt, lasse ich die 
Initialisierung durchlaufen. Das funktioniert, führt aber zu einer 
Endlosschleife, wenn am SPI Port kein Signal anliegt. Da brauche ich 
noch eine Fehlerbehandlung.

Alle Tests liefen bisher positiv.

Nochmals besten Dank für die Hilfe.

Viele Grüße
Stefan

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.