DZWON

Są tacy, którzy czytają tę wiadomość przed tobą.
Zapisz się, aby otrzymywać najnowsze artykuły.
E-mail
Imię
Nazwisko
Jak chcesz przeczytać The Bell
Bez spamu

Tryb przechwytywania jest specjalnym trybem timera, którego istota jest następująca, gdy poziom logiczny jest zmieniany na pewnym pinie mikrokontrolera, wartość rejestru zliczającego jest zapisywana do innego rejestru, który jest nazywany rejestrem przechwytywania.

Po co to jest?
W tym trybie można zmierzyć szerokość impulsu lub okres sygnału.

Tryb przechwytywania STM32 ma kilka funkcji:

  • możliwość wyboru, który front będzie aktywny
  • możliwość zmiany częstotliwości sygnału wejściowego za pomocą preskalera (1,2,4,8)
  • każdy kanał przechwytywania jest wyposażony we wbudowany filtr wejściowy
  • sygnał przechwytywania może pochodzić z innego timera
  • dla każdego kanału są dwie flagi, pierwsza jest ustawiana, jeśli nastąpiło przechwytywanie, druga, jeśli przechwytywanie nastąpiło, gdy ustawiono pierwszą flagę

Rejestry służą do konfigurowania trybu przechwytywania CCMR1(dla kanałów 1 i 2) i CCMR2(dla 3 i 4) oraz rejestry CCER, DIER.

Przyjrzyjmy się bliżej polom bitowym rejestru. CCMR2odpowiedzialny za ustawienie 4 kanałów timera, ustawimy go w przykładzie. Chciałbym również zauważyć, że w tym samym rejestrze znajdują się pola bitowe, które są używane podczas ustawiania timera w trybie porównania.

CC4S - określa kierunek działania czwartego kanału (wejściowego lub wyjściowego). Podczas konfigurowania kanału jako wejścia dopasowuje on sygnał przechwytywania

  • 00 - kanał działa jako wyjście
  • 01 - kanał działa jako wejście, sygnał przechwytywania - TI4
  • 10 - kanał pracuje jako wejście, sygnał przechwytywania - TI3
  • 11 - kanał pracuje jako wejście, sygnał przechwytywania - TRC
IC4PSC - określić współczynnik podziału sygnału przechwytywania
  • 00 - dzielnik nie jest używany, dla każdego zdarzenia generowany jest sygnał przechwytywania IC1PS
  • 01 - sygnał przechwytywania jest generowany dla co drugiego zdarzenia
  • 10 - sygnał przechwytywania jest generowany dla co czwartego zdarzenia
  • 11 - sygnał przechwytywania jest generowany dla co ósmego zdarzenia
IC4F - przeznaczony do regulacji filtra wejściowego, oprócz ilości próbek, podczas których mikrokontroler nie będzie reagował na sygnały wejściowe, można również regulować częstotliwość próbkowania. Zasadniczo dostosowujemy czas opóźnienia od momentu przybycia krawędzi do próbki „potwierdzającej”.

Teraz spójrzmy na rejestr CCER.

CC4E - włącza / wyłącza tryb przechwytywania.
CC4P - określa przód, wzdłuż którego bicie zostanie wykonane, 0 - przód, 1 - tył.

I zarejestruj się DIER.

CC4DE - umożliwia złożenie wniosku do DMA.
CC4IE - włącza przerwanie przy przechwytywaniu.

Po zakończeniu przechwytywania generowane jest zdarzenie przechwytywania, które ustawia odpowiednią flagę. Może to wygenerować przerwanie i żądanie DMAjeśli są dozwolone w rejestrze DIER... Ponadto zdarzenie przechwytywania można generować programowo, ustawiając pole bitowe w rejestrze generowania zdarzeń EGR:

Pola bitowe CC1G, CC2G, CC3G i CC4G pozwalają na wygenerowanie zdarzenia w odpowiednim kanale przechwytywania / porównywania.

Tak poza tym, CCR1, CCR2, CCR3 i CCR4 - rejestry przechwytywania, w których wartość timera jest zapisywana przez sygnał przechwytywania.

Aby kontrolować tworzenie sygnału przechwytywania, w rejestrze SR dla każdego kanału przydzielone są dwie flagi.

CC4IF - ustawiane, gdy generowany jest sygnał przechwytywania, flagi te są usuwane przez oprogramowanie lub przez odczyt odpowiedniego rejestru przechwytywania / porównywania.
CC4OF - ustawiane, jeśli flaga CC4IF nie została wyczyszczona, ale nadszedł następny sygnał przechwytywania. Ta flaga jest czyszczona programowo, zapisując zero.

Teraz zastosujmy wiedzę zdobytą w praktyce, od generatora sygnału do wejścia TIM5_CH4, dostarczymy sinusoidę o częstotliwości 50 Hz i spróbujemy zmierzyć jej okres. Aby przyspieszyć ten proces, sugeruję użycie DMA. Które wyjście MK odpowiada kanałowi 4 TIM5 można znaleźć w arkuszu danych na MK w sekcji Pinouty i opis pinów.

Dla DMA adres rejestracji wymagany CCR4, oto jak to znaleźć. Otwieramy RM0008 i w tabeli Zarejestruj adresy graniczne znajdź adres początkowy TIM5.


rejestracja przesunięcia CCR4 można znaleźć w tym samym dokumencie pod adresem zarejestruj mapę.

