STM32H7 perikelen

Hier mijn ervaringen tot nu toe met een van ST's nieuwere producten, de 400MHz dual precision floating point STM32H7x3 series microcontroller. Ik wilde wat gaan spelen met digitale audiobewerking, plus een printje ontwerpen met al dit spul erop. Voor digitale audio heeft ST twee soorten periferie ter beschikking staan: De serial audio interface (SAI) en de wat traditionelere Serial Pheriphiral Interconnect (SPI) poorten. Mijn doel was 1 I2S ingang, en 3 I2S uitgangen, elk stereo uitgevoerd. Zowel de SAI als de SPI poort zouden dit moeten kunnen. Uiteindelijk gekozen voor de SPI poort, omdat ik hier op andere platformen ook ervaring mee heb.

Omdat de configuratie van deze uitgebreide microcontroller best ingewikkeld kan zijn, heeft ST een mooi hulpprogramma hier voor bedacht: STM32CubeMX. Door middel van deze configuatietool moet het simpel zijn om een project op te starten met de juiste settings voor alle interfaces. Een grafische weergave van je microcontroller, met alle geconfigureerde pinnen, alle instellingen netjes in overzichtelijke keuzemenu's. Tot zover alles top dus.

Een DAC en ADC op een SPI poort is natuurlijk een hele kluif om bezig te houden. Mijn gekozen sampling rate van 96kHz houdt in dat 192000 keer per seconde een 32 bit waarde naar de SPI poort moet worden gestuurd, en evenveel data er vanaf komt. Omdat ik mijn cycles liever ergens anders aan besteed, ben ik gedoken in DMA oftewel Direct Memory Access. Dit is een mechanisme om een stuk geheugen te verplaatsen naar een ander stuk geheugen. Omdat de SPI poorten geïmplementeerd zijn als geheugenaddressen, is dit ideaal om, zonder tussenkomst van de processor, de SPI poorten te voorzien van data. De SPI poort genereerd, als zijn buffer leeg begint te lopen, DMA requests, en de DMA controller zorgt ervoor dat nieuwe gegevens op het dataregister adres worden geschreven.

Toverwoord hierin is Circular Buffering. Een geheugenplaats in een toegewezen blok wordt naar het dataregister van de SPI poort geschreven, en iedere keer wordt een opeenvolgende geheugenlocatie gekozen door middel van een incremental pointer. Bij het einde van mijn gekozen blok gaat de pointer weer terug naar het begin, en zo gaat dit proces door tot in het oneindige. De truc is om je geheugenblok op het juiste koment te vullen met nieuwe gegevens. Om deze reden kan de DMA controller een interrupt genereren als deze op de helft is gekomen van mijn geheugenblok (half Transfer Complete), en bij het einde van het blok (Transfer Complete). Bij Half transfer kan de eerste helft worden voorzien van nieuwe data, en bij Transfer Complete de tweede helft.

In de eerste instantie, bij het configureren van de DMA, deed deze niets, en het heeft mij veel lezen gekost om tot de oorzaak te komen waarom dit zo was. ST heeft een mooie reference manual samengesteld voor deze serie microcontrollers, en het zal best ergens tussen deze 3179 pagina's staan. Ik heb dus gelezen dat zowel de bron als doel adressen het gehele gebied van de 4GB geheugenbereik konden omvatten (in paragraaf 15.3.8). Echter, in een schematische weergave op pagina 100 (figuur 1) komen we erachter dat deze DMA controller op de D2 AHB bus gesitueerd is. De microcontroller heeft namelijk verschillende gegevensbussen. De AXI bus is de meest snelle, en is direct verbonden met de CPU. Onder andere de SD kaart, SDRAM en Quad SPI interface zijn verbonden met deze supersnelle databus.

In het wat langzamere segement hebben we andere poorten zoals mijn SPI, SAI, I2C en een hele hoop andere interfaces. Het geheugen op deze bus die de DMA kan gebruiken, beperkt zich tot 2 SRAMs van 128KB en een van 32KB. Dat is heel wat anders dan die beloofde 4GB aanstuurbaar bereik! tot slot hebben we nog een low power, low speed D3 AHB, speciaal bedoeld om de controller in een low power omgeving te kunnen laten werken. Elk van deze domeinen heeft een eigen DMA controller, en eigen stukjes SRAM geheugen. deze staan aangegeven in het linker script (.LD bestand):

code:


/* Specify the memory areas */
MEMORY
{
DTCMRAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D1 (xrw)      : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw)      : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw)      : ORIGIN = 0x38000000, LENGTH = 64K
ITCMRAM (xrw)      : ORIGIN = 0x00000000, LENGTH = 64K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 2048K
}

Mijn SRAM was dus het RAM_D2 geheugen, die op de juiste D2 AHB bus zat. Echter, hier stopt de pret niet. Om correct door de DMA to kunnen worden gelezen moest het toegewezen geheugenblok ook op een 32 bit grenswaarde liggen. Uiteindelijk waren de definities voor dit geheugen dus:

c code:


#define __SECTION_RAM_D2 __attribute__((section(".RAM_D2"))) __attribute__((aligned (4))) /* AHB SRAM (D2 domain): */

Nadat het geheugen helemaal was uitgezocht, werkte de initialisatie van de DMA controller. Met behulp van een oscilloscoop kan ik nu nagaan hoe mijn mooie sinusgolf uit de DAC komt. Althans, dat was de bedoeling. Een hele warboel kwam eruit, alles behalve mijn verwachte mooie sinus. Een aantal redenen waren hier oorzaak van. We beginnen met..

nummer 1: Het blijkt dat het dataregister van de SPI poort, bij handmatige aansturing, prima 32 bit waardes accepteerd. Maar in geval van de DMA controller is 16 bit schijnbaar het maximum. De DMA controller leest dus een 32 bit waarde uit mijn geheugen, en schrijft deze naar de SPI in 2 16 bit waardes. Dit proces heet packing, en gaat automatisch ALS je de juiste data breedtes instelt. Het heeft een tijdje geduurdt voordat ik erachter kwam dat dit dus 16 bits (HALF_WORD) voor de SPI poort was.

