Forum: Mikrocontroller und Digitale Elektronik STM32F4: SPI TX DMA (ohne HAL) Problem


von Markus M. (adrock)


Lesenswert?

Hi,

beschäftige mich gerade mit dem STM32F4 (STM32F405RG).

Ich möchte per DMA Daten per SPI ausgeben. Ich möchte zunächst ohne HAL 
/ CubeMX arbeiten, es ist aufgrund der Einfachheit auch eigentlich nicht 
notwendig (der F405 ist nur aufgrund des ausreichenden RAMs notwendig, 
ansonsten wäre die Applikation auch mit einem F030 möglich gewesen).

Hier ist der Schnipsel:
1
  static uint8_t SPI_Data[] = { 0b01010101, 0b01010101, 0b00110011, 0b00110011 };
2
3
  // Initialize SPI
4
5
  RCC->APB2ENR |= RCC_APB2ENR_SPI1EN_Msk;
6
  RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN_Msk;
7
  SPI1->CR1 = SPI_CR1_BIDIOE_Msk|SPI_CR1_SSM_Msk|SPI_CR1_SSI_Msk|
8
    (0b001<<SPI_CR1_BR_Pos)|SPI_CR1_SPE_Msk|SPI_CR1_MSTR_Msk;
9
  SPI1->CR2 = SPI_CR2_TXDMAEN_Msk;
10
11
  // Initialize DMA2, Stream 3, Channel 3 to be used for SPI TX
12
13
  DMA2_Stream3->PAR = (uint32_t) &(SPI1->DR);
14
  DMA2_Stream3->M0AR = (uint32_t) SPI_Data;
15
  DMA2_Stream3->NDTR = sizeof(SPI_Data);
16
  DMA2_Stream3->CR = (3<<DMA_SxCR_CHSEL_Pos)|DMA_SxCR_MINC_Msk|DMA_SxCR_CIRC_Msk|
17
    (0b01<<DMA_SxCR_DIR_Pos);
18
19
  DMA2_Stream3->CR |= DMA_SxCR_EN_Msk;

Der SPI funktioniert soweit wenn ich die Daten direkt in das DR 
schreibe.

Aber der DMA deaktiviert sich nach dem aktivieren sofort wieder und 
setzt im LISR einen Transfer Error (TEIF) und Fifo Error (FEIF) für 
Stream 3.

Dabei ist der DMA FIFO garnicht aktiviert (DMDIS im S3FCR ist 0).

Irgendeine Idee? Ja ich weiß, CubeMX und HAL (_LL) existieren, aber 
eigentlich ist das völlig überzogen hier...

: Bearbeitet durch User
von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Was ich da am Code vermisse ist das löschen der Statusflags vorm DMA 
einschalten. Das muss laut DB geschene bveor der DMA kanal akriviert 
wird.

Weiterhin erst den DMA Kanal initialisieren und danach das DMA Bit in 
der Periph setzen.

Den HAL nutz ich auch nicht ;)

von Jörg S. (jax)


Lesenswert?

Egal, ob man HAL benutzt oder nicht, könnte man ihn wenigstens als gute 
oder auch schlechte Vorlage benutzen und schauen, wie's da gemacht wird.

Es hindert einen ja nix, es selbst besser zu machen.

von Claudia k. (Gast)


Lesenswert?

Jörg S. schrieb:
> Egal, ob man HAL benutzt oder nicht, könnte man ihn wenigstens als
> gute
> oder auch schlechte Vorlage benutzen und schauen, wie's da gemacht wird.
>
> Es hindert einen ja nix, es selbst besser zu machen.


So ein Blödsinn.
Um zu schauen, wie es gemacht wird, ist das Reference Manual da. Da 
steht alles Nötige drin.

von Claudia k. (Gast)


Lesenswert?

Markus M. schrieb:
> Hier ist der Schnipsel:


Lass zum besseren Lesen die _Msk Defines weg. In der Header-Datei sind 
die richtigen vorhanden.

Wozu setzt Du das BIDIOE Bit?

von Claudia k. (Gast)


Lesenswert?

Mw E. schrieb:
> Weiterhin erst den DMA Kanal initialisieren und danach das DMA Bit in
> der Periph setzen.


Genauso ist es.


> Den HAL nutz ich auch nicht ;)


Gut so. Um die paar Bits zu setzen, muss man nicht Tausende von 
Taktzyklen sinnlos verbraten.
Ist wie die Kinder mit dem 3Tonnen-SUV zum Kindergarten um die Ecke 
bringen.
Machen auch immer mehr, ist dadurch aber nicht intelligenter.

von Markus M. (adrock)


Lesenswert?

Hi,

okay, das mit der Reihenfolge werde ich mal ausprobieren.

Ich hatte sogar anfangs probiert, die HAL zu verwenden. Aber es gibt 
wohl ein Versions-Mismatch zwischen den Code/Includes die die IDE 
mitbringt und der Verison der StdPeriphLib zum Download bei ST, 
jedenfalls liess sich die HAL nicht ohne obskure Fehler übersetzen. Ohne 
HAL hatte ich dann wenigstens schonmal den SPI nach kurzer Zeit am 
Funktionieren.

