Programming PIC Microcontrollers in C

Learning to program microcontrollers seems like an impossible task but with the help and support of a Hackerspace you’ll find that it’s actually simple to get started. The members of Harford Hackerspace set out to learn by starting at the basics and gradually taking on more difficult applications. The key to our quick success was group participation. Most of us had at least attempted PIC programming, but it took a group effort to get the whole picture.

In this tutorial we will teach you what we learned. You can then take this new knowledge to your local hackerspace and put it to good use. Let’s get started!

What will you need?

PicKit2 Debug Express
PicKit2 Debug Express
  • PicKit2 or PicKit3 Debug Express- The PicKits come with software, an in-circuit programmer (ICP) and a demo board. On the demo board is a PIC Microcontroller, 8 LEDs, 1 Potentiometer (variable resistor), and 1 momentary push button switch. You’ll want the Debug Express version.
  • MPLAB IDE – The PicKits come with MPLAB IDE (Integrated Development Environment) software on a CD. However, you should download and install the latest version, as with any software, to avoid running into bugs. MPLAB is where you will do your C Programming.
  • CCS PIC C Compiler- There is a free demo version of the CCS Compiler. However it’s limited in features. If you plan to do a lot of programming you may want to fork out the money for a full version. CCS comes with its own IDE but you won’t be using that.
  • Debug Express Examples – The PicKit 2 comes with a second CD which includes mostly assembly (.asm) examples but does include a couple Hi-Tech C examples. Hi-Tech is an alternative compiler to CCS and chances are these C examples will not work. However, they contain enough information that they are worth looking at.
PicKit2 44Pin Demo Board
PicKit2 44Pin Demo Board

Software Installation

Installing the software is fairly straight forward. Start by installing MPLAB and the Debug Express Examples. Then install the CCS Compiler.  You can post a comment here if you have trouble but I don’t think you will.

Start a new Project

    1. Start MPLAB
    2. From the Project Menu select Project Wizard
    3. Click Next – This is just a splash screen telling you about the wizard
    4. Select your PIC device – Our PicKit2s came with PIC16F887 chips. Yours may be different so make sure you select the correct chip.
    5. Click Next – This takes you to the second step which is the Compiler Setup.
    6. From the Active Toolsuite drop down box select CCS C Compiler…
    7. Under Toolsuite Contents,  CCS C Compiler (ccsc.exe) should be selected.
    8. Browse to the location of ccsc.exe which by default is C:Program filesPiccCCSC.exe
    9. Click Next – This takes you to the third step which is naming your project.
    10. Click browse and create a new project folder and create a new file called Blink. You can use any name but for this tutorial we will be blinking an LED so we call it Blink. It’s a good idea to put projects in their own folder because a bunch of files will be created and associated with the project.
    11. Click Next – This takes you to the fourth step which is adding files to your project. We won’t be adding any existing files here so you can just Click Next.
    12. Click Finish – It may look like nothing happened or there are no windows open. In this case select View -> Project and this should show the Project Tree.
    13. Select File -> New
    14. Type the following code:
#include <16f887.h>

void main()
{

}
  1. Select File -> Save and save the file as main.c in your Blink project folder.
  2. Right Click on the Source Files folder in your Project Tree and select Add Files… Then select the main.c file you just created.
    Add File
  3. Select Project -> Build All and notice that several new files have been created for you. If you get an error check your code.
Project Tree
Project Tree

After you select build all you should notice several new files in your Project Tree. The most important to note is the 16F887.h file under the Header Files folder. Depending on your chip it may be named something different.

Sample Code

So, did you pick up on what just happened here? We created a Source File which we called main.c. It could have been named anything. By using the code #include <16F887.h> and selecting build all the compiler found a compatible header file for our PIC chip and automatically inserted it into our project.

Now you have a complete skeleton project ready for you to fill in the blanks. We’ll give you your first program and examine it line by line. We will also tell you how we learned which functions would work for the compiler and chip combination we are using.

First, we need to get some reading material or resources. You will need to open the help file for the CCS Compiler. They are nice and give you an executable in Windows Start -> Programs -> Pic-C ->Pic C Help. This was installed with the CCS Compiler. Then, we need to get the Datasheet for our chip.

PIC16F887 Datasheet

Here is our sample Blink code:

#include <16f887.h>
#fuses NOMCLR, INTRC_IO, NOBROWNOUT
#use delay(clock=8M)

