Forum: Mikrocontroller und Digitale Elektronik [atmega328p] SPI-Setup als Master


von Tom V. (t_v)


Lesenswert?

Hallo

ich habe folgende Funktion um mein SPI zu initialisieren:
1
uint8_t spiMasterInit(enum spiClockRate clockRate,enum spiInterrupt spiInt)
2
{
3
    //Set MOSI and SCK as output
4
    DDR_SPI |= (1<<DD_MOSI)|(1<<DD_SCK);
5
6
    PORTB = 0b00000100; //TODO SS have to be set to high, otherwise MSTR in SPCR won't be set
7
    
8
    //XXX just debugoutput
9
    itoa(DDR_SPI,text,2);
10
    uartPutString("\nDDR_SPI: ");
11
    uartPutString(text);
12
13
14
    //Init with or without interrupt
15
    if(spiInt == SPI_INTERRUPT_DISABLE) 
16
        {
17
            //enable SPI, set as master, set clockrate
18
            SPCR = (1<<SPE) | (1<<MSTR) | clockRate;
19
            uartPutString("\nSPI ENABLED WITHOUT INTERRUPT!\n");
20
            
21
            //XXX just debugoutput           
22
            itoa(SPCR,text,2);
23
            uartPutString("\nSPCR: ");
24
            uartPutString(text);
25
        }
26
    else if(spiInt == SPI_INTERRUPT_ENABLE) 
27
        {
28
            //enable SPI, set as master, set clockrate, enable interrupts
29
            SPCR = (1<<SPE)|(1<<MSTR)|clockRate|(1<<SPIE);
30
            //enable global interrupts
31
            sei();
32
            uartPutString("\nSPI ENABLED WITH INTERRUPT!\n");
33
        }
34
    else
35
        {
36
            return 1;
37
        }
38
39
    return 0;
40
41
}
SS liegt auf PB2 und wird ausserhalb als Output gesetzt.
Nun habe ich das Problem, dass ich SS auf High setzen muss damit ich das 
MSTR-Bit in SPCR setzen kann. Wenn ich ich das nicht mache, also die 
Zeile
1
PORTB = 0b00000100;
 auskommentiere, kann ich alle Bits in SPCR setzen ausser das MSTR-Bit.
Da ich eine ganze Weile gebraucht habe um diesen Fehler zu finden und 
SPI für mich "Neuland" ist, habe ich mich sowohl mit dem Datenblatt als 
auch einige andere Quellen zu SPI auseinander gesetzt und nach dem was 
ich darüber gelesen habe müsste es doch unerheblich sein ob ich den Pin 
auf High setze oder nicht, da er ja als Output gesetzt ist.

Ich habe wohl irgendwo einen Denkfehler :/
Kann sich jemand von euch das Verhalten erklären und mir vll. auf die 
Sprünge helfen?

Danke schonmal...
Tom

von Thomas E. (thomase)


Lesenswert?

Tom V. schrieb:
> Nun habe ich das Problem, dass ich SS auf High setzen muss damit ich das
> MSTR-Bit in SPCR setzen kann. Wenn ich ich das nicht mache, also die
> ZeilePORTB = 0b00000100; auskommentiere, kann ich alle Bits in SPCR
> setzen ausser das MSTR-Bit.

Das ist Quatsch. Zeig dein ganzes Programm.

mfg.

von holger (Gast)


Lesenswert?

>SS liegt auf PB2 und wird ausserhalb als Output gesetzt.
>Nun habe ich das Problem, dass ich SS auf High setzen muss damit ich das
>MSTR-Bit in SPCR setzen kann.

Dann hast du PB2 nicht auf Ausgang gesetzt.

>Wenn ich ich das nicht mache, also die
>Zeile
>
>PORTB = 0b00000100;
>
> auskommentiere, kann ich alle Bits in SPCR setzen ausser das MSTR-Bit.

Ausgänge setzt man in DDRB.

von Christian K. (the_kirsch)


Lesenswert?

holger schrieb:
> Ausgänge setzt man in DDRB.

Über das Data-Direktion-Register wird eigentlich nur ein PullUP auf VCC 
geschaltet. Dass sollte man immer machen wenn der Ausgang vom µC 
versorgen will. Der Widerstand hat 100KOhm.

