PIC1000: Getting Started With Writing C-Code For PIC16 and PIC18
PIC1000: Getting Started With Writing C-Code For PIC16 and PIC18
Introduction
Authors: Cristian Săbiuţă, Cristina Ionescu, Microchip Technology Inc.
This technical brief provides the steps recommended to successfully program a PIC16 or PIC18 microcontroller and
defines coding guidelines to help write more readable and reusable code.
High-level programming languages are a necessity due to imposed short development time and high-quality
requirements. These languages make it easier to maintain and reuse code due to better portability and readability
than the low-level instructions specific for each microcontroller architecture.
Programming language alone does not ensure high readability and reusability, but good coding style does. Therefore,
the PIC® peripherals, header files and drivers are designed according to this presumption.
Since the most widely used high-level programming language for PIC microcontrollers is C, this document will focus
on C programming. To ensure compatibility with most PIC C compilers, the code examples in this document are
written using ANSI C coding standard.
Table of Contents
Introduction ....................................................................................................................................................1
5. Further Steps........................................................................................................................................ 23
5.1. MPLAB® X and XC8 Compiler................................................................................................... 23
5.2. Application Notes and Technical Briefs...................................................................................... 23
6. Conclusion............................................................................................................................................ 24
7. References............................................................................................................................................25
Customer Support........................................................................................................................................ 26
Legal Notice................................................................................................................................................. 26
Trademarks.................................................................................................................................................. 27
RC6
RC5
RC4
RD3
RD2
RD1
RD0
RC3
RC2
RC1
NC
44 43 42 41 40 39 38 37 36 35 34
RC7 1 33 NC
RD4 2 32 RC0
RD5 3 31 RA6
RD6 4 30 RA7
RD7 5 29 VSS
VSS 6 28 VDD
VDD 7 27 RE2
RB0 8 26 RE1
RB1 9 25 RE0
RB2 10 24 RA5
RB3 11 23 RA4
12 13 14 15 16 17 18 19 20 21 22
NC
NC
RB5
VPP/MCLR/RE3
RA1
RB4
ICSPCLK/RB6
ICSPDAT/RB7
RA0
RA2
RA3
The Pin Allocation Tables chapter from the PIC18F27/47Q10 data sheet contains information about the pre-
established pin functions. These functionalities that can be configured for each I/O pin are usually input or output of
peripheral modules instances. This information varies depending on the device number of pins. If an evaluation board
is used, such as the PIC18F47Q10 Curiosity Nano Development Platform, the routing of the pins on the specific
board must be known. The information is available at PIC18F47Q10 Curiosity Nano page.
A module type can be the Enhanced Universal Synchronous Asynchronous Receiver Transmitter (EUSART), while
the module instance is, for example, ‘EUSART1’, where the ‘1’ suffix indicates that the instance is ‘EUSART number
1’. For simplicity, a module instance will be referred to as a module throughout this document, unless there is a need
to differentiate.
Each module has several registers that contain control or status bits. All modules of a given type contain the same
set or subset of registers. All of these registers contain the same set or subset of control and status bits.
All of the registers corresponding to a module have a fixed address in the I/O memory map. This way, each register
will be available at an absolute address specified by the data sheet.
Every module has a dedicated chapter that presents the features of the module, a functional description of the
module and the specific signals and guidelines on how to configure a certain mode of operation with all the
terminology explained. At the end of a module chapter, the Register Definitions subchapter contains the scope of
every register, the reset values of the registers, and whether or not it is readable or writable. It also provides the
position of every configurable/accessible bit of a register.
All the registers, their addresses, and the bit names and positions are described in the Register Summary section for
each module. The register summary for the ADC module is presented in Figure 1-3.
0x00
... Reserved
0x0F50
0x0F51 ADACT 7:0 ADACT[4:0]
0x0F52 ADCLK 7:0 ADCS[5:0]
0x0F53 ADREF 7:0 ADNREF ADPREF[1:0]
0x0F54 ADCON1 7:0 ADPPOL ADIPEN ADGPOL ADDSEN
0x0F55 ADCON2 7:0 ADPSIS ADCRS[2:0] ADACLR ADMD[2:0]
0x0F56 ADCON3 7:0 ADCALC[2:0] ADSOI ADTMD[2:0]
0x0F57 ADACQ 7:0 ADACQ[7:0]
0x0F58 ADCAP 7:0 ADCAP[4:0]
0x0F59 ADPRE 7:0 ADPRE[7:0]
0x0F5A ADPCH 7:0 ADPCH[5:0]
0x0F5B ADCON0 7:0 ADON ADCONT ADCS ADFM ADGO
7:0 ADPREVL[7:0]
0x0F5C ADPREV
15:8 ADPREVH[7:0]
7:0 ADRESL[7:0]
0x0F5E ADRES
15:8 ADRESH[7:0]
0x0F60 ADSTAT 7:0 ADAOV ADUTHR ADLTHR ADMATH ADSTAT[2:0]
0x0F61 ADRPT 7:0 ADRPT[7:0]
0x0F62 ADCNT 7:0 ADCNT[7:0]
7:0 ADSTPTL[7:0]
0x0F63 ADSTPT
15:8 ADSTPTH[7:0]
7:0 ADLTHL[7:0]
0x0F65 ADLTH
15:8 ADLTHH[7:0]
7:0 ADUTHL[7:0]
0x0F67 ADUTH
15:8 ADUTHH[7:0]
7:0 ADERRL[7:0]
0x0F69 ADERR
15:8 ADERRH[7:0]
7:0 ADACCL[7:0]
0x0F6B ADACC
15:8 ADACCH[7:0]
7:0 ADFLTRL[7:0]
0x0F6D ADFLTR
15:8 ADFLTRH[7:0]
For examples on how to access the ADGO bit from the ADCON0 register, refer to section 2.1.1. Register Unions
0x00
... Reserved
0x0E93
0x0E94 RC2REG 7:0 RCREG[7:0]
0x0E95 TX2REG 7:0 TXREG[7:0]
7:0 SPBRGL[7:0]
0x0E96 SP2BRG
15:8 SPBRGH[7:0]
0x0E98 RC2STA 7:0 SPEN RX9 SREN CREN ADDEN FERR OERR RX9D
0x0E99 TX2STA 7:0 CSRC TX9 TXEN SYNC SENDB BRGH TRMT TX9D
0x0E9A BAUD2CON 7:0 ABDOVF RCIDL SCKP BRG16 WUE ABDEN
0x0E9B
... Reserved
0x0F97
0x0F98 RC1REG 7:0 RCREG[7:0]
0x0F99 TX1REG 7:0 TXREG[7:0]
7:0 SPBRGL[7:0]
0x0F9A SP1BRG
15:8 SPBRGH[7:0]
0x0F9C RC1STA 7:0 SPEN RX9 SREN CREN ADDEN FERR OERR RX9D
0x0F9D TX1STA 7:0 CSRC TX9 TXEN SYNC SENDB BRGH TRMT TX9D
0x0F9E BAUD1CON 7:0 ABDOVF RCIDL SCKP BRG16 WUE ABDEN
Since the PIC data bus width is 8 bits, larger registers are implemented using several 8-bit registers. For a 16-bit
register, the high and low bytes are accessed by appending ‘H’ and ‘L’ respectively to the register name. For
example, the ADC Result Register is named ADRES and the two bytes are ADRESL and ADRESH.
After the Register Summary section in the device data sheet, each register has a Register Definition section, which
fully describes the functionality of each bit and bit field in the register. The Register Definitions section shows one
instance of all the register names with an ‘x’ in place of the peripheral instance number. An example is presented in
Figure 1-5.
Bit 7 6 5 4 3 2 1 0
SPEN RX9 SREN CREN ADDEN FERR OERR RX9D
Access R/W R/W R/W R/W R/W RO R/HC R/HC
Reset 0 0 0 0 0 0 0 0
Since the prefix for the peripheral module type is unique, each bit name described in the data sheet is unique.
The device header file offers some register bits a Short Bit Name alternative consisting of only the bit function
abbreviation. Since this is defined in the context of a bit union for a specific register, the bit access remains unique.
For further details on how to access a bit using the short or long naming convention, refer to 2.1.1 Register Unions.
For further details on how to set the Configuration Bits consult the 3.4 Setting Configuration Bits section.
#include <xc.h>
All of the required register macro definitions can be found in the header file along with bit masks, bit field masks, bit
positions and union definitions for the registers. The macros and struct definitions which are already defined in the
device specific header file can be used instead of using a register's address.
This is useful for devices that contain the same module and where the header file definitions for that module are
identical.
typedef union {
struct {
unsigned ADGO :1;
unsigned :1;
unsigned ADFM :1;
unsigned :1;
unsigned ADCS :1;
unsigned :1;
unsigned ADCONT :1;
unsigned ADON :1;
};
struct {
unsigned GO :1;
unsigned :1;
unsigned ADFM0 :1;
};
} ADCON0bits_t;
extern volatile ADCON0bits_t ADCON0bits __at(0xF5B);
The union declaration of the ADCON0 register is shown in the code listing above. This register can be accessed as a
whole using the macro declaration or as an individual bit/bit field from the register using the union declaration. Here is
an example:
The convention used when accessing a bit or a bit field from a register using the union declaration of register is
presented in Figure 2-1.
Figure 2-1. Access Register Unions Convention
ADCON0bits.ADGO
...........continued
Bit Field - ADCRS - ADMD
Bit Mask 0x80 0x40 0x20 0x10 0x08 0x04 0x02 0x01
The naming convention adopted for the predefined bit masks in the header file is presented in Figure 2-2 with an
example for the ADPSIS bit in the ADCON2 register.
Figure 2-2. Naming Convention of Bit Masks
_ADCON2_ADPSIS_MASK
Note: The register name, bit name, bit field name, the ‘MASK’ suffix are separated with ‘_’ and the entire macro
name begins with ‘_’.
The naming convention adopted for the predefined bit field masks in the header file is presented in the Figure 2-3
with an example for the ADMD bit field in the ADCON2 register.
_ADCON2_ADMD_MASK
The bits from a bit field can be accessed as individual bits. To differentiate between these bits, a suffix (index of each
bit in the bit field) is appended to the bit field name. The masks for the bits in a bit field are defined below.
For further details on bit fields, consult Microchip Developer - Bit Fields.
_ADCON2_ADPSIS_POSITION
The bit position definition for the ADPSIS bit from the header file is shown below.
The bit positions are included for compatibility reasons. They are also needed when programming in assembly for
instructions that use a bit number.
3.1.1 Set, Clear and Read Register Bits using Bit Unions
The recommended coding style to set or clear a specific bit in a register is to use the union declaration of the register
from the header file.
The example below shows how to set and clear the Enable bit from the ADCON0 register using the recommended
coding style.
The value of a register bit can be tested as follows. The code listing below shows how to poll the ADGO bit, waiting
until it is cleared:
Note: Setting the ADGO bit in the ADC´s ADCON0 register starts an ADC conversion. This bit is then cleared by
hardware when the conversion is complete.
The code listing below shows how to read the value of a PORT pin using bit unions and execute a set of instructions
if that pin is low.
/* set of instructions */
}
3.1.2 Set, Clear and Read Register Bits using Bit Masks
There are alternative ways to set and clear register bits by using bit masks or bit positions.
In order to set a bit from a register using bit masks, the binary OR operator will be applied between the register and
the bit mask. Using the binary OR operator will ensure that the other bit settings made inside the register will remain
the same and unaffected by this operation.
In order to clear a bit from a register using bit masks, the binary AND operator will be applied between the register
and the negated value of the bit mask. This operation also keeps the other bit settings unchanged.
The bit mask for the ADC Enable bit (ADON) has the following declaration in the header file.
The code listing below shows how to read the value of a PORT pin using bit masks and execute a set of instructions
if that pin is low.
3.1.3 Set, Clear and Read Register Bits using Bit Positions
In order to set a bit from a register using bit positions, the binary OR operator will be applied between the register and
the value resulting from shifting ‘1’ with the value of the bit position. To clear a bit using bit positions the binary AND
operator is used with the negated value of the shifting result.
Note: The bit position for the ADC Enable bit (ADON) has the following declaration in the header file.
The code listing below shows how to read the value of a PORT pin using bit positions and execute a set of
instructions if that pin is low.
Name: IPR3
Address: 0xEB8
Bit 7 6 5 4 3 2 1 0
RC2IP TX2IP RC1IP TX1IP BCL2IP SSP2IP BCL1IP SSP1IP
Access R/W R/W R/W R/W R/W R/W R/W R/W
Reset 0 0 1 1 0 0 1 1
The following example will apply the various methods of configuring a register (T0CON0 – Timer 0 Control Register
0), shown in the figure below.
Note: For the register in this example (T0CON0), all bits and bit fields are ‘0’ after reset.
Name: T0CON0
Address: 0xFD4
Bit 7 6 5 4 3 2 1 0
T0EN T0OUT T016BIT T0OUTPS[3:0]
Access R/W R R/W R/W R/W R/W R/W
Reset 0 0 0 0 0 0 0
However, to improve the readability (and potentially the portability) of the code, it is recommended to use the device
defines, which are shown in the upcoming sections.
Note: The bit wise OR (‘|’) operator on the register side of the assignment is left out. In most cases, device and
peripheral initialization routines are written in this way.
The above initialization of the register must be done in a single line of C code. Writing as follows, the bit
CAUTION
mask used in the second and third line would clear the bits set in the previous lines.
Note: Bit Masks can only set bits in a single line of code, so configurations which require bits to be cleared are left
out since they are correctly configured by their reset value.
In this example, no mask is used to explicitly configure the timer in the 8-bit mode. This is possible because the reset
value of the T016BIT is '0' which corresponds to the 8-bit mode. To emphasize the configuration of this bit as 0, the
user could explicitly select the desired 8-bit mode by using a read-modify-write operation. However, this would need
to be a separate line of code and would leave the register unchanged:
| (1 << _T0CON0_T0OUTPS0_POSITION)
| (1 << _T0CON0_T0OUTPS3_POSITION); /* Select 1:10 postscaler */
Note: The (0 << _T0CON0_T016BIT_POSITION) line does nothing here, but it can be used as a placeholder to
quickly change bit settings.
This is the recommended way of updating bit field register configurations, which is simpler than the alternative
options.
/* The T0OUTPS bit field is cleared (Selecting a postscaler of 1:1 (T0OUTPS = 0)) */
T0CON0 &= ~_T0CON0_T0OUTPS_MASK;
/* Selecting new configuration (0b0111) of the T0OUTPS bit field */
T0CON0 |= _T0CON0_T0OUTPS2_MASK | _T0CON0_T0OUTPS1_MASK | _T0CON0_T0OUTPS0_MASK;
The bit field mask for the TMR0 Output Postscaler Select (T0OUTPS) has the following declaration in the header file.
These steps must be implemented in a single line to avoid putting the microcontroller in an unintended state.
Note: If the code is implemented as two separate lines, the first line of code will select for a short time, a postscaler
of 1:1 (T0OUTPS = 0).
Even though it may seem easier to split the code into two separate lines of code, one to clear the old
CAUTION
configuration and another to set the desired configuration. It is recommended to use a single line to
achieve this, as presented in the code listing.
Note: The (0 << _T0CON0_T0OUTPS3_POSITION) line is added simply for readability, but it can be removed.
CAUTION
• Not setting the Configuration Bits can prevent even blinking an LED. Even if the TRIS register is set
up and a value is written to the port, there are several things that can prevent such seemingly simple
program from working.
• Ensure that the device's Configuration registers are set up correctly. Also, the user must make sure
that every bit in these registers is explicitly specified, not leaving them in their default state. All the
configuration features are described in the device data sheet. If the Configuration Bits that specify the
oscillator source are wrong, for example, the device clock cannot be running.
• For more information, refer to the MPLAB XC8 C Compiler User’s Guide.
Example configurations can be found for specific devices under the Configuration Settings Reference section. An
example for the PIC16F18446 is shown below.
Figure 3-4. PIC16F18446 Example Configuration
#include <xc.h>
void main(void)
{
/* setting pin RE0 as output (LED) */
TRISEbits.TRISE0 = 0;
/* setting pin RE2 as input (button) */
TRISEbits.TRISE2 = 1;
/* enable digital input buffer for pin RE2 (button) */
ANSELEbits.ANSELE2 = 0;
/* enable internal pull-up for pin RE2 (button) */
WPUEbits.WPUE2 = 1;
#include <xc.h>
void main(void)
{
/* setting pin RE0 as output (LED) */
TRISE &= ~_TRISE_TRISE0_MASK;
/* setting pin RE2 as input (button) */
TRISE |= _TRISE_TRISE2_MASK;
/* enable digital input buffer for pin RE2 (button) */
ANSELE &= ~_ANSELE_ANSELE2_MASK;
/* enable internal pull-up for pin RE2 (button) */
WPUE |= _WPUE_WPUE2_MASK;
{
/* turn on the LED (pin RE0 high) */
LATE |= _LATE_LATE0_MASK;
}
else
{
/* turn off the LED (pin RE0 low) */
LATE &= ~_LATE_LATE0_MASK;
}
}
}
#include <xc.h>
void main(void)
{
/* setting pin RE0 as output (LED) */
TRISE &= ~(1 << _TRISE_TRISE0_POSITION);
/* setting pin RE2 as input (button) */
TRISE |= (1 << _TRISE_TRISE2_POSITION);
/* enable digital input buffer for pin RE2 (button) */
ANSELE &= ~(1 << _ANSELE_ANSELE2_POSITION);
/* enable internal pull-up for pin RE2 (button) */
WPUE |= (1 << _WPUE_WPUE2_POSITION);
5. Further Steps
The purpose of this section is to direct the user to the IDE installation guides and tutorials, the available application
notes and the technical briefs.
6. Conclusion
The main purpose of this technical brief is to introduce the user to a preferred coding style for programming the PIC
microcontrollers. After reviewing this document, users will understand the type of information the data sheet is
providing, macro definitions, variable declarations and data type definitions provided by the header files. The goals
are to use an easily maintainable, portable and readable coding style; to become familiar with the naming
conventions for the PIC registers and bits; and to prepare for further steps in developing a project using these
microcontrollers.
This document provides information on specific data sheets, naming conventions, guidance on how to write C-code
for PIC microcontrollers and further steps in developing a project.
While the C-code writing methods suggested here are not mandatory, one can consider the numerous advantages.
The larger the project and the more features the device has, the greater the benefit of C-code utilization.
7. References
1. PIC18F27/47Q10 Data Sheet
2. MPLAB XC8 Getting Started Guide
3. MPLAB XC8 C Compiler User’s Guide
4. Microchip Developer - Fundamentals of the C Programming Language
Customer Support
Users of Microchip products can receive assistance through several channels:
• Distributor or Representative
• Local Sales Office
• Embedded Solutions Engineer (ESE)
• Technical Support
Customers should contact their distributor, representative or ESE for support. Local sales offices are also available to
help customers. A listing of sales offices and locations is included in this document.
Technical support is available through the website at: www.microchip.com/support
Legal Notice
Information contained in this publication regarding device applications and the like is provided only for your
convenience and may be superseded by updates. It is your responsibility to ensure that your application meets with
Trademarks
The Microchip name and logo, the Microchip logo, Adaptec, AnyRate, AVR, AVR logo, AVR Freaks, BesTime,
BitCloud, chipKIT, chipKIT logo, CryptoMemory, CryptoRF, dsPIC, FlashFlex, flexPWR, HELDO, IGLOO, JukeBlox,
KeeLoq, Kleer, LANCheck, LinkMD, maXStylus, maXTouch, MediaLB, megaAVR, Microsemi, Microsemi logo, MOST,
MOST logo, MPLAB, OptoLyzer, PackeTime, PIC, picoPower, PICSTART, PIC32 logo, PolarFire, Prochip Designer,
QTouch, SAM-BA, SenGenuity, SpyNIC, SST, SST Logo, SuperFlash, Symmetricom, SyncServer, Tachyon,
TempTrackr, TimeSource, tinyAVR, UNI/O, Vectron, and XMEGA are registered trademarks of Microchip Technology
Incorporated in the U.S.A. and other countries.
APT, ClockWorks, The Embedded Control Solutions Company, EtherSynch, FlashTec, Hyper Speed Control,
HyperLight Load, IntelliMOS, Libero, motorBench, mTouch, Powermite 3, Precision Edge, ProASIC, ProASIC Plus,
ProASIC Plus logo, Quiet-Wire, SmartFusion, SyncWorld, Temux, TimeCesium, TimeHub, TimePictra, TimeProvider,
Vite, WinPath, and ZL are registered trademarks of Microchip Technology Incorporated in the U.S.A.
Adjacent Key Suppression, AKS, Analog-for-the-Digital Age, Any Capacitor, AnyIn, AnyOut, BlueSky, BodyCom,
CodeGuard, CryptoAuthentication, CryptoAutomotive, CryptoCompanion, CryptoController, dsPICDEM,
dsPICDEM.net, Dynamic Average Matching, DAM, ECAN, EtherGREEN, In-Circuit Serial Programming, ICSP,
INICnet, Inter-Chip Connectivity, JitterBlocker, KleerNet, KleerNet logo, memBrain, Mindi, MiWi, MPASM, MPF,
MPLAB Certified logo, MPLIB, MPLINK, MultiTRAK, NetDetach, Omniscient Code Generation, PICDEM,
PICDEM.net, PICkit, PICtail, PowerSmart, PureSilicon, QMatrix, REAL ICE, Ripple Blocker, SAM-ICE, Serial Quad
I/O, SMART-I.S., SQI, SuperSwitcher, SuperSwitcher II, Total Endurance, TSHARC, USBCheck, VariSense,
ViewSpan, WiperLock, Wireless DNA, and ZENA are trademarks of Microchip Technology Incorporated in the U.S.A.
and other countries.
SQTP is a service mark of Microchip Technology Incorporated in the U.S.A.
The Adaptec logo, Frequency on Demand, Silicon Storage Technology, and Symmcom are registered trademarks of
Microchip Technology Inc. in other countries.
GestIC is a registered trademark of Microchip Technology Germany II GmbH & Co. KG, a subsidiary of Microchip
Technology Inc., in other countries.
All other trademarks mentioned herein are property of their respective companies.
© 2020, Microchip Technology Incorporated, Printed in the U.S.A., All Rights Reserved.
ISBN: 978-1-5224-6166-1