Practical Embedded C || Preprocessor Directive || Chapter 2

In this blog you will learn what is Preprocessor Directives in Embedded C using LPC1768.

1. What is preprocessor Directive :


The first stage of C code compilation phase is preprocessing. Preprocessor scans the code, modify it and gives to compiler stage. C Preprocessor directives are commands to Preprocessor, Preprocessor directives starts with symbol #.

Preprocessor scans the code and look for token #, when # encounters preprocessor takes action to  based on preprocessor directive and modifies souce code.Modified source will not contain any preprocessor directive (starts with #) and this code without Preprocessor directive is given to compiler for compilation. 

The `#' is followed by an identifier that is the directive name.The set of valid directive names is fixed. Programs cannot define new preprocessing directives.

Note : There is no semicolon at end of preprocessor directive and multi line preprocessor directive uses " \" back slash.




2. Functionalities of Preprocessor Directives :


Below are the some functionalities of Preprocessor Directives used in embedded system

2.1. To include files :

To include files preprocessor directive used is #include. 

#include<filename.h> : Includes file from Standard directory. Stand directory path is defined in IDE.

#include "filename.h" : Includes file from current directory where respective source files is present  and if it does not found file in current directory then preprocessor searches file in standard directory and includes from there.

Preprocessor will find #include as command and includes a file's contents in C source code
e.g. #include<stdio.h>.  in C you have used this.

In embedded C, micro-controller specific header files are included. Consider example of LPC1768, device header file is lpc17xx.h and ARM CORTEX M3 related file is core_cm3.h so while programming you have to include below files :


#include <lpc17xx.h> 
#include <core_cm3.h> /*Cortex M3 architecture related content*/

void main()

{

 /*Kept empty to explain #include*/

}


After Preprocessing :

After Preprocessing Contents from header files will be added to source file, it will look like this :
There are more lines but for example few lines are shown below


Part of content from lpc17xx.h :
typedef struct
{
  volatile uint32_t SPCR;
  volatile const uint32_t SPSR;
  volatile uint32_t SPDR;
  volatile uint32_t SPCCR;
  uint32_t RESERVED0[3];
  volatile uint32_t SPINT;
} LPC_SPI_TypeDef;

Part of content from  core_m3.h :

typedef struct
{
  volatile uint32_t CTRL;
  volatile uint32_t LOAD;
  volatile uint32_t VAL;
  volatile const uint32_t CALIB;
} SysTick_Type;

void main()

{

}



2.2. To perform conditional compilation : 

In conditional compilation using preprocessor directive, particular section of code is selected using preprocessing Directive, and that selected code will be compiled.

Conditional compilation is required
1. A program may need to use different code depending on the machine or operating system it is to run on. So these will a conditional compilation Preprocessor directive.
2. You may want to be able to compile the same source file into two different programs
3. A conditional whose condition is always false is a good way to exclude code from the program but keep it as a sort of comment for future reference.


For these purpose Preprocessor Directives used are#if #else  #elsif  #ifdefined #ifndef, but remember to use #endif at end. It is similar as if else control structure.       

Below code snippet shows use of conditional compilation. If condition is true code will be part of source file after preprocessor else it will be excluded.

In lpc176xx.h file __Vendor_SysTickConfig     is defined as
#define __Vendor_SysTickConfig    0

as  __Vendor_SysTickConfig     is 0 below part of code will be compiled i.e. below code snippet will be part of source code after preprocessing.


#if (!defined (__Vendor_SysTickConfig)) || (__Vendor_SysTickConfig == 0)

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
     SysTick_CTRL_TICKINT_Msk   | 
     SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

#endif


Conside below example.  __CM3_REV is not defined

typedef struct
{
       uint32_t RESERVED0;
  __I  uint32_t ICTR;                         /*!< Offset: 0x04  Interrupt Control Type Register */
#if ((defined __CM3_REV) && (__CM3_REV >= 0x200))
  __IO uint32_t ACTLR;                        /*!< Offset: 0x08  Auxiliary Control Register      */
#else
       uint32_t RESERVED1;
#endif
} InterruptType_Type;

After Preprocessing
stage ACTLR will not be part of structure i.e will not be part of  source code, as shown in below code snippet.


typedef struct
{
       uint32_t RESERVED0;
       volatile const uint32_t ICTR;



       uint32_t RESERVED1;

} InterruptType_Type;


Similarly piece of code can be part of compilation based on conditional compilation preprocessor directive.




2.3. Macros Expansion :


Macros are used to give symbolic names. Macro is name given to piece of code, when Macros appears in code pre-processor will copy contents of macro (defined by programmer) and replace it in source file.

Syntax : #define  Macro_Name   Macro_defination
e.g. #define ARRAY_SIZE 5 
This is macro and when ARRAY_SIZE is encountered in C source code at time of preprocessing, preprocessor will replace ARRAY_SIZE with 5 in code.

Now we will see how macros are used in Embedded C . Consider case of LPC 1768, in lpc17xx.h file you can find ADC structure is defined like :

typedef struct
{
  __IO uint32_t ADCR;
  __IO uint32_t ADGDR;
       uint32_t RESERVED0;
  __IO uint32_t ADINTEN;
  __I  uint32_t ADDR0;
  __I  uint32_t ADDR1;
  __I  uint32_t ADDR2;
  __I  uint32_t ADDR3;
  __I  uint32_t ADDR4;
  __I  uint32_t ADDR5;
  __I  uint32_t ADDR6;
  __I  uint32_t ADDR7;
  __I  uint32_t ADSTAT;
  __IO uint32_t ADTRM;
} LPC_ADC_TypeDef



Some other MACROS as :

#define LPC_APB0_BASE         (0x40000000UL)
#define LPC_ADC_BASE          (LPC_APB0_BASE + 0x34000)
#define LPC_ADC                       ((LPC_ADC_TypeDef       *) LPC_ADC_BASE      )

e.g. Consider below code snippet. LPC_ADC.ADCR is used in main function. LPC_ADC.ADCR is defined in lpc17xx.h file as shown in above code snippet.


#include "lpc17xx.h"
#include "core_cm3.h"

void main()
{
 LPC_ADC.ADCR = 0x00;
}


After preprocessing LCP_ADC is replaced with MACRO definition defined in header file
Note : Header file expansion is not shown below it is  explained in 2.1
After preprocesssing source code of main function look like :

void main()
{
 ((LPC_ADC_TypeDef *) ((0x40000000UL) + 0x34000) ).ADCR = 0x00;
}



2.4. Error Reporting :

Error reporting is used in case of debugging, #error  displays fatal error.

Below snippet shows #error preprocessor.  If CLKSRCSEL_Val is not in range then error will thrown out at compilation time.


#if (CHECK_RANGE((CLKSRCSEL_Val), 0, 2))
   #error "CLKSRCSEL: Value out of range!"
#endif

This is how Preprocessor Directives are used in Embeddded system using Embedded C.

Post a Comment