Forum: Mikrocontroller und Digitale Elektronik STM32F4 SPI Problem


von stm32-anfänger (Gast)


Lesenswert?

Moin Leute,

irgendwie kriege ich bei meinem STM32F4-Discovery-Board das SPI nicht 
ans Laufen.
Konkret: Es werden keine Daten "rausgetaktet" und die while-Schleife, 
welche darauf wartet, dass das TXE-Bit gesetzt wird nie verlassen.
Wo liegt mein Fehler bzw. mein Brett vor meinem Kopf?
1
#include "stm32f4xx.h"
2
3
volatile uint32_t systick_cnt;
4
5
void GPIO_Init(void){
6
  RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
7
  GPIOD->MODER |= (GPIO_MODER_MODER12_0 | GPIO_MODER_MODER13_0 | GPIO_MODER_MODER14_0 | GPIO_MODER_MODER15_0);
8
  GPIOD->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR12_1 | GPIO_OSPEEDER_OSPEEDR13_1 | GPIO_OSPEEDER_OSPEEDR14_1 | GPIO_OSPEEDER_OSPEEDR15_1);
9
  GPIOD->PUPDR |= (GPIO_PUPDR_PUPDR12_1 | GPIO_PUPDR_PUPDR13_1 | GPIO_PUPDR_PUPDR14_1 | GPIO_PUPDR_PUPDR15_1);
10
  //GPIOD->ODR ^= (GPIO_ODR_ODR_15 | GPIO_ODR_ODR_14 | GPIO_ODR_ODR_13 | GPIO_ODR_ODR_12);
11
12
  RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN;
13
  GPIOE->MODER |= GPIO_MODER_MODER7_0;
14
  GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7_1;
15
  GPIOE->PUPDR |= GPIO_PUPDR_PUPDR7_1;
16
17
  return;
18
}
19
20
void SPI_Init(void){
21
  RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
22
  RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
23
24
/* PA5 = SCK
25
* PA6 = MISO
26
* PA7 = MOSI
27
 */
28
  GPIOA->MODER |= GPIO_MODER_MODER4_1;
29
  GPIOA->MODER |= GPIO_MODER_MODER5_1; // MODER[1:0] 10 = Alternate Function Mode
30
  GPIOA->MODER |= GPIO_MODER_MODER6_1;
31
  GPIOA->MODER |= GPIO_MODER_MODER7_1;
32
  GPIOA->OTYPER &= ~(GPIO_OTYPER_OT_4 | GPIO_OTYPER_OT_5 | GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7); // OTYPER = 0 -> PushPull; OTYPER = 1 -> OpenDrain
33
  GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4_1 | GPIO_OSPEEDER_OSPEEDR5_1 |  GPIO_OSPEEDER_OSPEEDR6_1 | GPIO_OSPEEDER_OSPEEDR7_1; //OSPEEDR[1:0] = 10 -> High Speed
34
  GPIOA->PUPDR |= GPIO_PUPDR_PUPDR4_0 | GPIO_PUPDR_PUPDR5_1 |  GPIO_PUPDR_PUPDR6_1 |  GPIO_PUPDR_PUPDR7_1; //PUPDR[1:0] = 10 -> PullDown
35
  GPIOA->ODR |= GPIO_ODR_ODR_4 | GPIO_ODR_ODR_5 | GPIO_ODR_ODR_7;
36
  GPIOA->AFR[0] |= 5U<<16 | 5U<<20 | 5U<<24 | 5U<<28;
37
38
  SPI1->CR1 |= SPI_CR1_BR_0 | SPI_CR1_BR_1 | SPI_CR1_BR_2; //Clock-Divider = 256
39
  SPI1->CR1 |= SPI_CR1_SSM;
40
  SPI1->CR1 |= SPI_CR1_MSTR;
41
  SPI1->CR1 |= SPI_CR1_CPOL;
42
  SPI1->CR1 |= SPI_CR1_BIDIOE;
43
  SPI1->CR1 |= SPI_CR1_SSI;
44
  GPIOE->ODR |= (GPIO_ODR_ODR_7);
45
  return;
46
}
47
48
void SPI_SendTest(void){
49
  GPIOD->ODR ^= (GPIO_ODR_ODR_15 | GPIO_ODR_ODR_14 | GPIO_ODR_ODR_13 | GPIO_ODR_ODR_12);
50
  SPI1->CR1 &= ~SPI_CR1_SSI;
51
  SPI1->CR1 |= SPI_CR1_SPE;
52
  while(SPI1->SR & SPI_SR_BSY);
53
  SPI1->DR = 0x53;
54
  while(!(SPI1->SR & SPI_SR_TXE));
55
  SPI1->CR1 |= SPI_CR1_SSI;
56
  SPI1->CR1 &= ~SPI_CR1_SPE; // Disable SPE
57
  return;
58
}
59
void SysTick_Handler(void){
60
  systick_cnt++;
61
62
  if(systick_cnt >= 300){
63
      SPI_SendTest();
64
      systick_cnt = 0;
65
  }
66
  return;
67
}
68
69
int main(void)
70
{
71
  systick_cnt = 0;
72
  SystemInit();
73
  GPIO_Init();
74
  SPI_Init();
75
  SysTick_Config(SystemCoreClock/1000);
76
77
  while(1)
78
  {
79
      
80
  }
81
}

von Martin (Gast)


Lesenswert?

Hallo, du musst auf jeden fall warten, bis die SPI nicht mehr busy ist, 
wenn du direkt nach dem das TXE bit wieder kommt die SPI disablest, 
kommt nichts raus.
Es gibt da ein Buffer, sobald du was in DR Register schreibst, geht 
gleich danach das TXE bit wieder an.

von Curby23523 N. (Gast)


Lesenswert?

Das BSY Bit braucht man für normales SPI nicht. Senden bei SPI bedeutet 
IMMER auch empfangen - und wenn es nur Nullen sind.

Nach dem Schreiben von SPIx->DR = DATA; musst du warten, bis das RXNE 
bit gesetzt ist, was bedeutet, dass Daten empfangen wurden (eben das 
Byte bei 8bit).

Initialisieren und Senden/Empfangen tu ich folgendermaßen:
1
void SPI_Init(SPI_TypeDef * oSPI, uint8_t ucPrescaler){
2
  oSPI->CR1 |= SPI_CR1_CPHA;
3
  oSPI->CR1 |= SPI_CR1_CPOL;
4
  oSPI->CR1 |= SPI_CR1_MSTR;
5
  oSPI->CR1 |= (ucPrescaler << 3);
6
  oSPI->CR2 |= SPI_CR2_SSOE;
7
  oSPI->CR1 |= SPI_CR1_SPE;
8
}
9
10
uint8_t SPI_Send(SPI_TypeDef * oSPI, uint8_t cVal){
11
  oSPI->DR = cVal;
12
  while(!(oSPI->SR & SPI_SR_RXNE));
13
14
  return oSPI->DR;
15
}

von stm32-anfänger (Gast)


Lesenswert?

Martin schrieb:
> Hallo, du musst auf jeden fall warten, bis die SPI nicht mehr busy ist,
> wenn du direkt nach dem das TXE bit wieder kommt die SPI disablest,
> kommt nichts raus.

Naja aber das mache ich doch hier:

stm32-anfänger schrieb:
1
while(SPI1->SR & SPI_SR_BSY);
2
SPI1->DR = 0x53;
3
while(!(SPI1->SR & SPI_SR_TXE));

Nils N. schrieb:
> Nach dem Schreiben von SPIx->DR = DATA; musst du warten, bis das RXNE
> bit gesetzt ist, was bedeutet, dass Daten empfangen wurden (eben das
> Byte bei 8bit).

Naja sollte das Ergebnis nicht das gleiche sein? - Wenn der TX-Buffer 
leer ist, dann muss das Byte gesendet worden sein. Das Empfangen geht 
mit dem Senden bei SPI natürlich einher.
Aber wie dem auch sei, es wird ja an und für sich überhaupt NICHTS 
gesendet. Keine Aktivitäten bei NSS, MOSI oder CLK...

von Curby23523 N. (Gast)


Lesenswert?

Der Transmitbuffer kann theoretisch schon leer sein. Du schreibst Daten 
rein, diese gehen in das Shift-Register und werden werden rausgeschoben.

Damit hast du quasi eine 1-byte FIFO. Das byte welches du in DR 
reinkopierst geht sofort in das Shiftregister, wird rausgeschicktet und 
DR ist sofort wieder leer.

stm32-anfänger schrieb:
> Aber wie dem auch sei, es wird ja an und für sich überhaupt NICHTS
> gesendet. Keine Aktivitäten bei NSS, MOSI oder CLK...

Dann überprüfe drei und vierfach, ob du die CLKs, GPIOs etc. richtig 
eingestellt hast. Schreibe für das Setzen der GPIOs Makros, die deinen 
Code lesbarer machen. Z.B:
1
#define T_ALTERNATE 0x02
2
#define MODE(a, b, c) (a)->MODER |= ((c)<<(2*b))
3
#define AFUNC(a,b,c) (a)->AFR[(b)/8] |= ((c)<<(4*((b)%8)))
4
5
MODE(GPIOC, 10, T_ALTERNATE);  
6
MODE(GPIOC, 12, T_ALTERNATE);
7
AFUNC(GPIOC, 10, 5);  
8
AFUNC(GPIOC, 12, 5);

von A. B. (Gast)


Lesenswert?

Nils N. schrieb:

> Dann überprüfe drei und vierfach, ob du die CLKs, GPIOs etc. richtig
> eingestellt hast. Schreibe für das Setzen der GPIOs Makros, die deinen
> Code lesbarer machen. Z.B:

Das ist der eine Punkt, der andere: Das ständige Aktivieren/Deaktivieren 
des SPI ist überflüssig. Solange man keine weiteren Daten in Datenreg. 
schreibt, bleibt das SPI sowieso still. Außerdem:

"Bit 6 SPE: SPI enable
0: Peripheral disabled
1: Peripheral enabled
Note: This bit is not used in I 2 S mode.
When disabling the SPI, follow the procedure described in Section 
25.3.10: Procedure for disabling the SPI."

Wenn man nicht aufpasst, gerät das BSY-Bit da durcheinander ...

Und: Diese Spaghetti-Initialisierung ist schon arg hässlich. die Bits 
nacheinander einzeln zu setzen, erhöht die Übersicht nicht gerade. 
(Außerdem wird dabei stillschweigend vorausgesetzt, dass das Register 
vorher gleich 0x0 war.)
Besser: Register lesen, alle Bits, die man verändern bzw. in 
bestimmten Zustand haben will ausmaskieren, und dann alle, die gesetzt 
werden sollen, setzen. Dann diesen Wert komplett ins Register 
schreiben.
Ist 1) effizienter, 2) geht auch bei 
*Neu*initialisierung/Umkonfiguration, 3) vermeidet 
unzulässige/unerwünschte "Zwischenzustände".

von Curby23523 N. (Gast)


Lesenswert?

A. B. schrieb:
> Und: Diese Spaghetti-Initialisierung ist schon arg hässlich. die Bits
> nacheinander einzeln zu setzen, erhöht die Übersicht nicht gerade.
> (Außerdem wird dabei stillschweigend vorausgesetzt, dass das Register
> vorher gleich 0x0 war.)
> Besser: Register lesen, alle Bits, die man verändern bzw. in
> bestimmten Zustand haben will ausmaskieren, und dann alle, die gesetzt
> werden sollen, setzen. Dann diesen Wert komplett ins Register
> schreiben.
> Ist 1) effizienter, 2) geht auch bei
> *Neu*initialisierung/Umkonfiguration, 3) vermeidet
> unzulässige/unerwünschte "Zwischenzustände".

Dadrüber kann man streiten. Ich habe z.b. untereinander eine Liste mit 
allen Änderungen, zeile für Zeile mit Kommentar am Ende. Finde ich 
persönlich sehr übersichtlich.

Beim Start ist JTAG immer aktiviert (paar Pins auf GPIOA und GPIOB 
vorkonfiguriert), da muss man aufpassen, sonst sollte alles "0" sein, 
sofern man keinen Defaultcode von der IDE mit übersetzt. Ein Blick ins 
Datenblatt spricht da Bände und klärt auf. Meine Markos sind z.B. nur 
ein Oder, bestehendes bleibt bestehen.

von stm32-anfänger (Gast)


Lesenswert?

Hallo zusammen, danke für eure Hilfe!
Ich versuche mal allen Beiträgen gerecht zu werden:

Nils N. schrieb:
> Dann überprüfe drei und vierfach, ob du die CLKs, GPIOs etc. richtig
> eingestellt hast. Schreibe für das Setzen der GPIOs Makros, die deinen
> Code lesbarer machen. Z.B:

Naja, das habe ich gemacht - ihr seht es ja oben. Und das Blinken der 
LEDs mittels Systick-Handler funktioniert auch wie erwartet, daher nehme 
ich an, dass die Takterzeugung mittels Quarz und PLL funktioniert wie 
sie soll.
Und laut dem Clock-Tree hängt das SPI1 an dem APB2 - und den Takt für 
SPI1 meine ich eigentlich ja mit der Zeile
1
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
abgefrühstückt zu haben.
Daher habe ich gehofft, dass ich einfach nur einen ganzen Lattenzaun vor 
dem Kopf hatte und ich einfach etwas absolut offensichtliches übersehen 
habe - bzw. falsch verstanden habe.

A. B. schrieb:
> Das ist der eine Punkt, der andere: Das ständige Aktivieren/Deaktivieren
> des SPI ist überflüssig. Solange man keine weiteren Daten in Datenreg.
> schreibt, bleibt das SPI sowieso still.

Das ist ein richtiger Punkt. Das werde ich in Zukunft berücksichtigen - 
aber mein Problem hat es immer noch nicht behoben :(

A. B. schrieb:
> Diese Spaghetti-Initialisierung ist schon arg hässlich. die Bits
> nacheinander einzeln zu setzen, erhöht die Übersicht nicht gerade.

Nils N. schrieb:
> Dadrüber kann man streiten. Ich habe z.b. untereinander eine Liste mit
> allen Änderungen, zeile für Zeile mit Kommentar am Ende. Finde ich
> persönlich sehr übersichtlich.

Ich finde es so auch übersichtlicher - zumal ich mit dem DaBla bzw. 
Reference-Manual nebenbei einfach von oben nach unten die Register 
durchgehe und dann Bitweise das Setzen mache.
Aber du hast Recht mit folgendem Punkt:

A. B. schrieb:
> (Außerdem wird dabei stillschweigend vorausgesetzt, dass das Register
> vorher gleich 0x0 war.)

Normalerweise setze ich meist (nicht immer) das entsprechende Register 
ohnehin immer gesamt auf Null bevor ich anfange die entsprechenden Bits 
zu setzen. Hier ist es diversen Löschereien/Umschreibaktionen zum Opfer 
gefallen.

von Curby23523 N. (Gast)


Lesenswert?

stm32-anfänger schrieb:
> Normalerweise setze ich meist (nicht immer) das entsprechende Register
> ohnehin immer gesamt auf Null bevor ich anfange die entsprechenden Bits
> zu setzen. Hier ist es diversen Löschereien/Umschreibaktionen zum Opfer
> gefallen.

Dann kann es aber passieren, dass du dir deine Debug-Schnittstelle 
deaktivierst. Stupide alles auf "0" setzen sollte man nicht, aber das 
weißt du bestimmt.

Die arbeit nimmt dir wohl keiner ab, aber:

1. Richtige SPI Clk aktiviert?
3. Richtigen GPIO Clk aktiviert?
3. GPIOs Pins als Alternate?
4. Richtige Alternate Funktion?
6. Sind GPIO Pins ggf. vorkonfiguriert?
7. Ggf. trotzdem Ausgänge auf Push-Pull setzen
8. Speed setzen habe ich bis 1Mhz oft nicht gemacht und funktioniert 
dennoch.

Anschließend probiere evtl. mal meine Zeilen von oben, hier nochmal:
1
void SPI_Init(SPI_TypeDef * oSPI, uint8_t ucPrescaler){
2
  oSPI->CR1 |= SPI_CR1_CPHA;
3
  oSPI->CR1 |= SPI_CR1_CPOL;
4
  oSPI->CR1 |= SPI_CR1_MSTR;
5
  oSPI->CR1 |= (ucPrescaler << 3);
6
  oSPI->CR2 |= SPI_CR2_SSOE;
7
  oSPI->CR1 |= SPI_CR1_SPE;
8
}
9
10
uint8_t SPI_Send(SPI_TypeDef * oSPI, uint8_t cVal){
11
  oSPI->DR = cVal;
12
  while(!(oSPI->SR & SPI_SR_RXNE));
13
14
  return oSPI->DR;
15
}

PS: Effizienz bei Initialisierung ist doch in der Regel egal. Da mach 
ich leiber Zeile für Zeile die entsprechenden Konfigurationen mit 
Kommentar. Womöglich optimiert der Compiler das sowieso weg. Pin für 
Pin, zeile für Zeile. Aber das kann jeder machen, wie man will :).

von Doctor What (Gast)


Lesenswert?

Nils N. schrieb:
> PS: Effizienz bei Initialisierung ist doch in der Regel egal. Da mach
> ich leiber Zeile für Zeile die entsprechenden Konfigurationen mit
> Kommentar. Womöglich optimiert der Compiler das sowieso weg. Pin für
> Pin, zeile für Zeile. Aber das kann jeder machen, wie man will :).

Das geht doch genauso übersichtlich Zeile für Zeile in effizient, z.B. 
so
1
  SPI1->CR1 |= (
2
                SPI_CR1_MSTR | \
3
                SPI_CR1_SPE  | \
4
                SPI_CR1_SSI  | \
5
                SPI_CR1_SSM
6
  );

Also warum unnötig, Speicherplatz, Rechenkapazität und Strom 
verschwenden?

von Doctor What (Gast)


Lesenswert?

A. B. schrieb:
> Das ist der eine Punkt, der andere: Das ständige Aktivieren/Deaktivieren
> des SPI ist überflüssig. Solange man keine weiteren Daten in Datenreg.
> schreibt, bleibt das SPI sowieso still. Außerdem:

Das macht er wohl, weil er erwartet, dass NSS automatisch beim Setzen 
und Löschen von SPE aktiviert bzw. deaktiviert wird.

von Doctor What (Gast)


Lesenswert?

stm32-anfänger schrieb:
> Naja sollte das Ergebnis nicht das gleiche sein? - Wenn der TX-Buffer
> leer ist, dann muss das Byte gesendet worden sein. Das Empfangen geht
> mit dem Senden bei SPI natürlich einher.
> Aber wie dem auch sei, es wird ja an und für sich überhaupt NICHTS
> gesendet. Keine Aktivitäten bei NSS, MOSI oder CLK...

Du hast einen Vorteiler von 256 eingestellt. Wie schnell der APB 
getaktet ist, läßt sich aus Deinem Beispiel nicht ersehen. Auf jeden 
Fall hat der mindestens einen Vorteiler von 2.
Aber auch so ist klar, dass das Byte niemals vor dem Abschalten der 
SPI-Schnittstelle rausgetaktet werden kann.
Daher: Nur wenn Du genau die Taktzyklen gezählt hast, auf das Abfragen 
des BSY-Flags verzichten. Als Anfänger solltest Du das besser immer 
machen.

von Doctor What (Gast)


Lesenswert?

Mein Vorschlag ist, das Ganze erstmal ein bisschen simpler zu machen.
Nimm also den NSS-Kram raus, den hast Du sicherlich nicht verstanden.
Mache den Chipselect ganz einfach per GPIO.
Ich bin sicher, dass Du damit die Daten rausgetaktet bekommst.
Anschliessend liest Du Dir mal das entsprechende Kapitel im Reference 
Manual gründlich durch, idealerweise mehr als einmal. Einmal reicht 
nämlich für Anfänger nicht.
Wenn Dir die GPIO-CS-Lösung dann nicht gefällt, kannst Du von da aus 
weitere Änderungen vornehmen.

von Doctor What (Gast)


Lesenswert?

Übrigens gibt es hierzu was im Forum hier:
Beitrag "stm32 SPI NSS Signal Anfänger"

von stm32-anfänger (Gast)


Lesenswert?

Nils N. schrieb:
> stm32-anfänger schrieb:
>> Normalerweise setze ich meist (nicht immer) das entsprechende Register
>> ohnehin immer gesamt auf Null bevor ich anfange die entsprechenden Bits
>> zu setzen. Hier ist es diversen Löschereien/Umschreibaktionen zum Opfer
>> gefallen.
>
> Dann kann es aber passieren, dass du dir deine Debug-Schnittstelle
> deaktivierst. Stupide alles auf "0" setzen sollte man nicht, aber das
> weißt du bestimmt.