#include "stm32f10x.h" #define TIM5_CCR4_Address ((u32) 0x40000C00 + 0x40) #define DMA_BUFF_SIZE 2 uint16_t buff; // Bufor uint16_t ulotny T; void DMA2_Channel1_IRQHandler (void) (T \u003d (buff\u003e buff)? (buff - buff): (65535+ buff - buff); DMA2-\u003e IFCR | \u003d DMA_IFCR_CGIF1;) void Init_DMA (void) (RCC-\u003e AHBENR | \u003d RCC_MAHBEN ; // Włącz taktowanie pierwszego modułu DMA DMA2_Channel1-\u003e CPAR \u003d TIM5_CCR4_Address; // Podaj adres peryferyjny - rejestr wyniku konwersji ADC dla zwykłych kanałów DMA2_Channel1-\u003e CMAR \u003d (uint32_t) buff; // Podaj adres pamięci - adres bazowy tablicy2_C w pamięci RAM DMAann -\u003e CCR & \u003d ~ DMA_CCR1_DIR; // Określ kierunek transferu danych z peryferii do pamięci DMA2_Channel1-\u003e CNDTR \u003d DMA_BUFF_SIZE; // Ilość przesyłanych wartości DMA2_Channel1-\u003e CCR & \u003d ~ DMA_CCR1_PINChery; // Po każdym przesłaniu nie zwiększamy adresu -\u003e CCR | \u003d DMA_CCR1_MINC; // Adres pamięci jest zwiększany po każdym transferze.DMA2_Channel1-\u003e CCR | \u003d DMA_CCR1_PSIZE_0; // Rozmiar danych peryferyjnych - 16 bitów DMA2_Channel1-\u003e CCR | \u003d DMA_CCR1_MSIZE1 rozmiar pamięci - 16C \u003e CCR | \u003d DMA_CCR1_PL; // Priorytet jest bardzo wysoki DMA2_Channel1-\u003e CCR | \u003d DMA_CCR1_CIRC; // Włącz działanie DMA w trybie cyklicznym DMA2_Channel1-\u003e CCR | \u003d DMA_CCR1_TCIE; // Włącz przerwanie na końcu transferu DMA2_Channel1-\u003e CCR | \u003d DMA_CCR1_EN; // Włącz działanie pierwszego kanału DMA) int main (void) (Init_DMA (); // włącz taktowanie portu A, alternatywne funkcje i timer RCC-\u003e APB2ENR | \u003d RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN; RCC-\u003e APB1ENR | \u003d RCC5_APB1; -\u003e PSC \u003d 56000-1; // nowa częstotliwość 1Khz TIM5-\u003e CCMR2 | \u003d TIM_CCMR2_CC4S_0; // wybierz TI4 dla TIM5_CH4 TIM5-\u003e CCMR2 & \u003d ~ (TIM_CCMR2_IC4F | TIM_CCMR2_IC4PSC); // nie używaj dzielnika i nie filtruj \u003e CCER & \u003d ~ TIM_CCER_CC4P; // wybierz przechwytywanie na krawędzi czołowej TIM5-\u003e CCER | \u003d TIM_CCER_CC4E; // włącz tryb przechwytywania dla 4. kanału TIM5-\u003e DIER | \u003d TIM_DIER_CC4DE; // włącz tworzenie żądania do DMA // TIM5 -\u003e DIER | \u003d TIM_DIER_CC4IE; // włącz przerwanie przy przechwytywaniu TIM5-\u003e CR1 | \u003d TIM_CR1_CEN; // włącz licznik // NVIC-\u003e ISER | \u003d NVIC_ISER_SETENA_18; // TIM5 Przerwij NVIC-\u003e ISER | \u003d NVIC_ISER_SETMA_24; // Przerwij podczas (1) ())

Timery w STM32, podobnie jak w zasadzie wszystkie urządzenia peryferyjne, są bardzo wyrafinowane. Mnogość różnych funkcji, które mogą wykonywać zegary, może nawet przyprawić o zawrót głowy. Chociaż wydaje się, że jest do tego timerem, żeby policzyć. Ale w rzeczywistości wszystko jest znacznie fajniejsze)

Nie tylko timery mają tak szerokie możliwości, ale każdy kontroler ma ich kilka więcej. I nawet nie dwa lub trzy, ale więcej! Ogólnie rzecz biorąc, możesz bez końca to chwalić. Zastanówmy się, co działa i jak. Tak więc mikrokontroler STM32F103CB ma:

  • 3 timery ogólny cel (TIM2, TIM3, TIM4)
  • 1 bardziej zaawansowany timer z rozszerzonymi możliwościami (TIM1)
  • 2 WDT (WatchDog Timer)
  • 1 SysTick Timer

W rzeczywistości timery ogólnego przeznaczenia i timer TIM1 nie różnią się zbytnio od siebie, więc ograniczymy się do rozważenia pojedynczego timera. Nawiasem mówiąc, zdecydowałem się na TIM4. Bez konkretnego powodu chciałem po prostu \u003d). Timery posiadają 4 niezależne kanały, które mogą służyć do:

  • Przechwytywanie sygnału
  • Porównania
  • Generacja PWM
  • Generowanie pojedynczego impulsu
  • Przelewowy
  • Przechwytywanie sygnału
  • Porównanie
  • Zdarzenie wyzwalające

Gdy wystąpi którekolwiek z tych zdarzeń, timery mogą generować żądanie do DMA (DMA to bezpośredni dostęp do pamięci, wkrótce się tym zajmiemy \u003d)). Teraz trochę więcej szczegółów na temat każdego z trybów timera.