(Technisch gesehen ist es sogar eine Stromquelle mit maximal 50mA)


Bei Selectleitungen (eigentlich bei allen Ausgängen wenn das Projekt 
fertig ist) empfehlt es sich zusätzlich einen externen Widersand zu 
benutzen, damit die Leitung immer Hi-Level hat, auch dann wenn der µC 
sich gerade Resettet (z. B. wenn du ihn gerade neu Programmierst).


Beim Reset sind alle Pins des µC Hochohmig und haben daher kein 
definierten Pegel. Ein SPI-Slave könnte in der Zeit denken seine 
Selectleitung sei Aktiv und würde so eine ISP-Programmierung stören.

Tom V. schrieb:
> Nun habe ich das Problem, dass ich SS auf High setzen muss damit ich das
> MSTR-Bit in SPCR setzen kann. Wenn ich ich das nicht mache, also die
> ZeilePORTB = 0b00000100; auskommentiere, kann ich alle Bits in SPCR
> setzen ausser das MSTR-Bit.

Thomas Eckmann schrieb:
> Das ist Quatsch.
Nein ist es nicht!

Aus dem Datenblatt:
Bit 4 – MSTR: Master/Slave Select
This bit selects Master SPI mode when written to one, and Slave SPI mode 
when written logic zero. If SS is configured as an input and is driven 
low while MSTR is set, MSTR will be cleared, and SPIF in SPSR will 
become set. The user will then have to set MSTR to re-enable SPI Master 
mode.

von Thomas E. (thomase)


Lesenswert?

Christian K. schrieb:
> Über das Data-Direktion-Register wird eigentlich nur ein PullUP auf VCC
> geschaltet. Dass sollte man immer machen wenn der Ausgang vom µC
> versorgen will. Der Widerstand hat 100KOhm.

Aber nur eigentlich. Und auch nur bei dir. Für welchen Controller gilt 
das?

Christian K. schrieb:
> Nein ist es nicht!

Sicher ist es das und das Ausrufezeichen hättest du dir sparen können.

Aber ohne den gesamten Code kommt man hier, wie immer, nicht weiter. Der 
Fehler liegt äusserst selten in dem Teil, den der Fragesteller für 
einzig relevant hält.

Tom V. schrieb:
> SS liegt auf PB2 und wird ausserhalb als Output gesetzt.

Es ist vollkommen klar, dass da der Fehler zu suchen ist.

Aber bislang kann man nicht mehr sagen, als dass in Zeile 42 ein 
Semikolon fehlt.

mfg.

von Karl H. (kbuchegg)


Lesenswert?

Thomas Eckmann schrieb:

> Tom V. schrieb:
>> SS liegt auf PB2 und wird ausserhalb als Output gesetzt.
>
> Es ist vollkommen klar, dass da der Fehler zu suchen ist.