Nummer 2: Endianness. Deze term duidt op de manier waarop de CPU gegevens opslaat in het geheugen. In Big Endian wordt een hex waarde van 0x34a2b710 weggeschreven als 0x34a2b710 in het geheugen. echter, deze ARM processor (en heel veel andere CPU's) is Little Endian! De waarde in het geheugen is in dit geval dus 0x10b7a234. Omdat de databreedte van mijn SPI poort 16 bit was, worden half-word waardes juist vertaald, maar een 32 bit waarde die ik gebruik, dus niet. De waardes moesten op 16 bit niveau conventioneel worden opgeslagen, maar de twee 16 bit halfword waardes in een 32 bit word moesten dus verwisseld worden. 0xa23410b7 dus. De oplossing is simpel maar voor je erachter bent hoe de data nu eigenlijk geïnterpreteerd wordt, ben je wel even bezig:

c code:


signed long shortswap(signed long val){
 return (val << 16) | ((val >> 16) & 0xffff);
}

Nummer 3: De datacache. In een processorsysteem met hogere klokfrequentie is het gebruik van een geheugencache uiterst effectief: Een cache is een klein stukje geheugen dicht in de buurt van de CPU waar de processor snel toegang tot heeft. Tijdens het vollopen van de cache worden dan waardes teruggeschreven naar het langzamere SRAM. Als de DMA controller moet lezen van dit SRAM moet je dus wel zorgen dat de data dus daar staat, en dus niet in dat cache geheugen! Nadat je dus nieuwe waardes in de circular buffer geschreven hebt, moet je de data cache flushen met het commando:

c code:


SCB_CleanDCache();

Na het implementeren van deze drie dingen kreeg ik mooie sinusvormpjes uit mijn DAC. Hoera! Maar we zijn er nog niet. Een ADC stuurt data naar dezelfde SPI poort, en deze willen we dus ook uitlezen om zo verwerkt te kunnen worden in mijn programma. In de standaard HAL (Hardware Abstraction Libraries) drivers stond geen functie die dit deed, maar in de extended versie van de I2S driver stonde de HAL_I2SEx_TransmitReceive_DMA functie die ik nodig heb. Het is mij tot nu toe nog niet gelukt om via deze functie gegevens uit de SPI's RXDR register te krijgen. Hardwarematig werkt alles prima, want als ik handmatig datzelfde RXDR register uitlees, krijg ik mooi wisselende waardes (vier stuks van 32 bits). Daarna is alles wat ik eruit krijg 0. Andere mensen hebben dezelfde of andere problemen, en ik vermoed dan ook dat de drivers voor deze microcontroller nog vol in ontwikkeling zitten.

1 electron per seconde = 16 atto ampere!

Dit is een mechanisme om een stuk geheugen te verplaatsen naar een ander stuk geheugen.

In theorie, volgens de boekjes: ja. In de praktijk, bij ST eigenlijk ook.

Maar.... ST heeft in de DMA controller op de een of andere manier de ene poort: "peripheral" en de andere "memory" genoemd.

Dus... De ST DMA controller kan van geheugen naar een randapparaat copieren. Of andersom. En het peripheral kan ook "memory" zijn als je dat echt wilt.

Het was m.i. logischer geweest om zoals jij eigenlijk dacht: memory->memory te kunnen copieren en dan toe te staan dat de ene of de andere kant een peripheral is. (waarbij je dan meestal de "inhibit pointer increment" aanzet (*).

Hmm. Ik mis dan net 1 mogelijkheid: Stel ik heb een langzame peripheral en een snelle. Of twee devices (I2S?) die structureel precies dezelfde snelheid hebben. Dan wil je misschien ook peripheral->peripheral copieren. Die kan dus denk ik niet in de ST implementatie.

(*) Maar dat kan ook in memory! Die gebruik ik voor WS2812. Een WS2812 bit is op een 2400kHz klok: <altijd 1> <een databit> <altijd nul> . Ik laat een counter tot 30 tellen op een 24MHz klok. Vanuit een timer trigger ik dan DMA-1, DMA-2, DMA-3 als de counter 1-11-21 is..... De kanalen DMA-1 en DMA-3 voor de altijd-1 en altijd-0 hebben dan een enkele geheugenlocatie waar ik gewoon allemaal eentjes of allemaal nulletjes zet.... Met minder dan 8 "chains" aan WS2812 leds moet je (zeker als je de andere GPIOS op de poort nog wilt gebruiken) de SET en CLEAR register van de GPIO module gebruiken als target van de DMA stream.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Vooruitgang!

I2S receive naar DMA werkt nu! Eerder had ik uitgelegd dat, als je data naar de SPI poort (TXDR) wilt gaan proppen met DMA, je dat per 16 bit (HALF_WORD) moet doen. De clou was, dat als ik het RXDR register handmatig lees, dit wel goed ging. Dat deed ik echter met 32 bits! Na 4 keer gelezen te hebben kreeg ik alleen maar 0 terug, omdat het interne RXFifo register in de SPI poort dus 16 bytes lang is. En dat is dus 4 keer een 32 bit WORD. De SPI poort had allang kaboem gezegd omdat de data niet hard genoeg uit het RXDR register gelezen werd, en een overflow detecteerde. Alles wat je er dan nog uitkrijgt is de data uit het Fifo register.

De oplossing is om de DMA wél met 32 bits te lezen, waar hij dus met 16 bits schrijft. De reden waarom dit goed gaat is omdat de SPI poort TX onafhankelijk van RX de DMA requests genereert. In mijn oplossing heb ik dus twee keer zoveel TX requests als RX requests, maar dat boeit me allemaal niet omdat de DMA transfers helemaal buiten de CPU om gaat. De juiste configuratie is dus:

Voor TX:

c code:


hdma_spi1_tx.Init.Request = DMA_REQUEST_SPI1_TX;
hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_spi1_tx.Init.Mode = DMA_CIRCULAR;
hdma_spi1_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
hdma_spi1_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_spi1_tx.Init.MemBurst = DMA_MBURST_SINGLE;
hdma_spi1_tx.Init.PeriphBurst = DMA_PBURST_SINGLE;

En voor RX:

c code:


hdma_spi1_rx.Init.Request = DMA_REQUEST_SPI1_RX;
hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; // YES REALLY!
hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_spi1_rx.Init.Mode = DMA_CIRCULAR;
hdma_spi1_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_spi1_rx.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
hdma_spi1_rx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_spi1_rx.Init.MemBurst = DMA_MBURST_SINGLE;
hdma_spi1_rx.Init.PeriphBurst = DMA_PBURST_SINGLE;

NU heb ik exact het probleem welke rew naar boven haalt: Ik kopieer de ontvangen data van mijn DMA geheugenblok naar het verzenden data DMA geheugenblok, en mijn signaal is "aanwezig", maar vervormt. Blijkbaar lees of schrijf ik nu data op het verkeerde moment. Omdat ik zowel lees- als schrijfbuffer effectief in tweeën heb gedeeld (door middel van mijn Half Transfer Complete en Transfer Complete), heb ik nu vier mogelijkheden:

Lees van 1e helft RXbuffer -> schrijf naar 1e helft TXbuffer.
Lees van 1e helft RXbuffer -> schrijf naar 2e helft TXbuffer.
Lees van 2e helft RXbuffer -> schrijf naar 1e helft TXbuffer.
Lees van 2e helft RXbuffer -> schrijf naar 2e helft TXbuffer.

Wordt vervolgt. Dank je rew voor de perfecte uitleg van DMA!

1 electron per seconde = 16 atto ampere!

Als je de ST handleiding leest over peripherals, dan lijkt het sterk dat die ooit gemaakt zijn voor een 16-bit CPU.

Waarom zitten er 16 IO's in GPIOA? geen 32? Raadsel!

Zo zijn alle registers in de SPI module ook 16 bits. Of op z'n minst in meer dan de helft van de peripherals en dan misschien de SPI net niet.

Dus als jij zegt dat er met max 16 bits geschreven kan worden, dan geloof ik je. Maar als je met 32 bits kan lezen dan denk ik dat 32 bits schrijven ook werkt. Zou het kunnen dat als je rekening houdt met evt byte- en word-swaps het 32bits schrijven ook werkt?

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

JE HEBT GELIJK! Nu ik 32 bit SPI RX aan de gang heb, gaat 32 bit SPI TX ook gewoon. Ik zweer het je, toen ik dat RX pad nog niet actief had, ging het met maximaal 16 bit. NU dus niet meer en kan ik hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; instellen. Bijkomend voordeel is dat ik dus ook geen shortswap(); meer hoef te doen, alle data stroomt binnen op de goede manier, en gaat dus ook zo weg. Hierdoor geen vervormd signaal meer, maar gewoon goed geluid.

Testje gedaan: Met interrupts de input buffer overzetten naar de output buffer. Hierna hangt mijn processor, maar het geluid is dus OK. Er gaat hier iets grondig mis. Klaarblijkelijk is mijn CPU volop bezig met data te swappen, terwijl ik net dacht dat ie snel was?!?!?!

Andere test: ingang en uitgangsbuffer van de DMA controller op hetzelfde bereik gezet, en de interrupt routines leeggehaald. Zoals verwacht nog steeds geluid OK, en de CPU heeft plotseling tijd voor mijn andere processen.

LEERMOMENT: De CPU heeft eigenlijk niets te zoeken in dat AHB D2 domein.

Als je kijkt naar het klokschema van de microcontroller, zie je waarom:

SYSCLK aan de linkerkant staat mooi op 400MHz, net zoals de klok in het D1 bereik. De klokfrequentie op het D2 bereik moet door HPRE en D2PRE's, met als gevolg dat er maar +/- 100MHz over is. Samen met alle overhead van de interrupt (alle CPU registers op de stack gooien enzo) kan ik me voorstellen dat er geen tijd is voor andere dingen. De CPU zal namelijk, zolang deze op de AHB D2 bus speelt, ook conform deze 100MHz klok moeten gedragen. Weg 3/4 van mijn CPU cycles!

NIEUW PLAN:
De DMA1 controller werkt goed om alle data in mijn D2 SRAM te zetten, maar ik heb de MDMA (memory DMA?) nodig om deze te verplaatsen naar het AXI SRAM. De AXI bus werkt namelijk wel op 400MHz (want: in het AHB D1 gebied) en daar kan mijn mijn CPU wel gemakkelijk bij in plaats van door een trage bus translator op 100MHz te moeten gaan.

Ik ga kijken hoe die MDMA werkt. Wordt vervolgt.

1 electron per seconde = 16 atto ampere!

Ik betwijfel of je CPU door die 100MHz bus tegen gehouden wordt. Ik vermoed een klein programmeerfoutje, waardoor in in of na de interrupt routine crasht. bij mij komt ie dan in "hardfault handler terecht.

Wat je kan doen is: aan het begin van je interrupt routine een GPIO hoog maken en aan het eind weer laag. Dan met een scoop of logic analyser kijken of het nog wiebelt en zoja hoeveel tijd er in de interrupt gehangen wordt.

Misschien is er iets als: "je bent vergeten de hardware te vertellen dat de interrupt afgehandeld is". Dan loopt ie DIRECT weer je interrupt in en.. niets te doen, dus klaar. En weer en weer. Dan zie je op de scoop of LA dus dat er zo nu en dan wat processing in die fuctie plaatsvind, maar buiten die tijd staat ie continue kort die interrupt aan te roepen.

Als je geen LA of scoop hebt, doe je gewoon een "numinterrupts++;". Als je die dan in de debugger opvraagt, zal je die VEEL te snel zien oplopen voor hoeveel interrupts je verwacht. Dit natuurlijk onder de voorwaarde dat ik weer eens goed gegokt heb. Kans dat ik zo vaak op een rij goed gok is niet heel hoog.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Zo te zien heb je al een hoop uitgezocht, maar mogelijk dat ik nog wat extra inzichten kan geven.

Je ziet al dat de DMA heel veel configuratie parameters heeft. Dit komt omdat in tegenstelling tot "gewoon geheugen lezen/schrijven" het heel belangrijk is voor peripherals hoeveel lees/schrijfacties er precies op welk adres komen. Je SPI peripheral ziet op de bus een lees access naar DR, en zal vervolgens DR volledig nieuw inladen, ongeacht hoeveel bytes je van je access gebruikt. Heb je SPI als 16 bits ingesteld, dan worden er dus 16 bits uit je SPI read FIFO gehaald en in DR klaargezet. De DMA moet dus exact weten wat voor reads/writes hij op de bus moet zetten.

Verder gaat de DMA naar een peripheral niet zomaar lezen/schrijven: Hij moet door het peripheral getriggered worden, en dit gaat buiten de APB bus om! Elk peripheral dat DMA ondersteunt via DMA1/DMA2 heeft een dedicated request lijntje naar die DMA liggen. Het plaatje wat jij van de busarchitectuur laat zien is hier niet eens nodig: de twee tabellen met request mappings uit het DMA hoofdstuk laat je precies zien welke peripheral op welke DMA aangesloten zijn, en op welke stream/channel ze zitten. Kort gezegd hangt DMA2 aan de (snellere) APB2 bus, en DMA1 aan de (langzamere) APB1 bus. Daarom zie je in de DMA2 tabel peripherals als QUADSPI en image processing, en op DMA1 I2C. (UART/SPI/timers zijn verdeeld over de twee bussen, dus tijdens je design kun je er rekening mee houden welke peripherals op een hogere snelheid moeten draaien).

De SPI peripheral zegt dus tegen de DMA wanneer er gelezen/geschreven moet worden (Dit zijn twee losstaande acties, vandaar dat er twee verschillende DMA streams nodig zijn. Je kunt bijvoorbeeld ook het lezen via DMA laten lopen, en het schrijven zonder DMA doen). Als SPI niets aangeeft, zal er niets gelezen/geschreven worden. Het SPI peripheral moet die requesten genereren, en ook daar is het een en ander aan te configureren: Als je 16-bits data over je SPI bus verstuurt, dan moet er een DMA request gegenereerd worden als de SPI FIFO half leeg is (deze is 32 bits). Vervolgens moet de DMA 16 bits schrijven. Deze settings komen ook vrij nauw: Als je bijvoorbeeld 8 bits over je SPI ontvangt, en er wordt bij 1/2 vol een DMA request gegenereerd, dan kan het voorkomen dat er voor de laatste byte uit je transfer geen DMA request wordt gegenereerd, die dus nooit meer door je DMA wordt opgehaald.

Wat je vertelt over het hangen van je processor klinkt meer als een probleem met je interrupts. Ik zou eerder verwachten dat bij het retourneren uit je interrupt je niet de correcte status bits gereset hebt, waardoor SPI of DMA weer een interrupt genereert en je dus volledig in interrupt-land houdt.

De CPU kan prima omgaan met accessen naar andere bussen. Maar deze accessen worden wel gesynchroniseerd: Een read-request naar een tragere bus wordt op bus niveau gestalld totdat de tragere bus dit verzoek heeft ingewilligd. Dit kan soms (in uitzonderingssituaties) vervelend zijn, maar het lijkt me in jouw geval niet van invloed.

In je eerste post noem je het flushen van de data cache. Die caches zijn prachtig. Vooral de instructie cache, want daar heb je in de praktijk vaak geen last van. Echter: het gebruik van de data cache is niet makkelijk. Het flushen van de data cache is /duur/. Echt heel, heel duur. Dusdanig duur dat als je een data cache flush ooit nodig hebt je beter de data cache uit kunt laten. Een andere oplossing is ervoor te zorgen dat je in de MPU instelt dat de buffer vanaf waar jij met de SPI via DMA communiceert niet gecached wordt, door een region aan te maken met caching disabled. Maar dat is echt geavanceerd gebruik wat echt leuk is als je de rest werkend hebt gekregen. Ik zou dus van harte aanbevelen om de data cache uit te laten staan.

Op 3 april 2018 15:43:34 schreef Lysergine:
Heb je SPI als 16 bits ingesteld, dan worden er dus 16 bits uit je SPI read FIFO gehaald en in DR klaargezet.

Neen.

Het SPI_DR is gevoelig voor wat voor reads je doet(*). Je kan gewoon met 8, 16 of 32-bit tegelijk lezen en dan worden er 1,2 of 4 bytes uit de FIFO getrokken.

Als je SPI instelt op 9 bits of meer (max = 16) dan krijg je woorden van 16 bits als "datablocks". Dus zou je 10 bits instellen dan krijg je niet 3x 10 bits in een 32-bit woord. De boel wordt dan op 16-bits uitgelijnd.

en je dus volledig in interrupt-land houdt.

Ja, dat zei ik ook al.

(*) Dat hangt wel een beetje af van welke versie van de SPI module je hebt. Maar ze zullen niet en oude versie in de 'H7 gezet hebben.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Op 3 april 2018 22:16:20 schreef rew:
[...]Het SPI_DR is gevoelig voor wat voor reads je doet(*). Je kan gewoon met 8, 16 of 32-bit tegelijk lezen en dan worden er 1,2 of 4 bytes uit de FIFO getrokken.

Ach, inderdaad, I stand corrected. (Let dan uiteraard wel een beetje op hoe je FTHVL configureert: zet deze hoog genoeg dat er gegarandeerd meerdere frames in DR zitten, maar doe dit niet als je een oneven aantal frames wil receiven, want dan wordt het FIFO level niet bereikt)

Op 3 april 2018 22:16:20 schreef rew:
[...](*) Dat hangt wel een beetje af van welke versie van de SPI module je hebt. Maar ze zullen niet en oude versie in de 'H7 gezet hebben.

Stiekem had ik de reference manual van STM32F76xxx gepakt, die had ik toch open staan, want hoeveel zouden die SPI modules nou schelen? Zowel de STM32F7xxx als de STM32H7x3 zijn nieuw. Nou blijkt dat de SPI module van de STM32H7x3 een 32-bits DR register heeft, en 128 of 64 bits FIFOs (afhankelijk van SPI1/2/3 of SPI4/5/6), terwijl de STMF7xxx een 16-bits DR en 32-bits FIFOs heeft... De verschillen zijn dus best fors.

Bij de F0 staat er bij:

RX threshold setting must always correspond with the read access currently used

Dat zou dus betekenen dat ze zich het recht voorbehouden om 16 bits uit de FIFO te halen als je een 8bit read doet.

Wat ik "oud" noem is stm32F1xx . Die 32bit fifo en 16 bit dataregister is wat ik als "nieuw" kende.

De CD00171190 handleiding van STM32F1xx legt uit dat je in 8bit mode gerust 16 bits kan lezen maar dan in de bovenste helft nullen krijgt. Dat is dus op de F0 (nieuwer!) anders.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Rew, ik vermoed dat je gelijk hebt. Ik doe ergens iets fout met die interrupt afhandeling, of met de registers. Wat betreft MDMA: de voorbeelden op het internet zijn.. enkelvoudig. Dat is: Er is 1 voorbeeld, van ST zelf.

MDMA. Je hebt vier varianten: Een buffer transfer. Dat zijn maximaal 128 bytes. Een block transfer zijn een verzameling aan buffer transfers achter elkaar. Dan heb je een repeated block transfer. Een reeks aan block transfers. Ten slotte is er de linked list. Deze is voor mij het meest interessant.

Met een linked list laat je een lijstje aan block transfers achter elkaar uitvoeren. Deze lijst aan block transfers in een DMA channel worden met een enkele trigger uitgevoerd. Deze linked list heb ik vooralsnog niet aan het werken gekregen. Hij blijft hangen aan het einde van de eerste block.

Wat ik wel heb laten werken is een (repeat) block transfer. Omdat er zo weinig voorbeelden te vinden zijn, hier mijn code:

c code:


  /* Configure MDMA channel MDMA_Channel0 */
  /* Configure MDMA request hmdma_mdma_channel0_dma1_stream3_tc_0 on MDMA_Channel0 */
  hmdma_mdma_channel0_dma1_stream3_tc_0.Instance = MDMA_Channel0;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.Request = MDMA_REQUEST_DMA1_Stream3_TC;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.TransferTriggerMode = MDMA_REPEAT_BLOCK_TRANSFER;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.Priority = MDMA_PRIORITY_HIGH;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.SourceInc = MDMA_SRC_INC_WORD;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.DestinationInc = MDMA_DEST_INC_WORD;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.DestDataSize = MDMA_DEST_DATASIZE_WORD;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.BufferTransferLength = 4;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.SourceBurst = MDMA_SOURCE_BURST_SINGLE;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.DestBurst = MDMA_DEST_BURST_SINGLE;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.SourceBlockAddressOffset = 0;
  hmdma_mdma_channel0_dma1_stream3_tc_0.Init.DestBlockAddressOffset = 0;
  if (HAL_MDMA_Init(&hmdma_mdma_channel0_dma1_stream3_tc_0) != HAL_OK)
  {
    Error_Handler();
  }

c code:


 if(HAL_MDMA_ConfigPostRequestMask(&hmdma_mdma_channel0_dma1_stream3_tc_0, (uint32_t)&DMA1->LIFCR, DMA_FLAG_TCIF3_7) != HAL_OK){
  Error_Handler();
 }
 HAL_MDMA_RegisterCallback(&hmdma_mdma_channel0_dma1_stream3_tc_0, HAL_MDMA_XFER_CPLT_CB_ID, MDMA_TransferCompleteCallback);
 HAL_MDMA_RegisterCallback(&hmdma_mdma_channel0_dma1_stream3_tc_0, HAL_MDMA_XFER_ERROR_CB_ID, MDMA_TransferErrorCallback);

 HAL_NVIC_SetPriority(MDMA_IRQn, 0, 0);
 HAL_NVIC_EnableIRQ(MDMA_IRQn);
 if(HAL_MDMA_Start_IT(&hmdma_mdma_channel0_dma1_stream3_tc_0, (uint32_t)&in_1_buffer_0, (uint32_t)&in_1_buffer, 1024, 1) != HAL_OK){
  Error_Handler();
 }

Er zijn dus nu twee keer zoveel voorbeelden aanwezig van MDMA.

1 electron per seconde = 16 atto ampere!

Update: Uiteindelijk heb ik het werkend gekregen, zonder MDMA. Wat ik fout deed?

- Interrupt priority toegevoegd
- Interrupts van mijn display communicatie een stuk verlaagd Refreshrate van mijn LCD is nu 10Hz (het is een 40x2 character LCD scherm)
- RX en TX I2S DMA buffer vul ik kruislings. Dus de Half Transfer (HT) verzend buffer vul ik met het Transfer Complete (TC) ontvang buffer

Het klinkt nu echt super! Ik heb altijd al geweten dat die PGA2500 best wel top zijn, maar alle volume open en geen ruis. GEEN.

Het volgende op de agenda staat FMC. Dit is de Flexible Memory Controller en ik heb em verbonden met een SDRAM van 16Mx16 (Dus 32MB). Als je dit doet, zorg er voor dat alle PCB spoortjes tussen de MCU en de SDRAM dezelfde lengte hebben. 133MHz is al aardig hoog om hier rekening mee te gaan houden.

Je krijgt dan funky kronkelspoortjes:

1 electron per seconde = 16 atto ampere!
vergeten

Golden Member

Op 7 april 2018 21:49:30 schreef Dweil:
Als je dit doet, zorg er voor dat alle PCB spoortjes tussen de MCU en de SDRAM dezelfde lengte hebben. 133MHz is al aardig hoog om hier rekening mee te gaan houden.
Je krijgt dan funky kronkelspoortjes:

Even lang kan ik me voorstellen, looptijdverschillen, maar in je bijlage/voorbeeld zitten er wel veel kronkeltjes(zelfinductie) in, die moet van ieder lijntje toch ook gelijk zijn?

Doorgaans schrijf ik duidelijk wat ik bedoel, en toch wordt het wel eens anders begrepen.

Hoe ik het zie:

Elk beetje printspoor is in weze een inductiviteit. En een capaciteit. Je zou dan ook kunnen zeggen dat je te maken hebt met een transmissielijn.

De spoorbreedte, afstand tot een plane, lengte en weerstand hebben er allemaal mee te maken. Echter: Wat heeft het meeste invloed, en wat ga je compenseren?

Een praktijksituatie is ALTIJD een compromis. Spoortjes te dicht bij elkaar kan crosstalk veroorzaken. Als je de length tuning elementen te dicht op elkaar plaatst krijg je zelfs crosstalk van je eigen signaal. Grotere componenten betekend meer parasitaire invloeden. De vraag is: is het compromis voldoende goed om je toepassing te laten werken?

Ik heb me niet zo bezig gehouden met spoorbreedte, maar dus wel met de length matching van de (meer dan 30) sporen onderling. Ook heb ik in elke verbinding een 33 ohm series terminator zitten. Als tradeoff heb ik alle componenten op 1 zijde, terwijl een dubbelzijdige componentbezetting beter zou zijn. Zoals bij elke high speed verbinding: Het helpt als je kleiner formaat componenten gebruikt, met kortere verbindingen.

1 electron per seconde = 16 atto ampere!

1 electron per seconde = 16 atto ampere!

Btw, elk electron draagt een lading van -1.6 x 10-19 = -0.16 × 10-18 coulombs, dat komt dus overeen met 0.16 atto ampere (dacht ik). :-)

Dit is een heel fijn forum met veel goede en duidelijke informatie, hier.
Goed bezig!

Hoezo kom een jaar na dato nog met zoiets? :?

MNM(tm)

Golden Member

Spoorbreedte van single-ended transmissielijnen bepalen vooral de impedantie. Mis-aanpassing daarin kan ernstige degradatie van je signaal tot gevolg hebben. Waarschijnlijk vertelt het datasheet wel wat de impedantie moet zijn. 133MHz, is dat DDR?

Cross-talk op het eigen signaal van een transmissielijn door te krappe serpentines (de lusjes) kun je voorkomen door de zogeheten self-spacing minimaal 4x (even uit mijn hoofd) de afstand tussen de signaallijn en het reference plane te kiezen. Let ook op het juiste potentiaal van het reference plane! (Sommige signalen van de DDR bus willen ground als reference, andere willen juist VDD zien).

@kruimel: sjips, je hebt gelijk. Was mij (ook) niet opgevallen dat dit topic al een jaar oud is.

Fan van Samsung (en repareer ook TV's). :)

Sjips. Ik had een scriptje wat het voor mij erg duidelijk maakt, maar dat blijkt niet meer te werken. :-(

Edit: Tampermonkey werkte niet meer. Op de "extensions page" stond dan : "this extension is disabled, do you want to reenable it?" en dan kon je clicken en gebeurde er niets. Maar click je op "remove" en daarna op "opnieuw installeren", dan ben je al je bestaande scripts kwijt. Fijn!

[Bericht gewijzigd door rew op 19 april 2019 18:56:36 (58%)]

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Op 18 april 2019 10:23:23 schreef Kruimel:
Hoezo kom een jaar na dato nog met zoiets? :?

Ik kwam dit forum nu pas tegen...

Ik gebruikte voor HAL_SPI_Transmit_DMA in plaats van __DSB() (wat bij mij niet werkte) SCB_CleanDCache_by_Addr((uint32_t*)buf, len);

Een alternatief wat ook werkt is in de MPU het gebruikte memory deel als "not cacheable" configureren, dit is wat ik prefereer:

void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct = { 0 };

/* Disable the MPU */
HAL_MPU_Disable();

/* Initialize and configure the Region and the memory to be protected */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.BaseAddress = 0x30020000;
MPU_InitStruct.Size = MPU_REGION_SIZE_8KB;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* Enable the MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);

}

Buffers gedeclareerd als volgt:

uint8_t pRXData[ETH_MAX_PACKET_SIZE] __attribute__((section(".MicrelRxArraySection"))); /* Ethernet Receive Buffer */
uint8_t pTXData[ETH_MAX_PACKET_SIZE] __attribute__((section(".MicrelTxArraySection"))); /* Ethernet Transmit Buffer */

En dat verwijst naar een aanpassing in het linker-script (8.ld)

.micrel (NOLOAD) : {
. = ABSOLUTE(0x30020000);
*(.MicrelRxArraySection)

. = ABSOLUTE(0x30021000);
*(.MicrelTxArraySection)
} >RAM_D2 AT> FLASH

Ik hoop dat dit ook helpt.