Das ist mir bewusst. Das ist ein Fall von "Nicht immer". Bevor ich ein 
Register "radikal" auf Null setze, mache ich mir natürlich schon ein 
Paar Gedanken, ob das ohne Probleme möglich/zulässig ist. Das "meist" 
bezog sich eher auf "0815"-Register a la SPI, wo soetwas problemlos 
möglich ist.

Nils N. schrieb:
> 1. Richtige SPI Clk aktiviert?
1
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
Check!
> 3. Richtigen GPIO Clk aktiviert?
1
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
2
3
4
5
/* PA5 = SCK
6
* PA6 = MISO
7
* PA7 = MOSI
8
 */
Check!

Nils N. schrieb:
> 3. GPIOs Pins als Alternate?
1
  GPIOA->MODER |= GPIO_MODER_MODER4_1;
2
  GPIOA->MODER |= GPIO_MODER_MODER5_1; // MODER[1:0] 10 = Alternate Function Mode
3
  GPIOA->MODER |= GPIO_MODER_MODER6_1;
4
  GPIOA->MODER |= GPIO_MODER_MODER7_1;
Check!

Nils N. schrieb:
> 4. Richtige Alternate Funktion?
1
GPIOA->AFR[0] |= 5U<<16 | 5U<<20 | 5U<<24 | 5U<<28;
Check?!

Nils N. schrieb:
> 6. Sind GPIO Pins ggf. vorkonfiguriert?
Ja, Port A hat ein paar Bits nach dem Reset gesetzt (Default Für 
Mode-Register ist 0xA800 0000, das sollte aber mich nicht tangieren).

Nils N. schrieb:
> 7. Ggf. trotzdem Ausgänge auf Push-Pull setzen
1
GPIOA->OTYPER &= ~(GPIO_OTYPER_OT_4 | GPIO_OTYPER_OT_5 | GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7); // OTYPER = 0 -> PushPull; OTYPER = 1 -> OpenDrain
Check!

Nils N. schrieb:
> 8. Speed setzen habe ich bis 1Mhz oft nicht gemacht und funktioniert
> dennoch.
1
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4_1 | GPIO_OSPEEDER_OSPEEDR5_1 |  GPIO_OSPEEDER_OSPEEDR6_1 | GPIO_OSPEEDER_OSPEEDR7_1; //OSPEEDR[1:0] = 10 -> High Speed
Check!

Deinen Code hier habe ich auch probiert, hat allerdings auch nicht 
funktioniert:

Nils N. schrieb:
> void SPI_Init(SPI_TypeDef * oSPI, uint8_t ucPrescaler){
>   oSPI->CR1 |= SPI_CR1_CPHA;
>   oSPI->CR1 |= SPI_CR1_CPOL;
>   oSPI->CR1 |= SPI_CR1_MSTR;
>   oSPI->CR1 |= (ucPrescaler << 3);
>   oSPI->CR2 |= SPI_CR2_SSOE;
>   oSPI->CR1 |= SPI_CR1_SPE;
> }
>
> uint8_t SPI_Send(SPI_TypeDef * oSPI, uint8_t cVal){
>   oSPI->DR = cVal;
>   while(!(oSPI->SR & SPI_SR_RXNE));
>
>   return oSPI->DR;
> }

Konkret sieht es so aus, dass der Controller wieder bei der Abfrage 
hängen bleibt, ob das RXNE-Bit gesetzt wurde. Da weder Daten 
herausgetaktet wurden, konnten auch keine Daten eingelesen werden - 
daher kann das Bit auch nie gesetzt werden.

Doctor What schrieb:
> Du hast einen Vorteiler von 256 eingestellt. Wie schnell der APB
> getaktet ist, läßt sich aus Deinem Beispiel nicht ersehen. Auf jeden
> Fall hat der mindestens einen Vorteiler von 2.

Ich habe ein Sysclock von 84 MHz und daher ein APB2-Peripheral-Clock von 
21MHz. Daher sollte der Takt bei ca. 82 kHz liegen.

Doctor What schrieb:
> Aber auch so ist klar, dass das Byte niemals vor dem Abschalten der
> SPI-Schnittstelle rausgetaktet werden kann.
> Daher: Nur wenn Du genau die Taktzyklen gezählt hast, auf das Abfragen
> des BSY-Flags verzichten. Als Anfänger solltest Du das besser immer
> machen.

Hm. Dann habe ich das BSY-Flag wohl nicht richtig verstanden. Ich habe 
es so verstanden, dass es so lange gesetzt ist, wie er noch am 
Raustakten ist. Sprich erst, wenn die vollen 8 Bit gesendet (bzw. 
empfangen) wurden, dass dann das BSY-Flag gelöscht wird.

Doctor What schrieb:
> A. B. schrieb:
>> Das ist der eine Punkt, der andere: Das ständige Aktivieren/Deaktivieren
>> des SPI ist überflüssig. Solange man keine weiteren Daten in Datenreg.
>> schreibt, bleibt das SPI sowieso still. Außerdem:
>
> Das macht er wohl, weil er erwartet, dass NSS automatisch beim Setzen
> und Löschen von SPE aktiviert bzw. deaktiviert wird.

Nein, dass das NSS entweder hardwareseitig (zu Beginn eines 
Transmit/Receive-Zyklus) oder softwareseitig (sprich manuell) gelöscht 
bzw. gesetzt. So zumindest mein Verständnis.

Doctor What schrieb:
> Ich bin sicher, dass Du damit die Daten rausgetaktet bekommst.

Mein Saleae-Logic-Analyer, mein Oszi (Agilent DSO 2000) und meine LEDs 
sind anderer Meinung. Ich habe sie extra an den Beginn der 
SPISendTest-Funktion gesetzt, um zu sehen, ob die Funktion brav zyklisch 
angesprungen wird. Da sie aber in einer Endlosschleife (die 
While-Schleife, welche auf das TXE bzw. RXNE-Bit wartet) festhängt.

Doctor What schrieb:
> Anschliessend liest Du Dir mal das entsprechende Kapitel im Reference
> Manual gründlich durch, idealerweise mehr als einmal. Einmal reicht
> nämlich für Anfänger nicht.

Das Kapitel habe ich gefühlt schon oft genug gelesen - daher habe ich 
die Vermutung, dass ich mehr einfach ein großes Brett vor dem Kopf habe 
und meinen Fehler/Missverständnis selber einfach nicht mehr sehe, da ich 
für meine Fehler betriebsblind geworden bin...
Das NSS ist auch (noch) nicht mein Problem - es findet einfach absolut 
keine Tätigkeit seitens des SPI1 statt - und das irritiert mich.

von Curby23523 N. (Gast)


Lesenswert?

GPIOA->AFR[0] |= 5U<<16 | 5U<<20 | 5U<<24 | 5U<<28;

Setze da mal Klammern. Meckert da der Compiler nicht?

