STM32F4: SPI TX DMA (ohne HAL) Problem

#5671161
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...
(Firma: fritzler-avr.de) #5671243
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 ;)
Gast #5671267
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.
Gast #5671274
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.
#5671979
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.
Gast #5671989
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.
Gast #5673062
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.
Gast #5673086
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.
#5673243
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.
(Firma: fritzler-avr.de) #5673249
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.
#5673298
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.
(Firma: fritzler-avr.de) #5673317
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.
#5673403
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren