UBMP4.1 Introductory Programming Activity 3

This activity was designed for UBMP4.1 and is now superseded by a new version for UBMP4.2.

Loops

The variables program in Activity 2 introduced different types of variables. Variables are used to hold values which can be modified by a program. Combining variables and conditional statements, such as those from Activity 1, will let us use the value of the variable to decide if a block of code should run, creating one of the most useful structures in programming – the loop.

Infinite, or finite?

The programs in the previous activities have already demonstrated infinite loops. Infinite loops continue forever (or at least until the reset button is pressed). The while(1) loop in the main( ) program function is an infinite loop.

Alternatively, finite loops run only while a condition is true, or until a condition is met. Finite loops are used to repeat blocks of code within the main infinite loop. This is similar to, but different from the if-conditions already in our programs – code in the if-conditions runs only once when the condition is true, while code in a loop would continue to repeat while the condition is true.

Finite loop anatomy

Three separate code structures are required to have code repeat in a finite loop:

  • a variable which is initialized with the starting condition for the loop;

  • a conditional statement that checks the value of the variable and then decides whether to continue or end the operation of the loop, and;

  • and a method of modifying the variable during successive cycles of the loop.

In this program, we will explore different ways of creating finite loops using both while( ) and for( ) loop structures in a program that makes PWM (pulse-width modulated) waves to control the brightness of an LED.

Pulse-width Modulation, or PWM

PWM is a technique that is commonly employed by circuits to change the brightness of lights, or to control the speed of an electric motor. PWM works by rapidly pulsing an output device on and off, for different relative amounts of time, in order to vary the average energy provided to the device. If the pulse frequency is high enough, the effects of the individual pulses won’t be noticed. For example, an observers won’t be able to see individual flashes of light if an LED is pulsed on and off hundreds of times per second, but they will be able to see changes in the brightness of the LED based on its duty cycle – the ratio of its on-time to off-time.

 

New Concepts

Loops are used to repeat blocks of code. Here are some of the concepts you will learn about in this activity:

  • finite loop - a loop that repeats while a condition is true, or until a condition is met

  • infinite loop - a loop that repeats forever. The main( ) function in microcontroller programs is usually an infinite loop.

  • loop condition - a conditional statement, similar to an if statement, that determines whether the program will continue into or exit the loop

  • accumulator, or loop index counter - a variable initialized to the starting value of a loop and modified during the execution of the loop until an ending value is reached

  • PWM (pulse-width modulation) - a system of varying the on and/or off times of repetitive pulses commonly used to represent an equivalent analogue value

  • duty cycle - the ratio of the on and off durations of PWM pulses

The main program

Download the Intro-3-Loops program files (.zip) to create the MPLAB project for this activity, or import the files into MPLAB from its Github repository. Let’s examine the contents of the main file, Intro-3-Loops.c, below:

/*==============================================================================
 Project: Intro-3-Loops
 Date:    March 1, 2022
 
 This example program demonstrates the use of while and for loop structures to
 change the brightness of LEDs using PWM (Pulse-Width Modulated) signals.
 
 Additional program analysis and programming activities expand the use of loops
 to create tones of different pitches and frequency sweeps.
==============================================================================*/

#include    "xc.h"              // Microchip XC8 compiler include file
#include    "stdint.h"          // Include integer definitions
#include    "stdbool.h"         // Include Boolean (true/false) definitions

#include    "UBMP410.h"         // Include UBMP4.1 constant and function definitions

// TODO Set linker ROM ranges to 'default,-0-7FF' under "Memory model" pull-down.
// TODO Set linker code offset to '800' under "Additional options" pull-down.

// Program variable definitions
unsigned char TonLED4 = 127;    // LED brightness PWM value
unsigned char PWMperiod;        // PWM period counter for PWM loops
unsigned int period = 460;      // Sound period value for later activities

