ATmega809 - SPI empfängt 1 Byte und dann nie wieder

OP #6084857
Lesenswert?

Schönen heilig Abend,

ich bin gerade dabei, das SP-Interface des ATmega809 zu initialisieren.
Der ATmega809 soll als Slave fungieren, so sieht meine Initialisierung 
aus:
1
void initSPI() {
2
  /* Set alternative SPI pins */
3
  PORTMUX.TWISPIROUTEA |= PORTMUX_SPI0_ALT1_gc;
4
  /* Enable Receive Interrupt */
5
  SPI0.INTCTRL |= SPI_IE_bm;
6
  /* Enable SPI */
7
  SPI0.CTRLA |= SPI_ENABLE_bm;
8
}

in einem anderen Code-Abschnitt hab ich den MISO-Pin entsprechend als 
Ausgang definiert:
1
  self->SPI.PORT = &PORTC;
2
  self->SPI.MOSI = PIN0_bm;
3
  self->SPI.MISO = PIN1_bm;
4
  self->SPI.SCK = PIN2_bm;
5
  self->SPI.SS = PIN3_bm;
6
  
7
  self->SPI.PORT->DIR |= self->SPI.MISO;

So sieht die ISR aus:
1
ISR(SPI0_INT_vect) {
2
  const uint8_t data = SPI0.DATA;
3
  dramHandler.buffer.push(&dramHandler.buffer, data); /* <-- Breakpoint */
4
  dramHandler.hasPendingBufferUpdate = true;
5
}

Das reinkommende SPI-Signal hab ich mit einem Logic-Analyzer gemessen 
(siehe Bild) - sieht meiner Meinung nach gut aus.

Nun ist das Problem, dass ich beim Debuggen mit dem Atmel-ICE mit 
Breakpoint auf die 2. Zeile der ISR nur genau 1x einen Interrupt 
bekomme, wenn ich danach das Program weiterlaufen lasse, kommt kein 
Interrupt mehr.

Erst wenn ich mich wieder vom Device trenne, es neu programmiere (inkl. 
Reset) und dann wieder im Debug-Modus bin, kommt der Interrupt genau 1x.

Ganz offensichtlich hab ich die SPI-Peripherie genauso wenig verstanden 
wie den TCA0 - mag mir hier jemand auf die Sprünge helfen?

Frohes Fest

Edit: Um sicher zu gehen, dass der ATmega nirgends stecken bleibt, hab 
ich in der Endlosschleife ein einfaches "asm("nop")" mit Breakpoint 
versehen, wo der ATmega dann auch fleißig reinspringt. Laufen tut er 
also nach dem Interrupt weiterhin.
Angehängte Dateien:
Gast #6084880
Lesenswert?

Moin Max,

ich kenn mich zwar mit ATmega nicht aus, aber bei vielen CPUs wenn ich 
mich richtig erinnere muss man am Ende des IRQs das Interruptflag 
löschen (bei manchen kann man auch einstellen das es automatisch 
gelöscht wird).
Tut man das nicht wird der IRQ nicht mehr ausgelöst. Nur so als Idee.

Viele Grüße
egon
#6084899
Lesenswert?

Hey Max.

Wer ist der Master?

Wenn der in seinem read(...) bzw. write(...) nur 1 Byte sendet/liest, 
dann taktet er auch nur 1 Byte...

SCK geht ja vom Master aus.

Wenn du mit dem Logic Analyser nichts siehst (außer dem 1 Byte), dann 
liegt ja auf der Datenleitung auch nichts...?

Oder verstehe ich deine Problemstellung falsch?
OP #6085010
Lesenswert?

Frank K. schrieb:
> Bei SPI empfängst Du ja nicht nur, sondern Du musst für jedes Byte, was
> Du sendest, auch eines empfangen bzw für jedes empfangene Byte auch
> eines senden, und sei es ein 0-Byte.

Achso - tatsächlich? Das war mir nicht bewusst.
Da muss ich mal blöd fragen: Wie implementiert man denn dann ein auf 
Nachrichten basiertes Protokoll?

Also ich hatte die Idee, dass ein Master per SPI den ATmega nach Daten 
fragen kann. Dazu hab ich mir zwei ganz einfache Nachrichten ausgedacht, 
die jeweils aus 4 bzw. 5 Byte bestehen.
Der ATmega hat aber erst nach den 4 bzw. 5 empfangenen Bytes die 
vollständigen Informationen um die Anfrage adäquat beantworten zu 
können, d.h. davor macht es keinen Sinn dem Master zu antworten?

Marc V. schrieb:
> Master sendet 1 Byte, MEGA809 empfängt 1 Byte, wo liegt dein Problem?

