91-SPI-DMA-Library (STM32F4)

Hier eine Library um die SPI-Schnittstelle im Master-Mode und per DMA-Betrieb nutzen zu könnnen.

ich habe wieder 3 identische Libs für SPI1, SPI2, SPI3 erstellt.

die SPI-Pins die benutzt werden sollen, müssen im C-File eingetragen werden
(im H-File kann der Clock-Vorteiler gewählt werden)

Das setzen der ChipSelect-Leitung muss von der Übergeordneten Funktion gemacht werden (event. muss das disable Signal in die ISR gepackt werden)

Beim initialisieren kann der Mode (Mode-0 bis Mode-3) und LSB oder MSB gewählt werden. Die Schnittstelle darf nur einmal initialisiert werden.

Zum senden und empfangen gibt es je ein Byte-Array dessen max. Größe im H-File eingestellt werden kann.

Vor dem senden müssen die Daten in das TX-Array kopiert werden. Beim senden muss die Anzahl der Daten zum senden mit übergeben werden. (1 bis Arraysize)

Das eigentliche senden/empfangen passiert dann per DMA und über eine Funktion kann geprüft werden ob das senden/empfangen fertig ist. Falls es fertig ist, wird die Anzahl der empfangenen Bytes zurückgegeben und die eigentlichen Daten stehen dann im RX-Array.

Falls die Senderoutine aufgerufen wird, aber das letzte senden noch gar nicht fertig ist, wird solange gewartet und erst im Anschluss daran gesendet.

im Beispiel wurde SPI2 benutzt mit dieser Pinbelegung :

SCK an PB13
MOSI an PB15
MISO an PB14

Voraussetzungen :

Benutzte Module der CooCox-IDE : GPIO, SPI, DMA, MISC
Benutzte Librarys : keine

Enumerationen (für SPI1) :

typedef enum {
  SPI_MODE_0_MSB = 0,  // CPOL=0, CPHA=0 (MSB-First)
  SPI_MODE_1_MSB,      // CPOL=0, CPHA=1 (MSB-First)
  SPI_MODE_2_MSB,      // CPOL=1, CPHA=0 (MSB-First)
  SPI_MODE_3_MSB,      // CPOL=1, CPHA=1 (MSB-First)
  SPI_MODE_0_LSB,      // CPOL=0, CPHA=0 (LSB-First)
  SPI_MODE_1_LSB,      // CPOL=0, CPHA=1 (LSB-First)
  SPI_MODE_2_LSB,      // CPOL=1, CPHA=0 (LSB-First)
  SPI_MODE_3_LSB       // CPOL=1, CPHA=1 (LSB-First) 
}SPI1_Mode_t;

Funktionen (für SPI1) :

ErrorStatus UB_SPI1_DMA_Init(SPI1_Mode_t mode);
ErrorStatus UB_SPI1_DMA_SendBuffer(uint32_t cnt);
uint32_t UB_SPI1_DMA_GetReceivedBytes(void);

Beispiel :

//--------------------------------------------------------------
// File     : main.c
// Datum    : 30.12.2014
// Version  : 1.0
// Autor    : UB
// EMail    : mc-4u(@)t-online.de
// Web      : www.mikrocontroller-4u.de
// CPU      : STM32F4
// IDE      : CooCox CoIDE 1.7.4
// GCC      : 4.7 2012q4
// Module   : CMSIS_BOOT, M4_CMSIS_CORE
// Funktion : Demo der SPI_DMA Library
// Hinweis  : Diese zwei Files muessen auf 8MHz stehen
//              "cmsis_boot/stm32f4xx.h"
//              "cmsis_boot/system_stm32f4xx.c"
//--------------------------------------------------------------

#include "main.h"
#include "stm32_ub_spi2_dma.h"

int main(void)
{
  SystemInit(); // Quarz Einstellungen aktivieren

  // SPI2 im Mode0 initialisieren
  UB_SPI2_DMA_Init(SPI_MODE_0_MSB);

  // TX-Puffer mit 3 Bytes Daten fuellen
  SPI2_DMA.tx_buffer[0]=0x55;
  SPI2_DMA.tx_buffer[1]=0xAA;
  SPI2_DMA.tx_buffer[2]=0x01;

  // 3Bytes per DMA senden
  UB_SPI2_DMA_SendBuffer(3);

  while(1)
  {
    if(UB_SPI2_DMA_GetReceivedBytes()>0) {
      // wenn senden fertig
      // hier die 3 Bytes vom RX-Buffer auswerten
    }
  }
}

