Forum: Mikrocontroller und Digitale Elektronik ATmega809 - SPI empfängt 1 Byte und dann nie wieder


von Max M. (maxmicr)


Angehängte Dateien:

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.

: Bearbeitet durch User
von egon (Gast)


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

von Max M. (maxmicr)


Lesenswert?

Hi egon,

egon schrieb:
> muss man am Ende des IRQs das Interruptflag löschen

Laut Datenblatt wird das mit dem Lesen von SPIn.DATA mit gelöscht.
Hab's auch mal probeweise im INTFLAGS-Register gesetzt, aber hat nichts 
gebracht

von Adam P. (adamap)


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?

von Frank K. (fchk)


Lesenswert?

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.

fchk

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Max M. schrieb:
> Der ATmega809 soll als Slave fungieren,

 Und?
 Master sendet 1 Byte, MEGA809 empfängt 1 Byte, wo liegt dein Problem?

von Veit D. (devil-elec)


Lesenswert?


von Max M. (maxmicr)


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.

: Bearbeitet durch User
von Frank K. (fchk)


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

von Dunno.. (Gast)


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.

von S. Landolt (Gast)


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".

von Max M. (maxmicr)


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.

von S. Landolt (Gast)


Lesenswert?

Auch Ihnen frohe Festtage!

Ich habe von C kaum Ahnung, was bewirkt dieses const in

> const uint8_t data = SPI0.DATA;
?

von Einer K. (Gast)


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.

von S. Landolt (Gast)


Lesenswert?

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

von Peter D. (peda)


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.

von S. Landolt (Gast)


Angehängte Dateien:

Lesenswert?

an Max M.:
Wie beim letzten Mal hier ein Assemblerprogramm für den ATmega4809, 
vielleicht hilft es ja beim Verständnis.

von Veit D. (devil-elec)


Lesenswert?


von Einer K. (Gast)


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.

von S. Landolt (Gast)


Lesenswert?

Danke für die Erklärung.
Dann ist mir erstmal nicht klar, was bei Max M. schiefläuft; 
andererseits lassen die Programmbruchstücke an Aussagekraft zu wünschen 
übrig.

von BlaBla (Gast)


Lesenswert?

Vielleicht hat es damit was zu tun. Siehe Link um 13:18 Uhr 24.12.2019

Beitrag "ATmega809 Timer CMP Interrupt funktioniert nicht"

von Max M. (maxmicr)


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.

von S. Landolt (Gast)


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.

von Max M. (maxmicr)


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

von S. Landolt (Gast)


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.

von 1000V Dc (Gast)


Lesenswert?

Und wieder stolpern die Jungs über uralte Hardware Bugs der avrs... lest 
doch mal die erata

von Peter D. (peda)


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.

von 1000V Dc (Gast)


Lesenswert?

1000V Dc schrieb:
> Und wieder stolpern die Jungs über uralte Hardware Bugs der avrs... lest
> doch mal die erata

Von was genau redest du?

von Peter D. (peda)


Lesenswert?

Max M. schrieb:
> Der Fehler war, dass die Funktionspointer des Puffers
> vom "dramHandler" nicht initialisiert waren.

So ist das eben, der Fehler ist grundsätzlich im nicht geposteten Code.
Was soll das Rumgetue mit Code-Schnipselchen. Ist der Code so geheim, 
daß Du ihn nicht als compilierfähigen Anhang senden kannst?

von Max M. (maxmicr)


Lesenswert?

Peter D. schrieb:
> Ist der Code so geheim,
> daß Du ihn nicht als compilierfähigen Anhang senden kannst?

Ich dachte mein Code wäre fehlerfrei, deswegen. War etwas überheblich 
von mir. Hab mir auch gedacht, dass alles passt da der µC in der 
Mainloop weitergelaufen ist.

: Bearbeitet durch User
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.