środa, 22 lipca 2020

[41] STM32F4 - Power Voltage Detector (PVD)

W tym poście chciałbym omówić sposób obsługi detektora napięcie PVD zastosowanego w mikrokontrolerach STM32Fxxxx.

[Źródło: http://www.st.com/en/evaluation-tools/stm32f4discovery.html]

PVD (Power Voltage Detector):


PVD jest to wewnętrzny blok w który zostały wyposażone wszystkie mikrokontrolery STM32Fxxxx. Pozwala na  generowanie przerwania, po przekroczeniu zdefiniowanych progów napięć. Progi wyzwalania zdefiniowane od napięcia 2.0V do 2.9V z krokiem około 0.1V.

Przerwania może być generowane w dwóch kierunkach. Kiedy wartość napięcia spadnie poniżej danego progu lub/i gdy napięcie wzrośnie ponad zadaną wartość.

Linia PWD jest wewnętrznie podłączona do linii EXTI16.

Definicja poszczególnych poziomów umieszczona w bibliotekach STM wygląda następująco:

  1. #define PWR_PVDLEVEL_0  PWR_CR2_PLS_LEV0  /*!< PVD threshold around 2.0 V */
  2. #define PWR_PVDLEVEL_1  PWR_CR2_PLS_LEV1  /*!< PVD threshold around 2.2 V */
  3. #define PWR_PVDLEVEL_2  PWR_CR2_PLS_LEV2  /*!< PVD threshold around 2.4 V */
  4. #define PWR_PVDLEVEL_3  PWR_CR2_PLS_LEV3  /*!< PVD threshold around 2.5 V */
  5. #define PWR_PVDLEVEL_4  PWR_CR2_PLS_LEV4  /*!< PVD threshold around 2.6 V */
  6. #define PWR_PVDLEVEL_5  PWR_CR2_PLS_LEV5  /*!< PVD threshold around 2.8 V */
  7. #define PWR_PVDLEVEL_6  PWR_CR2_PLS_LEV6  /*!< PVD threshold around 2.9 V */
  8. #define PWR_PVDLEVEL_7  PWR_CR2_PLS_LEV7  /*!< External input analog voltage (compared internally to VREFINT) or 3.0V*/

Próg PWR_PDLEVEL_7 może się różnić w zależności od rodziny układów.

Dane z poszczególnymi progami wyzwalania przechowywane są w rejestrze PWR->CR.

Za PVD odpowiedzialne są bity 7-5 (poziom wyzwalania) oraz 4 (uruchomienie układu sprawdzania):


Gdy napięcie jest mniejsze niż próg wyzwalania to następuje ustawienie bit 2 (PVDO) w rejestrze PWR_CSR. Bit ten jest utrzymywany na 0 gdy napięcie będzie powyżej progu wyzwalania. Dodatkowo należy pamiętać, że w trybie Standby Mode PVD jest wyłączone:


Poniżej znajdują się zakresy wyzwalania dla poszczególnych poziomów opisane w dokumentacji to układów STM32F4 [2 str. 83]:



Uruchomienie PVD w CubeMx:


W tej części procedura uruchomienia sprowadza się jedynie do uruchomienia przerwania wyzwalanego od PVD:



Kod HAL:


Po wygenerowaniu projektu w kodzie zostają umieszczone tylko funkcje odpowiedzialne za obsługę przerwania:

  1. void PVD_IRQHandler(void)           //stm32f4xx_it.c
  2. {
  3.   /* USER CODE BEGIN PVD_IRQn 0 */
  4.  
  5.   /* USER CODE END PVD_IRQn 0 */
  6.   HAL_PWR_PVD_IRQHandler();
  7.   /* USER CODE BEGIN PVD_IRQn 1 */
  8.  
  9.   /* USER CODE END PVD_IRQn 1 */
  10. }
  11.  
  12. void HAL_PWR_PVD_IRQHandler(void)   //stm32f4xx_hal_pwr.c
  13. {
  14.   /* Check PWR Exti flag */
  15.   if(__HAL_PWR_PVD_EXTI_GET_FLAG() != RESET)
  16.   {
  17.     /* PWR PVD interrupt user callback */
  18.     HAL_PWR_PVDCallback();
  19.    
  20.     /* Clear PWR Exti pending bit */
  21.     __HAL_PWR_PVD_EXTI_CLEAR_FLAG();
  22.   }
  23. }

Do programu należy jeszcze dorzucić ustawienie oraz uruchomienie PVD, uruchomienie przerwania od PVD.

Domyślnie w rejestrze PWR_CR bity PLS ora PVDE ustawione są na 0.

W programie wykorzystującym biblioteki HAL'a do konfiguracji można użyć struktury zdefiniowanej w pliku stm32f4xx_hal_pwr.h

  1. typedef struct
  2. {
  3.   uint32_t PVDLevel;   /*!< PVDLevel: Specifies the PVD detection level.
  4.                             This parameter can be a value of @ref PWR_PVD_detection_level */
  5.  
  6.   uint32_t Mode;      /*!< Mode: Specifies the operating mode for the selected pins.
  7.                            This parameter can be a value of @ref PWR_PVD_Mode */
  8. }PWR_PVDTypeDef;

Uruchomienie PVD wraz z przerwaniem oraz poziomem wyzwalania wygląda następująco:

  1. void PVD_Enable(PVD_Level_t Level, PVD_Inter_Trigger_t Trigger)
  2. {
  3.     PWR_PVDTypeDef PVD_Configuration;
  4.  
  5.     __HAL_RCC_PWR_CLK_ENABLE();
  6.     HAL_NVIC_SetPriority(PVD_IRQn, PVD_NVIC_PRIORITY, PVD_NVIC_SUBPRIORITY);
  7.     HAL_NVIC_EnableIRQ(PVD_IRQn);
  8.  
  9.     PVD_Configuration.PVDLevel = (uint32_t)Level;
  10.     PVD_Configuration.Mode = (uint32_t)Trigger;
  11.  
  12.     HAL_PWR_ConfigPVD(&PVD_Configuration);
  13.     HAL_PWR_EnablePVD();
  14. }

W powyższej funkcji uruchomiony zostaje zegar PWR, ustawione priorytety przerwań od PVD, uruchomienie przerwania od PVD, konfiguracja poziomów i trybu wyzwalania. Na samym końcu należy włączyć blok 

Ustawienie PVD zdefiniowane w bibliotece HAL'a wygląda następująco:

  1. void HAL_PWR_ConfigPVD(PWR_PVDTypeDef *sConfigPVD)
  2. {
  3.   /* Check the parameters */
  4.   assert_param(IS_PWR_PVD_LEVEL(sConfigPVD->PVDLevel));
  5.   assert_param(IS_PWR_PVD_MODE(sConfigPVD->Mode));
  6.  
  7.   /* Set PLS[7:5] bits according to PVDLevel value */
  8.   MODIFY_REG(PWR->CR, PWR_CR_PLS, sConfigPVD->PVDLevel);
  9.  
  10.   /* Clear any previous config. Keep it clear if no event or IT mode is selected */
  11.   __HAL_PWR_PVD_EXTI_DISABLE_EVENT();
  12.   __HAL_PWR_PVD_EXTI_DISABLE_IT();
  13.   __HAL_PWR_PVD_EXTI_DISABLE_RISING_EDGE();
  14.   __HAL_PWR_PVD_EXTI_DISABLE_FALLING_EDGE();
  15.  
  16.   /* Configure interrupt mode */
  17.   if((sConfigPVD->Mode & PVD_MODE_IT) == PVD_MODE_IT)
  18.   {
  19.     __HAL_PWR_PVD_EXTI_ENABLE_IT();
  20.   }
  21.  
  22.   /* Configure event mode */
  23.   if((sConfigPVD->Mode & PVD_MODE_EVT) == PVD_MODE_EVT)
  24.   {
  25.     __HAL_PWR_PVD_EXTI_ENABLE_EVENT();
  26.   }
  27.  
  28.   /* Configure the edge */
  29.   if((sConfigPVD->Mode & PVD_RISING_EDGE) == PVD_RISING_EDGE)
  30.   {
  31.     __HAL_PWR_PVD_EXTI_ENABLE_RISING_EDGE();
  32.   }
  33.  
  34.   if((sConfigPVD->Mode & PVD_FALLING_EDGE) == PVD_FALLING_EDGE)
  35.   {
  36.     __HAL_PWR_PVD_EXTI_ENABLE_FALLING_EDGE();
  37.   }
  38. }

Kod LL:

Tutaj należało wprowadzić kilka zmian tak aby więcej operacji była wykonywana bezpośrednio na rejestrach.

Funkcja uruchamiająca PVD z zadanymi parametrami:

  1. void PVD_Enable(PVD_Level_t Level, PVD_Inter_Trigger_t Trigger)
  2. {
  3.     LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
  4.  
  5.     NVIC_SetPriority(PVD_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),PVD_NVIC_PRIORITY, PVD_NVIC_SUBPRIORITY));
  6.     NVIC_EnableIRQ(PVD_IRQn);
  7.  
  8.     LL_PWR_SetPVDLevel((uint32_t)Level);
  9.  
  10.     //Clear prev config
  11.     (EXTI->EMR &= ~(((uint32_t)EXTI_IMR_MR16)));
  12.     (EXTI->IMR &= ~(((uint32_t)EXTI_IMR_MR16)));
  13.     CLEAR_BIT(EXTI->RTSR, ((uint32_t)EXTI_IMR_MR16));
  14.     CLEAR_BIT(EXTI->FTSR, ((uint32_t)EXTI_IMR_MR16));
  15.  
  16.     /* Configure interrupt mode */
  17.     (EXTI->IMR |= (((uint32_t)EXTI_IMR_MR16)));
  18.  
  19.     /* Configure the edge */
  20.     if(Trigger == PVD_Trigger_Rising)
  21.     {
  22.         SET_BIT(EXTI->RTSR, ((uint32_t)EXTI_IMR_MR16)); //Rising edge
  23.     }
  24.     else if(Trigger == PVD_Trigger_Falling)
  25.     {
  26.         SET_BIT(EXTI->FTSR, ((uint32_t)EXTI_IMR_MR16)); //Falling edge
  27.     }
  28.     else //both edge
  29.     {
  30.         SET_BIT(EXTI->RTSR, ((uint32_t)EXTI_IMR_MR16)); //Rising edge
  31.         SET_BIT(EXTI->FTSR, ((uint32_t)EXTI_IMR_MR16)); //Falling edge
  32.     }
  33.  
  34.     //Enable PVD
  35.     LL_PWR_EnablePVD();
  36. }

Obsługa przerwania wygląda następująco:

  1. void PVD_IRQHandler(void)
  2. {
  3.     if ((EXTI->PR & (((uint32_t)EXTI_IMR_MR16))) != RESET)
  4.     {
  5.         PVD_Handler(PWR_Get_PVDOutput());
  6.  
  7.         (EXTI->PR = (((uint32_t)EXTI_IMR_MR16)));
  8.     }
  9. }