Monitoring Power Supply with STM32 Peripherals

1. Introduction

The quality of the power supply is crucial for the reliability of electronic applications. Monitoring the power supply during the lifetime of an application can help identify failures, facilitate maintenance, and evaluate power source quality. The purpose of this article is to propose solutions to detect, in real-time, a degradation in the quality of the power supply or a disturbance that could alter the proper functioning of the application or its reliability. These solutions are based on STM32 embedded peripherals, thus avoiding the addition of external components.

STM32 peripherals

In this article, some examples are presented using comparators, ADC, operational amplifiers, internal references, NVIC, TIMER, and DMA. These are integrated into a case study to illustrate their potential use.

2. Case study: data center ventilation management

The system studied is the management of the ventilation of a data center. This system is critical because a failure could lead to severe material damage or unnecessary electricity consumption. This system must be robust against potential failures and detect unexpected behaviors to report them to the maintenance team.

2.1. Operation

Functional diagram of the case study

Temperature sensors transmit the measurements to the microcontroller via an analog signal. A state machine manages the turning on and off of the ventilation based on the sensor values. Additionally, the station sends operation logs and alerts of anomalies to a server. The functional diagram illustrates the overall operation of the system. Notably, the temperature sensor measurement is a sensitive step that can be altered by fast transients on the power supply.

2.2. Risks and threats

  • Power supply circuit alteration: Time and thermal cycles can lead to the failure of power supply circuit components. A failure can led to a degradation of the power supply circuit's performance, a total shutdown, or even damage the application. Detecting signs of degradation allows for planned maintenance to avoid a total and unexpected shutdown of the application. In our case study, the shutdown of the ventilation system presents a risk of server degradation or forced shutdown.
  • Measurement disturbance: The reliability and accuracy of analog measurements depend on a reference voltage that must be very stable and precise. Fast transients on the power supply voltages can disturb this reference voltage and disrupt the analog measurement. In our case study, the disturbance of the temperature measurement can alter the decision-making process.

2.3. Verifications to implement

To improve the robustness of the system, several types of verifications can be implemented:

  • Detect power supply loss: Monitor the upstream power source to detect loss of power supply and execute a shutdown routine.
  • Detection of temporary transients: Detect transients to repeat potentially false analog measurements.
  • Detection of power supply voltage drifts: Prevent malfunction if the power supply can no longer provide the necessary energy.
  • Continuous measurements with the ADC: Use signal processing models to identify potential unexpected behaviors.

2.4. Reaction speed

When detecting a phenomenon, the reaction speed can be critical to perform actions, such as detecting a power supply shutdown. The first step is detection; for this, it is preferable to prioritize asynchronous mechanisms and those independent of the CPU that trigger an interrupt, such as the comparator rather than the ADC. Then, the speed of information processing by the CPU is crucial. To develop STM32 code, the effective and recommended way is to use the software library provided by ST called HAL. The HAL simplifies the interaction between the user program and the STM32 hardware peripherals. For faster performances the LL library can be used but it is more complex to implement. Those libraries offer two elements that attract our attention:

  • An interrupt management mechanism using "interrupt handlers" that automatically handle verification and flag lowering and then execute the corresponding user code.
  • A set of data structures and functions that simplifies the configuration of peripherals and the reading of useful data.

2.4.1. Interrupt management

Functional diagram of HAL interrupt management

The nested vector interrupt control (NVIC) is the hardware peripheral that handles interrupts. All interrupt signals from the various peripherals arrive directly at the NVIC. It orders the CPU to interrupt the current program execution and provides it with the address of the IRQ Handler (Interrupt ReQuest Handler). The IRQ Handler (or ISR Interrupt Service Routine) is the function directly executed to handle the interrupt.

As illustrated in figure interrupt_hal_block, the HAL provides a function called by the IRQ Handler, which checks all the configuration registers and interrupt flags to determine the source of the interrupt and handles lowering the corresponding flag. It then calls a callback function that contains the user code. Therefore, the first line of user code is executed only after three context saves and dozens of conditional tests.

Although offering many advantages such as ease of use, readability, and portability, these procedures are computationally expensive and can be too heavy in some cases. The solution is to specify that the interrupt handler should not be used and to write the content of the IRQ Handler yourself. In this case, it is necessary to remember the necessary verifications and to lower the interrupt flag.

2.5. Measurement pin protection

When measuring the power supply voltage using an MCU (microcontroller unit), it is crucial to be cautious about overshoot and overvoltage conditions on the MCU pins. These conditions can damage the MCU.

To mitigate the risks associated with overshoot and overvoltage, consider the following precautions:

  • Use voltage clamping diodes: Diodes such as Zener diodes or TVS (transient voltage suppression) diodes can clamp the voltage to a safe level, protecting the MCU pin from transient spikes.
  • Implement proper filtering: Use capacitors and resistors to filter out high-frequency noise and transient spikes before the voltage reaches the MCU pin.
  • Design precise voltage dividers: Ensure that the voltage divider is correctly designed to scale down the voltage within the MCU's operating range.

3. Power loss detection

This solution is designed to detect that the power source is dropping and probably shutting down. Reflection is carried out to react as quickly as possible if actions are to be taken at the time of detection.

3.1. Operation

The source voltage is generally higher than the MCU operating voltage, leaving time between power source drop and VDD drop. Keep in mind that the falling rate highly depends on current consumption, the number of decoupling capacitors, or the presence of output discharge features on power supplies, for example. In this example, Vin is 11 V, leaving 890 µs.

