Adjusting LED Brightness with PWM and a Potentiometer

Imagine being able to adjust the brightness of an LED with just a simple turn of a knob, smoothly transitioning from a subtle glow to a bright shine. That’s exactly what we’re creating in this project! By using a potentiometer to control the Pulse Width Modulation (PWM) signal on a PIC16F15313 microcontroller, we can dynamically change the LED’s brightness. This technique allows you to fine-tune the light level, making it perfect for dimmable displays, visual indicators, or just experimenting with analog-to-digital interactions.

In this post, I’ll guide you through setting up the circuit, understanding the code, and showing how the potentiometer directly influences the LED’s brightness in real-time. Let’s dive in and see how a small knob can control the light!

The Circuit

ledblink pot serial

Let’s walk through each part of the circuit:

This project builds on the same basic circuit structure we’ve used before—just a microcontroller, an LED, and a potentiometer—but this time, we’re creating an entirely different behavior through code alone. And that’s where things start to get really exciting! Once you have a solid foundation in your circuit design, the possibilities are practically endless because it’s all in the .

You can keep the same circuit setup and, with just a few lines of code, transform it into a PWM dimmer, a serial data logger, or even a frequency generator. This is the real magic of microcontrollers: with a consistent hardware setup, you’re free to experiment and explore a world of different functionalities. It’s like having a canvas where the hardware is the frame, and the code is the paint—you can create something new every time.

So, as you continue to explore and tweak, remember that you’re not limited by the physical components you have in front of you. All it takes is a change in the code, and your project can become whatever you want it to be! Keep experimenting and see what new ideas you can bring to life.

Let’s walk through each part of the circuit:

  1. Powering the Microcontroller: At the heart of the setup is the PIC16F15313, which we’ll power with a 5V DC supply. To keep things running smoothly, a 100nF capacitor is connected for decoupling. Think of it as a small “smoothing tool” that filters out any power fluctuations, ensuring the microcontroller operates reliably.
  2. Connecting the LED to PortA.0: We’ll attach a 300-ohm resistor to PortA.0, with the LED connected right after it. The resistor limits the current, protecting the LED from burning out while making sure it glows at the right intensity. This LED will blink based on the values we read from the potentiometer.
  3. Connecting the Potentiometer to PortA.1: The middle pin (wiper) of the potentiometer connects to PortA.1 on the PIC. As you turn the knob, the voltage at this pin changes, allowing us to read its value and use it to adjust the LED’s blink speed. It’s a simple interaction, but it forms the foundation for understanding how to incorporate analog inputs.
  4. Adding Serial Output on PortA.4: We’re using PortA.4 as the TTL serial output pin. This pin sends the potentiometer’s reading as serial data to the TTL to USB Serial Converter. From there, the converter translates it into a format that your computer can read. Connect PortA.4 (serial TX) on the PIC to the RX (receive) pin on the TTL to USB module. {Feel Free to Leave this part out}

Putting It All Together

With the core components—the LED, potentiometer, and now the serial interface—working together, we’ve created a circuit that not only responds to physical changes but also communicates those changes back to us. This feedback is incredibly valuable for troubleshooting, monitoring, and exploring how the circuit responds in real time.

Once you’ve built the circuit, we’ll jump into the code and set up the serial output so that you can see exactly what’s happening as you turn the knob. It’s all about adding that extra layer of visibility and control, making this project more interactive and, hopefully, even more satisfying to experiment with. Ready to take the next step? Let’s get started!

Now for the Code

/****************************************************************************
* Title                 :   Vary LED Brightness Based on POT Values
* Filename              :   pwm_led.c
* Author                :   Jamie Starling
* Origin Date           :   2024/04/24
* Version               :   1.0.0
* Compiler              :   XC8 
* Target                :    
* Copyright             :   Jamie Starling
* All Rights Reserved
*
* THIS SOFTWARE IS PROVIDED BY JAMIE STARLING "AS IS" AND ANY EXPRESSED
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL JAMIE STARLING OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
*******************************************************************************/

/******************************************************************************
*                     LICENSED FOR NON-COMMERCIAL USE
*                Visit https://jamiestarling.com/corelicense
*                           for details 
*******************************************************************************/

/******************************************************************************
* Includes
*******************************************************************************/
#include "core16F/core16F.h" //Include Core MCU Functions


/******************************************************************************
* Functions
*******************************************************************************/
void main(void)
{
    /*Setup*/
    /*Initialize for the Core8 System   */
    CORE.Initialize(); //
  
    /*Set PORTA.0 to PWM Output*/    
    PWM3.Initialize(PORTA_0,PWM_10bit);
    
    /*Set PORTA.2 to Analog and Maps ANA2 Channel - Initializes Analog*/
    GPIO_Analog.PinSet(PORTA_1,ANA1); //  

    /*Initializes Serial1 to 9600 Baud
    *On the PIC16F15313 Receive is PORTC.5 : Transmit is on PORTC.4 */
    SERIAL1.Initialize(BAUD_9600);  //Initializes Serial1 - On the PIC16F15313 Receive is RC4
 
    while(1) //Program loop
        {      
            uint16_t POT_Value; 
            char SerialTransmit_Buffer[25];   
      
            POT_Value = GPIO_Analog.ReadChannel();
            sprintf(SerialTransmit_Buffer, "POT Value : %d\n", POT_Value);
            SERIAL1.WriteString(SerialTransmit_Buffer);  
            
            PWM3.DutyCycle(POT_Value);  //Set PWM Output to Value Read from POT
            
            CORE.Delay_MS(500);    //500ms Delay    
        }/*END of Program Loop*/
}




/*** End of File **************************************************************/

Breaking Down the Code: Adjusting LED Brightness with PWM Using a Potentiometer

This code is designed to read the value from a potentiometer and use it to control the brightness of an LED connected to a PIC16F15313 microcontroller. It uses Pulse Width Modulation (PWM) to vary the LED’s brightness based on the potentiometer’s position, while also sending the real-time value of the potentiometer over a serial interface for monitoring. Let’s walk through it step-by-step to see exactly what’s happening.

Code Overview

#include "core16F/core16F.h" // Include Core MCU Functions<br>

The code starts by including the Core Framework library, which simplifies many of the microcontroller’s functions and allows us to use a set of predefined methods for controlling PWM, GPIO, and serial communication.

void main(void)<br>{<br>    /* Setup */<br>    CORE.Initialize(); //<br>

The CORE.Initialize() function initializes the core settings for the microcontroller, preparing the system for basic operations. This sets up internal configurations like clock speeds and ensures the microcontroller is ready to handle GPIO, analog, and PWM tasks.

   /* Set PORTA.0 to PWM Output */    <br>    PWM3.Initialize(PORTA_0,PWM_10bit);<br>

Here, we set PortA.0 as a PWM output using PWM3.Initialize(PORTA_0, PWM_10bit);. This means that the LED connected to this pin will have its brightness controlled by varying the duty cycle of the PWM signal. The second parameter, PWM_10bit, sets up the PWM in 10-bit resolution, giving us 1024 steps for more precise control over brightness.

 /* Set PORTA.1 to Analog and Maps ANA2 Channel - Initializes Analog */<br>    GPIO_Analog.PinSet(PORTA_1, ANA1);<br>

This line configures PortA.1 as an analog input, mapped to analog channel ANA1. The potentiometer’s middle pin (wiper) is connected to this port, allowing us to read a continuous range of values between 0 and 1023 (the range of a 10-bit ADC).

   /* Initializes Serial1 to 9600 Baud */<br>    SERIAL1.Initialize(BAUD_9600);  // Initializes Serial1 at 9600 Baud<br>

The code then sets up Serial1 communication at a 9600 baud rate. This will be used to send the potentiometer’s value to the computer, making it possible to monitor the changes in real time through a serial terminal. The SERIAL1.Initialize() function configures PortC.4 as the serial Transmit (TX) pin.

The Endless Loop

Now we enter the main loop, where the microcontroller continuously reads the potentiometer’s value, adjusts the LED brightness, and sends the current value to the serial interface.

   while(1) // Program loop<br>    {      <br>        uint16_t POT_Value;        // Variable to store the potentiometer value<br>        char SerialTransmit_Buffer[25];   // Buffer to hold the string for serial transmission<br>

Two variables are declared inside the loop:

  1. POT_Value: A 16-bit unsigned integer to hold the analog value read from the potentiometer.
  2. SerialTransmit_Buffer: A character array to store the formatted string before sending it over the serial port.
  POT_Value = GPIO_Analog.ReadChannel();  // Read the potentiometer value<br>

This line reads the analog value from PortA.1 (where the potentiometer is connected) and stores it in POT_Value. The value will range from 0 to 1023, corresponding to the potentiometer’s position.

        sprintf(SerialTransmit_Buffer, "POT Value : %d\n", POT_Value);<br>        SERIAL1.WriteString(SerialTransmit_Buffer);  <br>

Here’s where the serial communication comes in. The sprintf() function formats the potentiometer value into a readable string like "POT Value : 512\n" and stores it in SerialTransmit_Buffer. This formatted string is then sent to the serial port using SERIAL1.WriteString(). As a result, you can view the potentiometer’s value in real-time on a serial terminal.

 PWM3.DutyCycle(POT_Value);  // Set PWM Output to Value Read from POT<br>

This is where the LED’s brightness is adjusted. The PWM3.DutyCycle(POT_Value); function sets the PWM duty cycle to match the value read from the potentiometer. Since the PWM was configured in 10-bit mode, a POT_Value of 0 turns the LED off, and a value of 1023 sets the LED to full brightness. Anything in between will adjust the LED to a corresponding brightness level.

  CORE.Delay_MS(500);  // 500ms Delay<br>

The loop includes a half-second delay (CORE.Delay_MS(500);) to give the system time to settle and prevent it from overwhelming the serial interface with too many updates at once.

Summary of What’s Happening

  1. Reads the Potentiometer Value: The microcontroller continuously reads the potentiometer’s position through PortA.1.
  2. Adjusts the PWM Duty Cycle: The value is used to set the PWM duty cycle on PortA.0, changing the LED’s brightness.
  3. Serial Output: The same value is formatted into a string and sent over the serial interface for live monitoring.

What This Code Does in Action

When you run this program, you’ll see the LED’s brightness change smoothly as you turn the potentiometer knob. Meanwhile, on your serial terminal (connected through the TTL to USB Serial Converter), you’ll see the corresponding potentiometer values being printed in real-time, giving you immediate feedback.

This combination of PWM control and serial monitoring helps you understand what’s happening behind the scenes, turning what could be a “black box” of electronics into something tangible, interactive, and—most importantly—easy to tweak and explore!

Where to Go From Here

Once you’re comfortable with this setup, there are plenty of ways to expand it. You could add multiple LEDs, use different analog inputs, or even incorporate to create more complex lighting effects. The key takeaway is that you now have a reliable way to connect the analog world (the potentiometer) to a digital system (the PIC microcontroller) and control real-world outputs like LED brightness with precision.

Core MCU Framework : Main Doc Page

Core MCU Framework Versions : Supported Devices


Have a Project or Idea!?

Seeking Bespoke Technology Solutions?

jamie@jamiestarling.com


Pin It on Pinterest

Share This