środa, 25 listopada 2015

[7] STM32 M3 - Nucleo - F103RB - DMA, Timer

W tym poście opiszę w jaki sposób odczytać dane z ADC poprzez DMA.

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:
  • najwyższy (ang. very high priority),
  • wysoki (ang. high priority),
  • średni (ang. medium priority),
  • niski (ang. low priority).
Są cztery wartości priorytetów oraz siedem kanałów. Jeśli zostanie ustawione więcej niż jeden kanał o tym samy priorytecie, wtedy jako pierwszy dostęp uzyska kanał o niższym numerze.

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.

  1. #include <stdio.h>
  2. #include <stdint.h>
  3. #include "stm32f10x.h"
  4.  
  5. #define Rozmiar 512
  6.  
  7. volatile uint32_t   czas_ms = 0;
  8. unsigned int   dane1[Rozmiar];
  9. unsigned int   dane2[Rozmiar];
  10.  
  11. void USARTInit(void);
  12. void GPIOInit(void);
  13. void SendCharc(volatile char c);
  14. void DelayInitial();
  15. void DMAInit(void);
  16. int __io_putchar(int c);
  17. void Proc();
  18. void DMA();
  19. void SysTick_Handler();
  20.  
  21. int main(void)
  22. {
  23.  int i;
  24.  int ProcKop = 0;
  25.  int DMAKop = 0;
  26.  
  27.  USARTInit();
  28.  GPIOInit();
  29.  DelayInitial();
  30.  DMAInit();
  31.  
  32.  czas_ms = 0;
  33.  
  34.  while(i<1000)
  35.  {
  36.   Proc();
  37.   i++;
  38.  }
  39.  
  40.  ProcKop = czas_ms;
  41.  printf("Kopiowanie CPU: %d ms\n", ProcKop);
  42.  
  43.  czas_ms = 0;
  44.  i=0;
  45.  
  46.  while(i<1000)
  47.  {
  48.   DMA();
  49.   i++;
  50.  }
  51.  DMAKop = czas_ms;
  52.  printf("Kopiowanie DMA: %d ms\n", DMAKop);
  53.  
  54.  while (1) {
  55.  }
  56. }
  57.  
  58. void DMAInit(void)
  59. {
  60.  DMA_InitTypeDef DMAInit;
  61.  
  62.  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
  63.  
  64.  DMA_StructInit(&DMAInit);
  65.  //Docelowy adres transferu
  66.  DMAInit.DMA_PeripheralBaseAddr = (u32)dane1;
  67.  //Wlaczenie automatycznego zwiekszania adresu po stronie ukladu
  68.  DMAInit.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
  69.  //Adres początku bloku jaki zostanie przesłany
  70.  DMAInit.DMA_MemoryBaseAddr = (u32)dane2;
  71.  //Wlaczenie automatycznego zwiekszania adresu po stronie pamięci
  72.  DMAInit.DMA_MemoryInc = DMA_MemoryInc_Enable;
  73.  //Liczba elemenetów jaki zostaną przesłane
  74.  DMAInit.DMA_BufferSize = Rozmiar;
  75.  //Ustawienie trybu pracy jako normalny czyli pojedyńcy
  76.  DMAInit.DMA_Mode = DMA_Mode_Normal;
  77.  //Ustawienie priorytetu dla zdażeń z kanału DMA
  78.  DMAInit.DMA_Priority = DMA_Priority_High;
  79.  //Wlaczenie obsługi transferu z pamięci do pamięci
  80.  DMAInit.DMA_M2M = DMA_M2M_Enable;
  81.  //Zapis konfiguracji
  82.  DMA_Init(DMA1_Channel5, &DMAInit);
  83.  //Wlączenie DMA dla kanału 1
  84.  DMA_Cmd(DMA1_Channel5, ENABLE);
  85. }
  86.  
  87. void Proc()
  88. {
  89.  //Kopiowanie danej z jednego bufora do drugiego, wykorzystany jest bezpośrednio mikrokontroler
  90.  int i;
  91.  for (= 0; i < Rozmiar;i++)
  92.   dane2[i] = dane1[i];
  93. }
  94.  
  95. void DMA()
  96. {
  97.  //Wylaczenie DMA
  98.  DMA_Cmd(DMA1_Channel5, DISABLE);
  99.  //Czyszczenie flagi zakończenia transferu danych
  100.  DMA_ClearFlag(DMA_ISR_TCIF5);
  101.  //Ustawienie licznika danych
  102.  DMA_SetCurrDataCounter(DMA1_Channel5, Rozmiar);
  103.  //Wlaczenie DMA
  104.  DMA_Cmd(DMA1_Channel5, ENABLE);
  105.  //Czeka na zakończenie kopiowania danych,
  106.  //czekanie na ustawienie flagi zakończenia transferu
  107.  while (DMA_GetFlagStatus(DMA1_FLAG_TC5) == RESET);
  108. }
  109.  
  110. void SysTick_Handler()
  111. {
  112.  czas_ms++;
  113. }
  114.  
  115. void DelayInitial(void)
  116. {
  117.  //Ustawienie opóźnień co 1ms = 64000000 / 1000
  118.  SysTick_Config(SystemCoreClock / 1000);
  119. }
  120.  
  121. //Inicjalizacja USARTU
  122. void USARTInit(void)
  123. {
  124.  USART_InitTypeDef USARTInit;
  125.  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
  126.  USARTInit.USART_BaudRate = 9600;
  127.  //Ustawienie dlugosci slowa
  128.  USARTInit.USART_WordLength = 8;
  129.  //Ustawienie bitu stopu
  130.  USARTInit.USART_StopBits = USART_StopBits_1;
  131.  //Brak kontroli parzystosci
  132.  USARTInit.USART_Parity = USART_Parity_No;
  133.  //Kontrola przepływu danych
  134.  USARTInit.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  135.  //Tryb pracy TX i RX
  136.  USARTInit.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  137.  USART_Init(USART2, &USARTInit);
  138.  USART_Cmd(USART2, ENABLE);
  139. }
  140.  
  141. //Ustawienie pinów dla USARTU
  142. void GPIOInit(void)
  143. {
  144.  GPIO_InitTypeDef GPIOInit;
  145.  
  146.  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  147.  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
  148.  
  149.  //PA2 Tx
  150.  GPIO_StructInit(&GPIOInit);
  151.  GPIOInit.GPIO_Pin = GPIO_Pin_2;
  152.  GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  153.  GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP;
  154.  GPIO_Init(GPIOA, &GPIOInit);
  155.  
  156.  //PA3 Rx
  157.  GPIOInit.GPIO_Pin = GPIO_Pin_3;
  158.  GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  159.  GPIOInit.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  160.  GPIO_Init(GPIOA, &GPIOInit);
  161. }
  162.  
  163. //Funckja obslugujaca printf
  164. int __io_putchar(int c)
  165. {
  166.  SendCharc(c);
  167.  return c;
  168. }
  169.  
  170. //Wyslanie danych
  171. void SendCharc(volatile char c)
  172. {
  173.  while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
  174.  USART_SendData(USART2, c);
  175. }

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.

  1. #include "stm32f10x.h"
  2.  
  3. int i =0;
  4. u16 PWM_Buf[198];
  5.  
  6. void TIMInit(void);
  7. void GPIOInit(void);
  8. void DMAInit(void);
  9. void PWMInit(void);
  10.  
  11. int main(void)
  12. {
  13.  GPIOInit();
  14.  TIMInit();
  15.  DMAInit();
  16.  PWMInit();
  17.  
  18.  for(= 0;i<99;i++)
  19.  {
  20.   PWM_Buf[i] = i+1;
  21.  }
  22.  for(= 99;i>0;i--)
  23.  {
  24.   PWM_Buf[i+98] = 100-i;
  25.  }
  26.  
  27.     while(1)
  28.     {
  29.     }
  30. }
  31. //Ustawienie Tim1
  32. void TIMInit(void)
  33. {
  34.  TIM_TimeBaseInitTypeDef TIMInit;
  35.  
  36.  //Wlaczenie zegara dla timera 1
  37.  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
  38.  
  39.  //Okres
  40.     TIMInit.TIM_Period = 99;
  41.     //Dzielnik sygnału taktującego
  42.     TIMInit.TIM_Prescaler = 50;
  43.  
  44.     TIMInit.TIM_ClockDivision = TIM_CKD_DIV1;
  45.     TIMInit.TIM_CounterMode = TIM_CounterMode_Up;
  46.     //Licznik powtórzen
  47.     TIMInit.TIM_RepetitionCounter = 200;
  48.  
  49.     TIM_TimeBaseInit(TIM1, &TIMInit);
  50. }
  51.  
  52. //Ustawienie portu I/O dla diody
  53. void GPIOInit(void)
  54. {
  55.   GPIO_InitTypeDef GPIOInit;
  56.  
  57.  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  58.  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
  59.  
  60.    GPIOInit.GPIO_Pin = GPIO_Pin_8;
  61.    GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP;
  62.    GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  63.  
  64.    GPIO_Init(GPIOA, &GPIOInit);
  65. }
  66.  
  67. //Konfiguracja DMA
  68. void DMAInit(void)
  69. {
  70.  //Adres rejestru TIM1 CCR1
  71.  #define TIM1_CCR1_Address 0x40012C34
  72.  
  73.  DMA_InitTypeDef DMAInit;
  74.  //Wlaczenie zegara
  75.  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
  76.  //Czyści wcześniejsze konfiguracje
  77.  DMA_DeInit(DMA1_Channel5);
  78.  //miejsce do którego dane zostaną skopiowane
  79.  DMAInit.DMA_PeripheralBaseAddr = (u32)TIM1_CCR1_Address;
  80.  //Adres poczatku przesyłanych danych
  81.  DMAInit.DMA_MemoryBaseAddr = (u32)PWM_Buf;
  82.  //Kierunek przysyłania danych
  83.  DMAInit.DMA_DIR = DMA_DIR_PeripheralDST;
  84.  //Długośc bufora danych
  85.  DMAInit.DMA_BufferSize = 198;
  86.  //Wylaczenie automatycznego zwiększania adresów timera
  87.  DMAInit.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  88.  //Wlaczeni automatycznego zwiększania adresów po stronie pamięci
  89.  DMAInit.DMA_MemoryInc = DMA_MemoryInc_Enable;
  90.  //Rozmiar przesyłanych danych po stronie pamięci
  91.  DMAInit.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  92.  //rozmiar przesylanych danych po stronie bufora
  93.  DMAInit.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  94.  //Ciągły tryb pracy
  95.  DMAInit.DMA_Mode = DMA_Mode_Circular;
  96.  //Wysoki priorytet tego kanału
  97.  DMAInit.DMA_Priority = DMA_Priority_High;
  98.  //Wylaczona opcja transferu z pamięci do pamięci
  99.  DMAInit.DMA_M2M = DMA_M2M_Disable;
  100.  
  101.  //Zapisanie konfiguracji
  102.  DMA_Init(DMA1_Channel5, &DMAInit);
  103.  
  104.  //Wlaczenie kanału 5 DMA
  105.     DMA_Cmd(DMA1_Channel5, ENABLE);
  106. }
  107.  
  108. void PWMInit(void)
  109. {
  110.  TIM_OCInitTypeDef  PWMInit;
  111.  
  112.  //Tryb pracy kanału
  113.     PWMInit.TIM_OCMode = TIM_OCMode_PWM1;
  114.     PWMInit.TIM_OutputState = TIM_OutputState_Enable;
  115.     //Czas trwania stanu wysokiego
  116.     PWMInit.TIM_Pulse = 50;
  117.     PWMInit.TIM_OCPolarity = TIM_OCPolarity_High;
  118.     TIM_OC1Init(TIM1, &PWMInit);
  119.  
  120.     //Wlączenie buforowania rejestrów lcznika
  121.     TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
  122.     //Zgłoszenie żądania DMA w momencie przepełnienia licznika
  123.     TIM_DMACmd(TIM1, TIM_DMA_Update, ENABLE);
  124.     //Wlaczenie Timera1
  125.     TIM_Cmd(TIM1, ENABLE);
  126.     //Wlączenie wyjścia PWM
  127.     TIM_CtrlPWMOutputs(TIM1, ENABLE);
  128. }