Power loss delay

This means that if the circuit detects a voltage drop with a threshold of 10 V, it provides anticipation to execute the power loss routine before the system's complete extinction. In the present example, this consists of saving a specific value into backup registers.

Configuration schematic

Use a comparator to compare the voltage with an internal reference voltage. An interrupt is triggered at falling and rising edges.

The structure is described with this state chart.

State chart
Comparator CubeMX configuration
NVIC CubeMX configuration

3.2. Comparator configuration

The STM32 has an internal reference voltage VREFINT of 1.25 V in the case of the STM32G4. This reference can be directly connected to the negative input of the comparator. The voltage to be monitored is connected to the positive input of the comparator through a voltage divider, which defines the detection threshold. Configure the comparator in interrupt mode for rising and falling edges. Select Internal VRef as the negative input.

Unselect code generation of the Call HAL Handler for comparator interrupt in NVIC configuration.

RTC configuration

3.3. Additional configurations

  • Activate the RTC to enable backup registers.
  • Activate UART, in this example the LPUART1.

3.4. Source code

  • In main.h, define some aliases. These have to be changed according to your application.
  /* USER CODE BEGIN Private defines */
  #define COMP_DETECT_PL COMP1 // replace COMP1 by the chosen one
  #define COMP_DETECT_PL_EXTI_LINE COMP_EXTI_LINE_COMP1 // Use the right EXTI line
  #define COMP_DETECT_PL_EXTI_CLF_MACRO LL_EXTI_ClearFlag_0_31 // Use the right EXTI clear flag macro
  /* USER CODE END Private defines */
  • In main.c, function int main(void); At boot, check if a power loss occurred, if so, execute specific instructions such as sending data to the server.
  /**
    * @brief  The application entry point.
    * @retval int
    */
  int main(void)
  {
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_COMP1_Init();
    MX_RTC_Init();
    MX_LPUART1_UART_Init();
    /* USER CODE BEGIN 2 */
    uint8_t tx_buff[] = "\rStart power loss example app\n\r";
    HAL_UART_Transmit(&hlpuart1, tx_buff, sizeof(tx_buff), 1000);

    // At boot, check for backup register to know if a power loss occurred
    // Be careful to check before enabling comparator interrupt!!
    if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0xEBEB) // check for power loss code in backup registers
    {
      if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) == 0xBEBE)
      {

        uint8_t tx_buff_2[] = "\rReboot after power loss !!!\n\r";
        HAL_UART_Transmit(&hlpuart1, tx_buff_2, sizeof(tx_buff_2), 1000);

        HAL_PWR_EnableBkUpAccess();
        HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0xEBEB); // remove shutdown code in backup registers
        HAL_PWR_DisableBkUpAccess();

        /*
        * insert USER code that reacts to a reboot after power loss
        */
      }
    }

    HAL_COMP_Start(&hcomp1);

    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
      /* USER CODE END WHILE */

      /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
  }
  • In stm32g4xx_it.c, function void COMP1_2_3_IRQHandler(void); Interrupt Request function executed at rising and falling edges of the comparator. Discriminate one from another with a condition and save the right number in the backup register depending on whether a power loss occurred or not.
  /**
    * @brief This function handles COMP1, COMP2 and COMP3 interrupts through EXTI lines 21, 22 and 29.
    */
  void COMP1_2_3_IRQHandler(void)
  {
    /* USER CODE BEGIN COMP1_2_3_IRQn 0 */
    COMP_DETECT_PL_EXTI_CLF_MACRO(COMP_DETECT_PL_EXTI_LINE); // Clear interrupt flag

    if ((COMP_DETECT_PL->CSR & (COMP_CSR_VALUE)) == 0) // if Comparator is low, power loss probably occurring
    {
      GPIOA->BSRR = (uint32_t)GPIO_PIN_4; // Debug
      HAL_PWR_EnableBkUpAccess();
      HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0xBEBE); // write power loss code in backup registers
      HAL_PWR_DisableBkUpAccess();

      /*
      * insert USER code that reacts to a shutdown
      */
    }
    else // if Comparator is high, no power loss occurred
    {
      GPIOA->BRR = (uint32_t)GPIO_PIN_4; // Debug
      HAL_PWR_EnableBkUpAccess();
      HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0xEBEB); // remove power loss code in backup registers
      HAL_PWR_DisableBkUpAccess();
    }
    /* USER CODE END COMP1_2_3_IRQn 0 */
    /* USER CODE BEGIN COMP1_2_3_IRQn 1 */

    /* USER CODE END COMP1_2_3_IRQn 1 */
  }

3.5. Result

In the context of the case study, this solution is implemented to send an error message to the maintenance company to warn that the power is shutting down. In this example, the PA4 pin is set high during the comparator interrupt, demonstrating the reaction time.

Scope capture of reaction time from comparator interrupt

4. Overshoot and undershoot detection

This solution is designed to detect when a power supply voltage drops below a defined critical threshold. Reflection is carried out to react as quickly as possible if actions are to be taken at the time of detection. Mechanisms are put in place to measure the duration of the voltage drop and the frequency at which these events occur. This section describes the solution as a whole; it can be partially implemented if some features are not required. Additionally, it is possible to combine several comparators in parallel to detect multiple levels of voltage drop or monitor different power supplies.

4.1. Operation

Use a comparator to compare the voltage with an internal reference voltage. The comparator's output is connected to a timer, called the duration timer, which measures the transient's duration, and another timer, called the timestamp timer, which records the timestamp. The comparator's output is high as long as the monitored voltage remains above the defined threshold and is low when a transient occurs. When a falling edge occurs at the comparator's output:

  • An interrupt is triggered
  • The duration timer's counter is reset and started
  • A capture of the timestamp timer is made and transferred to memory by DMA

When a rising edge occurs at the comparator's output:

  • The duration timer is captured.
  • The captured data is transferred to memory via DMA.
Functional diagram of comparator timer interrupt configuration

The functional diagram shows the connections between the elements.

Chronogram of comparator timer interrupt configuration

The chronogram illustrates the operation mode from a temporal perspective. The content of the interrupt routines is detailed later.

4.2. Comparator configuration

The STM32 has an internal reference voltage VREFINT of 1.25 V in the case of the STM32G4. This reference can be directly connected to the negative input of the comparator. The voltage to be monitored is connected to the positive input of the comparator through a voltage divider, which defines the detection threshold. When the monitored voltage drops below the threshold, a falling edge occurs at its output, triggering an interrupt.

4.3. Timer duration configuration

Slave mode triggers reset from remap comp1 One-pulse mode Channel 1 input capture DMA from remap comp1

In this example, timer 1 is used. The selected timer must have the following characteristics:

  • Minimum two channels
  • Slave mode combined Reset Trigger
  • Trigger source from Comparator
  • Input capture remap from Comparator

As a reminder, the comparator transmits a falling edge at the beginning of the transient to be detected and a rising edge at the end. Therefore, the goal is to measure the duration between the two edges. The slave mode reset trigger is used, it resets the counter and starts the increment on a falling edge. A channel, configured in input capture, captures the counter value at the end of the transient. The captured value, corresponding to the transient duration, is recorded in a circular buffer by the DMA.

The list below describes the timer configuration, and the indicated values are valid for the presented example. These values may vary depending on the chosen STM32, timer, and comparator, as well as the clock frequency, temporal resolution, and maximum transient duration.

  • Clock source: Internal clock, prescaler 164: The timer is clocked by the microcontroller's internal clock (165 MHz) divided by 164+1, resulting in 1 MHz, or a period of 1 microsecond.
  • Trigger Source: ETR1 through remap, trigger inverted: The trigger comes from the output of comparator 1, in our case ETR1 through remap. The inverted polarity allows triggering on the falling edge.
  • Slave mode: Combined Reset Trigger: When the trigger is activated, the counter is reset to zero, and the timer starts counting if it was paused.
  • Channel 1: Input capture, tim_ti_in1, rising edge, DMA: The channel captures the counter value when a rising edge comes from the comparator. A DMA transfer stores the value in a circular buffer in memory.
  • Channel 2: Output compare, interrupt: When the counter reaches the defined value, an interrupt is triggered. The maximum value correspond to the maximum pulse width measurable.
  • One pulse mode

4.4. Interrupt routines

4.4.1. Comparator

A routine can be integrated to perform actions as quickly as possible after detection if the application requires it.

4.4.2. Timer duration

The interrupt routine for output compare channel 2 is used to stop the timer at its maximum value.

4.4.3. Protection against disturbances

As presented in the introduction, there are sensitive routines. Whether it is analog measurements or decision-making, a disturbance in the power supply voltage, analog or core, can disrupt the application's operation. The idea is to raise a flag if a detection occurs during a sensitive routine and to repeat the routine to ensure the result's reliability.

4.5. Source code

  • In main.h, globals defines, change to used peripherals.
  /* USER CODE BEGIN Private defines */

  #define FLAG_SR_0_MASK 0x0001
  #define FLAG_SR_1_MASK 0x0002
  #define FLAG_SR_2_MASK 0x0004
  #define FLAG_SR_3_MASK 0x0008

  #define BUFFER_SIZE 10 // Size of DMA buffers
  #define MAX_UNDERSHOOT_DURATION 1000 // Maximum undershoot width accepted,

  #define COMP_DETECT_BO                       COMP1 // Instance of used comparator
  #define COMP_DETECT_BO_EXTI_LINE             COMP_EXTI_LINE_COMP1 // EXTI line of the comparator
  #define COMP_DETECT_BO_EXTI_CLF_MACRO        LL_EXTI_ClearFlag_0_31 // Macro to clear flag dedicated to the comparator EXTI line
  //#define COMP_DETECT_BO_EXTI_LINE_REG         EXTI->FTSR1 //

  #define TIM_DURATION                         TIM1 // Timer used to measure undershoot width

  #define TIM_DURATION_IC_DMA_CHANNEL          TIM_CHANNEL_1 // Channel used to measure undershoot width with input capture DMA
  //#define TIM_DURATION_IC_DMA_CHANNEL_FLAG    TIM_FLAG_CC1 // Corresponding interrupt flag
  //#define TIM_DURATION_IC_DMA_CHANNEL_CCR     CCR1 // Corresponding capture compare register

  #define TIM_DURATION_OVERLOAD_CHANNEL        TIM_CHANNEL_2 // Channel used to stop timer with output compare interrupt
  #define TIM_DURATION_OVERLOAD_CHANNEL_FLAG   TIM_FLAG_CC2 // Corresponding interrupt flag
  //#define TIM_DURATION_OVERLOAD_CHANNEL_CCR   CCR2 // Corresponding capture compare register

  #define TIM_DATE                             TIM2 // Timer used to get µs timestamp
  #define TIM_DATE_IC_DMA_CHANNEL              TIM_CHANNEL_1 // Channel used to measure undershoot timestamp with input capture DMA

  void clear_undershoot_bit_flag(uint32_t mask);
  void set_undershoot_bit_flag(uint32_t mask);
  int read_undershoot_bit_flag(uint32_t mask);
  /* USER CODE END Private defines */
  • In main.c, globals variables, flags and buffers used to interact between main function and interrupt routines.