Der Master sendet kontinuierlich 1 Byte jede Sekunde. Der Interrupt wird 
allerdings nur beim aller ersten Byte im ATmega809 (der das Byte 
empfängt) erzeugt.
#6085029
Lesenswert?

Max M. schrieb:
> Frank K. schrieb:
>> Bei SPI empfängst Du ja nicht nur, sondern Du musst für jedes Byte, was
>> Du sendest, auch eines empfangen bzw für jedes empfangene Byte auch
>> eines senden, und sei es ein 0-Byte.
>
> Achso - tatsächlich? Das war mir nicht bewusst.
> Da muss ich mal blöd fragen: Wie implementiert man denn dann ein auf
> Nachrichten basiertes Protokoll?
>
> Also ich hatte die Idee, dass ein Master per SPI den ATmega nach Daten
> fragen kann. Dazu hab ich mir zwei ganz einfache Nachrichten ausgedacht,
> die jeweils aus 4 bzw. 5 Byte bestehen.
> Der ATmega hat aber erst nach den 4 bzw. 5 empfangenen Bytes die
> vollständigen Informationen um die Anfrage adäquat beantworten zu
> können, d.h. davor macht es keinen Sinn dem Master zu antworten?

Schau Dir mal das Datenblatt eines SPI-ADCs an.

https://ww1.microchip.com/downloads/en/DeviceDoc/21298c.pdf

Hardware-SPI-Slaves schalten ihr MISO erst ein, wenn sie tatsächlich 
Nutzdaten haben, aber Du musst notfalls irgendwelche Dummy-Bytes senden.

fchk
Gast #6085071
Lesenswert?

Max M. schrieb:
> Da muss ich mal blöd fragen: Wie implementiert man denn dann ein auf
> Nachrichten basiertes Protokoll?

Zb mit multimaster, was die avrs können. Der logische master schickt 
Kommandos an die logischen slaves, diese antworten dann im Nachgang.

Spi mäßig ist immer der sendende der master.
Gast #6085211
Lesenswert?

> Laut Datenblatt wird das mit dem Lesen von SPIn.DATA mit gelöscht.

Den Satz
  "IF is cleared by hardware when executing the corresponding interrupt 
vector"
  kann ich nicht bestätigen, bei meinem Atmega4809 funktioniert nur die 
zweite Methode
  "Alternatively, the IF flag can be cleared by first reading the 
SPIn.INTFLAGS register when IF is set, and then accessing the SPIn.DATA 
register".
OP #6085268
Lesenswert?

Danke für eure Antworten und schönen 1. Weihnachtsfeiertag,

habs nun so gemacht:
1
ISR(SPI0_INT_vect) {
2
  if(SPI0.INTFLAGS & SPI_IF_bm) {
3
    const uint8_t data = SPI0.DATA;
4
    dramHandler.buffer.push(&dramHandler.buffer, data);
5
    dramHandler.hasPendingBufferUpdate = true;
6
    SPI0.DATA = 0x00; /* write dummy bytes */
7
  }
8
}

Hat allerdings am Verhalten nichts geändert. Probeweise hab ich nun auch 
noch einen Breakpoint auf den TCA0-Interrupt gesetzt, der alle 30ms 
laufen müsste, der wird nun auch nur noch genau 1x aufgerufen (d.h. 
SPI0_INT_vect und TCA0_CMP0_vect laufen jeweils 1x).

Sobald ich die initSPI()-Funktion nicht mehr aufrufe, läuft der Timer 
wieder wie gewohnt alle 30ms (logischerweise der SPI-Interrupt dafür 
nicht, aber das ist klar).

Hab auch mal ein *sei()* an das Ende der SPI-ISR getan aber das hat auch 
nichts bewirkt.
Gast #6085304
Lesenswert?

S. Landolt schrieb:
> Auch Ihnen frohe Festtage!
>
> Ich habe von C kaum Ahnung, was bewirkt dieses const in
>
>> const uint8_t data = SPI0.DATA;
> ?

Const sagt: Read Only

Die Variable namens data darf ihren Wert nicht ändern.
Versuchst du es, hauts dir der Compiler um die Ohren.

Auch erlaubt es weiter gehende Optimierungen.
#6085316
Lesenswert?

SPI-Slave auf einem MC ist ein pain in the ass.
Man muß erstmal ein Protokoll aufsetzen, um dem Slave zu sagen, daß und 
was er senden soll und ihm dann Zeit lassen, den Sendepuffer zu füllen. 
Der Slave muß dazu zwingend einen Sendepuffer oder DMA haben, sonst 
kriegt man Timingprobleme, da SPI kein Handshake unterstützt. Zumindest 
die älteren ATmega/tiny sind also nicht geeignet.
I2C oder UART sind deutlich besser geeignet, am besten ist natürlich CAN 
für eine Verbindung zwischen mehreren MCs.
Das SPI benutze ich ausschließlich als Master zu ADCs, DACs usw.
Gast #6085346
Lesenswert?

