After that just click on “Generate Source Code to Output”: a list of #pragma directives will be printed so you can copy them and paste on top of your main.c file.

Note: I’ve tried leaving default values for the configuration bits; sometimes it works, sometimes it doesn’t. According to the datasheet the default oscillator should be an external one that I have not connected: yet my setup still worked, running at 32 MHz (so possibly using the internal one). You can’t trust Microchip documentation, so make sure to initialize all values.

The Code

Finally we can start actually doing something. Fill in the main function with some meaningless instructions, just so we can confirm everything works.

// Here you should have all the #pragma directives from the

// register configuration

// ... int main() {

int x = 0;

while(1) {

x++;

x--;

x++;

}

return 0;

}

This obviously does nothing, but it’s enough to test the debugger. Add a breakpoint by clicking on the line number for the x-- instruction. Connect the MPLAB SNAP programmer to your PC, power the circuit and hit the “Debug” button.

The debug button.

Now the IDE should work its magic, compile and flash the program. You might need to select the SNAP tool in a popup window. Once everything is done you should see the program reaching your breakpoint; then you can try the debugger by advancing step by step in the loop.

When programming a PIC for debugging the firmware will only run if the programmer is connected. After checking that everything works, you should use the Programming button, highlighted in the image, to flash permanently.

If anything went wrong, head to the troubleshooting section at the bottom of the article.

Let there be Light

The debugger works. Cool. Let’s try to use the LED.

In my circuit the light is connected to the pin on the bottom left corner of the PIC. There is a 47 Ohm limiting resistor to make sure it doesn’t burn, but if you’re feeling lucky (or don’t mind breaking an LED) you can connect it directly.

The device pinout.

GPIO ports are named after alphabet letters on a PIC. You can read all about it in section 12.0 of the datasheet, but I’d advise you against it because it’s mostly electrical jargon.

Suffice to say that there are 3 classes of registers when working with PIC GPIOs: TRIS, PORT and LATCH.

TRIS registers select the GPIO direction (input or output).

PORT registers return the level perceived on the pin if it’s configured as an input.

LATCH registers (shortened to LAT) are used to set the level for an output pin.

Unlike smarter architectures PIC peripherals are not memory mapped; instead, a specific registers are defined as keywords in the compiler for every one of them.

You can include the <xc.h> header to benefit from a set of register definitions. The pin I’m using in my circuit is RC3 , so to select it as an output I have to write 0 in the TRISCbits.TRISC3 definition. A 1 corresponds to an input pin instead. Then, I can drive the line level by writing to the LATCbits.LATC3 register:

// Register configuration

// ... #include <xc.h> int main() {

TRISCbits.TRISC3 = 0; // set C3 as output

LATCbits.LATC3 = 1; while(1)

; return 0;

}

Using this example, your LED should light up!

Delay

Turning the LED on is nice, but we still don’t have a sign of sizeable activity. Blinking it is a start, but how do we tell how much time has passed?

Well, we configured our oscillator to run at 1 MHz and each instruction takes 4 clock ticks, so we could use a busy cycle to wait some arbitrary amount of time; for example, counting up to 10000 sums up to about 1 second.

There is however a built-in function to wait for a more-or-less precise type period: __delay_ms() . It is included in xc.h , but to use it you must first define a special macro, _XTAL_FREQ , short for “crystal frequency”.

Inside xc.h the crystal frequency define is used to build up a precise busy waiting cycle based on the specified clock speed.

#define __delay_ms(x)

_delay((unsigned long)((x)*(_XTAL_FREQ/4000.0)))

// _delay is a native PIC function

If we #define _XTAL_FREQ 1000000 before including xc.h we will be able to use __delay_ms(1000) to wait for 1 second. Note that _XTAL_FREQ must contain the actual frequency of your oscillator (1 MHz for us), otherwise the timing will be off.

The following program will blink the LED once per second:

// Register configuration

// ... #define _XTAL_FREQ 1000000

#include <xc.h> int main() {

TRISCbits.TRISC3 = 0; // Set C3 as output

LATCbits.LATC3 = 1; while(1) {

__delay_ms(1000);

LATCbits.LATC3 = ~LATCbits.LATC3;

}

return 0;

}

The LATCH register can be read as well to check the line level, so we use it in combination with the ~ operator to reverse, or toggle, the current output.

Interrupts

