Vaja 7: DMA
Namen tokratne vaje je, da se spoznate s konceptom neposrednega dostopa V/I naprav do pomnilnika ter prenosom med lokacijami v pomnilniku brez CPE.
Neposreden dostop do pomnilnika
Na dosedanjih vajah je v prenosu med registrom vhodno/izhodne naprave vedno posredovala centralno procesna enota. Na zadnji vaji smo na primer izvedli ukaz:
uint8_t nov_znak;
// ...
= USART3->RDR nov_znak
Iz registra RDR
naprave USART3
, se vsebina najprej prenese v register centralno-procesne enote, nato pa shrani v pomnilnik na lokacijo globalne spremenljivke. Temu pristopu rečemo programski vhod/izhod (z ali brez uporabe prekinitev).
Če želimo CPE razbremeniti prenašanja podatkov med registri in pomnilnikom, da v tem času lahko počne kaj drugega, lahko uporabimo pristop, ki mu rečemo neposredni dostop do pomnilnika (angl. Direct Memory Access). V tem primeru za prenose med V/I napravi in pomnilnikom skrbi ločena naprava - naprava DMA. Prenosi lahko potekajo v od V/I naprave proti pomnilniku ali obratno, kot tudi iz pomnilnika v pomnilnik (med dvemi lokacijami v pomnilniku).
Uporabnik mora za izvedbo prenosa inicializacirati DMA krmilnik. Za to je potrebno nastaviti izvorni in ciljni naslov (iz kje in kam prenašamo), število byte-ov v prenosu ter način prenosa. Ko so podatki na izvorni strani pripravljeni, DMA zaprosi CPE za prevzem vodila in s tem postane gospodar vodila. Po zaključku prenosa DMA naprava sproži prekinitev.
Za boljšo učinkovitost, DMA naprava običajno ne prenaša vsakega byte-a/worda posebej, ampak si jih shranjuje v medpomnilnik (angl. FIFO buffer), ki jih potem shrani na ciljno lokacijo v kosu.
DMA v STM32H750
V STM32H750 imamo na voljo dve DMA napravi: DMA1
in DMA2
. Obe omogočota prenos med V/I napravo in pomnilnikom, med dvemi V/I napravami kot tudi prenos od pomnilnika do pomnilnika. Slika 8.1 prikazuje poenostavljeno shemo DMA naprav v kontrolerjih STM32H7. Vsaka izmed DMA naprav podpira osem tokov (angl. streams). Tok/Stream je aktiven DMA prenos med izvorom in ciljem. Izvor DMA zahtev za posamezen tok izberemo v t.i. DMAMUX bloku, kjer imamo osem 128-na-1 multiplekserjev. V STM32H750 je sicer v praksi na voljo 115 od 128ih vhodnih linij multiplekserja.
Tokove upravlja arbiter, ki na podlagi prioritete določa kateri izmed njih je v danem trenutku aktiven. Prioriteto lahko definiramo na štirih nivojih: zelo visoka (angl. very high), visoka (high), srednja (medium), nizka (low). V primeru, da sta dve aktivni zahtevi po prenosu na dveh tokovih, arbiter izbere tok z višjo prioriteto. V primeru enakih prioritet, ima prednost tok z nižko številsko oznako.
Ko je V/I naprava pripravljena, pošlje DMA zahtevo v DMAMUX blok, iz katerega gre ta proti izbranemu DMA toku. Arbiter potem odloči kdaj bo izbrani DMA tok na vrsti za izvedbo prenosa.
Nastavitve prenosa
Za izvedbo DMA prenosa v STM32H7 moramo definirati sledeče nastavitve:
- izvorni in ciljni (ponorni) naslov
Določata kje naj DMA naprava bere podatke in kam naj jih pošilja. Če prenos poteka iz V/I naprave v pomnilnik, je izvorni naslov register naprave (na primer register RDR
naprave USART
), ciljni pa lokacija v pomnilniku. V primeru prenosa v obratni smeri naprava bere podatek iz pomnilnika in ga pošilja v register V/I naprave. V primeru prenosa pomnilnik-pomnilnik sta oba naslova lokaciji v pomnilniku.
- dolžina prenosa in velikost podatkov
Določamo koliko podatkov želimo prenesti in koliko je velik posamezen podatek - byte, halfword (16-bitov), word (32-bitov). Velikost podatkov definiramo tako za izvorno kot ciljno stran.
- inkrementiranje naslovov
Za oba naslova lahko izberemo, da po uspešno zaključenem prenosu podatka DMA naprava avtomatsko poveča izvorni ali ciljni naslov. To je uporabno v primeru, da pošiljamo ali shranjujemo podatek v polju.
- izbrani tok/stream
Izberemo enega od osmih tokov, ki ga želimo uporabiti.
- smer in naprava, ki bo zahtevala prenos
Smer je lahko od V/I naprave do pomnilnika, od pomnilnika do V/I naprave in pomnilnik-pomnilnik. V primeru, da bo DMA zahteve prožila V/I naprava, je to napravo potrebno tudi določiti.
- način prenosa
Imamo dve možnosti prenosa: normalen/običajen (angl. normal) in krožni (angl. circular). Pri normalnem prenosu se prenos zaključi ko se prenese izbrano število podatkov. Pri krožnem pa
- prioriteta
Določimo pomembnost toka. Kot že rečeno, imamo štiri nivoje prioritete: zelo visoka (angl. very high), visoka (high), srednja (medium), nizka (low).
- uporaba FIFO medpomnilnika (angl. FIFO buffer)
FIFO buffer ima prostora za 4 32-bitne podatke. Če buffer uporabimo, je potrebno nastaviti tudi pri koliko podatkih v buffer-ju ga izpraznimo (pošljemo podatke na cilj). Lahko ga praznimo pri 1/4, 1/2, 3/4 polnem bufferju ali šele ko je buffer povsem poln.
Programski vmesnik za DMA prenose
Kot vedno do sedaj je prvi korak vklop ure izbrane DMA naprave s klicem makroja __HAL_RCC_DMAx_CLK_ENABLE()
. Sledi inicializacija DMA naprave in izbrani DMA tok/stream. Za to uporabimo strukturo DMA_HandleTypeDef
, pri katerem sta pomembna elementa Instance
, v katerem izberemo DMA napravo in stream. Možnosti so: DMA1_Stream0
, DMA1_Stream1
, DMA1_Stream2
, DMA1_Stream3
, DMA1_Stream4
, DMA1_Stream5
, DMA1_Stream6
, DMA1_Stream7
, DMA2_Stream0
, DMA2_Stream1
, DMA2_Stream2
, DMA2_Stream3
, DMA2_Stream4
, DMA2_Stream5
, DMA2_Stream6
, DMA2_Stream7
.
Drugi element strukture je Init
, v katerem določimo nastavitve prenosa:
Request
: pri prenosih pomnilnik-pomnilnik izberemoDMA_REQUEST_MEM2MEM
. V primeru prenosa med V/I napravo in pomnilnikom v katerokoli smer, pa določimo napravo, ki bo prožila zahteve. Primera:DMA_REQUEST_USART1_RX
- zahteve se bodo prožile ob prejetih podatkih na napraviUSART1
,DMA_REQUEST_USART1_TX
- zahteve se bodo prožile vsakič ko bo napravaUSART1
pripravljena na prenos.Direction
: določimo smer prenosa. Možne nastavitve:DMA_PERIPH_TO_MEMORY
,DMA_MEMORY_TO_PERIPH
,DMA_MEMORY_TO_MEMORY
.PeriphInc
: Vklopimo/izklopimo povečevanje naslova na strani periferne V/I naprave:DMA_PINC_ENABLE
,DMA_PINC_DISABLE
. V primeru prenosa pomnilnik-pomnilnik se ta nastavitev uporabi za ciljni naslov.MemInc
: Vklopimo/izklopimo povečevanje naslova na strani pomnilnika:DMA_MINC_ENABLE
,DMA_MINC_DISABLE
. V primeru prenosa pomnilnik-pomnilnik se ta nastavitev uporabi za izvorni naslov.PeriphDataAlignment
,MemDataAlignment
: Velikost posameznega podatka:DMA_MDATAALIGN_BYTE
,DMA_MDATAALIGN_HALFWORD
,DMA_MDATAALIGN_WORD
.Mode
: Izberemo način prenosa:DMA_NORMAL
,DMA_CIRCULAR
.Priority
: Prioriteta toka:DMA_PRIORITY_LOW
,DMA_PRIORITY_MEDIUM
,DMA_PRIORITY_HIGH
,DMA_PRIORITY_VERY_HIGH
.FIFOMode
: Vklopimo/izklopimo uporabo FIFO medpomnilnika:DMA_FIFOMODE_ENABLE
,DMA_FIFOMODE_DISABLE
.FIFOThreshold
: Meja pri kateri se FIFO buffer izprazni -DMA_FIFO_THRESHOLD_1QUARTERFULL
,DMA_FIFO_THRESHOLD_HALFFULL
,DMA_FIFO_THRESHOLD_3QUARTERSFULL
,DMA_FIFO_THRESHOLD_FULL
.
Za zagon prenosa uporabimo funkcijo HAL_DMA_Start
.
Primer prenosa pomnilnik - pomnilnik
Primer brez uporabe prekinitev:
();
__HAL_RCC_DMA1_CLK_ENABLE
= {0};
DMA_HandleTypeDef dma1_struct
.Instance = DMA1_Stream0;
dma1_struct.Init.Request = DMA_REQUEST_MEM2MEM;
dma1_struct.Init.Direction = DMA_MEMORY_TO_MEMORY;
dma1_struct.Init.PeriphInc = DMA_PINC_ENABLE;
dma1_struct.Init.MemInc = DMA_MINC_ENABLE;
dma1_struct.Init.PeriphDataAlignment = DMA_MDATAALIGN_WORD;
dma1_struct.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
dma1_struct.Init.Mode = DMA_NORMAL;
dma1_struct.Init.Priority = DMA_PRIORITY_LOW;
dma1_struct.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
dma1_struct.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_1QUARTERFULL;
dma1_struct(&dma1_struct);
HAL_DMA_Init
// source in destination definiramo kot polji dolzine SIZE
// uint32_t source[SIZE];
// uint32_t destination[SIZE];
// SIZE * 4, ker je dolžina prenosa podana v bytih
(&dma1_struct, (uint32_t)source, (uint32_t)destination, SIZE * 4); HAL_DMA_Start
Po zagonu DMA prenosa lahko na konec prenosa počakamo s funkcijo HAL_DMA_PollForTransfer
:
// čakamo dokler se prenos ne konča
(&dma1_struct, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY); HAL_DMA_PollForTransfer
Obstaja tudi možnost uporabe prekinitev. V tem primeru DMA stream ob zaključku prenosa sproži prekinitev.
Naloga 6.1
Izmerite čas prenosa 32kB in 50kB podatkov med dvemi lokacijami v pomnilniku z zanko in DMA prenosom (s FIFO bufferjem in brez). Vseh šest izmerjenih časov (32kB zanka, 50kB zanka, 32kB DMA brez FIFO, 50kB DMA brez FIFO, 32kB DMA s FIFO, 50kB DMA s FIFO) zapišite v komentar programa. Za merjenje časa uporabite spodaj pripravljene funkcije. Merjenje časa ponovite večkrat in zapišite povprečje.
Merjenje časa
Spodnje funkcije vam omogočajo merjenje časa v mikrosekundah. Za delovanje je potrebno odkomentirati HAL_TIM_MODULE_ENABLED
v stm32h7xx_hal_conf.h
.
Uporaba spodnjih funkcij:
();
start_timer(200);
HAL_Delay();
stop_timer// izmeriti bi morali okoli 200 000 mikrosekund
// (zaradi nenatancosti HAL_Delay na nivoju mikrosekund jih boste izmerili nekaj vec)
uint32_t izmerjen_cas_v_us = get_time();
void start_timer() {
();
__HAL_RCC_TIM2_CLK_ENABLE= {0};
TIM_HandleTypeDef timer .Instance = TIM2;
timer.Init.CounterMode = TIM_COUNTERMODE_UP;
timer.Init.Period = 100000000;
timer.Init.Prescaler = 64 -1;
timer(&timer);
HAL_TIM_Base_Init
(&timer);
HAL_TIM_Base_Start(&timer, 0);
__HAL_TIM_SetCounter}
void stop_timer() {
= {0};
TIM_HandleTypeDef timer .Instance = TIM2;
timer(&timer);
HAL_TIM_Base_Stop}
uint32_t get_time() {
= {0};
TIM_HandleTypeDef timer .Instance = TIM2;
timerreturn __HAL_TIM_GetCounter(&timer);
}
Dodatno - Primer z uporabo prekinitev:
DMA prenos z uporabo prekinitev:
// definirajte kot globalno spremenljivko
= {0};
DMA_HandleTypeDef dma1_struct
();
__HAL_RCC_DMA1_CLK_ENABLE
.Instance = DMA1_Stream0;
dma1_struct.Init.Request = DMA_REQUEST_MEM2MEM;
dma1_struct.Init.Direction = DMA_MEMORY_TO_MEMORY;
dma1_struct.Init.PeriphInc = DMA_PINC_ENABLE;
dma1_struct.Init.MemInc = DMA_MINC_ENABLE;
dma1_struct.Init.PeriphDataAlignment = DMA_MDATAALIGN_WORD;
dma1_struct.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
dma1_struct.Init.Mode = DMA_NORMAL;
dma1_struct.Init.Priority = DMA_PRIORITY_LOW;
dma1_struct.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
dma1_struct.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_1QUARTERFULL;
dma1_struct(&dma1_struct);
HAL_DMA_Init
// vsak stream ima svojo prekinitveno oznako
(DMA1_Stream0_IRQn, 10, 0);
HAL_NVIC_SetPriority(DMA1_Stream0_IRQn);
HAL_NVIC_EnableIRQ
// source in destination definiramo kot polji dolzine SIZE
// uint32_t source[SIZE];
// uint32_t destination[SIZE];
(&dma1_struct, (uint32_t)source, (uint32_t)destination, SIZE);
HAL_DMA_Start_IT
// ...
// ...
void DMA1_Stream0_IRQHandler(void) {
// dma1_struct je struktura tipa DMA_HandleTypeDef
// HAL_DMA_IRQHandler pobriše zastavice in nastavi
// element State glede na prebrane zastavice
(&dma1_struct);
HAL_DMA_IRQHandler// preverimo da se je prenos uspešno zaključil
if (dma1_struct.State == HAL_DMA_STATE_READY) {
// ...
}
}