Generatory PWM – obsługa programowa
Często mikrokontrolery sterują układami peryferyjnymi za pomocą sygnału zegarowego lub sygnałów PWM. W przykładzie pokażemy działanie i praktyczne wykorzystanie licznika TIM1 oraz możliwość jego współpracy z innymi blokami mikrokontrolera.
Dioda LED2 na płytce STM32Butterfly jest włączona na stałe, natomiast dioda LED1 jest sterowana za pomocą licznika TIM1. Program można wgrać do pamięci RAM.
Sterowanie odbywa się przy wykorzystaniu joysticka, przełączenie go w odpowiednią pozycję powoduje następującą reakcję:
- wciśnięcie – wyłączenie licznika oraz włączenie na stałe diody LED1,
- w prawo – generowanie sygnału PWM, ustawienie jasności świecenia diody,
- w lewo – generowanie sygnału zegarowego o małej częstotliwości, miganie diody LED1,
- w górę – jeśli generowany jest sygnał PWM to zwiększenie wartości współczynnika wypełnienia (LED1 będzie świeciła jaśniej), jeśli generowany jest sygnał zegarowy to nastąpi zwiększenie częstotliwości sygnału,
- w dół – jeśli generowany jest sygnał PWM to zmniejszenie wartości współczynnika wypełnienia (LED1 będzie świeciła słabiej), jeśli generowany jest sygnał zegarowy to nastąpi zmniejszenie częstotliwości sygnału.
Należy pamiętać, że w zestawie STM32Butterfly diody LED1 i LED2 zapala się poprzez ustawienie stanu niskiego na wyjściu. Wykrywanie zmiany położenia joysticka zostało zrealizowane przy wykorzystaniu przerwań.
W programie wykorzystywane są następujące zmienne:
int a; //zmienna definiujaca aktualna konfiguracje danego trybu int b; //zmienna definiujaca aktualny tryb dzialania: // 0 - brak generowanego sygnalu, // 1 - generowany sygnal PWM, // 2 - generowany sygnal zegarowy int c[] = {0,2,4,8,15,30,60,125,175,250,500,1000}; //tablica sterowania wypelnieniem sygnalu PWM
Modułami wykorzystywanymi w projekcie są AFIO, GPIOE oraz TIM1, należy pamiętać o doprowadzeniu sygnału zegarowego do tych modułów poprzez polecenie:
//wlaczenie sygnalu zegarowego dla wykorzystywanych elementow: // TIM1, GPIOE, AFIO RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOE | RCC_APB2Periph_TIM1, ENABLE);
Konfiguracja pinów sterujących diodami jest następująca:
//ustawienie portów wyjsc (sterowanie diodami LED), //LED1 - sterowany przez timer, LED2 zapalony na stale GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOE, &GPIO_InitStructure); // GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOE, &GPIO_InitStructure); GPIO_ResetBits(GPIOE, GPIO_Pin_15);
Następnie jest przeprowadzana konfiguracja sygnałów generujących przerwania poprzez ustawienie wykorzystywanych pinów. Bardzo ważnym poleceniem jest NVIC_SetVectorTable biblioteki Standard Peripherals Library, informującym gdzie znajduje się tablica NVIC. Ma to znaczenie dla programu wgrywanego do pamięci RAM mikrokontrolera, gdyż domyślnie tablica jest w pamięci Flash, a dla programu wgrywanego do pamięci RAM program szukałby tablicy właśnie w pamięci RAM. Następnie przeprowadzana jest konfiguracja kanałów przerwań EXTI9_5_IRQn i EXTI10_15_IRQn wraz z ustawieniem linii jako generującej przerwanie oraz zdefiniowania sposobu generowania przerwania.
//konfiguracja pinow generujacych przerwanie GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOE, &GPIO_InitStructure); //zdefiniowanie polozenia tablicy przerwan NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); //wybranie grupy priorytetów NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //konfiguracja NVIC dla przerwania EXTI9_5_IRQn NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); //konfiguracja NVIC dla przerwania EXTI15_10_IRQn NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); //ustwienie dzialania GPIO jako linii EXTI GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource8); GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource9); GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource10); GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource11); GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource12); //ustawienie sposobu generowania przerwania EXTI_InitStructure.EXTI_Line = EXTI_Line8 | EXTI_Line9 | EXTI_Line10 | EXTI_Line11 | EXTI_Line12; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure);
Na samym końcu głównego programu włączany jest remaping, wymagane jest to ze względu iż standardowym wyjściem kanału czwartego licznika TIM1 (wykorzystywanego do sterowania diodą w projekcie) jest PA11 natomiast przy włączeniu pełnego remapingu dla timera TIM1 uzyskamy dla kanału czwartego wyjście PE14, do którego jest podłączona dioda LED1. Włączenie remapingu jest realizowane w następujący sposób:
//wlaczenie remapingu timera1 umozliwiajac w ten sposob //sterowanie dioda LED1, poprzez kanal 4 GPIO_PinRemapConfig(GPIO_FullRemap_TIM1, ENABLE);
Powyższy opis dotyczył wstępnej konfiguracji wykorzystywanych funkcji mikrokontrolera, pomijając konfigurację timera, która ma miejsce w obsłudze przerwań ze względu na rozróżnienie czy ma być generowany sygnał zegarowy czy sygnał PWM. Cała obsługa działania jest realizowana poprzez funkcje:
EXTI9_5_IRQHandler(void)
oraz
EXTI15_10_IRQHandler(void)
Należy pamiętać, że te funkcje grupują przerwania, w związku z czym w samej funkcji naeży rozpoznać źródło przerwania realizuje się to poprzez warunki EXTI_GetITStatus(EXTI_Line8) != RESET i analogicznie dla pozostałych linii. Należy także pamiętać o wyczyszczeniu flagi przerwania przy pomocy polecenia EXTI_ClearITPendingBit(EXTI_Line8), analogicznie wykonywanego dla pozostałych linii.
Konfiguracja licznika jako generatora sygnału zegarowego o małej częstotliwości została przypisana do pozycji joysticka w prawo, a jest realizowana następująco:
//przerwanie wywolane przez joystick w pozycji - right else if(EXTI_GetITStatus(EXTI_Line11) != RESET) { //ustawienie trybu dzialania oraz aktualnej konfiguracji b = 2; a = 8; //konfiguracja timera TIM1 jako generatora sygnalu //wolnozmiennego poprzez zadane wartosci dzielnika //zegara i preskalera czestotliwosc wynosi okolo 274 Hz TIM_TimeBaseStructure.TIM_Period = a * 34; TIM_TimeBaseStructure.TIM_Prescaler = 0xFFFF; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV4; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //konfiguracja kanału 4 timera TIM1 Tim_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle; Tim_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; Tim_OCInitStructure.TIM_Pulse = 17; Tim_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC4Init(TIM1, &Tim_OCInitStructure); TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); //zaladowanie konfiguracji TIM_ARRPreloadConfig(TIM1, DISABLE); //wlaczenie timera TIM1 TIM_Cmd(TIM1, ENABLE); //wlaczenie pinow wyjsciowych timera TIM_CtrlPWMOutputs(TIM1, ENABLE); }
W celu uzyskania sygnału wolnozmiennego zmienne struktury TIM_Prescaler oraz TIM_ClockDivision ustawiono na najwyższe wartości, uzyskując w ten sposób częstotliwość 274 Hz (dla częstotliwości APB2 wynoszącej 72 MHz), częstotliwość jest ustawiana poprzez zdefiniowanie wartości zmiennej TIM_Period. Zmienna TIM_Pulse definiuje w, którym momencie nastąpi zmiana stanów. W przypadku konfiguracji licznika TIM1 , jako bardziej rozbudowanego, w celu włączenia wyjść należy dodać polecenie TIM_CtrlPWMOutputs(TIM1, ENABLE).
W przypadku generowania sygnału PWM konfiguracja jest identyczna, z tą różnicą iż wypełnienie steruje się poprzez wartość zmiennej TIM_Pulse struktury opisującej kanał licznika. Konfiguracja następuje przez wygenerowanie przerwania przez pozycję josticka w lewo. Cała konfiguracja prezentuje się następująco:
//przerwanie wywolane przez joystick w pozycji - left else if(EXTI_GetITStatus(EXTI_Line12) != RESET) { //ustawienie trybu dzialania oraz aktualnej konfiguracji b = 1; a = 6; // konfiguracja timera TIM1 TIM_TimeBaseStructure.TIM_Period = 999; TIM_TimeBaseStructure.TIM_Prescaler = 0xFF; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV4; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //konfiguracja kanalu 4 Tim_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; Tim_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; Tim_OCInitStructure.TIM_Pulse = 1000 - c[a]; Tim_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC4Init(TIM1, &Tim_OCInitStructure); TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); //zaladowanie konfiguracji TIM_ARRPreloadConfig(TIM1, ENABLE); //wlaczenie timera TIM3 TIM_Cmd(TIM1, ENABLE); //wlaczenie pinow wyjsciowych timera TIM_CtrlPWMOutputs(TIM1, ENABLE); }