In addition to the software, you will need the following hardware:
1 Battery or Voltage Source 9-12V
1 PIC12F629 Microcontroller
1 LED with a forward current ~20-24mA
1 10K Resistor
1 Current Limiting Resistor for LED
1 0.1uF Capacitor
1 LM7805 Voltage Regulator IC
1 Normally-Open Momentary Switch
1 Breadboard
Jumper Wires
Creating a New Project
Figure 1 |
Upon opening MPLAB X IDE you will see the program's Start Page as shown in figure 1. Under the Dive In section of the start page, click "Create New Project". Alternatively, you can click File>Create New Project. In the new project window you should see that under the categories column "microchip embedded" is selected and "standalone project" under the projects column is selected, click next. We'll now select our device (the MCU we're using). On the family drop down menu, select "Mid-Range 8-bit MCUs", and then "PIC12F629" under the device drop down menu. Click next. The next step will ask you to select a Supported Debug Header which we will leave selected to "None", then click next again.
The next step is to select a tool. This is where we select which programmer we're going to use. I have the PICkit 2, which I have selected for my project. Click next. The next screen asks us to choose a compiler and you should see the XC8 compiler listed there. XC8 only appears in this menu after you have installed the compiler, so if you don't see it, make sure that you installed the XC8 compiler software. After selecting XC8, click next. Finally, we need to name our project. Let's name our project "learn_c", and click finish.
We should now see our projects in the top-left portion of the IDE, as seen in figure 2. This is a blank project (there are no files associated with it), so we need to add a file before we can start writing a program.
Figure 2 |
Figure 3.1 |
Figure 3.2 |
Our First C Program
Our first program is very simple. It will teach you the basic input/output interface by illuminating a light emitting diode when we press a button. The LED will remain lit for as long as we're pressing the button, and will extinguish when we release the button. The main purpose is to show how to read an input, and how to place data on an output.
The first thing we're going to want to do is plan out what I/O pins we want to use for the button and the LED. Planning the hardware in advance is necessary for programming. The PIC12F629 datasheet contains the pin diagram for the MCU as seen in figure 4.
Figure 4 |
Reading through the datasheet, we discover that the GP3 pin is an input only port, which means that we cannot use it to output data. Let's use GP3 (pin 4) for our button input. We'll select GP0 (pin 7) as the output for our LED. A schematic and breadboard image of this project is at the bottom of this post so you can build it and follow along.
Let's have a look at the program now, then we'll walk through it.
Figure 5 |
As we can see at the very top of the program, I have made use of the ability to place comments in the code. It is good practice to place a description of the program at the top of each program we write. Following the comments, we need to include the xc header file. As stated before, the header files contain a bunch of stuff that the compiler needs in order to generate code. The xc.h file automatically includes the MCU specific header file so that all of the definitions and addresses of the special function registers are there. Remember to place the file name within the <> symbols. I have made use of comments to describe what the program is doing, and also to break up sections of the software to make it easier to read. I haven't just done this for the sake of making it easier for the learner to look at the software, I always do this. As you start writing longer pieces of software, you will really appreciate breaking up the code like this.
Now, let's take a slight detour for just a moment. One of the the things that always bothered me about reading books that were supposed to teach you MCU programming in C is that they would do one of two things. They would show you an example first program and not include this section on the CONFIGURATION WORD, or they would have that section in the code, but never explain it until later in the book. The configuration word is SUPER important and will most likely be used in every program you write, so why wouldn't you explain it right away? Well I suppose because it's a long-winded explanation, but I'll try to keep it short and to the point.
Configuration Word
Microcontrollers have special options in their program memory separate from the Special Function Registers called the Configuration Word. The configuration word cannot be accessed during program execution, so they must be set during programming of the device (with the compiler). The configuration word allows the user to define certain configurable features of the microcontroller. We will talk briefly about those features now, focusing only on those that are currently relevant. Section 9.1 of the datasheet shows the PIC12F629 configuration word register (figure 6).
Figure 6 |
The datasheet defines each bit and it's available settings.
Bits 13 and 12 control the Bandgap Calibration. These bits are set at the factory and are used partly to calibrate the MCU's internal oscillator. We wont be messing with them.
Bits 11 through 9 are unimplemented.
Bits 8 and 7 are used to enable Code Protection for EEPROM data and program data respectively. Code protection is a means of keeping your software safe from theft. Basically, if you are a company producing a product, and you want to help keep someone from stealing the code off your MCU and using it to produce a "knock-off", code protection helps. We will always keep code protection disabled because once it's set, you cannot read from or write to your MCU anymore.
Bit 6 controls Brown-Out Detection, an internal trigger that shuts down the MCU when operating voltage drops below a threshold. It is useful for situations where the embedded system runs off batteries. The Brown-Out Detection (BOD) circuit will reset the MCU when voltage is restored above the threshold.
Bit 5 controls the Master-Clear Reset (MCLRE). The MCLRE function is used to externally reset the MCU. When enabled, the MCLRE pin must be provided with positive voltage for the MCU to operate. The MCU is held in reset as long as this pin is low (no voltage), and restarts the MCU from the reset vector when voltage is restored. MCLRE can be turned off so that the MCLRE is internally tied to the VDD pin. We will explore using the MCLRE function in later projects.
Bit 4 controls the Power-Up Timer (PWRTE). The power up timer is disabled by default, but is used to hold the MCU in reset mode upon power up. This feature allows time for the power supply to stabilize before program execution begins. The power up timer's delay is a nominal 72 milliseconds. We will always use this feature, since there is little reason not to.
Bit 3 is the Watchdog Timer (WDT) bit. Many times, embedded systems operate without human intervention, and in places that access by humans would be impractical. If for some reason during program execution the device get's hung up, the WDT will reset the device. The WDT must be periodically cleared in software in order to prevent the device from resetting during normal operation.
Bits 2 through 0 are used to select the Oscillator mode. Microcontrollers use an oscillator for all timing operations, including normal program execution. Precise timing operations require a more precise oscillator, using a crystal or resonator. Less precise operations may utilize a less expensive Resistor-Capacitor (RC) oscillator. Many MCU models include an internal RC oscillator, but not all. We will dedicate a lesson later specifically to the MCU's oscillator clock and modes.
To view the configuration word settings for a PIC chip, you can view them in the chips html files located at:
C> Program Files> Microchip> xc8> version #> docs> chips. Scroll down to the chip you're using, such as the 12f629.html file and open it. All of the options are listed under the "#pragma config Settings" header.
Back to our Program
We left off in the Configuration Word section. The configuration bits are set using the preprocessor directive #pragma. The pragma directive is a method of providing additional information to the compiler. It tells the compiler to do something, set some option, take some action, override some default, etc. We use it here to set the configuration bits during programming. The format of using #pragma is #pragma keyword options where in our case the keyword is "config" since we're setting the configuration options, and then the actual option we want. In my program I have strung all of my options together using commas, but you could indicate them separately if you chose, for example:
#pragma config BOREN = OFF
#pragma config MCLRE = OFF
#pragma config PWRTE = ON
And so on. You can find the configuration word options in the device's chip file. Chip files are located in: program files>microchip>xc8>version#>docs>chips. Looking in this document for the PIC12F629, we can find the options listed at the bottom. We do not need to specify bits we're not going to change from their default. For example, Code-Protection is disabled by default, so we don't need to specify CPD = OFF since it's already off. For this program, I have specified in my configuration word to turn off the BOD, MCLRE, and WDT; turn on the PWRTE; and am using the internal RC oscillator with I/O function on the clock-in and clock-out pins.
After the configuration word our main function begins. We're not passing any information into or out of the main function, hence the void keyword and the use of empty parenthesis. The following line opens our main function group with the left brace. Program execution begins with the main function and the entire executable program resides within it.
We use the TRISIO register to set the data direction of our I/O bits. Setting a TRISIO bit (=1) will make the corresponding GPIO pin an input, while clearing a TRISIO bit (=0) will make the corresponding GPIO pin an output. I have specified the entire TRISIO byte (8 bits) using a hexadecimal number. All hexadecimal numbers are preceded by a zero and lower-case x "0x" to indicate to the compiler that you're using a hexadecimal number. Since GPIO pin 3 is the only input we're using, and unused pins should be outputs, our TRISIO byte is 00001000. I have used a Binary to Hexadecimal Converter to convert this into a hexadecimal number. In reference to the preceding paragraph, I have made an error. Please see my blog post, which corrects this error.
After specifying port direction, I have cleared the GPIO port (turned all the pins off). We do this at the beginning of a program in case the device gets reset while a GPIO pin is on. It's good practice to do that, as not doing so could cause your embedded system to not operate properly.
The next line is the conditional function while( ). A while loop checks a condition and, if the condition is true, executes the loop for as long as the condition remains true. The loop is everything within that function's set of braces {}. If the condition is false, the loop will not execute. The condition to test resides within the parenthesis. The first while loop we use is a simple one that pretty much every program we write will contain, the while(1) loop. We place a 1 within the parenthesis to ensure our condition is always true, thus the program will continue to execute over and over again. Without this loop, we are depending on the compiler to assume we want the main function to execute repeatedly, and that doesn't always happen, so a while(1) function is a safety net.
Following while(1) is a second while loop. Here we have asked the program to check the condition of the GPIO3 pin that our button is connected to. When we press our button, it applies VDD to GP3, thus placing it in a logic high state (pin reads 1). When we release the button, no voltage reaches the pin and the logic state is low (pin reads 0). The operator "==" is the conditional operator. In plain English, while(GP3==1) says "while GP3 pin reads 1, do this..." The "this" is whatever is inside the function braces.
The C language differentiates between a single equals sign, and two equals signs. Example: GP0=1 means "make the GP0 pin equal to 1", whereas GP0==1 means "if the GP0 pin is equal to 1". Getting these confused causes problems in your code.
Okay, so our while function says that while we're getting a logic high on GP3, do whatever is inside the braces. In this case, there is only has 1 instruction (statement), GP0=1; This turns on the GP0 pin, which turns on our LED. Then the function is ended with the right brace. When we press the button, GP3 is evaluated and is at logic high, so the condition GP3 is equal to 1 holds true, which in turn allows the function to be executed and GP0 is turned on. This loop continues to repeat until we release the button and GP3 is no longer 1.
If the button is not pressed, GP3 is evaluated to be 0 and the while loop condition operator is false, therefore the loop is not executed. Then the default instruction GP0=0; is instead executed, which turns the LED off.
Our program ends by closing up our open functions. Adding a right brace for each open function, in this case the while(1) and the main functions, will end the function loops.
Compiling our Program
Okay, our program is written, now we need to turn it into a hexadecimal file (.hex) that the PIC can read. Refer to figure 7.
Figure 7 |
Label B shows where I have added the xc.h file into the IDE. Right click on "Header Files" and select "Add Existing Item..." This file is located in Program Files>Microchip>xc8>version#>include, be sure to add xc.h and not xc.inc. Once you have added the xc.h file, we can press Build.
A new Output window (label C) will pop up, and you'll see some text popping up within it. When the compiler has finished, you should see the words BUILD SUCCESSFUL. If something is wrong about your code, you would see BUILD FAILED here. This would indicate that there is some kind of mistake in your code, and the output window will show you what kind(s) of error(s) you have using a blue link. For instance, if you had forgotten to place a semicolon after a statement, the compiler would say "learn_c.c:22: error: ";" expected". If you click on this text it will point you to the location of your error(s). The number 22 within the error is the Error Code, and you can find a list of all the error codes and what they mean in the compiler manual, which is in the XC8 directory in the "docs" folder.
Finally, label D shows how much memory your program has taken up of the memory available in your PIC chip. Our program has only used 24 bytes out of 1024 available, or 2% of total memory.
Our program is successfully compiled now, so let's find the .hex file it generated. A .hex file is a hexadecimal file that we flash into the PIC. The .hex file get's put into your project folder by the compiler, which is located at: C:>users>(your name)>MPLABXProjects>learn_c.x>dist>default>production. Yea, I know that's a bit of a long directory, it's buried deep. You'll see your .hex file in there, learn_c.X.production.hex.
Flashing the Microcontroller
Alright, the project is compiled and we know where our .hex file is, now we need to get it inside the microcontroller. Break out your PIC Programmer, in my case, the PICkit 2. The PICkit 2 (and presumably the PICkit 3) come with their own software interface, and they flash the microcontrollers with what's called In Circuit Serial Programming (ICSP). The setup is fairly easy, and when properly set up you can flash your PIC without having to remove it from the project circuit. Microchip provides a lot of information on ICSP, and it is well covered in the PICkit 2 or 3 documentation.
Figure 8 |
Figure 9 |
The project schematic and breadboard layout images are below. When your project is assembled, you can test the program in action. Pressing the button will illuminate the LED and releasing the button will extinguish it.
IMPORTANT NOTES: Do not exceed 5V input to the MCU as doing so will destroy the device. There is a tolerance to about 5.5V but it is safest to use 5V, hence the use of the LM7805 voltage regulator. Also, the current limit of each MCU I/O pin is 25mA thus, be sure to use an appropriately sized current limiting resistor on the LED. My schematic shows the use of a 270 Ohm 1/4W 10% resistor. Voltage output on the I/O pin is VDD (5V) when turned on, thus using Ohm's law provides that current sourced from pin 0 to the LED is 5V/270R = 18.5mA. DO NOT EXCEED VOLTAGE OR CURRENT LIMITS!
Project 1 Schematic |
Project 1 Breadboard Layout |
November 25, 2014: Post edited to direct the reader's attention to an error in the text. Links have been added to allow a direct-open to the correction. The original information was left intact because I felt that the information provided, which instructs how to use a hexadecimal number in a C program, and a link to a hexadecimal converter were still useful.