Bei mir hieße das:
1
#define T_ALTERNATE 0x02
2
#define MODE(a, b, c) (a)->MODER |= ((c)<<(2*b))
3
#define AFUNC(a,b,c) (a)->AFR[(b)/8] |= ((c)<<(4*((b)%8)))
4
5
MODE(GPIOA, 4, T_ALTERNATE); 
6
MODE(GPIOA, 5, T_ALTERNATE); 
7
MODE(GPIOA, 6, T_ALTERNATE); 
8
MODE(GPIOA, 7, T_ALTERNATE); 
9
10
AFUNC(GPIOA, 4, 5); 
11
AFUNC(GPIOA, 5, 5); 
12
AFUNC(GPIOA, 6, 5); 
13
AFUNC(GPIOA, 7, 5);

Lesbarer oder?

von Doctor What (Gast)


Lesenswert?

Stell mal Deinen kompletten Source ein. Da fehlt doch was. Vermutlich 
liegt da der Fehler.

von stm32-anfänger (Gast)


Lesenswert?

Nils N. schrieb:
> GPIOA->AFR[0] |= 5U<<16 | 5U<<20 | 5U<<24 | 5U<<28;
>
> Setze da mal Klammern. Meckert da der Compiler nicht?
>
> Bei mir hieße das:AFUNC(GPIOA, 4, 5);
> AFUNC(GPIOA, 5, 5);
> AFUNC(GPIOA, 6, 5);
> AFUNC(GPIOA, 7, 5);
>
> Lesbarer oder?

Dem kann ich nicht widersprechen - keine Frage. Das werde ich auch für 
die Zukunft so beibehalten, danke für den Tipp! Nach einer 
übersichtlicheren Lösung habe ich schon länger gesucht bzw. wollte mir 
selbst etwas leserlicheres schreiben.
Aber mein Problem hat es trotzdem nicht gelöst :(

von Doctor What (Gast)


Lesenswert?

Lass mal
1
SPI1->CR1 |= SPI_CR1_BIDIOE;
weg.

von stm32-anfänger (Gast)


Angehängte Dateien:

Lesenswert?

Doctor What schrieb:
> Lass malSPI1->CR1 |= SPI_CR1_BIDIOE;
> weg.

Check! - Kein Unterschied.

Doctor What schrieb:
> Stell mal Deinen kompletten Source ein. Da fehlt doch was. Vermutlich
> liegt da der Fehler.

Das ist alles, was ich habe. Nicht mehr und nicht weniger.
Das einzige, was ich noch hochladen kann ist meine stm32f4.h bzw. 
system_stm32f4.c und .h

Ich habe mittlerweile den Sysclock auf 168 MHz erhöht und der APB2-Clock 
sollte jetzt bei 42 MHz liegen.

von Doctor What (Gast)


Lesenswert?

Welcher Controller ist das?
Und ein Makefile und ein Linkerscript hast Du bestimmt ;)

von Doctor What (Gast)


Lesenswert?

Schalt mal den Takt für AFIO in APB2ENR ein.

von stm32-anfänger (Gast)


Lesenswert?

Doctor What schrieb:
> Welcher Controller ist das?
> Und ein Makefile und ein Linkerscript hast Du bestimmt ;)

Es handelt sich hier um einen STM32F407VG auf einem 
STM32F4-Discovery-Board.

Und da ich die Coocox CoIDE nutze kann ich mit beidem nicht dienen.

Doctor What schrieb:
> Schalt mal den Takt für AFIO in APB2ENR ein.

Und den scheint es laut Reference-Manual bzw. Datenblatt bei meinem 
Controller nicht zu geben.
Allerdings schalte ich den Takt für das SPI1 mit folgender Zeile ein:

stm32-anfänger schrieb:
> Nils N. schrieb:
>> 1. Richtige SPI Clk aktiviert?RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
> Check!

von Curby23523 N. (Gast)


Lesenswert?

stm32-anfänger schrieb:
> Und da ich die Coocox CoIDE nutze kann ich mit beidem nicht dienen.

Das sollte man erstmal ändern. Benutze die STM32 Workbench oder Atollic 
für STM32.

von Doctor What (Gast)


Angehängte Dateien:

Lesenswert?

Ich habe schnell mal frei Schnauze was zusammengekloppt.
Hab leider kein Board hier zum Testen.
Aber Ausprobieren sollte Dich maximal zwei Minuten kosten.
Schau, ob mit dem Programm was aus SPI1 rauskommt.

von dasrotemopped (Gast)


Angehängte Dateien:

Lesenswert?

>Schau, ob mit dem Programm was aus SPI1 rauskommt.

Das sind aber gemütliche 164 kHz.

>>Und da ich die Coocox CoIDE nutze ...
>Das sollte man erstmal ändern.
>Benutze die STM32 Workbench oder Atollic für STM32.

Da bin ich auch für.

Darum auch ein SW4STM32 Projekt passend zur BIN. Macht 41 MHz auf SPI1 
und ein bisschen LED Blinky. Mit CubeMX zusammengeklickt. Ja, ich weiss, 
ist lame aber funzt.

Wer die harte Tour mag kann ja sich aus dem CubeMX Code was abgucken und 
nach Coocox kopieren.

von Doctor What (Gast)


Lesenswert?

dasrotemopped schrieb:
> Das sind aber gemütliche 164 kHz.


So war seine Vorgabe... ;)
1
SPI1->CR1 |= SPI_CR1_BR_0 | SPI_CR1_BR_1 | SPI_CR1_BR_2; //Clock-Divider = 256

von dasrotemopped (Gast)


Angehängte Dateien:

Lesenswert?

>>Das sind aber gemütliche 164 kHz.
>So war seine Vorgabe... ;)

Wollte nur nicht, das jemand sagt, CubeMX macht den uC/SPI lahm ;)
Ist in CubeMX ja nur ein Mausklick entfernt, den Prescaler 
hochzustellen.

von stm32-anfänger (Gast)


Lesenswert?

Nils N. schrieb:
> stm32-anfänger schrieb:
>> Und da ich die Coocox CoIDE nutze kann ich mit beidem nicht dienen.
>
> Das sollte man erstmal ändern. Benutze die STM32 Workbench oder Atollic
> für STM32.

