poniedziałek, 7 grudnia 2015

[I] STM32F4 - Discovery - Projekt - Wiatrak sterowany za pomocą PWM

Witam, tym razem chciałbym przedstawić projekt wykonany na STM32F4 - Discovery. Działanie programu polegało na sterowaniu współczynnikiem wypełnienia w celu regulacji prędkości obrotowej wiatraka. Współczynnik wypełnienia został przedstawiony na wyświetlaczu.

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);

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);
 }
}


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();
 }
}

Kolejne rysunki przedstawiają przebiegi sygnałów zarejestrowanych na analizatorze stanów logicznych.

Rys. 1. Przebiegi PWM 30%

Rys. 2. Przebiegi PWM 60%

Rys. 3. Przebiegi PWM 80%