Forum: Mikrocontroller und Digitale Elektronik STM32: SPI - HAL durch eigenen code ersetzen


von Malte _. (malte) Benutzerseite


Lesenswert?

Hallo,
ich versuche gerade den HAL Code durch selbst geschriebenen zu ersetzen. 
Ziel ist hinterher DMA zu verwenden, was leider mit der HAL Lib nicht 
funktioniert.
Leider funktioniert aber das eigentlich recht einfache SPI ohne DMA 
schon nicht mit meinem eigenem Code. Und ich kann einfach keinen Fehler 
erkennen.
Die SPI Initialisierung ist in beiden Fällen gleich, und der unten 
auskommentierte HAL Code kommuniziert einwandfrei. Auch kann ich mir mit 
PerPrint die Register ausgeben lassen und auch diese sind gleich 
eingestellt.

Bei meinem eigenem Code mache ich folgende Beobachtung:
SPI_SR_TXE ist gesetzt, ich schicke also Daten hin. SPI_SR_RXNE hingegen 
wird nie gesetzt und "RX1\r\n" folglich nie ausgegeben. Auch ein am 
Ausgabepin angeschlossenes Oszi zeigt keine gesendeten Daten an.
Die Konfigurationsregister der SPI Schnittstelle initialisiere ich mit:
1
g_spi->CR1 = SPI_CR1_MSTR | SPI_CR1_SPE | SPI_BAUDRATEPRESCALER_64 | SPI_CR1_SSM | SPI_CR1_SSI;
2
g_spi->CR2 = SPI_CR2_DS_0 | SPI_CR2_DS_1 | SPI_CR2_DS_2| SPI_CR2_FRXTH; //8Bit, 8Bit RX threshold

Der Chip ist ein STM32L452.
1
SPI_TypeDef * g_spi = (SPI_TypeDef *)SPI2_BASE;
2
3
void PerPrint(void) {
4
  printf("CR1: %08x\r\n", (unsigned int)g_spi->CR1);
5
  printf("CR2: %08x\r\n", (unsigned int)g_spi->CR2);
6
  printf("SR : %08x\r\n", (unsigned int)g_spi->SR);
7
}
8
9
void PeripheralTransfer(const uint8_t * dataOut, uint8_t * dataIn, size_t len) {
10
#if 1
11
  printf("Data %u, %x %x\r\n", len, dataOut, dataIn);
12
  uint32_t timeStop = HAL_GetTick() + 100;
13
  uint32_t txLen = len;
14
  uint32_t rxLen = len;
15
  while ((HAL_GetTick() != timeStop) && ((txLen) || (rxLen))) {
16
    //PerPrint();
17
    if ((g_spi->SR & SPI_SR_TXE) && (txLen)) { //something to transmit
18
      //printf("Wr1\r\n");
19
      uint8_t data = 0;
20
      if (dataOut) {
21
        data = *dataOut;
22
        dataOut += sizeof(uint8_t);
23
      }
24
      *(__IO uint8_t *)g_spi->DR = data;
25
      txLen -= 1;
26
    }
27
    if (g_spi->SR & SPI_SR_RXNE) { //something to receive
28
      printf("RX1\r\n");
29
      uint8_t data = *(__IO uint8_t *)g_spi->DR;
30
      if ((dataIn) && (rxLen >= 1)) {
31
        *dataIn = data;
32
        dataIn += sizeof(uint8_t);
33
        rxLen -= 1;
34
      }
35
    }
36
  }
37
  //while (g_spi->SR & SPI_FLAG_BSY);
38
#else
39
  if ((dataIn) && (dataOut)) {
40
    HAL_SPI_TransmitReceive(&hspi2, (uint8_t*)dataOut, dataIn, len, 100);
41
  } else if (dataOut) {
42
    HAL_SPI_Transmit(&hspi2, (uint8_t*)dataOut, len, 100);
43
  } else if (dataIn) {
44
    HAL_SPI_Receive(&hspi2 ,dataIn, len, 100);
45
  }
46
#endif
47
}

PS: Keine Ahnung wo die ganzen Leerzeilen im Code herkommen. Im Beitrag 
Editieren Feld sind sie nicht da.

: Bearbeitet durch User
von Erwin (Gast)


Lesenswert?

Warum sollte SPI mit DMA über die HAL nicht funktionieren?

von Kevin M. (arduinolover)


Lesenswert?

Malte _. schrieb:
> Ziel ist hinterher DMA zu verwenden, was leider mit der HAL Lib nicht
> funktioniert.

Der funktioniert ziemlich sicher mit der HAL. Habe ich aktuell mit F2, 
F3, G4, F4, F7 und H7 in Benutzung.

Hast du alle benötigten Interrupts aktiviert?

von Malte _. (malte) Benutzerseite


Lesenswert?

Kevin M. schrieb:
> Der funktioniert ziemlich sicher mit der HAL. Habe ich aktuell mit F2,
> F3, G4, F4, F7 und H7 in Benutzung.

Mein Versuch endete in einer HAL Funktion, die einfach nicht zurück 
kehrte. Meine Initialisierung war wie folgt:
1
DMA_HandleTypeDef g_hdma_spi2_tx;
2
volatile bool g_lcdDmaDone;
3
4
void LcdDmaTransferComplete(SPI_HandleTypeDef *hspi) {
5
  printfNowait("D\r\n"); //safe from interrupt
6
  g_lcdDmaDone = true;
7
}
8
9
void LcdWaitDmaDone(void) {
10
  if (hspi2.State != HAL_SPI_STATE_READY) {
11
    while (!g_lcdDmaDone);
12
    g_lcdDmaDone = false;
13
  }
14
}
15
16
void LcdInit(void) {
17
  //Copied from the STM cube generator output:
18
  __HAL_RCC_DMA1_CLK_ENABLE();
19
  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);
20
  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
21
  g_hdma_spi2_tx.Instance = DMA1_Channel5;
22
  g_hdma_spi2_tx.Init.Request = DMA_REQUEST_1;
23
  g_hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
24
  g_hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
25
  g_hdma_spi2_tx.Init.MemInc = DMA_MINC_ENABLE;
26
  g_hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
27
  g_hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
28
  g_hdma_spi2_tx.Init.Mode = DMA_NORMAL;
29
  g_hdma_spi2_tx.Init.Priority = DMA_PRIORITY_LOW;
30
  if (HAL_DMA_Init(&g_hdma_spi2_tx) != HAL_OK) {
31
    printf("Error, failed to init DMA\r\n");
32
  }
33
  __HAL_LINKDMA(&hspi2,hdmatx,g_hdma_spi2_tx);
34
  HAL_SPI_RegisterCallback(&hspi2, HAL_SPI_TX_COMPLETE_CB_ID, &LcdDmaTransferComplete);
35
}
36
37
void LcdTransfer(const uint8_t * dataOut, size_t len) {
38
  LcdCsOn();
39
  LcdWaitDmaDone();
40
  if (len <= 4) {
41
    HAL_SPI_Transmit(&hspi2, (uint8_t*)dataOut, len, 100);
42
  } else {
43
    printf("A\r\n");
44
    HAL_SPI_Transmit_DMA(&hspi2, (uint8_t*)dataOut, len);
45
    printf("B\r\n");
46
  }
47
  LcdCsOff(); //TODO: Should be done after DMA is done
48
}

Die Meldung "A\r\n" kommt, die "B\r\n" hingegen nicht.

von PittyJ (Gast)


Lesenswert?

Bei meinem H7 hat SPI mit DMA über die HAL wunderbar funktioniert. 
Gerade letzten Monat erst implementiert. Keine Probleme gehabt.

von Harry L. (mysth)


Lesenswert?

Du hast das Prinzip der Callbacks in HAL nict verstanden!

Nachlesen kannst du das u.A. hier:
https://www.st.com/resource/en/user_manual/um1786-description-of-stm32f3-hal-and-lowlayer-drivers-stmicroelectronics.pdf

Ab Seite 476 wird SPI erklärt.

Ein Beispiel von mir, wo SPI mit DMA genutzt wird, gibts hier:
Beitrag "[STM32/HAL] Treiber für ST7735 160 x 80 TFT"

: Bearbeitet durch User
von Malte _. (malte) Benutzerseite


Lesenswert?

Harry L. schrieb:
> Du hast das Prinzip der Callbacks in HAL nict verstanden!
Ok, ich hab jetzt DMA am laufen :)
Das Callback funktioniert wie von mir erwartet, was hingegen fehlte war
1
void DMA1_Channel5_IRQHandler(void)
2
{
3
  /* USER CODE BEGIN DMA1_Channel5_IRQn 0 */
4
5
  /* USER CODE END DMA1_Channel5_IRQn 0 */
6
  HAL_DMA_IRQHandler(&g_hdma_spi2_tx);
7
  /* USER CODE BEGIN DMA1_Channel5_IRQn 1 */
8
9
  /* USER CODE END DMA1_Channel5_IRQn 1 */
10
}
Den Code hatte ich beim Neugenerieren durch CubeMX übersehen, und so 
nicht ins eigene Projekt übernommen.

Es wäre zwecks Erkenntnisgewinn trotzdem schön zu wissen, was bei meinem 
eigenem SPI Code falsch ist.

von Harry L. (mysth)


Lesenswert?

Malte _. schrieb:
> Den Code hatte ich beim Neugenerieren durch CubeMX übersehen, und so
> nicht ins eigene Projekt übernommen.

Verstehe ich nicht!
Der generierte Code wird doch autom. ins Projekt übernommen...

Nutzt du nicht die CubeIDE oder hast du bei den Einstellungen von CubeMX 
was zerrissen?
Wenn das alles korrekt wäre, würde da garantiert nichts fehlen nach dem 
Code generieren.

: Bearbeitet durch User
von Malte _. (malte) Benutzerseite


Lesenswert?

Harry L. schrieb:
> Verstehe ich nicht!
> Der generierte Code wird doch autom. ins Projekt übernommen...
>
> Nutzt du nicht die CubeIDE oder hast du bei den Einstellungen von CubeMX
> was zerrissen?
> Wenn das alles korrekt wäre, würde da garantiert nichts fehlen nach dem
> Code generieren.
Naja, aus meiner Sicht generiert CubeMX eher Samplecode. Man hat zwar in 
den Quelltexten Stellen um eigenen Code einzufügen, aber wie ich auch 
nur eine extra .c Datei oder zusätzliche Include Suchpfade im Cube 
hinzufüge habe ich nicht herausgefunden. Stattdessen wird das Makefile 
einfach immer überschrieben. Selbiges gilt für Anpassungen im 
Linkerskript. Also verwalte ich mein Projekt lieber in einem separatem 
Verzeichnis.

Ich hätte die Änderungen natürlich leicht bemerkt, wenn ich das 
Ausgabeverzeichnis des CubeMX mit GIT o.ä. getrackt hätte. Dann hätte 
ein git diff direkt alle relevanten CubeMX Änderungen aufgelistet.

von Harry L. (mysth)


Lesenswert?

Malte _. schrieb:
> Naja, aus meiner Sicht generiert CubeMX eher Samplecode. Man hat zwar in
> den Quelltexten Stellen um eigenen Code einzufügen, aber wie ich auch
> nur eine extra .c Datei oder zusätzliche Include Suchpfade im Cube
> hinzufüge habe ich nicht herausgefunden. Stattdessen wird das Makefile
> einfach immer überschrieben. Selbiges gilt für Anpassungen im
> Linkerskript. Also verwalte ich mein Projekt lieber in einem separatem
> Verzeichnis.

Vollkommen falsche Vorgehensweise!
Einfach in der IDE im Project-Explorer eine neue C-Datei erzeugen und 
fertig.
Alle C-Files in deinem Projekt werden autom. verwendet.
Um das makefile musst du dich überhaupt nicht kümmern.

Und nein!
CubeMX erzeugt keinen "Sample-Code" sondern alles, was du brauchst.

Du solltest dich erstmal mit dem Umgang mit CubeIDE beschäftigen!

von STK500-Besitzer (Gast)


Lesenswert?

Harry L. schrieb:
> Einfach in der IDE im Project-Explorer eine neue C-Datei erzeugen und
> fertig.

Und selbst da kann man einen Zielordner angeben.

von Harry L. (mysth)


Lesenswert?

STK500-Besitzer schrieb:
> Harry L. schrieb:
>> Einfach in der IDE im Project-Explorer eine neue C-Datei erzeugen und
>> fertig.
>
> Und selbst da kann man einen Zielordner angeben.

Ja natürlich!

Muß man sogar.
Re-Klick auf den gewünschten Ordner -> New -> Source-File

von Malte _. (malte) Benutzerseite



Lesenswert?

Um das Rätsel nach 3 Jahren zu lösen (und für den Fall das hier jemand 
auf der Suche nach Hilfe auf den Thread stößt):

Im obigen Code wird der falsche Pointer eingetragen, aus:
1
*(__IO uint8_t *)g_spi->DR = data;
muss
1
*(__IO uint8_t *)&(g_spi->DR) = data;
werden. Das selbe beim Auslesen der Daten. Die Casterei ist notwendig, 
weil DR als 32 Bit Register in dem Struct definiert ist. Ohne Cast liest 
und Schreibt der Code 16 Bit in das FIFO Register (beim STM32L452, beim 
STM32F411 ist DR hingegen kein FIFO). Entsprechend ist dann jeder 2. 
Transfer eine 0 auf dem Bus...

Mit eigenem DMA Code habe ich es jetzt auch am laufen. Ergebnis der 
tagelangen Debuggerei:
3KiB Codeersparnis bei 6,4% höherer Geschwindigkeit gegenüber der HAL 
Lib.
Vielleicht helfen ja jemandem die angehängten Dateien.

von Harry L. (mysth)


Lesenswert?

Malte _. schrieb:
> Mit eigenem DMA Code habe ich es jetzt auch am laufen. Ergebnis der
> tagelangen Debuggerei:
> 3KiB Codeersparnis bei 6,4% höherer Geschwindigkeit gegenüber der HAL
> Lib.

Und dafür hast du jetzt 3 Jahre gebraucht?
Glückwunsch!

3kB Code-Ersparnis - geschenkt! (KiB ist im übrigen keine gültige 
Einheit für Speichergröße)
Dann lieber ordentlicher wart- portier- und lesbarer Code.

Wie du auf die 6,4% Geschwindigkeitsgewinn bei der Nutzung eines DMA 
kommst, ist mir schleierhaft.
Bei identischer Clock-Konfiguration spielt der Code absolut keine Rolle.

Achja; HAL ist keine "Lib"(du meintest sicher Library), sondern eine 
Sammlung nützliche Codeschnipsel, die bedarfsgerecht eingebunden werden.

von Cyblord -. (cyblord)


Lesenswert?

Auch wenn das nach 3 Jahren keine Rolle mehr spielen sollte: Ich habe 
gute Erfahrungen mit der Nutzung der Low-Level APIs gemacht, wenn man 
die HAL nicht komplett nutzen möchte.

Das ist letztlich ein Aufguss der StdPeriphLib aber offiziell und 
gepflegt Teil des HAL Repos.

Es ist eine gute Mischung aus übersichtlicher APIs und wenig bis gar 
kein verstecktes oder undurchsichtiges Verhalten. Perfekt um das meiste 
selbst zu implementieren. Es bringt auch kein Interrupt Handling mit.

von Bauform B. (bauformb)


Angehängte Dateien:

Lesenswert?

Malte _. schrieb:
> Die Casterei ist notwendig,
> weil DR als 32 Bit Register in dem Struct definiert ist.

Sollte man nicht lieber die Struct korrigieren, z.B. so für den 
STM32L431?

von Malte _. (malte) Benutzerseite


Lesenswert?

Harry L. schrieb:
> Und dafür hast du jetzt 3 Jahre gebraucht?
> Glückwunsch!
Na ich hab 3 Jahre den HAL Code verwendet und noch andere Sachen im 
Real-Life gemacht. Bis mir bei einem Programm ungefähr noch die Menge an 
Speicher fehlte, also habe ich das Problem wieder aufgegriffen.

> 3kB Code-Ersparnis - geschenkt! (KiB ist im übrigen keine gültige
> Einheit für Speichergröße)
https://en.wikipedia.org/wiki/Kilobyte#Binary_(1024_bytes)

> Wie du auf die 6,4% Geschwindigkeitsgewinn bei der Nutzung eines DMA
> kommst, ist mir schleierhaft.
> Bei identischer Clock-Konfiguration spielt der Code absolut keine Rolle.
Du hast recht die 6,4% sind eine unbrauchbare Angabe. Dies waren der 
Prozentsatz der gesparten CPU Zyklen bei meiner Display 
Neuzeichenfunktion (Display an SPI) samt Rendern des Framebuffers. Da 
die reine Datenübertragung tatsächlich gleich schnell ist, kommt der 
Geschwindigkeitsvorteil durch anderen Code beim Aufsetzen des DMA und 
warten auf den fertigen Transfer zustande. Der sollte aber konstant 
sein, unabhängig von der Transferlänge. Ein besseres Maß wäre daher 
gesparte CPU Zyklen pro Transfer.

Bauform B. schrieb:
> Sollte man nicht lieber die Struct korrigieren, z.B. so für den
> STM32L431?
Stimmt, die Lösung finde ich auch eleganter. Naja, das Struct war so 
auch von ST.

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