Entrar en modo de parada en STM32L011 solo funciona la primera vez

Aquí tengo una aplicación muy típica: largos períodos de consumo mínimo de corriente (15 minutos a 24 horas de duración del sueño) con cortos períodos de actividad en el medio. En mi caso particular, esto es principalmente manipulación de ADC y GPIO (lo que explica parte de la inicialización adicional en mi código que no se usa), pero para este ejemplo, solo estoy tratando de lograr lo siguiente:

  1. Encender LED
  2. Delay 1000ms (usando HAL delay - no se preocupe por la eficiencia aquí)
  3. Apague el LED
  4. Entrar en modo de parada
  5. Haga que RTC Wakeup Timer me devuelva al paso 1. después de 5 segundos

Hasta ahora, esto funciona bien para el primer bucle: veo que el LED se enciende, se apaga después de 1000 ms, permanece apagado durante 5 segundos (el consumo de corriente cae a aproximadamente 2 uA) y luego se vuelve a encender. Sin embargo, el LED nunca se apaga de nuevo. No puedo usar SWD para depurar esto, ya que parece que la placa nunca ingresa al modo de parada con la depuración de SWD activa.

Mi código (parcialmente generado por STM32CubeMX y parcialmente a partir de la lectura de documentos) es el siguiente:

#include "main.h"
#include "stm32l0xx_hal.h" // specific target MCU is STM32L011D3P6

ADC_HandleTypeDef hadc;

RTC_HandleTypeDef hrtc;

void SystemClock_Config(void);
void Error_Handler(void);
static void MX_GPIO_Init(void);
static void MX_ADC_Init(void);
static void MX_RTC_Init(void);

void SystemPower_Config(void);

int main(void)
{
  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();
  MX_ADC_Init();
  MX_RTC_Init();

  SystemPower_Config();

  #ifdef DEBUG
    HAL_DBGMCU_EnableDBGStopMode();
  #else
    HAL_DBGMCU_DisableDBGStopMode();
  #endif

  while (1)
  {
    HAL_GPIO_WritePin(LED_1_GPIO_Port, LED_1_Pin, GPIO_PIN_SET);
    HAL_Delay(1000);
    HAL_GPIO_WritePin(LED_1_GPIO_Port, LED_1_Pin, GPIO_PIN_RESET);

    HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);

    if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 5, RTC_WAKEUPCLOCK_CK_SPRE_16BITS) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_RCC_PWR_CLK_ENABLE();
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

  }  
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
  RCC_PeriphCLKInitTypeDef PeriphClkInit;

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSI
                              |RCC_OSCILLATORTYPE_MSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_DIV4;
  RCC_OscInitStruct.HSICalibrationValue = 16;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.MSIState = RCC_MSI_ON;
  RCC_OscInitStruct.MSICalibrationValue = 0;
  RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_4;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }

  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC;
  PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

static void MX_ADC_Init(void)
{

  ADC_ChannelConfTypeDef sConfig;

  hadc.Instance = ADC1;
  hadc.Init.OversamplingMode = DISABLE;
  hadc.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV16;
  hadc.Init.Resolution = ADC_RESOLUTION_12B;
  hadc.Init.SamplingTime = ADC_SAMPLETIME_160CYCLES_5;
  hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
  hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc.Init.ContinuousConvMode = DISABLE;
  hadc.Init.DiscontinuousConvMode = DISABLE;
  hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc.Init.DMAContinuousRequests = DISABLE;
  hadc.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc.Init.LowPowerAutoWait = DISABLE;
  hadc.Init.LowPowerFrequencyMode = ENABLE;
  hadc.Init.LowPowerAutoPowerOff = ENABLE;
  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_4;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

}

static void MX_RTC_Init(void)
{

  RTC_TimeTypeDef sTime;
  RTC_DateTypeDef sDate;

  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 127;
  hrtc.Init.SynchPrediv = 255;
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  sTime.Hours = 0x0;
  sTime.Minutes = 0x0;
  sTime.Seconds = 0x0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }

  sDate.WeekDay = RTC_WEEKDAY_MONDAY;
  sDate.Month = RTC_MONTH_JANUARY;
  sDate.Date = 0x1;
  sDate.Year = 0x0;

  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  HAL_GPIO_WritePin(SENSOR_EN_GPIO_Port, SENSOR_EN_Pin, GPIO_PIN_RESET);

  HAL_GPIO_WritePin(GPIOA, LED_2_Pin|LED_1_Pin|LED_0_Pin, GPIO_PIN_RESET);

  GPIO_InitStruct.Pin = SELF_TEST_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(SELF_TEST_GPIO_Port, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = SENSOR_EN_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(SENSOR_EN_GPIO_Port, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = LED_2_Pin|LED_1_Pin|LED_0_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

}


void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc){
  __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
}

void SystemPower_Config(void)
{
  __HAL_RCC_PWR_CLK_ENABLE();
  HAL_PWREx_EnableUltraLowPower();
}

void Error_Handler(void)
{
  while(1) 
  {
  } 
}

Main.h simplemente asigna pines GPIO a los nombres utilizados en el código; feliz de publicarlo si es útil. ¿Alguien puede señalar dónde me estoy equivocando aquí? ¿O sugerir un enfoque de depuración útil? (dado que, lamentablemente, no puedo registrarme mucho con SWD, lo que arruina mi capacidad para entrar en modo de suspensión)

Una pregunta difícil no puede dar mucha ayuda. ¿Puede alternar otro LED en la devolución de llamada RTC? Y en lugar del LED encendido, 1 segundo de retraso, LED apagado; haga que el LED parpadee un par de veces antes de entrar en el modo STOP. Tal vez la MCU se despierte de inmediato después de ingresar a STOP para que no pueda detectar el estado de LED apagado, no lo sé.
Hola @BenceKaulics, gracias por tus pensamientos. Tomé una ruta ligeramente diferente: usando un amplificador de derivación de corriente y un osciloscopio, verifiqué el consumo de corriente a lo largo del tiempo. Es muy fácil ver cuándo el dispositivo ingresa al modo de parada de esta manera (y se activa desde esto). Puedo ver en el seguimiento del alcance que el dispositivo solo ingresa al modo de parada una vez, es decir, no ingresa una segunda vez y luego regresa inmediatamente.
¿Alguna posible falla grave, interrupción no controlada?
No lo creo, si agrego un parpadeo más corto después de la llamada HAL_PWR_EnterSTOPMode(), veo eso, y luego solo veo un parpadeo largo continuo, un ciclo de parpadeo corto, lo que implica que la ejecución del código continúa y el bucle while (1) en main() se está ejecutando, solo que HAL_PWR_EnterSTOPMode() solo es efectivo la primera vez.
Tuve un problema similar solo con el modo STANDBY y cuando olvidé borrar este indicador PWR_FLAG_WU, estaba en un núcleo M0. No es el caso aquí. Compruebe si hay algunas banderas adicionales en el caso de M0+.

Respuestas (1)

La respuesta se identificó en el foro STM32 : ¡días felices!

Estaba fallando al implementar y habilitar la interrupción RTC correctamente. Esto requirió los siguientes pasos:

  1. Agregue stm32l0xx_hal_msp.clo siguiente a la HAL_RTC_MspInit(RTC_HandleTypeDef* hrtc)función:

    HAL_NVIC_SetPriority(RTC_IRQn, 0x0, 0);
    
    HAL_NVIC_EnableIRQ(RTC_IRQn);
    
  2. Además, stm32l0xx_it.cagregue las siguientes líneas:

    extern RTC_HandleTypeDef hrtc;
    
    void RTC_IRQHandler(void)
    {
      HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);
    }
    

Sin esto, las banderas que se configuran después de que se dispara la interrupción nunca se borran, por lo que funciona la primera vez, y no después. Gracias a @Bence Kaulics por su ayuda en esto también.