wtorek, 29 marca 2016

[10a] STM32F4 - Discovery - DAC, generacja sygnałów

W tym poście chciałbym przedstawić sposób generacji sygnałów za pomocą DAC oraz DMA.

Za pomocą konwertera cyfrowo analogowego można wygenerować sygnał prostokątny, piłokształtny trójkątny oraz sinusoidalny.

Sposób działania


Poniżej przedstawiam schemat blokowy pojedynczego konwertera cyfrowo analogowego. Dla przypomnienia w STM32 znajdują się dwa takie układy.

Rys. 1. Schemat DAC



VDDA - jest to napięcie wejściowe, zasilanie analogowe części układu
VSSA - wejściowe, uziemienie części analogowej
VREF+ - napięcie referencyjne dodatnie z zakresi od 1,8V do VDDA

Przed konfiguracją należy ustawić piny wyjściowe jako analogowe. Spowodowane jest to tym, że DAC po odpowiedniej konfiguracji generuje sygnał na wybranym pinie.

Sama konwersja odbywa się w części znajdującej się na samym dole rysunku. Działa ona we współpracy z liniami zasilającymi oraz referencyjnymi mikrokontrolera. Dane wyjściowe dostępne są na pinie oznaczonym jako DAC_OUTx. Natomiast dane wejściowe podawana są do 32-bitowego rejestru przechowującego DHR. Z niego przesyłane są do rejestru DOR gdzie zostają bezpośrednio zamienione na wartości analogowe. Dostęp uzyskuje się do 12 bitów DAC_DHR12R1, DAC_DHR12R2 odpowiednio dla DAC1 oraz dla DAC2.

Dane nie mogą być bezpośrednio przesłane do DOR z pominięciem DHR. 

Transfer danych pomiędzy DHR a DOR może przebiegać automatycznie lub poprzez użycie wyzwalania programowego SWTRIGx, sprzętowego TIMx_TRGO bądź z przerwania EXTI_9. Wybór definiowany jest przez rejestr TSELx[2:0]. 

Dodatkowe układy sprzętowe są umieszczone w jednostce kontrolnej, takie jak generator szumu lub sygnału trójkątnego. Informacje o ich użyciu są dodawane do rejestru DHR. 

Bity rozmieszczone pomiędzy rejestrami kontrolnym, sygnału programowego oraz statusu definiują sposoby działania DAC.

Istnieje też możliwość transferu danych za pomocą DMA.

Programowanie


W związku z tym, że przetwornik jest 12 bitowy, dobrze jest sygnał sinusoidany generować w zakresie od 0 do 4095, ze środkiem znajdującym się pomiędzy tymi wartościami. Aby tego dokonać należy stworzyć tablice z zdefiniowanymi próbkami sygnałów. 

  1. //Przebieg sinusoidalny 32 próbki
  2. uint32_t Sinus[32] =
  3. {
  4.     2047, 2447, 2831, 3185, 3498, 3750, 3939, 4056,
  5.     4095, 4056, 3939, 3750, 3495, 3185, 2831, 2447,
  6.     2047, 1647, 1263, 909, 599, 344, 155, 38,
  7.     0, 38, 155, 344, 599, 909, 1263, 1647
  8. };
  9. //Przebieg sinusoidalny 128 próbek
  10. const uint32_t Sinus2[128] =
  11. {
  12.      2048, 2145, 2242, 2339, 2435, 2530, 2624, 2717, 2808, 2897,
  13.    2984, 3069, 3151, 3230, 3307, 3381, 3451, 3518, 3581, 3640,
  14.    3696, 3748, 3795, 3838, 3877, 3911, 3941, 3966, 3986, 4002,
  15.    4013, 4019, 4020, 4016, 4008, 3995, 3977, 3954, 3926, 3894,
  16.    3858, 3817, 3772, 3722, 3669, 3611, 3550, 3485, 3416, 3344,
  17.    3269, 3191, 3110, 3027, 2941, 2853, 2763, 2671, 2578, 2483,
  18.    2387, 2291, 2194, 2096, 1999, 1901, 1804, 1708, 1612, 1517,
  19.    1424, 1332, 1242, 1154, 1068, 985, 904, 826, 751, 679,
  20.    610, 545, 484, 426, 373, 323, 278, 237, 201, 169,
  21.    141, 118, 100, 87, 79, 75, 76, 82, 93, 109,
  22.    129, 154, 184, 218, 257, 300, 347, 399, 455, 514,
  23.    577, 644, 714, 788, 865, 944, 1026, 1111, 1198, 1287,
  24.    1378, 1471, 1565, 1660, 1756, 1853, 1950, 2047
  25. };  
  26. //Przebieg trojkatny
  27. uint32_t Triangle[32] =
  28. {
  29.     0, 256, 512, 768, 1024, 1279, 1535, 1791,
  30.     2047, 2303, 2559, 2815, 3071, 3326, 3582, 3838,
  31.     4095, 3838, 3582, 3326, 3071, 2815, 2559, 2303,
  32.     2047, 1791, 1535, 1279, 1024, 768, 512, 256
  33. };
  34. //Przebieg piloksztaltny
  35. uint32_t Sawtooth[32] =
  36. {
  37.     0, 132, 264, 396, 528, 660, 792, 924,
  38.     1057, 1189, 1321, 1453, 1585, 1717, 1849, 1981,
  39.     2113, 2245, 2377, 2509, 2641, 2773, 2905, 3037,
  40.     3170, 3302, 3434, 3566, 3698, 3830, 3962, 4095
  41. };
  42. //Przebieg prostokatny
  43. uint16_t Square[2] =
  44. {
  45.     0, 4095
  46. };