/* USER CODE BEGIN PV */
volatile uint32_t flag_undershoot = 0;
volatile uint16_t dip_history[BUFFER_SIZE];
volatile uint32_t timestamp_history[BUFFER_SIZE];

const uint32_t sensitive_routine_period = 100;
/* USER CODE END PV */
  • In main.c, function HAL_StatusTypeDef my_sensitive_routine(uint16_t* measurement, const int max_attempt) demonstrates an example of sensitive routine perturbation protection.
  /* Private user code ---------------------------------------------------------*/
  /* USER CODE BEGIN 0 */
  /**
  * @brief Sample routine containing a sensitive operation which needs to be protected against disturbances.
  *        This example consists of performing an analog measurement.
  * @param measurement : Pointer to store analog value.
  * @param max_attempt : Limit number of attempts, avoid remaining blocked in infinite loop.
  * @return HAL_ERROR if max_attempt limit reached, else last HAL_ADC_PollForConversion status.
  */
  HAL_StatusTypeDef my_sensitive_routine(uint16_t* measurement, const int max_attempt)
  {
      int try_conv_cnt = 0;
      HAL_StatusTypeDef status = HAL_OK;
      do
      {
          if (try_conv_cnt > max_attempt) { break; } // Exit loop if too many attempts occurred.

          clear_undershoot_bit_flag(FLAG_SR_0_MASK); // Clear undershoot flag.
          if ((COMP_DETECT_BO->CSR & (COMP_CSR_VALUE)) == 0) // If comparator is low, an undershoot is still occurring.
          {
              set_undershoot_bit_flag(FLAG_SR_0_MASK); // Set the undershoot bit to invalidate measurements.
          }

          GPIOB->BSRR = (uint32_t)GPIO_PIN_0; // Debug; Set pin PB0 during the sensitive routine.
          HAL_ADC_Start(&hadc1);
          status = HAL_ADC_PollForConversion(&hadc1, 1); // Poll for conversion.
          GPIOB->BRR = (uint32_t)GPIO_PIN_0; // Debug; Reset pin PB0.

          try_conv_cnt++; // Increment counter.
      } while (read_undershoot_bit_flag(FLAG_SR_0_MASK)); // Check for undershoot flag, redo measurement if raised.

      if (status == HAL_OK) // If measurement is performed correctly, store analog value.
      {
          *measurement = HAL_ADC_GetValue(&hadc1); // Get the ADC value.
      }

      if (try_conv_cnt > max_attempt) return HAL_ERROR; // Return error if too many attempts occurred.
      return status; // Else return analog measurement status.
  }
  /* USER CODE END 0 */
  • In main.c, function int main(void); Start peripherals at setup. Then execute user code, in this case periodically the sensitive function.
  /**
    * @brief  The application entry point.
    * @retval int
    */
  int main(void)
  {
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_TIM1_Init();
    MX_COMP1_Init();
    MX_ADC1_Init();
    MX_LPUART1_UART_Init();
    MX_TIM2_Init();
    /* USER CODE BEGIN 2 */
    uint16_t analog_value = 0;
    uint32_t sensitive_routine_prev = 100;
    uint16_t hdma_tim1_ch1_read_index = BUFFER_SIZE;

    HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);

    COMP_DETECT_BO_EXTI_CLF_MACRO(COMP_DETECT_BO_EXTI_LINE);
    HAL_COMP_Start(&hcomp1);

    HAL_TIM_Base_Start_IT(&htim1); // Start duration timer.
    HAL_TIM_IC_Start_DMA(&htim1, TIM_DURATION_IC_DMA_CHANNEL, (uint32_t*)dip_history, BUFFER_SIZE); //
    HAL_TIM_OC_Start_IT(&htim1, TIM_DURATION_OVERLOAD_CHANNEL);
    HAL_TIM_Base_Start_IT(&htim2); // Start date timer (count µs over 32 bits).
    HAL_TIM_IC_Start_DMA(&htim2, TIM_DATE_IC_DMA_CHANNEL, (uint32_t*)timestamp_history, BUFFER_SIZE); //

    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
      if ((TIM_DATE->CNT - sensitive_routine_prev) > sensitive_routine_period) // Wait for sensitive_routine_period elapsed since last execution.
      {
        sensitive_routine_prev = TIM_DATE->CNT; // Save current timestamp.
        my_sensitive_routine(&analog_value, 10);
      }

      // Once this loop is reached, the code will go through all new samples. It can be replaced with an "if" to manage only one sample at a time.
      while (hdma_tim1_ch1_read_index != hdma_tim1_ch1.Instance->CNDTR) // If read index is different from DMA write index, new samples have arrived
      {
          if (dip_history[BUFFER_SIZE - hdma_tim1_ch1_read_index] > MAX_UNDERSHOOT_DURATION) // Check the width of the sample
          {
              /**
              * Insert USER code to warn that the undershoot width is greater than the maximum allowed
              */
          }
          hdma_tim1_ch1_read_index--; // Manage read index
          if (hdma_tim1_ch1_read_index == 0) hdma_tim1_ch1_read_index = BUFFER_SIZE; // Manage overflow
      }
      /* USER CODE END WHILE */

      /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
  }
  • In main.c, function void clear_undershoot_bit_flag(uint32_t mask); void set_undershoot_bit_flag(uint32_t mask); int read_undershoot_bit_flag(uint32_t mask); functions used to manage multiple flags, in the same 32 bits variable, with atomic mechanism to prevent multiple write access conflicts.
  /**
  * Functions to manage multiple flags and be robust to concurrent write access
  */

  /**
  * @brief Clear the bits defined with the mask
  * @param mask : bits to be cleared
  */
  void clear_undershoot_bit_flag(uint32_t mask) {
      __disable_irq(); // disable interrupt to make atomic set of instructions
      flag_undershoot = flag_undershoot & (~mask);
      __enable_irq();
  }

  /**
  * @brief Set the bits defined with the mask.
  * @param mask : Bits to be set.
  */
  void set_undershoot_bit_flag(uint32_t mask) {
      __disable_irq(); // Disable interrupt to make atomic set of instructions.
      flag_undershoot = flag_undershoot | mask;
      __enable_irq();
  }

  /**
  * @brief Check if bits are set.
  * @param mask : Bits to read.
  * @return 1 if all bits of the mask are set, else 0.
  */
  int read_undershoot_bit_flag(uint32_t mask) {
      return (flag_undershoot & mask) == mask;
  }
  • In stm32g4xx_it.c, function void TIM1_CC_IRQHandler(void);, nterrupt Request function executed at output compare channel 2. void COMP1_2_3_IRQHandler(void);, Interrupt Request function executed at rising edges of the comparator.
  /**
    * @brief This function handles TIM1 capture compare interrupt.
    */
  void TIM1_CC_IRQHandler(void)
  {
    /* USER CODE BEGIN TIM1_CC_IRQn 0 */
    uint32_t itflag = TIM_DURATION->SR;

    if ((itflag & (TIM_DURATION_OVERLOAD_CHANNEL_FLAG)) == (TIM_DURATION_OVERLOAD_CHANNEL_FLAG)) //
    {
      TIM_DURATION->CR1 &= ~(TIM_CR1_CEN); // Stop timer.
      if ((COMP_DETECT_BO->CSR & (COMP_CSR_VALUE)) == 0) // If comparator is low, under voltage is still occurring.
      {
        GPIOA->BRR = (uint32_t)GPIO_PIN_4; // Debug.
        /*
        * Insert USER code that reacts to a too long undershoot.
        */
      }

      TIM_DURATION->SR = ~(TIM_DURATION_OVERLOAD_CHANNEL_FLAG); // Clear IT flag.
    }

    /* USER CODE END TIM1_CC_IRQn 0 */
    /* USER CODE BEGIN TIM1_CC_IRQn 1 */

    /* USER CODE END TIM1_CC_IRQn 1 */
  }

  /**
    * @brief This function handles COMP1, COMP2 and COMP3 interrupts through EXTI lines 21, 22 and 29.
    */
  void COMP1_2_3_IRQHandler(void)
  {
    /* USER CODE BEGIN COMP1_2_3_IRQn 0 */
    /*
    * Insert USER code that reacts to an undershoot.
    */
    set_undershoot_bit_flag(0xFFFF); // Set flag.
    GPIOA->BSRR = (uint32_t)GPIO_PIN_4; // Debug.

    COMP_DETECT_BO_EXTI_CLF_MACRO(COMP_DETECT_BO_EXTI_LINE); // Clear IT flag.
    /* USER CODE END COMP1_2_3_IRQn 0 */
    /* USER CODE BEGIN COMP1_2_3_IRQn 1 */

    /* USER CODE END COMP1_2_3_IRQn 1 */
  }