Ich habe mich jetzt mal in STM32 Workbench ein wenig eingearbeitet und 
bin durchaus positiv überrascht. Ich habe eine Zeit lang versucht das 
reine Eclipse ans Laufen zu kriegen und es ist mir auch nach Tagen nicht 
vernünftig gelungen.
Beim Debuggen habe ich mir mal die Register genauer angeschaut - und 
offensichtlich kriege ich permanent einen "Master mode fault MODF" - was 
dazu führt, dass auch das MSTR-Bit gelöscht wird.
Das Reference-Manual sagt dazu folgendes:
1
Master mode fault (MODF)
2
Master mode fault occurs when the master
3
 device has its NSS pin pulled low (in NSS 
4
hardware mode) or SSI bit low 
5
(in NSS software mode), this 
6
automatically sets the MODF 
7
bit. Master mode fault affects the SPI peripheral in the following ways:
8
• The MODF bit is set and an SPI interrupt is generated if the ERRIE bit is set.
9
• The SPE bit is cleared. This blocks all output from the device and disables the SPI interface.
10
• The MSTR bit is cleared, thus forcing the device into slave mode. Use the following software sequence to clear the MODF bit: 1.
11
Make a read or write access to the SPI_SR register while the MODF bit is set.
12
2.     Then write to the SPI_CR1 register.
13
To avoid any multiple slave conflicts in a 
14
system comprising several MCUs, the NSS pin 
15
must be pulled high during the MODF bit clearing sequence. The SPE and MSTR bits can 
16
be restored to their original state after this clearing sequence.
17
As a security, hardware does not allow the setting of the SPE and MSTR bits while the 
18
MODF bit is set.
19
In a slave device the MODF bit cannot be set. 
20
However, in a multimaster configuration, the 
21
device can be in slave mode with this MODF bi
22
t set. In this case, th
23
e MODF bit indicates 
24
that there might have been a multimaster conflic
25
t for system control. An interrupt routine can 
26
be used to recover cleanly from this state by performing a reset or returning to a default 
27
state



Doctor What schrieb:
> Ich habe schnell mal frei Schnauze was zusammengekloppt.
> Hab leider kein Board hier zum Testen.
> Aber Ausprobieren sollte Dich maximal zwei Minuten kosten.
> Schau, ob mit dem Programm was aus SPI1 rauskommt.

Danke! - Das File sorgt in der Tat für eine periodische Ausgabe auf dem 
SPI1! Genau so, wie ich es auch testweise haben wollte.
Also muss bei mir der Fehler liegen (wie es nicht anders zu erwarten war 
;) )
Jetzt bleibt nur noch die Preisfrage: WO ist mein Fehler?

Doctor What schrieb:
> dasrotemopped schrieb:
>> Das sind aber gemütliche 164 kHz.
>
> So war seine Vorgabe... ;)
> SPI1->CR1 |= SPI_CR1_BR_0 | SPI_CR1_BR_1 | SPI_CR1_BR_2; //Clock-Divider
> = 256

dasrotemopped schrieb:
>>>Und da ich die Coocox CoIDE nutze ...
>>Das sollte man erstmal ändern.
>>Benutze die STM32 Workbench oder Atollic für STM32.
>
> Da bin ich auch für.

s.o.

dasrotemopped schrieb:
> Wer die harte Tour mag kann ja sich aus dem CubeMX Code was abgucken und
> nach Coocox kopieren.
Ja, so habe ich es in der Tat auch versucht, nachdem es die ersten 
Versuche nicht geklappt hat.

von Doctor What (Gast)


Lesenswert?

Hier mal mein Code. Ist nicht ausgefeilt und verbesserungswürdig, also 
nicht meckern. Aber vielleicht als Startpunkt für Dich, wenn Du bei 
Deinem Ansatz bleiben willst und nicht die ST-Bloatware verwendest 
(damit sparst Du auch gleich die stm32f4xx_conf.h, system_stm32f4xx.c 
und system_stm32f4xx.h...).

Init.c, ausgerufen aus dem startup vor Sprung in main():
1
#define MCO1PRE   5
2
#define MCO2PRE   5
3
#define PLLM      4
4
#define PLLN      168
5
#define PLLP      2
6
7
void SystemInit(void) {
8
        // Enable Caches, Prefetch and set Waitstates
9
        FLASH->ACR |= ( FLASH_ACR_DCEN | FLASH_ACR_ICEN | FLASH_ACR_PRFTEN | FLASH_ACR_LATENCY_5WS );
10
        while( ( FLASH->ACR & FLASH_ACR_LATENCY ) != FLASH_ACR_LATENCY_5WS )
11
                asm ("nop");
12
13
        RCC->CFGR = (uint32_t) ( ( ( MCO2PRE + 2 ) << 27 ) | RCC_CFGR_PPRE2_DIV4 | RCC_CFGR_PPRE1_DIV8 );
14
15
        // Enable HSE
16
        RCC->CR |= ( RCC_CR_HSEON );
17
        while( !( RCC->CR & RCC_CR_HSERDY ) )
18
                asm ("nop");
19
20
        RCC->PLLCFGR = (uint32_t) ( 0x24000000 | RCC_PLLCFGR_PLLSRC_HSE | ((( PLLP >> 1 ) - 1 ) << 16 ) | ( PLLN << 6 ) | PLLM );
21
22
        RCC->CR |= ( RCC_CR_PLLON );
23
        while( !( RCC->CR & RCC_CR_PLLRDY ) )
24
                asm ("nop");
25
26
        RCC->CFGR |= ( RCC_CFGR_SW_PLL );
27
        while( ( RCC->CFGR & RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL )
28
                asm ("nop");
29
30
        // Enable Peripheral Clocks
31
        RCC->AHB1ENR |= ( RCC_AHB1ENR_GPIOAEN );
32
        RCC->APB2ENR |= ( RCC_APB2ENR_SPI1EN );
33
34
        // Initialise GPIOA port
35
        GPIOA->MODER |= ( GPIO_MODER_MODER4_0 | \
36
                          GPIO_MODER_MODER5_1 | \
37
                          GPIO_MODER_MODER6_1 | \
38
                          GPIO_MODER_MODER7_1 );
39
        GPIOA->OSPEEDR |= ( GPIO_OSPEEDER_OSPEEDR4_1 | GPIO_OSPEEDER_OSPEEDR4_0 | \
40
                            GPIO_OSPEEDER_OSPEEDR5_1 | GPIO_OSPEEDER_OSPEEDR5_0 | \
41
                            GPIO_OSPEEDER_OSPEEDR6_1 | GPIO_OSPEEDER_OSPEEDR6_0 | \
42
                            GPIO_OSPEEDER_OSPEEDR7_1 | GPIO_OSPEEDER_OSPEEDR7_0 );
43
        GPIOA->AFR[0] |= (  (0x5 << 20) | \
44
                            (0x5 << 24) | \
45
                            (0x5 << 28) );
46
        return;
47
}


main.c
1
#include <stm32f4xx.h>
2
#include <core_cm4.h>
3
#include "spi/spi1.h"
4
#include "init.h"
5
6
7
#define SPI1_CS_SET     GPIOA->BSRR = (1<<4)
8
#define SPI1_CS_RESET   GPIOA->BSRR = (1<<20)
9
10
#define DEBUG_ENDLESS while(1) {asm ("nop"); }
11
12
13
volatile uint32_t *DWT_CTRL    = (uint32_t *) 0xE0001000; // Data Watchpoint and Trace counter  Control Register
14
volatile uint32_t *DWT_CYCCNT  = (uint32_t *) 0xE0001004; // Data Watchpoint and Trace counter  Cycle Count Register
15
volatile uint32_t *SCB_DEMCR   = (uint32_t *) 0xE000EDFC; // address of the register
16
#define TRCENA                   (1<<24)
17
18
19
void init_delay(void) {
20
        *SCB_DEMCR = *SCB_DEMCR | TRCENA;
21
        *DWT_CTRL = *DWT_CTRL | 1;
22
}
23
24
25
void delay(uint32_t cycles) {
26
        *DWT_CYCCNT = 0;
27
        uint32_t tmp = *DWT_CYCCNT + cycles;
28
29
        while(*DWT_CYCCNT < tmp) {
30
                ;
31
        }
32
}
33
34
35
void delay_ms(uint16_t del) {
36
        delay(168000 * del);
37
}
38
39
40
void SPI1Init(void) {
41
  SPI1->CR1 |= (
42
                  SPI_CR1_SSM  | \
43
                  SPI_CR1_SSI  | \
44
                  SPI_CR1_SPE  | \
45
                  SPI_CR1_MSTR | \
46
                  SPI_CR1_BR_0 | SPI_CR1_BR_1 | SPI_CR1_BR_2
47
  );
48
  return;
49
}
50
51
52
53
int main(void) {
54
        uint32_t i;
55
56
        SPI1Init();
57
        init_delay();
58
59
        for(i=0; i<100000; i++) {
60
          SPI1_CS_SET;
61
          SPI1_CS_RESET;
62
          SPI1->DR = 0x53;
63
          while (!(SPI1->SR & SPI_SR_TXE))
64
            asm ("nop");
65
          while ( SPI1->SR & SPI_SR_BSY )
66
            asm ("nop");
67
          SPI1_CS_SET;
68
          delay_ms(1);
69
        }
70
}

von stm32-anfänger (Gast)


Lesenswert?

So. Einen großen Spaziergang und Hausputz später habe ich es ans Laufen 
bekommen.
Offenbar muss ich mir die ganze Geschichte mit dem Software-NSS und 
Hardware-NSS mal ganz ganz ganz genau durchlesen, denn offenbar lag da 
das Problem. Nachdem alles, was damit zu tun hatte auskommentiert war 
hat es zumindest nicht mehr diesen Fehler gegeben und es wird zyklisch 
über das SPI etwas herausgetaktet.

Ich hatte es zwar schon vorher so versucht auszukommentieren aber da 
muss ich wohl irgendwas übersehen haben, denn jetzt funktioniert es auf 
einmal.

Wie dem auch sei, ein großes Danke an alle, die sich an diesem Thread 
beteiligt haben und ihre Zeit investiert haben!

Wenn noch weitere Probleme auftreten werde ich mich nochmal melden :)

von Doctor What (Gast)


Lesenswert?

stm32-anfänger schrieb:
> Offenbar muss ich mir die ganze Geschichte mit dem Software-NSS und
> Hardware-NSS mal ganz ganz ganz genau durchlesen, denn offenbar lag da
> das Problem. Nachdem alles, was damit zu tun hatte auskommentiert war
> hat es zumindest nicht mehr diesen Fehler gegeben und es wird zyklisch
> über das SPI etwas herausgetaktet.


Hättest Du mal gleich auf mich gehört ;)

Doctor What schrieb:
> Mein Vorschlag ist, das Ganze erstmal ein bisschen simpler zu machen.
> Nimm also den NSS-Kram raus, den hast Du sicherlich nicht verstanden.
> Mache den Chipselect ganz einfach per GPIO.

von stm32-anfänger (Gast)


Lesenswert?

Doctor What schrieb:
> stm32-anfänger schrieb:
>> Offenbar muss ich mir die ganze Geschichte mit dem Software-NSS und
>> Hardware-NSS mal ganz ganz ganz genau durchlesen, denn offenbar lag da
>> das Problem. Nachdem alles, was damit zu tun hatte auskommentiert war
>> hat es zumindest nicht mehr diesen Fehler gegeben und es wird zyklisch
>> über das SPI etwas herausgetaktet.
>
> Hättest Du mal gleich auf mich gehört ;)
>
> Doctor What schrieb:
>> Mein Vorschlag ist, das Ganze erstmal ein bisschen simpler zu machen.
>> Nimm also den NSS-Kram raus, den hast Du sicherlich nicht verstanden.
>> Mache den Chipselect ganz einfach per GPIO.

Ja Papa ;)
Nein, im Ernst - du hattest Recht. Ich hatte aber auch mal eine kleine 
Pause notwendig gehabt und irgendwie hatte ich mich da etwas verrant.

Wie dem auch sei, jetzt funktioniert alles wie erwartet und ich lese 
mich noch ein wenig zum NSS ein.

von stm32-anfänger (Gast)


Lesenswert?

Mal eine Verständnisfrage zu folgendem Ausschnitt:
1
          while (!(SPI1->SR & SPI_SR_TXE))
2
            asm ("nop");
3
          while ( SPI1->SR & SPI_SR_BSY )
4
            asm ("nop");
Das ist genau die Umsetzung von
1
1.
2
Enable the SPI by setting the SPE bit to 1.
3
2.     Write the first data item to send into 
4
the SPI_DR register (this clears the TXE bit).
5
3.     Wait until TXE=1 and write the next data item
6
 to be transmitted. Repeat this step for 
7
each data item to be transmitted.
8
4.     After writing the last data 
9
item into the SPI_DR register, wa
10
it until TXE=1, then wait until 
11
BSY=0, this indicates that 
12
the transmission of the 
13
last data is complete.

Sprich für Dummies und stm32-anfänger: Die erste While-Schleife wird so 
lange durchlaufen, wie das TXE-Bit als 0 gelesen wird. Die zweite wird 
so lange durchlaufen, wie das BSY-Bit gesetzt ist (was sich ja wiederum 
mit der Anleitung im dem Reference-Manual decken würde)?!

von Doctor What (Gast)


Lesenswert?

Korrekt.

von Doctor What (Gast)


Lesenswert?

Wobei das in meinem Beispiel nicht nötig wäre, das war extra für Dich 
als vollständiges Anschauungs-Beispiel gedacht :)

von stm32-anfänger (Gast)


Lesenswert?

Doctor What schrieb:
> Wobei das in meinem Beispiel nicht nötig wäre, das war extra für Dich
> als vollständiges Anschauungs-Beispiel gedacht :)

Hm.
Warum wäre es in deinem Beispiel nicht nötig gewesen? - du meinst, weil 
ein simples "Delay" (pöhse, ich weiß) ausgereicht hätte?
Ich habe nämlich erst ein wenig mit dem Weglassen dieser Schleifen 
herumexperimentiert und - oh Wunder - das ganze hat dann natürlich nicht 
mehr vernünftig funktioniert. Dann habe ich das RefManual nochmals 
konsultiert und bin auf diese Beschreibung gestoßen, was das ganze 
rückwirkend logisch erklärte.

von Doctor What (Gast)


Lesenswert?

stm32-anfänger schrieb:
> Warum wäre es in deinem Beispiel nicht nötig gewesen? - du meinst, weil
> ein simples "Delay" (pöhse, ich weiß) ausgereicht hätte?


Weil ich nur ein Byte übertrage. Der Test auf TXE ist damit unnötig.
Und je nach SPI-Takt / CPU-Takt ist auch der BSY-Test unnötig, da der 
Delay ausreichend lang ist.

von Doctor What (Gast)


Lesenswert?

Im Übrigen ist ein simpley Delay (ein bzw. mehrere "nop") nicht "pöhse", 
im Gegenteil, da Du die Frequenzen kennst, wird Dein Programm damit 
schneller.
Ist halt nur nicht portabel, wenn sich mal die Frequenz ändern soll.

von Korinthen Kacker (Gast)


Lesenswert?

Doctor What schrieb:
> Im Übrigen ist ein simpley Delay (ein bzw. mehrere "nop") nicht "pöhse",

Ist doch pöhse.

Braucht nur ein anderes Hardware Teil deines STM32 mal dazwischen
funken und dein sensibles Wait/Delay-Gefüge aus dem Rhythmus
bringen, und schon hängt die SPI Maschine wieder.

Kann ja sein dass du schon Delay machst während die SPI noch
darauf wartet überhaupt starten und übertragen zu können ....

von stm32-anfänger (Gast)


Lesenswert?

Doctor What schrieb:
> Im Übrigen ist ein simpley Delay (ein bzw. mehrere "nop") nicht "pöhse",
> im Gegenteil, da Du die Frequenzen kennst, wird Dein Programm damit
> schneller.

Es war auch mehr ironisch gemeint; das habe ich mir schon so gedacht, 
dass du explizit auf das Taktezählen hinauswolltest (ansonsten hätte ich 
schon "böse" geschrieben ;) ).

Ich werde es für die nähere Zukunft aber zunächst datenblattgetreu 
machen, da habe ich schon genug damit zu tun. Daher war dein Beispiel 
mit den beiden Schleifen schon genau richtig!
Wenn ich dann mit den STM32ern etwas sattelfester bin, dann werden auch 
solche händischen Optimierungen unter Umständen eingesetzt - natürlich, 
wie immer mit Bedacht und mit Köpfchen.

Und der Vollständigkeit halber eine Ergänzung für andere Leser, die über 
diesen Thread bzw. dieses Problem stolpern: Folgende Lösung zum Senden 
ist auch funktional:
1
uint32_t SPI_SendTest(void){
2
  uint32_t spi_byte_received = 0;
3
  GPIOD->ODR ^= (GPIO_ODR_ODR_15 | GPIO_ODR_ODR_14 | GPIO_ODR_ODR_13 | GPIO_ODR_ODR_12); //Toggle LED for each function-call
4
  GPIOE->ODR |= GPIO_ODR_ODR_7; //Set the SS-Pin
5
  GPIOE->ODR &= ~GPIO_ODR_ODR_7; //Clear the SS-Pin
6
  SPI1->DR = 0x93; //Send a dummy-byte
7
  while (!(SPI1->SR & SPI_SR_RXNE)); //wait until the RXNE-Flag is set by hardware
8
  spi_byte_received = SPI1->DR; //Read the SPI-DataRegister and by reading this register the SPI_SR_RXNE-Flag will be cleared.
9
  GPIOE->ODR |= GPIO_ODR_ODR_7; //After the transmission is complete, the SS-Pin will be set again
10
  return spi_byte_received; //Retun the value that was received via the SPI
11
}

von Korinthen Kacker (Gast)


Lesenswert?

stm32-anfänger schrieb:
> Und der Vollständigkeit halber eine Ergänzung für andere Leser, die über
> diesen Thread bzw. dieses Problem stolpern:

Beitrag "[STM32F4xx] SPI Optimierung"

(alles schon mal dagewesen)

von Doctor What (Gast)


Lesenswert?

Korinthen Kacker schrieb:
> Doctor What schrieb:
>> Im Übrigen ist ein simpley Delay (ein bzw. mehrere "nop") nicht "pöhse",
>
> Ist doch pöhse.
>
> Braucht nur ein anderes Hardware Teil deines STM32 mal dazwischen
> funken und dein sensibles Wait/Delay-Gefüge aus dem Rhythmus
> bringen, und schon hängt die SPI Maschine wieder.
>
> Kann ja sein dass du schon Delay machst während die SPI noch
> darauf wartet überhaupt starten und übertragen zu können ....


Nö, SPI ist eine State Machine. Ab Transfer ins DR-Register läuft die 
unabhängig von der anderen Hardware deterministisch.
Und worauf soll denn das SPI-Modul warten???

von Christopher J. (christopher_j23)


Lesenswert?

stm32-anfänger schrieb:
> Offenbar muss ich mir die ganze Geschichte mit dem Software-NSS und
> Hardware-NSS mal ganz ganz ganz genau durchlesen, denn offenbar lag da
> das Problem. Nachdem alles, was damit zu tun hatte auskommentiert war
> hat es zumindest nicht mehr diesen Fehler gegeben und es wird zyklisch
> über das SPI etwas herausgetaktet.

tldr;
STM32 Spi-Master -> Software-NSS nutzen

Da ich damit auch schonmal auf die Nase gefallen bin, hier noch ein Tip 
von mir:
Lass das mit dem Hardware-NSS so lange du deinen STM32 als SPI-Master 
betreibst am besten komplett bleiben und nutze Software-NSS. Selbst ST 
macht das in deren Demo-Code so und das hat definitiv seine Gründe. 
Keine Ahnung was sich ST bei der Implementierung gedacht hat aber man 
kann da die ein oder andere böse Überraschung erleben, z.B. das mit 
Hardware-NSS bei einem DMA-Transfer grundsätzlich nach jedem Byte die 
CS-Leitung getogglet wird, was man viel zu oft wahrscheinlich gar nicht 
möchte. Das ist aber zum Glück alles kein Problem, weil man - selbst bei 
Nutzung von DMA - ganz einfach jeden beliebigen GPIO-Pin als 
Software-NSS nutzen kann.

von stm32-anfänger (Gast)


Lesenswert?

Ich habe jetzt auch das ursprüngliche Problem lösen können! Es liegt 
quasi auf der Hand. Da ich nicht die korrekte Reihenfolge der Abfragen 
eingehalten hatte.
 Ich hatte ursprünglich nur auf das BSY-Flag gewartet, dann nur auf das 
TXE-Flag. Und schlussendlich nur auf das RXNE-Flag, ohne zu beachten, 
dass man es nur löschen kann, indem man das DataRegister liest.
Dadurch, dass ich mich nicht an die vorgeschriebene Reihenfolge gehalten 
habe wurde noch während das Byte aus dem Buffer in das Datenregister zum 
Senden geladen wurde an dem SS-Pin herumgepfuscht, was der Controller 
gar nicht gut fand.
Jetzt funktioniert es auch mit dem manuellen Setzen des eigentlichen 
NSS-Pins und morgen teste ich mal das automatische Setzen/Löschen.

Wenn ich Zeit habe lade ich zur Vollständigkeit auch mal ein Mitschnitt 
vom LA hoch, wo man genau sieht, dass bei fehlerhafter Reihenfolge der 
SS-Pin nur ganz kurz auf Low geht, noch bevor die eigentliche 
Übertragung gestartet wurde. Und genau DAS hatte mir letztlich das 
Genick gebrochen.

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.