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;
}
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
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...
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
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ß
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);
}
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.