Forum: Mikrocontroller und Digitale Elektronik STM32F SPI Slave Tx Interrupt Callback löst nicht aus


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von StmGuy (Gast)


Lesenswert?

Hallo,

ich habe Probleme beim Programmieren eines STM SPI-Slaves.
Dazu habe ich mir einen Testaufbau aus zwei Nucleo-Boards aufgebaut. 
Programmiert wird in STM32CubeIde, über dessen Oberfläche auch die 
Hardware-Konfiguration vorgenommen wird.

Das erste Board ist der SPI-Master (Nucleo-F411RE):
Dieser Master sendet erst ein Datenbyte auf der MOSI-Leitung aus (8 CLK 
Takte), danach gibt es eine kurze Pause, und anschließend erzeugt der 
Master 8 weitere Takte, um ein Byte vom Slave auf der MOSI-Leitung 
einzulesen.
Hier der zugehörige Code ausgeführt:
1
  //file: main.c (SPI Master)
2
  //... Setup functions generated by STM32CubeIde
3
  while (1)
4
  {
5
      //Send 1 byte 
6
      HAL_SPI_Transmit(&hspi1, msg, 1, HAL_MAX_DELAY);
7
        
8
        //short break
9
        for(int j=0; j<300; j++)
10
        { asm volatile("NOP"); }
11
12
        //receive 1 byte
13
        HAL_SPI_Receive(&hspi1, ans, 1, HAL_MAX_DELAY);
14
15
        //wait and repeat
16
        HAL_Delay(300);
17
  }
(Die Daten, die vom Slave kommen werden auf dem Master nicht weiter 
verarbeitet, das Master-Board ist hier nur "Taktgenerator" für die 
CKL-Leitung. Der Master erzeugt die CLK-Takte und sendet Daten auf der 
MOSI-Leitung, das konnte ich am Oszi überprüfen.)

Das zweite Board ist der SPI-Slave (Nucleo-F446RE). Dieses Board soll 
warten, bis ein Command Byte vom Master empfangen wird und beim nächsten 
Takt mit seinem eigenen Byte Antworten. (Eigentlich einfach, oder?)
Da der Slave noch andere (Mess-)Aufgaben ausführt und nicht blockieren 
kann, bis der Master ein Byte anfragt, sendet muss die Kommunikation auf 
dem Slave über Interrupts stattfinden. Dazu sollen die 
HAL_SPI_Receive_IT() bzw. HAL_SPI_Transmit_IT() Funktionen verwendet 
werden.

Hier ist der relevante Code der Slave-Main:
1
//file: main.c (SPI Slave)
2
//...
3
SPI_HandleTypeDef hspi1;
4
//...
5
int main(void
6
{
7
  //..
8
  MX_SPI1_Init();
9
  //...
10
  spi1_slave_init(&hspi1);
11
  while (1)
12
  {
13
     //leerer Loop, alles geschieht über Interrupts
14
  }
15
  //..
16
}
17
  
18
//..
19
20
static void MX_SPI1_Init(void)
21
{
22
  hspi1.Instance = SPI1;
23
  hspi1.Init.Mode = SPI_MODE_SLAVE;
24
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
25
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
26
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
27
  hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
28
  hspi1.Init.NSS = SPI_NSS_SOFT;
29
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
30
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
31
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
32
  hspi1.Init.CRCPolynomial = 10;
33
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
34
  {
35
    Error_Handler();
36
  }
37
}
38
39
//...

die Funktion spi1_slave_init() ist in der Datei spi-com.c definiert, 
genauso wie die entsprechenden Interrupt-Callbacks:
1
//file: spi-com.c
2
#include "spi-com.h"
3
4
extern UART_HandleTypeDef huart2;
5
extern SPI_HandleTypeDef hspi1;
6
7
uint8_t spi_command[2] = {0x00, 0};
8
uint8_t spi_tx_buf[2] = {0x3C, 0x55};
9
10
void spi1_slave_init()
11
{
12
  HAL_SPI_Receive_IT(&hspi1, spi_command, 1);
13
}
14
15
void HAL_SPI_TxCpltCallback (SPI_HandleTypeDef * hspi)
16
{
17
  /* THIS IS NEVER EXECUTED!!! WHY?*/
18
19
  //rearm SPI Rx Interrupt
20
  HAL_SPI_Receive_IT(&hspi1, spi_command, 1);
21
}
22
23
24
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef * hspi)
25
{
26
  char buf[30];
27
  int uart_buf_len = 0;
28
  
29
  //print received SPI command
30
  uart_buf_len = sprintf(buf, "%02x\r\n", spi_command[0]);
31
  HAL_UART_Transmit(&huart2, (uint8_t *)buf, uart_buf_len, 1000);
32
  
33
  //nachdem ein byte empfangen, beim nächsten Takt ein byte senden
34
  HAL_SPI_Transmit_IT(&hspi1, spi_tx_buf, 1);
35
36
}

