Wstęp
W skład tego projektu wchodzą:
- Wiatrak 12V czteropinowy.
- STM32F4 Discovery
- Przyciski funkcyjne 3x
- Wyświetlacz HD44780
- Dioda LED
Ogólna zasada działania jest dosyć prosta na wiatrak wysyłany jest sygnał PWM zależny od ustawionej wartości. Tą wartość można zwiększać bądź zmniejszać o 10%. Możliwe jest także całkowite wyłączenie wiatraka. Każdy z przycisków został zaprogramowany tak aby w momencie jego naciśnięcia zostało wykonywane przerwanie. W obsłudze przerwania zostają wykonywane zaprogramowane elementy.
Na wyświetlaczu przedstawiona jest aktualna wartość PWM jaka została ustawiona. Dodatkowo na wejście sygnału sterującego prędkością wiatraka dołączyłem diodę w celu obserwacji zmian sygnału.
Podłączenie
Układ podłączyłem w następujący sposób:
Wyświetlacz:
- GND - GND,
- VCC - 5V,
- V0 - potencjometr, do sterowania kontrastem,
- RS - PB2,
- RW - GND,
- E - PB7,
- D0, D1, D2, D3 - niepodłączone,
- D4 - PC12,
- D5 - PC13,
- D6 - PC14,
- D7 - PC15,
- A - 3,3V,
- K - GND,
Przycisk:
- Przycisk zwiększający wartość PA3,
- Przycisk zmniejszający - PA2,
- Wyłączenie PWM - PA1,
Wiatrak był zasilaniu z zewnętrznego źródła połączonego wspólnym punktem masy z STM32. Sygnał PWM generowany z portu PE11.
W kolejnych puntach przedstawiam poszczególne części programu wraz z opisem. Całość projektu jest umieszczona w linku w sekcji STM32.
Inicjalizacja pinów
Pierwszym elementem od którego należy zacząć jest inicjalizacja portów wejścia/wyjścia. Poniżej przedstawiam fragment kodu, który pozwoli zainicjalizować działanie jednego z przycisków.
W pierwszej kolejności należy podać struktury GPIO. Potem deklaracja zegara. Kolejne pola zawierają dokładną deklarację wykorzystywanego pinu. Przechodząc od początku funkcje linii są następujące: wybranie trybu pracy na wejściowy, ustawienie wejścia jako push-pull, wybranie pinu, rezystor podciągający do zasilania, częstotliwość taktowania linii. Ostatnia definicja pozwala na włączenie wybranego portu.
GPIO_InitTypeDef GPIO_InitStruct; //Wlaczenie zegara w tym przypadku RCC_AHB1Periph_GPIOA RCC_AHB1PeriphClockCmd(Button_Off_RCC, ENABLE); GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_Pin = Button_Off_Pin; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Init(Button_Off_Port, &GPIO_InitStruct);
Przerwanie
W celu poprawnej obsługi przerwania należy włączyć układy NVIC oraz EXTI. Oba pola zostały umieszczone w funkcji włączającej odpowiedni pin.
Poniżej przedstawiam fragment kodu wraz z komentarzem:
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
//Wlaczenie zegara dla SYSCFG
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
//Wybranie EXTI dla konkretnego pinu
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource1);
//PE4 polaczone z EXTI_Line4
EXTI_InitStruct.EXTI_Line = EXTI_Line1;
//Wlaczenie przerwania
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
//Wlaczenie trybu przerwania
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
//Działanie na zbocze rosnące i opadające
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
//Dodanie do EXTI
EXTI_Init(&EXTI_InitStruct);
//Dodanie wektora przerwań do NVIC
//Podłączenie PA1 do EXTI1_IRQn
NVIC_InitStruct.NVIC_IRQChannel = EXTI1_IRQn;
//Wartość priorytetu grupowego
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x00;
//Ustawienie podpriorytetu
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
//wlaczenie przerwania
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
W celu poprawnej obsługi przerwania należy włączyć układy NVIC oraz EXTI. Oba pola zostały umieszczone w funkcji włączającej odpowiedni pin.
Poniżej przedstawiam fragment kodu wraz z komentarzem:
EXTI_InitTypeDef EXTI_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; //Wlaczenie zegara dla SYSCFG RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); //Wybranie EXTI dla konkretnego pinu SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource1); //PE4 polaczone z EXTI_Line4 EXTI_InitStruct.EXTI_Line = EXTI_Line1; //Wlaczenie przerwania EXTI_InitStruct.EXTI_LineCmd = ENABLE; //Wlaczenie trybu przerwania EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; //Działanie na zbocze rosnące i opadające EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //Dodanie do EXTI EXTI_Init(&EXTI_InitStruct); //Dodanie wektora przerwań do NVIC //Podłączenie PA1 do EXTI1_IRQn NVIC_InitStruct.NVIC_IRQChannel = EXTI1_IRQn; //Wartość priorytetu grupowego NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x00; //Ustawienie podpriorytetu NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00; //wlaczenie przerwania NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct);
Obsługa przerwania
Kolejnym elementem jest obsługa przerwania. W tym projekcie wykorzystywane one były do zmniejszania, zwiększania bądź całkowitego wyłączenia wiatraka. Poniżej przedstawiam jedną cześć kodu dla zwiększania wartości PWM.
void EXTI3_IRQHandler(void)
{
//Sprawdzenie flagi przerwania
if (EXTI_GetITStatus(EXTI_Line3) != RESET)
{
//Ponowne ustawienie parametrów PWM
TIM_OCInitTypeDef outputChannelInit;
outputChannelInit.TIM_OCMode = TIM_OCMode_PWM2;
outputChannelInit.TIM_Pulse = pul;
outputChannelInit.TIM_OutputState = TIM_OutputState_Enable;
outputChannelInit.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC2Init(TIM1, &outputChannelInit);
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
//Jeśli keylock = 0 oraz stan portu też jest 0 wtedy wejdź do pętli
if (!keylock && !(GPIO_ReadInputDataBit(Button_Up_Port, Button_Up_Pin)))
{
keylock=1;
//Sprawdzenie czy PWM równa się 100%
if (pul >= 16000)
{
//Jeśli tak to wyzeruj ustawienia
pul=0;
proc=0;
outputChannelInit.TIM_Pulse = pul;
TIM_OC2Init(TIM1, &outputChannelInit);
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
DelayMain();
}
else
{
//W przeciwnym wypadku zwiększ wartość o 10%
pul=pul + 1600;
proc=proc+10;
outputChannelInit.TIM_Pulse = pul;
TIM_OC2Init(TIM1, &outputChannelInit);
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
DelayMain();
}
}
//wyzerowanie zabezpieczenie zwiekszajacego PWM
else if (keylock && (GPIO_ReadInputDataBit(Button_Up_Port, Button_Up_Pin)))
{
DelayMain();
keylock=0;
}
//Wyzerowanie flagi
EXTI_ClearITPendingBit(EXTI_Line3);
}
}
Kolejnym elementem jest obsługa przerwania. W tym projekcie wykorzystywane one były do zmniejszania, zwiększania bądź całkowitego wyłączenia wiatraka. Poniżej przedstawiam jedną cześć kodu dla zwiększania wartości PWM.
void EXTI3_IRQHandler(void) { //Sprawdzenie flagi przerwania if (EXTI_GetITStatus(EXTI_Line3) != RESET) { //Ponowne ustawienie parametrów PWM TIM_OCInitTypeDef outputChannelInit; outputChannelInit.TIM_OCMode = TIM_OCMode_PWM2; outputChannelInit.TIM_Pulse = pul; outputChannelInit.TIM_OutputState = TIM_OutputState_Enable; outputChannelInit.TIM_OCPolarity = TIM_OCPolarity_Low; TIM_OC2Init(TIM1, &outputChannelInit); TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); //Jeśli keylock = 0 oraz stan portu też jest 0 wtedy wejdź do pętli if (!keylock && !(GPIO_ReadInputDataBit(Button_Up_Port, Button_Up_Pin))) { keylock=1; //Sprawdzenie czy PWM równa się 100% if (pul >= 16000) { //Jeśli tak to wyzeruj ustawienia pul=0; proc=0; outputChannelInit.TIM_Pulse = pul; TIM_OC2Init(TIM1, &outputChannelInit); TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); DelayMain(); } else { //W przeciwnym wypadku zwiększ wartość o 10% pul=pul + 1600; proc=proc+10; outputChannelInit.TIM_Pulse = pul; TIM_OC2Init(TIM1, &outputChannelInit); TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); DelayMain(); } } //wyzerowanie zabezpieczenie zwiekszajacego PWM else if (keylock && (GPIO_ReadInputDataBit(Button_Up_Port, Button_Up_Pin))) { DelayMain(); keylock=0; } //Wyzerowanie flagi EXTI_ClearITPendingBit(EXTI_Line3); } }
Timer
Poniższy kod pokazuje inicjalizację timera. W tym przypadku jest to TIM1. Programowanie rozpoczyna się od przypisania struktury oraz włączenia zegara APB2 dla tego układu. Następne linijki zawierają odpowiednio takie funkcje jak: ustawienie wartości dzielnika częstotliwości, sposób zliczania, wartość okresu, dzielnik dla zegara czasu martwego oraz filtru, licznik powtórzeń. Kolejne linie kodu zapisują oraz włączają wpisane ustawienia.
void InitializeTimer(void) { TIM_TimeBaseInitTypeDef timerInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); timerInitStructure.TIM_Prescaler = 0; timerInitStructure.TIM_CounterMode = TIM_CounterMode_Up; timerInitStructure.TIM_Period = 16000; timerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; timerInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM1, &timerInitStructure); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }
Inicjalizacja sygnału PWM
Poniżej przedstawiłem fragment kodu inicjalizujący sygnał PWM. Konieczne informacje umieściłem bezpośrednio w kodzie programu.
void InitializePWM(void) { //Struktura TIM_OCInitTypeDef outputChannelInit; //Tryb pracy outputChannelInit.TIM_OCMode = TIM_OCMode_PWM1; //Wartosc wypelnienia outputChannelInit.TIM_Pulse = pul; //Wlaczenie, bo bedzie wykorzystywany do sterowania outputChannelInit.TIM_OutputState = TIM_OutputState_Enable; //Stan wysoki outputChannelInit.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC2Init(TIM1, &outputChannelInit); TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); //Wlaczenie Tim TIM_Cmd(TIM1, ENABLE); //Ustawienie pinu z którego dany timer bedzie wykorzystywany jako alternatywny, GPIO_PinAFConfig(GPIOE, GPIO_PinSource11, GPIO_AF_TIM1); }
Ostatnim elementem jest zaprogramowanie wyświetlacza. Ja do tego celu wykorzystałem bibliotekę dla STM32 jaką znalazłem na internecie. W związku z tym nie będę jej tutaj opisywał.
Na koniec zaprezentuję główną pętlę programu. W niej zawarłem odwołania do pozostałych funkcji w programie. W pętli while znajduje się cześć programu wyświetlająca informacje o aktualnej wartości współczynnika wypełnienia.
int main() { char* buf; int PWMPro; InitializePins(); InitializeTimer(); Configure_Button_Off(); Configure_Button_Down(); Configure_Button_Up(); GPIOD->BSRRL = 0xF000; //ustawienie PD12 do PD14 czyli diod wbudowanych DelayMain(); GPIOD->BSRRH = 0xF000; //wylaczenie diod pd12 do pd14 HD44780_Init(16, 2); HD44780_Puts(3, 0, "STM32F4"); Delayms(3000); HD44780_Clear(); while(1) { PWMPro=proc; itoa(PWMPro, buf, 10); HD44780_Puts(0, 0, "PWM:"); HD44780_Puts(4, 0, buf); HD44780_Puts(7, 0, "%"); Delayms(500); HD44780_Clear(); } }