Kolejnym elementem jest odpowiednie odpowiednie włączenie DAC-a, timera oraz DMA. Jeśli chodzi o liczniki, to do dyspozycji są TIM2, TIM3, TIM4, TIM6, TIM7, TIM15. Dodatkowo wyzwalanie może nastąpić programowo bądź z przerwania EXTI_9. 

Rys. 1. Sposoby wyzwalania DAC

Poniżej przedstawiam funkcje inicjujące pracę poszczególnych układów.

Kolejnym elementem jest konfiguracja timera:

  1. //Deklaracja timera dla DAC1 PA4
  2. static void TIMConfig_DAC1(void)
  3. {
  4.   TIM_TimeBaseInitTypeDef TimerInitStructure;
  5.    
  6.   //Wlaczenie zegara dla timera 6
  7.   RCC_APB1PeriphClockCmd(Timer_Clock1, ENABLE);
  8.   TIM_TimeBaseStructInit(&TimerInitStructure);
  9.   //Ustawienie okresu jako:
  10.   //zegar (APB1 Tim6/ilosc probek)*czestotliwosc sygnalu
  11.   TimerInitStructure.TIM_Period        = (uint16_t)(TIM_APB_DAC1/Num_Samp_DAC1)*Signal_Freq_DAC1;          
  12.   TimerInitStructure.TIM_Prescaler     = 0;      
  13.   TimerInitStructure.TIM_ClockDivision = 0;
  14.   //Zliczanie w gore
  15.   TimerInitStructure.TIM_CounterMode   = TIM_CounterMode_Up;  
  16.   TIM_TimeBaseInit(Tim_Number1, &TimerInitStructure);
  17.   TIM_SelectOutputTrigger(Tim_Number1, TIM_TRGOSource_Update);
  18.   TIM_Cmd(Tim_Number1, ENABLE);
  19. }
  20. //Deklaracja timera dla DAC2 PA5
  21. static void TIMConfig_DAC2(void)
  22. {
  23.   TIM_TimeBaseInitTypeDef TimerInitStructure;
  24.    
  25.   //Wlaczenie zegara dla wybranego timera
  26.   RCC_APB1PeriphClockCmd(Timer_Clock2, ENABLE);
  27.   TIM_TimeBaseStructInit(&TimerInitStructure);
  28.   //Ustawienie okresu jako:
  29.   //zegar (APB1 Tim6/ilosc probek)*czestotliwosc sygnalu
  30.  TimerInitStructure.TIM_Period    = (uint16_t)(TIM_APB_DAC2/Num_Samp_DAC2)*Signal_Freq_DAC2;          
  31.   TimerInitStructure.TIM_Prescaler     = 0;      
  32.   TimerInitStructure.TIM_ClockDivision = 0;
  33.   //Zliczanie w gore
  34.   TimerInitStructure.TIM_CounterMode   = TIM_CounterMode_Up;  
  35.   TIM_TimeBaseInit(Tim_Number2, &TimerInitStructure);
  36.   TIM_SelectOutputTrigger(Tim_Number2, TIM_TRGOSource_Update);
  37.   TIM_Cmd(Tim_Number2, ENABLE);
  38. }