Hier die Library zum Download :

ub_stm32f4_spi_dma_v100

Hier der komplette CooCox-Projektordner zum Download :

Demo_91_SPI_DMA

11 Antworten auf 91-SPI-DMA-Library (STM32F4)

  1. Lucas sagt:

    Hey Uwe!

    Wollte gerade mein EADOG-Display 1701 auf SPI-DMA umstellen, da ich für ein Programm eine Refreshzeit von 500ms des kompletten Displays brauche!

    Leider bin ich auf das Problem gestossen, dass die Funktion in der EADOG-Lib -> P_EADOG_SetCursor(…) den COMMAND/DATA-Pin umschaltet.

    Meine einziger “Lösungsansatz” ist, der aber nicht viel bringen wird, jeweils commands und data getrennt an den DMA zu übergeben….

    Hast du eine Idee, ob man das noch irgendwie anders machen kann. Eventuell hat der Display einen internen “Burst-mode” bei dem man keine Koordinaten angeben muss, sondern intern einfach pro empfangenen Byte automatisch die nächste Koordinate angesprungen wird? Oder kann man mit dem DMA vielleicht sogar einen normalen GPIOPin mit dem RICHTIGEN Timing zum SPI Transfer schalten(-> glaub ich nicht)?

    Würde mich freuen wenn du da helfen könntest!

    • Lucas sagt:

      Im Datenblatt vom Controller
      http://www.lcd-module.de/deu/pdf/grafik/dogs102-6.pdf
      hab ich gerade etwas gefunden, was sich sehr nach den vom mir betitelten “Burst-Mode” anhört.

      Seite 5:
      (25) Set Adv. Program Control 0

      Jetzt muss ich nur mal rausfinden wie ich die Lib dementsprechend umschreibe… Sollte aber nocht so heftig sein…

      • admin_ub sagt:

        ich hab für den ATMega eine Library für das EA-Dog Display geschrieben. Ein Byte setzt ja 8 Pixel vom LCD gleichzeitig
        (und das Display incrementiert die Adresse selbst)
        also kannst du nach jeder “Zeile” von 102 Pixel den Cursor
        um eine Page weiterschalten (das sind dann 8Pixel)
        somit brauchst du zum löschen vom kompletten LCD nur 8mal
        die Adresse neu setzen

  2. benne sagt:

    Hallo,

    Mit deiner Library für den ILI9341 kann ich bereits erfolgreich benutzen. Ist allerding zu langsam.
    Ich versuche deswegen schon lange DMA mit SPI zu benutzen um den ILI9341 bischen zu beschleunigen.
    Allerdings ohne erfolg.

    Ich habe den SPI3 Library bereits mit deinem SPI3_DMA lirary komplett ersetzt.
    ich sende vor der while() schleife einmal:
    UB_SPI3_DMA_SendBuffer(1);
    dann müsste doch das DMA permanent senden oder?

    dann habe ich in den ili9341 Library :
    UB_SPI3_SendByte(wert);
    mit
    SPI3_DMA.tx_buffer[0]=wert;
    while (SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_TXE) == RESET);
    ersetzt.

    aber kein erfolg…

    ich währe dir sehr dankbar, falls du mir da mal einen Tipp geben würdest.

    gruß
    Benne

    • admin_ub sagt:

      “was” willst du beschleunigen ?
      das senden von einem einzelnen Byte per DMA bringt 0.0% Geschwindigkeit
      (eher dauert es noch länger)
      die CPU ist viel schneller als ein einzelner SPI transfer,
      das “bremsende” element ist also deine SPI-Schnittstelle
      da bringt DMA nicht viel. Nimm ein Paralleles Display oder
      schraub den SPI Clock hoch.

      aber zu Info, das DMA Prinzip ist folgendes :
      1. den TX-Puffer mit Daten füllen (am besten so viel wie möglich)
      2. den DMA transfer starten “DMA_SendBuffer”
      3. die CPU kann jetzt was anderes machen z.B. wieder den Buffer füllen
      4. warten bis der transfer fertig ist “DMA_GetReceivedBytes”
      5. goto 2

      • benne sagt:

        Hi,

        auf der seite:
        http://stm32f4-discovery.com/2014/04/library-08-ili9341-lcd-on-stm32f429-discovery-board/
        ist auch ein library.
        Scheinbar hat er es hinbekommen es in der tat sehr schnell zu betreiben, nach einige Tipps aus den Kommentaren. (Youtube Videos aus Kommentaren)
        Nun wollte ich sein vorgehen hier auch realisieren.
        Ich wollte bei deiner Library bleiben weil ich deins viel überschaubarer und verständlicher finde.
        Nun ja dann muss ich andere Möglichkeiten finden es zu beschleunigen.
        Aber besten dank und mein Respekt, dass ud so schnell auf die Fragen reagiest!

        grüße
        Benne

        • Christian Julius sagt:

          Hallo,

          die Lib hab ich komplett zerlegt von dem unbelehrbaren Herrn Tilen (deswegen hat er mich auch gesperrt). Das einzige was beschleunigt wird sind die Rechtecke. Etwas anderes geht nicht, da die Daten zu inhomogen sind. Die SPI wird sehr schnell, wenn man bis auf 20 Mhz hoch geht. Sei vorsichtig mit den Libs, da sind einige kleine und fiese Bugs drin. die beim Keil Compiler aber nicht auffallen, sehr wohl aber beim GCC.

        • Christian Julius sagt:

          DMA bringt nur etwas bei Geräten, die zb laufend gleichartige Daten benötigen wie ein LTDC Display auf dem 429er Board, was ständig auf die 8MB SRAM zugreift, Zeile für Zeile. Darum ist dieses SRAM auch sehr langsam (10 Mal langsamer als das interne). Das ILI9341 SPI aber muss zwischendurch auch die CE leitung ziehen, manches sind 2, anderes 3, noch anderes n Bytes lang. Ich habe dieses Display auf einem kleinen stm32f103 laufen und es ist bei 20Mhz SPI so schnell, dass es echt nur kurz zuckt beim Neuaufbau. Man programmiert so, dass nur ein “Delta” gezeichnet wird und Neuzeichnen vermieden wird. zb bei Bargraphen. Ok, just my two cents

          • admin_ub sagt:

            in der Tat ist die SPI-Verbindung das “Bottle-Neck” und nicht die CPU. Aus dem Grund muß die CPU auch vor jedem erneuten senden prüfen, ob die alten Daten schon versendet wurden. Man wird nur schneller, wenn man den internen Adresscounter vom Display benutzt, dann muß man den Cursor nicht immer neu positionieren und spart dadurch SPI Trafic. Aber das geht auch nur, wenn man die Daten so schreibt, wie sich der LCD Adresscounter bewegt. Und bei diagonalen Linien funktioniert sowas gar nicht mehr.

  3. Vincent sagt:

    Moin!
    Wenn ich das richtig sehe hat sich hier ein kleiner Fehler eingeschlichen.
    Bereits im init wird der SPI per SPI_Cmd(SPI1, ENABLE) gestartet und nie wieder deaktiviert.
    In der Senderoutine beginnt das Versenden daher nicht mit der Wiederholung dieses Befehls, sondern bei DMA-TX Enable.
    Da der RX allerdings erst danach aktiviert wird kommt es ab und an zu einem Overflow und der Transfer bricht ab.
    Zuerst das RX konfiguriereren behebt den Fehler.
    Viele Grüße und vielen Dank für die ganzen Libs :)

  4. Sven sagt:

    I think it is possible to remove SPI_READY and use SPI_I2S_FLAG_TXE and SPI_I2S_FLAG_RXNE instead
    The first flag (TXE) is set when the transfer buffer is empty(data is sent and SPI is ready to send new data)
    the RNXE flag is set when there is data in receiving buffer

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *


sechs − = vier

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>