niedziela, 17 kwietnia 2016

[2] STM32F4 - Discovery - Projekt - Generator sygnałów DAC

W tym poście chciałbym przedstawić szybki mały projekt generatora sygnałów, wykonanego w oparciu o STM32F4 - Discovery. Układ generował będzie wybrane przez użytkownika sygnały za pomocą DAC na pinach PA4 oraz PA5. Z dostępnych sygnałów będzie do wybory sygnał sinusoidalny, trójkątny, piłokształtny, prostokątny oraz szum. W celu wybrania funkcji będzie wykorzystywany wyświetlacz Nokia 5110 oraz kilka przycisków.

Podłączenie


Elementem wykorzystywanym do prezentacji danych będzie wspomniany wcześniej wyświetlacz Nokia 5110. Jego podłączenie jest następujące:

  • RST - GPIOC15
  • CE - GPIOC13
  • DC - GPIOC14
  • Din - GPIOC3
  • Clk - GPIOB10
  • VCC - 3,3V
  • BL - Włączenie podświetlenia wyświetlacza
  • GND - GND

Kolejnym elementem będą przyciski funkcyjne, które zostały podłączone w następujący sposób:

  • Przycisk 1 do PD0 (przycisk już wbudowany w Discovery)
  • Przycisk 2 do PD1
  • Przycisk 3 do PD2
  • Przycisk 4 do PD3

Sygnały z DAC-a został wystawiony na liniach PA4 oraz PA5.

Programowanie


Poniżej przedstawiam krótki opis poszczególnych części programu:

  • Definicja tablic z danymi dla wyświetlacza oraz DAC-a
  • Inicjalizacja DAC1 oraz DAC2 oraz portów GPIO dla nich
  • Inicjalizacja timerów dla DAC1, DAC2
  • Przygotowanie funkcji uruchamiającej generator szumu dla DAC2
  • Włączenie wyświetlacza Nokia 5110 wraz z SPI oraz dodatkowymi funkcjami wykorzystywanymi przy przesyłaniu danych.
  • Włączenie funkcji przerwania dla przycisków PD1, PD2 oraz PD3. Aby je odpowiednio wykonać należy uruchomić EXTI, NVIC odpowiedni port GPIO.
    • PD1 będzie miał za zadanie ustawianie wybranego typu sygnału, który byłby generowany poprzez DAC1
    • PD2 odpowiada za ustawienie częstotliwości z jaką sygnał z DAC1 będzie taktowany
    • PD3 będzie miał za zadanie ustawianie wybranego typu sygnału, który byłby generowany poprzez DAC2
    • PD4 odpowiada za ustawienie częstotliwości z jaką sygnał z DAC2 będzie taktowany
    • PD5 jego zadaniem będzie włączenie sygnału z wprowadzonymi parametrami
  • Ostatnim elementem będzie obsługa przerwania dla zdeklarowanych przycisków, Każde z nich oprócz odpowiedniego zabezpieczenia sprzętowego musi być wyposażony w funkcję sprawdzającą stany poszczególnych przycisków.

W pierwszej kolejności należy włączyć DAC oraz przygotować tablicę z danymi dla generowanych sygnałów. Aby generator działał poprawnie należy dodatkowo zdeklarować licznik dla DAC1 oraz DAC2. W funkcji z inicjacją DAC-a znajduje się także włączenie DMA oraz GPIO dla pinów z generowanym sygnałem.

Poniżej przedstawiam deklarację dla TIM1.

  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.     if(signaltype == 1)
  12.     {
  13.         TimerInitStructure.TIM_Period      = (uint16_t)(TIM_APB_DAC1/Num_Samp_DAC1_128)*Signal_Freq_DAC1;
  14.     }
  15.     else
  16.     {
  17.         TimerInitStructure.TIM_Period      = (uint16_t)(TIM_APB_DAC1/Num_Samp_DAC1_32)*(Signal_Freq_DAC1*1000)
  18. ;
  19.     }
  20.   TimerInitStructure.TIM_Prescaler     = 0;      
  21.   TimerInitStructure.TIM_ClockDivision = 0;
  22.     //Zliczanie w gore
  23.   TimerInitStructure.TIM_CounterMode   = TIM_CounterMode_Up;  
  24.   TIM_TimeBaseInit(Tim_Number1, &TimerInitStructure);
  25.   TIM_SelectOutputTrigger(Tim_Number1, TIM_TRGOSource_Update);
  26.   TIM_Cmd(Tim_Number1, ENABLE);
  27. }

Poniżej włączenie DAC1 z GPIO 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.     switch(signaltype)
  41.     {
  42.         case 1:
  43.         {
  44.             DMAInit.DMA_Memory0BaseAddr    = (uint32_t)Sinus2;
  45.             break;
  46.         }
  47.         case 2:
  48.         {
  49.             DMAInit.DMA_Memory0BaseAddr    = (uint32_t)Sinus;
  50.             break;
  51.         }
  52.         case 3:
  53.         {
  54.             DMAInit.DMA_Memory0BaseAddr    = (uint32_t)Triangle;
  55.             break;
  56.         }
  57.         case 4:
  58.         {
  59.             DMAInit.DMA_Memory0BaseAddr    = (uint32_t)Sawtooth;
  60.             break;
  61.         }
  62.         case 5:
  63.         {
  64.             DMAInit.DMA_Memory0BaseAddr    = (uint32_t)Square;
  65.             break;
  66.         }
  67.     }
  68.     //Jak dane beda generowane
  69.     DMAInit.DMA_DIR = DMA_DIR_MemoryToPeripheral;
  70.     if(signaltypedac2 == 1)
  71.     {
  72.       //Liczba próbek sygnalu
  73.       DMAInit.DMA_BufferSize = Num_Samp_DAC1_128;
  74.     }
  75.     else
  76.     {
  77.       DMAInit.DMA_BufferSize = Num_Samp_DAC1_32;
  78.     }    
  79.     //Wylaczony automatyczny zwiekszanie adresu
  80.     DMAInit.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  81.     //Wylaczony transfer z pamieci do pamieci
  82.     DMAInit.DMA_MemoryInc = DMA_MemoryInc_Enable;
  83.     //Rozmiar przeslanych danych 16b
  84.     DMAInit.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  85.     //Rozmiar danych po stronie pamieci
  86.     DMAInit.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  87.     //Ciagly tryb pracy
  88.     DMAInit.DMA_Mode = DMA_Mode_Circular;
  89.     //Priprytet wysoki
  90.     DMAInit.DMA_Priority = DMA_Priority_High;
  91.     //Tryb fifo wylaczony
  92.     DMAInit.DMA_FIFOMode = DMA_FIFOMode_Disable;
  93.     DMAInit.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
  94.     //Pojedyncze wyzwalanie
  95.     DMAInit.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  96.     DMAInit.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  97.     //Wylaczenie DMA
  98.     DMA_DeInit(DMA1_Stream5);
  99.     //Inicjacja i wlaczenie DMA
  100.     DMA_Init(DMA1_Stream5, &DMAInit);
  101.     DMA_Cmd(DMA1_Stream5, ENABLE);
  102.    
  103.     //Wlaczenie DAC-a
  104.     DAC_Cmd(DAC_Channel_1, ENABLE);
  105.     DAC_DMACmd(DAC_Channel_1, ENABLE);
  106.    
  107.     //Inicjacja i wlaczenie wybranego pinu DAC
  108.     DAC_Init(DAC_Channel_1, &DACInit);
  109.        
  110.     //Wlaczenie kanalu pierwszego
  111.     //DAC->CR |= DAC_CR_EN1;
  112.     DAC_Cmd(DAC_Channel_1, ENABLE);
  113. }

Kolejnym elementem jest dodanie funkcji odpowiedzialnej za obsługę wyświetlacza. ZOstały one opisane w jednym ze wcześniejszych postów.

W ostatnim kroku należy przygotować funkcję odpowiedzialną za przerwania od przycisków funkcyjnych.

Poniżej przedstawiam jedną z funkcji ustawiającą przerwanie dla PD2:

  1. void Configure_PD2(void)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStruct;
  4.     EXTI_InitTypeDef EXTI_InitStruct;
  5.     NVIC_InitTypeDef NVIC_InitStruct;
  6.    
  7.     //Wlaczenie zegarow
  8.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
  9.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
  10.    
  11.     //Ustawienie pinu dla przycisku, jako wejscie
  12.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
  13.     GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
  14.     GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
  15.     GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN;
  16.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
  17.     GPIO_Init(GPIOD, &GPIO_InitStruct);
  18.    
  19.     //Przypisanie przerwania pod PD2
  20.     SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOD, EXTI_PinSource2);
  21.    
  22.     //Wlaczenie lini PD2 dla przerwania
  23.     EXTI_InitStruct.EXTI_Line = EXTI_Line2;
  24.     //Wlaczenie przerwania
  25.     EXTI_InitStruct.EXTI_LineCmd = ENABLE;
  26.     //Wlaczenie trybu przerwania
  27.     EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
  28.     //Przerwanie aktywowane zboczem opadajacym
  29.     EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
  30.     EXTI_Init(&EXTI_InitStruct);
  31.     //Dodanie wektora przerwania do NVIC, PD2 podpiete pod EXTI2_IRQn
  32.     NVIC_InitStruct.NVIC_IRQChannel = EXTI2_IRQn;
  33.     //Wybranie prioryteru
  34.     NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x01;
  35.     //Podpriorytet
  36.     NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
  37.     //Wlaczenie przerwania
  38.     NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  39.     NVIC_Init(&NVIC_InitStruct);
  40. }