Do deklaracji DMA trzeba znać deklarację odpowiednich rejestrów dla DAC, które przechowują dane. Ich deklaracje są następujące:

  1.   DAC->CR       = 0x00010001 // Control Register
  2.   DAC->SWTRIGR               // SoftWare TRIGger Register
  3.   DAC->DHR12R1               // Data Holding Register, 12bit, Right aligned, ch1
  4.   DAC->DHR12L1               // Data Holding Register, 12bit, Left aligned, ch1
  5.   DAC->DHR8R1                // Data Holding Register,  8bit, Right aligned, ch1
  6.   DAC->DHR12R2               // Data Holding Register, 12bit, Right aligned, ch2
  7.   DAC->DHR12L2               // Data Holding Register, 12bit, Left aligned, ch2
  8.   DAC->DHR8R2                // Data Holding Register,  8bit, Right aligned, ch2
  9.   DAC->DHR12RD               // Data Holding Register, 12bit, Right aligned, Dual
  10.   DAC->DHR12LD               // Data Holding Register, 12bit, Left aligned, Dual
  11.   DAC->DHR8RD                // Data Holding Register,  8bit, Right aligned, Dual
  12.   DAC->DOR1                  // Data Output Register, ch1, read only
  13.   DAC->DOR2                  // Data Output Register, ch2, read only
  14.   DAC->SR                    // Status Register

Pozwala to na odwołania do rejestru kontrolnego, przechowującego dane, wyjściowego bądź rejestru przechowującego status pracy konwertera.