Eben.
Und weil es bei SPI im Masterbetrieb meist sowieso nicht sinnvoll ist, 
denn SS Pin für etwas anderes zu verwenden, sollte die entsprechende 
Verschaltung auf Ausgang hier
1
uint8_t spiMasterInit(enum spiClockRate clockRate,enum spiInterrupt spiInt)
2
{
3
    //Set MOSI and SCK as output
4
    DDR_SPI |= (1<<DD_MOSI)|(1<<DD_SCK);
5
6
...
und nur hier erfolgen und nicht irgendwo ausserhalb.

von Karl H. (kbuchegg)


Lesenswert?

So etwas
1
....
2
            sei();
3
...

ist in derartigen Init-Funktionen auch immer problematisch. Denn ab 
sofort können dann Interrupts bereits feuern, egal ob der Rest der 
Initialisierung (anderer Komponenten) bereits fertig ist oder nicht.

Es gibt nur einen wirklich sinnvollen Platz für das initiale sei(). Und 
der ist hier ...
1
int main()
2
{
3
   Initialisierungen durchführen
4
5
   // jetzt ist alles fertig eingestellt. Ab jetzt gilts. Die
6
   // Hauptschleife geht gleich los, alles ist 'ready to work'
7
8
   sei();    // <--- daher 'Feuer frei'
9
10
   while( 1 )
11
   {
12
      Hauptschleife in der das Programm seine Arbeit ereledigt
13
   }
14
}
... unmittelbar als letzte Aktion vor der Hauptschleife.

Es kann maximal sein, dass die sei()/cli() Steuerung in der 
Hauptschleife als Folge von irgendwelchen Aktionen passiert.
Aber mitten in den Initialisierungen ist ein sei() ein 'ask for 
trouble'. Das geht 20 mal gut, weil es zwischen den benutzten 
Systemkomponenten keine Interrupt-Abhängigkeiten gibt, aber das 21.te 
mal sucht man sich einen Wolf, weil es dann eben doch ein Programm ist, 
bei dem die Reihenfolge der Initialisierungen kritisch ist und dann 
plötzlich ein Interrupt zu früh kommt, noch ehe die anderen Komponenten 
fertig initialisiert sind.

von Christian K. (the_kirsch)


Lesenswert?

Thomas Eckmann schrieb:
> Christian K. schrieb:
>> Über das Data-Direktion-Register wird eigentlich nur ein PullUP auf VCC
>> geschaltet. Dass sollte man immer machen wenn der Ausgang vom µC
>> versorgen will. Der Widerstand hat 100KOhm.
>
> Aber nur eigentlich. Und auch nur bei dir. Für welchen Controller gilt
> das?
Ok, es sind zwei Sachen, der Port-Output wird freigeschalten und es wird 
ein PullUP geschaltet (falls interne PullUps im MCUCR nicht deaktiviert 
sind).

Thomas Eckmann schrieb:
> Christian K. schrieb:
>> Nein ist es nicht!
>
> Sicher ist es das und das Ausrufezeichen hättest du dir sparen können.
Doch,
Tom V. hat sich über ein Verhalten des µC gewundert das du mit "Das ist 
Quatsch" betitelt hast. Dieses Verhalten ist aber im Datenblatt 
expliziert beschrieben.

Thomas Eckmann schrieb:
> Aber ohne den gesamten Code kommt man hier, wie immer, nicht weiter. Der
> Fehler liegt äusserst selten in dem Teil, den der Fragesteller für
> einzig relevant hält.
Das stimmt, der Fehler wird vermutlich sein das der nSS-Pin nicht vor 
der Initialisierung des SPI auf Output gesetzt wurde.

von Tom V. (t_v)


Lesenswert?

Hallo

danke erstmal für die Hinweise, insbesondere an Karl Heinz, es macht 
natürlich Sinn SS in der Initfunktion mit als Ausgang zu setzen und auch 
deine Anmerkung mit dem sei() nach allen Inits werde ich so umsetzen.

Jetzt kommts:
Plötzlich funktioniert es mit dem Codestand von gestern :/

Was ich heut Morgen tat:
Ich hatte lediglich das sei() aus der Initfunktion geschmissen und den 
SS Pin mit in der Init gesetzt, wie von Karl Heinz empfohlen:
1
uint8_t spiMasterInit(enum spiClockRate clockRate,enum spiInterrupt spiInt)
2
{
3
    //Set MOSI and SCK as output
4
    DDR_SPI |= (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);
und ausserdem die Zeile auskommentiert:
1
//PORTB = 0b00000100; //TODO SS muss HIGH gesetzt werden, sonst wird MSTR in SPCR nicht gesetzt
Plötzlich lief es.

Da aber das sei() für mein Problem hier keine direkte Rolle spielte und 
ich den SS Pin ja vorher auch schon in der main() auf High gesetzt habe 
wunderte ich mich das es funktionierte und bin noch einmal auf meinen 
ursprünglichen Code zurück. Und plötzlich funktioniert das ganze. Wobei 
mich das jetzt nicht gerade glücklich macht, da dieser Fehler(?) ja 
wieder auftreten könnte.(Ich mag es nicht wenn Fehler von selbst 
verschwinden)

So hier nochmal was in der ursprünglichen main() abläuft bevor der 
spiMasterInit() aufgerufen wird:
1
#include <avr/pgmspace.h>
2
#include <avr/io.h>
3
#include <util/delay.h>
4
#include <string.h>
5
#include "uart.h"
6
#include "spi.h"
7
8
int main (void){
9
10
const char* input;
11
char test[16];
12
int spiReturned = 0;
13
//init Port B
14
DDRB |= (1 << PB1);
15
DDRB |= (1 << PB2);
16
PORTB = 0x00;
17
18
uartInit();
19
spiMasterInit(SPI_CLK_DIV_128, SPI_INTERRUPT_DISABLE);
20
_delay_ms(1500);
21
uartPutString("\nInit finished!\n\n");
22
23
while(1)
24
{
25
[...]
26
}
27
28
return 0;
29
}

Wenn der uC startet erhalte ich folgende Ausgabe.
1
DDR_SPI: 101110
2
SPI ENABLED WITHOUT INTERRUPT!
3
4
SPCR: 1010011
5
Init finished!
Also funktioniert das ganze.

Gestern sah das mit dem selben Codestand anders aus:
Glücklicherweise hatte ich die Konsole von Gestern noch auf und da hatte 
ich folgende Ausgabe mit der auskommentierten Zeile:
1
//PORTB = 0b00000100; [...]
1
DDR_SPI: 101110
2
SPI ENABLED WITHOUT INTERRUPT!
3
4
SPCR: 1000000
5
Init finished!

Auch da sieht man ja das SS(PB2) schon als Ausgang gesetzt war, ich aber 
das MSTR-Bit gestern nicht setzen konnte.

Ich werde das ganze weiter beobachten und mich gegebenenfalls nochmal 
hier melden. Falls jemand eine Erklärung hat immer her damit. Danke 
nochmal für eure Hilfe.

Ach und falls jemand interesse verspürt auf den ganzen Code zu schauen 
ich habe den Stand von Gestern bei github hochgeladen: 
https://github.com/tom-vi/gugl/tree/spi
Ich bin noch recht frisch was die uC-Programmierung angeht und versuch 
mich da gerade rein zu fuchsen. Für Tips und Tricks bin ich immer sehr 
dankbar :)

von Thomas E. (thomase)


Lesenswert?

Christian K. schrieb:
> Ok, es sind zwei Sachen, der Port-Output wird freigeschalten und es wird
> ein PullUP geschaltet (falls interne PullUps im MCUCR nicht deaktiviert
> sind).

Ist dir klar, was du hier für einen Stuss schreibst? Wahrscheinlich 
nicht.
Im DDR-Register werden keine Pullups geschaltet.

Pullups werden im PORT geschaltet, wenn der Pin Eingang ist. Wird der 
Pin auf Ausgang geschaltet, wird er 'hart' an Vcc oder GND geschaltet. 
Je nach Ausgabe im PORT-Register.

mfg.

von Christian K. (the_kirsch)


Lesenswert?

Nicht ganz,
zugegeben hatte da was durcheinander gebracht.

PulluUps gibt es schon, und zwar wenn DDRx.y auf 0 ist, PORTx.y auf 1 
und MCUCR.PUD auf 0 (Standard).

Wenn DDRx.y auf 1 gesetzt wird, also auf Output, wird der PullUp 
weggeschaltet, stattdessen übernimmt ein Ausgangstreiber der dann je 
nach PORTx.y das Signal auf GND zieht oder über eine Stromquelle auf Hi.

von Thomas E. (thomase)


Lesenswert?

Christian K. schrieb:
> PulluUps gibt es schon, und zwar wenn DDRx.y auf 0 ist, PORTx.y auf 1
> und MCUCR.PUD auf 0 (Standard).
>
> Wenn DDRx.y auf 1 gesetzt wird, also auf Output, wird der PullUp
> weggeschaltet, stattdessen übernimmt ein Ausgangstreiber der dann je
> nach PORTx.y das Signal auf GND zieht oder über eine Stromquelle auf Hi.

Danke, dass du mir jetzt erklärst, was ich dir vorher geschrieben habe:

Thomas Eckmann schrieb:
> Pullups werden im PORT geschaltet, wenn der Pin Eingang ist. Wird der
> Pin auf Ausgang geschaltet, wird er 'hart' an Vcc oder GND geschaltet.
> Je nach Ausgabe im PORT-Register.

mfg.

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.