Forum: Mikrocontroller und Digitale Elektronik Seltsames SPI-Verhalten Atmega328P


You were forwarded to this site from EmbDev.net. Back to EmbDev.net
von Sebastian W. (wangnick)



Lesenswert?

Liebe Leute,

seit drei Tagen suche ich einen Fehler in dem Eigenbau einer 
Lüftungssteuerung meiner Werkstatt und bin jetzt an einem Punkt wo mir 
die Ideen ausgehen. Anbei der Schaltplan und ein Foto zur Illustration.

Der Fehler zeigt sich daran dass das Anzeigebild auf dem angeschlossenen 
ST7735-Display nicht vom Portraitmodus in den Landschaftsmodus rotiert.

Ein Austausch der Ansteuerbibliothek hat nicht geholfen. Ein Wechsel des 
Display ebenfalls nicht. Das Problem tritt auch auf wenn das 
MCP2515-Modul entfernt wird.

Ich habe die SPI-Kommunikation zwischen uC und ST7735 untersucht. Die 
analogen Pegel sehen korrekt aus. Ich habe aber tatsächlich eine 
digitale Problemstelle gefunden. Die korrekte Sequenz zur Rotation wäre 
CS H->L, SPI 0x28, SPI 0x36, DC L->H, SPI 0x60, DC H->L, SPI 0x29, CS 
L->H.

In früheren Versuchen mit teils anderer Reihenfolge der Initialisierung 
hat die Rotation zwar funktioniert, es gab dann aber anscheinend an 
anderer Stelle in der SPI-Kommunikation Probleme.

Ein rudimentärer Nachbau auf dem Arbeitstisch mit einem Arduino Pro Mini 
funktioniert mit derselben Softwareversion tadellos, siehe Bild 
t5_garage01_st7735_ok.png. Beim Aufbau in der Werkstatt fehlt aber SPI 
0x36 (und die entsprechende Übertragungszeit), siehe Bild 
t5_garage01_st7735_pl.png.

