Wstęp
DMA (ang. Direct Memory Access) jest to bezpośrednio dostęp do pamięci. Pozwala to zminimalizowanie zadań wykonywanych przez mikrokontroler. Jeśli nie potrzebuje on dostępu do magistrali danych, wtedy przesyłanie może odbywać sie w tle, w tym czasie możliwe jest wykonywanie przez niego innych czynności.
Normalny transfer danych bez użycia DMA polega na wyznaczeniu przez mikrokontroler adresu komórki źródłowej oraz docelowej. W następnym kroku dokonywane jest przesłanie danych.
DMA dokonuje głównie wyznaczania adresów, ponieważ dla każdej danej musi być on wyznaczany ponownie. Natomiast jeśli procesor nie potrzebuje w momencie przesyłania danych dostępu do magistrali danych, to może on w tym samym czasie zajmować się innymi procedurami.
Programowanie DMA
STM32 omawiany w przykładzie posiada jest 7-kanałowy kontroler DMA. Pozwala on na dokonywanie następujących transmisji:
- z pamięci do pamięci;
- z pamięci do urządzenia peryferyjnego;
- z urządzenia peryferyjnego do pamięci;
Kontroler DMA pozwala na wykorzystywanie zapętlonego zarządzania buforem danych. Dzięki czemu unika się wystąpienia przerwań, kiedy zostanie on zapełniony.
Może być wykorzystywany z takimi układami jak SPI, I2C, USART, porty wejścia/wyjścia, TIMx oraz ADC.
Kontroler DMA może pracować w dwóch trybach normalnym lub cyklicznym. Pierwszy tryb nie powtarza transmisji po zakończeniu, drugi natomiast po zakończeniu rozpoczyna nowy cykl przesyłania.
W celu konfiguracji tego układu należy wypełnić następujące pola:
- DMA_PeripheralBaseAddr - pozwala na określenie urządzenia, do którego dane będą wysyłane, lub odbierane.
- DMA_MemoryBaseAddr - określa adres komórki lub początek tablicy, do której dane będą wysyłane lub odbierane.
- DMA_DIR - kierunek transferu danych.
- DMA_M2M - pozwala na włączenie transferu z pamięci do pamięci.
- DMA_BufferSize - liczba elementów jaka zostanie przesłana.
- DMA_PeripheralInc - pozwala na włączenie autoinkrementacji adresu po stronie urządzenia.
- DMA_MemoryInc - autoinkrementacja po stronie pamięci.
- DMA_PeripheralDataSize - pozwala na określenie pojedynczej danej, jaka będzie przesłana od strony urządzenia peryferyjnego.
- DMA_MemoryDataSize - rozmiar pojedyńczej danej, jaka będzie przesłana od struny pamięci.
- DMA_Mode - ustawienie trybu pracy.
- DMA_Priority - priorytet kanału.
Istnieje możliwość także wywołania przerwania przez DMA. Możliwe jest to w trzech następujących przypadkach: po zakończeniu transmisji bloku danych, po transmisji połowy bloku lub w przypadku wystąpienia błędu transmisji.
Każdy kanał może mieć odpowiednio dobraną wartość priorytetu. Można rozróżnić cztery priorytety:
Każdy kanał może mieć odpowiednio dobraną wartość priorytetu. Można rozróżnić cztery priorytety:
- najwyższy (ang. very high priority),
- wysoki (ang. high priority),
- średni (ang. medium priority),
- niski (ang. low priority).
Program 1 - Kopiowanie danych
W tym punkcie przedstawię w jaki sposób dokonać kopiowania danych z pamięci do pamięci za pomocą DMA oraz bezpośrednio mikrokontrolera.
Zainicjalizowany został DMA1 kanał 5. Pozostałe elementy zostały opisane bezpośrednio w programie w postaci komentarzy.
- #include <stdio.h>
- #include <stdint.h>
- #include "stm32f10x.h"
- #define Rozmiar 512
- volatile uint32_t czas_ms = 0;
- unsigned int dane1[Rozmiar];
- unsigned int dane2[Rozmiar];
- void USARTInit(void);
- void GPIOInit(void);
- void SendCharc(volatile char c);
- void DelayInitial();
- void DMAInit(void);
- int __io_putchar(int c);
- void Proc();
- void DMA();
- void SysTick_Handler();
- int main(void)
- {
- int i;
- int ProcKop = 0;
- int DMAKop = 0;
- USARTInit();
- GPIOInit();
- DelayInitial();
- DMAInit();
- czas_ms = 0;
- while(i<1000)
- {
- Proc();
- i++;
- }
- ProcKop = czas_ms;
- printf("Kopiowanie CPU: %d ms\n", ProcKop);
- czas_ms = 0;
- i=0;
- while(i<1000)
- {
- DMA();
- i++;
- }
- DMAKop = czas_ms;
- printf("Kopiowanie DMA: %d ms\n", DMAKop);
- while (1) {
- }
- }
- void DMAInit(void)
- {
- DMA_InitTypeDef DMAInit;
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
- DMA_StructInit(&DMAInit);
- //Docelowy adres transferu
- DMAInit.DMA_PeripheralBaseAddr = (u32)dane1;
- //Wlaczenie automatycznego zwiekszania adresu po stronie ukladu
- DMAInit.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
- //Adres początku bloku jaki zostanie przesłany
- DMAInit.DMA_MemoryBaseAddr = (u32)dane2;
- //Wlaczenie automatycznego zwiekszania adresu po stronie pamięci
- DMAInit.DMA_MemoryInc = DMA_MemoryInc_Enable;
- //Liczba elemenetów jaki zostaną przesłane
- DMAInit.DMA_BufferSize = Rozmiar;
- //Ustawienie trybu pracy jako normalny czyli pojedyńcy
- DMAInit.DMA_Mode = DMA_Mode_Normal;
- //Ustawienie priorytetu dla zdażeń z kanału DMA
- DMAInit.DMA_Priority = DMA_Priority_High;
- //Wlaczenie obsługi transferu z pamięci do pamięci
- DMAInit.DMA_M2M = DMA_M2M_Enable;
- //Zapis konfiguracji
- DMA_Init(DMA1_Channel5, &DMAInit);
- //Wlączenie DMA dla kanału 1
- DMA_Cmd(DMA1_Channel5, ENABLE);
- }
- void Proc()
- {
- //Kopiowanie danej z jednego bufora do drugiego, wykorzystany jest bezpośrednio mikrokontroler
- int i;
- for (i = 0; i < Rozmiar;i++)
- dane2[i] = dane1[i];
- }
- void DMA()
- {
- //Wylaczenie DMA
- DMA_Cmd(DMA1_Channel5, DISABLE);
- //Czyszczenie flagi zakończenia transferu danych
- DMA_ClearFlag(DMA_ISR_TCIF5);
- //Ustawienie licznika danych
- DMA_SetCurrDataCounter(DMA1_Channel5, Rozmiar);
- //Wlaczenie DMA
- DMA_Cmd(DMA1_Channel5, ENABLE);
- //Czeka na zakończenie kopiowania danych,
- //czekanie na ustawienie flagi zakończenia transferu
- while (DMA_GetFlagStatus(DMA1_FLAG_TC5) == RESET);
- }
- void SysTick_Handler()
- {
- czas_ms++;
- }
- void DelayInitial(void)
- {
- //Ustawienie opóźnień co 1ms = 64000000 / 1000
- SysTick_Config(SystemCoreClock / 1000);
- }
- //Inicjalizacja USARTU
- void USARTInit(void)
- {
- USART_InitTypeDef USARTInit;
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
- USARTInit.USART_BaudRate = 9600;
- //Ustawienie dlugosci slowa
- USARTInit.USART_WordLength = 8;
- //Ustawienie bitu stopu
- USARTInit.USART_StopBits = USART_StopBits_1;
- //Brak kontroli parzystosci
- USARTInit.USART_Parity = USART_Parity_No;
- //Kontrola przepływu danych
- USARTInit.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
- //Tryb pracy TX i RX
- USARTInit.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
- USART_Init(USART2, &USARTInit);
- USART_Cmd(USART2, ENABLE);
- }
- //Ustawienie pinów dla USARTU
- void GPIOInit(void)
- {
- GPIO_InitTypeDef GPIOInit;
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
- //PA2 Tx
- GPIO_StructInit(&GPIOInit);
- GPIOInit.GPIO_Pin = GPIO_Pin_2;
- GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
- GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP;
- GPIO_Init(GPIOA, &GPIOInit);
- //PA3 Rx
- GPIOInit.GPIO_Pin = GPIO_Pin_3;
- GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
- GPIOInit.GPIO_Mode = GPIO_Mode_IN_FLOATING;
- GPIO_Init(GPIOA, &GPIOInit);
- }
- //Funckja obslugujaca printf
- int __io_putchar(int c)
- {
- SendCharc(c);
- return c;
- }
- //Wyslanie danych
- void SendCharc(volatile char c)
- {
- while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
- USART_SendData(USART2, c);
- }
Program 2 - Sterowanie jasnością diody
Działanie drugiego programu będzie oparte o TIM1 oraz DMA. Jego zadaniem będzie sterowanie jasnością świecenia diody poprzez dynamiczną zmianę współczynnika wypełnienia impulsu. Diode należy podłączyć pomiędzy masę a pin PA8.
Wszystkie timery mogą zostać obsługiwane przez DMA.
DMA ustawione jest na tryb działania ciągły, dzięki temu kopiowanie danych występuje cały czas. Jeśli został by ustawiony tryb normal program wykonał by się tylko jeden raz, po czym zakończy swoje działanie.
Licznik przepełnienia w timerze został ustawiony na 200. Pozwala to na wygenerowanie takiej ilości okresów sygnału PWM dla każdej z podanych wartości wypełnienia.
- #include "stm32f10x.h"
- int i =0;
- u16 PWM_Buf[198];
- void TIMInit(void);
- void GPIOInit(void);
- void DMAInit(void);
- void PWMInit(void);
- int main(void)
- {
- GPIOInit();
- TIMInit();
- DMAInit();
- PWMInit();
- for(i = 0;i<99;i++)
- {
- PWM_Buf[i] = i+1;
- }
- for(i = 99;i>0;i--)
- {
- PWM_Buf[i+98] = 100-i;
- }
- while(1)
- {
- }
- }
- //Ustawienie Tim1
- void TIMInit(void)
- {
- TIM_TimeBaseInitTypeDef TIMInit;
- //Wlaczenie zegara dla timera 1
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
- //Okres
- TIMInit.TIM_Period = 99;
- //Dzielnik sygnału taktującego
- TIMInit.TIM_Prescaler = 50;
- TIMInit.TIM_ClockDivision = TIM_CKD_DIV1;
- TIMInit.TIM_CounterMode = TIM_CounterMode_Up;
- //Licznik powtórzen
- TIMInit.TIM_RepetitionCounter = 200;
- TIM_TimeBaseInit(TIM1, &TIMInit);
- }
- //Ustawienie portu I/O dla diody
- void GPIOInit(void)
- {
- GPIO_InitTypeDef GPIOInit;
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
- GPIOInit.GPIO_Pin = GPIO_Pin_8;
- GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP;
- GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIOInit);
- }
- //Konfiguracja DMA
- void DMAInit(void)
- {
- //Adres rejestru TIM1 CCR1
- #define TIM1_CCR1_Address 0x40012C34
- DMA_InitTypeDef DMAInit;
- //Wlaczenie zegara
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
- //Czyści wcześniejsze konfiguracje
- DMA_DeInit(DMA1_Channel5);
- //miejsce do którego dane zostaną skopiowane
- DMAInit.DMA_PeripheralBaseAddr = (u32)TIM1_CCR1_Address;
- //Adres poczatku przesyłanych danych
- DMAInit.DMA_MemoryBaseAddr = (u32)PWM_Buf;
- //Kierunek przysyłania danych
- DMAInit.DMA_DIR = DMA_DIR_PeripheralDST;
- //Długośc bufora danych
- DMAInit.DMA_BufferSize = 198;
- //Wylaczenie automatycznego zwiększania adresów timera
- DMAInit.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
- //Wlaczeni automatycznego zwiększania adresów po stronie pamięci
- DMAInit.DMA_MemoryInc = DMA_MemoryInc_Enable;
- //Rozmiar przesyłanych danych po stronie pamięci
- DMAInit.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
- //rozmiar przesylanych danych po stronie bufora
- DMAInit.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
- //Ciągły tryb pracy
- DMAInit.DMA_Mode = DMA_Mode_Circular;
- //Wysoki priorytet tego kanału
- DMAInit.DMA_Priority = DMA_Priority_High;
- //Wylaczona opcja transferu z pamięci do pamięci
- DMAInit.DMA_M2M = DMA_M2M_Disable;
- //Zapisanie konfiguracji
- DMA_Init(DMA1_Channel5, &DMAInit);
- //Wlaczenie kanału 5 DMA
- DMA_Cmd(DMA1_Channel5, ENABLE);
- }
- void PWMInit(void)
- {
- TIM_OCInitTypeDef PWMInit;
- //Tryb pracy kanału
- PWMInit.TIM_OCMode = TIM_OCMode_PWM1;
- PWMInit.TIM_OutputState = TIM_OutputState_Enable;
- //Czas trwania stanu wysokiego
- PWMInit.TIM_Pulse = 50;
- PWMInit.TIM_OCPolarity = TIM_OCPolarity_High;
- TIM_OC1Init(TIM1, &PWMInit);
- //Wlączenie buforowania rejestrów lcznika
- TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
- //Zgłoszenie żądania DMA w momencie przepełnienia licznika
- TIM_DMACmd(TIM1, TIM_DMA_Update, ENABLE);
- //Wlaczenie Timera1
- TIM_Cmd(TIM1, ENABLE);
- //Wlączenie wyjścia PWM
- TIM_CtrlPWMOutputs(TIM1, ENABLE);
- }