Tryb przechwytywania sygnału. Pomiar okresu powtarzania impulsów jest bardzo wygodny, gdy timer działa w tym trybie. Przekonaj się sam: przychodzi impuls, licznik czasu umieszcza aktualną wartość licznika w rejestrze TIM_CCR. Szybko bierzemy tę wartość i ukrywamy ją w jakiejś zmiennej. Siedzimy, czekając na kolejny impuls. Ups! Nadszedł impuls, licznik czasu ponownie ustawia wartość licznika TIM_CCR, a od tej wartości musimy tylko odjąć tę, którą wcześniej zapisaliśmy. Jest to prawdopodobnie najprostsze użycie tego trybu timera, ale bardzo przydatne. Możliwe jest uchwycenie zarówno przedniej krawędzi impulsu, jak i zbocza spływu, więc możliwości są dość duże.

Tryb porównania. Tutaj po prostu podłączamy jakiś kanał timera do odpowiedniego wyjścia i gdy tylko timer odliczy do określonej wartości (jest w TIM_CCR) stan wyjścia będzie się zmieniał w zależności od ustawienia trybu (ustawiony na jeden lub zero, albo zmieni się na przeciwny).

Tryb generowania PWM. No cóż, wszystko jest ukryte w nazwie) W tym trybie timer generuje PWM! Chyba nawet teraz nie ma sensu pisać tutaj czegoś. Wkrótce będzie przykład tylko na PWM i tam będziemy kopać głębiej.

Tryb Dead-Time. Istotą tego trybu jest to, że pojawia się pewne opóźnienie między sygnałami na głównym i uzupełniającym wyjściu timera. W Internecie jest sporo informacji o tym, gdzie można i należy to zastosować.

Cóż, w zasadzie bardzo krótko o głównych trybach timera. Jeśli masz pytania dotyczące innych trybów, bardziej szczegółowych, napisz w komentarzach 😉

Konieczne byłoby powolne napisanie programu do pracy z licznikami czasu. Ale najpierw zobaczmy, co znajduje się w standardowej bibliotece urządzeń peryferyjnych. Zatem pliki są odpowiedzialne za liczniki czasu - stm32f10x_tim.h i stm32f10x_tim.c... Otwieramy pierwszy i widzimy, że struktura pliku powtarza strukturę pliku do pracy z GPIO, którą rozważaliśmy w poprzednim artykule. Struktury i pola struktur, które są potrzebne do skonfigurowania timerów, są opisane tutaj. Prawda to nie jedna, ale kilka struktur (tryby, a więc ustawienia, wtedy timery mają więcej niż porty I / O). Wszystkie pola struktury są opatrzone adnotacjami, więc nie powinno być tutaj żadnych problemów. Na przykład:

uint16_t TIM_OCMode; // Określa tryb TIM.

Tutaj ustawimy tryb pracy timera. A oto kolejny:

uint16_t TIM_Channel; // Określa kanał TIM.

Tutaj wybieramy kanał timera, nic nieoczekiwanego) Ogólnie wszystko jest dość przejrzyste, jeśli o coś zapytasz \u003d) Pierwszy plik jest czysty. I w pliku stm32f10x_tim.c - gotowe funkcje do pracy z timerem. Poza tym wszystko jest ogólnie jasne. Korzystaliśmy już z biblioteki do pracy z GPIO, teraz pracujemy z licznikami czasu i widać, że dla różnych urządzeń peryferyjnych wszystko jest bardzo podobne. Stwórzmy więc projekt i napiszmy program.

Dlatego składamy nowy projekt, dodajemy wszystkie niezbędne pliki:

Piszemy kod:

Należy zaznaczyć, że w polu TIM_Prescaler należy wpisać wartość o jeden mniejszą od tej, którą chcemy uzyskać.

/ ***************************************** ************* / #include "stm32f10x.h" #include "stm32f10x_rcc.h" #include "stm32f10x_gpio.h" #include "stm32f10x_tim.h" // Za pomocą tego preskalera dostaję jeden impuls timera na 10 μs # zdefiniować TIMER_PRESCALER 720 /*******************************************************************/ // Zmienna do przechowywania poprzedniego stanu pinu PB0 uint16_t previousState; Port GPIO_InitTypeDef; Zegar TIM_TimeBaseInitTypeDef; /*******************************************************************/ void initAll () ( // Włącz taktowanie portu GPIOB i timera TIM4 // Timer 4 wisi na magistrali APB1 RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM4, ENABLE); // Tutaj konfigurujemy wyjście portu PB0 // Przeczytaj więcej na ten temat w artykule o GPIO GPIO_StructInit (& port); port.GPIO_Mode \u003d GPIO_Mode_Out_PP; port.GPIO_Pin \u003d GPIO_Pin_0; port.GPIO_Speed \u200b\u200b\u003d GPIO_Speed_2MHz; GPIO_Init (GPIOB, & port); // A oto ustawienie timera // Wypełnij pola struktury wartościami domyślnymi TIM_TimeBaseStructInit (& timer); // Ustaw preskaler timer.TIM_Prescaler \u003d TIMER_PRESCALER - 1; // Oto wartość, odliczana do której licznik czasu wygeneruje przerwanie // Nawiasem mówiąc, zmienimy tę wartość w samym przerwaniu timer.TIM_Period \u003d 50; // Zainicjuj TIM4 naszymi wartościami TIM_TimeBaseInit (TIM4, & timer); ) /*******************************************************************/ int main () (__enable_irq (); initAll (); // Ustaw licznik czasu, aby wygenerować przerwanie aktualizacji (przepełnienia) TIM_ITConfig (TIM4, TIM_IT_Update, ENABLE); // Uruchom licznik czasu TIM_Cmd (TIM4, ENABLE); // Włącz odpowiednie przerwanie NVIC_EnableIRQ (TIM4_IRQn); podczas gdy (1) ( // Nieskończenie głupi) Wszystko użyteczna praca - w przerwie __NOP (); )) /*******************************************************************/ // Jeśli wynik wynosił 0 .. timer.TIM_Period \u003d 50; TIM_TimeBaseInit (TIM4, & timer); // Wyczyść bit przerwania TIM_ClearITPendingBit (TIM4, TIM_IT_Update); ) else ( // Ustaw wynik na zero timer.TIM_Period \u003d 250; TIM_TimeBaseInit (TIM4, & timer); TIM_ClearITPendingBit (TIM4, TIM_IT_Update); ))