Der Blick in die HAL (StdPeriphLib und CubeMX/HAL_LL) hat wieder 
gezeigt, dass es NULL Abstraktion ist, sondern nur ein Umherschieben von 
Bits / Umwandlung von enum-Werten.

Das BIDIOE ist obsolet, stimmt. Hatte vorher noch BIDIMODE gesetzt.

Das mit _Msk / _Pos ist Geschmackssache. Die Definitionen ohne _Msk habe 
ich sonst auch immer benutzt, allerdings finde ich es sprechender mit 
_Msk und _Pos.

von Grummel Grummel (Gast)


Lesenswert?

Markus M. schrieb:
> Das mit _Msk / _Pos ist Geschmackssache.

Nein.

Maske versteht "man" landläufig / weitläufig anders.

Du benutzt "Maske" zum Setzen von Bits. Dabei ist gerade
das Programmieren mit einer Maske (auf professioneller Seite)
dazu gedacht Gruppen von Bits in einem Wort zu löschen.

Man schaue sich nur diverse Header-Dateien von Atmel oder
ST an.

von Jan K. (jan_k)


Lesenswert?

Magst du mal dazu ein Beispiel zeigen? Danke

von W.S. (Gast)


Lesenswert?

Jan K. schrieb:
> Magst du mal dazu ein Beispiel zeigen?

Das ist völlig wurscht.

Fakt ist, daß man zum tatsächlichen Verstehen des geposteten Codes die 
Definitionen der verwendeten Bezeichner benötigt.

Oftmals rennen die Leute in die Falle, indem sie so einen Bezeichner 
versehentlich für ein Register verwenden, für das er nicht gedacht ist.

Aber selbst wenn Bezeichner und Register zueinander passen, ist immer 
noch die Frage, WIE der Bezeichner definiert ist. Wir hatten vor 
kurzem hier das Problem bei einer Diskussion über "Üblichkeiten" zw. 
Atmel und Cortex:
#define karlheinz (1<<9)
oder
#define karlheinz 9
was bei der Verwendung natürlich erhebliche Unterschiede macht, wobei 
man beim schieren Betrachten der Verwendungsstelle einen Bug gar nicht 
bemerken kann.

W.S.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Interessant, dass sich zum Thema Registerbit defines genau jetzt 
derjenige meldet, der eh Magic Numbers in die Register schiebt.

Siehe hier:
Beitrag "Re: STM32F3: IRQ-Prioritäten"

von Jan K. (Gast)


Lesenswert?

Aber W.S. sagt ja, dass man eben doch genau wissen muss, wie die 
Bezeichner definiert sind, also als Maske (1<<x) oder als Bitnummer x. 
Ist das die Argumentation für die Magic Numbers?

Ich denke, wenn die Bezeichner vernünftig definiert sind, sollte es klar 
sein.

Würde mich dennoch interessieren, was "Grummel Grummel (Gast)" oben 
meint und warum man keine Masken zum Bits setzen verwenden soll.

von Markus M. (adrock)


Lesenswert?

Ja, ich kann das Argument gegen "_Msk" auch nicht nachvollziehen.

Am Ende ist es natürlich identisch definiert, Beispiel aus der 
stm32f405xx.h:
1
#define DMA_LIFCR_CTCIF3_Pos     (27U)                                         
2
#define DMA_LIFCR_CTCIF3_Msk     (0x1U << DMA_LIFCR_CTCIF3_Pos)                /*!< 0x08000000 */
3
#define DMA_LIFCR_CTCIF3         DMA_LIFCR_CTCIF3_Msk

Habe inzwischen wohl die Ursache für mein Problem, den DMA Transfer-bzw. 
Bus-Error gefunden:

"CCM (core coupled memory) mapped at address 0x1000 0000 and accessible 
only by the CPU through the D-bus."

In der Tat liegen meine (DMA-) Daten in diesem Bereich. Ich muss mal 
schauen wie ich eine getrennte Section anlege, damit die Daten im 
"allgemeinen" SRAM liegen.

: Bearbeitet durch User
von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Den Fehler konnten wir jetzt nicht sehen, weil das nicht im Code steht.

Aber eigentlich sollt ers nicht sofort ins CCM packen, weil der eher 
klein ist.
Damit ers macht muss man schon am Linkerscript spielen.

Womit baust du dein Programm und zeig mal dein LInkerscript.

von Markus M. (adrock)


Lesenswert?

Habe SEGGER Embedded Studio in Benutzung.

Aber es gibt da zwei .xml Dateien:

MemoryMap:
1
<!DOCTYPE Board_Memory_Definition_File>
2
<root name="STM32F405RG">
3
  <MemorySegment name="FLASH" start="0x08000000" size="0x00100000" access="ReadOnly" />
4
  <MemorySegment name="RAM" start="0x10000000" size="0x00010000" access="Read/Write" />
5
  <MemorySegment name="RAM2" start="0x20000000" size="0x00020000" access="Read/Write" />
