Embedded C Programing w/ TM4C123G Microcontroller - 2.0 Functions, Macros & Header Files

In our previous section, Embedded C Programing w/ TM4C123G Microcontroller - 1.0 Register Addresses, it was discussed how to blink an LED by interacting directly with register addresses within the TM4C123G Microcontroller. A not very commune practice. 

Here, we would like to simplify things by introducing Functions, Macros, and Header Files


Function

Currently, the code is a small program easily to interact. But eventually, it would become a little bit more challenging when adding more features. This means, more register addresses to manage. 

Preliminary Code
/*
This program toggles the Green LED ON & OFF by using register addresses.  
The other LEDs (Red & Blue) can be configured as well. All LEDs are high active (a "1" turns ON the LED). PF1 - red LED PF2 - blue LED PF3 - green LED */ int main() { //Clock Gating Register *((unsigned int *)0x400FE608U) = 0x20U; //enable clock signal to GPIOF //GPIODIR Register *((unsigned int *)0x40025400U) = 0x0EU; //Setting bits 3-1 as outputs //GPIODEN Register *((unsigned int *)0x4002551CU) = 0x0EU; //Setting bits 3-1 as digital pins while (1) { //*((unsigned int *)0x400253FCU) = 0x02U; //Setting bit 1 to turn Red LED ON //*((unsigned int *)0x400253FCU) = 0x04U; //Setting bit 2 to turn Blue LED ON *((unsigned int *)0x400253FCU) = 0x08U; //Setting bit 3 to turn Green LED ON //Offset: 0x3FC - Bit banding int counter = 0; while (counter < 1000000) { ++counter; } *((unsigned int *)0x400253FCU) = 0x00U; //Turn OFF LED counter = 0; while (counter < 1000000) { ++counter; } } //return 0; }

 

To reduce the size of the code, promote reusability and for easy debugging, it would make sense to use Functions.

A function is a block of code that performs a specific task.

Example:

#include <stdio.h>

#define Pi 3.14

float CircleArea(float r);         // function prototype


int main()

{

    float Area, f;

    printf("Enters the diameter of the circle: ");

    scanf("%f",&f);


    Area = CircleArea(f);        // function call

    printf("Area of the Circle = %f",Area);


    return 0;

}


float CircleArea(float d)         // function definition   

{

    float a;

    a = Pi*((d*d)/4);

    return a;                  // return statement

}


In addition to main, the piece of block that performance an specific task here is:


float CircleArea(float r)         // function definition   

{

    float a;

    a = Pi*(r*r);

    return a;                  // return statement

} 


The C compiler recognizes CircleArea(float r) specifically as a function.

Having said that, let's create one for our previous code: the delay() function:

void delay()     // Function Declaration

Preliminary Code

void delay() {
int counter = 0;
while (counter < 1000000) {
    	++counter;
	}
}

int main()
{
  
   //Clock Gating Register
  *((unsigned int *)0x400FE608U) = 0x20U;
  //GPIODIR Register
  *((unsigned int *)0x40025400U) = 0x0EU;
  //GPIODEN Register
  *((unsigned int *)0x4002551CU) = 0x0EU;
  
  while (1) {
    
//*((unsigned int *)0x400253FCU) = 0x02U; //Red LED
   	//*((unsigned int *)0x400253FCU) = 0x04U; //Blue LED
  	*((unsigned int *)0x400253FCU) = 0x08U; //Green LED
    
  	delay();
    
   	*((unsigned int *)0x400253FCU) = 0x00U;
    	
	delay();
    
  }


I just replace the two while statements from the previous code:

while (counter < 1000000) {
    	++counter;
   	 }


with one single function definition called delay:

void delay() {
int counter = 0
while (counter < 1000000) {
    	++counter;
	}
}

Note:

  • A Prototype can occur at the top of a C source code file to describe what the function returns and what it takes (return type and parameter list). When this is the case (occurring at the top of the file), the function prototype should be followed by a semi-colon.
  • The function prototype is not needed if the user-defined function is defined before the main() function.


MACROS:


A macro is a fragment of code that has been given a name, sort of an abbreviation, which is defined once and then used later. 


Syntax


#define CNAME value


Example: 


#define GPIO_PORTF_DATA_R *((unsigned int *)0x400253FCU) 


Here:


#: Is a preprocessor directive. It acts as a flag or indicator telling the compiler to pay attention to the following text as a special instruction before the regular compilation process.


define: The directive name. In this case the directive name is define. Which means, treat what follows as a MACRO. 


#define: The actual preprocessor directive. Telling the compiler to create a macro named GPIO_PORTF_DATA_R with the value stored in this particular address *((unsigned int *)0x400253FCU).


GPIO_PORTF_DATA_R: name of the constant. 


*((unsigned int *)0x400253FCU): value of the constant.  


Note: semicolon ( ; ) is not needed.

 


Now, with the same aim of functions the code can be simplify further by using macros.


Typing  *((unsigned int *)0x400253FCU) = 0x08U over and over it’s time-consuming and error-prone. Is way effective to handle constant names instead of pointers pointing to different addresses : *((unsigned int *)0x400253FCU)and hexadecimal numbers.


Preliminary Code

/*Macro Definition*/

#define RCGCGPIO    *((unsigned int *)0x400FE608U)
#define GPIO_DIR    *((unsigned int *)0x40025400U)
#define GPIO_DEN    *((unsigned int *)0x4002551CU)
#define GPIO_DATA   *((unsigned int *)0x400253FCU)

void delay() {
int counter = 0
while (counter < 1000000) {
    	++counter;
	}
}

int main()
{
   
   RCGCGPIO = 0x20U; 	//Enable Clock Gating Register
   GPIO_DIR = 0x0EU;	//set pins 1, 2 & 3 as output. 
   GPIO_DEN  = 0x0EU;	//set pins 1, 2 & 3 as digital output. 
  
   while (1) {
    
GPIO_DATA = 0x02U; //Red LED ON

delay();
   	    
  	GPIO_DATA = 0x00U; //Red LED OFF

	delay();
    	
 	}
  }


Note: Read C Preprocessor: Macros and Header Files for an overview of macros.




HEADER FILES


Like we just did with preprocessor directive "#define", we can use "#include" as another preprocessor directive to include what is known as "header files". 


A header file is a file that contains C function declarations, struct and macro definitions to be shared between several source files. The given name should end with an .h extension.


Microcontrollers vendors already provide C "function" declaration, “struct” and “macro” definitions in a separate file. Therefore, no need to invest in defining the macros by your own. In the case of the Tiva TM4C123G LaunchPad there are two different header files that you can use.


HEADER FILE with MACROS definition


The one from Texas Instrument, which can be find through the TivaWare software suite, includes the  “tm4c123gh6pm.h” header file. In this header file, each register is defined with his own name (Macros). For example, the Direction Register of Port F is referred to as GPIO_PORTF_DIR_R and the data register of Port F is referred to as GPIO_PORTF_DATA_R. This file can be found at the installation folder: C:\ti\TivaWare_C_Series-2.2.0.295\inc


With this in mind we just need to remove the macro definitions of our previous program and replace it with a single line statement: #include “LM4F120H5QR.h”.


That is… from this

/*Macro Definition*/
#define RCGCGPIO    *((unsigned int *)0x400FE608U)
#define GPIO_DIR    *((unsigned int *)0x40025400U)
#define GPIO_DEN    *((unsigned int *)0x4002551CU)
#define GPIO_DATA   *((unsigned int *)0x400253FCU)


to one single line statement:

#include “LM4F120H5QR.h”

In your IAR Embedded Workbench IDE make one of the include paths be the "inc" folder. And add LM4F120H5QR.h to it.


Example Code (w/ the names as specify in the macro header file): 


#include “tm4c123gh6pm.h” // “Header File with macros”


void delay() {

int counter = 0;

while (counter < 1000000) {

     ++counter;

}

}


int main()

{

   SYSCTL_RCGCGPIO_R = 0x20U; //Enable Clock Gating Register

   GPIO_PORTF_DIR_R = 0x0EU; //set pins 1, 2 & 3 as output. 

   GPIO_PORTF_DEN_R  = 0x0EU; //set pins 1, 2 & 3 as digital output. 

  

   while (1) {

    

GPIO_DATA = 0x02U; //Red LED ON

delay();    

   GPIO_DATA = 0x00U; //Red LED OFF

delay();

    

  }

}


HEADER FILE w/ STRUCT definition


On the other hand, the IAR IDE tool provide <TM4C123GH6PM.H> as a header file with "struct" defenition, meaning each port is defined as a pointer to a struct with the registers as the members of the struct, For example, the Direction Register of Port A is referred to as GPIOA → DIR and the Data Register of Port F is referred to as GPIOF → DATA and so on. In the IAR IDE tool this file can be found in the system directory folder: C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.4\arm\inc\TexasInstruments



Example CODE (w/ the name as specify in the struct header file): 


#include “TM4C123GH6PM.H” // “Header File with struct”


void delay() {

int counter = 0;

while (counter < 1000000) {

     ++counter;

}

}


int main()

{

   SYSCTL → RCGCGPIO |= 0x20U; //Enable Clock Gating Register

   GPIOF → DIR = 0x0E; //set pins 1, 2 & 3 as output. 

   GPIOF → DEN = 0x0EU; //set pins 1, 2 & 3 as digital output. 

  

   while (1) {

    

GPIOF → DATA = 0x02U; //Red LED ON

delay();    

   GPIOF → DATA = 0x00U; //Red LED OFF

delay();

    

  }


}


IMPORTANT:


From now on, the following examples will use TM4C123GH6PM.H from the IAR IDE tool as the default header file.

Comments