void main()
{
    while(true)
    {
      output_high(PIN_D1);
      delay_ms(50);
      output_low(PIN_D1);
      delay_ms(50);
    }
}

Line 1:
We already know that this file is what tells the compiler to load the header file in our Project Tree. This line also tells the preprocessor to treat the contents of the 16F887.h file as if those contents had appeared in the source program starting from this line. Keywords that start with the character # are called a preprocessor directive. Some examples are #include, #use, #define, and #fuses which are used most often. This code is used to setup the compiler before it starts compiling your code.

#fuses NOMCLR, INTRC_IO, NOBROWNOUT

Line 2:
The #fuses directive tells the compiler how we want to setup our chip. The available #fuses can be found by looking at the top of the 16f887.h header file. Just double click on it in the Project Tree to open it. At the very top there will be a several-line comment which tells you what fuses can be set. This is one of the murky areas for us still but we can explain what we know so far.

We are setting 3 fuses in our example code. The first NOMCLR stands for No Master Clear. Master Clear is a pin on most chips which can be used to reset the chip. Usually this pin doubles as a Input/Output (I/O) pin. In order to use it as an I/O you must set the fuse NOMCLR to disable it as a Master Clear pin. You’ll always want to do this if you are not using it as a Master Clear because leaving it float will cause your chip to randomly reboot itself.

The second fuse is INTRC_IO which tells the compiler we want to use the Internal RC (Resistor Capacitor) Oscillator and that we want Pins (9) OSC1/CLKIN and (10) OSC2/CLKOUT set as outputs. In our chips datasheet this is cryptically explained on page 61. You might be tempted to use INTRC instead of INTRC_IO. INTRC outputs a clock on Pin 10 which is 4 times slower than our internal oscillator. This is usually written as Fosc/4. Pin 9 is an I/O when using INTRC.

The third fuse we set in NOBROWNOUT. Brownout is a circuit protection feature. If voltage to our chip drops below a certain threshold the chip will shutdown or reset. On page 216 this is described as Vbor for Voltage Brown Out Reset. In our particular chip the brown out voltage can be set at 4Volts or 2.1volts. In our case we don’t want the chip to ever reset no matter what the voltage is. So we set NOBROWNOUT.

#use delay(clock=8M)

Line 3:
The #use directive is followed by another keyword delay. This must be set to the speed at which are oscillator is actually running. Since we are using the INTRC_IO oscillator we know we are using the internal oscillator of this chip. Therefore you need to look in the datasheet of the chip and find out what the normal operating speed of the internal oscillator is. Some chips, including this one, can have their clocks divided to slow them down or they can be clocked faster using an external oscillator such as a crystal. On page 3 of our chip’s datasheet we can see that our chip actually has two internal oscillators. 8Mhz and 31Khz. We are using the 8Mhz here.

The reason #use delay needs to be defined is actually because we are using the delay_ms() function on line 10. delay_ms() uses the #use delay to know how fast the oscillator is running so that it can correctly delay the execution of our code. If you set #use delay to an incorrect value your delay_ms() will function but it will be at the wrong speed. We’ll touch on this again in a minute.

void main()
{

Line 5:
Every program needs a starting point. void main() is the starting point for our program. This is simply how the C language works. Our chip needs to know where to start executing code and it will start in the void main() function. Why void? Void is simply saying that we do not expect our function to return any value. It could be int main but then we would have to return an integer at the end of our program. This is sometimes done to indicate a successful run of the program, but what’s the point? So we just use void and then we don’t have to return anything.

void main()
{
    while(true)
    {
      output_high(PIN_D1);
      delay_ms(50);
      output_low(PIN_D1);
      delay_ms(50);
    }
}

Line 6 and 14, 8 and 13:
Sets of brackets are called blocks. Line 6 starts a block of code and line 14 ends that block of code. Lines 8 and 13 are another pair of brackets and are nested inside of the void main(){} code block. A common syntax error is having too many or two few brackets. For each open bracket you need a matching close bracket.

    while(true)
    {
      output_high(PIN_D1);
      delay_ms(50);
      output_low(PIN_D1);
      delay_ms(50);
    }

Line 7:
We use a while(true) loop so our code keeps restarting. If we did not use a while(true) loop our LED would turn on and off only one time. We want the LED to keep blinking.

CCS Help Program
CCS Help Program

Line 9:
output_high() is a compiler function. This is unique to the CCS Compiler. Remember that Help File I told you to get ready? It’s located under Windows Start -> Programs -> Pic-C -> Pic C Help. If you select the index tab and type ‘output’ you will see a long list of output options. output_A would control an entire bank, or PORT, of outputs. In our case we only want to toggle one output and we want to set it to High (on). The parameter PIN_D1 is defined in the 16f887.h header file.

See…That header file is coming in handy after all. Instead of you having to look in the datasheet and try to figure out what the hex value of PIN_D1 is, the CCS Compiler has given you a header file which defines a constant name for the value. All you need to know is where to look for the name (in the header file). If you look at the pinout diagram in your datasheet you might notice there is no pin labeled D1. It’s actually labeled RD1. ‘D’ is the Bank and 1 is the Port. Honestly, I don’t know what the R means but who cares. Let’s try another one. What do you think output_high(PIN_A0) will do? You’ve got it. It will turn the pin labeled RA0 in your datasheet to high. See…It’s not that hard once you know where to look.

delay_ms(50);

Line 10 and Line 12:
We already touched on delay_ms(50) when we were talking about the #use directive. Just a refresher here: In order for delay_ms to work you must set #use delay to the proper clock speed. A wrong clock speed will cause delay_ms to not work properly. delay_ms stands for Delay Milliseconds. Where do you think you would look if you wanted to delay micro seconds? You look in the CCS Help program and simply type delay. You would then notice you can delay for Microseconds(_us) or Cycles(_cycles). Clicking on those entries gives you examples of their use.

Line 11:
This one I am going to leave for your homework. If output_high(PIN_D1) turns the LED on then what do you think output_low(PIN_D1) does? Here’s a tip. Look in the CCS Help file.

Conclusion

So, you flashed an LED and now you are bored again. Use the CCS Help file and look up ADC for Analog to Digital Converter. Your Potentiometer is connected to an ADC pin and you should be able to read the help file and learn how to read the position of the potentiometer. You can also search the 44 Pin Demo Board User guide and learn what port the pushbutton is connected to. Then search the CCS Help for Input_State and learn how to use an input to control the LED. So don’t stop with this tutorial. There is lots to learn.

This is where I’m supposed to tell you to come join our hackerspace. But if you don’t live near Baltimore, check out www.hackerspaces.org and find a hackerspace near you. They are all over the world and are a ton of fun.

We are taking the knowledge we learned about flashing LEDs and applying it to hacking Toy RC Cars to make them autonomous (drive themselves). Check out our wiki.harfordhackerspace.org projects page for details on that project. It’s actually a very simple yet rewarding project.

Comments

Joseph

Hi Squintz, I have read your tutorial about pics and I think it was a great work, very detailed and perfect for beginners like me XD.
I was wondeting if you coudl help me to understand something about the clock speed.
I am programming with PIC C Compiler and i don’t know how the number in the delay function works.
If im running at 4MHz, the delay function is delay_ms(1000), but if im running at 8Mhz, the delay function is delay_ms(500). What happens if i want to use a crsytal with a lower frequency or higher frequency? How could I determine the value within the delay fucntion?

I hope you can help me, and thanks in advanced.

Frasker

Great tutorial! What software you use to draw the Pickit2 diagram?

Squintz

@Frasker, I got that diagram from the PicKit documents. My guess is that it was created from Altium when they designed the board.

Gigi

What if I change the processor to 4Mhz? Will the delay(50) in fact will delay with 100ms?

superfro

@Gigi – the delay_ms() function is calculated based off of the #use statement in the header. In the example it is “#use delay(clock=8M)” which sets the delay loop based off of a 8 mhz clock. If you leave that statement as 8mhz and change the crystal to 4mhz then yes, it may take 100ms when you delay_ms(50); However that is not advised. You should update your #use statement to “#use delay(clock=4M)” and delay_ms(50) will still be 50 ms delay.

Joseph

Do you have any tutorials similar to the Blink code but using the XC8 C compiler? Would the code be different?

superfro

@joseph Since the XC8 compiler is in MPLAB-X the entire process would be a little different. The code for CCS would be different as well. We probably should post some updated tutorials, but the code is pretty simple. Try looking at this link http://www.tautic.com/blinking-leds-with-a-pic16f1503-and-xc8-v1-10

Leave a Reply

%d bloggers like this: