C Code Organization


One of the key benefits to use a high level language is the ability to write modularized code.  This allows us to separate functionally distinct pieces of code into files that can be shared between multiple projects.  We will look at how to use header files and corresponding source files can be organized to form a functional library that can be called from other functions within an application.

Header Files

Including a Header File

In the C programming language, a header file is included in a program using the keyword include.  The C compiler will replace this include statement with the contents of the header file.  So lets take a look at a few examples of how to include a header file.

// main.c
// Author: jkrachey@wisc.edu

#include <stdint.h>               // 1

#include "cyhal.h"                // 2
#include "./boardUtil.h"          // 3
#include "../include/test.h"      // 4


  1.  C compilers generally support a group of standard libraries.  In order to include these standard libraries, you use the less than and greater than characters surrounding the library name
  2. The PSoC6 BSP includes a set of libraries that are required to support specific microprocessors.
  3. For a header file found in the same directory as the current project, we use open and closed quotes along with the file name.
  4. If the header file is not contained in the current project directory, we need to give a file path relative to the current directory.  The “../” means back one directory.

Header File Structure

Header files are used to describe how to properly interface with the code contained in the associated source file.  The header file is used as a forward declaration of global variables and functions defined by the library.  Lets take a look at an example of a header file and examine the purpose of different parts of the header file.

#ifndef __BOARDUTIL_H__                       //(1)
#define __BOARDUTIL_H__                       //(2)

#include <stdint.h>                           //(3)

#define   ENABLE_DEBUG    1                   //(4)

typedef  struct {                             //(5)
  uint32_t baseAddr;
  uint32_t serialNumber;
} exampleStruct;

// Global Variables
extern uint32_t BoardStatus;                  //(6)

// initBoard
void initBoard(exampleStruct *boardConfig);   //(7)

  1.  Header files should always start with an #ifndef statement.  If this line was not present, we may have a situation where multiple files in a project all include the same header file.  This would result in a compiler error about a function/variable being multiply declared.  The #ifndef says to only include the following code if the specified token has not previously been defined.  It should be noted that a line of code that starts with a # is a compiler directive.  These lines do not generate any source code, rather it instructs the compiler on how the image should be generated
  2. If the token on line 1 has not been defined, we need to define it
  3. Any necessary #include statements that are required can be added in the header file
  4. You can define any macros required by your application with a #define.  The compiler replaces the macro with whatever string follows the macro.
  5. Any structures created for the accompanying source file should be declared in the header file.
  6. Global variables that can be accessed from another portion of the program should be declared as type extern.
  7. Any functions that will be accessed from outside of the accompanying source file should be declared in the header file.  The list of functions in the header file form the external API of the library.  List only functions that are intended to be accessed from outside the library.

Source Files

Each header file is normally accompanied by a corresponding source file.  The source file contains the implementation (source code) of the library.  Lets take a look at how the source files are structured.

#include "boardUtil.h"                          // 1

// Global Variables
uint32_t BoardStatus = 0;                       // 2
static uint32_t SerialNumber;                   // 3

// initUart
static void initUart(uint32_t newSerialNumber)  // 4
  // initialize UART

// initBoard
void initBoard(exampleStruct *boardConfig)      // 5
  BoardStatus = 1;  
  1.  The source file will include its associated header file.
  2. Global variables that are accessed by the library or a different part of the application need to be declared in the source file.  Make sure there is an accompanying extern of the global variable in the header file.
  3. Global variables that should not be accessed outside of the current source file should be marked as static.  This makes the global variable private to the current file
  4. Functions that are internal to the library and you wish to restrict access should also be marked as static.
  5. Functions declared in the header file must be implemented in the source file.  The function declaration in the header file must match what is in the source file.