Skocz do zawartości
Komentator

Kurs STM32 F1 HAL - #7 - liczniki (timery) w praktyce, PWM

Pomocna odpowiedź

html_mig_img
Przed nami kolejna część kursu programowania STM32 z użyciem HAL. Tym razem poznamy podstawy sprzętowych liczników (timerów).Po krótkim wstępie teoretycznym przejdziemy do ćwiczeń praktycznych, podczas których przyda nam się umiejętność generowania sygnału PWM.

UWAGA, to tylko wstęp! Dalsza część artykułu dostępna jest na blogu.

Przeczytaj całość »

Poniżej znajdują się komentarze powiązane z tym wpisem.

Udostępnij ten post


Link to post
Share on other sites

"Okazuje się, że timer TIM4 udostępnia wyjścia PWM na pinach PB6, PB7, PB8 oraz PB8,"

Wkradł się chochlik ma być "PB8 oraz PB9" 🙂.

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

dejmieno, gratuluję czujności - poprawione 🙂 Dzięki!

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

Zamiast pisać:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_RESET)
 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
 else
 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
}

Można napisać:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
}

Zrobiłem też mały eksperyment i postanowiłem sprawdzić jaka jest największa częstotliwość wywołania przerwania TIM2_IRQHandler. Okazuje się że nie za duża (też zależy do jakich zastosowań). Przy taktowaniu 64MHz udało mi się uzyskać stabilne 90kHz (czyli przerwanie wywoływane z częstotliwością 180kHz bo na pełen okres w przebiegu prostokątnym potrzeba dwóch wywołań)

Dalsze zmniejszanie wartości Init.Prescaller lub Init.Period powodowało że układ oscylował z częstotliwością około 98KHz i wartość ta nie korespondowała z teoretyczną wartością wyliczoną dla zadanej konfiguracji Prescaller/Period.

W przypadku sprzętowego PWM można podkręcić timer nawet do 4MHz (dla 64MHz zegara). Nawet przy tak dużej częstotliwości otrzymamy w miarę regularny sygnał prostokątny. Na oscyloskopie jednak widać już dość znaczący wpływ "signal overshoot" na poziomie 0.7V.

Do większości zastosowań wystarczy jednak PWM o częstotliwości rzędu kHz.

Pozdrawiam

PWM_4MHz.png

Edytowano przez MR1979
  • Lubię! 2

Udostępnij ten post


Link to post
Share on other sites

Ok, to jak rozwiązać zadanie 7.4? Próbowałem przypisać do oc.Pulse wartość zmiennej 32 bitowej (np. pulseval1) i sterować jej wartością w pętli od 0 do 999, ale to nie działa. Umieściłem zmienną pulseval1 wewnątrz while(1), ale wartość oc.Pulse się nie aktualizuje. Czego mi brakuje? Bo przypisanie wartości (ale stałej) do zmiennej pulseval1 poza while(1) zmienia wartość oc.Pulse. Poniżej fragment kodu, który nie działa.

 TIM_OC_InitTypeDef oc;
  oc.OCMode = TIM_OCMODE_PWM1;
  oc.Pulse = pulseval1;
  oc.OCPolarity = TIM_OCPOLARITY_HIGH;
  oc.OCNPolarity = TIM_OCNPOLARITY_LOW;
  oc.OCFastMode = TIM_OCFAST_ENABLE;
  oc.OCIdleState = TIM_OCIDLESTATE_SET;
  oc.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_1);

  oc.Pulse = pulseval2;
  HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_2);

  oc.Pulse = pulseval3;
  HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_3);

  oc.Pulse = pulseval4;
  HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_4);

  HAL_TIM_PWM_Start(&tim4, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&tim4, TIM_CHANNEL_2);
  HAL_TIM_PWM_Start(&tim4, TIM_CHANNEL_3);
  HAL_TIM_PWM_Start(&tim4, TIM_CHANNEL_4);
 while (1) {

	  pulseval1 = 500;
	
 }
}

