2. Hardware requirements
3. Software requirements
4. Configure MP LAB and HI-TECH C compiler
5. Save 4 source files to your disk
6. Create a project with MP LAB
7. Burning the Hex file to Microcontroller
8. Running your first microcontroller program
I was asked more than 100 times about this topic so I thought to write a little article on how to start working with microcontrollers. I'll be using Microchip PIC16F877 microcontroller for my explanation due to high availability. TRONIC.LK
- Read some preliminary tutorials
First thing is, read the following articles and try to understand as much as possible. These will give you a brief idea on what is a microcotroller and for what purposes we use them.
Introduction to Microcontrollers
Introduction to Microcontrollers
PIC16F84 Tutorial
I agree with you, these can be a little complicated for starters, but I think that's okay for now.
- Hardware requirements
- PC running Windows with a serial port (For JDM programmer or USB for PICKIT2)
- PIC programmer
You can either buy one or build one your own.
You can fins a PIC programmer from TRONIC.LK or build your own one such as JDM programmer or PICKIT2.
Circuit diagram can be found here.
Multi PIC programmer
PICKIT2 or 3 can be used not only to program but also to debug as well. - Custom PCB for your requirement
You may use a PIC mini development board or else assemble the circuit used in this tutorial in a Breadboard like the one below. First have a look at the pin diagram of PIC16F877 You can see that the PIC16F877 has many number of I/O ports that can be used for number of purposes.
For this tutorial lets use a single LED circuit as in the schematic diagram below. We are going to blink this LED every few seconds.
PIC Power Supply LED Circuit Note: Use a 4 MHz crystal oscillator insted of the 20 MHz one in the diagram (denoted as XTAL20mhz)
- Software requirements
- Microchip MPLAB IDE
You can download Microchip's MPLAB IDE/assembler for free. - C/C++ compiler
Most of us choose C/C++ rather than assembly due to simplicity and readability.
You can download the Lite Version of HI-TECH C compiler for PIC10/12/16 MCU for free - Chip programmer
To program the chip use either WinPic800 or IC-Prog Prototype Programmer. For this tutorial, I'll be using WinPic800.
- Microchip MPLAB IDE
- Configure MP LAB and HI-TECH C compiler
Once all these are prepared, next step is to prepare MP LAB and HI-TECH C compiler work together.- Install MP LAB and HI-TECH C compiler to your PC
- Open MP LAB
- Go to Projects menu and click on Select Language Toolsuite...
- Select HI-TECH Universal Tool Suite from the list
- Click on Browse button and set the path to picc.exe as below
- Click Ok button
- Save 4 source files to your disk
Now, we have setup the HI-TECH C compiler to MP LAB IDE successfully. Next step is to write the program. I'm sure most of you are interested in this step.
First note that, we have connected the LED to First pin of PORT A (RA0).
Save following 4 source files to disk in given names.
main.cAs in comments of the code, we will set PORT A as an output port. Then we make all 6 pins to low at start up.Code: Select all
/****************************************************************************************************** * * Project : Simple Tutorial * File : main.c * Description : Simple LED blink project * Author : Neo * Copyright : ROBOT.LK * * * Version Date Author Change/Reason * ------- ---------- -------------------- ------------------------------------------------------- * 1.0 29/03/2010 NEO Created *******************************************************************************************************/ #include <pic.h> #include "delay.h" main() { int current_status = 1; // Initialise to 1 // Configuring PORT A ADCON1 = 0; // Make all pins on PORTA as GPIO (Not ADC) TRISA = 0; // Set all 6 pins as outputs PORTA = 0; // Set all 6 pins to low at startup while(1){ DelayBigMs(1000); // Wait 1 second RA0 = current_status; current_status = current_status ^ 1; // Alternate between 1 and 0 } }
Later on the while loop, we wait for 1 second and then switch on the LED. On the next iteration, we off the LED and this will repeat indefinitely. Alternating between 1 and 0 is done by a simple XOR operation. If you are not clear of this program, please ask me under same post.
Always.hNext there are two important files to attach to get the delay functionality (Delay.C and Delay.h).Code: Select all
/* Common header file Designed by Shane Tolmie of www.microchipC.com corporation. Freely distributable. Questions and comments to [email protected] Lots of Hi-Tech C FAQ and sample source code at www.microchipC.com. For Microchip PIC16Fx. Compiled with Hitech-C v7.85 Usage: #include in all ".c" files in project along with "pic.h" */ //warning about #if statements: if any of the arguments are spelled wrong or //unrecognised by the compiler, it will not generate a warning, but not include code #ifndef ALWAYS_H #define ALWAYS_H /* Turning bits on/off according to mask use ~0 instead of 0xFF, etc, because this ensures machine independence if int changes from 16-bit to 32-bit Example C: x=0b001; bits_on(x,0b100) //now x=0b101 */ #define bits_on(var,mask) var |= mask #define bits_off(var,mask) var &= ~0 ^ mask //defines #define INPUT 1 //port directions, ie: TRISA0=INPUT; #define OUTPUT 0 #define TRUE 1 #define FALSE 0 #define HIGH 1 #define LOW 0 #define hi 1 #define lo 0 #define b asm("nop") //convenient point for breakpoint (debugging) #define l while(1) //loop for ever (debugging) //see AM576. If interrupt occurs just when gie gets set to zero, it won't be cleared #define gie_on GIE=1 #define gie_off while(GIE==1) GIE=0 /* Reading an 8-bit byte in a 16-bit int With Hi-Tech C, this method is better than using pointers, as using pointers in different banks needs different #defines It is just as efficient - the optimizer picks out the correct byte. Of course, >>7 requires 7 shifts. This method cannot be used to alter a hi/lo byte, this needs pointers (as below) Example C: unsigned int x; unsigned char y; x=0x1234; y=hibyte(x); //now y=0x12 - works for variables in any bank 0 to 3 y=lobyte(x); //now y=0x34 lobyte(x)=0xaa; //will not work :( - use pointers */ #define hibyte(x) (unsigned char)(x>>8) #define lobyte(x) (unsigned char)(x & 0xFF) /* given variable of any type (char, uchar, int, uint, long) it modifies the unsigned char residing at that memory location for ints, byte1 is msb, byte0 is lsb (least significant byte) for longs byte3 is msb, byte0 is lsb ie: sample C code unsigned int myint=0x4321; long mylong=0x87654321; //for myint byte1(myint)=0x43; (msb) and byte0(myint)=0x21; (lsb) //for mylong byte3(mylong)=0x87; (msb), byte2(mylong)=0x65; byte2(mylong)=0x43; and byte0(mylong)=0x21; (lsb) note: to avoid fixup overflow errors add bankX if the target variable resides in banks 1, 2 or 3 */ #define byte0(x) (unsigned char)(*(((unsigned char *)&x)+0)) #define byte1(x) (unsigned char)(*(((unsigned char *)&x)+1)) #define byte2(x) (unsigned char)(*(((unsigned char *)&x)+2)) #define byte3(x) (unsigned char)(*(((unsigned char *)&x)+3)) #define lobyte_atbank0 byte0 //another way of saying it #define hibyte_atbank0 byte1 #define byte0_atbank1(x) (unsigned char)(*(((bank1 unsigned char *)&x)+0)) #define byte1_atbank1(x) (unsigned char)(*(((bank1 unsigned char *)&x)+1)) #define byte2_atbank1(x) (unsigned char)(*(((bank1 unsigned char *)&x)+2)) #define byte3_atbank1(x) (unsigned char)(*(((bank1 unsigned char *)&x)+3)) #define lobyte_atbank1 byte0_atbank1 //another way of saying it #define hibyte_atbank1 byte1_atbank1 #define byte0_atbank2(x) (unsigned char)(*(((bank2 unsigned char *)&x)+0)) #define byte1_atbank2(x) (unsigned char)(*(((bank2 unsigned char *)&x)+1)) #define byte2_atbank2(x) (unsigned char)(*(((bank2 unsigned char *)&x)+2)) #define byte3_atbank2(x) (unsigned char)(*(((bank2 unsigned char *)&x)+3)) #define byte0_atbank3(x) (unsigned char)(*(((bank3 unsigned char *)&x)+0)) #define byte1_atbank3(x) (unsigned char)(*(((bank3 unsigned char *)&x)+1)) #define byte2_atbank3(x) (unsigned char)(*(((bank3 unsigned char *)&x)+2)) #define byte3_atbank3(x) (unsigned char)(*(((bank3 unsigned char *)&x)+3)) /* given variable of any type (char, uchar, int, uint, long) it modifies the int residing at that memory location ie: sample C code unsigned char array[4]; unsigned int test; uint_atbyteaddr(&array[0])=0x4321; //now array[0->3]={0x21,0x43,0,0}; uint_atbyteaddr(&array[0+2])=0x8765; //now array[0->3]={0x21,0x43,0x65,0x87}; test=uint_atbyteaddr(&array[0+2]) //now test=0x8765 note: do NOT use &(array[0]+1) to reference the int stored at array[1] as it will reference the int after array[0] in pointer arithmetic. This will result with the int at array[2]. Instead use &array[0+1] to reference the int at uchar array[1] note: to avoid fixup overflow errors add bankX if the target variable resides in banks 1, 2 or 3 */ #define uint_atbyteaddr(x) (unsigned int)(*(((unsigned int *)x))) #define uint_atbank1byteaddr(x) (unsigned int)(*(((bank1 unsigned int *)x))) #define uint_atbank2byteaddr(x) (unsigned int)(*(((bank2 unsigned int *)x))) #define uint_atbank3byteaddr(x) (unsigned int)(*(((bank3 unsigned int *)x))) #define THE_BEER_IS_PLENTIFUL_AND_THE_PARTY_SWINGING TRUE /* NOTE: it is not recommended that unions are used to reference hi/lo bytes or bits of a variable. Use >>8 or &FF or pointers instead, as above. It makes passing variables to a function difficult, as the function must be defined to accept variables of the same union. Then, the function will no longer accept normally defined variables. these two structures allow access to 2 byte word, high and low bytes of variable declaration: union wordtype x; usage: x.word=0xABCD; x.byte.high=0xAB; x.byte.low=0xCD; x.part.bit15=1; (msb), x.part.bit0=1; (lsb) declaration: union chartype x; usage: x.byte=0xAB; x.part.bit7=1; (msb), x.part.bit0=1; (lsb) */ struct sixteen_bits { unsigned char bit0 :1; unsigned char bit1 :1; unsigned char bit2 :1; unsigned char bit3 :1; unsigned char bit4 :1; unsigned char bit5 :1; unsigned char bit6 :1; unsigned char bit7 :1; unsigned char bit8 :1; unsigned char bit9 :1; unsigned char bit10 :1; unsigned char bit11 :1; unsigned char bit12 :1; unsigned char bit13 :1; unsigned char bit14 :1; unsigned char bit15 :1; }; struct eight_bits { unsigned char bit0 :1; unsigned char bit1 :1; unsigned char bit2 :1; unsigned char bit3 :1; unsigned char bit4 :1; unsigned char bit5 :1; unsigned char bit6 :1; unsigned char bit7 :1; }; struct two_bytes { unsigned char low; unsigned char high; }; union wordtype { unsigned int word; struct two_bytes byte; struct sixteen_bits part; }; union chartype { unsigned char byte; struct eight_bits part; }; #endif
Delay.CDelay.hCode: Select all
/* high level delay routines - see delay.h for more info. Designed by Shane Tolmie of www.microchipC.com corporation. Freely distributable. Questions and comments to [email protected]. PICuWEB - Program PIC micros with C. Site has FAQ and sample source code. www.microchipC.com. For Microchip 12C67x, 16C7x, 16F87x and Hi-Tech C */ #ifndef __DELAY_C #define __DELAY_C #include <pic.h> #include "always.h" unsigned char delayus_variable; #include "delay.h" void DelayBigUs(unsigned int cnt) { unsigned char i; i = (unsigned char)(cnt>>8); while(i>=1) { i--; DelayUs(253); CLRWDT(); } DelayUs((unsigned char)(cnt & 0xFF)); } void DelayMs(unsigned char cnt) { unsigned char i; do { i = 4; do { DelayUs(250); CLRWDT(); } while(--i); } while(--cnt); } //this copy is for the interrupt function void DelayMs_interrupt(unsigned char cnt) { unsigned char i; do { i = 4; do { DelayUs(250); } while(--i); } while(--cnt); } void DelayBigMs(unsigned int cnt) { unsigned char i; do { i = 4; do { DelayUs(250); CLRWDT(); } while(--i); } while(--cnt); } void DelayS(unsigned char cnt) { unsigned char i; do { i = 4; do { DelayMs(250); CLRWDT(); } while(--i); } while(--cnt); } #endif
Save these two files for later use. Note that, on Delay.h, we have to set the oscillator value. In our case, we have set it to a 4MHz.Code: Select all
#define PIC_CLK 4000000 //4Mhz /* lowlevel delay routines Designed by Shane Tolmie for www.microchipC.com. Freely distributable. Questions and comments to [email protected]. For Microchip 12C67x, 16C7x, 16F87x and Hi-Tech C Example C: #define PIC_CLK 4000000 #include "delay.h" unsigned int timeout_int, timeout_char; DelayUs(40); //do NOT do DelayUs(N) of N<5 @ 4Mhz or else it executes DelayUs(255) !!!! DelayUs(255); //max dly250n; //delay 250ns dly1u; //delay 1us timeout_char=timeout_char_us(1147); while(timeout_char-- && (RA1==0)); //wait up to 1147us for port RA1 to go high // - this is the max timeout timeout_int=timeout_int_us(491512); while(timeout_int-- && (RA1==0)); //wait up to 491512us for port RA1 to go high // - this is the max timeout */ #ifndef __DELAY_H #define __DELAY_H extern unsigned char delayus_variable; #if (PIC_CLK == 4000000) #define dly125n please remove; for 32Mhz+ only #define dly250n please remove; for 16Mhz+ only #define dly500n please remove; for 8Mhz+ only #define dly1u asm("nop") #define dly2u dly1u;dly1u #elif (PIC_CLK == 8000000) #define dly125n please remove; for 32Mhz+ only #define dly250n please remove; for 16Mhz+ only #define dly500n asm("nop") #define dly1u dly500n;dly500n #define dly2u dly1u;dly1u #elif ( (PIC_CLK == 16000000) || (PIC_CLK == 16257000) ) #define dly125n please remove; for 32Mhz+ only #define dly250n asm("nop") #define dly500n dly250n;dly250n #define dly1u dly500n;dly500n #define dly2u dly1u;dly1u #elif (PIC_CLK == 20000000) #define dly200n asm("nop") #define dly400n dly250n;dly250n #define dly2u dly400n;dly400n;dly400n;dly400n;dly400n #elif (PIC_CLK == 32000000) #define dly125n asm("nop") #define dly250n dly125n;dly125n #define dly500n dly250n;dly250n #define dly1u dly500n;dly500n #define dly2u dly1u;dly1u #else #error delay.h - please define pic_clk correctly #endif //***** //delay routine #if PIC_CLK == 4000000 #define DelayDivisor 4 #define WaitFor1Us asm("nop") #define Jumpback asm("goto $ - 2") #elif PIC_CLK == 8000000 #define DelayDivisor 2 #define WaitFor1Us asm("nop") #define Jumpback asm("goto $ - 2") #elif ( (PIC_CLK == 16000000) || (PIC_CLK==16257000) ) #define DelayDivisor 1 #define WaitFor1Us asm("nop") #define Jumpback asm("goto $ - 2") #elif PIC_CLK == 20000000 #define DelayDivisor 1 #define WaitFor1Us asm("nop"); asm("nop") #define Jumpback asm("goto $ - 3") #elif PIC_CLK == 32000000 #define DelayDivisor 1 #define WaitFor1Us asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop") #define Jumpback asm("goto $ - 6") #else #error delay.h - please define pic_clk correctly #endif #define DelayUs(x) { \ delayus_variable=(unsigned char)(x/DelayDivisor); \ WaitFor1Us; } \ asm("decfsz _delayus_variable,f"); \ Jumpback; /* timeouts: C code for testing with ints: unsigned int timeout; timeout=4000; PORT_DIRECTION=OUTPUT; while(1) { PORT=1; timeout=8000; while(timeout-- >= 1); //60ms @ 8Mhz, opt on, 72ms @ 8Mhz, opt off PORT=0; } Time taken: optimisations on: 16cyc/number loop, 8us @ 8Mhz optimisations off: 18cyc/number loop, 9us @ 8Mhz with extra check ie: && (RB7==1), +3cyc/number loop, +1.5us @ 8Mhz C code for testing with chars: similar to above Time taken: optimisations on: 9cyc/number loop, 4.5us @ 8Mhz with extra check ie: && (RB7==1), +3cyc/number loop, +1.5us @ 8Mhz Formula: rough timeout value = (<us desired>/<cycles per loop>) * (PIC_CLK/4.0) To use: //for max timeout of 1147us @ 8Mhz #define LOOP_CYCLES_CHAR 9 //how many cycles per loop, optimizations on #define timeout_char_us(x) (unsigned char)((x/LOOP_CYCLES_CHAR)*(PIC_CLK/4.0)) unsigned char timeout; timeout=timeout_char_us(1147); //max timeout allowed @ 8Mhz, 573us @ 16Mhz while((timeout-- >= 1) && (<extra condition>)); //wait To use: //for max 491512us, half sec timeout @ 8Mhz #define LOOP_CYCLES_INT 16 //how many cycles per loop, optimizations on #define timeout_int_us(x) (unsigned int)((x+/LOOP_CYCLES_INT)*(PIC_CLK/4.0)) unsigned int timeout; timeout=timeout_int_us(491512); //max timeout allowed @ 8Mhz while((timeout-- >= 1) && (<extra condition>)); //wait */ #define LOOP_CYCLES_CHAR 9 //how many cycles per loop, optimizations on #define timeout_char_us(x) (long)(((x)/LOOP_CYCLES_CHAR)*(PIC_CLK/1000000/4)) #define LOOP_CYCLES_INT 16 //how many cycles per loop, optimizations on #define timeout_int_us(x) (long)(((x)/LOOP_CYCLES_INT)*(PIC_CLK/1000000/4)) //if lo byte is zero, faster initialization by 1 instrucion #define timeout_int_lobyte_zero_us(x) (long)(((x)/LOOP_CYCLES_INT)*(PIC_CLK/4.0)&0xFF00) //function prototypes void DelayBigUs(unsigned int cnt); void DelayMs(unsigned char cnt); void DelayMs_interrupt(unsigned char cnt); void DelayBigMs(unsigned int cnt); void DelayS(unsigned char cnt); #endif
- Create a project with MP LAB
In this step, we are going to create a project using MP LAB.
- Go to Project menu and click on Project Wizard
On MP LAB, you will see a window named as MyFirstProject.mcw as below. If you don't have it yet, you can get it by going to View menu and clicking on Project. You will see all 3 files are attached to the project now.
Now, we need to compile the project.
Go to Project menu and click on Build (Alternatively you can press Ctrl+F10) to build the project. You will get an output witndow as below. Check whether you have a file called MyFirstProject.hex in the project folder. This is the output file that is required to burn to Flash memory of 16F877 Microcontroller.
- Burning the Hex file to Microcontroller
We will be using WinPic800 to do this.
- Insert the PIC16F877 microcontroller to the ZIF socket of the programmer in the right direction
- Plug serial cabel from PC to Programmer board
- Install the program and run WinPic800 executable
- Click on Settings menu and click on Hardware
- Select JDM programmer (As above)
- Now select the COM port. For my system I have selected COM1.
- Click on Apply edits
- Click on Settings button on Toolbar and make the settings as in the following screenshot (if different).
- Select PIC 16F and 16F877A on the controls at top-right corner as below.
- Now click on Erase All button to clear the Flash memory of the microcontroller (This step is not essential). Notice the LED of the programmer which should blink while programming the flash memory.
- Now, go to File menu and click on Open
- Select the Hex file that was generated before
- Now click on Program All button
- After the process is completed, click on Verify All to make sure data is written successfully
- Done!
- Running your first microcontroller program
We are going to run the code that we have just flashed on our custom board now.
- Place the microcontroller to the custom board (or breadboard)
- Now, switch on the board
- You must see the LED is blinking on custom board