int main(void)
{
    OSC_config();               // Configure internal oscillator for 48 MHz
    UBMP4_config();             // Configure on-board UBMP4 I/O devices
	
    while(1)
	{
        // Decrease brightness
        if(SW2 == 0)
        {
            TonLED4 -= 1;
        }

        // Increase brightness
        if(SW3 == 0)
        {
            TonLED4 += 1;
        }
        
        // PWM LED4 brightness
        PWMperiod = 255;
        while(PWMperiod != 0)
        {
            if(TonLED4 == PWMperiod)
            {
                LED4 = 1;
            }
            PWMperiod --;
            __delay_us(20);
        }
        LED4 = 0;
        
        // Activate bootloader if SW1 is pressed.
        if(SW1 == 0)
        {
            RESET();
        }
    }
}

Program Operation

The top of the program should now be starting to become a familiar thing: comments for other programmers, one or more include statements for the compiler, variable and constant definitions for defining values that will be used in the program, followed by the main( ) program function that identifies the start of all of the program code (including the main while( ) loop, which is an infinite loop that will run forever).

Inside the while( ) loop are two simple conditions which will be used to increment and decrement the TonLED4 variable, which will represent our PWM value (TonLED4 stands for Time, on, of LED4 ). The next block of code used TonLED4 to create a PWM wave controlling LED4’s brightness using a finite loop structure.

PWM while loop

        // PWM LED4 brightness
        PWMperiod = 255;
        while(PWMperiod != 0)
        {
            if(TonLED4 == PWMperiod)
            {
                LED4 = 1;
            }
            PWMperiod --;
            __delay_us(20);
        }
        LED4 = 0;

The PWM code starts before the while loop by setting the PWMperiod variable to 255, the highest number that can be encoded an 8-bit character. The PWMperiod variable is will be used to count the number times this loop is going to repeat. The decision to repeat the loop is made by the condition in the while statement, which will repeat the statements within the loop while PWMperiod is not equal ( != ) to zero. Since PWMperiod was initially set to 255 – which is definitely not zero – we know the loop contents will run at least this one time.

Inside the loop, the value of the PWMperiod variable and TonLED4 variable are compared. If they’re equal, LED4 will be turned on. Since TonLED4 was previously set to 127, and would only have changed by 1 if either SW2 or SW3 was pressed (making TonLED4 either 126 or 128), LED4 will stay off during this first iteration of the loop.

Next, the statement PWMperiod --; modifies the loop variable by subtracting 1 to make it 254, followed by a 20 µs delay. After the delay, the closing brace of the while loop on the next line causes the program to return to re-evaluate the PWMperiod count in the while statement. The loop will be repeated until the PWMperiod variable is zero. Since the PWMperiod variable drops by 1 every time, we know contents of this loop will be repeated 255 times.

Now that we understand the loop’s operation, how does this translate into a PWM wave to change the LED’s brightness? To understand that, we need to remember that running each line of code in this loop takes a small amount of time. Each cycle through the loop runs adds to that time, and the included delay statement adds an even greater amount of time. Some time spent running through the loop’s 255 cycles will be with the LED off, and some of the time will be with the LED turned on. The more loop cycles the LED is on for, the more time the LED is on, as a ratio of its on-time to off-time, and the brighter the LED will appear to be.

Learn more – program analysis activities

Did you build and run the program? Pressing the buttons should either repeatedly brighten or dim the LED. What effect do you think changing the time delay in the line __delay_us(20); have? Will it change the LED brightness, or the responsiveness of the buttons, or both? Try to change the delay and see (try 1µs, or 100µs, for example).

Pressing and holding either button will repeatedly cycle through the brightness range. Why is that?

Do you remember how numbers are represented in computers? The TonLED4 variable is an unsigned character, with a range of 0 to 255. Incrementing TonLED4 past 255 results in it resetting to 0. Similarly, decrementing the variable below 0 results in it counting down from 255. How could the value of TonLED4 be made to stop either at the brightest or dimmest setting, without exceeding the thresholds 0 or 255?

PWM for loop

An alternative method of creating this PWM program is by using a for-loop structure instead of the while-loop. You can replace the while loop code in the program with the for loop code, below:

        // PWM LED4 brightness
        for(unsigned char PWMperiod = 255; PWMperiod != 0; PWMperiod --)
        {
            if(TonLED4 == PWMperiod)
            {
                LED4 = 1;
            }
            __delay_us(20);
        }
        LED4 = 0;

Let’s look at the fundamental differences between while and for loops, the for initialization statement. The for loop’s initialization statement contains operations to define and initialize the loop counter variable, the conditional statement that determines when the loop ends, and the loop variable modifier.

All three of these operations are necessary parts of a loop structure and mirror the separate statements that accomplish the same results in the while loop. But, all three are combined into one statement, separated by semi-colons, in the for loop. This makes the for loop code more compact than the while loop.

For, or while, or for a while?

Given that the for loop structure is more condensed than the while loop, you might wonder why while loops are even used. While loops have two advantages over for loops. First, the while loop conditions can be tied to non-numeric events instead of just the state of the loop counter. A while loop can be made to repeat while a button is held, for example. The second advantage is that it’s easy to modify the amount by which the loop counter variable changes during the execution of the loop, whereas the for loop initialization statement sets the amount by which the loop variable changes.

In general, if the number of required loops is known ahead of time, a for loop results in more compact code. If the loop needs to run an arbitrary number of times, or based on factors external to the loop, a while loop is the solution.

Hard conditions are faster

An important consideration when programming relatively slow and simple microcontrollers is that hard conditional comparisons, such as equality or inequality, run faster than soft conditional comparisons, such as less or greater than. In mid-range PICmicro microcontrollers, specifically, there are no conditional operators in their machine code instructions, with the exception of a check for a zero result. This means that the fastest loop is one that counts down to zero. All other types of loops will run more slowly. For example, each of the loops below will run 10 times, but the first example will run faster:

// Fast loop code counts down to zero
for(counter = 10; counter != 0; counter --)
{
  ...
}

// Loop code that counts up to an arbitrary value runs slower
for(counter = 0; counter < 10; counter ++)
{
  ...
}

Activity 3 learning summary

Pulse width modulation is an effective (and electrically efficient) way to control the brightness of an LED, or the speed of a motor. PWM is often created using a software loop structure.

Loops can be made using a while-loop structure containing separate variable assignment, condition, and modifier statements, or by using a for-loop structure with an initialization statement combining all three actions. For loops are best used when the number of loop cycles is known, and while loops provide the flexibility to repeat code based on events or variables outside of the loop itself.

Programming Activity 3 Challenges

1. Pressing and holding SW2 or SW3 causes the brightness of LED D4 to cycle through its entire brightness range. Modify the code so that pressing and holding SW2 will dim the LED until it is off and then keep if off, and pressing and holding SW3 will brighten the LED and keep it at maximum brightness.

2. Modify your program to control the brightness of LED D5 using SW4 and SW5 while using SW3 and SW2 to control LED D4. Hint: To ensure each LED can reach maximum brightness (100% PWM on-time), you'll have to perform both PWM functions in the same loop. You can see the resulting PWM wave if you have access to an oscilloscope. If not, just light the other two LEDs and compare the brightness of LEDs D4 and D5 to them.

3. Rather than having lights suddenly turn on at full brightness, or motors turn on at full power, create a program that uses a for loop and your PWM code to make a 'soft-start' program that slowly increases the PWM on-time when you press a button. Can you make it turn off in a similar way?

4. Make a program that creates an automated, electronic 'pulse', repeatedly brightening and dimming one or more LEDs.

5. Make a 'chirp' or 'pew-pew' sound effect by sweeping through a range of frequencies when a button is pressed.