Do tego należy dodać funkcje obsługującą przerwania. Poniżej przedstawiam funkcję odpowiedzialną za ustawienie i generowanie sygnału szumu losowego dla DAC1.

  1. void DAC1Init_NoiseGeneration()
  2. {
  3.     DAC_InitTypeDef DACInit;
  4.     GPIO_InitTypeDef GPIOInit;  
  5.     //Wlaczenie zegara dla portu GPIOA
  6.     //RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
  7.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  8.    
  9.     //Wlaczenie zegara dla DAC
  10.     //RCC->APB1ENR |= RCC_APB1ENR_DACEN;
  11.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
  12.    
  13.     //Pozostale ustawienia wyprowadzen GPIO
  14.     GPIOInit.GPIO_Pin = GPIO_Pin_4;
  15.     GPIOInit.GPIO_Mode = GPIO_Mode_AN;
  16.     GPIOInit.GPIO_OType = GPIO_OType_PP;
  17.     GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  18.     GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  19.     GPIO_Init(GPIOA, &GPIOInit);
  20.    
  21.     //Ustawienie opcji dla DAC
  22.     //Sygnal wyzwalajacy
  23.     DACInit.DAC_Trigger = DAC_Trigger_Software;
  24.     //Generacja sygnalu wylaczona
  25.     DACInit.DAC_WaveGeneration = DAC_WaveGeneration_Noise;
  26.     //Bufor wyjsciowy wlaczony
  27.     DACInit.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
  28.     //0x3FF - 1111111111
  29.     DACInit.DAC_LFSRUnmask_TriangleAmplitude = 0x3FF;
  30.     DAC_Init(DAC_Channel_1, &DACInit);
  31.    
  32.     //Wlaczenie DAC-a
  33.     DAC_Cmd(DAC_Channel_1, ENABLE);
  34.     DAC_DMACmd(DAC_Channel_1, ENABLE);
  35.    
  36.     //Inicjacja i wlaczenie wybranego pinu DAC
  37.     DAC_Init(DAC_Channel_1, &DACInit);
  38. }

Poniżej przedstawiam programy obsługujące przerwania dla DAC1, oraz część włączającą generację sygnałów.

  1. void EXTI1_IRQHandler(void)
  2. {
  3.     if (EXTI_GetITStatus(EXTI_Line1) != RESET)
  4.         {
  5.         if (!keylog1 && !(GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_1)))
  6.         {
  7.                 keylog1 = 1;
  8.                 del(700);
  9.                 LCDDraw(0, 30, "         ");
  10.                 signaltype++;
  11.                    
  12.                 switch(signaltype)
  13.                 {
  14.                   case 1:
  15.                   {
  16.                     LCDDraw(0, 30, "Sinus 128");
  17.                     break;
  18.                   }
  19.                   case 2:
  20.                   {
  21.                     LCDDraw(0, 30, "Sinus");
  22.                     break;
  23.                   }
  24.                   case 3:
  25.                   {
  26.                     LCDDraw(0, 30, "Triangle");
  27.                     break;
  28.                   }
  29.                   case 4:
  30.                   {
  31.                     LCDDraw(0, 30, "Sawtooth");
  32.                     break;
  33.                   }
  34.                   case 5:
  35.                   {
  36.                     LCDDraw(0, 30, "Square");
  37.                     break;
  38.                   }
  39.                   case 6:
  40.                   {
  41.                     LCDDraw(0, 30, "Noise");
  42.                     break;
  43.                   }
  44.                   case 7:
  45.                   {
  46.                     signaltype = 0;
  47.                     LCDDraw(0, 30, "Brak");
  48.                     break;
  49.                   }
  50.               }
  51.         //Czyszczenie flagi przerwania
  52.         EXTI_ClearITPendingBit(EXTI_Line1);
  53.         keylog1 = 0;
  54.       }
  55.     }
  56. }
  57. void EXTI2_IRQHandler(void)
  58. {
  59.     char res[20];
  60.    
  61.     //Sprawdzenie czy flaga przerwania zostala ustawiona
  62.     if (EXTI_GetITStatus(EXTI_Line2) != RESET)
  63.     {
  64.         if (!keylog2 && !(GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_2)))
  65.         {
  66.             keylog2 = 1;
  67.             del(700);
  68.             Signal_Freq_DAC1++;
  69.                
  70.             sprintf(res, "%d [kHz]", Signal_Freq_DAC1);
  71.             LCDDraw(1, 30, res);   
  72.          }
  73.          EXTI_ClearITPendingBit(EXTI_Line2);
  74.          keylog2 = 0;
  75.     }
  76. }

Wszystkie części kodu zostaną udostępnione na dysku onet, dostępne z zakładki STM32 na blogu.

Co do częstotliwości i parametrów sygnałów. To sinus działa bardzo dobrze przy częstotliwości 100 kHz, Natomiast sygnał prostokątny przy 50kHz, z wypełnieniem równym 50%.

Należy pamiętać, że sygnały generowane są w przedziale od 0 do 4095, wobec tego punkt "zerowy" dla sygnałów znajdzie się w połowie tego zakresu. Powoduje to, że dla sygnału brak jest części ujemnej. Aby były generowane wartości ujemne należałoby podłączyć kondensator w szeregowo z wyjściem sygnału.