Czy da się w ogóle dynamicznie zmienić wartość oc.Pulse z pętli while(1) korzystając ze sprzętowego PWM?

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

@aldenham Wydaje mi się, że powinieneś zacząć od nadrobienia pewnych zaległości z samego języka C. Operator = przypisuje zmiennej po lewej stronie wartość, która jest wynikiem wyrażenia po prawej, czyli:

oc.Pulse = pulseval1;

Taka instrukcja oznacza odczytanie wartości zmiennej pulseval1 i wstawienie tej wartości do oc.Pulse. Jednak od tego momentu pulseval1 jest zupełnie niezależna od oc.Pulse. Można do niej przypisywać dowolne wartości, odczytywać, nawet sama zmienna może przestać istnieć - a wartość oc.Pulse pozostanie bez zmian.

Inna sprawa, że nawet zmiana wartości oc.Pulse nic by nie zmieniła - to jest tylko kolejna zmienna, dopiero jej przekazanie do funkcji HAL powoduje zmianę wypełnienia PWM.

Ale jak napisałem, proponowałbym zacząć od powtórki samego języka C, opanowanie działania zmiennych, przypisań itd. A później wrócić do kursu STM32.

Edytowano przez Elvis
  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

@Elvis

Myślę, że do tak prostych podstaw nie muszę się cofać 🙂 Rozumiem, co oznacza operator przypisania i wiem, czym różni się np. od operatora ==. Poza tym jakoś dotarłem do tego momentu w kursie rozwiązując wszystkie wcześniejsze zadania, czyli coś tam musiałem umieć zaprogramować.

Dotarłem trochę dalej w moich przemyśleniach i wykoncypowałem, żeby rzeczywiście przypisać wartość do oc.Pulse w pętli while(1) (nie wiem, dlaczego wcześniej tak nie zrobiłem). Kod wygląda teraz tak i działa częściowo:

  tim4.Instance = TIM4;
  tim4.Init.Period = 1000 - 1;
  tim4.Init.Prescaler = 8 - 1;
  tim4.Init.ClockDivision = 0;
  tim4.Init.CounterMode = TIM_COUNTERMODE_UP;
  tim4.Init.RepetitionCounter = 0;
  tim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  HAL_TIM_PWM_Init(&tim4);

  TIM_OC_InitTypeDef oc;
  oc.OCMode = TIM_OCMODE_PWM1;

  oc.OCPolarity = TIM_OCPOLARITY_HIGH;
  oc.OCNPolarity = TIM_OCNPOLARITY_LOW;
  oc.OCFastMode = TIM_OCFAST_ENABLE;
  oc.OCIdleState = TIM_OCIDLESTATE_SET;
  oc.OCNIdleState = TIM_OCNIDLESTATE_RESET;


  oc.Pulse = pulseval2;
  HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_2);

  oc.Pulse = pulseval3;
  HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_3);

  oc.Pulse = pulseval4;
  HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_4);


  HAL_TIM_PWM_Start(&tim4, TIM_CHANNEL_2);
  HAL_TIM_PWM_Start(&tim4, TIM_CHANNEL_3);
  HAL_TIM_PWM_Start(&tim4, TIM_CHANNEL_4);

  pulseval1 = 0;

 while (1) {
	 while(pulseval1 <= 999)
	 {
		 pulseval1++;
	 }
	  oc.Pulse = pulseval1;
	  HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_1);
	  HAL_TIM_PWM_Start(&tim4, TIM_CHANNEL_1);
 }

Skupiłem się tutaj póki co tylko na kanale 1 i nie usuwałem kodu dla pozostałych kanałów. Pewnie działałoby to też bez zmiennej pulseval1, ale stwierdziłem, że bezpieczniej jest sterować jej wartością, a później przypisać jej wartość do oc.Pulse i dalej wstawić do kanału 1.

Rzeczywiście taki kod uruchamia diodę, ale nie powoduje "łagodnego narastania". Wygląda to tak, jakby oc.Pulse osiągało od razu wypełnienie graniczne z pętli, czyli tutaj wartość 999  (na razie dla testu pętla miała liczyć tylko w górę).

while(pulseval1 <= 999)

Na pewno mam gdzieś prosty błąd, którego nie widzę, a jest oczywisty dla Ciebie @Elvis, czy kogokolwiek z większym doświadczeniem, niż moje. Będę wdzięczny za podpowiedź, nie rozwiązanie (chciałbym dojść do tego w pewnym stopniu sam).

Udostępnij ten post


Link to post
Share on other sites

@aldenham, jeśli można wtrącić się z szybką odpowiedzią 🙂 Zobacz na kod, który masz w głównej pętli programu. Za pierwszym razem program "wchodzi" do tej pętli z wartością zmiennej == 0. Potem w kolejnym while'u ta zmienna ustawia się ostatecznie na 1000. Dopiero potem ten tysiąc jest wpisany do pola oc.Pulse. W kolejnych iteracjach ta pętla while już nie jest wyknywana, zmienna ma stale wartość 1000. To jest właśnie błędem, że za szybko zamknąłeś tę pętlę, która inkrementuje pulseval1 🙂

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Nie gniewaj się za moje uwagi, wcześniejszy program wygladał jakbyś nie za bardzo zrozumiał instrukcję przypisania, były już takie nieporozumienia na forum więc to nie jest niemożliwe 🙂 Teraz masz w programie źle nawiasy, w pętli while zwiększasz pulseval1, ale używasz jej dopiero jak osiagnie 1000.

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

@Elvis Nie gniewam się oczywiście, a przyznaję, że często muszę zaglądać do podstaw kursu. I jednocześnie odgrzewam temat po dłuższym czasie. Problem ogarnąłem, ale skorzystałem z makra

__HAL_TIM_SET_COMPARE

1) Zastanawiam się jednak, czy da radę sterować jasnością diody wyłącznie sprzętowo, nie z poziomu pętli głównej programu while(1). Np. czy można zaimplementować sterowanie diodą RGB pokazane w kursie całkowicie sprzętowo, pozostawiając moc procesora wolną na inne obliczenia? Jeśli nie przez samo PWM, to może przez DMA?

------------------------------------------------------------------------------

2) W ogóle kurs jest obszerny, ale brakuje mi trochę wyjaśnienia, co robią poszczególne makra, albo jakie dokładnie zmienne pisać w . Np. właśnie powyższe makro __HAL_TIM_SET_COMPARE jest teoretycznie opisane w instrukcji, a szczegóły są w dokumentacji projektu (HAL_Driver), jednak czytając pewne rzeczy nadal mam wątpliwości, skąd się biorą. Nie będę udawał, że wszystko jest dla mnie oczywiste, bo nie jest. Możecie polecić jakąś dodatkową lekturę, która opisze makra od podszewki? Ten kurs jest naprawdę dobry i bez tego nie wiedziałbym nic, ale fajnie byłoby wiedzieć trochę więcej.

Inny przykład, HAL_TIM_PWM_ConfigChannel. W instrukcji rozpisany jest jako

HAL_TIM_PWM_ConfigChannel (TIM_HandleTypeDef *htim, TIM_OC_InitTypeDef *sConfig, uint32_t Channel)

a gdy już piszę kod, to wygląda on np. tak

HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_1);

Czy dobrze rozumiem, że wskaźniki *htim i *sConfig zadeklarowane są w przykładach w kursu jako odpowiednio tim4 i oc? I mogę ewentualnie zamiast tim4 i oc użyć innych dowolnych nazw (oczywiście później muszę odpowiednio do nich odnosić się w kolejnych pozycjach danej struktury)?
TIM_CHANNEL_1 jest rzeczywiście podany w instrukcji jako wybór kanału 1, także to jest jasne.

Swoją drogą gdybym miał uczyć się programowania stm32 od zera korzystając tylko z dokumentacji, to na pewno i ja, i wielu innych śmiałków by się zniechęciło. Także jeszcze raz brawa i szacunek za ten kurs.

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

Czy możliwe jest , że zadanie 7.3  to 15,4 (1 kanał) i 61,6% (4 kanały).

Częstotliwość sterowania 20kHz  czyli 0,00005s (czas jednego taktu sterowania czyli nasze 100% )

7,7uS to 0,0000077s stąd powyższe moje wyniki, cały czas zastanawiam się czy 100% czasu procesora to jeden takt jego zegara sterującego??

Inaczej, sprawdźmy ile czasu zajmie wykonywanie przerwań w czasie 1s:

wyliczmy: (dla 1kanału)

7,7x10^(-6) x 20^3 = 0,154s x100% = 15,4% x 4(kanały) = 61,6% czasu procesora. 

Oczywiście dla pełnego obrazu czasu trzeba by dodać czas obsługi przerwania pełnego okresu timera , który byłby podobny i przy 4 kanałach mogło by przekroczyć 100% obciążenia procesora i zamiast 20kH realnie by pracował timer wolniej.. strzelam jakieś 18kHz.

To tylko moje głośne rozumowanie ale fajnie by ktoś potwierdził mnie że nie zmierzam do nikąd 🙂

 

 

Edytowano przez jackg

Udostępnij ten post


Link to post
Share on other sites

Wyniki są jak najbardziej poprawne. W tym zadaniu chodziło o pokazanie jak dużo czasu może zajmować obsługa przerwań jeśli nie zadbamy o optymalizację.

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

Mam pytanie do zadania 7.4. Co autor miał na myśli? Czy to co zapisałem poniżej jest celem, czy może coś jeszcze innego?
1000 -> 0100 -> 0010 -> 0001 -> 1000 -> ... (1 - dioda zapalona, 0 - dioda zgaszona)
Jeśli zadanie jest jak powyżej, to mam w głowie jak to zrobić przy pomocy przerwań. Jeśli okres będzie T=1s, to przy 4 przerwaniach 4*7.7us / 1 s = 0.00308 % okresu, więc całkiem ok.
Jeśli miałbym zrobić powyższe zadanie przy pomocy PWM, to niestety nie potrafię sobie tego wyobrazić. PWM ma swoje wypełnienie mierzone od punktu 0, do określonego punktu (PWM1), lub alternatywnie od odkreślonego punktu do końca okresu (PWM2). Może jakaś funkcja pulse byłaby ok, ale w kursie jeszcze nie było nic takiego omawiane.

Edytowano przez Emerid

Udostępnij ten post


Link to post
Share on other sites

	TIM_OC_InitTypeDef oc;
	oc.OCMode = TIM_OCMODE_PWM2;
	oc.Pulse = 100;
	oc.OCPolarity = TIM_OCPOLARITY_HIGH;
	oc.OCNPolarity = TIM_OCNPOLARITY_LOW;
	oc.OCFastMode = TIM_OCFAST_ENABLE;
	oc.OCIdleState = TIM_OCIDLESTATE_SET;
	oc.OCNIdleState = TIM_OCNIDLESTATE_RESET;
	HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_1);
	HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_2);
	HAL_TIM_PWM_ConfigChannel(&tim4, &oc, TIM_CHANNEL_3);

	--------------------------------------------------------

	__HAL_TIM_SET_COMPARE(&tim4, TIM_CHANNEL_1, calc_pwm(b));
	__HAL_TIM_SET_COMPARE(&tim4, TIM_CHANNEL_2, calc_pwm(g));
	__HAL_TIM_SET_COMPARE(&tim4, TIM_CHANNEL_3, calc_pwm(r));
 


oc.Pulse = 100; ustawia wartość rejestru output_compare na 100 przy jego pierwotnej konfiguracji
__HAL_TIM_SET_COMPARE(tim, channel, compare) jest makrem nadpisującym tylko wartość tego jednego rejestru output_compare, pozostawiając resztę konfiguracji output_compare nienaruszoną.
 

Proszę o potwierdzenie, czy dobrze rozumiem działanie tego kodu.

Udostępnij ten post


Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.


×
×
  • Utwórz nowe...