Następnym elementem w kolejce jest odpowiednie ustawienie portów GPIO, DAC oraz DMA.

  1. //Wlaczenie DAC1
  2. void DAC1Init()
  3. {
  4.     DAC_InitTypeDef DACInit;
  5.     GPIO_InitTypeDef GPIOInit;  
  6.     DMA_InitTypeDef DMAInit;
  7.     //Wlaczenie zegara dla portu GPIOA
  8.     //RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
  9.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  10.    
  11.     //Wlaczenie zegara dla DAC
  12.     //RCC->APB1ENR |= RCC_APB1ENR_DACEN;
  13.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
  14.    
  15.     //Wlaczenie zegara dla DMA
  16.     //RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
  17.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
  18.    
  19.     //Pozostale ustawienia wyprowadzen GPIO
  20.     GPIOInit.GPIO_Pin = GPIO_Pin_4;
  21.     GPIOInit.GPIO_Mode = GPIO_Mode_AN;
  22.     GPIOInit.GPIO_OType = GPIO_OType_PP;
  23.     GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  24.     GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  25.     GPIO_Init(GPIOA, &GPIOInit);
  26.    
  27.     //Ustawienie opcji dla DAC
  28.     //Sygnal wyzwalajacy
  29.     DACInit.DAC_Trigger = DAC1_Trigger;
  30.     //Generacja sygnalu wylaczona
  31.     DACInit.DAC_WaveGeneration = DAC_WaveGeneration_None;
  32.     //Bufor wyjsciowy wlaczony
  33.     DACInit.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
  34.     DAC_Init(DAC_Channel_1, &DACInit);
  35.    
  36.     DMA_DeInit(DMA1_Stream5);
  37.     DMAInit.DMA_Channel            = DMA_Channel_7;
  38.     //Data holding register, 12 bit, right aligned ch1
  39.     DMAInit.DMA_PeripheralBaseAddr = (uint32_t)&DAC->DHR12R1;
  40.     //Jaki sygnal bedzie generowany
  41.     DMAInit.DMA_Memory0BaseAddr    = (uint32_t)Signal_DAC1;
  42.     //Jak dane beda generowane
  43.     DMAInit.DMA_DIR                = DMA_DIR_MemoryToPeripheral;
  44.     //Liczba próbek sygnaBu
  45.     DMAInit.DMA_BufferSize         = Num_Samp_DAC1;
  46.     //Wylaczony automatyczny zwiekszanie adresu
  47.     DMAInit.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
  48.     //Wylaczony transfer z pamieci do pamieci
  49.     DMAInit.DMA_MemoryInc          = DMA_MemoryInc_Enable;
  50.     //Rozmiar przeslanych danych 16b
  51.     DMAInit.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  52.     //Rozmiar danych po stronie pamieci
  53.     DMAInit.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;
  54.     //Ciagly tryb pracy
  55.     DMAInit.DMA_Mode               = DMA_Mode_Circular;
  56.     //Priprytet wysoki
  57.     DMAInit.DMA_Priority           = DMA_Priority_High;
  58.     //Tryb fifo wylaczony
  59.     DMAInit.DMA_FIFOMode           = DMA_FIFOMode_Disable;
  60.     DMAInit.DMA_FIFOThreshold      = DMA_FIFOThreshold_HalfFull;
  61.     //Pojedyncze wyzwalanie
  62.     DMAInit.DMA_MemoryBurst        = DMA_MemoryBurst_Single;
  63.     DMAInit.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single;
  64.     //Wylaczenie DMA
  65.     DMA_DeInit(DMA1_Stream5);
  66.     //Inicjacja i wlaczenie DMA
  67.     DMA_Init(DMA1_Stream5, &DMAInit);
  68.     DMA_Cmd(DMA1_Stream5, ENABLE);
  69.    
  70.     //Wlaczenie DAC-a
  71.     DAC_Cmd(DAC_Channel_1, ENABLE);
  72.     DAC_DMACmd(DAC_Channel_1, ENABLE);
  73.    
  74.     //Inicjacja i wlaczenie wybranego pinu DAC
  75.     DAC_Init(DAC_Channel_1, &DACInit);
  76.        
  77.     //Wlaczenie kanalu pierwszego
  78.     //DAC->CR |= DAC_CR_EN1;
  79.     DAC_Cmd(DAC_Channel_1, ENABLE);
  80. }
  81. //Wlaczenie DAC2
  82. void DAC2Init(void)
  83. {
  84.     DAC_InitTypeDef DACInit;
  85.     GPIO_InitTypeDef GPIOInit;  
  86.     DMA_InitTypeDef DMAInit;
  87.     //Wlaczenie zegara dla portu GPIOA
  88.     //RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
  89.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  90.    
  91.     //Wlaczenie zegara dla DAC
  92.     //RCC->APB1ENR |= RCC_APB1ENR_DACEN;
  93.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
  94.    
  95.     //Wlaczenie zegara dla DMA
  96.     //RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
  97.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
  98.    
  99.     //Pozostale ustawienia wyprowadzen GPIO
  100.     GPIOInit.GPIO_Pin = GPIO_Pin_5;
  101.     GPIOInit.GPIO_Mode = GPIO_Mode_AN;
  102.     GPIOInit.GPIO_OType = GPIO_OType_PP;
  103.     GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  104.     GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  105.     GPIO_Init(GPIOA, &GPIOInit);
  106.    
  107.     //Ustawienie opcji dla DAC
  108.     //Sygnal wyzwalajacy
  109.     DACInit.DAC_Trigger = DAC1_Trigger;
  110.     //Generacja sygnalu wylaczona
  111.     DACInit.DAC_WaveGeneration = DAC_WaveGeneration_None;
  112.     //Bufor wyjsciowy wlaczony
  113.     DACInit.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
  114.     DAC_Init(DAC_Channel_2, &DACInit);
  115.    
  116.     DMA_DeInit(DMA1_Stream6);
  117.     DMAInit.DMA_Channel            = DMA_Channel_7;
  118.     //Data holding register, 12 bit, right aligned ch1
  119.     DMAInit.DMA_PeripheralBaseAddr = (uint32_t)&DAC->DHR12R2;
  120.     //Jaki sygnal bedzie generowany
  121.     DMAInit.DMA_Memory0BaseAddr    = (uint32_t)Signal_DAC2;
  122.     //Jak dane beda generowane
  123.     DMAInit.DMA_DIR                = DMA_DIR_MemoryToPeripheral;
  124.     //Liczba próbek sygnaBu
  125.     DMAInit.DMA_BufferSize         = Num_Samp_DAC2;
  126.     //Wylaczony automatyczny zwiekszanie adresu
  127.     DMAInit.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
  128.     //Wylaczony transfer z pamieci do pamieci
  129.     DMAInit.DMA_MemoryInc          = DMA_MemoryInc_Enable;
  130.     //Rozmiar przeslanych danych 16b
  131.     DMAInit.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  132.     //Rozmiar danych po stronie pamieci
  133.     DMAInit.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;
  134.     //Ciagly tryb pracy
  135.     DMAInit.DMA_Mode               = DMA_Mode_Circular;
  136.     //Priprytet wysoki
  137.     DMAInit.DMA_Priority           = DMA_Priority_High;
  138.     //Tryb fifo wylaczony
  139.     DMAInit.DMA_FIFOMode           = DMA_FIFOMode_Disable;
  140.     DMAInit.DMA_FIFOThreshold      = DMA_FIFOThreshold_HalfFull;
  141.     //Pojedyncze wyzwalanie
  142.     DMAInit.DMA_MemoryBurst        = DMA_MemoryBurst_Single;
  143.     DMAInit.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single;
  144.     //Wylaczenie DMA
  145.     DMA_DeInit(DMA1_Stream6);
  146.     //Inicjacja i wlaczenie DMA
  147.     DMA_Init(DMA1_Stream6, &DMAInit);
  148.     DMA_Cmd(DMA1_Stream6, ENABLE);
  149.    
  150.     //Wlaczenie DAC-a
  151.     DAC_Cmd(DAC_Channel_2, ENABLE);
  152.     DAC_DMACmd(DAC_Channel_2, ENABLE);
  153.    
  154.     //Inicjacja i wlaczenie wybranego pinu DAC
  155.     DAC_Init(DAC_Channel_2, &DACInit);
  156.        
  157.     //Wlaczenie kanalu pierwszego
  158.     //DAC->CR |= DAC_CR_EN2;
  159.     DAC_Cmd(DAC_Channel_2, ENABLE);
  160. }

Odpowiednie sygnały będą generowane w zależności od ustawienia wartości początkowej w polach define.

  1. //Timer DAC1
  2. #define Tim_Number1 TIM6
  3. #define Timer_Clock1 RCC_APB1Periph_TIM6
  4. #define TIM_APB_DAC1 42000000
  5. #define Num_Samp_DAC1 32
  6. #define Signal_Freq_DAC1 5000
  7. #define DAC1_Trigger DAC_Trigger_T6_TRGO
  8. //Definicja generowanego sygnalu:
  9. //Do wyboru Sinus, Sinus2, Triangle, Sawtooth, Square
  10. #define Signal_DAC1 Sinus
  11. //Timer DAC2
  12. #define Tim_Number2 TIM5
  13. #define Timer_Clock2 RCC_APB1Periph_TIM5
  14. #define TIM_APB_DAC2 42000000
  15. #define Num_Samp_DAC2 32
  16. #define Signal_Freq_DAC2 5000
  17. #define DAC2_Trigger DAC_Trigger_T5_TRGO
  18. //Definicja generowanego sygnalu:
  19. //Do wyboru Sinus, Sinus2, Triangle, Sawtooth, Square
  20. #define Signal_DAC2 Sinus

Główna funkcja programu polega na wywołaniu funkcji inicjujących z odpowiednimi parametrami dobranymi na samym początku programu.

Bibliografia


[1] RM0090 - Reference manual