Der entsprechende Assemblercode an dieser Stelle lautet (aus 
https://github.com/lexus2k/lcdgfx/blob/master/src/v2/lcd/st7735/lcd_st7735.inl, 
template <class I> void InterfaceST7735<I>::setRotation(uint8_t 
rotation):
1
    49c4:  88 e2         ldi  r24, 0x28  ; 40
2
    49c6:  0e 94 5a 0a   call  0x14b4  ; 0x14b4 <SPIClass::transfer(unsigned char)>
3
    49ca:  86 e3         ldi  r24, 0x36  ; 54
4
    49cc:  0e 94 5a 0a   call  0x14b4  ; 0x14b4 <SPIClass::transfer(unsigned char)>

transfer sieht wie folgt aus (aus SPI.cpp der aktuellen Arduino-IDE 
1.8.19):
1
  inline static uint8_t transfer(uint8_t data) {
2
    SPDR = data;
3
    14b4:  8e bd         out  0x2e, r24  ; 46
4
    asm volatile("nop");
5
    14b6:  00 00         nop
6
    while (!(SPSR & _BV(SPIF))) ; // wait
7
    14b8:  0d b4         in  r0, 0x2d  ; 45
8
    14ba:  07 fe         sbrs  r0, 7
9
    14bc:  fd cf         rjmp  .-6        ; 0x14b8 <SPIClass::transfer(unsigned char)+0x4>
10
    return SPDR;
11
    14be:  8e b5         in  r24, 0x2e  ; 46
12
  }
13
    14c0:  08 95         ret

Es scheint als ob beim Werkstattaufbau beim Aufruf von transfer(0x36) 
SPIF schon gesetzt wäre. Allerdings sollte das Lesen von SPDR beim 
vorherigen Aufruf von transfer(0x28) SPIF gelöscht haben.

Ich habe nun zwei Fragen. Erstens: Hat jemand eine Erklärung für dieses 
Verhalten. Zweitens: Hat jemand einen Vorschlag wo und wie ich 
weitersuchen soll?

LG, Sebastian

PS: PB2 (!SS) ist nach bestem Wissen und Gewissen als Output 
konfiguriert, da er als !CS für das MCP2515-Modul benutzt wird.

: Bearbeitet durch User
von Schlaumaier (Gast)


Lesenswert?

Hm . Vielleicht hilft dir der im Forum-Datenblatt verlinkter Teil.

Ist zwar nicht ganz der selbe MC aber ....

Beitrag "ATmega644PA und MCP2515 SPI Problem"

von Holger L. (max5v)


Lesenswert?

Wenn der Schaltplan so gezeichnet ist wie der Aufbau, Resetbeschaltung 
spendieren, Abblockkondensatoren einlöten und bei größeren 
Temperaturschwankungen lieber einen externen Taktgeber verwenden, die 
sind in der Regel deutlich präziser.

von Peter D. (peda)


Lesenswert?

Sebastian W. schrieb:
> PS: PB2 (!SS) ist nach bestem Wissen und Gewissen als Output
> konfiguriert, da er als !CS für das MCP2515-Modul benutzt wird.

Der Knackpunkt ist, ist das auch vor dem SPI-Init erfolgt!

von Sebastian W. (wangnick)


Lesenswert?

Peter D. schrieb:
> Der Knackpunkt ist, ist das auch vor dem SPI-Init erfolgt!

Guter Punkt! Da könnte der Hase im Pfeffer liegen!

LG, Sebastian

von Ron T. (rontem)


Lesenswert?

Sebastian W. schrieb:
> Da könnte der Hase im Pfeffer liegen!

Wieso? SPI-Init konfiguriert sich stets nur und alles was es braucht...

von Sebastian W. (wangnick)


Lesenswert?

Peter D. schrieb:
> er Knackpunkt ist, ist das auch vor dem SPI-Init erfolgt!

Das wars! Mille grazie, Peter. YMMD!

Manchmal sieht man den Wald vor lauter Bäumen nicht.

LG, Sebastian

: Bearbeitet durch User
von Holger L. (max5v)


Lesenswert?

Das ist merkwürdig!

Sebastian W. schrieb:
> Ein rudimentärer Nachbau auf dem Arbeitstisch mit einem Arduino Pro Mini
> funktioniert mit derselben Softwareversion tadellos, siehe Bild
> t5_garage01_st7735_ok.png. Beim Aufbau in der Werkstatt fehlt aber SPI
> 0x36 (und die entsprechende Übertragungszeit), siehe Bild
> t5_garage01_st7735_pl.png.

War bei dem "Arbeitstischaufbau" das MCP2515-Modul nicht angeschlossen?

von Sebastian W. (wangnick)


Lesenswert?

Holger L. schrieb:
> War bei dem "Arbeitstischaufbau" das MCP2515-Modul nicht angeschlossen?

Nein, auch nicht. Das Problem war, dass ich SPI.begin() vor der 
MCP2515-Initialisierung aufgerufen habe, aber nicht auch vor der 
ST7735-Initialisierung, und dass die ST7735-Initialisierung vor der 
MCP2515-Initialisierung stattfand. PB2 war also während der 
ST7735-Initialisierung (obwohl dafür nicht benutzt) als Input 
konfiguriert, und der in der Werkstatt schwankende Eingangspegel an der 
"Antenne" PB2, also !SS, hat dann sporadisch die SPI-Maschinerie des 
Atmega in den Slave-Modus versetzt ...

LG, Sebastian

von Sebastian W. (wangnick)



Lesenswert?

Sebastian W. schrieb:
> Peter D. schrieb:
>> er Knackpunkt ist, ist das auch vor dem SPI-Init erfolgt!
>
> Das wars! Mille grazie, Peter. YMMD!
> Manchmal sieht man den Wald vor lauter Bäumen nicht.

Leider zu früh gefreut. Es hat zwar so kurz funktioniert aber jetzt ist 
der Fehler trotz der Softwarekorrektur wieder da!

Ich habe in der Prozedur setRotation jetzt ein Portgewackel auf PD4 
eingebaut:
1
template <class I> void InterfaceST7735<I>::setRotation(uint8_t rotation)
2
{
3
    uint8_t ram_mode;
4
    if ( (rotation ^ m_rotation) & 0x01 )
5
    {
6
        m_base.swapDimensions();
7
        lcduint_t temp = m_offset_x;
8
        m_offset_x = m_offset_y;
9
        m_offset_y = temp;
10
    }
11
    m_rotation = (rotation & 0x03);
12
    this->start();
13
    PORTD |= (1<<PORTD4);
14
    setDataMode(0);
15
    PORTD &= ~(1<<PORTD4);
16
    this->send(0x28);
17
    PORTD |= (1<<PORTD4);
18
    this->send(0x36);
19
    PORTD &= ~(1<<PORTD4);
20
    setDataMode(1);
21
    PORTD |= (1<<PORTD4);
22
    switch ( m_rotation )
23
    {
24
        case 0: // 0 degree CW
25
            ram_mode = 0b00000000;
26
            break;
27
        case 1: // 90 degree CW
28
            ram_mode = 0b01100000;
29
            break;
30
        case 2: // 180 degree CW
31
            ram_mode = 0b11000000;
32
            break;
33
        default: // 270 degree CW
34
            ram_mode = 0b10100000;
35
            break;
36
    }
37
    this->send(ram_mode | m_rgb_bit);
38
    PORTD &= ~(1<<PORTD4);
39
    setDataMode(0);
40
    PORTD |= (1<<PORTD4);
41
    this->send(0x29);
42
    PORTD &= ~(1<<PORTD4);
43
    this->stop();
44
}

Und dann habe ich sowohl das CAN-Modul als auch das Display abgezogen 
und die Software laufen lassen ohne dass irgendetwas an den SPI-Ports 
angeschlossen ist. Das Ergebnis siehe Bild: Es wird jetzt 0x28 statt 
0x36 gesendet!

Was mir auffällt ist das DEBUG L->H geht obwohl das erste Byte 0x28 
immer noch gesendet wird ...

LG, Sebastian

von OMG (Gast)


Lesenswert?

Sebastian W. schrieb:
> Leider zu früh gefreut. Es hat zwar so kurz funktioniert aber jetzt ist
> der Fehler trotz der Softwarekorrektur wieder da!

An diesem Punkt solltest du einmal sorgfältig in dich
gehen und

- einen soliden Aufbau wählen.
- genügend Abblock-Kondensatoren vorsehen (laut Schaltplan
  fehlen am AVR alle Kondensatoren!)
- alle fliegenden Leitungen beseitigen da diese beliebig
  Störungen auffangen können. Wenn ich "Lüftungssteuerung "
  lese dann ist das allein schon ein Grund für Fehlfunktion.

So wie das jetzt aufgebaut ist kann das nicht gutgehen.

SCNR

von OMG (Gast)


Lesenswert?

Sebastian W. schrieb:
> Leider zu früh gefreut.

Nachdem ich sehe dass du hier schon fast 10 Jahre angemeldet bist
solltest du eigentlich hier im Forum genügend (Negativ-) Beispiele
an Aufbauten gesehen haben wie man es nicht macht. Dein Aufbau
gehört sicherlich dazu.

von Sebastian W. (wangnick)


Lesenswert?

OMG schrieb:

Also zunächst mal danke für die Rückmeldung. Allerdings:

OMG schrieb:
> - einen soliden Aufbau wählen.
Der Aufbau mit Streifenraster ist sehr solide.

OMG schrieb:
> - genügend Abblock-Kondensatoren vorsehen (laut Schaltplan
>   fehlen am AVR alle Kondensatoren!)
Jetzt nachgerüstet. Wie zu erwarten war (zum Zeitpunkt des Fehlers 
benötigte der uC nur Strom für winzigste interne Gateumladungen) keine 
Änderung.

OMG schrieb:
> - alle fliegenden Leitungen beseitigen da diese beliebig
>   Störungen auffangen können.
Fliegende Leitungen habe ich keine (Zuleitungen wohl).

OMG schrieb:
> Wenn ich "Lüftungssteuerung "
>   lese dann ist das allein schon ein Grund für Fehlfunktion.
> So wie das jetzt aufgebaut ist kann das nicht gutgehen.
Ist halt eine induktive Last.

Mir ist allerdings bei der Beantwortung deines Beitrags aufgefallen, das 
das Gate des IRLML6244 keinen Pulldown hat und also während der 
Initialisierung floatet, und auch noch einen 12k zu GND nachgerüstet.

Ich habe jetzt alle Leitungen nach außen (ausser der 15V-Zuleitung) 
gekappt. Der Fehler tritt immer noch auf!

Weitere Infos folgen ...

LG, Sebastian

: Bearbeitet durch User
von Sebastian W. (wangnick)


Lesenswert?

Liebe Leute,

ich habe höchstwahrscheinlich die Ursache für mein Problem gefunden.

Sebastian W. schrieb:
> Es scheint als ob beim Werkstattaufbau beim Aufruf von transfer(0x36)
> SPIF schon gesetzt wäre. Allerdings sollte das Lesen von SPDR beim
> vorherigen Aufruf von transfer(0x28) SPIF gelöscht haben.

Sebastian W. schrieb:
> Was mir auffällt ist das DEBUG L->H geht obwohl das erste Byte 0x28
> immer noch gesendet wird ...

Werkstattaufbau und Arbeitstischnachbau unterscheiden sich in einem 
wesentlichen Punkt: Während der Arduino Pro Mini auf dem Arbeitstisch 
den Arduino-Bootloader enthält ist beim Werkstattaufbau ein 
selbstgebauter CAN-Bootloader im Einsatz. Dieser CAN-Bootloader endet 
mit dem Senden einer ACK-Nachricht an den CAN-Bootserver. Dabei mache 
ich im CAN-Bootloader den Fehler am Ende SPDR nicht mehr zu lesen und 
also SPIF gesetzt zu lassen. Und es scheint so zu sein, dass danach SPCR 
= SPSR = 0 vor dem Sprung vom Bootloader zur Applikation zwar SPI 
deaktiviert, SPIF aber gesetzt bleibt!

Dann startet die Applikation, aktiviert SPI wieder und beginnt das 
Display zu initialisieren. SPDR wird geschrieben aber SPIF ist noch 
gesetzt. Also endet das anschliessende Warten auf SPIF sofort. Wird dann 
SPDR gelesen dann wird SPIF zwar gelöscht, aber man liest das bei einer 
vorherigen Übertragung erhaltene Byte. Sobald dann die aktuelle 
Übertragung fertig ist wird SPIF wieder gesetzt. Und dieser Zustand kann 
dann recht lange anhalten.

Wenn in diesem Zustand dann aber SPDR geschrieben wird während die 
frühere Übertragung noch läuft (gegen das es in diesem Zustand ja keine 
Absicherung mehr gibt) dann gibt es eine race condition bei der 
anscheinend entweder die neue Übertragung verfällt oder das vorherige 
Byte erneut gesendet oder auch ein ganz anderes Byte gesendet wird

Ich habe jetzt provisorisch folgenden Code in die Lüftungssoftware 
eingebaut und das Problem wird umgangen:
1
#ifdef CANBOOT_WORKAROUND
2
  // The following is to work around an issue of the canboot bootloader ...
3
  SPI.begin();
4
  uint8_t spif = !!(SPSR & _BV(SPIF));
5
  if (spif) {
6
    uint8_t spi = 0xFF;
7
    if (SPSR & _BV(SPIF)) spi = SPDR;
8
    TraceVar(spi);
9
    spif = !!(SPSR & _BV(SPIF));
10
    TraceVar(spif);
11
  }
12
  SPI.end();
13
#endif

Später werde ich den CAN-Bootloader korrigieren und installieren. 
Hoffentlich funktioniert mein Mechanismus, bei dem der CAN-Bootloader 
sich selbst überschreibt! Ansonsten müsste ich bei allen Geräten lokal 
an die ISP-Stecker und das wäre doch ziemlicher Aufwand ...

LG, Sebastian

von S. Landolt (Gast)


Lesenswert?

> Und es scheint so zu sein

Es scheint nicht nur, es ist so.

> provisorisch folgenden Code

Das nun verstehe ich nicht - weshalb reicht als Notbehelf nicht ein 
simples 'wirfmichweg = SPDR;' zu Beginn der Anwendungssoftware?

von Sebastian W. (wangnick)


Lesenswert?

S. Landolt schrieb:
> Das nun verstehe ich nicht - weshalb reicht als Notbehelf nicht ein
> simples 'wirfmichweg = SPDR;' zu Beginn der Anwendungssoftware?

Sollte auch reichen. Ich brauche aber die Rückmeldung (über TraceVar auf 
TXD0) ob SPIF tatsächlich gesetzt ist um meine spätere Korrektur des 
CAN-Bootloaders zu verifizieren. Ausserdem hatte ich die Befürchtung, 
wirfmichweg würde wegoptimiert. Die zweite Abfrage von SPIF innerhalb 
der if-Anweisung ist aber in der Tat völlig überflüssig ...

LG, Sebastian.

: Bearbeitet durch User
von S. Landolt (Gast)


Lesenswert?

> Befürchtung, wirfmichweg würde wegoptimiert

Okay - in meinem Assembler wird nichts wegoptimiert, und in C gibt es 
doch sicher eine Möglichkeit, eine solche Variable festzunageln 
(volatile?), oder?

von Sebastian W. (wangnick)


Lesenswert?

S. Landolt schrieb:
>> Befürchtung, wirfmichweg würde wegoptimiert
>
> Okay - in meinem Assembler wird nichts wegoptimiert, und in C gibt es
> doch sicher eine Möglichkeit, eine solche Variable festzunageln
> (volatile?), oder?

Das sollte gehen. Allerdings habe ich grad noch mal das Datenblatt 
gelesen, und dort steht: "Alternatively, the SPIF bit is cleared by 
first reading the SPI Status Register with SPIF set, then accessing the
SPI Data Register (SPDR)". Insofern ist es wohl sicherer, vor dem Lesen 
von SPDR auch noch SPSR zu lesen ...

LG, Sebastian

von Εrnst B. (ernst)


Lesenswert?

Sebastian W. schrieb:
> Ausserdem hatte ich die Befürchtung,
> wirfmichweg würde wegoptimiert.

Wird es auch. Macht aber nix, der Lesezugriff auf SPDR wird nicht 
wegoptimiert. Ziel erreicht, kein Speicher verschwendet.

"volatile" ist am SPDR schon gesetzt, das reicht.
1
 uint8_t wirfmichweg=SPDR;
2
 SPDR;
3
 (void)SPDR;

Kompiliert zu:
1
        in r24,0x2e
2
        in r24,0x2e
3
        in r24,0x2e

Ich bevorzuge die letzte Variante, da ist klar dokumentiert, dass der 
gelesene Wert verworfen werden soll.

von S. Landolt (Gast)


Lesenswert?

> reading the SPI Status Register with SPIF set

Das macht doch Ihr Bootloader, oder wartet der nicht auf das Ende der 
von ihm angestoßenen Übertragung?
  Egal, ist schließlich nur ein Notbehelf.

von Sebastian W. (wangnick)


Lesenswert?

Sebastian W. schrieb:
> Später werde ich den CAN-Bootloader korrigieren und installieren.
> Hoffentlich funktioniert mein Mechanismus, bei dem der CAN-Bootloader
> sich selbst überschreibt!

Ok, der Bootloader ist korrigiert und installiert, und der 
CANBOOT_WORKAROUND ist nicht mehr nötig, SPIF ist beim Einsprung in die 
Anwendung jetzt 0.

Im Bootloader waren zwei Fehler. Zum einen wurde beim Senden nach dem 
Warten auf SPIF nie SPDR gelesen und so SPIF gelöscht. Das erklärt mir 
auch einige vorher unerklärliche Fehler bei der Installation neuer 
Anwendungssoftware mit dem Bootloader, weil dadurch genau die oben 
beschriebenen Sendefehler auftreten können. Zum anderen habe ich vor dem 
Sprung zur Anwendung erst die Ports auf Input ohne Pullup zurückgesetzt 
und danach SPI über SPCR abgeschaltet, und dadurch wohl den SPI Slave 
Modus aktiviert und so SPIF gesetzt!

In diesem Zusammenhang habe ich auch noch herausgefunden das allein das 
Lesen von SPDR nicht ausreicht um SPIF zu löschen; wie im Datenblatt 
erwähnt muss zuvor erst noch SPSR gelesen werden.

LG, Sebastian

von Peter D. (peda)


Lesenswert?

Es ist good practice in jeder Initfunktion, das Interface zurück zu 
setzen. Dazu das Interface disablen und alle Interruptflags durch 
1-setzen (beim AVR) zu löschen.

von S. Landolt (Gast)


Angehängte Dateien:

Lesenswert?

> Interruptflags durch 1-setzen (beim AVR) zu löschen

Was beim hier diskutierten ATmega328.SPSR.SPIF so nicht funktioniert, da 
read-only.

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.