4.6. Result

In the context of the case study, this solution is implemented to redo the temperature measurement if an undershoot is susceptible to have altered the value. In this scope trace, PA4 (violet) is set during measurement, if a transient occurs, this is done twice.

If an undershoot occur during measurement, it is done a second time.

5. ADC measurement average voltage

This solution is designed to obtain an average value of VDD to monitor its value and detect lack of precision or drift. This solution has limitations as it only detects slow variations with a certain delay due to the filter.

An analog windowed watchdog can be programmed to generate an interrupt if the sample is out of a specific window. This mechanism is an alternative to the comparator presented earlier; however, it is less reactive and cannot detect short transients.

5.1. Operation

Connect the measured voltage to the STM32 through a voltage divider. Internally, connect the signal to an operational amplifier in "follower internally connected" mode. Connect the output of the amplifier to the ADC input. The role of the amplifier is to provide the ADC with enough current to perform measurements without perturbing the input signal. A timer is used to automatically trigger ADC conversion periodically. At the end of the conversion, an interrupt is triggered that adds the last sample to the filter calculation.

Functional diagram of ADC measurement average voltage configuration

The functional diagram shows the connections between the elements.

5.2. ADC configuration

On the ADC, an injected channel is configured:

  • Number of Conversions: 1
  • External Trigger Source: Timer 4 Trigger Out event
  • Oversampling Ratio: 256
  • Channel: Vopamp1
  • ADC1 global interrupt enable

5.3. Timer configuration

Timer 4 is configured to trigger every millisecond; this period can be modified depending on the application requirements.

5.4. Interrupt routines ADC end of conversion

An interrupt is triggered at the end of each conversion. In this routine, the last sample is added to the filter calculation to update the average voltage value.

  /* USER CODE BEGIN 0 */
  /**
  * @brief Callback from Analog Injected channel measurement
  * @param hadc : ADC handle
  */
  void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc)
  {
    InjADC_Reading = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_1); // Read the Injected Channel Result
    vdd_rms += ((InjADC_Reading - vdd_rms) * coef_vdd_filter); // Update the global variable vdd_rms according to the filtered measured value
  }
  /* USER CODE END 0 */

5.5. Source code

  • In main.h, global defines, change to used peripherals.
  /* USER CODE BEGIN Private defines */
  #define CUT_FREQUENCY 10.0f // Cut-off frequency of the low pass filter that filters measured value to get an average value

  // Replace here with the used peripherals
  #define ADC_MONITOR ADC1 // ADC instance
  #define ADC_MONITOR_HANDLE hadc1 // ADC handle
  #define TIM_MONITOR TIM4 // TIM instance
  #define TIM_MONITOR_HANDLE htim4 // TIM handle
  #define OPAMP_MONITOR_HANDLE hopamp1 // OPAMP handle

  #define PI 3.14159265359f
  /* USER CODE END Private defines */
  • In main.c, global variables, flags, and buffers used to interact between the main function and interrupt routines.
  /* USER CODE BEGIN PV */
  volatile float vdd_rms = 22000f;        // V measured average value, filtered at FC = CUT_FREQUENCY, initialize to standard value
  volatile float InjADC_Reading = 0.0f; // V measured last sample
  volatile float coef_vdd_filter = 1.0f;  // Coefficient of the low pass filter that filters measured value to get an average value
  /* USER CODE END PV */
  • In main.c, function void HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef* hadc) void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc)
  /* USER CODE BEGIN 0 */
  /**
  * @brief Callback from Analog Injected channel measurement
  * @param hadc : ADC handle
  */
  void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc)
  {
    InjADC_Reading = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_1); // Read the Injected Channel Result
    vdd_rms += ((InjADC_Reading - vdd_rms) * coef_vdd_filter); // Update the global variable vdd_rms according to the filtered measured value
  }
  /* USER CODE END 0 */
  • In main.c, function int main(void); Start peripherals at setup. Then execute user code, in this case periodically the sensitive function.
  /**
    * @brief  The application entry point.
    * @retval int
    */
  int main(void)
  {
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_ADC1_Init();
    MX_OPAMP1_Init();
    MX_TIM4_Init();
    /* USER CODE BEGIN 2 */
   
    // Calculate the sample frequency, don't forget to update TIM_MONITOR_HANDLE to get right timer values
    float sample_frequency = SystemCoreClock / (float)((TIM_MONITOR_HANDLE.Init.Prescaler + 1) * (TIM_MONITOR_HANDLE.Init.Period + 1));

    // Calculate filter coefficient depending on the sample frequency and the cut-off frequency defined by the application
    coef_vdd_filter = (CUT_FREQUENCY * 2 * PI) / sample_frequency;
    if (coef_vdd_filter > 1.0f) coef_vdd_filter = 1.0f; // Ensure that the coefficient is strictly between 0 and 1
    if (coef_vdd_filter < 0.0f) coef_vdd_filter = 0.0f;

    HAL_OPAMP_Start(&OPAMP_MONITOR_HANDLE); // Start OPAMP
    HAL_TIM_Base_Start_IT(&TIM_MONITOR_HANDLE); // Start timer
    HAL_ADCEx_InjectedStart_IT(&ADC_MONITOR_HANDLE); // Start ADC injected channels

    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
      /* USER CODE END WHILE */

      /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
  }

5.6. Result

This solution is implemented to monitor VDD and detect slow variations. The following figures illustrate the effect of a 10 Hz cutoff frequency filter on sine waves of different frequencies:

10 Hz cutoff frequency filter effect on 3 Hz sine
10 Hz cutoff frequency filter effect on 10Hz sine
10 Hz cutoff frequency filter effect on 100 Hz sine

6. ADC measurement signal processing

This solution is designed to detect signatures of perturbations in the supply voltage. The idea is to process bursts of high sampling rate ADC measurements and extract data from these bursts. To limit the impact on the CPU load, this processing is not continuous.

6.1. Operation

Connect the measured voltage to the STM32 through a voltage divider. Internally, connect the signal to an operational amplifier in "follower internally connected" mode. Connect the output of the amplifier to the ADC input. The role of the amplifier is to provide the ADC with enough current to perform measurements without perturbing the input signal.

Functional diagram of ADC measurement signal processing configuration

The functional diagram shows the connections between the elements.

A DMA transfer of N samples is initiated to automatically proceed to a high-frequency measurement and store all data in a buffer. In this example, the buffer size is 1000 samples.

6.2. ADC configuration

On the ADC, a regular channel is configured:

  • Continuous Conversion Mode: Enabled
  • DMA Conversion Requests: Disabled
  • Number of Conversions: 1
  • External Trigger Source: Regular Conversion launched by software
  • Oversampling: Disabled
  • Channel: Vopamp1
  • Sampling Time: As low as possible, depending on the ADC frequency and impedance of the input signal

6.3. DMA configuration

A DMA transfer, peripheral to memory in circular mode, is configured to store data in memory.

6.4. Data processing

In the provided example, all the processing is done on a 1000-sample buffer at 1.6 mega samples per second. These parameters may vary depending on the application characteristics, nature of signals to analyze, and processing capabilities.

6.4.1. Best fit line

Calculate the coefficient and offset of the best fit line as f(x) = ax + b. The coefficient 'a' should be close to 0 for a stable power supply voltage. The term 'b' gives the average value of the signal, avg = a * (half of window size) + b. This helps detect drift or lack of accuracy in the power supply. If 'a' is close to 0, it ensures a significant average value; otherwise, it indicates significant variation larger than the window itself.

6.4.2. Distribution / Cumulative distribution

On one hand, the distribution graph shows the frequency of occurrence of each voltage level within the measured samples. On the other hand, the cumulative distribution graph shows the cumulative frequency of the voltage levels. These graphs help in understanding the behavior of the power supply and identifying any anomalies. Here are two examples of distributions. By characterizing the power supply distribution, it is possible to create a template that can be compared with the measured signal to detect anomalies.

Distribution of SMPS noise
Distribution of 50m Vpp sine wave

6.4.3. Standard deviation

Estimate the level of noise using the standard deviation at 1σ. To calculate it, generate the distribution graph and compute the standard deviation. The standard deviation provides a measure of the amount of variation or dispersion in the voltage levels, indicating the noise level in the power supply.

6.5. Source code

  • In main.h, global defines, change to used peripherals.
  /* USER CODE BEGIN Private defines */
  #define BURST_WIDTH 1000 // Size of measurement buffer

  #define VREF 3.3 // Reference voltage for ADC
  #define RES_ADC 4096 // 2^(number of bits of ADC): 2^12

  #define V_DC_TARGET 1.2 // Middle of analysis window in volts; mean value of signal at the ADC input
  #define CODE_DC_TARGET (V_DC_TARGET / (VREF / RES_ADC)) // Middle of analysis window in ADC LSB; mean value of signal at the ADC input

  #define V_AC_MAX 0.1 // Maximum amplitude of signal in volts, defines the size of the analysis window
  #define TABLE_SIZE (V_AC_MAX / (VREF / RES_ADC)) // Maximum amplitude of signal in ADC LSB, defines the size of the analysis window
  /* USER CODE END Private defines */
  • In main.c, global variables, flags, and buffers used to interact between the main function and interrupt routines.
  /* USER CODE BEGIN PV */
  const int Window_size = DISTRIBUTION_WINDOW_SIZE;

  const int SIG_1_LOW = (BURST_WIDTH - (BURST_WIDTH * 0.68)) / 2; // Low limit position 1 sigma (68%): 160 for 1000 samples
  const int SIG_1_HIGH = BURST_WIDTH - SIG_1_LOW; // High limit position 1 sigma (68%): 840 for 1000 samples
  const int MEDIAN_ID = BURST_WIDTH / 2; // Median position: 500 for 1000 samples

  const float SUM_1_BURST_WIDTH = (BURST_WIDTH * (BURST_WIDTH - 1)) / 2.0; // Precalculate constant for best fit line calculation
  const float SUM_1_BURST_WIDTH_SQR = ((BURST_WIDTH - 1) * BURST_WIDTH * (2 * (BURST_WIDTH - 1) + 1)) / 6.0; // Precalculate constant for best fit line calculation

  /**
  * Global variables
  */
  signal_data_t signal_instance; // Instance that contains all data measured and processed of the signal
  volatile int new_vdd_buffer = 0; // Flag: new sample available

  volatile uint16_t vdd_buffer[BURST_WIDTH]; // Store ADC samples from DMA
  volatile uint16_t distribution_buffer[(int)DISTRIBUTION_WINDOW_SIZE]; // Store sample distribution depending on level
  volatile uint16_t cumulative_distribution_buffer[(int)DISTRIBUTION_WINDOW_SIZE]; // Store sample cumulative distribution depending on level
  /* USER CODE END PV */
  • In main.c, function void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
  /* USER CODE BEGIN 0 */
  /**
  * @brief Callback from DMA complete transfer
  * @param hadc : ADC handle
  */
  void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
  {
    HAL_ADC_Stop_DMA(&ADC_MONITOR_HANDLE); // Stop ADC
    new_vdd_buffer = 1; // Raise flag to indicate a new buffer is ready for processing
  }
  /* USER CODE END 0 */
  • In main.c, function int main(void); Start peripherals at setup.
  /* USER CODE BEGIN 0 */
  /**
  * @brief Process measurements to analyze the signal
  * @param signal : Pointer to signal data structure
  */
  void process_measurements(signal_data_t* signal)
  {
    GPIOA->BSRR = (uint32_t)GPIO_PIN_4; // Debug; Set PA4 to measure routine duration

    /*
    * Calculate the best fit line as f(x) = ax + b
    * This gives the average value of the signal
    */
    for (int i = 0; i < BURST_WIDTH; i++)
    {
    //      sumX +=  i; // as x (i) is always 0 to BURST_WIDTH-1 sumX is precalculated in SUM_1_BURST_WIDTH
  //      sumX2 +=  i*i; // as x (i) is always 0 to BURST_WIDTH-1 sumX2 is precalculated in SUM_1_BURST_WIDTH_SQR      
      sumY += vdd_buffer[i];
      sumXY += i * vdd_buffer[i];
    }
    sumX = SUM_1_BURST_WIDTH; // Avoid unnecessary calculation
    sumX2 = SUM_1_BURST_WIDTH_SQR;

    /* Calculating a and b : f(x) = ax + b */
    signal->a = (BURST_WIDTH * sumXY - sumX * sumY) / (BURST_WIDTH * sumX2 - sumX * sumX);
    signal->b = (sumY - signal->a * sumX) / BURST_WIDTH;

    // Calculate the average value of the signal, the middle of the best fit line
    signal->average = signal->a * (BURST_WIDTH / 2) + signal->b;
    // Calculate the low limit of the window to center the window on the average
    signal->V_code_min = (signal->average - Window_size / 2);

    /*
    * Calculate distribution in the defined window:
    */
    for (int i = 0; i < Window_size; i++) // Reinitialize distribution table
      signal->distribution[i] = 0;

    for (int i = 0; i < BURST_WIDTH; i++) // Calculate distribution table
    {
      int16_t val = signal->buffer[i] - signal->V_code_min; // Remove offset to limit the range

      if (val < 0) // Verify that the value stays within the range
        val = 0;
      else if (val > Window_size)
        val = Window_size - 1;

      signal->distribution[val]++; // Increment the distribution
    }

    /*
    * Calculate the cumulative distribution in the defined window:
    */
    signal->cumulative_distribution[0] = signal->distribution[0]; // Set the first element of cumulative distribution
    for (int i = 1; i < Window_size; i++) // Calculate the cumulative distribution
      signal->cumulative_distribution[i] = signal->cumulative_distribution[i - 1] + signal->distribution[i]; // Add current step weight with cumulative weight of the previous step

    /*
    * Estimate standard deviation at 1 sigma, gives noise level
    */
    int index = 0;
    for (; signal->cumulative_distribution[index] < SIG_1_LOW; index++);
    int sig_1_low = index;
    for (; signal->cumulative_distribution[index] < MEDIAN_ID; index++);
    signal->median = index;
    for (; signal->cumulative_distribution[index] < SIG_1_HIGH; index++);
    int sig_1_high = index;
    signal->noise_level = sig_1_high - sig_1_low; // Calculate the noise level

    GPIOA->BRR = (uint32_t)GPIO_PIN_4; // Debug
  }
  /* USER CODE END 0 */
  • In main.c, function int main(void); Start peripherals at setup.
  /**
    * @brief  The application entry point.
    * @retval int
    */
  int main(void)
  {
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_ADC1_Init();
    MX_OPAMP1_Init();
    MX_TIM4_Init();
    MX_LPUART1_UART_Init();
    /* USER CODE BEGIN 2 */

    HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED); // Calibrate ADC

    HAL_OPAMP_Start(&OPAMP_MONITOR_HANDLE); // Start OPAMP

    /**
      * Initialize signal_instance, provide buffer pointers
      */
    signal_instance.buffer = vdd_buffer;
    signal_instance.distribution = distribution_buffer;
    signal_instance.cumulative_distribution = cumulative_distribution_buffer;

    signal_instance.V_code_min = (CODE_DC_TARGET - Window_size / 2);
    signal_instance.noise_level = 0; // Standard deviation at 1 sigma of the signal
    signal_instance.median = 0;
    signal_instance.a = 0; // Best fit line slope
    signal_instance.b = 0; // Best fit line intercept

    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    if (HAL_ADC_Start_DMA(&ADC_MONITOR_HANDLE, vdd_buffer, BURST_WIDTH) != HAL_OK) // Launch a measurement burst
    {
      Error_Handler();
    }
    while (1)
    {
      if (new_vdd_buffer) // If a new measurement is available
      {
        process_measurements(&signal_instance); // Process measurement
        new_vdd_buffer = 0; // Clear flag
       
        HAL_Delay(50); // In this example, a simple wait; place user code instead
        HAL_ADC_Start_DMA(&ADC_MONITOR_HANDLE, vdd_buffer, BURST_WIDTH); // Launch a new conversion burst
      }
      /* USER CODE END WHILE */

      /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
  }

