We are going to look at how the C preprocessor is used in the textbook examples to configure the clock of the PIC24 processor.
Almost all of the textbook examples start with the following include to get the needed definitions and compiled functions.
#include "pic24_all.h"
The textbook’s include files are located in the lib/include directory. The pic24_all.h include file contains the standard #include guard to prevent double inclusion.
#ifndef _PIC24_ALL_H_ #define _PIC24_ALL_H_
The pic24_all.h file also #include
s
several more .h that
provide specialized fucntionality.
One of these, pic24_chip.h,
provides definitions that vary according to the processor.
#include "pic24_chip.h"
The first action of pic24_chip.h is to include the appropriate definition files provided with the Microchip compiler.
#if defined(__PIC24H__) #include "p24Hxxxx.h"
The way in which __PIC24H__
is defined
isn’t straightforward.
In our case, the MPLAB X IDE generates
a Makefile which controls the build process.
The Makefile specifies
that the C compiler is invoked with a command line argument
similar to -mcpu=24HJ64GP502
.
The C compiler will then generate a pre-defined macro
__PIC24H__
.
C and C++ compiler generates a lot of pre-defined macro.
Programmers just need to look up what they are.
The include file p24Hxxxx.h is part of the Microchip compiler distribution, not the textbook examples. You’ll find this file within the support/PIC24H/h subdirectory of the Microchip distribution. This include file contains unusual C definitions, such as the following, which are used to manipulate specical function registers.
extern volatile unsigned int PORTB __attribute__((__sfr__)); typedef struct tagPORTBBITS { unsigned RB0:1; unsigned RB1:1; unsigned RB2:1; unsigned RB3:1; unsigned RB4:1; unsigned RB5:1; unsigned RB6:1; unsigned RB7:1; unsigned RB8:1; unsigned RB9:1; unsigned RB10:1; unsigned RB11:1; unsigned RB12:1; unsigned RB13:1; unsigned RB14:1; unsigned RB15:1; } PORTBBITS; extern volatile PORTBBITS PORTBbits __attribute__((__sfr__));
/* PORTB */ #define _RB0 PORTBbits.RB0 #define _RB1 PORTBbits.RB1 #define _RB2 PORTBbits.RB2 #define _RB3 PORTBbits.RB3 #define _RB4 PORTBbits.RB4 #define _RB5 PORTBbits.RB5 #define _RB6 PORTBbits.RB6 #define _RB7 PORTBbits.RB7 #define _RB8 PORTBbits.RB8 #define _RB9 PORTBbits.RB9 #define _RB10 PORTBbits.RB10 #define _RB11 PORTBbits.RB11 #define _RB12 PORTBbits.RB12 #define _RB13 PORTBbits.RB13 #define _RB14 PORTBbits.RB14 #define _RB15 PORTBbits.RB15
These C definitions do not specify the actual address of the
special function register PORTB
.
That is found in linker files within the directory
support/PIC24H/gld subdirectory containing
lines similar to the following:
PORTB = 0x2CA; _PORTB = 0x2CA; _PORTBbits = 0x2CA;
Eventually p24Hxxxx.h “returns” to pic24_chip.h which “returns” pic24_all.h. There we will find an include of another configuration file.
// Include user-configurable options #include "pic24_libconfig.h"
It is within pic24_libconfig.h that we find a macro definition for the default of the clock for a PIC24H processor. (Doxygen is a tool, similar to Javadoc, for generaing documention from a program.)
#elif defined(__PIC24H__) || defined(__DOXYGEN__) #define CLOCK_CONFIG FRCPLL_FCY40MHz
We soon drop back to pic24_all.h which contains the include for the file for actually defining the clock configuration options.
#include "pic24_clockfreq.h"
Within pic24_clockfreq.h is some of the most complicated C macrory (invented word) you will even encounter. First is the definition of possible clock frequencies.
#define FRCPLL_FCY40MHz 5, FNOSC_FRCPLL, 40000000L, POSCMD_NONE, -1, (PIC24H_DEFINED || dsPIC33F_DEFINED), configClockFRCPLL_FCY40MHz, 498
This is followed by a “non-standard #if” to “to make sure the CLOCK_CONFIG choice selected exists and is valid”.
#if (CLOCK_CONFIG != 498)
This test takes advantage of the C compiler’s ability to do a little
expression evalution.
Remember that CLOCK_CONFIG
is FRCPLL_FCY40MHz
and
FRCPLL_FCY40MHz
is a sequence of eight expressions separated by commas.
The last expression is the number 498.
When the macro substiutions are made the last expression of this sequence will be
498 != 498
. Because the comma operator of C, just like the
comma operator of Java, returns its last value;
(CLOCK_CONFIG != 498)
evalutes to zero.
Wow!
If you continue through the file, you will encounter several
definitions designed to produce a value for FCY
.
#define GET_FCY(params) _GET_FCY(params)
#define _GET_FCY(ndx, oscSel, Fcy, posCmdSel, poscFreq, isSupported, configClockFunc, magic) Fcy
#define FCY GET_FCY(CLOCK_CONFIG)
There is also a series of definitions to define a default code configuration function
#define GET_IS_SUPPORTED(params) _GET_IS_SUPPORTED(params) #define GET_CONFIG_DEFAULT_CLOCK(params) _GET_CONFIG_DEFAULT_CLOCK(params)
#define _GET_IS_SUPPORTED(ndx, oscSel, Fcy, posCmdSel, poscFreq, isSupported, configClockFunc, magic) isSupported #define _GET_CONFIG_DEFAULT_CLOCK(ndx, oscSel, Fcy, posCmdSel, poscFreq, isSupported, configClockFunc, magic) configClockFunc
#define CONFIG_DEFAULT_CLOCK() GET_CONFIG_DEFAULT_CLOCK(CLOCK_CONFIG)()
#if GET_IS_SUPPORTED(FRCPLL_FCY40MHz) void configClockFRCPLL_FCY40MHz(void); #endif
static inline void configClock() { CONFIG_DEFAULT_CLOCK(); }
It’s the last definition that makes
a call to configClock()
result in a call of
configClockFRCPLL_FCY40MHz()
.
If you take a look at pic24_clockfreq.c,
contained in the
lib/common directory,
you’ll see the
configClockFRCPLL_FCY40MHz()
is compiled
only when the FRCPLL_FCY40MHz
clock is supported for
your chosen processor.
This is explained in great detail in the textbook.