PIC from NULL: Programming Concepts and Tips
This tutorial aims to get beginners started with PIC programming. This is the second post of the cleverly named series PIC from NULL. To understand PIC flashing, please refer to the first post: [PIC from NULL: Flashing]({% post_url 2020-09-12-pic-from-null-flashing %}).
This post covers some basic C programming concepts and tips in the context of MPLabX. If you are unaware, MPLabX is the official IDE (integrated development environment) for programming and debugging PIC microcontrollers.
Concepts
Generally when programming a microcontroller, the datasheet for said microcontroller is your best friend. No, it’s more than your best friend. It is your bible. Cometh with questions and thee shall find answers within. Much like the bible, however, it can be hard to understand, especially for beginners. The good news is you get better with experience!
Second, you are programming hardware. You are responsible for managing the hardware. There is no OS (unless you write one).
Third, C is dumb. It does exactly what you tell it to do, even if that means to shoot yourself in the foot. Think of C as an easier way to write assembly.
Fourth, this one is a general software development tip. Write readable code! Someday, far in the future, you will find this code. You will want to be able to understand it without the context you have now. This is especially important for programming microcontrollers because a line like
TRISAbits.TRISA2 = 0;
means nothing to you a year from now. It means nothing to beginners trying to learn. It’s simply magic. Please distill the magic and write readable code!
#define OUTPUT 0
#define INPUT 1
// configure pin A2 as an output
TRISAbits.TRISA2 = OUTPUT;
PIC Specific Tips
Here is some useful information for starting out programming the PIC.
Timing and Delays
The __delay_ms
function can be used to cause a delay in your
program. For this function to work, however, you need to #define
the
_XTAL_FREQ
which basically tells the function how fast the clock is
oscillating. Most PICs, or, at least the one I am using, default to a
4 MHz internal clock.
Note: _XTAL_FREQ
stands for “crystal frequency”
#define _XTAL_FREQ 4000000
int main() {
// snip
__delay_ms(1000);
// snip
return 0;
}
Set the clock frequency using OSCFRQbits.HFFRQ
. This is dependent on
your PIC of course, so check your bible!
#define FREQ_32MHZ 0b110
#define _XTAL_FREQ 32000000
int main() {
// set internal oscillator frequency
OSCFRQbits.HFFRQ = FREQ_32MHZ;
// snip
}
GPIO
General purpose I/O is configured through memory-mapped registers. This
means we configure a pin by writing a 1 or a 0 to a specific region in memory.
The TRIS
registers are the entrypoints for configuration. TRIS
stands for
“tristate”. It is followed by a letter indicating the register.
#define OUTPUT 0
#define INPUT 1
// configure pin A2 as an output
TRISAbits.TRISA2 = OUTPUT;
Analog input requires a bit more setup. Like I mentioned in the concepts section, you are responsible for managing the hardware, and analog input requires the analog to digital conversion hardware (ADC). Here is an example with comments.
#define OUTPUT 0
#define INPUT 1
#define LOW 0
#define HIGH 1
#define VDD_REF 0b00
#define FOSC_DIV_16 0b101
#define RIGHT_JUST 1
#define LEFT_JUST 0
#define CH4 0b000100
int main() {
// Setup analog input on A4
TRISAbits.TRISA4 = INPUT;
ANSELAbits.ANSA4 = HIGH; // analog select A4
ADCON1bits.ADPREF = VDD_REF; // configure positive reference voltage
ADCON1bits.ADCS = FOSC_DIV_16; // configure conversion frequency as Fosc / 16
ADCON1bits.ADFM = RIGHT_JUST; // right justify the 10-bit result across the two 8-bit registers
ADCON0bits.ADON = HIGH; // turn ADC on
ADCON0bits.CHS = CH4; // select channel 4
ADCON0bits.GOnDONE = HIGH; // start a read
while (ADCON0bits.GOnDONE); // wait for the result
uint16_t result = (ADRESH << 8) | ADRESL; // right justified read
// snip
}
Watchdog
The watchdog timer periodically resets the microcontroller. Most of the
time, we do not want this behavior, so to disable it you can use this
PRAGMA
.
/* Disable the watchdog timer */
#pragma config WDTE = OFF
Conclusion
When in doubt, RTFM. It will distill the magic into knowledge and understanding. Most importantly, leave these bits of knowledge in writing so your code is readable!