6.6. Result

This solution processes bursts of high sampling rate ADC measurements to detect perturbations in the supply voltage. The processing includes calculating the best fit line, distribution, cumulative distribution, and standard deviation to analyze the signal and identify anomalies.

7. Conclusion

The reliability and stability of power supplies are crucial for the proper functioning of electronic applications. This document has presented elements to help building solutions for monitoring and detecting issues in power supply quality using STM32 embedded peripherals. By leveraging built-in components such as ADCs, comparators, operational amplifiers, and timers, these solutions could avoid the need for additional external components, thus simplifying the design and reducing costs.

It is important to note that the examples provided in this document are for illustrative purposes and may not be directly applicable to all applications. It is essential to thoroughly characterize your specific application and its power supply before implementing these solutions. Adapt the mechanisms and values to meet your unique requirements, considering factors such as voltage levels, sampling rates, and the nature of potential disturbances.

The offered solutions include:

  • Power loss detection: This solution could enable the quick detection of power source drops and initiate a power loss routine to prevent unexpected shutdowns and potential damage.
  • Overshoot and undershoot detection: It could monitor voltage levels to detect critical threshold breaches, measuring the duration and frequency of these events to ensure system robustness.
  • ADC measurement average voltage: This solution could provide an average value of VDD to monitor for slow variations and drifts, ensuring long-term stability.
  • ADC measurement signal processing: It could detect perturbation signatures in the supply voltage by processing bursts of high-frequency ADC measurements, extracting detailed information about voltage behavior and anomalies.

Each solution is designed to address specific aspects of power supply monitoring, from real-time detection of power loss to detailed analysis of voltage perturbations. By implementing these techniques, maintenance could be facilitated, failures anticipated, and overall system reliability significantly improved.