Procesor RP2040 má řadič s přímým přístupem do paměti, angl. Direct Memory Access — DMA. Představme si ho jako samostatný koprocesor, který dokáže pracovat nezávisle na hlavním procesoru. V tomto článku popíšeme a ukážeme co se s ním dá dělat. Následuje překlad z datového listu procesoru RP2040 část 2.5.
DMA (2.5)

Řadič RP2040 s přímým přístupem k paměti (DMA) má oddělená připojení hlavních jednotek pro čtení a zápis k sběrnicové strukturě a provádí hromadné přenosy dat samostatně bez hlavního procesoru. Díky tomu se procesory mohou věnovat jiným úlohám nebo přecházet do úsporných režimů. Datová propustnost DMA je také výrazně vyšší než u jednoho z procesorů RP2040.

DMA může v každém hodinovém cyklu provést jeden přístup pro čtení a jeden přístup pro zápis, až do velikosti 32 bitů. Existuje 12 nezávislých kanálů, z nichž každý dohlíží na sekvenci přenosů sběrnice, obvykle v jednom z následujících scénářů:
-
Z paměti do periferie: periferie signalizuje DMA, když potřebuje k přenosu více dat. DMA čte data z pole v RAM nebo flash paměti a zapisuje je do datového FIFO periferie.
-
Z periferie do paměti: periferie signalizuje DMA, když přijala data. DMA čte tato data z datového FIFO periferie a zapisuje je do pole v RAM.
-
Z paměti do paměti: DMA přenáší data mezi dvěma vyrovnávacími paměťmi v RAM co nejrychleji.
Každý kanál má své vlastní řídicí a stavové registry (CSR), pomocí kterých může software programovat a monitorovat jejich průběh. Pokud je aktivních více kanálů současně, DMA sdílí šířku pásma rovnoměrně mezi kanály s kruhovým obsluhováním všech kanálů, které aktuálně požadují přenos dat.
Velikost přenosu může být 32, 16 nebo 8 bitů. Konfiguruje se jednou pro každý kanál: velikost přenosu zdroje a velikost přenosu cíle jsou stejné. DMA provádí standardní replikaci bajtových linek při úzkých zápisech, takže bajtová data jsou k dispozici ve všech 4 bajtech datové sběrnice a data půlslov v obou půlslovech.
Kanály lze kombinovat různými způsoby pro sofistikovanější chování a větší autonomii. Například jeden kanál může konfigurovat jiný, načítat konfigurační data ze sekvence řídicích bloků v paměti a druhý kanál může poté volat zpět na první pomocí volby CHAIN_TO, když je potřeba jej znovu nakonfigurovat.
Díky větší autonomii DMA je zapotřebí mnohem menší dohled nad procesorem: celkově to umožňuje systému dělat více věcí najednou nebo spotřebovávat méně energie.
Konfigurace kanálů (2.5.1)
Každý DMA kanál má čtyři řídící/stavové registry:
-
READ_ADDR je ukazatel na další adresu paměti, ze které se bude číst.
-
WRITE_ADDR je ukazatel na další adresu paměti, kam se bude zapisovat.
-
TRANS_COUNT obsahuje počet zbývajících přenosů současné poslopnosti, které je nutné provést a používá se k programování počtu přenosů v další posloupnosti.
-
CTRL se používá k nastavení dalších paramtrů kanálu, k spuštění a ukončení a ke kontrole úplnosti.
Toto byly živé registry, jejich hodnoty se průběžně mění v čase, v závistosli na DMA přenosu.
Adresy čtení a zápisu (2.5.1.1)
READ_ADDR a WRITE_ADDR obsahují adresu, ze které bude kanál dále číst, respektive do které bude zapisovat. Tyto registry se automaticky aktualizují po každém přístupu pro čtení/zápis. Zvyšují se o 1, 2 nebo 4 bajty najednou v závislosti na velikosti přenosu konfigurované v CTRL.
Software by měl obecně programovat tyto registry s novými počátečními adresami pokaždé, když začíná nová sekvence přenosu. Pokud nejsou READ_ADDR a WRITE_ADDR přeprogramovány, DMA použije aktuální hodnoty jako počáteční adresy pro další přenos. Například:
-
Pokud se adresa nezvyšuje (např. je to adresa periferního FIFO) a další sekvence přenosu je na/z téže adresy, není třeba do registru znovu zapisovat.
-
Při přenosu do/z po sobě jdoucí řady vyrovnávacích pamětí v paměti (např. rozptyl a shromažďování) se adresní registr již po dokončení přenosu zvýší na začátek další vyrovnávací paměti. Tím, že se neprogramují všechny čtyři CSR pro každou přenosovou sekvenci, může software používat kratší obslužné rutiny přerušení a kompaktnější formáty řídicích bloků při použití s řetězením kanálů.
| READ_ADDR a WRITE_ADDR musí být vždy zarovnány s aktuální velikostí přenosu, jak je uvedeno v CTRL.DATA_SIZE. Je na softwaru, aby zajistil správné zarovnání počátečních hodnot. |
Počet přenosů (2.5.1.2)
Čtením z TRANS_COUNT získáte počet zbývajících přenosů v aktuální sekvenci přenosů. Tato hodnota se průběžně aktualizuje s postupem kanálu. Zápisem do TRANS_COUNT se nastaví délka další sekvence přenosů. V jedné sekvenci lze provést až \(2^{32} - 1\) přenosů.
Pokaždé, když kanál zahájí novou sekvenci přenosů, poslední hodnota zapsaná do TRANS_COUNT se zkopíruje do čítače živých přenosů, který se poté začne znovu snižovat s postupem nové sekvence přenosů. Pro účely ladění lze naposledy zapsanou hodnotu přečíst z registru DBG_TCR (hodnota pro opětovné načtení TRANS_COUNT).
Pokud je kanál spuštěn vícekrát bez zápisů do TRANS_COUNT, provede pokaždé stejný počet přenosů. Například při řetězení může jeden kanál načíst řídicí blok pevné velikosti do CSR jiného kanálu. TRANS_COUNT by byl naprogramován jednou softwarem a poté by se pokaždé automaticky znovu načetl.
Alternativně lze do TRANS_COUNT zapsat novou hodnotu před zahájením každé sekvence přenosů. Pokud je TRANS_COUNT spouštěčem kanálu (viz oddíl 2.5.2.1), kanál se spustí okamžitě a použije se právě zapsaná hodnota, nikoli hodnota aktuálně v registru pro opětovné načtení.
| TRANS_COUNT je počet přenosů, které mají být provedeny. Celkový počet přenesených bajtů je TRANS_COUNT krát velikost každého přenosu v bajtech, daná parametrem CTRL.DATA_SIZE. |
Control/status registr (2.5.1.3)
Registr CTRL má více menších polí než ostatní 3 registry a úplné podrobnosti o nich jsou uvedeny v seznamu registrů CTRL. CTRL se mimo jiné používá k:
-
Konfiguraci velikosti datových přenosů tohoto kanálu pomocí CTRL.DATA_SIZE. Čtení a zápisy mají stejnou velikost.
-
Konfiguraci, zda a jak se READ_ADDR a WRITE_ADDR po každém čtení nebo zápisu zvyšují, pomocí CTRL.INCR_WRITE, CTRL.INCR_READ, CTRL.RING_SEL, CTRL.RING_SIZE. K dispozici jsou kruhové přenosy, kde jeden z ukazatelů adresy končí na nějaké hranici mocniny 2.
-
Výběr jiného kanálu (nebo žádného), který se má spustit po dokončení tohoto kanálu, pomocí CTRL.CHAIN_TO.
-
Výběr signálu požadavku na periferní data (DREQ) pro řízení rychlosti přenosů tohoto kanálu pomocí CTRL.TREQ_SEL.
-
Zjištění, kdy je kanál nečinný, pomocí CTRL.BUSY.
-
Zkontrolujte, zda kanál nenarazil na chybu sběrnice, např. kvůli přístupu k chybné adrese prostřednictvím CTRL.AHB_ERROR, CTRL.READ_ERROR nebo CTRL.WRITE_ERROR.
Spuštění DMA kanálů (2.5.2)
DMA kanál lze spustit třemi způsoby:
-
Zápis do registru spouštěčů kanálu.
-
Řetězový spouštěč z jiného kanálu, který byl právě dokončen a má nakonfigurované pole CHAIN_TO.
-
Registr MULTI_CHAN_TRIGGER, který může spustit více kanálů najednou.
Každý z nich pokrývá různé případy použití. Například spouštěcí registry jsou jednoduché a efektivní při konfiguraci a spouštění kanálu v rutině obsluhy přerušení a CHAIN_TO umožňuje jednomu kanálu zpětné volání do jiného kanálu, který pak může překonfigurovat první kanál.
| Spuštění kanálu, který již běží, nemá žádný vliv. |
Aliasy a spouštěče
| Offset | +0x0 | +0x4 | +0x8 | +0xC (spouštěč) |
|---|---|---|---|---|
0x00 (alias 0) |
READ_ADDR |
WRITE_ADDR |
TRANS_COUNT |
CTRL_TRIG |
0x10 (alias 1) |
CTRL |
READ_ADDR |
WRITE_ADDR |
TRANS_COUNT_TRIG |
0x20 (alias 2) |
CTRL |
TRANS_COUNT |
READ_ADDR |
WRITE_ADDR_TRIG |
0x30 (alias 3) |
CTRL |
WRITE_ADDR |
TRANS_COUNT |
READ_ADDR_TRIG |
Čtyři CSR jsou v paměti několikrát aliasovány. Každý alias – jsou čtyři – zpřístupňuje stejné čtyři fyzické registry, ale v jiném pořadí. Poslední registr v každém aliasu (na offsetu +0xC, zvýrazněno) je spouštěcí registr. Zápis do spouštěcího registru spouští kanál.
Často se používá pouze alias 0 a aliasy 1-3 lze ignorovat. Kanál se konfiguruje a spouští zápisem READ_ADDR, WRITE_ADDR, TRANS_COUNT a nakonec CTRL. Protože CTRL je spouštěcí registr v aliasu 0, spouští se tímto kanál.
Ostatní aliasy umožňují kompaktnější seznamy řídicích bloků při použití jednoho kanálu ke konfiguraci jiného a efektivnější rekonfiguraci a spouštění v obslužných rutinách přerušení:
-
Každý CSR je spouštěcí registr v jednom z aliasů:
-
Při shromažďování vyrovnávacích pamětí pevné velikosti do periferního zařízení lze kanál DMA konfigurovat a spouštět zápisem pouze READ_ADDR_TRIG.
-
Při rozptylu z periferie do vyrovnávacích pamětí s pevnou velikostí lze kanál konfigurovat a spustit pouze zápisem WRITE_ADDR_TRIG.
-
-
Užitečné kombinace registrů se zobrazují jako přirozeně zarovnané n-tice, které obsahují spouštěcí registr. Ve spojení s řetězením kanálů a zalamováním adres implementují komprimované formáty řídicích bloků, např.:
-
(WRITE_ADDR, TRANS_COUNT_TRIG) pro operace rozptylu na periferii
-
(TRANS_COUNT, READ_ADDR_TRIG) pro periferní operace shromažďování nebo výpočet CRC na seznamu bufferů
-
(READ_ADDR, WRITE_ADDR_TRIG) pro manipulaci s buffery pevné velikosti v paměti
-
Spouštěcí registry nespouštějí kanál, pokud: * Kanál je deaktivován pomocí CTRL.EN. (Pokud je spouštěčem CTRL, použije se právě zapsaná hodnota EN, nikoli hodnota aktuálně v registru CTRL.) * Kanál již běží * Do spouštěcího registru se zapíše hodnota 0. (To je užitečné pro ukončení řetězců řídicích bloků. Viz nulové spouštěče, oddíl 2.5.2.3)
Řetězení DMA kanálů
Po dokončení kanálu může pojmenovat jiný kanál, který se okamžitě spustí. To lze použít jako zpětné volání pro druhý kanál k překonfigurování a restartování prvního.
Tato funkce se konfiguruje pomocí pole CHAIN_TO v registru CTRL kanálu. Tato 4bitová hodnota vybírá kanál, který se spustí po dokončení tohoto kanálu. Kanál se nemůže řetězit sám se sebou. Nastavení CHAIN_TO na vlastní index kanálu znamená, že k žádnému řetězení nedojde.
Řetězcové spouštěče se chovají stejně jako spouštěče z jiných zdrojů, jako jsou spouštěcí registry. Například způsobují opětovné načtení TRANS_COUNT a jsou ignorovány, pokud cílový kanál již běží.
Jednou z aplikací CHAIN_TO je, aby kanál vyžádal rekonfiguraci jiným kanálem ze sekvence řídicích bloků v paměti. Kanál A je konfigurován k provádění zabaleného přenosu z paměti do řídicích registrů kanálu B (včetně spouštěcího registru) a kanál B je konfigurován k řetězení zpět do kanálu A po dokončení každé sekvence přenosů. Toto je explicitněji ukázáno v příkladu řídicích bloků DMA (kapitola 2.5.6.2).
Použití aliasů registrů (kapitola 2.5.2.1) umožňuje kompaktní formáty pro řídicí bloky DMA: v některých případech i jen jedno slovo.
Dalším využitím řetězení je konfigurace „ping-pong“, kde se dva kanály vzájemně spouštějí. Procesor může reagovat na přerušení dokončení kanálu a po jeho dokončení překonfigurovat každý kanál; zřetězený kanál, který již byl nakonfigurován, se však spustí okamžitě. Jinými slovy, konfigurace kanálu a provoz kanálu jsou zřetězeny. Výkon se může dramaticky zlepšit tam, kde je vyžadováno mnoho krátkých přenosových sekvencí.
Kapitola 2.5.6 se podrobněji zabývá možnostmi řetězení spouštěčů v reálném světě.
Nulové spouštěče a řetězová přerušení (2.5.2.3.)
Jak je uvedeno v části 2.5.2.1, zápis všech nul do spouštěcího registru nespustí kanál. Tomu se říká nulový spouštěč a má dva účely:
-
Způsobit zastavení na konci pole řídicích bloků přidáním bloku všech nul
-
Snížit počet přerušení generovaných při použití řídicích bloků
Ve výchozím nastavení kanál generuje přerušení pokaždé, když dokončí přenosovou sekvenci, pokud není IRQ daného kanálu maskováno v INTE0 nebo INTE1. Četnost přerušení může být nadměrná, zejména proto, že pozornost procesoru obecně není vyžadována, během probíhající sekvence řídicích bloků; pozornost procesoru je však vyžadována na konci řetězce.
Registr CTRL kanálu má pole s názvem IRQ_QUIET. Jeho výchozí hodnota je 0. Pokud je nastavena na 1, kanály generují přerušení, když obdrží nulový spouštěč, a nikoli jindy. Přerušení je generováno kanálem, který spouštěč obdrží.
Požadavek na data (DREQ) (2.5.3)
Periferie produkují nebo spotřebovávají data vlastním tempem. Pokud by DMA jednoduše přenášel data co nejrychleji, došlo by ke ztrátě nebo poškození dat. DREQ jsou komunikačním kanálem mezi periferiemi a DMA, který umožňuje DMA řídit rychlost přenosů podle potřeb periferie.
Pole CTRL.TREQ_SEL (požadavek na přenos) vybírá externí DREQ. Lze jej také použít k výběru jednoho z interních časovačů rychlosti nebo k výběru nulového TREQ (přenos probíhá co nejrychleji), např. pro přenosy z paměti do paměti.
Systémová DREQ tabulka
Existuje globální přiřazení čísel DREQ periferním kanálům DREQ.
| DREQ | DREQ kanál | DREQ | DREQ kanál | DREQ | DREQ kanál | DREQ | DREQ kanál |
|---|---|---|---|---|---|---|---|
0 |
DREQ_PIO0_TX0 |
10 |
DREQ_PIO1_TX2 |
20 |
DREQ_UART0_TX |
30 |
DREQ_PWM_WRAP6 |
1 |
DREQ_PIO0_TX1 |
11 |
DREQ_PIO1_TX3 |
21 |
DREQ_UART0_RX |
31 |
DREQ_PWM_WRAP7 |
2 |
DREQ_PIO0_TX2 |
12 |
DREQ_PIO1_RX0 |
22 |
DREQ_UART1_TX |
32 |
DREQ_I2C0_TX |
3 |
DREQ_PIO0_TX3 |
13 |
DREQ_PIO1_RX1 |
23 |
DREQ_UART1_RX |
33 |
DREQ_I2C0_RX |
4 |
DREQ_PIO0_RX0 |
14 |
DREQ_PIO1_RX2 |
24 |
DREQ_PWM_WRAP0 |
34 |
DREQ_I2C1_TX |
5 |
DREQ_PIO0_RX1 |
15 |
DREQ_PIO1_RX3 |
25 |
DREQ_PWM_WRAP1 |
35 |
DREQ_I2C1_RX |
6 |
DREQ_PIO0_RX2 |
16 |
DREQ_SPI0_TX |
26 |
DREQ_PWM_WRAP2 |
36 |
DREQ_ADC |
7 |
DREQ_PIO0_RX3 |
17 |
DREQ_SPI0_RX |
27 |
DREQ_PWM_WRAP3 |
37 |
DREQ_XIP_STREAM |
8 |
DREQ_PIO1_TX0 |
18 |
DREQ_SPI1_TX |
28 |
DREQ_PWM_WRAP4 |
38 |
DREQ_XIP_SSITX |
9 |
DREQ_PIO1_TX1 |
19 |
DREQ_SPI1_RX |
29 |
DREQ_PWM_WRAP5 |
39 |
DREQ_XIP_SSIRX |
Schéma DREQ založené na kreditech (2.5.3.2.)
DMA RP2040 je navržen pro systémy, kde:
-
Náklady na plochu a energii velkých periferních datových FIFO jsou neúnosné
-
Požadavky na šířku pásma jednotlivých periferií mohou být vysoké, např. >50% rychlost vkládání dat do sběrnice po krátkou dobu
-
Latence sběrnice je nízká, ale o přístup ke sběrnici může soupeřit více masterů
Kromě toho přenosové FIFO a struktura duálního masteru DMA umožňují více přístupů ke stejné periferii současně, a tím se zlepšuje celková propustnost. Volba mechanismu DREQ je proto kritická:
-
Tradiční metoda „zapněte kohoutek“ může způsobit přetečení, pokud je v TDF zálohováno více zápisů. Některé systémy to řeší nadměrným zřizováním periferních FIFO a nastavením prahu DREQ pod plnou úroveň, ale to plýtvá drahocennou plochou a energií
-
Jednoduché a dávkové navazování spojení ve stylu ARM neumožňuje registraci dalších požadavků během obsluhy aktuálního požadavku. To omezuje výkon, když jsou FIFO velmi mělké.
DMA RP2040 používá mechanismus DREQ založený na kreditech. Pro každou periferii se DMA snaží udržet v provozu tolik přenosů, kolik periferie zvládne. To umožňuje plnou propustnost sběrnice (1 slovo na hodinu) přes 8násobnou periferní FIFO bez možnosti přetečení nebo podtečení, bez latence nebo soupeření v síti.
Pro každý kanál DMA udržuje čítač. Každý 1hodinový impuls na signálu dreq tento čítač zvýší (saturuje). Pokud je nenulová, kanál požaduje přenos od interního arbitra DMA a čítač se snižuje, když je přenos vydán na adresní FIFO. V tomto okamžiku je přenos v provozu, ale ještě nebyl nutně dokončen.

Důsledkem je omezení počtu přenosů za provozu na základě množství prostoru nebo dat dostupných v periferním FIFO. V ustáleném stavu to poskytuje maximální propustnost, ale nemůže dojít k podtečení nebo podtečení.
Jednou z výhrad je, že uživatel nesmí přistupovat k FIFO, které je aktuálně obsluhováno DMA. To způsobí desynchronizaci kanálu a periferního zařízení a může to vést k poškození nebo ztrátě dat.
Další výhradou je, že více kanálů by nemělo být připojeno ke stejnému DREQ.
Přerušení (2.5.4.)
Každý kanál může generovat přerušení; tato přerušení lze maskovat pro každý kanál pomocí registrů INTE0 nebo INTE1. Existují dvě situace, kdy kanál vyvolá požadavek na přerušení:
-
Po dokončení každé přenosové sekvence, pokud je CTRL.IRQ_QUIET zakázán
-
Po přijetí nulového spouštěče, pokud je CTRL.IRQ_QUIET povolen
Stav maskovaného přerušení je viditelný v registrech INTS; pro každý kanál je jeden bit. Přerušení se mažou zápisem bitové masky do INTS. Jedním z idiomů pro potvrzení přerušení je přečíst INTS a poté zapsat stejnou hodnotu zpět, takže se mažou pouze povolená přerušení.
RP2040 DMA poskytuje dva systémové IRQ s nezávislými maskovacími a stavovými registry (např. INTE0, INTE1). Jakákoli kombinace požadavků na přerušení kanálu může být směrována do kteréhokoli systémového IRQ. Například:
-
Některým kanálům lze v řadiči přerušení systému přiřadit vyšší prioritu, pokud mají obzvláště přísné požadavky na časování
-
V multiprocesorových systémech lze přerušení různých kanálů směrovat nezávisle na různá jádra
Pro účely ladění mohou registry INTF vynutit aktivaci kteréhokoli z IRQ.
Další funkce (2.5.5.)
Časovače stimulace (2.5.5.1.)
Ty umožňují přenos dat zhruba jednou za n taktů clk_sys namísto použití externího periferního DREQ ke spouštění přenosů. Používá se zlomkový dělič (X/Y), který vygeneruje maximálně 1 požadavek na cyklus clk_sys.
V RP2040 jsou k dispozici 4 časovače. Každý DMA si může vybrat kterýkoli z nich v CTRL.TREQ_SEL.
Výpočet CRC (2.5.5.2.)
DMA může sledovat data z daného kanálu procházející datovým FIFO a na základě těchto dat vypočítávat kontrolní součty. Jedná se o čistě pasivní záležitost: data nejsou tímto hardwarem měněna, pouze pozorována.
Tato funkce je ovládána pomocí registrů SNIFF_CTRL a SNIFF_DATA a lze ji povolit/zakázat pro každý přenos DMA pomocí pole CTRL.SNIFF_EN.
Protože tento hardware nemůže vyvíjet zpětný tlak na FIFO, musí udržet krok s maximální přenosovou rychlostí DMA 32 bitů za hodinu. Podporované kontrolní součty jsou:
-
CRC-32, nejdříve MSB a nejdříve LSB
-
CRC-16-CCITT, nejdříve MSB a nejdříve LSB
-
Jednoduché sčítání (přičtení k 32bitovému akumulátoru)
-
Sudá parita
Registr výsledků je čitelný i zapisovatelný, takže lze nastavit počáteční hodnotu.
S výsledkem jsou k dispozici manipulace s bity/bajty, které mohou pomoci v konkrétních případech použití:
-
Inverze bitů
-
Obrácení bitů
-
Výměna bajtů
Tyto manipulace neovlivňují výpočet CRC, pouze způsob, jakým jsou data prezentována ve výsledném registru.
2.5.5.3. Přerušení kanálu
Je možné, že se kanál dostane do neobnovitelného stavu: např. pokud je zadán příkaz k přenosu většího množství dat, než periférie kdy požaduje, přenos se nikdy nedokončí. Vymazání bitu CTRL.EN pouze pozastaví kanál a problém se tím nevyřeší. K tomu by za normálních okolností nemělo docházet, ale je důležité, aby existoval mechanismus pro obnovení bez pouhého tvrdého resetu celého bloku DMA.
Registr CHAN_ABORT vynutí předčasné dokončení kanálů. Pro každý kanál je jeden bit a zápis 1 ukončí daný kanál. Tím se vymaže čítač přenosů a kanál se přepne do neaktivního stavu.
| Kvůli RP2040-E13 může přerušení kanálu DMA, který probíhá (tj. není zablokován na neaktivním DREQ), způsobit vyvolání IRQ pro dokončení. Povolení přerušení kanálu by mělo být před provedením přerušení vymazáno a přerušení by mělo být po přerušení zkontrolováno a vymazáno. |
V okamžiku spuštění přerušení může kanál mít aktuálně probíhající přenosy sběrnice mezi masterem pro čtení a zápis a tyto přenosy nelze zrušit. Příznak CTRL.BUSY zůstává na vysoké úrovni, dokud tyto přenosy nedokončí a kanál nedosáhne bezpečného stavu, což obvykle trvá jen několik cyklů. Kanál nesmí být restartován, dokud jeho příznak CTRL.BUSY nezruší. Spuštění nové sekvence přenosů, zatímco přenosy ze staré sekvence stále probíhají, může vést k nepředvídatelnému chování.