Are PIC Microcontrollers Any Good?

If you take a look at my channel’s videos, you’ll find that nearly everytime a microcontroller comes up, it’s usually an AVR. And while they are what I am most comfortable with, it’s usually a good thing to learn a new platform. And what better microcontroller to learn other than AVR’s old rival PIC? And well, PICs are actually a very interesting series of microcontrollers, so I’ve made this video to teach any newcomers about the basics of PIC programming. Let’s dive in.

What do we need?

When getting started with any microcontroller project, you need three things. First and most obviously, you need the actual microcontroller itself. Next, you will need a device to upload the code that you will write to the microcontroller. For these PIC devices, the official programmer is called the PIC kit. Now there are several versions of this programmer, and they are differentiated by number. You get to choose between the PICkit 2, PICkit 3, and even the PICkit 4 and PICkit 5. Now, which one should you choose? Well I’ll tell you the answer to that later in the video. For now, we have to talk about the third thing that you need, which is the actual software that you will use to write, compile, and upload your code. But more on all of this in a minute.

Selecting your microcontroller

Now, when selecting a microcontroller for your project, it’s important to be as open-minded as possible. But, for the sake of this video, we will narrow our focus down to just 8-bit PIC microcontrollers. You’ll need to pick a microcontroller that has all of the peripherals that you’ll need. The PIC microcontrollers generally have a good selection of parts and peripherals to choose from. As for my microcontroller of choice, I went with the PIC18F2450. It has the usual peripherals, like three timers, a USART, and an ADC. The stand-out peripheral on this MCU, though, is its USB capabilities. So, it’s definetly an interesting microcontroller and it’ll make for a good test subject for this video.

Minimum Required Circuit

Before we can write any code for this thing, we have to setup the bare minimum in terms of circuitry. This means that we have to supply power and any clock sources as needed. Luckily there isn’t much that goes into all of this. It’s simply just +5V on VDD an ground to VSS. You should also attach a 10k pull-up to MCLR, this will keep it high normally, but will allow our PICkit to pull this pin low. On the OSC1 and OSC2 pins you should attach your crystal oscillator, if you decide to use it. You can leave it out if you want to use the internal oscillator.

Finally, we have the programming header. If we take a look at the PICkit pinout, we’ll find pins that correspond to MCLR, VDD, VSS, PGD, and PGC. It’s pretty easy to connect these, since they match with the pin names. Beyond this point, everything is optional. So, I went ahead an added an LED to RA0, which we will use to verify our blink program later on.

The programmer

Now, we need a way to communicate with our PIC from our computer. Like I said earlier, one of the PICkits could be a good choice for this purpose. The PICkit 2 and PICkit 3 have been discontinued, but they have also been cloned to death. I myself am making this video using a PICkit 3, so it is a good option. However, like I said, they have been discontinued in favor of the newer PICkits. This means that these do not support the newest chips. So I recommend that you check whether or not your specific PIC microcontroller will work with your PICkit.

I’d also like to mention alternatives to the PICkit, namely Microchip’s ICD programmer. This programmer is more expensive than the PICkit, but should be more reliable and professional. I don’t have any experience with it though. You can also use one of the many DIY solutions that you will find on the internet, though these can be very frustrating to implement and will take a lot of time to debug if something goes wrong. They are much cheaper though. So basically, the more you spend, the easier it will be to get everything working.

The Code (MPLAB X)

Now there are multiple ways to write code for a microcontroller and then subsequently send that data over to the programmer. If you are inexperienced, you will want to pick the MPLAB method for the most ‘out-of-the-box’ experience, since microchip has made it really difficult for other applications to be used. Anyways, let’s get to the installation.

Go to the MPLAB X webpage, I’ll link it down in the description for easy access. It has a very straightforward installation process, just download the installer and run it and go through the prompts. Don’t forget to allow the installer to add MPLAB to your PATH. Near the end of the process, you will be prompted for a license key. To use the free version, just leave it as is. We aren’t done with installation yet, you will also have to install a compiler. Microchip recommends the XC8 compiler, and you can install it in a similar fashion as before. I have linked it in the description.

At this point, you should have the basic required software to get everything working. Now you should open MPLAB and click FILE > New Project. Select standalone project, on the next screen find your microcontroller under device. My microcontroller is the PIC18F2450, so that’s what I chose. On the next screen, select that XC8 compiler that you just installed. And finally, name your project and save it wherever you’d like. Now that you have created your project, we will create the first file. First click on Source files. Then go to File > New file. Go to the C category and click C main file. Change the filename to main.c and you are now ready to write some code.

Our first program together will be blink, so I’ll walk you through the process. Go to your microcontroller’s datasheet and find the I/O Ports section. This section has all that you need to know about controlling the I/O ports. If you’ve watched my previous videos about AVR programming, then you’ll notice that the I/O interface here is very similar to that found in AVR. The parallel to the Data-Direction register in AVRs is the the TRIS register in PICs. This TRIS register will allow us to decide whether a certain pin is either an input or an output. What we will have to do is set the corresponding bit to a 0 to allow the pin to be an output. We want the LED on RA0 to be an output, so we will be dealing with the TRISA register. To set RA0 as an output, you can simply write to the TRISA0 bit directly.

Now it’s time to toggle the LED in the infinite loop. The register that we will use is the LAT register. It works in a similar way to the PORT register on AVRs. We can toggle our LED using the exclusive OR bitwise operator. And for our delay, I simply implemented a very long for-loop to waste CPU time.

Now there is just one more thing that we need to do, we need to configure our config registers. The config bits are similar to the fuses on the AVRs, and you can find the whole list of them on the datasheet in the ‘Special Features of the CPU’ section. Now there are a lot of settings, and luckily MPLAB provides a tool that will set them for us. Go to Window > Target Memory Views

Configuration Bits. You’ll see a window pop-up at the bottom of your screen. You can go through an select which bits you’d like to enable. I changed mine so that the PIC operates from the crystal. Once you are satisfied with your settings, click on the big Generate Source Code to Output, this will compile your list. This will bring you to another window that will contain the bit settings. Simply copy everything in here and paste it into your main.c file.

We are now ready to upload our code to the microcontroller. Go to the top of the screen and click on the button with a picture of the microcontroller and the green arrow. From here click on Make and program device. You will be prompted as to which tool you will use. Select your programmer, here I am using the PICkit 3 so that is what I chose. After this, you can see everything working at the bottom. Any errors will be reported here. If everything goes properly, you should see Programming/Verify complete and your LED should be blinking.

Now I know the video makes this all seem striaghtforward so far, and if MPLAB is cooperative then it should be easy. However, MPLAB is also weirdly intermittent. I had several occurances where the connection to the programmer suddenly stopped working and the fix was to restart MPLAB.

Basic Peripheral Examples


Now that we have blink completed, we can focus our attention on getting some of these peripherals working. Let’s start with our timers. If we go to timer0 on the datasheet, we will find a very basic timer which we use as either an 8-bit or 16-bit timer. You can also use it as a counter. It also has an overflow interrupt. So, let’s implement blink again, but this time we will use the timer as our delay. I placed this green LED on RA1, which will act as our timer blink.

To get started, we should take a look at the T0CON register, since this will allow us to set some timer settings. You can setup the prescaler, determine the clock source, and most notably, decide whether it will be an 8-bit or 16-bit timer. For this example, I went with a 16-bit timer with 8x prescaler. The clock source is also the main system clock. Next, we need to enable this timer’s interrupt settings. The TMR0IE bit is located in the INTCON register, and it simply enables our overflow interrupt. The next bit, TMR0IP, is located in the INTCON2 register. This signifies whether or not the Timer0 interrupt is a high or low priority interrupt. And to fully explain what that means, we will have to explain how interrupts work in this PIC.

Basically, when defining your own interrupts, you can choose whether or not an interrupt is of high or low priority. Let’s imagine an example scenario. Let’s say that you have defined two interrupts. One is a timer overflow, and you’ve assigned it a low priority. The other is a button press from the user, and you’ve defined this as a high priority. Now let’s say that the timer overflows and interrupts as usual, and it starts processing its interrupt. Then the user presses the button in the middle of processing. Since the button press is higher priority, the low priority interrupt gets interrupted by the high priority interrupt. So, as you can see, you can select certain interrupts to be more important than others. It’s worth noting, however, that this interrupt priority system isn’t present on all PICs. The PIC18F2450 in particular has both this priority system and also has backward combatibility with the single priority interrupt system on other PICs. For this video though, I will stick with the priority system since I think that it really is a fascinating feature.

So, back to the actual project, to enable the priority system of interrupts, we have to set the IPEN bit. Afterwards, set the GIEH and the GIEL bits to enable both high priority and low priority interrupts respectfully. And finally, to catch these interrupts, make two methods like shown for both high and low priority. Whether you decide to set the timer as high or low priority is up to you, but whichever you chose, place an IF statement checking for the timer0 overflow bit. Inside of this we can toggle our new green LED. Don’t forget to clear the flag at the end, or else you will be stuck in an infinite interrupt loop. Now there is just one more thing we need to add: set TMR0ON to a 1 to enable our timer.

And let’s admire our work after uploading the code. As you can see, the red LED is running our original software blink program and the green LED is running our new timer based system and is blinking at a different rate. The best part is that the green LED’s code is nowhere to be found in the main loop.

PWM, CCP, and Timer2

We can’t go talking about timers without at least touching on PWM. Unfortunately, timer0 does not have hardware PWM functionality and you’d need some software intervention. Timer2 in conjuction with the CCP does allow for hardware PWM though. The Timer2 peripheral is rather simple itself, with it simply being an 8-bit counter with a prescaler and a postscaler. It also has the PR2 register which defines the TOP of the timer and can clear the CCP1 bit on reset. Now this is only half of the PWM problem, we still need a way to set the CCP1 output bit determined by a specific duty cycle.

This is where that CCP peripheral comes in. It can perform a few different tasks, but we will set it into PWM mode by setting CCP1M3 and CCP1M2 both to 1’s. At this point, we can set our duty cycle using the CCPR1L register and the DC1B1 and the DC1B0 bits. Yes that’s right, our ten bits are spread over that 8-bit register and then those two other bits located in the CCP1CON register. To make setting all ten duty cycle bits easier, I made a helper method that will divide the first ten bits of this 16-bit integer up between the registers. Anyways, the CCP1 pin is located in the same spot as RC2, so we can modify TRISC2 to be an output. And finally, we simply turn on the timer using TMR2ON. The datasheet provides a setup guide for the whole PWM process as well.

Either way, we can’t see much from here, other than the LED being on of course. Let’s open up the oscilloscope. Here we can see our PWM square wave operating at 5.8 kHz. The duty cycle is 50%, because that is what I set it to in the code. If we change it instead to 25%, we will see much more off time while maintaining the same frequency. The actual LED is also much dimmer than before. If we go ahead and use a 75% duty cycle, we will see the opposite and the LED will be much brighter. And all of this happens without using processing in the main loop.

Here’s a fun little bit of code that will give a cool looking effect on the LED, let me show you. Let’s first enable the overflow interrupt on timer2. You can do this in a similar way to timer0. I also set the postscale on timer2 to 16x with the T2OUTPS bits. What this does is essentially only trigger the interrupt once every 16 overflows. Now, inside of our interrupt we can dynamically adjust our duty cycle. To make this process easier, I made a structure that will hold data about our PWM settings. It stores the maximum duty cycle value that we’d like, the current duty cycle, which I called brightness, and finally direction. The reason why I included direction is because we are going to adjust the duty cycle both up and down. So, inside of our interrupt, we will first check whether we are close to the minimum or maxmimum duty cycles, and switch directions if necessary. Afterwards, we will increment or decrement depending on the direction variable.

And here is the result. We get this nice looking breathing effect. With the LED smoothly alternating between dimming and brightening. And we still have those two other blinking LEDs running at the same time.

The other peripherals

There are a few more peripherals like Timer1, the EUSART, and the 10-bit ADC, all of which should be somewhat self-explanatory depending on your skill level. But I’ll briefly cover them now. Timer1 is mostly like the other two, but it also allows other functionality like acting as an RTC or operating in capture mode using the CCP. The ADC simply takes an analog voltage on one of the analog pins and then converts it into a digital value. And the EUSART is a serial communication peripheral, which may be useful for talking with your PC over the serial port for example. Your specific PIC microcontroller may even have other peripherals which mine doesn’t, but I believe that the examples in this video should have equipped you to understand how to manipulate the registers and bits properly.

Now for the elephant in the room: the USB peripheral, it’s honestly the star of this microcontroller. But, I won’t be covering it in this video since it’d easily add another twenty minutes of run-time, and I’d like to at least get this part of the video out now. Don’t worry though, I do plan on making a project using this microcontroller in a future video.

Final review

Well, it’s time to sum up my experience with PIC and determine whether they can be viably used in projects. And my answer is a firm ‘Yes’, so long as you are OK with Microchip’s MPLAB software. Let’s start by talking about the hardware itself. And I’d honestly say that the hardware and the supporting datasheet really is PIC’s selling point. You can select from a wide range of microcontrollers with so many different combinations of peripherals. The other features are great too. For example, I really like the priority interrupt system, which isn’t something I’ve seen from my time with the ATmega series of AVRs. The datasheet is also easy to read and follow.

Now let’s talk about the programmer. Like I’ve said and shown, I have been using the PICkit 3 programmer, or rather a clone of a PICkit 3. When this thing worked, it worked without a hitch. It was basically plug and play. However, there were a few times where it suddenly stopped working and I’m not sure whether it was the PICkit 3, the fact that this is a clone, or MPLAB causing the issue. This was always an easy fix though, since all I had to do was restart MPLAB. I didn’t really show much of it in the video, but the PICkit 3 also worked great as a debugger, and it really helped me get Timer0 started.

As for the software. If the hardware was the selling point of PICs, then the software is the main drawback. If you want to program PIC microcontrollers, then your best bet really is to just use MPLAB and use the free version of XC8. You see, the problem here is that there aren’t really any good free and opensource tools out there for PICs. There is a project called SDCC, but PIC support is now unmaintained. If you do try to use it, it won’t allow you to use the extended instruction set, and you are consistently warned to use the use-non-free argument. XC8 on the other hand, will lock you out of some compilier optimizations unless you buy the pro license. I haven’t purchased it so I can’t tell you how well that works.

So really, in summary, I would say that if you are willing to put up with the MPLAB environment, then PICs are a great choice. But don’t forget to consider other systems that likely have more longevity. Whichever path you decide to go down on, I would like to thank you for watching. If you’ve made it this far into the video, please consider subscribing so that you can see my future videos. Also check out my buymeacoffee page, since your support is what allows me to make these videos in the first place. Thank you to Mr. devNull and Cogniscent for being long-time buymeacoffee members, you both have really helped make this video possible. Thanks for watching, have a good one!