We have to build a low-power system that minimizes energy consumption by using the microcontroller’s power-saving features. The system must remain in power-saving mode until triggered by a user input (push button), at which point it wakes up, performs an ADC measurement, transmits the result, and returns to low-power mode.
So, by considering the above points, we can implement the task.
Below are the solutions to the given task using different microcontrollers
We’re using an STM32 NUCLEO-F103RB board.
The STM32F103RB (Cortex-M3) supports three low-power modes. Choosing the right mode depends on the required wake-up latency and system current budget.
In this task, STOP Mode is preferred because it saves significant power while still allowing wake-up on button press via EXTI (PA8). After waking, we reconfigure clocks and peripherals before ADC and UART operations.
Circuit Diagram
Project Setup in STM32CubeIDE
HAL_Init()
→ Initializes the HAL library.SystemClock_Config()
→ Configures system clock.MX_USART2_UART_Init()
→ Sets up USART2.MX_GPIO_Init()
→ Configures GPIO (e.g., PA8).MX_ADC1_Init()
→ Configures ADC1_IN0 (e.g., PA0).GPIO Initialization (Button Config on PA8)
Configures PA8 as an external interrupt input activated on falling edge (button press). Pull-up resistor ensures a stable idle high level.
static void MX_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin : PA8 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* Set EXTI IRQ for PA8 (EXTI8 → EXTI9_5_IRQn) */
HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0); // High priority (0 = highest)
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
/* USER CODE END MX_GPIO_Init_2 */
}
UART Initialization
Configures UART2 for reliable communication via a serial terminal. Settings are: 115200 baud, 8/N/1, no flow control.
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart2);
ADC Initialization
Configures ADC1 to perform a single conversion on channel 0, right-aligned result, triggered by software.
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
HAL_ADC_Init(&hadc1);
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
Header File Include
Includes standard C library stdio.h, mainly for sprintf(), which formats data (e.g., integer ADC readings) into strings before transmitting via UART.
#include <stdio.h>
Debounce Macro Definition
Defines a debounce delay in milliseconds to stabilize mechanical button inputs and prevent false multiple triggers on a single press.
#define DEBOUNCE_DELAY 50
Button Press Flag
A volatile flag variable updated inside the interrupt callback to signal that the user pressed the button connected to PA8.
volatile uint8_t isPressed = 0;
External Interrupt Handler (EXTI9_5 IRQ)
The vector table directs interrupts from EXTI lines [9:5] (here PA8) to this handler. Internally, it calls HAL’s EXTI handler.
void EXTI9_5_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
}
External Interrupt Callback
This is the HAL’s user callback invoked when EXTI is triggered.
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_8)
{
HAL_NVIC_DisableIRQ(EXTI9_5_IRQn); // temporarily disable interrupts (debounce)
isPressed = 1; // flag button press for main loop
}
}
Enter Sleep Mode Function
Implements STOP mode entry for low-power consumption, configures PA8 as wake-up source, and re-initializes clocks and peripherals after wake-up.
void enterSleepMode(void)
{
__HAL_RCC_AFIO_CLK_ENABLE();
HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_8);
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// Restore system after waking
SystemClock_Config();
MX_USART2_UART_Init();
MX_ADC1_Init();
HAL_Delay(DEBOUNCE_DELAY);
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
}
Main Firmware Logic with Explanation
int main(void) {
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_ADC1_Init();
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
while (1) {
// Put MCU into low-power sleep mode until an interrupt (button press) occurs
enterSleepMode();
// Check if button press event flag is set
if (isPressed) {
isPressed = 0; // Clear the button press flag
// --- ADC Measurement ---
uint16_t adcValue = 0;
HAL_ADC_Start(&hadc1); // Start ADC conversion
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) // Wait for ADC conversion with 10ms timeout
{
adcValue = HAL_ADC_GetValue(&hadc1); // Read the converted ADC value
}
HAL_ADC_Stop(&hadc1); // Stop ADC to save power
// --- Send ADC Value via UART ---
uint8_t msg[20];
uint16_t len = sprintf((char *)msg, "ADC value: %d\r\n", adcValue);
HAL_UART_Transmit(&huart2, msg, len, HAL_MAX_DELAY); // Transmit ADC value to serial terminal
// Small delay to ensure stable UART communication
HAL_Delay(100);
}
}
}
The complete STM32CubeIDE project (including .ioc configuration, main.c, and HAL files) is available here:
📥 Download Project
Power Saving Analysis
We are using the ESP32 DevKit v4 development board and programming it using the Arduino IDE.
ESP32 supports multiple sleep modes to save power. Deep sleep is ideal for this task because it consumes very little power and can wake the controller on an external signal.
Powe Mode | Description | Power Consumption |
Active (RF Working) | Wi-Fi and Bluetooth ON | 95-240 mA |
Modem Sleep | CPU ON | Max Speed: 20mA |
Normal: 5-10mA | ||
Slow: 3mA | ||
Light Sleep | - | 0.8 mA |
Deep Sleep | ULP ON | 150 uA |
RTC Timer+ RTC Memory | 10 uA | |
Hibernation | RTC timer only | 5 uA |
Power Off | - | 0.1 uA |
Note: Deep sleep provides maximum power saving while allowing wake-up via an external signal, making it perfect for this task.
In ESP32 deep sleep mode:
RTC-capable pins are required for deep sleep wake-up because they stay active while the ESP32 CPU is powered down. So in the given task, we use GPIO pin 33 to connect the push button switch.
Note: Only GPIOs 0, 2, 4, 12–15, 25–27, 32–39 are RTC-capable on ESP32.
Code
#include <esp_sleep.h>
#define WAKEUP_PIN 33 // RTC-capable GPIO for wake-up
#define ADC_PIN 34 // GPIO34 (ADC1 channel 6), input-only, ideal for ADC
bool isPressed = false;
void setup() {
Serial.begin(115200);
delay(100); // Allow time for Serial connection
// Determine wakeup reason
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT0) {
isPressed = true;
}
if (isPressed) {
int adcValue = analogRead(ADC_PIN);
Serial.print("ADC value: ");
Serial.println(adcValue);
delay(10);
isPressed = false;
}
// Configure wakeup with internal pull-up
pinMode(WAKEUP_PIN, INPUT_PULLUP); // Ensure button connects to GND when pressed
esp_sleep_enable_ext0_wakeup((gpio_num_t)WAKEUP_PIN, 0); // Wake on LOW level
// Optional debounce delay
delay(50);
// Enter deep sleep
Serial.println("Entering deep sleep...");
Serial.flush();
esp_deep_sleep_start();
}
void loop() {
// Empty - ESP32 never reaches loop() after deep sleep, code restarts at setup()
}
Logic of the Code
Some Important Functions to Understand
esp_sleep_enable_ext0_wakeup(pin, level);
Configures a GPIO pin as an external wake-up source. Wakes on HIGH or LOW level.esp_sleep_get_wakeup_cause();
Returns the reason ESP32 woke up (e.g., external GPIO, timer, etc.).esp_deep_sleep_start();
Puts ESP32 into deep sleep mode. Code execution stops and resumes at setup() after wake-up.
Note: The analysis was performed using a USB voltage/current meter. Due to the additional peripherals present on the development board, the measured values are higher than the theoretical power consumption of the ESP32 chip alone.
We are using the Arduino UNO development board and programming it using the Arduino IDE.
From the task, we understand that,
The Arduino UNO (ATmega328P) supports six sleep modes, each offering different power-saving levels:
For best power efficiency, use Power-down Mode with external interrupts for wake-up.
Here is a comparison of all sleep modes available in the Arduino UNO (ATmega328P):
Circuit Connection
Before writing the firmware, let’s break down the key functionalities:
SLEEP_MODE_PWR_DOWN
is the lowest power mode, turning off everything except external interrupts.sleep_bod_disable()
function disables the Brown-Out Detector (BOD), further reducing power consumption.#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#define INT0_PIN 2 // External Interrupt (D2)
#define ADC_PIN A0 // ADC Input Pin
#define DEBOUNCE_DELAY 50
bool isPressed = false;
void wakeUpISR() {
// Empty ISR, just needed to wake the microcontroller
}
void setup() {
pinMode(13, OUTPUT);
digitalWrite(13, LOW); //Turning off the on board LED for power saving
pinMode(INT0_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(INT0_PIN), wakeUpISR, FALLING);
}
void enterSleepMode() {
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode
cli(); // Disable interrupts to avoid race conditions
sleep_enable();
sleep_bod_disable(); // Disable Brown-Out Detector for lower power
sei(); // Re-enable interrupts
sleep_mode(); // Enter sleep mode (Execution stops here)
// Code resumes here after wake-up
sleep_disable();
delay(DEBOUNCE_DELAY);
if (digitalRead(2) == LOW)
isPressed = true;
}
void loop() {
enterSleepMode(); // Sleep until button press
if (isPressed) {
Serial.begin(115200); // Restore Serial after wake-up
Serial.print("ADC value: ");
Serial.println(analogRead(ADC_PIN)); // Print ADC value after waking up
delay(10); // Allow time for stability
isPressed = false;
}
}
Setup Function (setup()
):
INPUT_PULLUP
.INT0
) to wakeUpISR()
with a FALLING
edge trigger on Pin D2.Sleep Mode (enterSleepMode()
):
cli()
.sei()
and enter sleep using sleep_mode()
.Debouncing Handling:
delay(DEBOUNCE_DELAY)
: Handles button debounce.digitalRead(2) == LOW
: Checks if button is pressed.isPressed = true
: Flags a valid button press.Loop Function (loop()):
enterSleepMode()
to enter low-power state.Power Saving Analysis
Hardware Setup
Serial Monitor Output