W tym programie przyjrzymy się, co było na wyjściu przed wygenerowaniem przerwania - jeśli zero, ustaw je na 0,5 ms. Jeśli był jeden, ustaw zero na 2,5 ms. Skompiluj i uruchom debugowanie \u003d)

Mała, ale bardzo ważna dygresja ... Nasz przykład oczywiście się sprawdzi i do testu spisze się całkiem nieźle, niemniej jednak w programach "bojowych" trzeba monitorować optymalność kodu zarówno pod względem jego objętości, jak i wydajności i zużycia pamięć. W takim przypadku nie ma sensu używać struktury licznika czasu, a także wywoływać funkcję TIM_TimeBaseInit () za każdym razem, gdy zmienia się okres. Bardziej poprawne jest zmienianie tylko jednej wartości w jednym rejestrze, a mianowicie w rejestrze TIMx-\u003e ARR (gdzie x to numer zegara). W ten przykład kod jest przekształcany w następujący sposób:

/*******************************************************************/ void TIM4_IRQHandler () ( // Jeśli wynik wynosił 0 .. if (previousState \u003d\u003d 0) ( // Ustaw wynik na jeden previousState \u003d 1; GPIO_SetBits (GPIOB, GPIO_Pin_0); // Okres 50 taktów licznika czasu, czyli 0,5 ms TIM4-\u003e ARR \u003d 50; ) else ( // Ustaw wynik na zero previousState \u003d 0; GPIO_ResetBits (GPIOB, GPIO_Pin_0); // A okres będzie teraz wynosił 250 taktów - 2,5 ms TIM4-\u003e ARR \u003d 250; ) TIM_ClearITPendingBit (TIM4, TIM_IT_Update); ) / **************************** Koniec pliku ****************** ********** /

A więc kontynuujemy, po drodze mamy kolejną prowizję) Mianowicie błąd:

.. \\ .. \\ .. \\ SPL \\ src \\ stm32f10x_tim.c (2870): błąd: # 20: identyfikator „TIM_CCER_CC4NP” jest niezdefiniowany

Nie tak przerażające, jak mogłoby się wydawać, przejdź do pliku stm32f10x.h, znajdź linie

Teraz wszystko idzie, możesz debugować. Włączamy analizator logiczny. W wiersz poleceń piszemy: la portb & 0x01i obserwuj wynik:

Dostali to, czego chcieli) Innymi słowy, wszystko działa poprawnie. W następnym artykule zagłębimy się w tryb generowania PWM, pozostańmy w kontakcie :)

Nie przegap dobrego artykułu na temat timerów w ogóle -.

Właściwie więc przejdźmy od razu do programowania. Weźmy jeden z podstawowych timerów mikrokontrolera STM32F3, ustalmy jego minimalne ustawienie i spróbujmy generować przerwania w regularnych odstępach czasu. Tak proste, jak to tylko możliwe 😉

Więc od Standardowa biblioteka urządzeń peryferyjnych będziemy potrzebować kilku plików, które implementują interakcję z rejestrami timera:

#include "stm32f30x_gpio.h" #include "stm32f30x_rcc.h" #include "stm32f30x_tim.h" #include "stm32f30x.h" /*******************************************************************/ Zegar TIM_TimeBaseInitTypeDef; /*******************************************************************/

Minimalna inicjalizacja timera jest następująca. Nawiasem mówiąc, w tym samym czasie skonfigurujemy jedną z nóg kontrolera do pracy w trybie wyjściowym. Wystarczy migać dioda 😉

/*******************************************************************/ void initAll () ( // Zegar - gdzie bez niego RCC_AHBPeriphClockCmd (RCC_AHBPeriph_GPIOE, ENABLE); RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM2, ENABLE); // Na tym pinie mamy niebieską diodę LED (STM32F3Discovery) gpio.GPIO_Mode \u003d GPIO_Mode_OUT; gpio.GPIO_Pin \u003d GPIO_Pin_8; gpio.GPIO_OType \u003d GPIO_OType_PP; gpio.GPIO_Speed \u200b\u200b\u003d GPIO_Speed_50MHz; GPIO_Init (GPIOE, & gpio); // A oto długo oczekiwane ustawienie timera TIM2 TIM_TimeBaseStructInit (& timer); timer.TIM_Prescaler \u003d 7200; timer.TIM_Period \u003d 20000; TIM_TimeBaseInit (TIM2, & timer); /*******************************************************************/

Tutaj warto zwrócić uwagę na dwie niezrozumiałe liczby, które pochodzą z - 7200 i 20000 ... Teraz zastanówmy się, co to jest 😉 Zegar jest taktowany częstotliwością 72 MHz... Aby podzielić tę częstotliwość, potrzebny jest prescaler, czyli prescaler) 72 MHz / 7200 \u003d 10 KHz... Odpowiada więc jedno „tiknięcie” timera (1/10000) sekundco jest równe 100 mikrosekundy. Okres licznika czasu to wartość, do której program przejdzie do procedury obsługi przerwania przepełnienia zegara. W naszym przypadku licznik osiągnie 20000 co odpowiada (100 * 20000) μs lub 2 sekundy. Oznacza to, że dioda LED (którą włączamy i wyłączamy w obsłudze przerwań) będzie migać z kropką 4 sekundy (2 sekundy włączone, 2 sekundy wyłączone \u003d)). Teraz wszystko jest jasne, kontynuujemy ...

W działaniu główny () nazywamy funkcję inicjalizacyjną, a także włączamy przerwania i licznik czasu. W pętli podczas gdy (1) jeszcze mniej kodu - jest po prostu pusty 😉

/*******************************************************************/ int main () (__enable_irq (); initAll (); TIM_ITConfig (TIM2, TIM_IT_Update, ENABLE); TIM_Cmd (TIM2, ENABLE); NVIC_EnableIRQ (TIM2_IRQn); while (1) ()) /*******************************************************************/

To wszystko, pozostaje napisać kilka wierszy dla obsługi przerwań i zadanie jest wykonane:

/*******************************************************************/ void TIM2_IRQHandler () (TIM_ClearITPendingBit (TIM2, TIM_IT_Update); if (GPIO_ReadInputDataBit (GPIOE, GPIO_Pin_8) \u003d\u003d 1) (GPIO_ResetBits (GPIOE, GPIO_Pin_8); (else (GPIO_Pin_8));) /*******************************************************************/

Po wczytaniu programu do sterownika obserwujemy migającą niebieską diodę LED, dzięki czemu program działa poprawnie! W zasadzie to wszystko na dziś, to jest krótki artykuł)

Timery to takie peryferia sterownika STM32, które pozwalają nam bardzo dokładnie zliczać przedziały czasowe. Jest to prawdopodobnie jedna z najważniejszych i najczęściej używanych funkcji, ale są też inne. Należy zacząć od tego, że w sterownikach STM32 są timery o różnym stopniu wychłodzenia. Najprostsze są Podstawowy timery ... Są dobre, ponieważ są bardzo łatwe w konfiguracji i zarządzaniu przy minimalnej liczbie rejestrów. Wszystko, co mogą zrobić, to odliczać przedziały czasu i generować, gdy licznik osiągnie określoną wartość. Następna grupa ( zegary ogólnego przeznaczenia ) jest dużo fajniejszy niż pierwszy, wiedzą jak generować PWM, potrafią liczyć impulsy docierające do określonych nóg, można podłączyć enkoder itp. A najfajniejszy timer to zaawansowany zegar sterujący Myślę, że nie będę go używać przez bardzo długi czas, ponieważ na razie nie muszę sterować trójfazowym silnikiem elektrycznym. Aby zacząć poznawać timery od czegoś prostszego, zdecydowałem się na podstawowe timery. Zadanie, które sobie postawiłem: spraw, aby licznik czasu generował przerwania co sekundę.

Przede wszystkim zaznaczam, że podstawowe timery (TIM6 i TIM7) są podłączone do magistrali APB1, więc jeśli częstotliwość impulsów zegara na niej się zmieni, to timery zaczną tykać szybciej lub wolniej. Jeśli nic nie zmienisz w ustawieniach taktowania i pozostawisz je domyślne, to częstotliwość APB1 wynosi 24 MHz, pod warunkiem, że zewnętrzny kryształ jest podłączony do częstotliwości 8 MHz. Generalnie układ zegara STM32 jest bardzo zawiły i spróbuję normalnie napisać o nim osobny post. Na razie użyjmy po prostu tych ustawień taktowania, które są ustawione przez kod automatycznie generowany przez CooCox. Warto zacząć od najważniejszego rejestru - TIMx_CNT (dalej x to numer podstawowego zegara 6 lub 7). Jest to 16-bitowy rejestr zliczający, który zajmuje się bezpośrednio zliczaniem czasu. Za każdym razem poza autobusem APB1 przychodzi impuls zegarowy, zawartość tego rejestru zwiększa się o jeden. Kiedy rejestr się przepełnia, wszystko zaczyna się od zera. Przy naszej domyślnej częstotliwości magistrali APB1 zegar tyka 24 miliony razy w ciągu jednej sekundy! Jest to bardzo szybkie, dlatego timer ma preskaler, którym możemy sterować za pomocą rejestru TIMx_PSC... Wpisując do niego wartość 24000-1, wymusimy rejestr liczący TIMx_CNT zwiększaj swoją wartość co milisekundę (Frequency APB1 podziel przez liczbę w rejestrze preskalera i uzyskaj, ile razy licznik zwiększa się na sekundę). Jednostkę należy odjąć, ponieważ jeśli rejestr jest równy zero, oznacza to, że uwzględniony jest dzielnik przez jeden. Teraz, gdy rejestr liczący osiągnie 1000, możemy z całą pewnością powiedzieć, że minęła dokładnie jedna sekunda! I po co teraz przesłuchać rejestr liczenia i czekać, aż pojawi się tam 1000? To nie jest nasza metoda, bo możemy jej użyć! Problem w tym, że mamy tylko jedną przerwę, która pojawia się, gdy licznik jest resetowany. Aby licznik resetował się przed terminem, a nie po osiągnięciu 0xFFFF, używany jest rejestr TIMx_ARR... Wpisujemy w nim numer, do którego ma się liczyć rejestr TIMx_CNT przed wyzerowaniem. Jeśli chcemy, aby przerwanie występowało raz na sekundę, to musimy tam wpisać 1000. Jeśli chodzi o czas, to wszystko, ale sam licznik czasu nie zacznie tykać. Musi być włączony poprzez ustawienie bitu CEN w rejestrze TIMx_CR1... Ten bit pozwala odpowiednio rozpocząć liczenie, jeśli go wyczyścisz, liczenie zostanie zatrzymane (twój K.O.). W rejestrze są inne bity, ale nie są one dla nas szczególnie interesujące. Ale interesuje nas jeszcze jeden kawałek, ale już w rejestrze TIMx_DIER... To się nazywa UIE,ustawiając go, włączamy timer do generowania przerwań, gdy rejestr licznikowy jest resetowany. To wszystko, nie jest to nawet bardziej skomplikowane niż w niektórych AVR. Małe podsumowanie: Aby korzystać z podstawowego timera, potrzebujesz:

  1. Ustawić preskaler tak, aby timer nie tykał szybko ( TIMx_PSC)
  2. Ustaw limit, do którego licznik powinien się zakończyć przed zresetowaniem ( TIMx_ARR)
  3. Włącz odliczanie po bitach CEN w rejestrze TIMx_CR1
  4. Włącz przerwanie przepełnienia bitu UIEw rejestrze TIMx_DIER

Oto prosta sekwencja. A teraz czas wyjść i spróbować mrugnąć tymi niefortunnymi diodami po raz milionowy, ale z pomocą timera 🙂

#include "stm32f10x.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" int main () (GPIO_InitTypeDef PORT; // Włącz port C i licznik czasu 6 RCC_APB2PeriphClockCmd (RipCC_APBip2PerCC) ; // Ustaw nogi z diodami LED dla wyjścia PORT.GPIO_Pin \u003d (GPIO_Pin_9 | GPIO_Pin_8); PORT.GPIO_Mode \u003d GPIO_Mode_Out_PP; PORT.GPIO_Speed \u200b\u200b\u003d GPIO_Speed_2MHz; GPIO_Init\u003e GPIOC, & P000; // dzielnik, że licznik tykał 1000 razy na sekundę TIM6-\u003e ARR \u003d 1000; // Aby przerwanie następowało raz na sekundę TIM6-\u003e DIER | \u003d TIM_DIER_UIE; // włącz przerwanie timera TIM6-\u003e CR1 | \u003d TIM_CR1_CEN; // Zacznij liczyć! NVIC_EnableIRQ (TIM6_DAC_IRQn); // Włącz przerwanie TIM6_DAC_IRQn while (1) (// Program nic nie robi w pustej pętli)) // Program obsługi przerwań TIM6_DAC void TIM6_DAC_IRQHandler (void) (TIM6-\u003e SR & \u003d ~ TIM_SR_UIF; UIF GPIOC-\u003e ODR ^ \u003d (GPIO_Pin_9 | GPIO_Pin_8); // Odwróć stan Światła ledowe)

Warto dodać małą uwagę do obsługi przerwań. Faktem jest, że jest używany przez dwie jednostki peryferyjne naraz: zegar 6 i DAC. Oznacza to, że jeśli napiszesz program, który zezwala na przerwania z obu tych urządzeń peryferyjnych, to w treści procedury obsługi musisz sprawdzić, które z nich spowodowały przerwanie. W naszym przypadku tego nie zrobiłem, ponieważ nie mogą wystąpić żadne przerwania DAC. Nie jest skonfigurowany, a przerwania są domyślnie wyłączone. Następnym razem przyjrzymy się zegarom ogólnego przeznaczenia i ich praktycznemu zastosowaniu.

W STM32 jest wiele bardzo wygodnych i elastycznych timerów. Nawet najmłodszy mikrokontroler (STM32F030F4P6) ma 4 takie timery.

8. Skonfigurujmy projekt - dodajmy potrzebne pliki

Aby użyć licznika czasu, musimy dołączyć plik biblioteki urządzeń peryferyjnych stm32f10x_tim.c. W ten sam sposób kliknij prawym przyciskiem myszy w obszarze roboczym (lewe okno) na grupie StdPeriphLib, Dodaj -\u003e Dodaj pliki, plik LibrariesSTM32F10x_StdPeriph_Driversrcstm32f10x_tim.c.