6
</root>


 flash_placement.xml:
1
<!DOCTYPE Linker_Placement_File>
2
<Root name="Flash Section Placement">
3
  <MemorySegment name="$(FLASH_NAME:FLASH)">
4
    <ProgramSection alignment="0x100" load="Yes" name=".vectors" start="$(FLASH_START:)" />
5
    <ProgramSection alignment="4" load="Yes" name=".init" />
6
    <ProgramSection alignment="4" load="Yes" name=".init_rodata" />
7
    <ProgramSection alignment="4" load="Yes" name=".text" />
8
    <ProgramSection alignment="4" load="Yes" name=".dtors" />
9
    <ProgramSection alignment="4" load="Yes" name=".ctors" />
10
    <ProgramSection alignment="4" load="Yes" name=".rodata" />
11
    <ProgramSection alignment="4" load="Yes" name=".ARM.exidx" address_symbol="__exidx_start" end_symbol="__exidx_end" />
12
    <ProgramSection alignment="4" load="Yes" runin=".fast_run" name=".fast" />
13
    <ProgramSection alignment="4" load="Yes" runin=".data_run" name=".data" />
14
    <ProgramSection alignment="4" load="Yes" runin=".tdata_run" name=".tdata" />
15
  </MemorySegment>
16
  <MemorySegment name="$(RAM_NAME:RAM);SRAM">
17
    <ProgramSection alignment="0x100" load="No" name=".vectors_ram" start="$(RAM_START:$(SRAM_START:))" />
18
    <ProgramSection alignment="4" load="No" name=".fast_run" />
19
    <ProgramSection alignment="4" load="No" name=".data_run" />
20
    <ProgramSection alignment="4" load="No" name=".bss" />
21
    <ProgramSection alignment="4" load="No" name=".tbss" />
22
    <ProgramSection alignment="4" load="No" name=".tdata_run" />
23
    <ProgramSection alignment="4" load="No" name=".non_init" />
24
    <ProgramSection alignment="4" size="__HEAPSIZE__" load="No" name=".heap" />
25
    <ProgramSection alignment="8" size="__STACKSIZE__" load="No" place_from_segment_end="Yes" name=".stack" />
26
    <ProgramSection alignment="8" size="__STACKSIZE_PROCESS__" load="No" name=".stack_process" />
27
  </MemorySegment>
28
  <MemorySegment name="$(FLASH2_NAME:FLASH2)">
29
    <ProgramSection alignment="4" load="Yes" name=".text2" />
30
    <ProgramSection alignment="4" load="Yes" name=".rodata2" />
31
    <ProgramSection alignment="4" load="Yes" runin=".data2_run" name=".data2" />
32
  </MemorySegment>
33
  <MemorySegment name="$(RAM2_NAME:RAM2)">
34
    <ProgramSection alignment="4" load="No" name=".data2_run" />
35
    <ProgramSection alignment="4" load="No" name=".bss2" />
36
  </MemorySegment>
37
</Root>

Man sieht also, dass die section für das 128k große RAM2 dann ".bss2" 
sein muss. Mir war diese Segmentierung nicht präsent, programmiere zum 
ersten mal einen Controller > F0.

Mit einem entsprechenden "attribute" funktioniert nun auch der DMA:
1
__attribute__ ((section(".bss2"))) static uint8_t buffer[BUF_SIZE];

Und der Hinweis dass man den DMA in der Peripherie erst aktivieren darf 
nachdem man den Stream konfguriert hat war auch goldrichtig.

Habe ich auch in der Application Note AN4031 dann gefunden. Aber auch 
dort wird das mit dem CCM RAM nicht erwähnt...

Also DANKE, DMA funktioniert nun.

: Bearbeitet durch User
von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Warum auch immer Segger .data / .bss in das CCM gepackt hat und es 
auchnoch RAM heißt.

Da dein Array laut obigem Code initialisiert ist, sollte es aber eher 
nach .data2 .

Weil der 405 hat drei RAMs.
-den Data CCM direkt am ARM Kern D-Bus
-SRAM1
-SRAM2

Die haben SRAM 1 und 2 zusammengepackt und nennen es RAM2.

Der DCCM ist für den Stack und für Datenstrukturen auf die shcnell 
zueggriffen werden muss.

SRAM1 ist der "normale" RAM.
SRAM2 nutzt man dann speziell für den DMA.

Durch die Busmatrix kann der ARM Kern auf SRAM1 zugreifen und einer der 
DMA auf SRAM2 und das gleichzeitig ohne dass sich beide in die Quere 
kommen.
Was mit dem Segger Linkereinstellungen nicht mehr geht.

von Markus M. (adrock)


Lesenswert?

Danke für die Info.

Ich werde mal versuchen es durch Modifikation der .xml Files 
entsprechend zu ändern / trennen.

Das SRAM2 ist ja relativ klein (16kB), wäre als DMA-Bereich genau 
richtig, da die Daten vorher sowieso noch aufbereitet und somit kopiert 
werden müssen.

Das mit dem festen initialisierten Array war nur ein Test.

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.