PROBLEM:
Zwar wird der HAL_SPI_RxCpltCallback aufgerufen, wenn ein Byte vom 
Master empfangen wurde. Obwohl im dort der Tx-Interrupt scharf 
geschalten wird (mittels HAL_SPI_Transmit_IT()) wird der zugehörige 
Tx-Callback nie aufgerufen. also:

Warum wird HAL_SPI_TxCpltCallback nicht aufgerufen?

Anmerkungen:
1. SPI Interrupts wurden in CubeMX aktiviert
2. Kommentare des automatisch erzeugten Codes habe ich hier entfernt, 
sowie alle automatisch generierten Codeteile, die nichts mit SPI zu tun 
haben.
3. Wenn in HAL_SPI_RxCpltCallback() nicht HAL_SPI_Transmit_IT() sondern 
HAL_SPI_Receive_IT() aktiviert wird läuft das Programm ohne Probleme 
weiter und reagiert auf jedes eingehende Byte. Das Problem tritt also 
erst auf, wenn ich versuche die Tx-Funktionalität zu benutzen.



Viel Text, aber über Lösungsmöglichkeiten würde ich mich sehr freuen!

PS: Für dieses Projekt bin ich auf die HAL-Libraries beschränkt. Deshalb 
bitte keine Vorschläge à la "HAL wegwerfen und alles selber in die 
Register coden" ;)

von Peter (Gast)


Lesenswert?

StmGuy schrieb:
> void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef * hspi)
> {
>   char buf[30];
>   int uart_buf_len = 0;
>
>   //print received SPI command
>   uart_buf_len = sprintf(buf, "%02x\r\n", spi_command[0]);
>   HAL_UART_Transmit(&huart2, (uint8_t *)buf, uart_buf_len, 1000);
>
>   //nachdem ein byte empfangen, beim nächsten Takt ein byte senden
>   HAL_SPI_Transmit_IT(&hspi1, spi_tx_buf, 1);
> }

Das ist sehr optimistisch programmiert.

Du rufst hier in einem Interrupt-Callback (also im Interrupt selber) 
zwei Funktionen (HAL_UART_Transmit() und HAL_SPI_Transmit_IT()) auf, die 
eine Menge Code ausführen. Kein Mensch weiss, was genau da passiert. Es 
sei denn man hat sich die Funktionen mal angschaut. Hast Du das ?
Ausserdem ist es so, dass diese HAL-Funktionen ihre eigene 
State-Maschine mitbringen.

Mach eins nach dem anderen. Setz ein Flag und starte die Routinen in der 
Haupschleife.

Gruß Peter

von ergebnisseprüfen (Gast)


Lesenswert?

Wie wäre erstmal, die Ergebnisse der HAL_ Funktionen zu prüfen.

von Harry L. (mysth)


Lesenswert?

Peter schrieb:
> HAL_SPI_Transmit_IT()

Die darf man im Callback durchaus verwenden, da die nicht blockierend 
ist.

Anders ist das mit HAL_UART_Transmit(). Das ist blockierend und hat im 
Interrupt nichts zu suchen!
Peter schrieb:
> Setz ein Flag und starte die Routinen in der
> Haupschleife.

Genau so!

Ausserdem fehlt am Anfang des Callback die obligatorische Überprüfung ob 
das übergebene hspi (SPI-Handle) tatsächlich zu dem gewünschten Port 
gehört.

Also so:
1
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef * hspi)
2
{
3
  if (hspi == myspi)
4
    {
5
      // do something
6
    }
7
}

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.