We’re going to build a simple LED bar graph using a potentiometer.
As we rotate the potentiometer, the LEDs will light up one by one smoothly.
Solving Approach:
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_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 = 4095; // 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.
Variable Definitions and Macros:
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...
uint16_t g_adcSection1 = (ADC_MAX_VALUE / 5) * 1; // 819
uint16_t g_adcSection2 = (ADC_MAX_VALUE / 5) * 2; // 1638
// Additional section boundaries…
// ADC value range (12-bit ADC)
#define ADC_MIN_VALUE 0
#define ADC_MAX_VALUE 4095
// PWM duty cycle range
#define PWM_MIN_VALUE 0
#define PWM_MAX_VALUE 4095
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;
}
This function scales ADC values (0-4095) to PWM duty cycle values (0-4095) within each segment.
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 while Loop:
The infinite loop continuously reads the ADC and updates LED brightness:
while (1) {
// Start ADC conversion and wait for result
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 20);
g_adcValue = HAL_ADC_GetValue(&hadc1);
// LED control logic based on ADC value sections
if (g_adcValue < g_adcSection1) {
// Section 1: Only LED0 is active with variable brightness
// Turn off all other LEDs
__HAL_TIM_SET_COMPARE(LED1_TIM_HANDLE, LED1_TIM_CHANNEL,
ADC_MIN_VALUE);
__HAL_TIM_SET_COMPARE(LED2_TIM_HANDLE, LED2_TIM_CHANNEL,
ADC_MIN_VALUE);
__HAL_TIM_SET_COMPARE(LED3_TIM_HANDLE, LED3_TIM_CHANNEL,
ADC_MIN_VALUE);
__HAL_TIM_SET_COMPARE(LED4_TIM_HANDLE, LED4_TIM_CHANNEL,
ADC_MIN_VALUE);
// Map ADC value to PWM range for LED0 brightness
g_brightness = mapValue(g_adcValue, ADC_MIN_VALUE, g_adcSection1,
PWM_MIN_VALUE,
PWM_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED0_TIM_HANDLE, LED0_TIM_CHANNEL,
g_brightness);
} else if (g_adcValue < g_adcSection2) {
// Section 2: LED0 at full brightness, LED1 with variable brightness
__HAL_TIM_SET_COMPARE(LED0_TIM_HANDLE, LED0_TIM_CHANNEL,
ADC_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED2_TIM_HANDLE, LED2_TIM_CHANNEL,
ADC_MIN_VALUE);
__HAL_TIM_SET_COMPARE(LED3_TIM_HANDLE, LED3_TIM_CHANNEL,
ADC_MIN_VALUE);
__HAL_TIM_SET_COMPARE(LED4_TIM_HANDLE, LED4_TIM_CHANNEL,
ADC_MIN_VALUE);
// Map ADC value to PWM range for LED1 brightness
g_brightness = mapValue(g_adcValue, g_adcSection1, g_adcSection2,
PWM_MIN_VALUE,
PWM_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED1_TIM_HANDLE, LED1_TIM_CHANNEL,
g_brightness);
} else if (g_adcValue < g_adcSection3) {
// Section 3: LEDs 0-1 at full brightness, LED2 with variable brightness
__HAL_TIM_SET_COMPARE(LED0_TIM_HANDLE, LED0_TIM_CHANNEL,
ADC_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED1_TIM_HANDLE, LED1_TIM_CHANNEL,
ADC_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED3_TIM_HANDLE, LED3_TIM_CHANNEL,
ADC_MIN_VALUE);
__HAL_TIM_SET_COMPARE(LED4_TIM_HANDLE, LED4_TIM_CHANNEL,
ADC_MIN_VALUE);
// Map ADC value to PWM range for LED2 brightness
g_brightness = mapValue(g_adcValue, g_adcSection2, g_adcSection3,
PWM_MIN_VALUE,
PWM_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED2_TIM_HANDLE, LED2_TIM_CHANNEL,
g_brightness);
} else if (g_adcValue < g_adcSection4) {
// Section 4: LEDs 0-2 at full brightness, LED3 with variable brightness
__HAL_TIM_SET_COMPARE(LED0_TIM_HANDLE, LED0_TIM_CHANNEL,
ADC_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED1_TIM_HANDLE, LED1_TIM_CHANNEL,
ADC_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED2_TIM_HANDLE, LED2_TIM_CHANNEL,
ADC_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED4_TIM_HANDLE, LED4_TIM_CHANNEL,
ADC_MIN_VALUE);
// Map ADC value to PWM range for LED3 brightness
g_brightness = mapValue(g_adcValue, g_adcSection3, g_adcSection4,
PWM_MIN_VALUE,
PWM_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED3_TIM_HANDLE, LED3_TIM_CHANNEL,
g_brightness);
} else if (g_adcValue < g_adcSection5) {
// Section 5: LEDs 0-3 at full brightness, LED4 with variable brightness
__HAL_TIM_SET_COMPARE(LED0_TIM_HANDLE, LED0_TIM_CHANNEL,
ADC_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED1_TIM_HANDLE, LED1_TIM_CHANNEL,
ADC_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED2_TIM_HANDLE, LED2_TIM_CHANNEL,
ADC_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED3_TIM_HANDLE, LED3_TIM_CHANNEL,
ADC_MAX_VALUE);
// Map ADC value to PWM range for LED4 brightness
g_brightness = mapValue(g_adcValue, g_adcSection4, g_adcSection5,
PWM_MIN_VALUE,
PWM_MAX_VALUE);
__HAL_TIM_SET_COMPARE(LED4_TIM_HANDLE, LED4_TIM_CHANNEL,
g_brightness);
}
}
LED Control Logic Explanation:
HAL Library Functions Used:
HAL_ADC_Start():
Initiates ADC conversionHAL_ADC_PollForConversion():
Waits for conversion completionHAL_ADC_GetValue():
Retrieves converted value__HAL_TIM_SET_COMPARE():
Updates PWM duty cycle by modifying CCR registerThe complete STM32CubeIDE project (including .ioc
configuration, main.c,
and HAL files) is available here:
📥Download Project
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
#define POT_PIN 34 // pin connected to Potentiometer
const uint8_t led_pin[] = { 15, 16, 17, 18, 19 }; // Pins connected to LEDs (PWM pins)
void setup() {
// Set all LED pins as PWM outputs
for (uint8_t i = 0; i < 5; i++) {
ledcAttach(led_pin[i], 1000, 12); // Attach pin with freq=1kHz, resolution=12-bit
ledcWrite(led_pin[i], 0); // Start with 0 duty
}
analogReadResolution(12); // set ADC resolution
Serial.begin(115200);
}
void loop() {
uint16_t pot_value = analogRead(POT_PIN); // Read the potentiometer ADC value (0–4095)
uint16_t brightness; // To store brightness value
// Divide the potentiometer range into 5 segments, one for each LED
if (pot_value < 819) {
// Segment 1: Potentiometer value 0–818
ledcWrite(led_pin[1], 0);
ledcWrite(led_pin[2], 0);
ledcWrite(led_pin[3], 0);
ledcWrite(led_pin[4], 0);
brightness = map(pot_value, 0, 819, 0, 4095);
ledcWrite(led_pin[0], brightness);
} else if (pot_value < 1638) {
// Segment 2: Potentiometer value 819–1638
ledcWrite(led_pin[2], 0);
ledcWrite(led_pin[3], 0);
ledcWrite(led_pin[4], 0);
ledcWrite(led_pin[0], 4095);
brightness = map(pot_value, 819, 1638, 0, 4095);
ledcWrite(led_pin[1], brightness);
} else if (pot_value < 2457) {
// Segment 3: Potentiometer value 1638–2457
ledcWrite(led_pin[3], 0);
ledcWrite(led_pin[4], 0);
ledcWrite(led_pin[0], 4095);
ledcWrite(led_pin[1], 4095);
brightness = map(pot_value, 1638, 2457, 0, 4095);
ledcWrite(led_pin[2], brightness);
} else if (pot_value < 3276) {
// Segment 4: Potentiometer value 2457–3276
ledcWrite(led_pin[4], 0);
ledcWrite(led_pin[0], 4095);
ledcWrite(led_pin[1], 4095);
ledcWrite(led_pin[2], 4095);
brightness = map(pot_value, 2457, 3276, 0, 4095);
ledcWrite(led_pin[3], brightness);
} else {
// Segment 5: Potentiometer value 3276–4095
ledcWrite(led_pin[0], 4095);
ledcWrite(led_pin[1], 4095);
ledcWrite(led_pin[2], 4095);
ledcWrite(led_pin[3], 4095);
brightness = map(pot_value, 3276, 4095, 0, 4095);
ledcWrite(led_pin[4], brightness);
}
}
uint16_t pot_value = analogRead(POT_PIN);
brightness = map(pot_value, 819, 1638, 0, 4095);
pot_value
goes from 819 → 1638, the PWM duty goes from 0 → 4095, smoothly fading the LEDledcWrite(led_pin[1], brightness);
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
As we have 5 LEDs connected, the ADC range (0–1023) is divided into 5 equal segments, each corresponding to a specific LED.
ADC value Segmentation:
Let’s do ADC value segmentation, as we have 5 LEDs = 1023/5, i.e., 204.
const int ledPins[] = { 11, 10, 9, 6, 3 }; // Pins connected to LEDs (PWM pins)
void setup() {
// Set all LED pins as outputs
for (int i = 0; i < 5; i++) {
pinMode(ledPins[i], OUTPUT);
}
}
void loop() {
int potValue = analogRead(A0); // Read the potentiometer ADC value (0–1023) connected to analog pin A0
int brightness; //To store Brightness value
// Divide the potentiometer range into 5 segments, one for each LED
if (potValue < 204) {
// Segment 1: Potentiometer value 0–203
analogWrite(ledPins[1], 0); // Turn LED 1 fully OFF
analogWrite(ledPins[2], 0); // Turn LED 2 fully OFF
analogWrite(ledPins[3], 0); // Turn LED 3 fully OFF
analogWrite(ledPins[4], 0); // Turn LED 4 fully OFF
brightness = map(potValue, 0, 203, 0, 255); // Map ADC value to PWM values
analogWrite(ledPins[0], brightness); // Control LED 0 brightness
} else if (potValue < 408) {
// Segment 2: Potentiometer value 204–407
analogWrite(ledPins[2], 0); // Turn LED 2 fully OFF
analogWrite(ledPins[3], 0); // Turn LED 3 fully OFF
analogWrite(ledPins[4], 0); // Turn LED 4 fully OFF
analogWrite(ledPins[0], 255); // Turn LED 0 fully ON
brightness = map(potValue, 204, 407, 0, 255); // Map ADC value to PWM values
analogWrite(ledPins[1], brightness); // Control LED 1 brightness (0% to 100%)
} else if (potValue < 612) {
// Segment 3: Potentiometer value 408–611
analogWrite(ledPins[3], 0); // Turn LED 3 fully OFF
analogWrite(ledPins[4], 0); // Turn LED 4 fully OFF
analogWrite(ledPins[0], 255); // Turn LED 0 fully ON
analogWrite(ledPins[1], 255); // Turn LED 1 fully ON
brightness = map(potValue, 408, 611, 0, 255); // Map ADC value to PWM values
analogWrite(ledPins[2], brightness); // Control LED 2 brightness (0% to 100%)
} else if (potValue < 816) {
// Segment 4: Potentiometer value 612–815
analogWrite(ledPins[4], 0); // Turn LED 4 fully OFF
analogWrite(ledPins[0], 255); // Turn LED 0 fully ON
analogWrite(ledPins[1], 255); // Turn LED 1 fully ON
analogWrite(ledPins[2], 255); // Turn LED 2 fully ON
brightness = map(potValue, 612, 815, 0, 255); // Map ADC value to PWM values
analogWrite(ledPins[3], brightness); // Control LED 3 brightness (0% to 100%)
} else {
// Segment 5: Potentiometer value 816–1023
analogWrite(ledPins[0], 255); // Turn LED 0 fully ON
analogWrite(ledPins[1], 255); // Turn LED 1 fully ON
analogWrite(ledPins[2], 255); // Turn LED 2 fully ON
analogWrite(ledPins[3], 255); // Turn LED 3 fully ON
brightness = map(potValue, 816, 1023, 0, 255); // Map ADC value to PWM values
analogWrite(ledPins[4], brightness); // Control LED 4 brightness (0% to 100%)
}
}
const int ledPins[] = { 11, 10, 9, 6, 3 }; // Pins connected to LEDs (PWM pins)
void setup() {
// Set all LED pins as outputs
for (int i = 0; i < 5; i++) {
pinMode(ledPins[i], OUTPUT);
}
}
void loop() {
int potValue = analogRead(A0); // Read potentiometer value (0–1023)
int segment = potValue / 204; // Determine which segment the value falls into (0–4)
int brightness = potValue % 204; // Get the remainder for brightness mapping in the segment
// Smooth transition for LED brightness
for (int i = 0; i < 5; i++) {
if (i < segment) {
analogWrite(ledPins[i], 255); // Fully on LEDs in previous segments
} else if (i == segment) {
analogWrite(ledPins[i], map(brightness, 0, 203, 0, 255)); // Gradually vary brightness of current LED
} else {
analogWrite(ledPins[i], 0); // Turn off LEDs in next segments
}
}
// Short delay to stabilize transitions
delay(10);
}
Logic
Method 1: Explanation
Method 2: Explanation