Why Most People Waste Weeks on PIC Microcontroller Projects
The PIC16F877A is still one of the most common chips people start with, even though it was released in the late 1990s. It has enough peripherals to teach you about everything from UART communication to PWM motor control, and the tooling around it is free if you know where to look. The problem isn't the hardware. The problem is that most tutorials skip the parts that actually matter and leave you debugging a serial output that works in the simulator but fails on the real chip every single time. I spent about three years working with these chips professionally before moving into ARM-based systems. Most of the headaches I encountered came from the same root cause: people treat PIC programming like it's just C with extra steps. It isn't. The compiler doesn't optimize the same way GCC does for ARM, and the way you set up your project structure matters far more than you'd expect on a device with 36 KB of flash and 1.5 KB of RAM.
Starting a Pic Microcontroller C Programming Course
You'll need MikroC Pro for PIC, MPLAB X IDE, and a programmer. The Pickit 3 is the cheapest option that actually works reliably for development. You can pick one up for around $30 from distributors like Mouser or Digi-Key. The PICDEM Lite board is another option if you want something ready to plug in, but it's overpriced for what it is. A bare-bones setup with a breadboard and a few components costs about as much as the kit and teaches you more. Download the MikroC compiler from mikroelectronika.com. The trial version will let you compile projects up to 2 KB of code, which is enough to get through the basics. Once you're past that point, the full version is around $195. There are free alternatives like PICC Lite from Hi-Tech, which Microchip acquired, but the free version was discontinued. Now you're looking at XC8, which is also free for small projects and is the officially supported compiler from Microchip. XC8 compiles slower and generates slightly larger code than MikroC, but it's free and it's the direction Microchip is pushing everyone toward. That's the version you'll see in most current tutorials and application notes. Open MPLAB X and create a new project. Select your PIC device from the family dropdown. Make sure you're picking the exact part number from your datasheet. The difference between a PIC16F877A and a PIC16F877 is just a letter, but the register map changes enough that your code won't work if you pick wrong. I've lost half a day to this exact mistake on a project deadline. It happens to everyone eventually.
Setting Up Your First Project Correctly
When you create the project in MPLAB X with XC8, the default configuration gives you a blinky LED example. Don't just copy and paste it. Read through the generated code. You'll see things like the oscillator configuration, the GPIO initialization, and the main loop. These are the pieces you'll be modifying constantly, so understanding what each section does upfront saves you from confusion later. The oscillator configuration is where most beginners hit their first wall. The PIC16F877A supports internal and external clock sources. If you're using the internal oscillator block (available on the 877A with the right configuration bits), you save yourself an external crystal and some board space. But the internal oscillator runs at 4 MHz by default, and the clock is divided further depending on your fuse settings. If your UART baud rate is off because you didn't account for the actual clock speed, the receiver will see garbage characters. I spent two days chasing a UART bug once and the root cause was that I'd assumed a 20 MHz crystal was running when the configuration bits had actually disabled the external oscillator and the chip was running on the internal 4 MHz clock instead. The simulator in MPLAB showed the right timing because it used the configured oscillator settings, but the physical chip was clocking differently because of how the fuses were programmed. Set your configuration bits correctly from the start. In XC8, you can define them directly in your source file using #pragma config statements. This is better than setting them through the IDE menu because they become part of your source code and travel with your project. Everyone who has ever worked with these chips has burned through a few PICs with incorrect configuration bits before learning to lock it down in code.
Get the Full Details

Working With Peripherals in C
The thing that trips people up most with PIC C programming is that the compiler doesn't give you access to the same level of register manipulation that you get with assembly. You have to be deliberate about how you interact with hardware registers. The XC8 compiler provides special function register (SFR) declarations for most PIC devices, which means you can read and write to them directly. But there are subtleties you need to be aware of. Take the TRIS registers, for example. Setting a bit in TRISA to 0 makes that pin an output. Setting it to 1 makes it an input. Simple. But if you're reading a pin that has analog functionality, like AN0 on PORTA, you also need to clear the corresponding bit in the ADCON1 register before the pin will behave as a digital I/O. If you don't do this, the pin reads as 0 all the time because the analog-to-digital converter is driving the input buffer. This is one of those things that isn't explained clearly in most beginner materials because it requires reading the datasheet section on port configurations rather than following a tutorial. I recommend reading the relevant section of the datasheet before you write any code for a new peripheral. It takes longer upfront but cuts debugging time dramatically. When working with the ADC on these chips, remember that the conversion result is 10 bits for most PIC16 devices. The result is split across two registers: ADRESH and ADRESL. Reading only ADRESH gives you the upper 8 bits and loses precision. Reading only ADRESL gives you the lower 8 bits but with the upper 2 bits zeroed. You need to read both in the correct order as specified in the datasheet. The compiler provides library functions like ADC_Read() in MikroC that handle this for you, but XC8 doesn't include built-in ADC libraries in the same way. You'll either need to write the read sequence yourself or use a library from somewhere like mikroC's documentation, which has sample code for most peripherals.
UART Communication That Actually Works
The USART module on PIC16F877A is fully hardware-based, which means once you configure the baud rate generator correctly, the chip handles the serial framing automatically. The tricky part is getting the baud rate right. The BRG9 and BRG8 bits in the BAUDCON register affect the baud rate calculation, and the SPBRG register value depends on whether you're using high or low baud rate mode. The formula for baud rate is Fosc divided by 64 times (SPBRG plus 1) for asynchronous low-speed mode. If your oscillator is 20 MHz and you want 9600 baud, SPBRG should be 31. But 31 doesn't give you exactly 9600. It gives you 9615, which is a 0.16% error. Most UART implementations can tolerate up to 2-3% error without issues. If your calculation puts you outside that range, you need to adjust your oscillator frequency or switch to high-speed mode, which divides by 16 instead of 64 and uses different SPBRG values. I once had a project where the PC was receiving corrupted data from the PIC, but the PIC was receiving data from the PC fine. The issue was asymmetrical baud rate error because the two chips were using different oscillator frequencies. One was on a 20 MHz crystal and the other on a 16 MHz crystal. The baud rate error from each side looked acceptable individually but together they compounded. Switching both to the same oscillator frequency solved it immediately. This kind of problem doesn't show up in simulators because the simulator assumes perfect timing on both ends.
PWM and Motor Control Basics
The PIC16F877A has two CCP modules that can be configured for PWM output. The PWM period is determined by the PR2 register in the Timer2 module, and the duty cycle is controlled by the CCPR1L register and the DC1B bits in the CCP1CON register. The resolution of the PWM depends on the oscillator frequency and the PR2 value. At 20 MHz with a prescaler of 1, you get about 10 bits of resolution at a reasonable PWM frequency. One thing that isn't obvious from the datasheet is that changing the duty cycle while the PWM is running can cause glitches if you don't do it carefully. You need to update the CCPR1L register and the DC1B bits in a specific sequence to avoid pulse width anomalies. The workaround is to disable the CCP module, update the registers, and then re-enable it. This adds a small delay but eliminates the glitch. For most hobby projects this doesn't matter, but if you're driving a motor or a servo where a glitch could cause a jerk or overshoot, it's worth handling properly.

What This Approach Doesn't Cover Well
Programming PIC microcontrollers in C is not the most efficient way to squeeze performance out of these chips. The code size is typically 20-40% larger than equivalent assembly, and execution speed is slower too. If you're working on a project with tight memory constraints or real-time timing requirements, you'll eventually hit a wall. The PIC16F877A has limited RAM, and complex data structures in C can eat through it quickly. I've seen projects where the entire variable set in C barely fit into the available RAM, forcing a rewrite of large sections in assembly just to free up a few bytes. For anything beyond simple control tasks, you might want to consider moving to a PIC24 or dsPIC33 platform. These use a 16-bit architecture, have significantly more memory, and the XC16 compiler generates much more efficient code. The transition isn't trivial because the instruction set and peripheral model are different, but the skills you learn with the PIC16 transfer over reasonably well. The community and documentation for these newer platforms are also stronger now since Microchip has shifted focus away from the older 8-bit line.
Practical Next Steps
Build a project that combines at least three peripherals. Something like reading an analog sensor with the ADC, displaying the value on an LCD, and sending it over UART to a computer. This forces you to deal with interrupt priorities, timing conflicts, and memory management all at once. The moment you add an LCD to a UART project, you'll discover that the baud rate generation and the LCD refresh timing interfere with each other if you're not careful about how you structure your code. Use the debugger in MPLAB X. Set breakpoints, watch variables, and step through your code line by line. This is where you'll actually learn what's happening inside the chip. Reading datasheets is necessary but it's slow and dry. Watching the registers change in real time as your code runs makes everything click faster. I still use the debugger this way on projects even years later. It's faster than adding print statements and waiting for serial output. Bookmark the Microchip application notes. They're technically written but they contain the kind of information that doesn't make it into tutorials. AN774 about ADC design, AN578 about low-power techniques, and AN554 about USART implementation are all worth reading. They'll save you from repeating mistakes that other people have already solved.