When working with low-level MCUs, interrupts are paramount to manage multiple tasks. Maybe you want your device to constantly flash a light to signal it is alive even while doing other work. To achieve this, we can activate a timer interrupt and have it fire periodically.

Every PIC can have multiple timers; for now we’re just going to use the first usable one (would be TMR2) described at section 28.0 of the datasheet.

A timer is nothing but a register that is increased every time a condition is met (e.g. a clock tick) and that can fire an interrupt on rollover (reaches its maximum value and turns back to 0) or when a certain number is reached.

Timers are somewhat of a complex peripheral, although they really shouldn’t be. This is where every device disagrees on register number and disposition: some let you choose the clock source, they can have a prescaler, a postscaler, comparators, enables, different modes,… I’m going to explain how to use this one, but this knowledge is not easily portable. To fire a TMR2 induced interrupt we have to use no less than 8 registers.

Luckily, the clock source for TMR2 is fixed to the system oscillator divided by 4, so 250 kHz because we’re running at 1 MHz. This means that the timer counter (which is 8-bits, so reaching up to 255 before rolling over) is increased by one every 4 microseconds and resets every 255*4 = 510 microseconds; it’s quite fast, not a very usable time slice.

We can slow it down by setting up a prescaler and a postscaler: the latter is a divider applied to the source frequency before the timer, while the former is applied after. In the end, both contribute to slow down the count.

The maximum settings we can have for TMR2 is a 1:16 division for the postscaler and 1:64 for the prescaler; together they tone down the 250 kHz clock frequency to 244 Hz, just below the max value for the 8-bit counter, 255. The timer counter will then roll over a little more than once per second.

We can then set up a comparator register to 244–1 (count starts at 0) to have the interrupt fire exactly once per second. Those are the registers we will be using:

T2OUTPS : the postscaler register

T2CKPS: the prescaler register.

PR2: the comparator register.

TMR2ON: enable register for TMR2.

TMR2IE: enable register for TMR2 interrupt.

PEIE: enable register for peripheral interrupts.

GIE: enable register for all interrupts.

TMR2IF: flag register for TMR2 interrupt.

The prescaler and postscaler registers have specific codes for the preset values (found in the datasheet); PR2 contains the count we want to reach; the various enable registers must be set to 1 for the peripheral to be active, and the flag register is set whenever an interrupt is pending and must be cleared after it was handled, otherwise new interrupts will not be launched.

In the end, this is what we should have:

#define _XTAL_FREQ 1000000

#include <xc.h> void __interrupt () my_isr(void)

{

if (PIR1bits.TMR2IF) {

LATCbits.LATC3 = ~LATCbits.LATC3;

PIR1bits.TMR2IF = 0;

}

} int main() {

TRISCbits.TRISC3 = 0;

LATCbits.LATC3 = 1;



T2CONbits.T2OUTPS = 0b1111; // Timer2 postscaler 1:16

T2CONbits.T2CKPS = 0b11; // Timer2 prescaler 1:64

PR2 = 244-1; // Set comparator



T2CONbits.TMR2ON = 1; // Timer2 enable

PIE1bits.TMR2IE = 1; // Timer2 interrupt enable

PIR1bits.TMR2IF = 0; // Clear timer2 interrupt flag



INTCONbits.PEIE = 1; // Peripheral interrupt enable

INTCONbits.GIE = 1; // Global interrupt enable





while(1) {

__delay_ms(1000);

}



return 0;

}

8-bits PIC devices have a single interrupt routine for every peripheral, defined by the __interrupt() directive. This is nice, makes everything easier for the developer to set up. Our main function does nothing now, but the LED still flashes thanks to the recurring interrupt. The comparator register needs to be set only once and it will keep firing at the right time.

Note that the resulting frequency is not actually 244 Hz, but 244.140625 Hz. The comparator and counter register are digital integer values, so we can’t capture the decimal places; the resulting time will be off by ~0.06% . TMR2 cannot be more precise than that, but we could have it run multiple times in a second and increment a variable to know when we are done. For this example, I’ve kept it simple.

Troubleshooting

There are hundreds of things that can go wrong in this tutorial. The whole software toolchain is barely functional, so if you notice ghost breakpoints, buttons not working the first time, IDE crashes and freezes don’t worry, it’s not your fault.

Also, error messages are pretty generic and unhelpful. If you don’t power up the breadboard before programming, for example, you will be met with the following error:

Which is stupid, because the programmer has power lines and should be able to detect voltage.

For every other connection problem the IDE returns the same misleading error message: