Libraries¶
A library is considered any external C code from the main code module that we want to reuse. Typically, libraries contain reusable functions, such as mathematical functions that have multiple use cases. The actual library code can be a C-language program, i.e., a source file
pasta.c
, or it can be precompiled code (i.e., binary pasta.o
) that is linked to our program during the compilation process.Library Introduction¶
A library always has its own header file. The header file introduces the functions, global variables, constants (and macros) of the library that the programs using the library can access. Adding a header file from the compiler's perspective is equivalent to copying the code directly where the #include command is placed in the code file.
A header file
pasta.h
might look like this:#pragma once // Note! The macro prevents multiple inclusions
#include <stdio.h>
// Function prototypes
uint8_t add_water(void);
uint8_t add_salt(void);
uint8_t is_water_boiling(void);
void stir_pot(void);
void set_cooking_time(void);
uint8_t is_ready(void);
// New enum type for constants, exciting!!
enum pasta_types { SPAGHETTI=1, MACARONI, FUSILLI, PENNE_RIGATE };
// Global variables
uint8_t _portion_size; // This variable is initialized in the main program
uint8_t _selected_pasta = SPAGHETTI;
Library Implementation¶
Library source code is created in its own code module, i.e., file (here
pasta.c
), which contains the implementations of the shared and internal functions of the libraries.#include "pasta.h" // Forces the library functions to follow the prototypes
// Internal library variables
uint8_t _water_amount = 0;
uint8_t _cooking_time = 0;
uint8_t _portion_size = 0; // We moved this variable here from the header file!!
// Functions
void set_portion_size(uint8_t portions) {
_portion_size = portions;
}
uint8_t get_portion_size(void) {
return _portion_size;
}
uint8_t add_water(void) {
_water_amount = _portion_size * 0.2;
}
void set_cooking_time(void) {
switch(_selected_pasta) {
case SPAGHETTI:
_cooking_time = 10;
break;
case MACARONI:
_cooking_time = 8;
break;
...
}
}
When using a library function in C, there is no need to specify which library the function comes from. Of course, there should not be functions with the same name in different libraries, as the compiler might get confused as to which function is being called.
Variables in the Header File¶
Declaring variables and/or using global variables directly through header files is somewhat questionable. Actually, it might lead to errors.
In this course we are recommending two options: using setter and getter functions or declaring the variable using the
extern
keyword. Getter and setters¶
In this case, variables are not defined in the header, but instead the are accessed and modified using setter and getter function. The variable, can be defined in the C code as
The h. file looks like
static
if we want to make sure that it is not shared other c files. This way, the variable itself would be encapsulated as an internal variable of the library module, allowing better control of its use across different modules.The h. file looks like
#pragma once
#include <stdio.h>
// Function prototypes
uint8_t add_water(void);
uint8_t add_salt(void);
uint8_t is_water_boiling(void);
void stir_pot(void);
void set_cooking_time(void);
uint8_t is_ready(void);
// New enum type for constants, exciting!!
enum pasta_types { SPAGHETTI=1, MACARONI, FUSILLI, PENNE_RIGATE };
//New setters and getters functions.
uint8_t get_portion_size(void);
void set_portion_size(uint8_t size);
The implementation of the library
// Internal library variables
uint8_t _water_amount = 0;
uint8_t _cooking_time = 0;
static uint8_t _portion_size = 0; // We make sure that other files cannot modify this variable.
// Functions
void set_portion_size(uint8_t portions) {
_portion_size = portions;
}
uint8_t get_portion_size(void) {
return _portion_size;
}
uint8_t add_water(void) {
_water_amount = _portion_size * 0.2;
}
void set_cooking_time(void) {
switch(_selected_pasta) {
case SPAGHETTI:
_cooking_time = 10;
break;
case MACARONI:
_cooking_time = 8;
break;
...
}
}
Now other files can use the library:
// main.c
#include "pasta.h"
int main(void) {
// Set and get the global variable
set_portion_size(5);
uint8_t portions = get_portion_size();
printf("Portion size is: %d\n", portions);
return 0;
}
Using extern keyword¶
Concerning global variables in header files, it is worth mentioning the
extern
keyword. This keyword is used to declare a variable in a header file without defining it (that is, do not allocate space in memory). The extern
keyword tells the compiler that the variable or function is defined elsewhere, typically in a .c file, and allows other files to access it without redefining it.Using extern helps to share global variables across multiple source files while ensuring that the variable is defined only once, preventing linker errors that result from multiple definitions. Usually, the extern variable is defined in the library implementation.
The
pasta.h
file looks like: //pasta.h
#pragma once
#include <stdio.h>
// Function prototypes
uint8_t add_water(void);
uint8_t add_salt(void);
uint8_t is_water_boiling(void);
void stir_pot(void);
void set_cooking_time(void);
uint8_t is_ready(void);
// New enum type for constants, exciting!!
enum pasta_types { SPAGHETTI=1, MACARONI, FUSILLI, PENNE_RIGATE };
extern uint8_t _portion_size;//We indicate that variable would be later declared.
Then the
pasta.c
// pasta.c
#include "pasta.h"
// Define the global variable (allocate memory and initialize it)
//Other source files can now access this variable through the extern declaration in the header file.
uint8_t _portion_size = 0;
uint8_t add_water(void) {
_water_amount = _portion_size * 0.2;
}
void set_cooking_time(void) {
switch(_selected_pasta) {
case SPAGHETTI:
_cooking_time = 10;
break;
case MACARONI:
_cooking_time = 8;
break;
...
}
}
Now other files can use the library:
// main.c
#include "pasta.h"
int main(void) {
//Now I can use the variable, since it is extern in the library, and it is defined in the pasta.c
_portion_size = 5;
uint8_t portions = _portion_size;
printf("Portion size is: %d\n", portions);
return 0;
}
Problems defining variables in .h¶
What would happen if, lead by my intuition, I define the global variable in the header size?
Let's see
Let's see
// pasta.h
#pragma once
#include <stdio.h>
// Define the global variable directly
uint8_t _portion_size = 0;
// Declare function prototypes
void set_portion_size(uint8_t portions);
uint8_t get_portion_size(void);
and then i include the library in both files
// pasta.c
#include "pasta.h"
// main.c
#include "pasta.h"
each file will define its own version of _portion_size. The linker will then throw an error of
multiple definition of
!!!!Constants in header files¶
If a constant is declared in the header file, it can be freely used across multiple code modules. For example, the
math.h
header file contains a constant defined for the value of pi, which should naturally be used throughout the program.// Inside math.h
#define M_PI 3.14159265358979323846
However, if you are working in a Windows environment, using this constant in your program won’t work unless you first define the constant
#define _USE_MATH_DEFINES
.C Standard Library¶
The C standard library contains, first of all, a large number of definitions that support the implementation of (ANSI) standard C across different hardware platforms and operating systems.
The implementation of parts of the standard library typically comes precompiled as binaries (especially in commercial compiler environments), often optimized by the manufacturer. Therefore, it is always a good idea to use ready-made functions whenever possible! Reinventing the wheel, especially in the workplace, won’t earn you bonus points from your boss.
You can find an overview of C's standard libraries on Wikipedia C Standard Library or in textbooks.
Some useful libraries:
- inttypes.h: Useful derived data types
- stdio.h: Reading and writing functions, file handling
- stdlib.h: Type conversions, memory management, system commands
- string.h: String handling
- ctype.h: Character handling
- time.h: Clock and calendar (usually not implemented in embedded systems)
- math.h: Mathematical functions
In embedded systems, parts of the standard library may need to be excluded due to device performance and memory limitations. Typically, for example, the
stdio.h
library is optimized by restricting the precision of floating-point numbers, and the printf
function might not implement floating-point printing.General Information About Libraries¶
As the program grows, it’s a good idea to organize functions into libraries, which will simplify the main program considerably, making it easier to manage and maintain.
Dividing into libraries also facilitates testing and debugging (debugging) in larger programs. Each library can be tested as a module before being integrated into the main program. During runtime error checking, libraries can be inspected separately using a debugger.
Programs can be divided into libraries in several different ways, and the best way often depends on who you ask. Here is one approach:
- Platform-Specific functions and definitions: In embedded programming, platform-specific functions often need to be written, which aren’t useful on other platforms, at least not without significant modification. Such platform-specific functions and definitions should be placed in their own library, so that when compiling the same program for another platform, the source code does not include unnecessary code.
- Application-Specific functions and definitions: In programming, it’s always good to strive for modularity, but it’s very difficult to make all functions as general-purpose as possible, and forcing them to be is not practical. Such application-specific functions and definitions should be placed in their own library.
- Generic functions and definitions: Functions and definitions that you think you could use in another software project for another device should be placed in their own library.
Conclusion¶
In this course, we will use a lot of the standard library, as well as the ready-made libraries provided by our device and our RTOS SDK for programming the device.
Hobbyists also create libraries for different functionalities and embedded components, so Google is your friend here. However, caution is advised because you don’t know what assumptions or other conditions the code in the library may have. Therefore, it’s a good idea to review external code yourself before using it in your program. Additionally, comments in the code may provide valuable information about using the library.
ur C programming language material.
ur C programming language material.
Give feedback on this content
Comments about this material