Hello World

So it has been a while since I have posted anything here. Since I have been starting up using Microchips PIC micro-controllers I thought that I would record my learning experiences with the world. Like many people I started my digital electronics experience with the easy to use Arduino Platform. After realizing that I didn’t need to throw a development board into every project and felt the need to branch out into other chips, and manufacturers. I fooled around with the parallax propeller and a few other random chips and finally landed with the PIC. This first post will start out with the very basics and will get more complex as I learn how to manipulate all the registers and peripherals that are included in the PIC chips. But first thing first, Which chip do you use?

Selecting a Chip to fit your needs

The PIC line has a variety of different flavors. Most of these are still supported and not to many of the older chips have actually been phased out. So what is it that you need? a plethora of ADC channels? USB HID compatibility? SPI, UART, I2C or CAN bus integration? These are some of the questions that a designer needs to know before selecting a chip. If you are reading this you are probably just a hobbyist and for that I would recommend a mid or advanced based 8 bit micro. The beauty of the PIC architecture is that the code is highly portable. If you make some firmware that you want to later port to a 32 bit micro, you can do that. For the majority of these tutorials I will be using the PIC16f18323. There is no real reason I chose this for my first chip, it is a mid ranged 8 bit controller that can do anything an arduino can do with perhaps a little bit more of code.


First time setup

Now that you have your chip selected and ordered you’re going to need something to program it with. For all of my tutorials I will be using the PICKIT3. I highly suggest you get the PICKIT3 from microchip. For the $50 you pay you can program any chip 8, 16, or 32 bit. You can also do in program debugging and other useful things. All of the tutorials will be using the MPLAB IDE and the XC8 compiler. All of these are free from microchip and are fairly good tools. If you are having problems enabling the peripherals within the chip there is also a plugin called MCC that provides a GUI allowing you to click the pins on your chip and enable certain peripherals and the code will be generated for you. I have yet to use this but have heard good reviews on it. The PICKIT3 comes with a giant poster that shows how you should connect your PIC chip to your programmer and the proper bits that need to be set/ cleared within the configuration bits. Follow these and you will be successful in your first program.

Hello world, blink damn it

getting the hello world program wasn’t that hard to implement. The code is pretty well documented but I will go into more detail and break it down into sections.


/* 
 * File:   main.c
 * Author: Bill Heaster
 * TheCreator aT ApexLogic d0T Net
 * 
 * 
 * This is the basic blink an LED Hello world program. 
 * Revision 1.0
 *
 * Created on October 5, 2015, 5:00 PM
 */

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>


// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG1
#pragma config FEXTOSC = HS     // FEXTOSC External Oscillator mode Selection bits (HS (crystal oscillator) above 4 MHz)
#pragma config RSTOSC = EXT1X   // Power-up default value for COSC bits (EXTOSC operating per FEXTOSC bits)
#pragma config CLKOUTEN = OFF   // Clock Out Enable bit (CLKOUT function is disabled; I/O or oscillator function on OSC2)
#pragma config CSWEN = ON       // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config MCLRE = ON       // Master Clear Enable bit (MCLR/VPP pin function is MCLR; Weak pull-up enabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config WDTE = OFF       // Watchdog Timer Enable bits (WDT disabled; SWDTEN is ignored)
#pragma config LPBOREN = OFF    // Low-power BOR enable bit (ULPBOR disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable bits (Brown-out Reset enabled, SBOREN bit ignored)
#pragma config BORV = LOW       // Brown-out Reset Voltage selection bit (Brown-out voltage (Vbor) set to 2.45V)
#pragma config PPS1WAY = ON     // PPSLOCK bit One-Way Set Enable bit (The PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a Reset)
#pragma config DEBUG = OFF      // Debugger enable bit (Background debugger disabled)

// CONFIG3
#pragma config WRT = OFF        // User NVM self-write protection bits (Write protection off)
#pragma config LVP = OFF        // Low Voltage Programming Enable bit (High Voltage on MCLR/VPP must be used for programming.)

// CONFIG4
#pragma config CP = OFF         // User NVM Program Memory Code Protection bit (User NVM code protection disabled)
#pragma config CPD = OFF        // Data NVM Memory Code Protection bit (Data NVM code protection disabled)

#define _XTAL_FREQ 8000000 //let the compiler know what our crystal is so our _delay functions properly.

#define LED LATCbits.LATC3
//setup for the program, the LED pin is RC3 which is pin 7 on the PIC16F18323
void setup()
{
    //if we are not using analog we should disable it.
    ANSELA = 0;
    ANSELC = 0;
    
    //need to set the direction of our our output pin
    TRISCbits.TRISC3 = 0; // 0  = Output 1 = Input
    
    //pre set the output pin low, just good practice
    LED = 0;
    
    
    return;
}



int main(int argc, char** argv) {

   
    //call the setup routine
    setup();
    
    //this is our main loop
    while(1)
    {
        
        int j=0;
        // loop 100 times , 10ms each time.
        for(j=0; j<100; j++)
        {
             __delay_ms(10);
            
        }
        
       // toggle the LED pin 
       LED ^= 1;        
    } 
    
    
    //should never reach here
    return (EXIT_SUCCESS);
}



If you are coming from the Arduino world, this will look very daunting to you. To calm your nerves, everything in the beginning is generated for you using the MPLAB IDE. I wont be getting into the configuration bits at this point. If you are using the Internal Oscillator or a crystal less than 4Mhz then you will need to adjust the FEXTOSC bit. Otherwise this configuration will work fine for most projects.


#include <xc.h>


// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG1
#pragma config FEXTOSC = HS     // FEXTOSC External Oscillator mode Selection bits (HS (crystal oscillator) above 4 MHz)
#pragma config RSTOSC = EXT1X   // Power-up default value for COSC bits (EXTOSC operating per FEXTOSC bits)
#pragma config CLKOUTEN = OFF   // Clock Out Enable bit (CLKOUT function is disabled; I/O or oscillator function on OSC2)
#pragma config CSWEN = ON       // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config MCLRE = ON       // Master Clear Enable bit (MCLR/VPP pin function is MCLR; Weak pull-up enabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config WDTE = OFF       // Watchdog Timer Enable bits (WDT disabled; SWDTEN is ignored)
#pragma config LPBOREN = OFF    // Low-power BOR enable bit (ULPBOR disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable bits (Brown-out Reset enabled, SBOREN bit ignored)
#pragma config BORV = LOW       // Brown-out Reset Voltage selection bit (Brown-out voltage (Vbor) set to 2.45V)
#pragma config PPS1WAY = ON     // PPSLOCK bit One-Way Set Enable bit (The PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a Reset)
#pragma config DEBUG = OFF      // Debugger enable bit (Background debugger disabled)

// CONFIG3
#pragma config WRT = OFF        // User NVM self-write protection bits (Write protection off)
#pragma config LVP = OFF        // Low Voltage Programming Enable bit (High Voltage on MCLR/VPP must be used for programming.)

// CONFIG4
#pragma config CP = OFF         // User NVM Program Memory Code Protection bit (User NVM code protection disabled)
#pragma config CPD = OFF        // Data NVM Memory Code Protection bit (Data NVM code protection disabled)

The next line of code is used to set the crystal that you are using in Hertz. I am using an 8Mhz crystal which would be 8,000,000 Hertz. This value is given to the XC8 compiler and is used for functions like __delay(), __delay_ms() and __delay_us().

#define _XTAL_FREQ 8000000 //let the compiler know what our crystal is so our _delay functions properly.

This line is a macro to make it easier to address the output pin we will be using in the code. Notice how the LAT register (latch register) is being used. This is because we are using this pin as an output. It is a rule of thumb to use the LAT register for output, the PORT register for reading inputs, and the TRIS register to select the direction of the pin (input or output).

#define LED LATCbits.LATC3

Like I said before regarding the Arduino up-bringing. I am used to the setup and loop functions that are in every Arduino sketch. Because of that I set up my PIC in a similar fashion. The setup function is called once and is used for what its name implys … setting this thing up. The first two lines of this are used to turn off the analog function of the chip. This is done by setting the ANSELA and ANSELC registers. If you are using a different chip you may have different letters which correspond to the ports available on your chip. Technically we could have just cleared the ANSELC register because we are only using port C. If you are not used to working with registers then this will be your first. In this example all 8 bits of the register are cleared to 0 using the hex characters 0x00. Alternatively you can use the binary version 0b00000000 or the decimal version 0.

 //need to set the direction of our our output pin
 TRISCbits.TRISC3 = 0; // 0  = Output 1 = Input

Remember the rule of thumb? TRIS registers are for setting the direction (input, output) of a certain pin. The TRISCbits is a structure (i think) that stores the bits that are addressable within that register. The particular bit that we want to set or clear is TRISC3(because we are using pin RC3). Because you usually only set the pin direction once there is no real need to create a macro for it, like we did with the LAT register.

    //pre set the output pin low, just good practice
    LED = 0;

    return;

As it says in the comment, this is just good practice to set your output pins low before the main program loop starts.

int main(int argc, char** argv) {

   
    //call the setup routine
    setup();

Now on to the main function of the program. If you are used to C, or C++ and perhaps other languages, there is always a main function. This is where the program starts from. The first function call we make is to the setup function that was created earlier.

    while(1)
    {
        
        int j=0;
        // loop 100 times , 10ms each time.
        for(j=0; j<100; j++)
        {
             __delay_ms(10);
            
        }
        
       // toggle the LED pin 
       LED ^= 1;        
    } 

This would be considered the loop() function within the Arduino environment. seeing as how “while(1)” will never be false (0) then this loop will go through to the end and start back at the beginning. I assume that you have an understanding of C or C++ enough to know that j is a variable of type integer. This integer is used in a for loop that will execute what is inside of it 100 times and then move on to the next set of instructions. in this particular for loop we are using the XC8 compilers built in function __delay_ms(). This function does all the math to create the delay by counting the clicks on the system clock until it reaches the desired amount of milliseconds of time passed. Because the micro controller is 8-bit we are restricted to a limited amount of clicks we can count until the buffer overflows. Therefore, to get a 1 second delay we have to delay 10ms, 100 times. (10ms *100 = 1000ms or 1 second). Once we have hit one second of wait time the next instruction will be executed. our macro LED is toggled. The ^= 1 is basically checking what the current pin is set to ( high or low) and setting it to the opposite of that. We get to the end of our endless loop and then the whole thing starts again.


Why this sucks.

This is great for a first project. It makes you confident once you can get that little LED to blink. However, it is very inefficient. Every time that the __delay_ms() function is called the processor is doing nothing. So if you wanted to do things like, read a sensor, toggle a different pin, do some arithmetic…you cant. But, there is a solution to this and they are called Timers. That is what the next article will be about.