After analyzing the task,
We need to connect five LEDs and a potentiometer to the microcontroller. The idea is to make their brightness change based on the potentiometer’s position so that it creates a seesaw effect.
How will the LED brightness adjust as per the potentiometer?
The potentiometer value (0%–100%) is mapped to PWM duty cycles of each LED so that each LED follows the required brightness pattern to create a seesaw effect:
LED brightness for the full range of potentiometer(0% → 100%):
We’ll use PWM to control each LED’s brightness, and the ADC value from the potentiometer will be mapped to the duty cycle for each LED.
For example, consider a 10-bit PWM resolution (0–1023) and the potentiometer spanning the same range (0–1023):
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_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 and MX_TIM3_Init):
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.
Macros Definition:
The provided code defines timer handles and channel mappings for cleaner code organization:
#define LED0_TIM_HANDLE &htim3
#define LED0_TIM_CHANNEL TIM_CHANNEL_1
// Additional LED mappings...
// ADC value range
#define ADC_MIN_VALUE 0
#define ADC_MAX_VALUE 4095
// PWM duty cycle range
#define PWM_MIN_VALUE 0
#define PWM_MAX_VALUE 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;
}
PWM Initialization in main():
Before the while loop, PWM channels are enabled:
HAL_TIM_PWM_Start(LED0_TIM_HANDLE, LED0_TIM_CHANNEL);
HAL_TIM_PWM_Start(LED1_TIM_HANDLE, LED1_TIM_CHANNEL);
// Start remaining PWM channels…
Main Control Loop:
The infinite loop continuously reads the ADC and updates LED brightness:
while (1) {
// Start ADC conversion and wait for the result
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 20);
adcValue = HAL_ADC_GetValue(&hadc1);
// Map the potentiometer value to brightness levels for each LED
uint16_t brightnessLed0 = mapValue(adcValue, ADC_MIN_VALUE, ADC_MAX_VALUE,
PWM_MIN_VALUE, PWM_MAX_VALUE);
uint16_t brightnessLed1 = mapValue(adcValue, ADC_MIN_VALUE, ADC_MAX_VALUE, 10, 130);
uint16_t brightnessLed3 = mapValue(adcValue, ADC_MIN_VALUE, ADC_MAX_VALUE, 130, 10);
uint16_t brightnessLed4 = mapValue(adcValue, ADC_MIN_VALUE, ADC_MAX_VALUE,
PWM_MAX_VALUE, PWM_MIN_VALUE);
uint16_t brightnessLed2; // LED 2: Dynamic range with midpoint behavior
// Calculate brightness for the middle LED (LED 2)
if (adcValue < (ADC_MAX_VALUE / 2) + 1) {
// If the potentiometer is below the midpoint, brightness increases
brightnessLed2 = mapValue(adcValue, ADC_MIN_VALUE, ADC_MAX_VALUE / 2, 40, 60);
} else {
// If the potentiometer is above the midpoint, brightness decreases
brightnessLed2 = mapValue(adcValue, (ADC_MAX_VALUE / 2) + 1,
ADC_MAX_VALUE, 60, 40);
}
// Apply calculated brightness to each LED
__HAL_TIM_SET_COMPARE(LED0_TIM_HANDLE, LED0_TIM_CHANNEL, brightnessLed0);
__HAL_TIM_SET_COMPARE(LED1_TIM_HANDLE, LED1_TIM_CHANNEL, brightnessLed1);
__HAL_TIM_SET_COMPARE(LED2_TIM_HANDLE, LED2_TIM_CHANNEL, brightnessLed2);
__HAL_TIM_SET_COMPARE(LED3_TIM_HANDLE, LED3_TIM_CHANNEL, brightnessLed3);
__HAL_TIM_SET_COMPARE(LED4_TIM_HANDLE, LED4_TIM_CHANNEL, brightnessLed4);
HAL_Delay(1); // Short delay for smooth transitions
}
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.
PWM Options on ESP32
Among these, the LEDC (LED Controller) peripheral is a dedicated hardware PWM controller, optimized for applications like LED brightness control, 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
Mapping ADC to PWM:
The ESP32’s 12-bit ADC converts the potentiometer’s position into digital values ranging from 0 (minimum) to 4095 (maximum).
To control the LED brightness using PWM (which ranges from 0 to 4095) in the given code, the ADC values are converted to a PWM value using the map() function.
Mapping value linearly for PWM:
Since LED brightness didn’t shift as expected with the above approach, we adjusted the mapping for smoother, more visible effects.
(Note: Brightness levels/ intensity behavior of LEDs vary based on specifications; different LEDs have different behaviors. The LEDs we used have similar brightness levels visible for PWM values in the upper range (e.g.,> 3500 PWM value)
Mapping value more dynamically to adjust for Better Visibility:
Below is the code with both mapping (linear and dynamic).
#define POT_PIN 34
const uint8_t led_pins[] = { 15, 16, 17, 18, 19 }; // PWM-capable pins for LEDs
void setup() {
// Attach LEDs to PWM channels with 1 kHz frequency and 12-bit resolution (0–4095)
for (uint8_t i = 0; i < 5; i++) {
ledcAttach(led_pins[i], 1000, 12); // pin, freq, resolution
ledcWrite(led_pins[i], 0); // Start with LEDs OFF
}
analogReadResolution(12); // Set ADC to 12-bit (range: 0–4095)
}
void loop() {
// Read the potentiometer value (0–4095) from Pin 34
uint16_t potValue = analogRead(POT_PIN);
// Update LED brightness levels based on potentiometer value
setLEDBrightness(potValue);
}
// Function to calculate and set LED brightness levels from potentiometer input
void setLEDBrightness(uint16_t potValue) {
/**
* Brightness mapping (0–4095 duty cycle):
* - LED0: Directly follows potentiometer (0 → 100% brightness)
* - LED1: Increases gradually (25% → 75% brightness)
* - LED2: Fixed at 50% brightness
* - LED3: Decreases gradually (75% → 25% brightness, inverse of LED1)
* - LED4: Inverse of LED0 (100% → 0% brightness)
*/
uint16_t brightnessLed0 = potValue;
uint16_t brightnessLed1 = map(potValue, 0, 4095, 1024, 3071);
uint16_t brightnessLed2 = 2048;
uint16_t brightnessLed3 = map(potValue, 0, 4095, 3071, 1024);
uint16_t brightnessLed4 = 4095 - potValue;
// Apply calculated brightness values to LEDs
ledcWrite(led_pins[0], brightnessLed0);
ledcWrite(led_pins[1], brightnessLed1);
ledcWrite(led_pins[2], brightnessLed2);
ledcWrite(led_pins[3], brightnessLed3);
ledcWrite(led_pins[4], brightnessLed4);
delay(1); // Short delay for smoother LED transitions
}
#define POT_PIN 34
#define ADC_RESOLUTION 12
#define PWM_RESOLUTION 12
#define ADC_MAX_VALUE 4095
#define PWM_MAX_VALUE 4095
const int led_pins[] = { 15, 16, 17, 18, 19 }; // PWM-capable LED pins
uint16_t potValue = 0;
void setup() {
for (uint8_t i = 0; i < 5; i++) {
// Attach PWM to pin with 1kHz frequency and 12-bit resolution
ledcAttach(led_pins[i], 1000, PWM_RESOLUTION);
ledcWrite(led_pins[i], 0); // Start OFF
}
analogReadResolution(ADC_RESOLUTION); // Set ADC to 12-bit (0–4095)
Serial.begin(115200);
}
void loop() {
potValue = analogRead(POT_PIN); // Read potentiometer value
setLEDBrightness(potValue); // Update LED brightness levels
delay(10); // Small delay for smooth transitions
}
void setLEDBrightness(int potValue) {
uint16_t brightnessLed0 = potValue; // LED0 directly follows potentiometer value (0–100%)
// LED1 brightness increases gradually from 4% → 90% as potValue goes 0 → 4095
uint16_t brightnessLed1 = map(potValue, 0, ADC_MAX_VALUE, PWM_MAX_VALUE * 0.04, PWM_MAX_VALUE * 0.90);
// LED3 brightness decreases gradually from 90% → 4% as potValue goes 0 → 4095
uint16_t brightnessLed3 = map(potValue, 0, ADC_MAX_VALUE, PWM_MAX_VALUE * 0.90, PWM_MAX_VALUE * 0.04);
uint16_t brightnessLed4 = ADC_MAX_VALUE - potValue; // LED4 inversely follows potentiometer value
uint16_t brightnessLed2;
if (potValue <= (ADC_MAX_VALUE / 2)) {
// LED2 increases between 12% → 80% brightness for first half of pot range
brightnessLed2 = map(potValue, 0, ADC_MAX_VALUE / 2, PWM_MAX_VALUE * 0.12, PWM_MAX_VALUE * 0.80);
} else {
// LED2 decreases back from 80% → 12% brightness for second half of pot range
brightnessLed2 = map(potValue, ADC_MAX_VALUE / 2, ADC_MAX_VALUE, PWM_MAX_VALUE * 0.80, PWM_MAX_VALUE * 0.12);
}
// Debug output (uncomment if needed for testing)
// Serial.printf("%u %u %u %u %u\n", brightnessLed0, brightnessLed1, brightnessLed2, brightnessLed3, brightnessLed4);
// Write brightness values to LEDs
ledcWrite(led_pins[0], brightnessLed0);
ledcWrite(led_pins[1], brightnessLed1);
ledcWrite(led_pins[2], brightnessLed2);
ledcWrite(led_pins[3], brightnessLed3);
ledcWrite(led_pins[4], brightnessLed4);
}
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.
Let's connect,
Circuit Diagram
After analyzing the task requirement, as shown in the GIF.
Mapping ADC to PWM:
The ADC provides values ranging from 0 to 1023.
To control the LED brightness using PWM (which ranges from 0 to 255), the ADC values are converted to PWM values using the map() function.
Mapping value linearly for PWM:
Since LED brightness didn’t shift as expected with the above approach, we adjusted the mapping for smoother, more visible effects.
(Note: Brightness levels/ intensity behavior of LEDs vary based on specifications; different LEDs have different behaviors. The LEDs we are using have similar brightness levels visible for PWM values in the upper range (e.g., 150 to 255)
Mapping value more dynamically to adjust for Better Visibility:
const int ledPins[] = { 11, 10, 9, 6, 3 }; // PWM pins for LEDs
// Variable to store potentiometer value
int potValue = 0;
void setup() {
// Initialize all LED pins as outputs
for (int i = 0; i < 5; i++) {
pinMode(ledPins[i], OUTPUT);
}
}
void loop() {
// Read the potentiometer value (range: 0–1023) connected to Pin A0
potValue = analogRead(A0);
// Adjust the brightness of LEDs based on the potentiometer value
setLEDBrightness(potValue);
}
// Function to calculate and set LED brightness levels based on potentiometer input
void setLEDBrightness(int potValue) {
// Map the potentiometer value (0–1023) to PWM brightness range (0–255)
// Brightness levels are adjusted to create a gradient effect across LEDs
int brightnessLed0 = map(potValue, 0, 1023, 0, 255); // LED 0: 0% to 100% brightness
int brightnessLed1 = map(potValue, 0, 1023, 64, 191); // LED 1: Mid-range brightness
int brightnessLed2 = 127; // LED 2: Fixed 50% brightness
int brightnessLed3 = map(potValue, 0, 1023, 191, 64); // LED 3: Inverse mid-range brightness
int brightnessLed4 = map(potValue, 0, 1023, 255, 0); // LED 4: 100% to 0% brightness
/**
* When the potentiometer is at 100% position, the ADC value fluctuates between 1023 and 1022.
* This causes the PWM value to alternate between 255 and 254, leading to noticeable LED flickering.
* To eliminate the flickering, any brightness value below 2 is set to 0,
* ensuring the LED turns off completely in such cases.
*/
if (brightnessLed4 < 2) {
brightnessLed4 = 0;
}
// Set PWM values for each LED
analogWrite(ledPins[0], brightnessLed0); // Adjust brightness for LED 0
analogWrite(ledPins[1], brightnessLed1); // Adjust brightness for LED 1
analogWrite(ledPins[2], brightnessLed2); // Fixed brightness for LED 2
analogWrite(ledPins[3], brightnessLed3); // Adjust brightness for LED 3
analogWrite(ledPins[4], brightnessLed4); // Adjust brightness for LED 4
// Short delay for smooth transitions
delay(10);
}
// Define the PWM pins for the LEDs
const int ledPins[] = { 11, 10, 9, 6, 3 }; // Array of LED pins (PWM-enabled pins)
// Variable to store the potentiometer reading
int potValue = 0;
void setup() {
// Configure each LED pin as an output
for (int i = 0; i < 5; i++) {
pinMode(ledPins[i], OUTPUT);
}
}
void loop() {
// Read the analog input from the potentiometer connected to pin A0 (range: 0–1023)
potValue = analogRead(A0);
// Set the brightness levels of LEDs based on the potentiometer value
setLEDBrightness(potValue);
// Short delay for smoother brightness transitions
delay(10);
}
/**
* Function to calculate and set the brightness levels of LEDs
* based on the potentiometer input.
* @param potValue - Potentiometer ADC reading (0–1023)
*/
void setLEDBrightness(int potValue) {
// Map the potentiometer value to brightness levels for each LED
int brightnessLed0 = map(potValue, 0, 1023, 0, 255); // LED 0: Brightness increases from 0 to 255
int brightnessLed1 = map(potValue, 0, 1023, 10, 245); // LED 1: Limited brightness range (10–245)
int brightnessLed3 = map(potValue, 0, 1023, 245, 10); // LED 3: Reverse brightness (245–10)
int brightnessLed4 = map(potValue, 0, 1023, 255, 0); // LED 4: Brightness decreases from 255 to 0
int brightnessLed2; // LED 2: Dynamic range with midpoint behavior
/**
* When the potentiometer is at 100% position, the ADC value fluctuates between 1023 and 1022.
* This causes the PWM value to alternate between 255 and 254, leading to noticeable LED flickering.
* To eliminate the flickering, any brightness value below 2 is set to 0,
* ensuring the LED turns off completely in such cases.
*/
if (brightnessLed4 < 2) {
brightnessLed4 = 0;
}
// Calculate brightness for the middle LED (LED 2)
if (potValue < 512) {
// If potentiometer is below the midpoint, brightness increases (30–225)
brightnessLed2 = map(potValue, 0, 511, 30, 225);
} else {
// If potentiometer is above the midpoint, brightness decreases (225–30)
brightnessLed2 = map(potValue, 512, 1023, 225, 30);
}
// Apply calculated brightness to each LED
analogWrite(ledPins[0], brightnessLed0); // Set brightness for LED 0
analogWrite(ledPins[1], brightnessLed1); // Set brightness for LED 1
analogWrite(ledPins[2], brightnessLed2); // Set brightness for LED 2
analogWrite(ledPins[3], brightnessLed3); // Set brightness for LED 3
analogWrite(ledPins[4], brightnessLed4); // Set brightness for LED 4
// Short delay for smooth transitions
delay(10);
}
Brightness for LEDs:
Map Function: