Introductory Programming Activity 1

This is the first of five step-by-step introductory programming activities specifically designed to help you learn the fundamentals of microcontroller programming using the mirobo CHRP4 or UBMP4 circuits. Many of the concepts introduced in these activities can easily be applied to other types of microcontroller circuits as well.

After working through the first four out of five introductory activities, you should be able to confidently write programs to tackle most basic microcontroller interfacing and control tasks, and you will be ready to move on to the more advanced programming activities on this website. Let’s get you started on your programming journey!

Input and output

The first step in getting any new microcontroller circuit to do something useful is learning to create programs that read inputs, make decisions, and control output devices. This simple input and output (I/O) program does exactly that, teaching you these fundamental concepts that will be used in all of your future microcontroller programs.

Learning objectives

In this programming activity you will learn about the basic structure of PIC microcontroller-based C programs, and how to write programs to:

  • read pushbutton input

  • turn on individual LEDs (known as bit, or single-bit output)

  • turn on multiple LEDs at the same time (known as port, or byte output)

  • work with built-in compiler time delay functions

  • combine if conditions in input statements

  • make logical AND and OR input conditions, and

  • make sounds using the beeper and time delay functions

Activity overview

All of the introductory programming activities will follow the same format, and have been designed to help you learn by doing. The activities follow a format that can be described using the acronym RAMP-M, incorporating each of the following steps:

Run the provided example program to see what it does.

Analyze the program and its operation to learn new concepts.

Modify the program to extend existing ideas and to introduce new or related concepts.

Predict, test, and analyze the results of the modifications to build deeper understanding.

Make new programs by applying the concepts learned in new or different ways.

Every programming activity starts with an example programming project that you can download from GitHub, paste into your IDE and compile in your computer, and upload into your circuit for testing.

Next, program analysis activities (also found in the program source code) will introduce new learning and guide you through the exploration of related concepts to help you develop a better understanding of the program and its operation. You will do this by making small modifications to the existing program and predicting and testing the outcomes after these changes.

Finally, programming challenges at the end of the activity (and also in the program source code) give you the opportunity to practice and extend your new software and hardware skills. The programming challenges are designed to give you the confidence to tackle new and different programming tasks while building and expanding on the same basic concepts introduced in the activity.

Getting started

To complete these activities you will need:

  1. A computer running the MPLAB® X IDE software with the MPLAB XC8 compiler installed, or a computer or Chromebook to run the MPLAB Xpress cloud-based IDE (and a myMicrochip cloud account).

  2. An assembled CHRP4 or UBMP4 circuit and the appropriate USB 2.0 type-C cable to connect the circuit to your computer. (This assumes you will be programming your CHRP4 or UBMP4 using the USB µC bootloader pre-programmed into the kit microcontrollers.)

  3. Optionally, Microchip’s PICkit 4 or PICkit 5 programmer to either program the USB µC bootloader into the microcontroller in your CHRP4 or UBMP4 circuit, or to write the example programs directly into the microcontroller in CHRP4 or UBMP4 without using the bootloader.

New Concepts

You might be new to C, or new to programming in general – don’t worry, we’ll take it step-by-step. Here are some important terms and other things you should know to get started:

  • C programs are often composed of more than one source code file. Separating programs into multiple files makes the code more portable and modular (for example, to easily support a different processor, or a new circuit board design).

  • most C programs include files containing source code instructions (with a .c file extension), as well as files containing directives that might normally reside at the top, or head, of a program, called header files (.h file extension).

  • each C program has to include one file containing the main( ) function, which is the start of the program and contains the main body of the program inside its braces { }. In this program, the main( ) function appears as:

    int main(void)
    {
         ...
    }
  • blocks of C statements making up structures or functions are grouped inside curly braces – { }

  • C statements or expressions are terminated by a semicolon ( ; ) – think of it as being similar to sentence that are ended with a period.

  • statements inside curly braces are indented for readability, but the type and amount of spacing used for indenting doesn’t matter to the compiler – consistent spacing just makes the code easier to read and debug.

  • single-line code comments are ignored by the compiler and start with double slashes – //

  • multi-line code comments start with /* and end with */

  • C source code files are written in a code editor or IDE, and these are later compiled into machine code object files by a separate C compiler program.

  • object files must be programmed or downloaded into the memory of a microcontroller before they can be run in the microcontroller.

Step 1 - Create the MPLAB project

Projects for these introductory programs can be created using either of Microchip Technology’s IDEs (Integrated Development Environments). Both the computer-based MPLAB X IDE, or the browser-based MPLAB Xpress IDE work equally well for all of the introductory programming activities, so it doesn’t matter which one you choose to begin with.

Start with either one of the tutorials below – both will lead you through the creation of the programming project for this first introductory activity.

Create your project using the MPLAB X IDE

If you plan to use a computer to create most of your programming projects, the MPLAB X IDE is a more powerful and fully featured IDE and the one you should probably become familiar with first. To create your introductory program using the MPLAB X software, start here: Getting started with the MPLAB X desktop IDE.

Create your project using the MPLAB Xpress IDE

If you plan to program your circuits using a Chromebook, your only choice will be the MPLAB Xpress cloud-based IDE. Begin here: Getting started with the MPLAB Xpress cloud-based IDE.

Do you already know how to use MPLAB X or MPLAB Xpress?

If you are already familiar with MPLAB X or MPLAB Xpress, create or clone your project for this activity from one of the GitHub links, below:

CHRP4 Intro-1-Input-Output programming activity on GitHub.

UBMP4 Intro-1-Input-Output programming activity on GitHub.

Step 2 - Upload the program into your circuit

After creating and successfully compiling (or, in MPLAB terms, building) the program, the next step is uploading the compiled .hex object code file into the microcontroller of your CHRP4 or UBMP4 circuit to watch it run!

The easiest method of uploading a program into CHRP4 or UBMP4 is by using the USB µC bootloader and a USB type-C cable. The USB µC bootloader is pre-loaded into microcontrollers that come as part of mirobo CHRP4 and UBMP4 kits, and the bootloader program is also available separately for uploading into blank microcontrollers.

To use the bootloader:

  1. Connect your circuit to your computer through the appropriate USB Type-C cable and press and release pushbutton SW1 (it’s the one closest to the USB connector). If everything is working, your circuit will be mounted on your computer as an virtual drive named PIC16F1459.

  2. Drag and drop your program’s .hex file onto the PIC16F1459 drive to upload it into the microcontroller’s memory. The program starts running in your circuit as soon as the upload is complete.

For more details on using the USB µC bootloader, including what to do if your circuit doesn’t respond to SSW1 being pressed, refer to the page: Uploading programs using a bootloader.

Instead of using a bootloader, CHRP4 or UBMP4 can also be programmed through their ICSP (In-Circuit Serial Programming) connectors using one of Microchip’s hardware programmer/debugger tools.

Step 3 - Run and test your project

After uploading the program into the microcontroller in your CHRP4 or UBMP4 circuit, leave it connected to your computer and press pushbutton SW2 to see what this program does. If everything is working properly, you will see a flashing light pattern on your circuit’s LEDs!

Program analysis

If you are new to programming, or just new to C programming, there are a lot of new concepts that will be introduced in the next few sections. First, we will explore why this program (and most C programs) are split into multiple source code files instead of being contained in a single program file. Next, we will take a detailed look at the content in two of the program’s files: the main source code file, and the header file. Then, we will examine the structure of the main program file and the contents of its two most important parts: the initialization section, and the main loop. Finally, after developing a better understanding of how all the parts work together, we will analyze how the program reads the pushbuttons and lights the LEDs to make a flashing light pattern.

The four project source code files

Unlike typical beginner programs in other computer languages, the C language program for this introductory project is split into four individual source code files. In fact, all of the introductory and advanced programming projects on this website will have at least four files, and three of these files will remain unchanged (or have just a few slight changes) from one program to the next. Only the main program source code file will significantly change between programs.

Some of the reasons for splitting a program into multiple source code files are that doing so makes it simpler for a human programmer to find and edit the main functional parts of the program, and that it makes the program more modular. Modularity makes it more easy to adapt a program to different hardware designs or new microcontrollers than for large, monolithic programs coded into one single source code file.

These introductory activities actually showcase this adaptability by being designed for use in both the CHRP4 and UBMP4 circuits with minimal differences in their main source code files. The circuit-specific requirements for each circuit board are handled by two customized header and function files.

Below is a list of the four files that make up both the CHRP4 and UBMP4 versions of this project. Take a moment to compare the project file names, and to read the description of each project file:

CHRP4 Files File Descriptions UBMP4 Files
Intro-1-Input-Output.c The main C language program source code file containing both the main( ) program function and the majority of the user code. Intro-1-Input-Output.c
CHRP4.c A program source file containing C language functions to initialize the microcontroller and enable the use of each circuit's I/O devices. UBMP4.c
CHRP4.h A header file containing definitions of the circuit board's hardware features, which are used by both the initialization functions as well as the user's program code. UBMP4.h
PIC16F1459-config.c A processor configuration file used to enable, disable, or select between specific hardware features of the PIC16F1459 microcontroller. PIC16F1459-config.c

See how mirobo programs are structured for a more detailed explanation of the contents of each of the four source code files.

The main program file – Intro-1-Input-Output.c

The file Intro-1-Input-Output.c contains the main( ) program function. A function is a block of related code elements that perform a specific task, and in this case the entire user portion of the introductory program is contained in the program’s main( ) function.

The main( ) function is considered to be the start of any C language program, and the structure of the main( ) function also establishes the overall layout and structure of the program. For all of these reasons, the program source code file containing the main( ) program function is considered to be the main file in C language programs.

In the first few introductory programs, the main( ) function (we will just call it main from now on) also happens to contain all of the user-editable program code. Putting all of the program code into the main function isn’t always desirable, and future projects will explore how and when to add program code into additional functions outside of the main program function. For now though, the focus will be on learning to understand the typical structure of microcontroller programs, and specifically how the main code in this program works.

The first part of the Intro-1-Input-Ouput.c main source code file for the CHRP4 circuit is shown below. As can be seen, the file contains more than just the main function, and its other parts are also important for this program’s operation, including: a header section composed of program comments, a set of #include directives, more comments, and then the contents of the actual main function starting with the main function declaration int main(void). We will examine each of the sections of the program, below.

/*==============================================================================
 Project: Intro-1-Input-Output          Activity: mirobo.tech/intro-1
 Date:    January 10, 2024
 
 This introductory input and output programming activity for the mirobo.tech
 CHRP4 and UBMP4 demonstrates pushbutton input, LED (bit) output, port (byte)
 output, the use of MPLAB's built-in time delay functions, and simple 'if'
 condition structures.
 
 Additional program analysis and programming activities demonstrate the logical
 AND and OR conditional operators, the use of time delay functions to create
 sound output, and software-based simulated start-stop button functionality.
==============================================================================*/

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

#include    "CHRP4.h"           // Include CHRP4 constants and functions

// 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.

// The main function is a required part of every C program. The microcontroller
// begins executing the program starting at the first line in the main function.

int main(void)
{
    // The configuration functions run once during program start-up.
    OSC_config();               // Configure internal oscillator for 48 MHz
    CHRP4_config();             // Configure I/O for on-board CHRP4 devices
    
    // The contents of the while loop repeat continuously.
    while(1)
    {
        // If SW2 is pressed, make a flashy light pattern
        if(SW2 == 0)
        {
            LED2 = 1;
            __delay_ms(100);
            LED3 = 1;
            __delay_ms(100);
            LED4 = 1;
            __delay_ms(100);
            LED5 = 1;
            __delay_ms(100);
            LED2 = 0;
            __delay_ms(100);
            LED3 = 0;
            __delay_ms(100);
            LED4 = 0;
            __delay_ms(100);
            LED5 = 0;
            __delay_ms(100);
        }

        // Add your Program Analysis Activities and Programming Activities code here:

        // Reset the microcontroller and start the bootloader if SW1 is pressed.
        if(SW1 == 0)
        {
            RESET();
        }
    }
}

Can you identify the two different types of comments used in this program? It contains both a comment block as well as single line comments.

The program header

The first, or top part of any program is usually referred to as the program header. The program header typically contains program comments, include statements, and often various function, constant, and variable declarations that are important for the operation of the program – though this program is so simple that it doesn’t actually need any of these declarations.

The top-most part of this program’s header contains a multi-line comment block. Comments can be used to document the program in general or to explain parts of the program code, and the contents of comment lines and blocks are ignored by the C compiler.

Multi-line comment blocks in the C language are enclosed within /* and */ characters. Single line comments in C follow double slash // characters, and either comment out an entire line of code, or just the part of the line following the // characters.

In C language programs, statements that are meant to be a part of the program’s header can either be placed at the top of the program source code file, in separate files known as header files, or both. Aside from the code comment block and the four #include statements just below the comments, the circuit-specific header declarations for this program are actually located in separate header files called UBMP4.h (for the UBMP4 circuit), or CHRP4.h (for the CHRP4 circuit).

How does the content from these separate files get added into this program? It gets added, or included, through the last of the #include directives shown at the bottom of the header:

/*==============================================================================
 Project: Intro-1-Input-Output          Activity: mirobo.tech/intro-1
 Date:    January 10, 2024
 
 This introductory input and output programming activity for the mirobo.tech
 CHRP4 and UBMP4 demonstrates pushbutton input, LED (bit) output, port (byte)
 output, the use of MPLAB's built-in time delay functions, and simple 'if'
 condition structures.
 
 Additional program analysis and programming activities demonstrate the logical
 AND and OR conditional operators, the use of time delay functions to create
 sound output, and software-based simulated start-stop button functionality.
==============================================================================*/

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

#include    "CHRP4.h"           // Include CHRP4 constants and functions

The header #include directives

The four #include directives in the program header include the entire contents of their named files into this program. By including these files, their contents actually become a part of the program, making include directives a simple and effective technique to expand the capabilities of C programs (but more on that in another activity).

The first three included files are commonly found in every PIC microcontroller C program. The fourth CHRP4.h file is unique and has been specifically created for the CHRP4 circuit. Similarly, the header file for the UBMP4 circuit is called UBMP4.h, and one of these two header files, either CHRP4.h or UBMP4.h will be located in your program’s local MPLAB project folder along with the other source code files. Any of the files in the project folder will be easy to open and edit in MPLAB. The other three included files reside in MPLAB system folders, and their actual code is not as easily viewable (though there are ways to see it).

The CHRP4.h and UBMP4.h header files

The included CHRP4.h and UBMP4.h header files contain circuit-specific definitions for each circuit board’s built-in I/O devices, as well as function prototype declarations for pre-made functions located in each circuit’s function code files (CHRP4.c for the CHRP4 circuit, and UBMP4.c for the UBMP4 circuit). We will examine some of the pre-made functions a bit later, but first let’s take a look at the I/O definitions in the header file.

#define directives

The header file’s #define directives are used to create meaningful alternate names, or aliases, for the circuits and electronic components connected to the hardware pins of the microcontroller. A few selected #define directives from the CHRRP4.h file are shown below:

// PORTA I/O pin definitions
#define SW1         PORTAbits.RA3   // SW1/PROG/Reset (MCLR) pushbutton input
#define BEEPER      LATAbits.LATA4  // Piezo beeper (LS1) output
#define	LS1         LATAbits.LATA4  // Piezo beeper (LS1) output
#define D1          LATAbits.LATA5  // LED D1/RUN LED output (active-low)
#define LED1        LATAbits.LATA5  // LED D1/RUN LED output (active-low)

These definitions make it easier for users to program their circuit by enabling them to refer to each I/O device by its name, which is clearly printed on the circuit board, rather than by the more obscure name of the microcontroller port pin it connects to. For example, a user can control LED D1 simply by using either of its defined names, D1 or LED1, instead of constructing the longer port and pin specifier LATAbits.LATA5 after tracing the wiring from LED D1 to the microcontroller on the schematic.

An interesting aspect of both #include and #define directives is that they don't themselves become a part of the final program run by the microcontroller. Both types of directives obviously provide important information that the compiler will use during the process of building the program, but the actual include and define statements will not get converted into the machine code instructions that will be run by the microcontroller because they are only of use to the compiler. This is both interesting and important to build a better understanding of how the C language and its compilation process works. Notably, C program source code contains both instructions for the microcontroller as well as directive and other useful information for the compiler, including information that is helpful for us human programmers!

Alternative hardware names can be created using the #define directives. The comments after the double slashes // provide additional information. All of these definitions are found in an external header file, rather than being written into the header section of the main program.

The main( ) function

Every C language program must contain one main( ) function (we will refer to it as main from now on), and in this program the main function is found in the main Intro-1-Input-Output.c program source code file. The main function itself starts just below the header #include statements with the function declaration int main(void), as shown below:

int main(void)
{
    // The configuration functions run once during program start-up.
    OSC_config();               // Configure internal oscillator for 48 MHz
    CHRP4_config();             // Configure I/O for on-board CHRP4 devices
    
    // The contents of the while loop repeat continuously.
    while(1)
    {
        // If SW2 is pressed, make a flashy light pattern
        if(SW2 == 0)
        {
            LED2 = 1;
            __delay_ms(100);
            LED3 = 1;
            __delay_ms(100);
            LED4 = 1;
            __delay_ms(100);
            LED5 = 1;
            __delay_ms(100);
            LED2 = 0;
            __delay_ms(100);
            LED3 = 0;
            __delay_ms(100);
            LED4 = 0;
            __delay_ms(100);
            LED5 = 0;
            __delay_ms(100);
        }

        // Add your Program Analysis Activities and Programming Activities code here:

        // Reset the microcontroller and start the bootloader if SW1 is pressed.
        if(SW1 == 0)
        {
            RESET();
        }
    }
}

The main function contains of all of the code within the left-most set of curly braces { } in the code – from the top opening brace { right below the int main(void) declaration, to the bottom closing brace }, the last of the three closing braces in a row. Sets of braces group all of the program code within them together, and the spacing of the braces from the left margin shows the code hierarchy. So, in this program, the main function contains all of the program code, and the while(1) loop within main contains two separate blocks of if conditional code.

Both the MPLAB X and MPLAB Xpress IDEs will try to help programmers manage the proper spacing and grouping of program code, and the indenting is automatically done to help the programmer to understand the program structure and logically group part of the program together.

The structure of main( )

Let’s look at the structure of the main function since it is essentially divided into two parts. The top of the main function, following the int main(void) function declaration, consists of a code comment and the following two lines of code:

    // The configuration functions run once during program start-up.
    OSC_config();               // Configure internal oscillator for 48 MHz
    CHRP4_config();             // Configure I/O for on-board CHRP4 devices

As can be gleaned from both the comment and the names of the function calls, this section of the main function includes configuration statements that are used to configure some microcontroller or circuit board features and will run just once when the program starts up. We will explore how these configuration functions work in a moment, but first let’s take a look at the next part of the code in the main function:

    // The contents of the while loop repeat continuously.
    while(1)
    {
        // If SW2 is pressed, make a flashy light pattern
        if(SW2 == 0)
        {
            LED2 = 1;
            __delay_ms(100);
            LED3 = 1;
            __delay_ms(100);
            LED4 = 1;
            __delay_ms(100);
            LED5 = 1;
            __delay_ms(100);
            LED2 = 0;
            __delay_ms(100);
            LED3 = 0;
            __delay_ms(100);
            LED4 = 0;
            __delay_ms(100);
            LED5 = 0;
            __delay_ms(100);
        }

        // Add your Program Analysis Activities and Programming Activities code here:

        // Reset the microcontroller and start the bootloader if SW1 is pressed.
        if(SW1 == 0)
        {
            RESET();
        }
    }

Again, the comments provide an obvious clue about the next part of the code – this entire section of program code repeats forever! But, why does it need to do that?

It makes complete sense if you think about the kinds of tasks microcontrollers typically do. Microcontrollers typically control devices or processes that generally don’t stop doing whatever it is that they are designed to do. The microcontroller running inside a television remote control, for example, only ever needs to run the program that makes it operate as a remote control. After its batteries are installed and the television remote control’s program starts up, it just needs to repeatedly check for a button press, and transmit a coded signal to the television set whenever a button is pressed. After the button is released, it waits for the next button press which will repeat the process again. Wait for a button press, send a code – forever. Or, at least until the batteries die or are removed!

This type of program structure – a small configuration section followed by an infinite loop – is the most common way that simple microcontroller programs end up being designed and structured, because it is an easy way to control a device or process. Now that we have a better understanding of the structure of this program, let’s dive into the details of how this introductory program works.

The main function consists of all of the code inside the left-most set of opening { and closing } braces. All of the program code inside the main function is indented to show the reader that it belongs to and is a part of the main function.

The configuration functions

Remember these two configuration statements? Let’s learn about what they do.

    OSC_config();               // Configure internal oscillator for 48 MHz
    CHRP4_config();             // Configure I/O for on-board CHRP4 devices

Each of these statements is actually a function call – the statement does not do the action itself, but instead calls a function containing the program statements to accomplish this task. Function calls can be identified by the brackets ( ) at the end of the function’s name. So, if these are both function calls, where are the actual program statements that make up the functions? Didn’t we learn that the main function contains all of the program code for this program?

Well, no. The entire user part of this program is in the main function, but if you recall how mirobo programs are structured, and that this program is composed of four files, you might remember that additional program code and functions are found in some of the other three project files. One of the three files contains the configuration code for these functions.

Why did we not just put all of the configuration code into this file, with the rest of the main program? Well, recall that this program file is the same for both the CHRP4 and UBMP4 circuits, but the circuits themselves are different. Using separate CHRP4.c (for the CHRP4 circuit ), or UBMP4.c (for the UBMP4 circuit) configuration files allows the same program to be customized to run in two different circuit boards.

Let’s take a look at the code that makes up the CHRP4_config function in the CHRP4.c file (the UBMP4_config function in the UBMP4.c file is very similar):

// Configure hardware ports and peripherals for on-board CHRP4 I/O devices.
void CHRP4_config(void)
{
    OPTION_REG = 0b01010111;    // Enable port pull-ups, TMR0 internal div-256

    LATA = 0b00000000;          // Clear output latches before configuring PORTA
    ANSELA = 0b00000000;        // Disable analog input on all PORTA input pins
    WPUA = 0b00001000;          // Enable weak pull-up on SW1 input only
    TRISA = 0b00001111;         // Set LED D1 and Beeper pins as outputs

    LATB = 0b00000000;          // Clear output latches before configuring PORTB
    ANSELB = 0b00000000;        // Disable analog input on all PORTB input pins
    WPUB = 0b11110000;          // Enable weak pull-ups on pushbutton inputs
    TRISB = 0b11110000;         // Enable pushbutton SW2-SW5 inputs
//    TRISB = 0b11010000;         // Enable SONAR module TRIG output, ECHO input

    LATC = 0b00000000;          // Clear output latches before configuring PORTC
    ANSELC = 0b00000000;        // Disable analog input on all PORTC input pins
    TRISC = 0b00001100;         // Enable phototransistor Q1/Q3, Q2/Q4 inputs

    // TODO - Enable interrupts here, if required.
}

The C statements in the CHRP4_config( ) function are typical assignment operations – in this case, various microcontroller register are each being assigned a binary value. The purpose of these statements is to prepare the microcontroller’s I/O (input and output) ports for interfacing with the circuitry attached to them. Writing the configuration values in binary makes it somewhat easier for human programmers to match the numeric bits to the I/O pins. Exactly what is being done by each specific statement isn’t important for understanding the rest of this program now, and will be covered in more detail in future activities. You can read more about PIC I/O ports on the how PIC I/O ports work page if you would like to have a better understanding of what this function is doing. For now though, it’s enough to understand that the assignment operations in this function are configuring the microcontroller’s I/O ports for use in its circuit board.

The great thing about the two configuration functions is that, as a programmer new to mirobo circuits and probably even new to programming PIC microcontrollers, you didn’t need to write the functions, and you don’t even need to know how they work in order to use them! The functions can simply be launched by single-line function call statements in the main file, and their operations will perform the required tasks.

Depending on your point of view, another great thing about these functions is that all of their code isn’t cluttering up the program code in the main file. The corresponding drawback, of course, is that the function code is in a separate file, removed and somewhat hidden from the main code, meaning that you have to go to the trouble to open a different file to get a look at or edit the function code.

One strange thing that you may have already thought about is how the compiler would know about the configuration code in the function file. Remember that the #include statements in the main file included only the CHRP4.h circuit board header file, but not the CHRP4.c circuit board function file that these functions are a part of. So, how do the contents of this function file get included in the program?

The board function file is simply made a part of this programming project when the project itself is set up in MPLAB. The header file is also a part of the programming project, but it had to be specifically included at the beginning of the program because it contains the declarations statements defining these functions, including information that the compiler needs to know in order to use the functions. By including the header file, the compiler will obtain this important information before continuing to compile the rest of the program. During compilation, MPLAB will find the functions in the function files, and will use the information obtained from the header file to link the functions into the rest of the program code.

Whew, that’s a lot of information for a small piece of the program that only runs once! The most important things for you to remember about this startup code are: the configuration function code is necessary to configure the microcontroller to control all of the circuits on its circuit board; it’s completely possible and normal to use functions without fully understanding what their code is doing; and, this same configuration code will be used by every one of these introductory programming activities.

Now, let’s move on to the fun part – main loop of the program.

The main while loop

Finally! The most important part of this program, and all microcontroller programs, is the main while loop. The repeating part of this program loop is coded into a while structure starting with the while(1) conditional statement:

    // The contents of the while loop repeat continuously.
    while(1)
    {
        // If SW2 is pressed, make a flashy light pattern
        if(SW2 == 0)
        {
            LED2 = 1;
            __delay_ms(100);
            LED3 = 1;
            __delay_ms(100);
            LED4 = 1;
            __delay_ms(100);
            LED5 = 1;
            __delay_ms(100);
            LED2 = 0;
            __delay_ms(100);
            LED3 = 0;
            __delay_ms(100);
            LED4 = 0;
            __delay_ms(100);
            LED5 = 0;
            __delay_ms(100);
        }

        // Add your Program Analysis Activities and Programming Activities code here:

        // Reset the microcontroller and start the bootloader if SW1 is pressed.
        if(SW1 == 0)
        {
            RESET();
        }
    }

As can be seen from the braces { }, the entire block of code, above, is a part of the while(1) loop. It’s also important to understand that this while structure is in the form of an infinite loop (a loop that goes on forever) because the condition inside its brackets is set to be 1 – equivalent to being logically true.

The brackets of a while statement normally contain a conditional expression that would be evaluated to either be true (1) or false (0) at run time. For example, the expression while(total < 10) would be evaluated as true (or 1) if the value of total is less than 10, or false (or 0) if the value of total is 10 or more. Forcing the expression to be true by putting a 1 inside the brackets makes the contents of the while loop repeat forever because the condition is always true.

If SW2 is pressed…

Inside the while loop are two if( ) decision structures. The if conditions are checked in turn, and each block of code can run if its condition is true. In the first one, the if expression reads the state of switch SW2 and compares its value to 0. The comparison of two values for equality in the C language is always performed using two equal signs == , and this is also the case in many other programming languages.

        if(SW2 == 0)
        {
            LED2 = 1;
            __delay_ms(100);
            .
            .
            .
        }

The reason that SW2 is checked for a value of 0 is that the pull-up switch circuits on CHRP4 or UBMP4 will cause the microcontroller pins to ‘pulled-up’ to the 5V power supply level and read as a high voltage, or logic 1, when the switches are released. When they are pressed, the switches connect the microcontroller pins to ground and are read as a low voltage, or logic 0.

So, whenever pushbutton switch SW2 is pressed its level is read as 0, and the if statement’s condition is evaluated to be logically true. When this happens, all of the code inside the set of curly braces below the if statement will run – once. The first statement to run when the condition is true turns LED2 on. A single equal sign = is used to assign a value in C statements, so the line LED2 = 1; writes a logic 1 to the LED2 output latch, causing the associated microcontroller pin, the pin connected to LED D2 (defined in the CHRP4.h or UBMP4.h header files) to output 5V. A 5V level on an LED pin causes current to flow through its connected resistor and LED, turning the LED on.

Next, the __delay_ms(100); statement calls a built-in compiler function to produce a 100 ms (or 0.1 s) time delay. The data inside the brackets (100) is passed to the delay function and specifies how much delay is needed. This delay function is a part of MPLAB, and did not have to be added in the project’s program files.

The rest of the LED on and off commands and time delays that follow these first two statements are executed in sequence, turning each of the LEDs on and then off, with delays between each step, until the program reaches the closing curly brace of the if structure. At this point the execution of the conditional if structure is complete, and the program continues to the next statement below its closing brace.

What if SW2 is not pressed?

If SW2 is not pressed the pushbutton’s state would be 1, making the first if condition false, and causing all of the code inside the if structure’s curly braces to be bypassed. The program would simply continue to the next if statement, located below the closing brace of the first one in the program, and get to the code shown below:

        // Reset the microcontroller and start the bootloader if SW1 is pressed.
        if(SW1 == 0)
        {
            RESET();
        }

This second if statement checks if pushbutton switch SW1 is pressed. If it is, the RESET( ) function causes a software reset of the microcontroller, forcing the program to restart. In microcontrollers pre-programmed with a bootloader, the bootloader code will always run first after a reset – instead of the user’s program – to check for a connection with a computer for re-programming the microcontroller. (See the page on uploading programs using a bootloader for more information on how the bootloader works.)

Finishing the main loop

If SW1 is not pressed, the microcontroller will have reached the end of the program code in the main while loop. Since the while condition was forced to always be true, program execution will automatically resume from the beginning of the while loop, causing the program to repeat these same two if conditions forever. SW2 and SW1 will be checked in turn, repeatedly, making pretty flashing patterns whenever button SW2 is pressed, until SW1 is pressed to reset the microcontroller and start the bootloader, or until power is removed from the circuit.

The 1 in the while statement’s conditional brackets set the condition to true, which causes all of the code inside the while statement’s curly braces to repeat forever.

Learn more – Program analysis activities

Hooray, if you made it this far you should have a basic understanding of how this program flashes LEDs in response to a button press! Next, we will modify parts of the existing program code, building on the concepts introduced earlier and helping to further your understanding of microcontroller operation and C language features.

A condensed version of some of these analysis activities is embedded in the comment block below the source code in the program to make it easier for you to copy and paste the example code directly into the program from inside MPLAB. Add the new program instructions at the location shown by the single line comment in the program code – it’s located right between the two if structures.

1. Microcontrollers are fast!

Microcontrollers are fast, and they can only do one thing at a time – in this example at least. Hardware modules can allow PIC microcontrollers to perform certain kinds of tasks while the software is doing something else, but that capability is not being used here. In this program, everything is accomplished through software with one instruction following another, in sequence and controlled by the microcontroller’s clock oscillator.

How fast is our microcontroller’s clock? The PIC16F1459 microcontroller used in CHRP4 and UBMP4 is configured to run at its maximum clock frequency of 48 MHz (the microcontroller can be run slower, but this speed is necessary to use the USB bootloader). The oscillator frequency is internally divided by 4 to produce a 12 MHz instruction cycle frequency. Due to its RISC core, this microcontroller can actually execute 12 million machine code instructions per second, which works out to one instruction every 83.3 ns (or 0.000 000 083 3 s, or in 83.3 billionths of a second!). That’s pretty fast, but still a whole lot slower than your average desktop microprocessor!

Let’s apply this concept to the program and try an experiment to see how fast the microcontroller is. Comment out the delay following each of the statements that turns one of the LEDs on or off by adding two slashes before each delay statement, like this:

            LED2 = 1;
      //      __delay_ms(100);
            ...

What do you think will happen when the program runs without the delays enabled? Re-build the program, upload it into your circuit, and try it out. Press and release SW2 and observe the circuit’s LEDs.

What did you notice? Could you see the LEDs turning on and off? Do you think they are still turning on and off?

Commenting out the delay after each LED off statement will still turn them off in sequence, but with no delay so they will seem to turn off simultaneously. There will be a slightly longer delay from when the last LED is turned off until the fist LED is turned on again – due to the instructions being run to check SW1 and return to the top of the while loop – but this will also be too short to notice.

It actually takes the microcontroller a few instructions to test and skip all of the code in a a false if condition, and it also takes a few instructions to jump from the end of the while loop back to the top of the program. In total, if no buttons are pressed, it should take the microcontroller only a dozen or so instructions to completely run through the main while loop once. Sixteen instructions works out to about 2µs (two microseconds), or two millionths of a second. This means that from the time you press pushbutton SW2 until the first LED turns on, the maximum length of time you will have to wait is going to be less than 2µs! It will appear to happen instantly.

Try it for yourself. Build the code with these changes and download it into your circuit. Now randomly press pushbutton SW2. Can you detect a delay between when the button is pressed and when the first LED lights?

Next, press and hold pushbutton SW2. Can you see a delay between the LEDs turning off, or between the last LED turning off and the first LED turning on after making the changes to your code?

All of this happens so fast that you won’t be able to notice it. You will need test equipment such as an oscilloscope or logic analyzer to actually measure the delay.

Microcontrollers can only do one thing at a time

Now that we know how fast microcontrollers are, let’s verify that this program can only do one thing at a time. Start by un-commenting the delays, to return the code to its original state. Build the program and download it into your circuit board.

Quickly press and release pushbutton SW2, once. You should see the flashing pattern light up and turn off the LEDs in sequence, once. Press and hold pushbutton SW2. You should see the flashing pattern continue while you hold the button. What do you think will happen if you press SW2 twice, really quickly, so that both button presses happen during the time it takes to do one complete flashing pattern?

If you think about how other circuits you may have come across work, you might predict that the program will respond with one of these three possible outcomes:

  1. The one thing at a time outcome. The flashing pattern will start immediately after the first button press and will continue until the flashing pattern ends after just one cycle, ignoring the second button press. The circuit will not start the flashing cycle again until SW2 is pressed after the cycle has ended.

  2. The rapid-fire outcome. The flashing pattern will start immediately after the first button press, and the second button press will immediately re-start the flashing pattern. Every new press of SW2 will re-start the pattern no matter where in the pattern the program happens to be.

  3. The button memory outcome. The flashing pattern will start immediately after the first button press, complete the whole first flashing pattern, and then start a second pattern since the microcontroller knows that SW2 was pressed twice. The pattern will flash once for every press of SW2.

Try it! Which outcome is correct? Why? It helps to understand that microcontrollers execute one instruction at a time, in order, to work their way through a program. Using this logic, the correct outcome has to be the first one.

Here’s why. After SW2 is first pressed, the program starts to execute the instructions inside the curly braces of the SW2 if structure. While each LED instruction and delay function within the if structure is being run, the program does not check SW2 again, and will not know (or care) if SW2 is pressed or released, eliminating outcome 2. SW2 will only be sensed again after the first full flashing cycles ends, when the while loop is re-run. If the second SW2 press happened during this time, there is no memory of it. Bye-bye outcome 3.

Assignment and conditional operators

In the program, the statement LED2 = 1; uses the equal sign as an assignment operator. The value 1 is assigned to the port pin referenced by LED D2, setting its output high and causing LED D2 to turn on.

The statement if(SW2 == 0) uses two equal signs as a conditional operator instead of one. Why is that? It would seem that wording the conditional statement with a single equal sign would make just as much sense, and be easier for programmers to remember while coding than having to remember to use two equal signs.

The answer to this goes back to the early days of programming languages. While the statement if(SW2 = 1) makes perfect sense to us humans as a comparison between the SW2 input and logical level 1, the operation inside the brackets (SW2 = 1) is typically used to assign the value of logical 1 to SW2. A computer would have to decide when one equal sign means we want to assign a value, or when one equal sign means we want to use it to compare values instead.

Getting the computer to make this decision would require extra code, making the code in the compiler more complex, larger, and slower. And, in the early days of computers, with their slow processors and limited memory, that was just not a desirable option when a simpler solution exists. The simpler solution was of course to ask the author of the program code to decide – after all, the programmer should know what they intend to do – and can tell the computer to compare values by using two equal signs, or to assign a value using a single equal sign.

Conditional operators

This program uses the double equal sign (==) conditional operator to check for equality, and there are five more conditional operators shown in the chart, below. Any of these can be used within the conditional expression brackets of both if statements and while statements in your programs.

Operator Condition
== equal to
!= not equal to
> greater than
>= greater or equal to
< less than
<= less or equal to

I/O pins and ports

The statement LED2 = 1; turns on an output pin. How do you know which pin it is?

Remember that LED2 (or D2) is the reference name of one of the components on your circuit board. LED D2 is connected to a physical pin on the PIC16F1459 microcontroller. The schematic diagram can be used to check which microcontroller chip pin connects to any component on the circuit board. Another way to find out is by looking up the pin definitions in the circuit board header file.

In either case, LED2-LED5 on both CHRP4 and UBMP4 are connected to the RC4-RC7 (PORTC) pins of the PCI16F1459 microcontroller, and these outputs are controlled by the LATC register. Each bit in the LATC RAM location controls one pin of port C. The code, below, uses LATC expressions to write all eight bits of the PORTC output latches simultaneously, instead of writing one bit at a time using the LED statements.

Copy and paste this code into your program below the existing SW2 if structure, at the location shown by the comment in the main section of the code. After compiling the code and downloading it into your circuit, you should see all of the LEDs flashing simultaneously when you press and hold pushbutton SW3.

        if(SW3 == 0)
        {
            LATC = 0b00000000;
            __delay_ms(100);
            LATC = 0b11110000;
            __delay_ms(100);
        }

This works because the binary value 11110000 has ones in the bit locations corresponding to the microcontroller output pins connected to the four LEDs attached to port C. When writing a binary value in C, it has to be pre-fixed with the binary 0b radix selector as shown in the LATC = 0b11110000; instruction. And, since this instruction is the last instruction to modify LATC in this if condition, it will leave the LEDs on after SW3 is released.

Controlling multiple PORTC pins using a single LATC assignment statement is easier and takes less code than using separate statements to turn on the LEDs as was done in the original if structure. It is also faster, since one LATC assignment operation will compile into fewer machine code instructions than four individual LED assignment operations. So, if this method of writing to all of the port pins at once produces simpler, smaller, and faster code, why wouldn’t we use it all of the time?

One reason is that even though these circuits have four LEDs connect to port C, the LATC command overwrites all eight pins of the port. What are the other pins of PORTC used for? Is it ok to overwrite them with zeroes? Without knowing what they are connected to and, more importantly, what their values should be, blindly overwriting them might cause unexpected results or even more serious problems for our circuit. While LATC commands are fast and powerful, they have to be used very carefully (remember the rule: with great power comes great responsibility!).

Introductory Activity 5 will look at logical bit operations that can be used to selectively change specific bits within a port without overwriting them all. For now though, realize that LATx operations dangerous because they will also potentially change bits you may not have wanted to change.

Comparing if and while

When an if condition in a program is true, the code immediately following the if statement is executed one time. The code after the if statement is usually grouped inside a pair of curly braces, although a single line statement without curly braces is also permitted. For predictability and consistency, it is a good idea to always use curly braces. This is important because without curly braces any lines of code added below the first line of the if statement will always execute, regardless of whether the if condition is true or false!

For example, the code below is indented so it looks like both LED3 and LED4 should only turn on if SW3 is pressed, but if you run this code LED4 will always turn on, regardless of the state of SW3:

if(SW3 == 0)
    LED3 = 1;		// Turns on only if SW3 is pressed
    LED4 = 1;		// Always turns on

To ensure both LEDs are controlled by SW3, group both statements inside curly braces as shown below:

if(SW3 == 0)
{
    LED3 = 1;		// Both LEDs turn on if SW3 is pressed
    LED4 = 1;
}

While conditions can be used in the same way as if conditions, with one important difference being that the code in the braces below the while statement will be repeated while the condition remains true. Add this code to your program, below the SW2 and SW3 if conditions:

        // Momentary button using an if structure
        if(SW4 == 0)
        {
            LED4 = 1;
        }
        else
        {
            LED4 = 0;
        }

        // Momentary button using a while structure
        while(SW5 == 0)
        {
            LED5 = 1;
        }
        LED5 = 0;

To see the difference in operation between if statements and the while loops, try pressing and releasing SW4 and SW5 one at a time. Pressing each button should light its associated LED until the button is released.

Next, press and hold SW4 while pressing and releasing SW5. It should work similarly as before – each LED should light when its pushbutton is pressed, and in this case LED4 will stay lit while LED5 turns on.

Next, try press and holding SW5 while pressing and releasing SW4. You will notice that LED4 does not turn on at all when SW4 is pressed. Can you figure out why from the code?

Pressing and holding SW5 keeps its while condition true, and the microcontroller keeps running the code inside the SW5 while loop, alternating between lighting LED5 and checking SW5 – but it can’t check the state of SW4 from inside the loop. Only after SW5 is released, when the while the condition is false, will the microcontroller be able to run the rest of the program code and check the state of SW4.

This gives us a valuable insight about the differences between if and while statements: use if statements when you need to have your program respond to different conditions quickly, or use while statements when you want one condition to repeat.

There’s something else

Did you notice the else statement below the if condition in the above example? Rather than adding a second if condition to that will be true if SW4 is released, the else block will be true whenever the original if condition is false. This saves a bit of conditional code, and, more importantly, links the two actions together.

In fact, additional conditions can be linked with else if conditions, as shown in the example below. One or more else if conditions, and one optional else condition can be added to any if statement. Check out the sample code below:

        // Checking multiple conditions
        if(SW4 == 0)
        {
            LED4 = 1;
            LED5 = 0;
        }
        else if(SW5 == 0)
        {
            LED4 = 0;
            LED5 = 1;
        }
        else
        {
            LED4 = 0;
            LED5 = 0; 
        }

Each linked if condition will will be evaluated in turn, and the first condition that is true will be executed. If a stand-alone else statement is added and none of the prior if or else if conditions are true, the else statement will automatically be true.

Logical AND conditions

One way to create a logical AND operation is by nesting two if conditions. Both the first condition as well as the condition nested inside it must be true for the code in the inner if condition to run. Make a logical AND operation using the program code below to replace the if and while code if you added in the step above. After building and downloading the program, test the program to ensure LED D4 will only light if both SW4 and SW5 are pressed simultaneously.

        // Nested if 'AND' code
        if(SW4 == 0)
        {
            if(SW5 == 0)
            {
                LED4 = 1;
            }
            else
            {
                LED4 = 0;
            }
        }
        else
        {
            LED4 = 0;
        }

Does the order of the if conditions matter? It shouldn’t, as both conditions have to be true in order for the code inside the second condition to run. You can think of these two if conditions as the software equivalent of two switches in series – current will only flow through the series circuit to the load if both switches are closed. In contrast, two separate if conditions act like switch circuits wired in parallel.

Instead of nesting the conditions, a logical AND operator within the conditional statement can make the code more compact, especially when a number of conditions need to be combined in an if statement. A logical AND is composed of two ampersands && as shown in the code below. Try it by replacing the code added above, with this new code and verify that it works in the same way:

        // Conditional 'AND' code
        if(SW4 == 0 && SW5 == 0)
        {
            LED4 = 1;
        }
        else
        {
            LED4 = 0;
        }

The conditional statement above is read as ‘if SW4 is equal to zero and if SW5 is equal to zero’, and if that combination of conditions is ever true, LED D4 will turn on.

Logical OR conditions

A similar OR logical operator can be used to replace multiple, separate if conditions. Replace the double ampersand && in the example above with double vertical bars || to make a logical OR conditional operator. The program code should now look like this:

        // Conditional 'OR' code
        if(SW4 == 0 || SW5 == 0)
        {
            LED4 = 1;
        }
        else
        {
            LED4 = 0;
        }

After building and testing the code using the OR operator, pressing either SW4 or SW5, or both, will turn LED4 on. You can think of an OR operation as the equivalent of a parallel circuit – the two switches are wired in parallel to a single load and pressing either pushbutton will allow the current to flow to light the LED.

There is one more logical operator that is less commonly used than AND and OR in this type of input program, and that is the XOR operator. XOR represents the exclusive-OR condition, which turns on the LED if either SW4 is pressed, or SW5 is pressed, but not both. An XOR operation is made by using two caret symbols ^^ in some programming languages but this operator is not supported in C. Since an XOR operation represents only one of the inputs being true, and not the other, it can be replaced by the not equal to operator, !=. This table summarizes all of the logical operators:

Operator Operation
&& logical AND
|| logical OR
!= logical XOR

Activity 1 learning summary

C language programs are often composed of one or more source code files. Header files contain information about the program, its functions, register names, and constants. Definition statements in header files allow programmers to provide more informative names for microcontroller I/O pins. Source files contain program instruction statements and function routines, and can all be placed into one main program file, or spread between multiple program files.

The main( ) function is the beginning of a C language program, and typically contains functions or instructions that will run once, followed by the main program instructions which are in a loop that will repeat forever. The separate parts of programs and program structures are grouped inside nested pairs of curly braces, and it’s important to maintain their hierarchy when creating and editing the program code.

Input/output (I/O) circuits are connected to microcontroller pins, and the state of those pins is read or written by program instructions through RAM registers inside the microcontroller. I/O port pins can be read or written individually, or in groups by using port instructions.

Conditional statements allow programs to make decisions based on inputs. If statements evaluate single or logically grouped conditions once to determine whether or not to perform another action. While statements evaluate their conditions repeatedly, looping program instructions until the condition becomes untrue, or false.

Logical operators such as AND and OR and commonly used to evaluate more complex conditions combining multiple inputs.

Activity 1 Programming Challenges

1. The statement __delay_ms(100); creates a 100ms delay. Try changing one or more of the delay values in the program to 500, for a 500 ms (0.5s delay) and see what happens.

Can the delay be made even longer? Try 1000 to make a 1 second delay.

At some point it might be important to make a really long delay, but we don’t yet know if there is a limit to how long a delay can be. There is actually a limit, and if you try using a really big number as the delay value the C compiler will produce an error. How big can the delay get before the compiler produces an error message? (Hint: can you think of a fast and efficient way of guessing an unknown number?)

2. The __delay_ms( ); function accepts only integers as delay values (whole numbers, without decimals). To create delays shorter than 1 ms, the C compiler provides a different function. Use the __delay_us( ); function to specify delays in microseconds.

You won't be able to see microsecond length LED flashes with your eyes, but you can measure them using an oscilloscope, or hear them if they are used to turn a piezo beeper on and off instead of an LED. Try the following code in your program:

        // Make a tone while SW5 is held
        if(SW5 == 0)
        {
            BEEPER = 1;
            __delay_us(567);
            BEEPER = 0;
            __delay_us(567);
        }

Try changing the delay values in both of the __delay_us( ); functions. Does the pitch of the tone increase or decrease if the delay value is made smaller? Does the opposite happen if the delay is made larger?

3. This code demonstrates a more compact way of toggling the beeper output using a logical NOT operator – the exclamation mark symbol ( ! ). Replace the code in the example above with this code:

        // Make a tone while SW5 is held
        if(SW5 == 0)
        {
            BEEPER = !BEEPER;
            __delay_us(567);
        }

One difference between this code and the code in activity 2, above, is the state of the BEEPER pin when SW5 is released. Will you know what state the BEEPER output will be in after this code runs? While one advantage of this method is less program code, can you think of one or more disadvantages of not knowing the output state of a pin when using code like this? List at least one disadvantage of this type of code.

4. Using modified versions of the original SW2 'if' structure, create a program that makes a unique LED flashing pattern for each pushbutton.

Test each of your flashing patterns. Describe what happens when more than one button is held. Do all of the patterns try to flash the LEDs at the same time, or sequentially? Explain why this is so.

5. Create a program that makes a different tone for each pushbutton.

Test each tone by pressing each button individually. Next, press two or more buttons at the same time. Describe what you think the tone waveforms will look like when two or more buttons are held. (You can verify your prediction if you have access to an oscilloscope.)

6. Use individual 'if' structures to simulate individual 'Start' and 'Stop' buttons for an industrial machine. Make LED D3 turn on when SW3 is pressed, have LED D3 stay on even after SW3 is released. Use another if structure for pushbutton SW4 to turn LED D3 off when pressed. Test your program to make sure it works.

7. Running your program from activity 6, above, describe what happens when both the SW3 and SW4 pushbuttons are held. Does LED D3 stay on? If so, how does its brightness compare to when only SW3 is pressed and released? If the brightness is different, can you explain what part of the code causes it to change, and why it changes?

8. As you might imagine, an industrial machine that is able to turn on even while its 'Stop' button is pressed represents a significant safety hazard. Using one or more of the logical conditional operators introduced in the analysis activities, above, modify your start-stop program to make it safer. LED D3 should turn on only if pushbutton SW3 is pressed while pushbutton SW4 is not being pressed.

9. LED D1 is normally used to indicate that a program is running, but it can be controlled by your program as well. If you examine the CHRP4 or UBMP4 schematic diagrams, you will see that LED D1's cathode (or negative) pin is connected to the microcontroller instead of having its anode (positive) pin connected to the microcontroller as the other LEDs are. This means that the LED D1 output must be made equal to 0 to turn D1 on.

Try it! Make a program that controls or flashes LED D1 as part of a light pattern. Be careful that you don't change the SW1 reset code when making your light pattern, or you may not be able to use switch SW1 to reset the board and enter programming mode using the bootloader. (If this happens, unplug the USB cable and press and hold SW1 while re-connecting the USB cable. LED D1 will remain off until you release SW1, and then the bootloader will start. Then, be sure to re-enable the SW1 reset code that your code edits accidentally disabled.)

Introductory Programming Activity 1 - Input-Output Program