Musisz także włączyć używanie nagłówka dla tego pliku. Otwórz plik stm32f10x_conf.h (kliknij prawym przyciskiem myszy nazwę tego pliku w kodzie, „Otwórz stm32f10x_conf.h”. Usuń komentarz z wiersza #include „stm32f10x_tim.h”.

9. Dodaj licznik czasu

Opóźnienie pustej pętli jest bluźnierstwem, szczególnie w przypadku tak potężnego kryształu jak STM32, z wieloma zegarami. Dlatego zrobimy to opóźnienie za pomocą timera.

W STM32 są różne zegary z różnymi zestawami właściwości. Najprostsze to podstawowe timery, trudniejsze to timery ogólnego przeznaczenia, a najtrudniejsze to timery zaawansowane. Proste liczniki czasu są ograniczone tylko do liczenia uderzeń. W bardziej złożonych zegarach pojawia się PWM. Na przykład najbardziej wyrafinowane timery mogą generować 3-fazowe PWM z wyjściami bezpośrednimi i odwrotnymi oraz czasem martwym. Wystarczy nam prosty zegar numer 6.

Trochę teorii

Wszystko, czego potrzebujemy od timera, to policzyć do określonej wartości i wygenerować przerwanie (tak, nauczymy się również, jak używać przerwań). Timer TIM6 taktowany od magistrala systemowa, ale nie bezpośrednio, ale przez preskaler - prosty programowalny licznik (pomyśl tylko, że w ZSRR produkowano specjalne liczniki mikroukładów, a programowalne miały szczególny deficyt - a teraz mówię o takim liczniku tylko przelotnie). Preskaler może być ustawiony na dowolną wartość od 1 (tzn. Pełna częstotliwość magistrali 24 MHz przejdzie do miernika) do 65536 (tj. 366 Hz).

Z kolei sygnał zegara zwiększa wewnętrzny licznik timera, zaczynając od zera. Gdy tylko wartość licznika osiągnie wartość ARR, licznik przepełnia się i następuje odpowiednie zdarzenie. Po wystąpieniu tego zdarzenia timer ponownie ładuje 0 do licznika i rozpoczyna odliczanie od zera. Jednocześnie może wywołać przerwanie (jeśli zostało skonfigurowane).

Właściwie proces jest nieco bardziej skomplikowany: istnieją dwa rejestry ARR - zewnętrzny i wewnętrzny. Podczas zliczania aktualna wartość jest dokładnie porównywana z rejestrem wewnętrznym i tylko w przypadku przepełnienia wewnętrzny jest aktualizowany z zewnętrznego. Dzięki temu możesz bezpiecznie zmienić ARR podczas działania timera - w dowolnym momencie.

Kod

Kod będzie bardzo podobny do poprzedniego, ponieważ inicjalizacja wszystkich urządzeń peryferyjnych jest taka sama, z jedynym wyjątkiem, że zegar TIM6 zawiesza się na magistrali APB1. Dlatego włączenie timera: RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM6, ENABLE);

Teraz tworzymy strukturę typu TIM_TimeBaseInitTypeDef, inicjalizujemy ją (TIM_TimeBaseStructInit), ustawiamy, przekazujemy do funkcji inicjalizacji timera (TIM_TimeBaseInit) i na końcu włączamy timer (TIM_Cmd).

TIM_TimeBaseInitTypeDef TIM_InitStructure; // Skonfiguruj strukturę TIM_TimeBaseStructInit (& TIM_InitStructure); // Zainicjuj strukturę TIM_InitStructure.TIM_Prescaler \u003d 24000; // TIM_InitStructure.TIM_Period \u003d 1000; // Okres licznika czasu TIM_TimeBaseInit (TIM6, & TIM_InitStructure); // Funkcja ustawiania timera TIM_Cmd (TIM6, ENABLE); // Włącz minutnik

Jakie są magiczne liczby? Jak pamiętamy, magistrala ma częstotliwość taktowania 24 MHz (z naszymi ustawieniami projektu). Ustawiając preskaler timera na 24000, dzielimy tę częstotliwość przez 24000 i otrzymujemy 1kHz. To właśnie ta częstotliwość trafi na wejście licznika timera.

Wartość licznika to 1000. Oznacza to, że licznik przepełni się w 1000 cykli zegara. dokładnie w 1 sekundę.

Potem naprawdę mamy działający zegar. Ale to nie wszystko.

10. Zajmijmy się przerwaniami

Dobra, przerywa. Dla mnie kiedyś (w czasach PIC) były to ciemny las i starałem się ich w ogóle nie używać - i naprawdę nie wiedziałem jak. Jednak zawierają siłę, której na ogół nie warto ignorować. To prawda, że \u200b\u200bprzerwania w STM32 są jeszcze bardziej złożone, zwłaszcza mechanizm ich przemieszczania; ale o tym później.

Jak zauważyliśmy wcześniej, licznik czasu generuje przerwanie w momencie przepełnienia licznika - jeśli przetwarzanie przerwań tego urządzenia w ogóle jest włączone, to konkretne przerwanie jest włączone, a poprzednie jest kasowane. Analizując to zdanie, rozumiemy, czego potrzebujemy:

  1. Włącz ogólnie przerwania timera TIM6;
  2. Włącz przerwanie timera TIM6 dla przepełnienia licznika;
  3. Napisz procedurę obsługi przerwań;
  4. Po obsłużeniu przerwania, zresetuj je.

Włączanie przerwań

Szczerze mówiąc, nie ma tu w ogóle nic skomplikowanego. Przede wszystkim włącz przerwania TIM6: NVIC_EnableIRQ (TIM6_DAC_IRQn); Skąd taka nazwa? Bo w rdzeniu STM32 przerwania z TIM6 i z DAC-a mają tę samą liczbę. Nie wiem, dlaczego tak się dzieje - ekonomia, brak liczb, czy po prostu coś w stylu spuścizny - w każdym razie nie będzie to sprawiało żadnych problemów, ponieważ ten projekt nie używa DAC-a. Nawet jeśli nasz projekt korzysta z przetwornika cyfrowo-analogowego, możemy, wprowadzając przerwanie, dowiedzieć się, kto dokładnie je spowodował. Prawie wszystkie inne zegary mają pojedyncze przerwanie.

Konfigurowanie zdarzenia źródłowego przerwania: TIM_ITConfig (TIM6, TIM_DIER_UIE, ENABLE); - włącz przerwanie timera TIM6 na zdarzeniu TIM_DIER_UIE, tj. Zdarzenie aktualizacji wartości ARR. Jak pamiętamy z obrazka, dzieje się to jednocześnie z przepełnieniem licznika - więc jest to dokładnie to zdarzenie, którego potrzebujemy.

W tej chwili kod zadań timera wygląda następująco:

RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM6, ENABLE); TIM_TimeBaseInitTypeDef TIM_InitStructure; TIM_TimeBaseStructInit (& TIM_InitStructure); TIM_InitStructure.TIM_Prescaler \u003d 24000; TIM_InitStructure.TIM_Period \u003d 1000; TIM_TimeBaseInit (TIM6, & TIM_InitStructure); TIM_Cmd (TIM6, ENABLE); NVIC_EnableIRQ (TIM6_DAC_IRQn); TIM_ITConfig (TIM6, TIM_DIER_UIE, ENABLE);

Obsługa przerwania

Teraz nie możesz rozpocząć projektu - pierwsze przerwanie od timera nie znajdzie swojej obsługi, a kontroler zawiesi się (a dokładniej dostanie się do handlera HARD_FAULT, który jest w zasadzie taki sam). Musisz to napisać.

Trochę teorii

Musi mieć bardzo konkretną nazwę, void TIM6_DAC_IRQHandler (void). Ta nazwa, tzw. Wektor przerwań, jest opisana w pliku startowym (w naszym projekcie jest to startup_stm32f10x_md_vl.s - sam się przekonasz, wiersz 126). W rzeczywistości wektor jest adresem obsługi przerwań, a gdy wystąpi przerwanie, rdzeń ARM wspina się do obszaru początkowego (do którego jest tłumaczony plik startowy - to znaczy jego lokalizacja jest ustawiona całkowicie twardo, na samym początku pamięci flash), szuka tam wektora i idzie we właściwym miejscu w kodzie.

Sprawdzenie wydarzenia

Pierwszą rzeczą, którą musimy zrobić, wchodząc do takiego handlera, jest sprawdzenie, które zdarzenie spowodowało przerwanie. Teraz mamy tylko jedno wydarzenie, ale w prawdziwym projekcie może być kilka wydarzeń na jednym zegarze. Dlatego sprawdzamy zdarzenie i wykonujemy odpowiedni kod.

W naszym programie sprawdzenie to będzie wyglądać następująco: if (TIM_GetITStatus (TIM6, TIM_IT_Update)! \u003d RESET) - wszystko jest jasne, funkcja TIM_GetITStatus sprawdza licznik czasu dla określonego zdarzenia i zwraca 0 lub 1.

Usuwanie flagi UIF

Drugim krokiem jest wyczyszczenie flagi przerwania. Wróć do ilustracji: najnowszy wykres UIF to flaga przerwania. Jeśli nie zostanie wyczyszczone, następne przerwanie nie będzie mogło zostać wywołane, a kontroler ponownie wpadnie w HARD_FAULT (ale co to jest!).

Przerwij działanie

Po prostu przełączymy stan diody LED, tak jak w pierwszym programie. Różnica polega na tym, że teraz nasz program to utrudnia! W rzeczywistości pisanie w ten sposób jest o wiele bardziej poprawne.

Jeśli (stan) GPIO_WriteBit (GPIOC, GPIO_Pin_8, Bit_SET); jeszcze GPIO_WriteBit (GPIOC, GPIO_Pin_8, Bit_RESET); state \u003d 1 - stan;

Używamy zmiennej globalnej int state \u003d 0;

11. Cały kod projektu z zegarem

#include "stm32f10x_conf.h" int state \u003d 0; void TIM6_DAC_IRQHandler (void) (if (TIM_GetITStatus (TIM6, TIM_IT_Update)! \u003d RESET) (TIM_ClearITPendingBit (TIM6, TIM_IT_Update); if (stan) GPIO_WriteBit (GPPIOC_8, stan GPIOC, GPIOC_8) \u003d 1 - stan;)) nieważności główny () (RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOC, umożliwiają) GPIO_InitTypeDef GPIO_InitStructure; GPIO_StructInit (i GPIO_InitStructure) GPIO_InitStructure.GPIO_Speed \u200b\u200b\u003d GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Mode \u003d GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin \u003d GPIO_Pin_8; GPIO_Init (GPIOC, i GPIO_InitStructure ) GPIO_WriteBit (GPIOC, GPIO_Pin_8, Bit_SET) RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM6, umożliwiają) TIM_TimeBaseInitTypeDef TIM_InitStructure; TIM_TimeBaseStructInit (& TIM_InitStructure) TIM_InitStructure.TIM_Prescaler \u003d 24000; TIM_InitStructure.TIM_Period \u003d 1000; TIM_TimeBaseInit (TIM6, i TIM_InitStructure) TIM_Cmd (TIM6, umożliwiają ); NVIC_EnableIRQ (TIM6_DAC_IRQn); TIM_ITConfig (TIM6, TIM_DIER_UIE, ENABLE); while (1) ())

Archiwizuj z projektem timera.

Cóż, przy okazji, timer może sam przełączyć stopę, bez przerw i ręcznego przetwarzania. To będzie nasz trzeci projekt.

Cały cykl:

1. Porty we / wy

2. Zegar i przerwania

3. Wyjścia timera

4. Przerwania zewnętrzne i NVIC

5. Zainstaluj FreeRTOS

Wyświetlenia postów: 235

DZWON

Są tacy, którzy czytają tę wiadomość przed tobą.
Zapisz się, aby otrzymywać najnowsze artykuły.
E-mail
Imię
Nazwisko
Jak chcesz przeczytać The Bell
Bez spamu