S. Landolt schrieb:
> Und weshalb wird dann so etwas der veränderliche Wert aus
> SPI0.DATA
> zugewiesen?
> (Man entschuldige die vielleicht dumme Frage eines
> Assemblerprogrammierers)


Das ist sowas, wie ein Dialog mit dem Kompiler.
Assembler sind in der Hinsicht blöd.

Aber Kompilern kann man seine Wünsche mitteilen.

Hier:
Liebster Kompiler ich möchte mit dem Wert von SPI0.DATA unter dem Namen 
data arbeiten. Stelle bitte sicher, dass der Wert für diesen 
Geltungsbereich unveränderlich ist.

Wenn möglich, wirds gemacht.
Vielleicht anders, als du erwartest.
Wenn nicht möglich, haut er dir den Kram um die Ohren.
OP #6085366
Lesenswert?

Peter D. schrieb:
> Das SPI benutze ich ausschließlich als Master zu ADCs, DACs usw.

Okay, da schickst du aber vermutlich auch Befehle an die ICs und die 
schicken dir dann Daten zurück?

S. Landolt schrieb:
> Wie beim letzten Mal hier ein Assemblerprogramm für den ATmega4809,
> vielleicht hilft es ja beim Verständnis.

Danke.

Hat es einen konkreten Grund warum du hier virtuelle Ports verwendest?

Ich verwende SPI0 leider an anderen Pins, deswegen hab ich das Beispiel 
noch etwas abgeändert:
1
.include    "m809def.inc"
2
.def    tmp0    = r16
3

4
.org    0
5
    rjmp        reset
6

7
.org    SPI0_INT_vect
8
    lds     tmp0,SPI0_INTFLAGS
9
    lds     tmp0,SPI0_DATA
10
    inc     tmp0
11
    ori     tmp0,$20
12
    sts     SPI0_DATA,tmp0
13
    reti
14
;******************************************************
15

16
reset:
17
  ldi    tmp0,PORTMUX_SPI0_ALT1_gc
18
  sts    PORTMUX_TWISPIROUTEA,tmp0
19

20
  sbi    VPORTC_DIR,1    ;PIN1
21

22
    ldi     tmp0,SPI_IE_bm
23
    sts     SPI0_INTCTRL,tmp0
24

25
    ldi     tmp0,SPI_ENABLE_bm
26
    sts     SPI0_CTRLA,tmp0
27

28
    sei
29
main_loop:
30
  rjmp      main_loop

Der Interrupt wird allerdings nie angesprungen.
Gast #6085371
Lesenswert?

> Hat es einen konkreten Grund warum du hier virtuelle Ports verwendest?
Faulheit - es erlaubt die Verwendung von sbi.

> Der Interrupt wird allerdings nie angesprungen.
Tja, keine Ahnung, tut mir leid.
OP #6085386
Lesenswert?

Ahh tut mir Leid. Der Fehler war, dass die Funktionspointer des Puffers 
vom "dramHandler" nicht initialisiert waren. Normalerweise hätte ich 
hier erwartet, dass der Controller in einen exception-handler springt 
oder allgemein einfach "abstürzt", deswegen hab ich mir nichts böses 
gedacht, dass die Hauptschleife weiterhin durchlaufen wird.

Sorry wegen dem blöden Fehler meinerseits :(

Schöne Feiertage noch
Gast #6085387
Lesenswert?

Also, ich habe jetzt das von Ihnen modifizierte Programm wieder 
übernommen, die bei mir nötigen Änderungen, "4809" sowie das Addieren 
von $2000 bei den Adressen, durchgeführt und die Signale auf den unteren 
C-Port gelegt: bei mir läuft es.
#6085460
Lesenswert?

Max M. schrieb:
> Okay, da schickst du aber vermutlich auch Befehle an die ICs und die
> schicken dir dann Daten zurück?

Der große Unterschied zu einem MC ist jedoch, der ADC liefert mir die 
32Bit in einem Stück. Ich muß den Master keine Gedenkpausen einlegen 
lassen, bis der ADC das nächste Byte in das Senderegister geladen hat.
Ein MC kann jedoch in einem anderen Interrupt hängen und dann liefert 
mir das SPI Lottozahlen statt Daten. Aber selbst, wenn nicht, hat ein 
Slave ohne Sendepuffer/DMA nur 1/2 SPI-Clock Zeit, das nächste Byte 
bereit zu stellen.
Die MC-Lösung braucht mindestens ein Protokoll mit CRC, um zwischen 
Lottozahlen und Daten unterscheiden zu können.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren