Skocz do zawartości

STM32L476 konfiguracja timerów


Pomocna odpowiedź

Napisano (edytowany)

Potrzebuję wygenerować sygnały PWM na czterech wyjściach mikrokontrolera, specyfika jest taka: przed podaniem pierwszego pulsu - nazwijmy go roboczo preimpuls muszę mieć nieco czasu na uruchomienie innych peryferii ADC i/lub komparatora, ten preimpuls powinien trwać około 20μS po czym należy odczekać 10...20μS na odpowiedź układów peryferyjnych. Po tej odpowiedzi należy podjąć decyzję czy generować impuls główny na kolejnym kanale PWM. Impuls główny powinien być regulowany w czasie trwania z zewnętrznego urządzenia. Następnie cykl powtarza się dla drugiej pary. Do tej pory próbowałem wykorzystać timer1 z DMA jednak ze względu na ograniczoną szybkość działania nie umiem uzyskać przerwy krótszej niż te 200μS co spowalnia ogólną częstotliwość powtórzeń, no i jak dla tak prostego rozwiązania zużywa niepotrzebnie cztery kanały DMA. Potrzebuję raczej koncepcji, naprowadzenia, niż gotowego rozwiązania, nie wiem może użyć licznika zdarzeń i trybu triggera? Chętnie wykluczyłbym też DMA w tym miejscu bo sygnały nie są aż tak skomplikowane.
roboczo mam funkcję która generuje te powtórzenia, wygląda tak (narazie dla dwóch kanałów)

 

/* USER CODE END Header_StartTask02 */
void StartTask02(void *argument)
{
  /* USER CODE BEGIN StartTask02 */
  /* Infinite loop */

# define  tab_size  3

  uint32_t pre_puls[tab_size] =    {800,0,0};
  uint32_t main_puls[tab_size] =   {0,4000,0};


 // HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_1,pre_puls,10 );
 // HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_2,main_puls,10 );
 // __HAL_TIM_MOE_ENABLE(&htim1);


  //HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  // HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
  // HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);


  uint16_t pwm1 = 0;
  int8_t dir = 1;
  int counter = 0;
  for(;;)
  {
    counter++;
    vTaskDelay(1/portTICK_RATE_MS);
    HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
if (counter < 1000) {
  HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_1,pre_puls,tab_size *2 );
  HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_2,main_puls,tab_size*2);
}
  }
  /* USER CODE END StartTask02 */
}

init
 

/* TIM1 init function */
void MX_TIM1_Init(void)
{

  /* USER CODE BEGIN TIM1_Init 0 */

  /* USER CODE END TIM1_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  /* USER CODE BEGIN TIM1_Init 1 */

  /* USER CODE END TIM1_Init 1 */
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 9999;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.BreakFilter = 0;
  sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
  sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
  sBreakDeadTimeConfig.Break2Filter = 0;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */

  /* USER CODE END TIM1_Init 2 */
  HAL_TIM_MspPostInit(&htim1);

}

Powyższy program generuje takie sygnały, dość blisko tego czego poszukuję ale to wciąż za długo trwa
Logic_Js5cJDub7v.thumb.png.396226ef8c6f3a9dd9a01d6a7012f1d6.png

Edytowano przez _LM_
  • Lubię! 1

Ogólnie już jestem do przodu o tyle że pozbyłem się DMA z obsługi timerów, rzeczywiście nie jest potrzebne do generowania prostych impulsów. Na teraz udało się mi to zrobić na tim1 jako generator pierwszego impulsu i wyzwalania tim2 w trybie podrzędnym. Będzie nieco czasu to znów do tego usiądę

  • 2 miesiące później...
(edytowany)

Cześć, dzięki za zainteresowanie, temat musiałem odłożyć z braku czasu ale prędzej czy później będzie trzeba do tego wrócić i wszelkie materiały są przydatne. W czasie prób i poszukiwań znalazłem kilka źródeł które mi pomogły w ustawieniu timerów do żądanego zachowania, teraz nie pamiętam dokładnie ale zakończyło się na tym iż nie mogłem zmusić PWM aby był w stanie niskim na swoim wyjściu po inicjalizacji a jeśli już do tego doszło to zmieniało mi to fazę i inne zależności na wyjściu. Jednakże z powodu że wpadły inne rzeczy musiałem to zostawić, z pewnością prędzej czy później powrócę do tego urządzenia. Poniżej pewien zestaw źródeł z których korzystałem (oprócz kursu na udemy)
https://controllerstech.com/stm32-timers-9-one-pulse-mode/
https://blog.embeddedexpert.io/?p=3421

https://blog.embeddedexpert.io/?p=585

https://deepbluembedded.com/stm32-edge-aligned-pwm-3-phase-example-code/

https://deepbluembedded.com/stm32-pwm-frequency-resolution-configuration/

https://www.st.com/resource/en/application_note/an4013-introduction-to-timers-for-stm32-mcus-stmicroelectronics.pdf

Edytowano przez _LM_

Nie wiem jak to robisz, ale pokażę jak ja mam zrobiony PWM. I od razu żebyś nie myślał że jestem taki genialny to przyznam się że nie ja to wymyśliłem.

Kwestia dotyczy pinu PB1, który leci na bazę tranzystora kluczującego napięciem na LCD BACK LIGHT - czyli sterując wielkością PWM można przyciemniać bądź rozjaśniać podświetlenie LCD. Rzecz dotyczy tego konkretnego pinu, ale resztę na spokojnie sobie przerobisz, chodzi o zasadę.

 

1. A więc pierwsza rzecz - sprawdzasz czy pin którym chcesz "PeWueMować" jest jakimś kanałem dla jakiegoś timera, bo tak jest najłatwiej.

2. Jeżeli chcesz koniecznie machać jakimś pinem nieskojarzonym jako "TIMx_CHx" to wówczas musisz włączyć przerwanie od tego timera i w przerwaniu programowo ustawiać stan wybranego pinu... Co przy dużych częstotliwościach może być bez sensu, że oprogramowanie mrugania pinem może trwać dłużej niż faza PWM - trzeba o tym pamiętać bo zawiesisz proca w niekończącym się przyjmowaniu IRQ (!).

3. Rzecz trzecia - normalnie po ustawieniu w CUBE ani PWM, ani IRQ nie zostaje po resecie automatycznie uruchomione. Dla przerwania musisz zrobić coś takiego w swoim programie gdzieś na początku po inicjalizacji timera:

extern TIM_HandleTypeDef htim4;
HAL_TIM_Base_Start_IT(&htim4);

I dopiero to staruje przerwanie od timera4 - oczywiście pod warunkiem że jest ono włączone w jego konfiguracji.

 

No i teraz dochodząc do PWM - przykładowa konfiguracja w CUBE:

PWM.thumb.jpg.bd498c725ff65ab5318662e8c4618ed5.jpg

 

ALE...

gdzieś po inicjalizacji timera musisz wystartować PWM

extern TIM_HandleTypeDef htim3;

HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4); // enable PWM back light

 

I dopiero teraz możesz sterować PWM używając na przykład takiej definicji:

extern TIM_HandleTypeDef htim3;

#define setPWM(x) __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_4, x);

// 0 dla wyłączenia podświetlenia, 65535 dla maksymalnego rozjaśnienia
// 0 to jest oczekiwany przez Ciebie niski stan na wyjściu PWM
// oczywiście można wybierać wartości pośrednie pomiędzy 0 - 65535
    setPWM(0); // or setPWM(65535); 

Poniżej FADE-OUT obrazka za pomocą PWM od 0:00 do 0:06 sekundy :

 

Kod FADE-OUT'u:

  for (int t = 65535; t > 0; t -= 32)
  {
	  HAL_Delay(1);
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_4, t);
  }

 

Na tym filmiku akurat bawię się sterowaniem PWM podświetlenia LCD:

Opcja AUTO włącza automatyczne użycie drugiego ustawienia podświetlenia pomiędzy godziną 20:00, a 6:00, ponieważ w nocy widać na LCD poświatę.

Opcja LIGHT ustawia wielkość PWM w 10 stopniach (bez zjechania do 0 bo potem by nie było widać co i gdzie nacisnąć LOL), która w zależności od ustawień AUTO jest całodobowa, lub dla okresu pomiędzy 6:00 a 20:00

Link do całego repozytorium:

https://github.com/wegi1/STM32F407VET6-BLACK-DEVBOARD-AS-RTC

Link do tematu tego zegara na FORBOT:

Mam nadzieję że pomogłem.

 

(edytowany)

Wierz mi że gdyby chodziło o klasyczny PWM nie zawracałbym gitary. Problemem nie było wygenerowanie pojedyńczego PWM a sekwencji sygnałów które podlegają dodatkowym zależnościom w algorytmie sterowania. Ja do tego tematu wrócę. Wtedy pokażę czy, co i w jaki sposób udało się zbudować

Edytowano przez _LM_

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...