Vaja 7: DMA & UART

Namen tokratne vaje je, da se spoznamo s tem kako lahko uporabimo DMA za pošiljanje nizov preko naprave U(S)ART.

Prekinitve DMA

Vsak stream obeh DMA naprav ima svojo prekinitveno oznako. Spodaj je zapisana tabela vseh prekinitvenih oznak ter prekinitveno servisnih programov.

Prekinitvena oznaka Ime PSP-ja
DMA1_Stream0_IRQn DMA1_Stream0_IRQHandler
DMA1_Stream1_IRQn DMA1_Stream1_IRQHandler
DMA1_Stream2_IRQn DMA1_Stream2_IRQHandler
DMA1_Stream3_IRQn DMA1_Stream3_IRQHandler
DMA1_Stream4_IRQn DMA1_Stream4_IRQHandler
DMA1_Stream5_IRQn DMA1_Stream5_IRQHandler
DMA1_Stream6_IRQn DMA1_Stream6_IRQHandler
DMA1_Stream7_IRQn DMA1_Stream7_IRQHandler
DMA2_Stream0_IRQn DMA2_Stream0_IRQHandler
DMA2_Stream1_IRQn DMA2_Stream1_IRQHandler
DMA2_Stream2_IRQn DMA2_Stream2_IRQHandler
DMA2_Stream3_IRQn DMA2_Stream3_IRQHandler
DMA2_Stream4_IRQn DMA2_Stream4_IRQHandler
DMA2_Stream5_IRQn DMA2_Stream5_IRQHandler
DMA2_Stream6_IRQn DMA2_Stream6_IRQHandler
DMA2_Stream7_IRQn DMA2_Stream7_IRQHandler

Razlogi za proženje prekinitev so:

  • prenos se je zaključil (DMA_IT_TC),
  • pri prenosu je prišlo do napake (DMA_IT_TE, DMA_IT_DME),
  • polovica prenosa je opravljena (DMA_IT_HT).

Prekinitev ob polovici prenosa se proži le v primeru izrecnega vklopa, ostale tri pa se avtomatsko vklopijo če uporabimo funkcijo HAL_DMA_Start_IT() namesto HAL_DMA_Start() ali uporabimo DMA funkcije posameznih naprav (eno od njih bomo uporabili spodaj).

V PSP-ju DMA toka se bomo torej znašli ker se je prenos končal, bodisi uspešno ali neuspešno. Knjižnica HAL ponuja funkcijo HAL_DMA_IRQHandler, ki jo kličemo znotraj PSP-ja. S tem poskrbimo, da se pobrišejo zastavice in posodobi stanje DMA naprave v elementu State. Prekinitveno servisne programe bomo torej pisali tako, kot je prikazano spodaj:

void DMA1_Stream0_IRQHandler(void) {
  // dma1_struct je struktura tipa DMA_HandleTypeDef
    HAL_DMA_IRQHandler(&dma1_struct);
  // preverimo da se je prenos uspešno zaključil
  if (dma1_struct.State == HAL_DMA_STATE_READY) {
    // v primeru uspešnega prenosa
    // se znajdemo tu
  }
}

Primer prenosa pomnilnik -> USART3

Za uporabo naprave USART3 v navezavi z napravo DMA, moramo najprej inicializirati napravo USART3, kot smo to naredili na zadnji vaji:

__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef init_structure;
init_structure.Pin = GPIO_PIN_10 | GPIO_PIN_11;
init_structure.Mode = GPIO_MODE_AF_PP;
init_structure.Pull = GPIO_NOPULL;
init_structure.Speed = GPIO_SPEED_FREQ_LOW;
init_structure.Alternate = GPIO_AF7_USART3;
HAL_GPIO_Init(GPIOB, &init_structure);

__HAL_RCC_USART3_CLK_ENABLE();
uart.Instance = USART3;
uart.Init.BaudRate = 115200;
uart.Init.WordLength = UART_WORDLENGTH_8B;
uart.Init.StopBits = UART_STOPBITS_1;
uart.Init.Parity = UART_PARITY_NONE;
uart.Init.Mode = UART_MODE_TX_RX;
uart.Init.HwFlowCtl = UART_HWCONTROL_NONE;
HAL_UART_Init(&uart);

Nato inicializiramo DMA napravo in stream, kot je prikazano spodaj. Spremembe glede na prenos pomnilnik-pomnilnik so:

  • Request nastavimo tako, da bo naprava USART3 prožila DMA prenose,
  • Direction nastavimo na pomnilnik -> V/I naprava,
  • PeriphInc izklopimo, ker naprava USART vedno pošilja iz istega naslova (TDR).
// globalni strukturi
DMA_HandleTypeDef dma1_struct = {0};
UART_HandleTypeDef uart;

// v main() funkciji:
__HAL_RCC_DMA1_CLK_ENABLE();

dma1_struct.Instance = DMA1_Stream0;
dma1_struct.Init.Request = DMA_REQUEST_USART3_TX;
dma1_struct.Init.Direction = DMA_MEMORY_TO_PERIPH;
dma1_struct.Init.PeriphInc = DMA_PINC_DISABLE;
dma1_struct.Init.MemInc = DMA_MINC_ENABLE;
dma1_struct.Init.PeriphDataAlignment = DMA_MDATAALIGN_BYTE;
dma1_struct.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
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;
HAL_DMA_Init(&dma1_struct);

Nadalje “povežemo” DMA strukturo s tisto od naprave UART ter vklopimo prekinitve za DMA in USART3 napravo.

__HAL_LINKDMA(&uart, hdmatx, dma1_struct);

Dodati moramo še prekinitveno servisna programa za napravi DMA, kjer zgolj kličemo HAL-ovo funkcijo HAL_DMA_IRQHandler, ki pobriše zastavice in v primeru uspešnega prenosa ponastavi napravi UART in DMA, da omogočata nadaljni prenos. Za primer glej prvo poglavje v vaji.

DMA prenos nato sprožimo s klicem funkcije HAL_UART_Transmit_DMA:

uint8_t txt[] = "DMA test\r\n";
HAL_UART_Transmit_DMA(&uart, txt, sizeof(txt));

Za čakanje na konec prenos lahko uporabimo HAL_DMA_PollForTransfer kot pri prenosu pomnilnik-pomnilnik iz zadnje vaje. Za povrnitev v prvotno stanje nastavimo element gState v strukturi UART naprave na HAL_UART_STATE_READY.

HAL_DMA_PollForTransfer(&dma1_struct, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY);
uart.gState = HAL_UART_STATE_READY;

V primeru, da uporabljamo prekinitve DMA naprave, ki jih vklopimo s spodnjima ukazoma, pa se ob koncu prenosa proži tudi prekinitev in s tem PSP DMA1_Stream0_IRQHandler.

HAL_NVIC_SetPriority(DMA1_Stream0_IRQn, 5, 5);
HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn);

Naloga 7.1

Izmerite čas prenosa 40 tisoč znakov z DMA ter s pomočjo funkcije HAL_UART_Transmit iz zadnje vaje. V času prenosa utripajte z vsemi tremi LED diodami. Utripanje LED diod naj se izklopi takoj po končanem prenosu. Izmerjena časa zapišite v komentar programa. Za merjenje časa uporabite pripravljene funkcije iz zadnje vaje. Merjenje časa ponovite večkrat in zapišite povprečje.