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.
Elementem wykorzystywanym do prezentacji danych będzie wspomniany wcześniej wyświetlacz Nokia 5110. Jego podłączenie jest następujące:
Kolejnym elementem będą przyciski funkcyjne, które zostały podłączone w następujący sposób:
Sygnały z DAC-a został wystawiony na liniach PA4 oraz PA5.
Poniżej przedstawiam krótki opis poszczególnych części programu:
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.
Poniżej włączenie DAC1 z GPIO oraz DMA.
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:
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.
Poniżej przedstawiam programy obsługujące przerwania dla DAC1, oraz część włączającą generację sygnałów.
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.
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.
- //Deklaracja timera dla DAC1 PA4
- static void TIMConfig_DAC1(void)
- {
- TIM_TimeBaseInitTypeDef TimerInitStructure;
- //Wlaczenie zegara dla timera 6
- RCC_APB1PeriphClockCmd(Timer_Clock1, ENABLE);
- TIM_TimeBaseStructInit(&TimerInitStructure);
- //Ustawienie okresu jako:
- //zegar (APB1 Tim6/ilosc probek)*czestotliwosc sygnalu
- if(signaltype == 1)
- {
- TimerInitStructure.TIM_Period = (uint16_t)(TIM_APB_DAC1/Num_Samp_DAC1_128)*Signal_Freq_DAC1;
- }
- else
- {
- TimerInitStructure.TIM_Period = (uint16_t)(TIM_APB_DAC1/Num_Samp_DAC1_32)*(Signal_Freq_DAC1*1000)
- ;
- }
- TimerInitStructure.TIM_Prescaler = 0;
- TimerInitStructure.TIM_ClockDivision = 0;
- //Zliczanie w gore
- TimerInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
- TIM_TimeBaseInit(Tim_Number1, &TimerInitStructure);
- TIM_SelectOutputTrigger(Tim_Number1, TIM_TRGOSource_Update);
- TIM_Cmd(Tim_Number1, ENABLE);
- }
Poniżej włączenie DAC1 z GPIO oraz DMA.
- //Wlaczenie DAC1
- void DAC1Init()
- {
- DAC_InitTypeDef DACInit;
- GPIO_InitTypeDef GPIOInit;
- DMA_InitTypeDef DMAInit;
- //Wlaczenie zegara dla portu GPIOA
- //RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
- //Wlaczenie zegara dla DAC
- //RCC->APB1ENR |= RCC_APB1ENR_DACEN;
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
- //Wlaczenie zegara dla DMA
- //RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
- //Pozostale ustawienia wyprowadzen GPIO
- GPIOInit.GPIO_Pin = GPIO_Pin_4;
- GPIOInit.GPIO_Mode = GPIO_Mode_AN;
- GPIOInit.GPIO_OType = GPIO_OType_PP;
- GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIOInit);
- //Ustawienie opcji dla DAC
- //Sygnal wyzwalajacy
- DACInit.DAC_Trigger = DAC1_Trigger;
- //Generacja sygnalu wylaczona
- DACInit.DAC_WaveGeneration = DAC_WaveGeneration_None;
- //Bufor wyjsciowy wlaczony
- DACInit.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
- DAC_Init(DAC_Channel_1, &DACInit);
- DMA_DeInit(DMA1_Stream5);
- DMAInit.DMA_Channel = DMA_Channel_7;
- //Data holding register, 12 bit, right aligned ch1
- DMAInit.DMA_PeripheralBaseAddr = (uint32_t)&DAC->DHR12R1;
- switch(signaltype)
- {
- case 1:
- {
- DMAInit.DMA_Memory0BaseAddr = (uint32_t)Sinus2;
- break;
- }
- case 2:
- {
- DMAInit.DMA_Memory0BaseAddr = (uint32_t)Sinus;
- break;
- }
- case 3:
- {
- DMAInit.DMA_Memory0BaseAddr = (uint32_t)Triangle;
- break;
- }
- case 4:
- {
- DMAInit.DMA_Memory0BaseAddr = (uint32_t)Sawtooth;
- break;
- }
- case 5:
- {
- DMAInit.DMA_Memory0BaseAddr = (uint32_t)Square;
- break;
- }
- }
- //Jak dane beda generowane
- DMAInit.DMA_DIR = DMA_DIR_MemoryToPeripheral;
- if(signaltypedac2 == 1)
- {
- //Liczba próbek sygnalu
- DMAInit.DMA_BufferSize = Num_Samp_DAC1_128;
- }
- else
- {
- DMAInit.DMA_BufferSize = Num_Samp_DAC1_32;
- }
- //Wylaczony automatyczny zwiekszanie adresu
- DMAInit.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
- //Wylaczony transfer z pamieci do pamieci
- DMAInit.DMA_MemoryInc = DMA_MemoryInc_Enable;
- //Rozmiar przeslanych danych 16b
- DMAInit.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
- //Rozmiar danych po stronie pamieci
- DMAInit.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
- //Ciagly tryb pracy
- DMAInit.DMA_Mode = DMA_Mode_Circular;
- //Priprytet wysoki
- DMAInit.DMA_Priority = DMA_Priority_High;
- //Tryb fifo wylaczony
- DMAInit.DMA_FIFOMode = DMA_FIFOMode_Disable;
- DMAInit.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
- //Pojedyncze wyzwalanie
- DMAInit.DMA_MemoryBurst = DMA_MemoryBurst_Single;
- DMAInit.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
- //Wylaczenie DMA
- DMA_DeInit(DMA1_Stream5);
- //Inicjacja i wlaczenie DMA
- DMA_Init(DMA1_Stream5, &DMAInit);
- DMA_Cmd(DMA1_Stream5, ENABLE);
- //Wlaczenie DAC-a
- DAC_Cmd(DAC_Channel_1, ENABLE);
- DAC_DMACmd(DAC_Channel_1, ENABLE);
- //Inicjacja i wlaczenie wybranego pinu DAC
- DAC_Init(DAC_Channel_1, &DACInit);
- //Wlaczenie kanalu pierwszego
- //DAC->CR |= DAC_CR_EN1;
- DAC_Cmd(DAC_Channel_1, ENABLE);
- }
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:
- void Configure_PD2(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- EXTI_InitTypeDef EXTI_InitStruct;
- NVIC_InitTypeDef NVIC_InitStruct;
- //Wlaczenie zegarow
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
- //Ustawienie pinu dla przycisku, jako wejscie
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
- GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
- GPIO_Init(GPIOD, &GPIO_InitStruct);
- //Przypisanie przerwania pod PD2
- SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOD, EXTI_PinSource2);
- //Wlaczenie lini PD2 dla przerwania
- EXTI_InitStruct.EXTI_Line = EXTI_Line2;
- //Wlaczenie przerwania
- EXTI_InitStruct.EXTI_LineCmd = ENABLE;
- //Wlaczenie trybu przerwania
- EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
- //Przerwanie aktywowane zboczem opadajacym
- EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
- EXTI_Init(&EXTI_InitStruct);
- //Dodanie wektora przerwania do NVIC, PD2 podpiete pod EXTI2_IRQn
- NVIC_InitStruct.NVIC_IRQChannel = EXTI2_IRQn;
- //Wybranie prioryteru
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x01;
- //Podpriorytet
- NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
- //Wlaczenie przerwania
- NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
- NVIC_Init(&NVIC_InitStruct);
- }
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.
- void DAC1Init_NoiseGeneration()
- {
- DAC_InitTypeDef DACInit;
- GPIO_InitTypeDef GPIOInit;
- //Wlaczenie zegara dla portu GPIOA
- //RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
- //Wlaczenie zegara dla DAC
- //RCC->APB1ENR |= RCC_APB1ENR_DACEN;
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
- //Pozostale ustawienia wyprowadzen GPIO
- GPIOInit.GPIO_Pin = GPIO_Pin_4;
- GPIOInit.GPIO_Mode = GPIO_Mode_AN;
- GPIOInit.GPIO_OType = GPIO_OType_PP;
- GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIOInit);
- //Ustawienie opcji dla DAC
- //Sygnal wyzwalajacy
- DACInit.DAC_Trigger = DAC_Trigger_Software;
- //Generacja sygnalu wylaczona
- DACInit.DAC_WaveGeneration = DAC_WaveGeneration_Noise;
- //Bufor wyjsciowy wlaczony
- DACInit.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
- //0x3FF - 1111111111
- DACInit.DAC_LFSRUnmask_TriangleAmplitude = 0x3FF;
- DAC_Init(DAC_Channel_1, &DACInit);
- //Wlaczenie DAC-a
- DAC_Cmd(DAC_Channel_1, ENABLE);
- DAC_DMACmd(DAC_Channel_1, ENABLE);
- //Inicjacja i wlaczenie wybranego pinu DAC
- DAC_Init(DAC_Channel_1, &DACInit);
- }
Poniżej przedstawiam programy obsługujące przerwania dla DAC1, oraz część włączającą generację sygnałów.
- void EXTI1_IRQHandler(void)
- {
- if (EXTI_GetITStatus(EXTI_Line1) != RESET)
- {
- if (!keylog1 && !(GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_1)))
- {
- keylog1 = 1;
- del(700);
- LCDDraw(0, 30, " ");
- signaltype++;
- switch(signaltype)
- {
- case 1:
- {
- LCDDraw(0, 30, "Sinus 128");
- break;
- }
- case 2:
- {
- LCDDraw(0, 30, "Sinus");
- break;
- }
- case 3:
- {
- LCDDraw(0, 30, "Triangle");
- break;
- }
- case 4:
- {
- LCDDraw(0, 30, "Sawtooth");
- break;
- }
- case 5:
- {
- LCDDraw(0, 30, "Square");
- break;
- }
- case 6:
- {
- LCDDraw(0, 30, "Noise");
- break;
- }
- case 7:
- {
- signaltype = 0;
- LCDDraw(0, 30, "Brak");
- break;
- }
- }
- //Czyszczenie flagi przerwania
- EXTI_ClearITPendingBit(EXTI_Line1);
- keylog1 = 0;
- }
- }
- }
- void EXTI2_IRQHandler(void)
- {
- char res[20];
- //Sprawdzenie czy flaga przerwania zostala ustawiona
- if (EXTI_GetITStatus(EXTI_Line2) != RESET)
- {
- if (!keylog2 && !(GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_2)))
- {
- keylog2 = 1;
- del(700);
- Signal_Freq_DAC1++;
- sprintf(res, "%d [kHz]", Signal_Freq_DAC1);
- LCDDraw(1, 30, res);
- }
- EXTI_ClearITPendingBit(EXTI_Line2);
- keylog2 = 0;
- }
- }
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.