We need to make five LEDs fade in opposite phases (some brighten while others dim), with the fade speed controlled by a potentiometer.
So we have to interface a potentiometer and five LEDs with a microcontroller.
Potentiometer Interfacing
Note: We can use any of the potentiometers with values between 1kΩ and 10kΩ.
Five LEDs Interfacing
Connect each PWM GPIO pin to an anode of a separate LED, and the cathode of each LED to GND with a current-limiting resistor in series to protect LEDs and GPIO pins.
Case 1: 5V Supply
Standard resistor values near 320 Ω: 330 Ω or 300 Ω (whichever is available).
Similarly, Case 2: 3.3V Supply
Standard resistor value: 150 Ω.
So, by selecting a proper resistor, LED, and potentiometer connections, we can implement the task.
Below are the solutions to the given task using different microcontrollers
We’re using an STM32 NUCLEO-F103RB board, which runs at a 3.3V logic level.
Circuit Diagram
Project Setup in STM32CubeIDE:
SystemClock_Config
).HAL_Init()
→ Initializes the HAL library.SystemClock_Config()
→ Configures HSI + PLL system clock.MX_GPIO_Init()
→ Initializes all GPIO ports.MX_ADC1_Init()
→ Configures ADC1 for analog input.MX_TIM1_Init()
→ Configures TIM1 for PWM output.MX_TIM3_Init()
→ Configures TIM3 for PWM output.MX_TIM4_Init()
→ Configures TIM4 as basic timerMX_USART2_UART_Init()
→ Initializes UART2 for debugging.ADC Initialization (MX_ADC1_Init):
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
This configures ADC1 for single-channel operation with right-aligned 12-bit results.
Timer Initialization (MX_TIM1_Init, MX_TIM3_Init, and MX_TIM4_Init):
TIM1 and TIm3
htim1.Init.Period = 255; // ARR register value
htim1.Init.Prescaler = 0; // No prescaling
sConfigOC.OCMode = TIM_OCMODE_PWM1; // PWM Mode 1
//Similar configuration is applied for TIM3 as well.
These functions configure timers for PWM generation.
TIM4
htim4.Instance = TIM4;
htim4.Init.Prescaler = 100;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 8000;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
These functions configure TIM4 as a basic up-counter with an internal clock, prescaler 100, period 8000, and update interrupt for periodic ADC triggering and PWM fading control.
Macros Definition:
The provided code defines timer handles and channel mappings for cleaner code organization:
// Define timer handles and channels for each LED
#define LED0_TIMER &htim3
#define LED0_TIMER_CHANNEL TIM_CHANNEL_1
#define LED1_TIMER &htim3
#define LED1_TIMER_CHANNEL TIM_CHANNEL_2
#define LED2_TIMER &htim1
#define LED2_TIMER_CHANNEL TIM_CHANNEL_2
#define LED3_TIMER &htim1
#define LED3_TIMER_CHANNEL TIM_CHANNEL_1
#define LED4_TIMER &htim1
#define LED4_TIMER_CHANNEL TIM_CHANNEL_3
// ADC value range (12-bit ADC)
#define ADC_MIN_VALUE 0
#define ADC_MAX_VALUE 4095
// PWM duty cycle range
#define PWM_MIN_DUTY_CYCLE 0
#define PWM_MAX_DUTY_CYCLE 255
Custom Mapping Function:
The mapValue()
function provides linear interpolation between ranges:
uint16_t mapValue(uint16_t x, uint16_t inMin, uint16_t inMax,
uint16_t outMin, uint16_t outMax) {
return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}
TIM4 interrupt handling (HAL_TIM_PeriodElapsedCallback
)
// Period elapsed callback
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM4) {
// Start ADC conversion and wait for the result
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 20);
g_adcValue = HAL_ADC_GetValue(&hadc1);
// Map ADC value to PWM range for LED1 brightness
g_fadeDelay = mapValue(g_adcValue, ADC_MIN_VALUE, ADC_MAX_VALUE, 1, 15);
}
}
This function executes whenever timer TIM4 overflows, triggering an interrupt. It reads the current potentiometer value via the ADC, then maps this reading to a delay value (g_fadeDelay
) between 1 and 15 milliseconds, which adjusts the LED fading speed dynamically.
PWM Initialization in main():
Before the while loop, PWM channels are enabled:
HAL_TIM_PWM_Start(LED0_TIMER, LED0_TIMER_CHANNEL);
HAL_TIM_PWM_Start(LED1_TIMER, LED1_TIMER_CHANNEL);
// Start remaining PWM channels…
Main Control Loop:
while (1) {
// Fade in all odd-numbered LEDs while fading out even-numbered LEDs
for (int i = PWM_MIN_DUTY_CYCLE; i <= PWM_MAX_DUTY_CYCLE; i += 1) {
// Set PWM duty cycle for each LED
// LED0, LED2, LED4 fade in (increasing brightness)
__HAL_TIM_SET_COMPARE(LED0_TIMER, LED0_TIMER_CHANNEL, i);
__HAL_TIM_SET_COMPARE(LED2_TIMER, LED2_TIMER_CHANNEL, i);
__HAL_TIM_SET_COMPARE(LED4_TIMER, LED4_TIMER_CHANNEL, i);
// LED1 and LED3 fade out (decreasing brightness)
__HAL_TIM_SET_COMPARE(LED1_TIMER, LED1_TIMER_CHANNEL, PWM_MAX_DUTY_CYCLE - i);
__HAL_TIM_SET_COMPARE(LED3_TIMER, LED3_TIMER_CHANNEL, PWM_MAX_DUTY_CYCLE - i);
// Delay based on ADC reading (controls fade speed)
HAL_Delay(g_fadeDelay);
}
// Pause for some time
HAL_Delay(10 * g_fadeDelay);
// Fade out all odd-numbered LEDs while fading in even-numbered LEDs
for (int i = PWM_MAX_DUTY_CYCLE; i >= PWM_MIN_DUTY_CYCLE; i -= 1) {
// Set PWM duty cycle for each LED
// LED0, LED2, LED4 fade out (decreasing brightness)
__HAL_TIM_SET_COMPARE(LED0_TIMER, LED0_TIMER_CHANNEL, i);
__HAL_TIM_SET_COMPARE(LED2_TIMER, LED2_TIMER_CHANNEL, i);
__HAL_TIM_SET_COMPARE(LED4_TIMER, LED4_TIMER_CHANNEL, i);
// LED1 and LED3 fade in (increasing brightness)
__HAL_TIM_SET_COMPARE(LED1_TIMER, LED1_TIMER_CHANNEL, PWM_MAX_DUTY_CYCLE - i);
__HAL_TIM_SET_COMPARE(LED3_TIMER, LED3_TIMER_CHANNEL, PWM_MAX_DUTY_CYCLE - i);
// Delay based on ADC reading (controls fade speed)
HAL_Delay(g_fadeDelay);
}
// Pause for some time
HAL_Delay(10 * g_fadeDelay);
}
LED Control Logic Explanation:
The complete STM32CubeIDE project (including .ioc configuration, main.c, and HAL files) is available here:
We are using the ESP32 DevKit v4 development board and programming it using the Arduino IDE.
LEDC (LED Controller) peripheral is a dedicated hardware PWM controller, optimized for applications like LED brightness control using PWM, and we will use it in this task.
In Arduino Core v2.x or below for ESP32, LEDC API functions like ledcSetup()
and ledcAttachPin()
are used for PWM configuration.
In Arduino Core v3.x, these functions are removed to avoid compilation errors; use the updated LEDC API instead; otherwise, you will encounter a compilation error. e.g.:
ledcAttach(pin, freq, resolution);
ledcWrite(channel, dutyCycle);
Reference: ESP32 Arduino Core 2.x → 3.0 Migration Guide
Circuit Diagram
After analyzing the task as shown in the GIF.
We observed the following points:
LED Grouping:
Fade Timing:
// Hardware Configuration
#define POT_PIN 34 // Potentiometer connected to GPIO15 (ADC input pin)
#define ADC_RESOLUTION 12U // ADC resolution = 12-bit → values from 0 to 4095
#define PWM_RESOLUTION 12U // PWM resolution = 12-bit → duty cycle 0–4095
#define ADC_MAX_VALUE 4095U // Maximum ADC value for 12-bit
#define PWM_MAX_VALUE 4095U // Maximum PWM value for 12-bit
#define MAX_DELAY 1000U // Maximum fade delay (slower fading)
#define MIN_DELAY 10U // Minimum fade delay (faster fading)
const int led_pins[] = { 15, 16, 17, 18, 19 }; // Array of LED pins (must support PWM)
uint16_t potValue = 0; // Variable to store potentiometer reading
// Setup Function
void setup() {
// Attach PWM functionality to each LED pin
// Frequency = 1 kHz, Resolution = 12-bit
for (uint8_t i = 0; i < 5; i++) {
ledcAttach(led_pins[i], 1000, PWM_RESOLUTION);
ledcWrite(led_pins[i], 0); // Start with LEDs OFF
}
// Configure ADC resolution for potentiometer input
analogReadResolution(ADC_RESOLUTION);
// Initialize serial monitor for debugging
Serial.begin(115200);
}
// Loop Function
void loop() {
// 1. Read potentiometer value (0–4095)
potValue = analogRead(POT_PIN);
// 2. Map potentiometer value to delay (10–1000 µs)
// This controls how FAST or SLOW the LEDs fade
uint16_t fadeDelay = map(potValue, 0, ADC_MAX_VALUE, MIN_DELAY, MAX_DELAY);
Serial.println(fadeDelay); // Debugging: print fade speed
// 3. FADE IN sequence (LEDs gradually increase brightness)
for (uint16_t brightness = 0; brightness <= PWM_MAX_VALUE; brightness++) {
// LED behavior:
ledcWrite(led_pins[0], brightness); // LED 0: fade in
ledcWrite(led_pins[1], PWM_MAX_VALUE - brightness); // LED 1: fade out
ledcWrite(led_pins[2], brightness); // LED 2: fade in
ledcWrite(led_pins[3], PWM_MAX_VALUE - brightness); // LED 3: fade out
ledcWrite(led_pins[4], brightness); // LED 4: fade in
// Apply delay based on potentiometer position
delayMicroseconds(fadeDelay);
}
// Hold LEDs at max brightness before fading out
delayMicroseconds(1500 * fadeDelay);
// 4. FADE OUT sequence (LEDs gradually decrease brightness)
for (uint16_t brightness = PWM_MAX_VALUE; brightness > 0; brightness--) {
// LED behavior:
ledcWrite(led_pins[0], brightness); // LED 0: fade out
ledcWrite(led_pins[1], PWM_MAX_VALUE - brightness); // LED 1: fade in
ledcWrite(led_pins[2], brightness); // LED 2: fade out
ledcWrite(led_pins[3], PWM_MAX_VALUE - brightness); // LED 3: fade in
ledcWrite(led_pins[4], brightness); // LED 4: fade out
// Apply delay based on potentiometer position
delayMicroseconds(fadeDelay);
}
// Hold LEDs at minimum brightness before next cycle
delayMicroseconds(1500 * fadeDelay);
}
ledcWrite()
in a loop.We are using the Arduino UNO development board and programming it using the Arduino IDE.
The LEDs are connected to an Arduino UNO
In Arduino UNO, the PWM signal is generated using the analogWrite()
.
It has 6 PWM pins (3, 5, 6, 9, 10, 11) with 8-bit resolution (0–255) and fixed frequencies of ~490 Hz or ~976 Hz.
After analyzing the task as shown in the GIF.
We observed the following points:
LED Grouping:
Fade Timing:
const int ledPins[] = { 11, 10, 9, 6, 3 }; // PWM pins for LEDs
int potValue = 0; // Variable to store potentiometer reading
void setup() {
// Set all LED pins as output
for (int i = 0; i < 5; i++) {
pinMode(ledPins[i], OUTPUT); // Configuring LED pins for output
}
}
void loop() {
// Read the potentiometer value from A0 (range: 0–1023)
potValue = analogRead(A0);
// Map potentiometer value (0–1023) to delay range (1–10 ms)
// This controls how fast the LEDs fade in and out
int fadeDelay = map(potValue, 0, 1023, 1, 10);
// Gradually increase brightness (fade in)
for (int brightness = 0; brightness <= 255; brightness++) {
// Adjust the brightness of each LED:
analogWrite(ledPins[0], brightness); // LED 0: fade in (brightness increases)
analogWrite(ledPins[1], 255 - brightness); // LED 1: fade out (brightness decreases)
analogWrite(ledPins[2], brightness); // LED 2: fade in (brightness increases)
analogWrite(ledPins[3], 255 - brightness); // LED 3: fade out (brightness decreases)
analogWrite(ledPins[4], brightness); // LED 4: fade in (brightness increases)
delay(fadeDelay); // Add delay to control the speed of fading
}
// Add a delay after the fade-in sequence for visibility
delay(100 * fadeDelay);
// Gradually decrease brightness (fade out)
for (int brightness = 255; brightness >= 0; brightness--) {
// Adjust the brightness of each LED:
analogWrite(ledPins[0], brightness); // LED 0: fade out (brightness decreases)
analogWrite(ledPins[1], 255 - brightness); // LED 1: fade in (brightness increases)
analogWrite(ledPins[2], brightness); // LED 2: fade out (brightness decreases)
analogWrite(ledPins[3], 255 - brightness); // LED 3: fade in (brightness increases)
analogWrite(ledPins[4], brightness); // LED 4: fade out (brightness decreases)
delay(fadeDelay); // Add delay to control the speed of fading
}
// Add a delay after the fade-out sequence for visibility
delay(100 * fadeDelay);
}
analogWrite()
in a loop.